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

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 (107) 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 +1 -2
  8. package/dist/src/connection/ConnectorFacade.js.map +1 -1
  9. package/dist/src/connection/connectivityChecker.js +3 -2
  10. package/dist/src/connection/connectivityChecker.js.map +1 -1
  11. package/dist/src/connection/websocket/ClientWebsocket.d.ts +1 -0
  12. package/dist/src/connection/websocket/ClientWebsocket.js +6 -3
  13. package/dist/src/connection/websocket/ClientWebsocket.js.map +1 -1
  14. package/dist/src/connection/websocket/ServerWebsocket.d.ts +4 -0
  15. package/dist/src/connection/websocket/ServerWebsocket.js +32 -21
  16. package/dist/src/connection/websocket/ServerWebsocket.js.map +1 -1
  17. package/dist/src/connection/websocket/WebsocketConnector.d.ts +0 -1
  18. package/dist/src/connection/websocket/WebsocketConnector.js +21 -10
  19. package/dist/src/connection/websocket/WebsocketConnector.js.map +1 -1
  20. package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.d.ts +1 -1
  21. package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.js +8 -11
  22. package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.js.map +1 -1
  23. package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.d.ts +2 -2
  24. package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.js +3 -37
  25. package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.js.map +1 -1
  26. package/dist/src/connection/websocket/WebsocketServer.js +21 -4
  27. package/dist/src/connection/websocket/WebsocketServer.js.map +1 -1
  28. package/dist/src/dht/DhtNode.d.ts +4 -4
  29. package/dist/src/dht/DhtNode.js +30 -19
  30. package/dist/src/dht/DhtNode.js.map +1 -1
  31. package/dist/src/dht/DhtNodeRpcLocal.d.ts +1 -4
  32. package/dist/src/dht/DhtNodeRpcLocal.js +1 -5
  33. package/dist/src/dht/DhtNodeRpcLocal.js.map +1 -1
  34. package/dist/src/dht/PeerManager.d.ts +10 -6
  35. package/dist/src/dht/PeerManager.js +95 -30
  36. package/dist/src/dht/PeerManager.js.map +1 -1
  37. package/dist/src/dht/contact/SortedContactList.d.ts +20 -6
  38. package/dist/src/dht/contact/SortedContactList.js +55 -24
  39. package/dist/src/dht/contact/SortedContactList.js.map +1 -1
  40. package/dist/src/dht/discovery/DiscoverySession.d.ts +2 -5
  41. package/dist/src/dht/discovery/DiscoverySession.js +12 -9
  42. package/dist/src/dht/discovery/DiscoverySession.js.map +1 -1
  43. package/dist/src/dht/discovery/PeerDiscovery.d.ts +1 -1
  44. package/dist/src/dht/discovery/PeerDiscovery.js +4 -10
  45. package/dist/src/dht/discovery/PeerDiscovery.js.map +1 -1
  46. package/dist/src/dht/find/FindSession.js +6 -1
  47. package/dist/src/dht/find/FindSession.js.map +1 -1
  48. package/dist/src/dht/find/Finder.js +6 -1
  49. package/dist/src/dht/find/Finder.js.map +1 -1
  50. package/dist/src/dht/routing/Router.d.ts +1 -1
  51. package/dist/src/dht/routing/Router.js +8 -4
  52. package/dist/src/dht/routing/Router.js.map +1 -1
  53. package/dist/src/dht/routing/RoutingSession.js +8 -1
  54. package/dist/src/dht/routing/RoutingSession.js.map +1 -1
  55. package/dist/src/dht/store/StoreRpcLocal.js +19 -5
  56. package/dist/src/dht/store/StoreRpcLocal.js.map +1 -1
  57. package/dist/src/helpers/PeerID.d.ts +1 -0
  58. package/dist/src/helpers/PeerID.js +7 -2
  59. package/dist/src/helpers/PeerID.js.map +1 -1
  60. package/dist/src/helpers/peerIdFromPeerDescriptor.js +2 -2
  61. package/dist/src/helpers/peerIdFromPeerDescriptor.js.map +1 -1
  62. package/package.json +5 -5
  63. package/src/connection/ConnectionLockRpcRemote.ts +1 -2
  64. package/src/connection/ConnectionManager.ts +16 -17
  65. package/src/connection/ConnectorFacade.ts +0 -3
  66. package/src/connection/connectivityChecker.ts +3 -2
  67. package/src/connection/websocket/ClientWebsocket.ts +5 -2
  68. package/src/connection/websocket/ServerWebsocket.ts +40 -25
  69. package/src/connection/websocket/WebsocketConnector.ts +23 -12
  70. package/src/connection/websocket/WebsocketConnectorRpcLocal.ts +9 -11
  71. package/src/connection/websocket/WebsocketConnectorRpcRemote.ts +5 -14
  72. package/src/connection/websocket/WebsocketServer.ts +20 -5
  73. package/src/dht/DhtNode.ts +31 -21
  74. package/src/dht/DhtNodeRpcLocal.ts +2 -9
  75. package/src/dht/PeerManager.ts +110 -36
  76. package/src/dht/contact/SortedContactList.ts +87 -44
  77. package/src/dht/discovery/DiscoverySession.ts +14 -14
  78. package/src/dht/discovery/PeerDiscovery.ts +9 -16
  79. package/src/dht/find/FindSession.ts +6 -1
  80. package/src/dht/find/Finder.ts +6 -7
  81. package/src/dht/routing/Router.ts +8 -4
  82. package/src/dht/routing/RoutingSession.ts +8 -8
  83. package/src/dht/store/StoreRpcLocal.ts +19 -7
  84. package/src/helpers/PeerID.ts +6 -2
  85. package/src/helpers/peerIdFromPeerDescriptor.ts +4 -4
  86. package/test/benchmark/Find.test.ts +1 -1
  87. package/test/benchmark/KademliaCorrectness.test.ts +1 -1
  88. package/test/benchmark/SortedContactListBenchmark.test.ts +150 -0
  89. package/test/benchmark/WebsocketServerMemoryLeak.test.ts +41 -0
  90. package/test/benchmark/kademlia-simulation/SimulationNode.ts +6 -1
  91. package/test/end-to-end/Layer0.test.ts +4 -4
  92. package/test/end-to-end/Layer0MixedConnectionTypes.test.ts +10 -10
  93. package/test/end-to-end/Layer0Webrtc-Layer1.test.ts +4 -4
  94. package/test/end-to-end/Layer1-Scale-WebSocket.test.ts +2 -2
  95. package/test/end-to-end/Layer1-Scale-Webrtc.test.ts +2 -2
  96. package/test/end-to-end/RecoveryFromFailedAutoCertification.test.ts +1 -1
  97. package/test/end-to-end/memory-leak.test.ts +1 -0
  98. package/test/integration/DhtJoinPeerDiscovery.test.ts +2 -2
  99. package/test/integration/Layer1-scale.test.ts +1 -1
  100. package/test/integration/Mock-Layer1-Layer0.test.ts +15 -15
  101. package/test/integration/MultipleEntryPointJoining.test.ts +7 -7
  102. package/test/integration/ReplicateData.test.ts +6 -1
  103. package/test/integration/SimultaneousConnections.test.ts +81 -49
  104. package/test/integration/StoreOnDhtWithTwoNodes.test.ts +1 -1
  105. package/test/integration/WebsocketConnectionManagement.test.ts +41 -3
  106. package/test/integration/WebsocketConnectorRpc.test.ts +3 -5
  107. package/test/unit/SortedContactList.test.ts +15 -10
