@streamr/dht 100.0.0-testnet-one.0 → 100.0.0-testnet-one.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. package/dist/src/connection/ConnectionLockRpcRemote.js +1 -25
  2. package/dist/src/connection/ConnectionLockRpcRemote.js.map +1 -1
  3. package/dist/src/connection/ConnectionManager.d.ts +0 -1
  4. package/dist/src/connection/ConnectionManager.js +7 -6
  5. package/dist/src/connection/ConnectionManager.js.map +1 -1
  6. package/dist/src/connection/ConnectorFacade.d.ts +2 -2
  7. package/dist/src/connection/ConnectorFacade.js +2 -3
  8. package/dist/src/connection/ConnectorFacade.js.map +1 -1
  9. package/dist/src/connection/ManagedConnection.d.ts +1 -0
  10. package/dist/src/connection/ManagedConnection.js +11 -1
  11. package/dist/src/connection/ManagedConnection.js.map +1 -1
  12. package/dist/src/connection/connectivityChecker.js +3 -2
  13. package/dist/src/connection/connectivityChecker.js.map +1 -1
  14. package/dist/src/connection/websocket/ClientWebsocket.d.ts +1 -0
  15. package/dist/src/connection/websocket/ClientWebsocket.js +6 -3
  16. package/dist/src/connection/websocket/ClientWebsocket.js.map +1 -1
  17. package/dist/src/connection/websocket/ServerWebsocket.d.ts +4 -0
  18. package/dist/src/connection/websocket/ServerWebsocket.js +32 -21
  19. package/dist/src/connection/websocket/ServerWebsocket.js.map +1 -1
  20. package/dist/src/connection/websocket/WebsocketConnector.d.ts +0 -1
  21. package/dist/src/connection/websocket/WebsocketConnector.js +21 -10
  22. package/dist/src/connection/websocket/WebsocketConnector.js.map +1 -1
  23. package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.d.ts +1 -1
  24. package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.js +8 -11
  25. package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.js.map +1 -1
  26. package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.d.ts +2 -2
  27. package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.js +3 -37
  28. package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.js.map +1 -1
  29. package/dist/src/connection/websocket/WebsocketServer.js +21 -4
  30. package/dist/src/connection/websocket/WebsocketServer.js.map +1 -1
  31. package/dist/src/dht/DhtNode.d.ts +4 -4
  32. package/dist/src/dht/DhtNode.js +31 -20
  33. package/dist/src/dht/DhtNode.js.map +1 -1
  34. package/dist/src/dht/DhtNodeRpcLocal.d.ts +1 -4
  35. package/dist/src/dht/DhtNodeRpcLocal.js +1 -5
  36. package/dist/src/dht/DhtNodeRpcLocal.js.map +1 -1
  37. package/dist/src/dht/PeerManager.d.ts +10 -6
  38. package/dist/src/dht/PeerManager.js +95 -30
  39. package/dist/src/dht/PeerManager.js.map +1 -1
  40. package/dist/src/dht/contact/SortedContactList.d.ts +20 -6
  41. package/dist/src/dht/contact/SortedContactList.js +55 -24
  42. package/dist/src/dht/contact/SortedContactList.js.map +1 -1
  43. package/dist/src/dht/discovery/DiscoverySession.d.ts +3 -5
  44. package/dist/src/dht/discovery/DiscoverySession.js +11 -9
  45. package/dist/src/dht/discovery/DiscoverySession.js.map +1 -1
  46. package/dist/src/dht/discovery/PeerDiscovery.d.ts +4 -2
  47. package/dist/src/dht/discovery/PeerDiscovery.js +17 -16
  48. package/dist/src/dht/discovery/PeerDiscovery.js.map +1 -1
  49. package/dist/src/dht/find/FindSession.js +6 -1
  50. package/dist/src/dht/find/FindSession.js.map +1 -1
  51. package/dist/src/dht/find/Finder.js +6 -1
  52. package/dist/src/dht/find/Finder.js.map +1 -1
  53. package/dist/src/dht/routing/Router.d.ts +1 -1
  54. package/dist/src/dht/routing/Router.js +8 -4
  55. package/dist/src/dht/routing/Router.js.map +1 -1
  56. package/dist/src/dht/routing/RoutingSession.js +8 -1
  57. package/dist/src/dht/routing/RoutingSession.js.map +1 -1
  58. package/dist/src/dht/store/StoreRpcLocal.js +19 -5
  59. package/dist/src/dht/store/StoreRpcLocal.js.map +1 -1
  60. package/dist/src/helpers/PeerID.d.ts +1 -0
  61. package/dist/src/helpers/PeerID.js +7 -2
  62. package/dist/src/helpers/PeerID.js.map +1 -1
  63. package/dist/src/helpers/peerIdFromPeerDescriptor.js +2 -2
  64. package/dist/src/helpers/peerIdFromPeerDescriptor.js.map +1 -1
  65. package/package.json +5 -5
  66. package/src/connection/ConnectionLockRpcRemote.ts +1 -2
  67. package/src/connection/ConnectionManager.ts +16 -17
  68. package/src/connection/ConnectorFacade.ts +1 -4
  69. package/src/connection/ManagedConnection.ts +12 -1
  70. package/src/connection/connectivityChecker.ts +3 -2
  71. package/src/connection/websocket/ClientWebsocket.ts +5 -2
  72. package/src/connection/websocket/ServerWebsocket.ts +40 -25
  73. package/src/connection/websocket/WebsocketConnector.ts +23 -12
  74. package/src/connection/websocket/WebsocketConnectorRpcLocal.ts +9 -11
  75. package/src/connection/websocket/WebsocketConnectorRpcRemote.ts +5 -14
  76. package/src/connection/websocket/WebsocketServer.ts +20 -5
  77. package/src/dht/DhtNode.ts +32 -24
  78. package/src/dht/DhtNodeRpcLocal.ts +2 -9
  79. package/src/dht/PeerManager.ts +110 -36
  80. package/src/dht/contact/SortedContactList.ts +87 -44
  81. package/src/dht/discovery/DiscoverySession.ts +15 -14
  82. package/src/dht/discovery/PeerDiscovery.ts +37 -22
  83. package/src/dht/find/FindSession.ts +6 -1
  84. package/src/dht/find/Finder.ts +6 -7
  85. package/src/dht/routing/Router.ts +8 -4
  86. package/src/dht/routing/RoutingSession.ts +8 -8
  87. package/src/dht/store/StoreRpcLocal.ts +19 -7
  88. package/src/helpers/PeerID.ts +6 -2
  89. package/src/helpers/peerIdFromPeerDescriptor.ts +4 -4
  90. package/test/benchmark/Find.test.ts +1 -1
  91. package/test/benchmark/KademliaCorrectness.test.ts +1 -1
  92. package/test/benchmark/SortedContactListBenchmark.test.ts +150 -0
  93. package/test/benchmark/WebsocketServerMemoryLeak.test.ts +41 -0
  94. package/test/benchmark/kademlia-simulation/SimulationNode.ts +6 -1
  95. package/test/end-to-end/Layer0.test.ts +4 -4
  96. package/test/end-to-end/Layer0MixedConnectionTypes.test.ts +10 -10
  97. package/test/end-to-end/Layer0Webrtc-Layer1.test.ts +4 -4
  98. package/test/end-to-end/Layer1-Scale-WebSocket.test.ts +2 -2
  99. package/test/end-to-end/Layer1-Scale-Webrtc.test.ts +2 -2
  100. package/test/end-to-end/RecoveryFromFailedAutoCertification.test.ts +1 -1
  101. package/test/end-to-end/memory-leak.test.ts +1 -0
  102. package/test/integration/DhtJoinPeerDiscovery.test.ts +2 -2
  103. package/test/integration/Layer1-scale.test.ts +1 -1
  104. package/test/integration/Mock-Layer1-Layer0.test.ts +15 -15
  105. package/test/integration/MultipleEntryPointJoining.test.ts +7 -7
  106. package/test/integration/ReplicateData.test.ts +6 -1
  107. package/test/integration/SimultaneousConnections.test.ts +81 -49
  108. package/test/integration/Store.test.ts +1 -1
  109. package/test/integration/StoreOnDhtWithTwoNodes.test.ts +1 -1
  110. package/test/integration/WebrtcConnectionManagement.test.ts +29 -0
  111. package/test/integration/WebsocketConnectionManagement.test.ts +65 -4
  112. package/test/integration/WebsocketConnectorRpc.test.ts +3 -5
  113. package/test/unit/SortedContactList.test.ts +15 -10
