@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
@@ -51,7 +51,7 @@ import { MarkRequired } from 'ts-essentials'
51
51
  import { DhtNodeRpcLocal } from './DhtNodeRpcLocal'
52
52
  import { ServerCallContext } from '@protobuf-ts/runtime-rpc'
53
53
  import { ExternalApiRpcLocal } from './ExternalApiRpcLocal'
54
- import { PeerManager } from './PeerManager'
54
+ import { PeerManager, getDistance } from './PeerManager'
55
55
 
56
56
  export interface DhtNodeEvents {
57
57
  newContact: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
@@ -148,7 +148,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
148
148
 
149
149
  public connectionManager?: ConnectionManager
150
150
  private started = false
151
- private stopped = false
151
+ private abortController = new AbortController()
152
152
  private entryPointDisconnectTimeout?: NodeJS.Timeout
153
153
 
154
154
  constructor(conf: DhtNodeOptions) {
@@ -173,7 +173,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
173
173
  }
174
174
 
175
175
  public async start(): Promise<void> {
176
- if (this.started || this.stopped) {
176
+ if (this.started || this.abortController.signal.aborted) {
177
177
  return
178
178
  }
179
179
  logger.trace(`Starting new Streamr Network DHT Node with serviceId ${this.config.serviceId}`)
@@ -283,7 +283,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
283
283
  localDataStore: this.localDataStore,
284
284
  dhtNodeEmitter: this,
285
285
  getNodesClosestToIdFromBucket: (id: Uint8Array, n?: number) => {
286
- return this.peerManager!.bucket!.closest(id, n)
286
+ return this.peerManager!.getClosestNeighborsTo(id, n)
287
287
  },
288
288
  rpcRequestTimeout: this.config.rpcRequestTimeout
289
289
  })
@@ -297,7 +297,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
297
297
  private initPeerManager() {
298
298
  this.peerManager = new PeerManager({
299
299
  numberOfNodesPerKBucket: this.config.numberOfNodesPerKBucket,
300
- maxNeighborListSize: this.config.maxNeighborListSize,
300
+ maxContactListSize: this.config.maxNeighborListSize,
301
301
  ownPeerId: this.getNodeId(),
302
302
  connectionManager: this.connectionManager!,
303
303
  peerDiscoveryQueryBatchSize: this.config.peerDiscoveryQueryBatchSize,
@@ -343,13 +343,16 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
343
343
  }
344
344
 
345
345
  private bindRpcLocalMethods(): void {
346
- if (!this.started || this.stopped) {
346
+ if (!this.started || this.abortController.signal.aborted) {
347
347
  return
348
348
  }
349
349
  const dhtNodeRpcLocal = new DhtNodeRpcLocal({
350
- bucket: this.peerManager!.bucket!,
351
350
  serviceId: this.config.serviceId,
352
351
  peerDiscoveryQueryBatchSize: this.config.peerDiscoveryQueryBatchSize,
352
+ getClosestPeersTo: (kademliaId: Uint8Array, limit: number) => {
353
+ return this.peerManager!.getClosestNeighborsTo(kademliaId, limit)
354
+ .map((dhtPeer: DhtNodeRpcRemote) => dhtPeer.getPeerDescriptor())
355
+ },
353
356
  addNewContact: (contact: PeerDescriptor) => this.peerManager!.handleNewPeers([contact]),
354
357
  removeContact: (contact: PeerDescriptor) => this.removeContact(contact)
355
358
  })
@@ -382,8 +385,8 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
382
385
  }
383
386
 
384
387
  private isPeerCloserToIdThanSelf(peer1: PeerDescriptor, compareToId: PeerID): boolean {
385
- const distance1 = this.peerManager!.bucket!.distance(peer1.nodeId, compareToId.value)
386
- const distance2 = this.peerManager!.bucket!.distance(this.localPeerDescriptor!.nodeId, compareToId.value)
388
+ const distance1 = getDistance(peer1.nodeId, compareToId.value)
389
+ const distance2 = getDistance(this.localPeerDescriptor!.nodeId, compareToId.value)
387
390
  return distance1 < distance2
388
391
  }
389
392
 
@@ -408,16 +411,16 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
408
411
  return this.localPeerDescriptor
409
412
  }
410
413
 
411
- public getClosestContacts(maxCount?: number): PeerDescriptor[] {
412
- return this.peerManager!.neighborList!.getClosestContacts(maxCount).map((c) => c.getPeerDescriptor())
414
+ public getClosestContacts(limit?: number): PeerDescriptor[] {
415
+ return this.peerManager!.getClosestContactsTo(this.localPeerDescriptor!.nodeId, limit).map((peer) => peer.getPeerDescriptor())
413
416
  }
414
-
417
+
415
418
  public getNodeId(): PeerID {
416
419
  return peerIdFromPeerDescriptor(this.localPeerDescriptor!)
417
420
  }
418
421
 
419
- public getBucketSize(): number {
420
- return this.peerManager!.bucket!.count()
422
+ public getNumberOfNeighbors(): number {
423
+ return this.peerManager!.getNumberOfNeighbors()
421
424
  }
422
425
 
423
426
  private connectToEntryPoint(entryPoint: PeerDescriptor): void {
@@ -435,7 +438,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
435
438
  }
436
439
 
437
440
  public async send(msg: Message): Promise<void> {
438
- if (!this.started || this.stopped) {
441
+ if (!this.started || this.abortController.signal.aborted) {
439
442
  return
440
443
  }
441
444
  const reachableThrough = this.peerDiscovery!.isJoinOngoing() ? this.config.entryPoints ?? [] : []
@@ -446,9 +449,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
446
449
  if (!this.started) {
447
450
  throw new Error('Cannot join DHT before calling start() on DhtNode')
448
451
  }
449
- await Promise.all(entryPointDescriptors.map((entryPoint) =>
450
- this.peerDiscovery!.joinDht(entryPoint, doAdditionalRandomPeerDiscovery, retry)
451
- ))
452
+ await this.peerDiscovery!.joinDht(entryPointDescriptors, doAdditionalRandomPeerDiscovery, retry)
452
453
  }
453
454
 
454
455
  public async startFind(key: Uint8Array, action?: FindAction, excludedPeer?: PeerDescriptor): Promise<FindResult> {
@@ -481,7 +482,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
481
482
  }
482
483
 
483
484
  public async deleteDataFromDht(key: Uint8Array, waitForCompletion: boolean): Promise<void> {
484
- if (!this.stopped) {
485
+ if (!this.abortController.signal.aborted) {
485
486
  await this.finder!.startFind(key, FindAction.DELETE_DATA, undefined, waitForCompletion)
486
487
  }
487
488
  }
@@ -508,8 +509,9 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
508
509
  return Array.from(this.peerManager!.connections.values()).map((peer) => peer.getPeerDescriptor())
509
510
  }
510
511
 
511
- public getKBucketPeers(): PeerDescriptor[] {
512
- return this.peerManager!.bucket!.toArray().map((rpcRemote: DhtNodeRpcRemote) => rpcRemote.getPeerDescriptor())
512
+ // TODO rename to getNeighbors
513
+ public getAllNeighborPeerDescriptors(): PeerDescriptor[] {
514
+ return this.peerManager!.getNeighbors()
513
515
  }
514
516
 
515
517
  public getNumberOfConnections(): number {
@@ -529,7 +531,13 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
529
531
  }
530
532
 
531
533
  public async waitForNetworkConnectivity(): Promise<void> {
532
- await waitForCondition(() => this.peerManager!.connections.size > 0, this.config.networkConnectivityTimeout)
534
+ await waitForCondition(() => {
535
+ if (!this.peerManager) {
536
+ return false
537
+ } else {
538
+ return (this.peerManager.getNumberOfConnections() > 0)
539
+ }
540
+ }, this.config.networkConnectivityTimeout, 100, this.abortController.signal)
533
541
  }
534
542
 
535
543
  public hasJoined(): boolean {
@@ -537,11 +545,11 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
537
545
  }
538
546
 
539
547
  public async stop(): Promise<void> {
540
- if (this.stopped || !this.started) {
548
+ if (this.abortController.signal.aborted || !this.started) {
541
549
  return
542
550
  }
543
551
  logger.trace('stop()')
544
- this.stopped = true
552
+ this.abortController.abort()
545
553
  await this.storeRpcLocal!.destroy()
546
554
  if (this.entryPointDisconnectTimeout) {
547
555
  clearTimeout(this.entryPointDisconnectTimeout)
@@ -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
  }