@@ -1,6 +1,5 @@
1
1
  import { ServerCallContext } from '@protobuf-ts/runtime-rpc'
2
2
  import { Logger } from '@streamr/utils'
3
- import KBucket from 'k-bucket'
4
3
  import { getNodeIdFromPeerDescriptor } from '../helpers/peerIdFromPeerDescriptor'
5
4
  import { Empty } from '../proto/google/protobuf/empty'
6
5
  import {
@@ -13,12 +12,11 @@ import {
13
12
  } from '../proto/packages/dht/protos/DhtRpc'
14
13
  import { IDhtNodeRpc } from '../proto/packages/dht/protos/DhtRpc.server'
15
14
  import { DhtCallContext } from '../rpc-protocol/DhtCallContext'
16
- import { DhtNodeRpcRemote } from './DhtNodeRpcRemote'
17
15
 
18
16
  interface DhtNodeRpcLocalConfig {
19
- bucket: KBucket<DhtNodeRpcRemote>
20
17
  serviceId: string
21
18
  peerDiscoveryQueryBatchSize: number
19
+ getClosestPeersTo: (nodeId: Uint8Array, limit: number) => PeerDescriptor[]
22
20
  addNewContact: (contact: PeerDescriptor) => void
23
21
  removeContact: (contact: PeerDescriptor) => void
24
22
  }
@@ -36,17 +34,12 @@ export class DhtNodeRpcLocal implements IDhtNodeRpc {
36
34
  async getClosestPeers(request: ClosestPeersRequest, context: ServerCallContext): Promise<ClosestPeersResponse> {
37
35
  this.config.addNewContact((context as DhtCallContext).incomingSourceDescriptor!)
38
36
  const response = {
39
- peers: this.getClosestPeerDescriptors(request.nodeId, this.config.peerDiscoveryQueryBatchSize),
37
+ peers: this.config.getClosestPeersTo(request.nodeId, this.config.peerDiscoveryQueryBatchSize),
40
38
  requestId: request.requestId
41
39
  }
42
40
  return response
43
41
  }
44
42
 
45
- private getClosestPeerDescriptors(nodeId: Uint8Array, limit: number): PeerDescriptor[] {
46
- const closestPeers = this.config.bucket.closest(nodeId, limit)
47
- return closestPeers.map((rpcRemote: DhtNodeRpcRemote) => rpcRemote.getPeerDescriptor())
48
- }
49
-
50
43
  async ping(request: PingRequest, context: ServerCallContext): Promise<PingResponse> {
51
44
  logger.trace('received ping request: ' + getNodeIdFromPeerDescriptor((context as DhtCallContext).incomingSourceDescriptor!))
52
45
  setImmediate(() => {
@@ -21,7 +21,7 @@ const logger = new Logger(module)
21
21
 
22
22
  interface PeerManagerConfig {
23
23
  numberOfNodesPerKBucket: number
24
- maxNeighborListSize: number
24
+ maxContactListSize: number
25
25
  peerDiscoveryQueryBatchSize: number
26
26
  ownPeerId: PeerID
27
27
  connectionManager: ConnectionManager
@@ -37,14 +37,24 @@ export interface PeerManagerEvents {
37
37
  kBucketEmpty: () => void
38
38
  }
39
39
 
40
+ export const getDistance = (peerId1: Uint8Array, peerId2: Uint8Array): number => {
41
+ return KBucket.distance(peerId1, peerId2)
42
+ }
43
+
40
44
  export class PeerManager extends EventEmitter<PeerManagerEvents> {
41
45
 
42
- // TODO make private
43
- public bucket?: KBucket<DhtNodeRpcRemote>
44
- // TODO make private
46
+ // Glossary:
47
+ // * 'neighbors' are the nodes that are our neighbors according to
48
+ // the protocol of the layer we are in
49
+ // * 'connections' are the nodes that are connected to this node on Layer0
50
+ // * 'contacts' are all non-unresponsive nodes that we know about
51
+
52
+ // The kademlia k-bucket
53
+ private bucket?: KBucket<DhtNodeRpcRemote>
54
+ // Nodes that are connected to this node on Layer0
45
55
  public readonly connections: Map<PeerIDKey, DhtNodeRpcRemote> = new Map()
46
- // TODO make private
47
- public neighborList?: SortedContactList<DhtNodeRpcRemote>
56
+ // All nodes that we know about
57
+ private contacts?: SortedContactList<DhtNodeRpcRemote>
48
58
  private randomPeers?: RandomContactList<DhtNodeRpcRemote>
49
59
  private readonly config: PeerManagerConfig
50
60
  private stopped: boolean = false
@@ -61,24 +71,30 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
61
71
  numberOfNodesPerKBucket: this.config.numberOfNodesPerKBucket,
62
72
  numberOfNodesToPing: this.config.numberOfNodesPerKBucket
63
73
  })
74
+
64
75
  this.bucket.on('ping', (oldContacts: DhtNodeRpcRemote[], newContact: DhtNodeRpcRemote) => this.onKBucketPing(oldContacts, newContact))
65
76
  this.bucket.on('removed', (contact: DhtNodeRpcRemote) => this.onKBucketRemoved(contact))
66
77
  this.bucket.on('added', (contact: DhtNodeRpcRemote) => this.onKBucketAdded(contact))
67
78
  this.bucket.on('updated', () => {
68
79
  // TODO: Update contact info to the connection manager and reconnect
69
80
  })
70
- this.neighborList = new SortedContactList(this.config.ownPeerId, this.config.maxNeighborListSize)
71
- this.neighborList.on('contactRemoved', (removedContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) => {
81
+ this.contacts = new SortedContactList({
82
+ referenceId: this.config.ownPeerId,
83
+ maxSize: this.config.maxContactListSize,
84
+ allowToContainReferenceId: false,
85
+ emitEvents: true
86
+ })
87
+ this.contacts.on('contactRemoved', (removedContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) => {
72
88
  if (this.stopped) {
73
89
  return
74
90
  }
75
91
  this.emit('contactRemoved', removedContact.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
76
92
  this.randomPeers!.addContact(this.config.createDhtNodeRpcRemote(removedContact.getPeerDescriptor()))
77
93
  })
78
- this.neighborList.on('newContact', (newContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
94
+ this.contacts.on('newContact', (newContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
79
95
  this.emit('newContact', newContact.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
80
96
  )
81
- this.randomPeers = new RandomContactList(this.config.ownPeerId, this.config.maxNeighborListSize)
97
+ this.randomPeers = new RandomContactList(this.config.ownPeerId, this.config.maxContactListSize)
82
98
  this.randomPeers.on('contactRemoved', (removedContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
83
99
  this.emit('randomContactRemoved', removedContact.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
84
100
  )
@@ -91,11 +107,16 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
91
107
  if (this.stopped) {
92
108
  return
93
109
  }
94
- const sortingList: SortedContactList<DhtNodeRpcRemote> = new SortedContactList(this.config.ownPeerId, 100)
110
+ const sortingList: SortedContactList<DhtNodeRpcRemote> = new SortedContactList({
111
+ referenceId: this.config.ownPeerId,
112
+ maxSize: 100, // TODO use config option or named constant?
113
+ allowToContainReferenceId: false,
114
+ emitEvents: false
115
+ })
95
116
  sortingList.addContacts(oldContacts)
96
117
  const sortedContacts = sortingList.getAllContacts()
97
118
  this.config.connectionManager?.weakUnlockConnection(sortedContacts[sortedContacts.length - 1].getPeerDescriptor())
98
- this.bucket?.remove(sortedContacts[sortedContacts.length - 1].getPeerId().value)
119
+ this.bucket?.remove(sortedContacts[sortedContacts.length - 1].getPeerId().value)
99
120
  this.bucket!.add(newContact)
100
121
  }
101
122
 
@@ -151,9 +172,9 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
151
172
  }
152
173
 
153
174
  private getClosestActiveContactNotInBucket(): DhtNodeRpcRemote | undefined {
154
- for (const contactId of this.neighborList!.getContactIds()) {
155
- if (!this.bucket!.get(contactId.value) && this.neighborList!.isActive(contactId)) {
156
- return this.neighborList!.getContact(contactId)!.contact
175
+ for (const contactId of this.contacts!.getContactIds()) {
176
+ if (!this.bucket!.get(contactId.value) && this.contacts!.isActive(contactId)) {
177
+ return this.contacts!.getContact(contactId)!.contact
157
178
  }
158
179
  }
159
180
  return undefined
@@ -198,7 +219,7 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
198
219
  logger.trace(`Removing contact ${getNodeIdFromPeerDescriptor(contact)}`)
199
220
  const peerId = peerIdFromPeerDescriptor(contact)
200
221
  this.bucket!.remove(peerId.value)
201
- this.neighborList!.removeContact(peerId)
222
+ this.contacts!.removeContact(peerId)
202
223
  this.randomPeers!.removeContact(peerId)
203
224
  }
204
225
 
@@ -209,46 +230,99 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
209
230
  this.bucket!.remove(rpcRemote.id)
210
231
  })
211
232
  this.bucket!.removeAllListeners()
212
- this.neighborList!.stop()
233
+ this.contacts!.stop()
213
234
  this.randomPeers!.stop()
214
235
  this.connections.clear()
215
236
  }
216
237
 
238
+ getClosestNeighborsTo(kademliaId: Uint8Array, limit?: number, excludeSet?: Set<PeerIDKey>): DhtNodeRpcRemote[] {
239
+ const closest = new SortedContactList<DhtNodeRpcRemote>({
240
+ referenceId: PeerID.fromValue(kademliaId),
241
+ allowToContainReferenceId: true,
242
+ emitEvents: false
243
+ })
244
+ this.bucket!.toArray().map((contact) => closest.addContact(contact))
245
+ // TODO should set the excludeSet and limit to SortedContactList constructor and remove these line
246
+ return closest.getClosestContacts(limit).filter((contact) => {
247
+ if (!excludeSet) {
248
+ return true
249
+ } else {
250
+ return !excludeSet.has(contact.getPeerId().toKey())
251
+ }
252
+ })
253
+ }
254
+
255
+ // TODO reduce copy-paste?
256
+ getClosestContactsTo(kademliaId: Uint8Array, limit?: number, excludeSet?: Set<PeerIDKey>): DhtNodeRpcRemote[] {
257
+ const closest = new SortedContactList<DhtNodeRpcRemote>({
258
+ referenceId: PeerID.fromValue(kademliaId),
259
+ allowToContainReferenceId: true,
260
+ emitEvents: false
261
+ })
262
+ this.contacts!.getAllContacts().map((contact) => closest.addContact(contact))
263
+ // TODO should set the excludeSet and limit to SortedContactList constructor and remove these line
264
+ return closest.getClosestContacts(limit).filter((contact) => {
265
+ if (!excludeSet) {
266
+ return true
267
+ } else {
268
+ return !excludeSet.has(contact.getPeerId().toKey())
269
+ }
270
+ })
271
+ }
272
+
273
+ getNumberOfContacts(excludeSet?: Set<PeerIDKey>): number {
274
+ return this.contacts!.getAllContacts().filter((contact) => {
275
+ if (!excludeSet) {
276
+ return true
277
+ } else {
278
+ return !excludeSet.has(contact.getPeerId().toKey())
279
+ }
280
+ }).length
281
+ }
282
+
217
283
  getNumberOfConnections(): number {
218
284
  return this.connections.size
219
285
  }
220
286
 
287
+ getNumberOfNeighbors(): number {
288
+ return this.bucket!.count()
289
+ }
290
+
291
+ getNeighbors(): PeerDescriptor[] {
292
+ return this.bucket!.toArray().map((rpcRemote: DhtNodeRpcRemote) => rpcRemote.getPeerDescriptor())
293
+ }
294
+
221
295
  handlePeerActive(peerId: PeerID): void {
222
- this.neighborList!.setActive(peerId)
296
+ this.contacts!.setActive(peerId)
223
297
  }
224
298
 
225
299
  handlePeerUnresponsive(peerId: PeerID): void {
226
300
  this.bucket!.remove(peerId.value)
227
- this.neighborList!.removeContact(peerId)
301
+ this.contacts!.removeContact(peerId)
228
302
  }
229
303
 
230
- handleNewPeers(contacts: PeerDescriptor[], setActive = false): void {
304
+ handleNewPeers(peerDescriptors: PeerDescriptor[], setActive?: boolean): void {
231
305
  if (this.stopped) {
232
306
  return
233
307
  }
234
- contacts.forEach((contact) => {
235
- if (!PeerID.fromValue(contact.nodeId).equals(this.config.ownPeerId)) {
308
+ peerDescriptors.forEach((contact) => {
309
+ const peerId = peerIdFromPeerDescriptor(contact)
310
+ if (!peerId.equals(this.config.ownPeerId)) {
236
311
  logger.trace(`Adding new contact ${getNodeIdFromPeerDescriptor(contact)}`)
237
- const rpcRemote = this.config.createDhtNodeRpcRemote(contact)
238
- if ((this.bucket!.get(contact.nodeId) === null)
239
- && (this.neighborList!.getContact(peerIdFromPeerDescriptor(contact)) === undefined)
240
- ) {
241
- this.neighborList!.addContact(rpcRemote)
242
- if (setActive) {
243
- const peerId = peerIdFromPeerDescriptor(contact)
244
- this.neighborList!.setActive(peerId)
245
- }
246
- this.bucket!.add(rpcRemote)
247
- } else {
248
- this.randomPeers!.addContact(rpcRemote)
312
+ const remote = this.config.createDhtNodeRpcRemote(contact)
313
+ const isInBucket = (this.bucket!.get(contact.nodeId) !== null)
314
+ const isInNeighborList = (this.contacts!.getContact(peerId) !== undefined)
315
+ if (isInBucket || isInNeighborList) {
316
+ this.randomPeers!.addContact(remote)
249
317
  }
250
- if (this.neighborList!.getContact(rpcRemote.getPeerId()) !== undefined) {
251
- this.neighborList!.addContact(rpcRemote)
318
+ if (!isInBucket) {
319
+ this.bucket!.add(remote)
320
+ }
321
+ if (!isInNeighborList) {
322
+ this.contacts!.addContact(remote)
323
+ }
324
+ if (setActive) {
325
+ this.contacts!.setActive(peerId)
252
326
  }
253
327
  }
254
328
  })
@@ -1,26 +1,34 @@
1
- import KBucket from 'k-bucket'
2
- import { PeerID } from '../../helpers/PeerID'
3
- import { ContactList, ContactState } from './ContactList'
1
+ import { PeerID, PeerIDKey } from '../../helpers/PeerID'
2
+ import { ContactState, Events } from './ContactList'
3
+ import { sortedIndexBy } from 'lodash'
4
+ import EventEmitter from 'eventemitter3'
5
+ import { getDistance } from '../PeerManager'
6
+
7
+ export interface SortedContactListConfig {
8
+ referenceId: PeerID // all contacts in this list are in sorted by the distance to this ID
9
+ allowToContainReferenceId: boolean
10
+ // TODO could maybe optimize this by removing the flag and then we'd check whether we have
11
+ // any listeners before we emit the event
12
+ emitEvents: boolean
13
+ maxSize?: number
14
+ // if set, the list can't contain any contacts which are futher away than this limit
15
+ peerIdDistanceLimit?: PeerID
16
+ // if set, the list can't contain contacts with these ids
17
+ excludedPeerIDs?: PeerID[]
18
+ }
4
19
 
5
- export class SortedContactList<C extends { getPeerId: () => PeerID }> extends ContactList<C> {
20
+ export class SortedContactList<C extends { getPeerId: () => PeerID }> extends EventEmitter<Events<C>> {
6
21
 
7
- private allowLocalPeerId: boolean
8
- private peerIdDistanceLimit?: PeerID
9
- private excludedPeerIDs?: PeerID[]
22
+ private config: SortedContactListConfig
23
+ private contactsById: Map<PeerIDKey, ContactState<C>> = new Map()
24
+ private contactIds: PeerID[] = []
10
25
 
11
26
  constructor(
12
- ownId: PeerID,
13
- maxSize: number,
14
- defaultContactQueryLimit?: number,
15
- allowLocalPeerId = false,
16
- peerIdDistanceLimit?: PeerID,
17
- excludedPeerIDs?: PeerID[]
27
+ config: SortedContactListConfig
18
28
  ) {
19
- super(ownId, maxSize, defaultContactQueryLimit)
29
+ super()
30
+ this.config = config
20
31
  this.compareIds = this.compareIds.bind(this)
21
- this.allowLocalPeerId = allowLocalPeerId
22
- this.peerIdDistanceLimit = peerIdDistanceLimit
23
- this.excludedPeerIDs = excludedPeerIDs
24
32
  }
25
33
 
26
34
  public getClosestContactId(): PeerID {
@@ -32,38 +40,44 @@ export class SortedContactList<C extends { getPeerId: () => PeerID }> extends Co
32
40
  }
33
41
 
34
42
  public addContact(contact: C): void {
35
- if (this.excludedPeerIDs
36
- && this.excludedPeerIDs.some((peerId) => contact.getPeerId().equals(peerId))) {
43
+ if (this.config.excludedPeerIDs !== undefined
44
+ && this.config.excludedPeerIDs.some((peerId) => contact.getPeerId().equals(peerId))) {
37
45
  return
38
46
  }
39
-
40
- if ((!this.allowLocalPeerId && this.ownId.equals(contact.getPeerId())) ||
41
- (this.peerIdDistanceLimit !== undefined && this.compareIds(this.peerIdDistanceLimit, contact.getPeerId()) < 0)) {
47
+
48
+ if ((!this.config.allowToContainReferenceId && this.config.referenceId.equals(contact.getPeerId())) ||
49
+ (this.config.peerIdDistanceLimit !== undefined && this.compareIds(this.config.peerIdDistanceLimit, contact.getPeerId()) < 0)) {
42
50
  return
43
51
  }
44
52
  if (!this.contactsById.has(contact.getPeerId().toKey())) {
45
- if (this.contactIds.length < this.maxSize) {
53
+ if ((this.config.maxSize === undefined) || (this.contactIds.length < this.config.maxSize)) {
46
54
  this.contactsById.set(contact.getPeerId().toKey(), new ContactState(contact))
47
- this.contactIds.push(contact.getPeerId())
48
- this.contactIds.sort(this.compareIds)
49
- } else if (this.compareIds(this.contactIds[this.maxSize - 1], contact.getPeerId()) > 0) {
55
+
56
+ const index = sortedIndexBy(this.contactIds, contact.getPeerId(), (id: PeerID) => { return this.distanceToReferenceId(id) })
57
+ this.contactIds.splice(index, 0, contact.getPeerId())
58
+ } else if (this.compareIds(this.contactIds[this.config.maxSize - 1], contact.getPeerId()) > 0) {
50
59
  const removedId = this.contactIds.pop()
51
60
  const removedContact = this.contactsById.get(removedId!.toKey())!.contact
52
61
  this.contactsById.delete(removedId!.toKey())
53
62
  this.contactsById.set(contact.getPeerId().toKey(), new ContactState(contact))
54
- this.contactIds.push(contact.getPeerId())
55
- this.contactIds.sort(this.compareIds)
63
+
64
+ const index = sortedIndexBy(this.contactIds, contact.getPeerId(), (id: PeerID) => { return this.distanceToReferenceId(id) })
65
+ this.contactIds.splice(index, 0, contact.getPeerId())
66
+ if (this.config.emitEvents) {
67
+ this.emit(
68
+ 'contactRemoved',
69
+ removedContact,
70
+ this.getClosestContacts()
71
+ )
72
+ }
73
+ }
74
+ if (this.config.emitEvents) {
56
75
  this.emit(
57
- 'contactRemoved',
58
- removedContact,
76
+ 'newContact',
77
+ contact,
59
78
  this.getClosestContacts()
60
79
  )
61
80
  }
62
- this.emit(
63
- 'newContact',
64
- contact,
65
- this.getClosestContacts()
66
- )
67
81
  }
68
82
  }
69
83
 
@@ -71,6 +85,10 @@ export class SortedContactList<C extends { getPeerId: () => PeerID }> extends Co
71
85
  contacts.forEach((contact) => this.addContact(contact))
72
86
  }
73
87
 
88
+ public getContact(id: PeerID): ContactState<C> | undefined {
89
+ return this.contactsById.get(id.toKey())
90
+ }
91
+
74
92
  public setContacted(contactId: PeerID): void {
75
93
  if (this.contactsById.has(contactId.toKey())) {
76
94
  this.contactsById.get(contactId.toKey())!.contacted = true
@@ -83,7 +101,7 @@ export class SortedContactList<C extends { getPeerId: () => PeerID }> extends Co
83
101
  }
84
102
  }
85
103
 
86
- public getClosestContacts(limit = this.defaultContactQueryLimit): C[] {
104
+ public getClosestContacts(limit?: number): C[] {
87
105
  const ret: C[] = []
88
106
  this.contactIds.forEach((contactId) => {
89
107
  const contact = this.contactsById.get(contactId.toKey())
@@ -91,7 +109,11 @@ export class SortedContactList<C extends { getPeerId: () => PeerID }> extends Co
91
109
  ret.push(contact.contact)
92
110
  }
93
111
  })
94
- return ret.slice(0, limit)
112
+ if (limit === undefined) {
113
+ return ret
114
+ } else {
115
+ return ret.slice(0, limit)
116
+ }
95
117
  }
96
118
 
97
119
  public getUncontactedContacts(num: number): C[] {
@@ -124,22 +146,29 @@ export class SortedContactList<C extends { getPeerId: () => PeerID }> extends Co
124
146
  }
125
147
 
126
148
  public compareIds(id1: PeerID, id2: PeerID): number {
127
- const distance1 = KBucket.distance(this.ownId.value, id1.value)
128
- const distance2 = KBucket.distance(this.ownId.value, id2.value)
149
+ const distance1 = this.distanceToReferenceId(id1)
150
+ const distance2 = this.distanceToReferenceId(id2)
129
151
  return distance1 - distance2
130
152
  }
131
153
 
154
+ // TODO inline this method?
155
+ private distanceToReferenceId(id: PeerID): number {
156
+ return getDistance(this.config.referenceId.value, id.value)
157
+ }
158
+
132
159
  public removeContact(id: PeerID): boolean {
133
160
  if (this.contactsById.has(id.toKey())) {
134
161
  const removed = this.contactsById.get(id.toKey())!.contact
135
162
  const index = this.contactIds.findIndex((element) => element.equals(id))
136
163
  this.contactIds.splice(index, 1)
137
164
  this.contactsById.delete(id.toKey())
138
- this.emit(
139
- 'contactRemoved',
140
- removed,
141
- this.getClosestContacts()
142
- )
165
+ if (this.config.emitEvents) {
166
+ this.emit(
167
+ 'contactRemoved',
168
+ removed,
169
+ this.getClosestContacts()
170
+ )
171
+ }
143
172
  return true
144
173
  }
145
174
  return false
@@ -152,4 +181,18 @@ export class SortedContactList<C extends { getPeerId: () => PeerID }> extends Co
152
181
  public getAllContacts(): C[] {
153
182
  return this.contactIds.map((peerId) => this.contactsById.get(peerId.toKey())!.contact)
154
183
  }
184
+
185
+ public getSize(): number {
186
+ return this.contactIds.length
187
+ }
188
+
189
+ public clear(): void {
190
+ this.contactsById.clear()
191
+ this.contactIds = []
192
+ }
193
+
194
+ public stop(): void {
195
+ this.removeAllListeners()
196
+ this.clear()
197
+ }
155
198
  }
@@ -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,7 +15,6 @@ 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
@@ -31,6 +29,7 @@ export class DiscoverySession {
31
29
  private noProgressCounter = 0
32
30
  private ongoingClosestPeersRequests: Set<string> = new Set()
33
31
  private readonly config: DiscoverySessionConfig
32
+ private contactedPeers: Set<PeerIDKey> = new Set()
34
33
 
35
34
  constructor(config: DiscoverySessionConfig) {
36
35
  this.config = config
@@ -49,7 +48,7 @@ export class DiscoverySession {
49
48
  }
50
49
  logger.trace(`Getting closest peers from contact: ${getNodeIdFromPeerDescriptor(contact.getPeerDescriptor())}`)
51
50
  this.outgoingClosestPeersRequestsCounter++
52
- this.config.peerManager.neighborList!.setContacted(contact.getPeerId())
51
+ this.contactedPeers.add(contact.getPeerId().toKey())
53
52
  const returnedContacts = await contact.getClosestPeers(this.config.targetId)
54
53
  this.config.peerManager.handlePeerActive(contact.getPeerId())
55
54
  return returnedContacts
@@ -60,9 +59,12 @@ export class DiscoverySession {
60
59
  return
61
60
  }
62
61
  this.ongoingClosestPeersRequests.delete(peerId.toKey())
63
- const oldClosestContact = this.config.peerManager.neighborList!.getClosestContactId()
62
+ const oldClosestNeighbor = this.config.peerManager.getClosestNeighborsTo(this.config.targetId, 1)[0]
63
+ const oldClosestDistance = getDistance(this.config.targetId, oldClosestNeighbor.getPeerId().value)
64
64
  this.addNewContacts(contacts)
65
- if (this.config.peerManager.neighborList!.getClosestContactId().equals(oldClosestContact)) {
65
+ const newClosestNeighbor = this.config.peerManager.getClosestNeighborsTo(this.config.targetId, 1)[0]
66
+ const newClosestDistance = getDistance(this.config.targetId, newClosestNeighbor.getPeerId().value)
67
+ if (newClosestDistance >= oldClosestDistance) {
66
68
  this.noProgressCounter++
67
69
  } else {
68
70
  this.noProgressCounter = 0
@@ -81,7 +83,7 @@ export class DiscoverySession {
81
83
  if (this.stopped) {
82
84
  return
83
85
  }
84
- const uncontacted = this.config.peerManager.neighborList!.getUncontactedContacts(this.config.parallelism)
86
+ const uncontacted = this.config.peerManager.getClosestContactsTo(this.config.targetId, this.config.parallelism, this.contactedPeers)
85
87
  if (uncontacted.length === 0 || this.noProgressCounter >= this.config.noProgressLimit) {
86
88
  this.emitter.emit('discoveryCompleted')
87
89
  this.stopped = true
@@ -103,18 +105,16 @@ export class DiscoverySession {
103
105
  }
104
106
  }
105
107
 
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!
108
+ public async findClosestNodes(timeout: number): Promise<void> {
109
+ if (this.config.peerManager.getNumberOfContacts(this.contactedPeers) === 0) {
110
+ return
111
111
  }
112
+ // TODO add abortController and signal it in stop()
112
113
  await runAndWaitForEvents3<DiscoverySessionEvents>(
113
114
  [this.findMoreContacts.bind(this)],
114
115
  [[this.emitter, 'discoveryCompleted']],
115
116
  timeout
116
117
  )
117
- return this.config.peerManager.neighborList!
118
118
  }
119
119
 
120
120
  public stop(): void {
@@ -4,9 +4,9 @@ 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
10
 
11
11
  interface PeerDiscoveryConfig {
12
12
  localPeerDescriptor: PeerDescriptor
@@ -51,8 +51,6 @@ export class PeerDiscovery {
51
51
  this.config.connectionManager?.lockConnection(entryPointDescriptor, `${this.config.serviceId}::joinDht`)
52
52
  this.config.peerManager.handleNewPeers([entryPointDescriptor])
53
53
  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
54
  const sessions = [this.createSession(targetId)]
57
55
  if (doAdditionalRandomPeerDiscovery) {
58
56
  sessions.push(this.createSession(createRandomNodeId()))
@@ -65,7 +63,6 @@ export class PeerDiscovery {
65
63
  private createSession(targetId: Uint8Array): DiscoverySession {
66
64
  const sessionOptions = {
67
65
  targetId,
68
- localPeerDescriptor: this.config.localPeerDescriptor,
69
66
  parallelism: this.config.parallelism,
70
67
  noProgressLimit: this.config.joinNoProgressLimit,
71
68
  peerManager: this.config.peerManager
@@ -83,7 +80,7 @@ export class PeerDiscovery {
83
80
  logger.debug(`DHT join on ${this.config.serviceId} timed out`)
84
81
  } finally {
85
82
  if (!this.isStopped()) {
86
- if (this.config.peerManager.bucket!.count() === 0) {
83
+ if (this.config.peerManager.getNumberOfNeighbors() === 0) {
87
84
  if (retry) {
88
85
  // TODO should we catch possible promise rejection?
89
86
  setAbortableTimeout(() => this.rejoinDht(entryPointDescriptor), 1000, this.abortController.signal)
@@ -103,7 +100,6 @@ export class PeerDiscovery {
103
100
  logger.debug(`Rejoining DHT ${this.config.serviceId}`)
104
101
  this.rejoinOngoing = true
105
102
  try {
106
- this.config.peerManager.neighborList!.clear()
107
103
  await this.joinDht(entryPoint)
108
104
  logger.debug(`Rejoined DHT successfully ${this.config.serviceId}!`)
109
105
  } catch (err) {
@@ -128,16 +124,13 @@ export class PeerDiscovery {
128
124
  if (this.isStopped()) {
129
125
  return
130
126
  }
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])
127
+ const nodes = this.config.peerManager.getClosestNeighborsTo(this.config.localPeerDescriptor.nodeId, this.config.parallelism)
128
+ await Promise.allSettled(
129
+ nodes.map(async (peer: DhtNodeRpcRemote) => {
130
+ const contacts = await peer.getClosestPeers(this.config.localPeerDescriptor.nodeId!)
131
+ this.config.peerManager.handleNewPeers(contacts)
139
132
  })
140
- }))
133
+ )
141
134
  }
142
135
 
143
136
  public isJoinOngoing(): boolean {
@@ -158,7 +151,7 @@ export class PeerDiscovery {
158
151
  clearTimeout(this.rejoinTimeoutRef)
159
152
  this.rejoinTimeoutRef = undefined
160
153
  }
161
- this.ongoingDiscoverySessions.forEach((session, _id) => {
154
+ this.ongoingDiscoverySessions.forEach((session) => {
162
155
  session.stop()
163
156
  })
164
157
  }
@@ -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