@@ -1,12 +1,11 @@
1
1
  import { Logger, runAndWaitForEvents3 } from '@streamr/utils'
2
2
  import EventEmitter from 'eventemitter3'
3
3
  import { v4 } from 'uuid'
4
- import { PeerID } from '../../helpers/PeerID'
4
+ import { PeerID, PeerIDKey } from '../../helpers/PeerID'
5
5
  import { PeerDescriptor } from '../../proto/packages/dht/protos/DhtRpc'
6
- import { SortedContactList } from '../contact/SortedContactList'
6
+ import { PeerManager, getDistance } from '../PeerManager'
7
7
  import { DhtNodeRpcRemote } from '../DhtNodeRpcRemote'
8
8
  import { getNodeIdFromPeerDescriptor } from '../../helpers/peerIdFromPeerDescriptor'
9
- import { PeerManager } from '../PeerManager'
10
9
 
11
10
  const logger = new Logger(module)
12
11
 
@@ -16,10 +15,11 @@ interface DiscoverySessionEvents {
16
15
 
17
16
  interface DiscoverySessionConfig {
18
17
  targetId: Uint8Array
19
- localPeerDescriptor: PeerDescriptor
20
18
  parallelism: number
21
19
  noProgressLimit: number
22
20
  peerManager: PeerManager
21
+ // Note that contacted peers will be mutated by the DiscoverySession or other parallel sessions
22
+ contactedPeers: Set<PeerIDKey>
23
23
  }
24
24
 
25
25
  export class DiscoverySession {
@@ -49,7 +49,7 @@ export class DiscoverySession {
49
49
  }
50
50
  logger.trace(`Getting closest peers from contact: ${getNodeIdFromPeerDescriptor(contact.getPeerDescriptor())}`)
51
51
  this.outgoingClosestPeersRequestsCounter++
52
- this.config.peerManager.neighborList!.setContacted(contact.getPeerId())
52
+ this.config.contactedPeers.add(contact.getPeerId().toKey())
53
53
  const returnedContacts = await contact.getClosestPeers(this.config.targetId)
54
54
  this.config.peerManager.handlePeerActive(contact.getPeerId())
55
55
  return returnedContacts
@@ -60,9 +60,12 @@ export class DiscoverySession {
60
60
  return
61
61
  }
62
62
  this.ongoingClosestPeersRequests.delete(peerId.toKey())
63
- const oldClosestContact = this.config.peerManager.neighborList!.getClosestContactId()
63
+ const oldClosestNeighbor = this.config.peerManager.getClosestNeighborsTo(this.config.targetId, 1)[0]
64
+ const oldClosestDistance = getDistance(this.config.targetId, oldClosestNeighbor.getPeerId().value)
64
65
  this.addNewContacts(contacts)
65
- if (this.config.peerManager.neighborList!.getClosestContactId().equals(oldClosestContact)) {
66
+ const newClosestNeighbor = this.config.peerManager.getClosestNeighborsTo(this.config.targetId, 1)[0]
67
+ const newClosestDistance = getDistance(this.config.targetId, newClosestNeighbor.getPeerId().value)
68
+ if (newClosestDistance >= oldClosestDistance) {
66
69
  this.noProgressCounter++
67
70
  } else {
68
71
  this.noProgressCounter = 0
@@ -81,7 +84,7 @@ export class DiscoverySession {
81
84
  if (this.stopped) {
82
85
  return
83
86
  }
84
- const uncontacted = this.config.peerManager.neighborList!.getUncontactedContacts(this.config.parallelism)
87
+ const uncontacted = this.config.peerManager.getClosestContactsTo(this.config.targetId, this.config.parallelism, this.config.contactedPeers)
85
88
  if (uncontacted.length === 0 || this.noProgressCounter >= this.config.noProgressLimit) {
86
89
  this.emitter.emit('discoveryCompleted')
87
90
  this.stopped = true
@@ -103,18 +106,16 @@ export class DiscoverySession {
103
106
  }
104
107
  }
105
108
 
106
- public async findClosestNodes(timeout: number): Promise<SortedContactList<DhtNodeRpcRemote>> {
107
- if (this.config.peerManager.neighborList!.getUncontactedContacts(this.config.parallelism).length === 0) {
108
- logger.trace('getUncontactedContacts length was 0 in beginning of discovery, neighborList.size: '
109
- + this.config.peerManager.neighborList!.getSize())
110
- return this.config.peerManager.neighborList!
109
+ public async findClosestNodes(timeout: number): Promise<void> {
110
+ if (this.config.peerManager.getNumberOfContacts(this.config.contactedPeers) === 0) {
111
+ return
111
112
  }
113
+ // TODO add abortController and signal it in stop()
112
114
  await runAndWaitForEvents3<DiscoverySessionEvents>(
113
115
  [this.findMoreContacts.bind(this)],
114
116
  [[this.emitter, 'discoveryCompleted']],
115
117
  timeout
116
118
  )
117
- return this.config.peerManager.neighborList!
118
119
  }
119
120
 
120
121
  public stop(): void {
@@ -4,9 +4,10 @@ import { areEqualPeerDescriptors, getNodeIdFromPeerDescriptor, peerIdFromPeerDes
4
4
  import { PeerDescriptor } from '../../proto/packages/dht/protos/DhtRpc'
5
5
  import { Logger, scheduleAtInterval, setAbortableTimeout } from '@streamr/utils'
6
6
  import { ConnectionManager } from '../../connection/ConnectionManager'
7
+ import { PeerManager } from '../PeerManager'
7
8
  import { createRandomNodeId } from '../../helpers/nodeId'
8
9
  import { ServiceID } from '../../types/ServiceID'
9
- import { PeerManager } from '../PeerManager'
10
+ import { PeerIDKey } from '../../helpers/PeerID'
10
11
 
11
12
  interface PeerDiscoveryConfig {
12
13
  localPeerDescriptor: PeerDescriptor
@@ -36,7 +37,27 @@ export class PeerDiscovery {
36
37
  this.abortController = new AbortController()
37
38
  }
38
39
 
39
- async joinDht(entryPointDescriptor: PeerDescriptor, doAdditionalRandomPeerDiscovery = true, retry = true): Promise<void> {
40
+ async joinDht(
41
+ entryPoints: PeerDescriptor[],
42
+ doAdditionalRandomPeerDiscovery = true,
43
+ retry = true
44
+ ): Promise<void> {
45
+ const contactedPeers = new Set<PeerIDKey>()
46
+ await Promise.all(entryPoints.map((entryPoint) => this.joinThroughEntryPoint(
47
+ entryPoint,
48
+ contactedPeers,
49
+ doAdditionalRandomPeerDiscovery,
50
+ retry
51
+ )))
52
+ }
53
+
54
+ async joinThroughEntryPoint(
55
+ entryPointDescriptor: PeerDescriptor,
56
+ // Note that this set is mutated by DiscoverySession
57
+ contactedPeers: Set<PeerIDKey>,
58
+ doAdditionalRandomPeerDiscovery = true,
59
+ retry = true
60
+ ): Promise<void> {
40
61
  if (this.isStopped()) {
41
62
  return
42
63
  }
@@ -51,24 +72,22 @@ export class PeerDiscovery {
51
72
  this.config.connectionManager?.lockConnection(entryPointDescriptor, `${this.config.serviceId}::joinDht`)
52
73
  this.config.peerManager.handleNewPeers([entryPointDescriptor])
53
74
  const targetId = peerIdFromPeerDescriptor(this.config.localPeerDescriptor).value
54
- const closest = this.config.peerManager.bucket!.closest(targetId, this.config.peerDiscoveryQueryBatchSize)
55
- this.config.peerManager.neighborList!.addContacts(closest)
56
- const sessions = [this.createSession(targetId)]
75
+ const sessions = [this.createSession(targetId, contactedPeers)]
57
76
  if (doAdditionalRandomPeerDiscovery) {
58
- sessions.push(this.createSession(createRandomNodeId()))
77
+ sessions.push(this.createSession(createRandomNodeId(), contactedPeers))
59
78
  }
60
79
  await this.runSessions(sessions, entryPointDescriptor, retry)
61
80
  this.config.connectionManager?.unlockConnection(entryPointDescriptor, `${this.config.serviceId}::joinDht`)
62
81
 
63
82
  }
64
83
 
65
- private createSession(targetId: Uint8Array): DiscoverySession {
84
+ private createSession(targetId: Uint8Array, contactedPeers: Set<PeerIDKey>): DiscoverySession {
66
85
  const sessionOptions = {
67
86
  targetId,
68
- localPeerDescriptor: this.config.localPeerDescriptor,
69
87
  parallelism: this.config.parallelism,
70
88
  noProgressLimit: this.config.joinNoProgressLimit,
71
- peerManager: this.config.peerManager
89
+ peerManager: this.config.peerManager,
90
+ contactedPeers
72
91
  }
73
92
  return new DiscoverySession(sessionOptions)
74
93
  }
@@ -83,7 +102,7 @@ export class PeerDiscovery {
83
102
  logger.debug(`DHT join on ${this.config.serviceId} timed out`)
84
103
  } finally {
85
104
  if (!this.isStopped()) {
86
- if (this.config.peerManager.bucket!.count() === 0) {
105
+ if (this.config.peerManager.getNumberOfNeighbors() === 0) {
87
106
  if (retry) {
88
107
  // TODO should we catch possible promise rejection?
89
108
  setAbortableTimeout(() => this.rejoinDht(entryPointDescriptor), 1000, this.abortController.signal)
@@ -103,8 +122,7 @@ export class PeerDiscovery {
103
122
  logger.debug(`Rejoining DHT ${this.config.serviceId}`)
104
123
  this.rejoinOngoing = true
105
124
  try {
106
- this.config.peerManager.neighborList!.clear()
107
- await this.joinDht(entryPoint)
125
+ await this.joinThroughEntryPoint(entryPoint, new Set())
108
126
  logger.debug(`Rejoined DHT successfully ${this.config.serviceId}!`)
109
127
  } catch (err) {
110
128
  logger.warn(`Rejoining DHT ${this.config.serviceId} failed`)
@@ -128,16 +146,13 @@ export class PeerDiscovery {
128
146
  if (this.isStopped()) {
129
147
  return
130
148
  }
131
- const nodes = this.config.peerManager.bucket!.closest(
132
- peerIdFromPeerDescriptor(this.config.localPeerDescriptor).value,
133
- this.config.parallelism
134
- )
135
- await Promise.allSettled(nodes.map(async (peer: DhtNodeRpcRemote) => {
136
- const contacts = await peer.getClosestPeers(this.config.localPeerDescriptor.nodeId)
137
- contacts.forEach((contact) => {
138
- this.config.peerManager.handleNewPeers([contact])
149
+ const nodes = this.config.peerManager.getClosestNeighborsTo(this.config.localPeerDescriptor.nodeId, this.config.parallelism)
150
+ await Promise.allSettled(
151
+ nodes.map(async (peer: DhtNodeRpcRemote) => {
152
+ const contacts = await peer.getClosestPeers(this.config.localPeerDescriptor.nodeId!)
153
+ this.config.peerManager.handleNewPeers(contacts)
139
154
  })
140
- }))
155
+ )
141
156
  }
142
157
 
143
158
  public isJoinOngoing(): boolean {
@@ -158,7 +173,7 @@ export class PeerDiscovery {
158
173
  clearTimeout(this.rejoinTimeoutRef)
159
174
  this.rejoinTimeoutRef = undefined
160
175
  }
161
- this.ongoingDiscoverySessions.forEach((session, _id) => {
176
+ this.ongoingDiscoverySessions.forEach((session) => {
162
177
  session.stop()
163
178
  })
164
179
  }
@@ -46,7 +46,12 @@ export class FindSession extends EventEmitter<FindSessionEvents> {
46
46
  this.nodeIdToFind = config.nodeIdToFind
47
47
  this.localPeerId = config.localPeerId
48
48
  this.waitedRoutingPathCompletions = config.waitedRoutingPathCompletions
49
- this.results = new SortedContactList(PeerID.fromValue(this.nodeIdToFind), 10, undefined, true)
49
+ this.results = new SortedContactList({
50
+ referenceId: PeerID.fromValue(this.nodeIdToFind),
51
+ maxSize: 10, // TODO use config option or named constant?
52
+ allowToContainReferenceId: true,
53
+ emitEvents: false
54
+ })
50
55
  this.action = config.action
51
56
  this.rpcCommunicator = new ListeningRpcCommunicator(this.serviceId, this.transport, {
52
57
  rpcRequestTimeout: 15000
@@ -275,13 +275,12 @@ export class Finder implements IFinder {
275
275
 
276
276
  private getClosestConnections(nodeId: Uint8Array, limit: number): PeerDescriptor[] {
277
277
  const connectedPeers = Array.from(this.connections.values())
278
- const closestPeers = new SortedContactList<DhtNodeRpcRemote>(
279
- PeerID.fromValue(nodeId),
280
- limit,
281
- undefined,
282
- true,
283
- undefined
284
- )
278
+ const closestPeers = new SortedContactList<DhtNodeRpcRemote>({
279
+ referenceId: PeerID.fromValue(nodeId),
280
+ maxSize: limit,
281
+ allowToContainReferenceId: true,
282
+ emitEvents: false
283
+ })
285
284
  closestPeers.addContacts(connectedPeers)
286
285
  return closestPeers.getClosestContacts(limit).map((peer) => peer.getPeerDescriptor())
287
286
  }
@@ -134,13 +134,13 @@ export class Router implements IRouter {
134
134
  }
135
135
  }
136
136
 
137
- public doRouteMessage(routedMessage: RouteMessageWrapper, mode = RoutingMode.ROUTE): RouteMessageAck {
137
+ public doRouteMessage(routedMessage: RouteMessageWrapper, mode = RoutingMode.ROUTE, excludedPeer?: PeerDescriptor): RouteMessageAck {
138
138
  if (this.stopped) {
139
139
  return createRouteMessageAck(routedMessage, RouteMessageError.STOPPED)
140
140
  }
141
141
  logger.trace(`Routing message ${routedMessage.requestId} from ${getNodeIdFromPeerDescriptor(routedMessage.sourcePeer!)} `
142
142
  + `to ${getNodeIdFromPeerDescriptor(routedMessage.destinationPeer!)}`)
143
- const session = this.createRoutingSession(routedMessage, mode)
143
+ const session = this.createRoutingSession(routedMessage, mode, excludedPeer)
144
144
  const contacts = session.updateAndGetRoutablePeers()
145
145
  if (contacts.length > 0) {
146
146
  this.addRoutingSession(session)
@@ -173,7 +173,11 @@ export class Router implements IRouter {
173
173
  }
174
174
  }
175
175
 
176
- private createRoutingSession(routedMessage: RouteMessageWrapper, mode: RoutingMode): RoutingSession {
176
+ private createRoutingSession(routedMessage: RouteMessageWrapper, mode: RoutingMode, excludedPeer?: PeerDescriptor): RoutingSession {
177
+ const excludedPeers = routedMessage.routingPath.map((descriptor) => peerIdFromPeerDescriptor(descriptor))
178
+ if (excludedPeer) {
179
+ excludedPeers.push(peerIdFromPeerDescriptor(excludedPeer))
180
+ }
177
181
  logger.trace('routing session created with connections: ' + this.connections.size)
178
182
  return new RoutingSession(
179
183
  this.rpcCommunicator,
@@ -182,7 +186,7 @@ export class Router implements IRouter {
182
186
  this.connections,
183
187
  areEqualPeerDescriptors(this.localPeerDescriptor, routedMessage.sourcePeer!) ? 2 : 1,
184
188
  mode,
185
- routedMessage.routingPath.map((descriptor) => peerIdFromPeerDescriptor(descriptor))
189
+ excludedPeers
186
190
  )
187
191
  }
188
192
 
@@ -98,14 +98,14 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
98
98
  this.mode = mode
99
99
  const previousPeer = getPreviousPeer(messageToRoute)
100
100
  const previousId = previousPeer ? PeerID.fromValue(previousPeer.nodeId) : undefined
101
- this.contactList = new SortedContactList(
102
- PeerID.fromValue(this.messageToRoute.destinationPeer!.nodeId),
103
- 10000,
104
- undefined,
105
- true,
106
- previousId,
107
- excludedPeerIDs
108
- )
101
+ this.contactList = new SortedContactList({
102
+ referenceId: PeerID.fromValue(this.messageToRoute.destinationPeer!.nodeId),
103
+ maxSize: 10000, // TODO use config option or named constant?
104
+ allowToContainReferenceId: true,
105
+ peerIdDistanceLimit: previousId,
106
+ excludedPeerIDs: excludedPeerIDs,
107
+ emitEvents: false
108
+ })
109
109
  }
110
110
 
111
111
  private onRequestFailed(peerId: PeerID) {
@@ -93,8 +93,12 @@ export class StoreRpcLocal implements IStoreRpc {
93
93
  const localPeerId = PeerID.fromValue(this.localPeerDescriptor.nodeId)
94
94
 
95
95
  const closestToData = this.getNodesClosestToIdFromBucket(dataEntry.key, 10)
96
-
97
- const sortedList = new SortedContactList<Contact>(dataId, 20, undefined, true)
96
+ const sortedList = new SortedContactList<Contact>({
97
+ referenceId: PeerID.fromValue(dataEntry.key),
98
+ maxSize: 20, // TODO use config option or named constant?
99
+ allowToContainReferenceId: true,
100
+ emitEvents: false
101
+ })
98
102
  sortedList.addContact(new Contact(this.localPeerDescriptor))
99
103
 
100
104
  closestToData.forEach((con) => {
@@ -108,6 +112,7 @@ export class StoreRpcLocal implements IStoreRpc {
108
112
  return false
109
113
  }
110
114
 
115
+ this.localDataStore.setStale(dataId, dataEntry.creator!, false)
111
116
  const newPeerId = PeerID.fromValue(newNode.nodeId)
112
117
  sortedList.addContact(new Contact(newNode))
113
118
 
@@ -125,10 +130,8 @@ export class StoreRpcLocal implements IStoreRpc {
125
130
  // do replicate data to it
126
131
 
127
132
  if (index < this.redundancyFactor) {
128
- this.localDataStore.setStale(dataId, dataEntry.creator!, false)
129
133
  return true
130
134
  } else {
131
- this.localDataStore.setStale(dataId, dataEntry.creator!, true)
132
135
  return false
133
136
  }
134
137
  }
@@ -201,7 +204,12 @@ export class StoreRpcLocal implements IStoreRpc {
201
204
  private selfIsOneOfClosestPeers(dataId: Uint8Array): boolean {
202
205
  const localPeerId = PeerID.fromValue(this.localPeerDescriptor.nodeId)
203
206
  const closestPeers = this.getNodesClosestToIdFromBucket(dataId, this.redundancyFactor)
204
- const sortedList = new SortedContactList<Contact>(localPeerId, this.redundancyFactor, undefined, true)
207
+ const sortedList = new SortedContactList<Contact>({
208
+ referenceId: localPeerId,
209
+ maxSize: this.redundancyFactor,
210
+ allowToContainReferenceId: true,
211
+ emitEvents: false
212
+ })
205
213
  sortedList.addContact(new Contact(this.localPeerDescriptor))
206
214
  closestPeers.forEach((con) => sortedList.addContact(new Contact(con.getPeerDescriptor())))
207
215
  return sortedList.getClosestContacts().some((node) => node.getPeerId().equals(localPeerId))
@@ -282,8 +290,12 @@ export class StoreRpcLocal implements IStoreRpc {
282
290
  const dataId = PeerID.fromValue(dataEntry.key)
283
291
  const incomingPeerId = PeerID.fromValue(incomingPeer.nodeId)
284
292
  const closestToData = this.getNodesClosestToIdFromBucket(dataEntry.key, 10)
285
-
286
- const sortedList = new SortedContactList<Contact>(dataId, this.redundancyFactor, undefined, true)
293
+ const sortedList = new SortedContactList<Contact>({
294
+ referenceId: dataId,
295
+ maxSize: this.redundancyFactor,
296
+ allowToContainReferenceId: true,
297
+ emitEvents: false
298
+ })
287
299
  sortedList.addContact(new Contact(this.localPeerDescriptor))
288
300
 
289
301
  closestToData.forEach((con) => {
@@ -1,10 +1,14 @@
1
- import { BrandedString } from '@streamr/utils'
1
+ import { BrandedString, binaryToHex } from '@streamr/utils'
2
2
  import { UUID } from './UUID'
3
3
  import { IllegalArguments } from './errors'
4
4
  import crypto from 'crypto'
5
5
 
6
6
  export type PeerIDKey = BrandedString<'PeerIDKey'>
7
7
 
8
+ export const createPeerIDKey = (nodeId: Uint8Array): PeerIDKey => {
9
+ return binaryToHex(nodeId) as PeerIDKey
10
+ }
11
+
8
12
  export class PeerID {
9
13
  // avoid creating a new instance for every operation
10
14
  private static readonly textEncoder = new TextEncoder()
@@ -30,7 +34,7 @@ export class PeerID {
30
34
  throw new IllegalArguments('Constructor of PeerID must be given either ip, value or stringValue')
31
35
  }
32
36
 
33
- this.key = Buffer.from(this.data).toString('hex') as PeerIDKey
37
+ this.key = createPeerIDKey(this.data)
34
38
  }
35
39
 
36
40
  static fromIp(ip: string): PeerID {
@@ -1,6 +1,6 @@
1
- import { binaryToHex } from '@streamr/utils'
1
+ import { areEqualBinaries, binaryToHex } from '@streamr/utils'
2
2
  import { PeerDescriptor } from '../proto/packages/dht/protos/DhtRpc'
3
- import { PeerID, PeerIDKey } from './PeerID'
3
+ import { PeerID, PeerIDKey, createPeerIDKey } from './PeerID'
4
4
 
5
5
  export const peerIdFromPeerDescriptor = (peerDescriptor: PeerDescriptor): PeerID => {
6
6
  return PeerID.fromValue(peerDescriptor.nodeId)
@@ -12,9 +12,9 @@ export const getNodeIdFromPeerDescriptor = (peerDescriptor: PeerDescriptor): str
12
12
  }
13
13
 
14
14
  export const keyFromPeerDescriptor = (peerDescriptor: PeerDescriptor): PeerIDKey => {
15
- return PeerID.fromValue(peerDescriptor.nodeId).toKey()
15
+ return createPeerIDKey(peerDescriptor.nodeId)
16
16
  }
17
17
 
18
18
  export const areEqualPeerDescriptors = (peerDescriptor1: PeerDescriptor, peerDescriptor2: PeerDescriptor): boolean => {
19
- return peerIdFromPeerDescriptor(peerDescriptor1).equals(peerIdFromPeerDescriptor(peerDescriptor2))
19
+ return areEqualBinaries(peerDescriptor1.nodeId, peerDescriptor2.nodeId)
20
20
  }
@@ -71,7 +71,7 @@ describe('Find correctness', () => {
71
71
  logger.info('waiting over')
72
72
 
73
73
  nodes.forEach((node) => logger.info(getNodeIdFromPeerDescriptor(node.getLocalPeerDescriptor()) + ': connections:' +
74
- node.getNumberOfConnections() + ', kbucket: ' + node.getBucketSize()
74
+ node.getNumberOfConnections() + ', kbucket: ' + node.getNumberOfNeighbors()
75
75
  + ', localLocked: ' + node.getNumberOfLocalLockedConnections()
76
76
  + ', remoteLocked: ' + node.getNumberOfRemoteLockedConnections()
77
77
  + ', weakLocked: ' + node.getNumberOfWeakLockedConnections()))
@@ -102,7 +102,7 @@ describe('Kademlia correctness', () => {
102
102
  }
103
103
 
104
104
  if (i > 0) {
105
- sumKbucketSize += nodes[i].getBucketSize()
105
+ sumKbucketSize += nodes[i].getNumberOfNeighbors()
106
106
  sumCorrectNeighbors += correctNeighbors
107
107
  }
108
108
  }
@@ -0,0 +1,150 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import KBucket from 'k-bucket'
4
+ import { SortedContactList } from '../../src/dht/contact/SortedContactList'
5
+ import { PeerID } from '../../src/helpers/PeerID'
6
+ import crypto from 'crypto'
7
+
8
+ const NUM_ADDS = 1000
9
+ interface Item {
10
+ id: Uint8Array
11
+ vectorClock: number
12
+ getPeerId: () => PeerID
13
+ }
14
+
15
+ const createRandomItem = (index: number): Item => {
16
+ const rand = new Uint8Array(crypto.randomBytes(20))
17
+ return {
18
+ getPeerId: () => PeerID.fromValue(rand),
19
+ id: rand,
20
+ vectorClock: index
21
+ }
22
+ }
23
+
24
+ function shuffleArray<T>(array: T[]): T[] {
25
+ for (let i = array.length - 1; i > 0; i--) {
26
+ const j = Math.floor(Math.random() * (i + 1));
27
+ [array[i], array[j]] = [array[j], array[i]]
28
+ }
29
+ return array
30
+ }
31
+
32
+ describe('SortedContactListBenchmark', () => {
33
+
34
+ it('adds ' + NUM_ADDS + ' random peerIDs', async () => {
35
+ const randomIds = []
36
+ for (let i = 0; i < NUM_ADDS; i++) {
37
+ randomIds.push(createRandomItem(i))
38
+ }
39
+ const list = new SortedContactList({
40
+ referenceId: PeerID.fromValue(crypto.randomBytes(20)),
41
+ allowToContainReferenceId: true,
42
+ emitEvents: true
43
+ })
44
+
45
+ console.time('SortedContactList.addContact() with emitEvents=true')
46
+ for (let i = 0; i < NUM_ADDS; i++) {
47
+ list.addContact(randomIds[i])
48
+ }
49
+ console.timeEnd('SortedContactList.addContact() with emitEvents=true')
50
+
51
+ const list2 = new SortedContactList({
52
+ referenceId: PeerID.fromValue(crypto.randomBytes(20)),
53
+ allowToContainReferenceId: true,
54
+ emitEvents: false
55
+ })
56
+
57
+ console.time('SortedContactList.addContact() with emitEvents=false')
58
+ for (let i = 0; i < NUM_ADDS; i++) {
59
+ list2.addContact(randomIds[i])
60
+ }
61
+ console.timeEnd('SortedContactList.addContact() with emitEvents=false')
62
+
63
+ const kBucket = new KBucket<Item>({ localNodeId: crypto.randomBytes(20) })
64
+ console.time('KBucket.add()')
65
+ for (let i = 0; i < NUM_ADDS; i++) {
66
+ kBucket.add(randomIds[i])
67
+ }
68
+ console.timeEnd('KBucket.add()')
69
+
70
+ console.time('kBucket toArray()')
71
+
72
+ for (let i = 0; i < NUM_ADDS; i++) {
73
+ kBucket.toArray()
74
+ }
75
+ console.timeEnd('kBucket toArray()')
76
+
77
+ console.time('kBucket closest()')
78
+ for (let i = 0; i < NUM_ADDS; i++) {
79
+ kBucket.closest(crypto.randomBytes(20), 20)
80
+ }
81
+ console.timeEnd('kBucket closest()')
82
+
83
+ console.time('SortedContactList.getClosestContacts() with emitEvents=true')
84
+ for (let i = 0; i < NUM_ADDS; i++) {
85
+ const closest = new SortedContactList<Item>({
86
+ referenceId: PeerID.fromValue(crypto.randomBytes(20)),
87
+ allowToContainReferenceId: true,
88
+ emitEvents: true
89
+ })
90
+
91
+ const arrayFromBucket = kBucket.toArray()
92
+ arrayFromBucket.map((contact) => closest.addContact(contact))
93
+ closest.getClosestContacts(20)
94
+ }
95
+ console.timeEnd('SortedContactList.getClosestContacts() with emitEvents=true')
96
+
97
+ console.time('SortedContactList.getClosestContacts() with emitEvents=false')
98
+ for (let i = 0; i < NUM_ADDS; i++) {
99
+ const closest = new SortedContactList<Item>({
100
+ referenceId: PeerID.fromValue(crypto.randomBytes(20)),
101
+ allowToContainReferenceId: true,
102
+ emitEvents: false
103
+ })
104
+
105
+ const arrayFromBucket = kBucket.toArray()
106
+ arrayFromBucket.map((contact) => closest.addContact(contact))
107
+ closest.getClosestContacts(20)
108
+ }
109
+ console.timeEnd('SortedContactList.getClosestContacts() with emitEvents=false')
110
+
111
+ console.time('SortedContactList.getClosestContacts() with emitEvents=false and lodash')
112
+ for (let i = 0; i < NUM_ADDS; i++) {
113
+ const closest = new SortedContactList<Item>({
114
+ referenceId: PeerID.fromValue(crypto.randomBytes(20)),
115
+ allowToContainReferenceId: true,
116
+ emitEvents: false
117
+ })
118
+
119
+ const arrayFromBucket = kBucket.toArray()
120
+ arrayFromBucket.map((contact) => closest.addContact(contact))
121
+ closest.getClosestContacts(20)
122
+ }
123
+ console.timeEnd('SortedContactList.getClosestContacts() with emitEvents=false and lodash')
124
+
125
+ console.time('SortedContactList.getClosestContacts() with emitEvents=false and addContacts()')
126
+ for (let i = 0; i < NUM_ADDS; i++) {
127
+ const closest = new SortedContactList<Item>({
128
+ referenceId: PeerID.fromValue(crypto.randomBytes(20)),
129
+ allowToContainReferenceId: true,
130
+ emitEvents: false
131
+ })
132
+
133
+ const arrayFromBucket = kBucket.toArray()
134
+ closest.addContacts(arrayFromBucket)
135
+ closest.getClosestContacts(20)
136
+ }
137
+ console.timeEnd('SortedContactList.getClosestContacts() with emitEvents=false and addContacts()')
138
+
139
+ const shuffled = shuffleArray(kBucket.toArray())
140
+ console.time('kbucket add and closest')
141
+ for (let i = 0; i < NUM_ADDS; i++) {
142
+ const bucket2 = new KBucket<Item>({ localNodeId: crypto.randomBytes(20) })
143
+
144
+ shuffled.map((contact) => bucket2.add(contact))
145
+ bucket2.closest(crypto.randomBytes(20), 20)
146
+ }
147
+ console.timeEnd('kbucket add and closest')
148
+
149
+ })
150
+ })
@@ -0,0 +1,41 @@
1
+ /* eslint-disable no-console */
2
+
3
+ import { wait } from '@streamr/utils'
4
+ import { WebsocketServer } from '../../src/connection/websocket/WebsocketServer'
5
+ import { ClientWebsocket } from '../../src/exports'
6
+
7
+ // This 'test' is meant to be run manually using the following command:
8
+ // node --inspect ../../../../node_modules/.bin/jest WebsocketServerMemoryLeak.test.ts
9
+ // while wathing for memory leaks in Chrome DevTools
10
+
11
+ describe('WebsocketServermemoryLeak', () => {
12
+
13
+ it('Accepts and detroys connections', async () => {
14
+ const server = new WebsocketServer({
15
+ portRange: { min: 19792, max: 19792 },
16
+ enableTls: false
17
+ })
18
+
19
+ server.on('connected', (connection) => {
20
+ console.log('ServerWebsocket connected')
21
+ connection.destroy()
22
+ console.log('ServerWebsocket destroyed')
23
+ })
24
+
25
+ const port = await server.start()
26
+ expect(port).toEqual(19792)
27
+
28
+ for (let i = 0; i < 10000; i++) {
29
+ const clientWebsocket: ClientWebsocket = new ClientWebsocket()
30
+ clientWebsocket.on('connected', () => {
31
+ console.log('clientWebsocket connected ' + i)
32
+ })
33
+
34
+ clientWebsocket.connect(`ws://127.0.0.1:${port}`)
35
+ i++
36
+ await wait(3000)
37
+ }
38
+
39
+ await server.stop()
40
+ }, 120000000)
41
+ })
@@ -26,7 +26,12 @@ export class SimulationNode {
26
26
  numberOfNodesPerKBucket: this.numberOfNodesPerKBucket
27
27
  })
28
28
 
29
- this.neighborList = new SortedContactList(this.ownId, 1000)
29
+ this.neighborList = new SortedContactList({
30
+ referenceId: this.ownId,
31
+ maxSize: 1000,
32
+ allowToContainReferenceId: false,
33
+ emitEvents: false
34
+ })
30
35
  }
31
36
 
32
37
  // For simulation use
@@ -71,9 +71,9 @@ describe('Layer0', () => {
71
71
  node4.joinDht([epPeerDescriptor])
72
72
  ])
73
73
 
74
- expect(node1.getBucketSize()).toBeGreaterThanOrEqual(2)
75
- expect(node2.getBucketSize()).toBeGreaterThanOrEqual(2)
76
- expect(node3.getBucketSize()).toBeGreaterThanOrEqual(2)
77
- expect(node4.getBucketSize()).toBeGreaterThanOrEqual(2)
74
+ expect(node1.getNumberOfNeighbors()).toBeGreaterThanOrEqual(2)
75
+ expect(node2.getNumberOfNeighbors()).toBeGreaterThanOrEqual(2)
76
+ expect(node3.getNumberOfNeighbors()).toBeGreaterThanOrEqual(2)
77
+ expect(node4.getNumberOfNeighbors()).toBeGreaterThanOrEqual(2)
78
78
  }, 10000)
79
79
  })