@streamr/dht 100.0.0-testnet-three.1 → 100.0.0-testnet-three.3

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 (61) hide show
  1. package/dist/package.json +5 -5
  2. package/dist/src/connection/connectivityRequestHandler.js +25 -21
  3. package/dist/src/connection/connectivityRequestHandler.js.map +1 -1
  4. package/dist/src/connection/websocket/WebsocketConnector.js +10 -1
  5. package/dist/src/connection/websocket/WebsocketConnector.js.map +1 -1
  6. package/dist/src/dht/DhtNode.d.ts +3 -2
  7. package/dist/src/dht/DhtNode.js +28 -11
  8. package/dist/src/dht/DhtNode.js.map +1 -1
  9. package/dist/src/dht/DhtNodeRpcLocal.d.ts +1 -1
  10. package/dist/src/dht/DhtNodeRpcLocal.js +2 -2
  11. package/dist/src/dht/DhtNodeRpcLocal.js.map +1 -1
  12. package/dist/src/dht/PeerManager.d.ts +7 -9
  13. package/dist/src/dht/PeerManager.js +7 -14
  14. package/dist/src/dht/PeerManager.js.map +1 -1
  15. package/dist/src/dht/contact/ContactList.d.ts +1 -1
  16. package/dist/src/dht/contact/RandomContactList.js +1 -1
  17. package/dist/src/dht/contact/RandomContactList.js.map +1 -1
  18. package/dist/src/dht/contact/SortedContactList.js +18 -17
  19. package/dist/src/dht/contact/SortedContactList.js.map +1 -1
  20. package/dist/src/dht/discovery/DiscoverySession.d.ts +1 -1
  21. package/dist/src/dht/discovery/DiscoverySession.js +5 -5
  22. package/dist/src/dht/discovery/DiscoverySession.js.map +1 -1
  23. package/dist/src/dht/discovery/PeerDiscovery.js +2 -2
  24. package/dist/src/dht/discovery/PeerDiscovery.js.map +1 -1
  25. package/dist/src/dht/recursive-operation/RecursiveOperationRpcLocal.js +0 -1
  26. package/dist/src/dht/recursive-operation/RecursiveOperationRpcLocal.js.map +1 -1
  27. package/dist/src/dht/routing/RouterRpcLocal.js +2 -3
  28. package/dist/src/dht/routing/RouterRpcLocal.js.map +1 -1
  29. package/dist/src/dht/routing/RoutingSession.js +2 -1
  30. package/dist/src/dht/routing/RoutingSession.js.map +1 -1
  31. package/dist/src/dht/store/LocalDataStore.d.ts +1 -1
  32. package/dist/src/dht/store/LocalDataStore.js +2 -5
  33. package/dist/src/dht/store/LocalDataStore.js.map +1 -1
  34. package/dist/src/dht/store/StoreManager.d.ts +1 -1
  35. package/dist/src/dht/store/StoreManager.js +10 -17
  36. package/dist/src/dht/store/StoreManager.js.map +1 -1
  37. package/dist/src/identifiers.d.ts +1 -0
  38. package/dist/src/identifiers.js +3 -3
  39. package/dist/src/identifiers.js.map +1 -1
  40. package/package.json +5 -5
  41. package/src/connection/connectivityRequestHandler.ts +26 -22
  42. package/src/connection/websocket/WebsocketConnector.ts +9 -1
  43. package/src/dht/DhtNode.ts +33 -16
  44. package/src/dht/DhtNodeRpcLocal.ts +3 -3
  45. package/src/dht/PeerManager.ts +12 -21
  46. package/src/dht/contact/ContactList.ts +1 -1
  47. package/src/dht/contact/RandomContactList.ts +1 -1
  48. package/src/dht/contact/SortedContactList.ts +26 -24
  49. package/src/dht/discovery/DiscoverySession.ts +5 -5
  50. package/src/dht/discovery/PeerDiscovery.ts +2 -2
  51. package/src/dht/recursive-operation/RecursiveOperationRpcLocal.ts +0 -1
  52. package/src/dht/routing/RouterRpcLocal.ts +2 -3
  53. package/src/dht/routing/RoutingSession.ts +2 -1
  54. package/src/dht/store/LocalDataStore.ts +2 -5
  55. package/src/dht/store/StoreManager.ts +10 -17
  56. package/src/identifiers.ts +1 -1
  57. package/test/end-to-end/Layer0Webrtc-Layer1.test.ts +5 -5
  58. package/test/end-to-end/Layer0Webrtc.test.ts +0 -2
  59. package/test/unit/PeerManager.test.ts +1 -1
  60. package/test/unit/SortedContactList.test.ts +13 -0
  61. package/test/unit/StoreManager.test.ts +26 -23
@@ -34,10 +34,25 @@ export const attachConnectivityRequestHandler = (connectionToListenTo: ServerWeb
34
34
  }
35
35
 
36
36
  const handleIncomingConnectivityRequest = async (connection: ServerWebsocket, connectivityRequest: ConnectivityRequest): Promise<void> => {
37
- let outgoingConnection: IConnection | undefined
38
- let connectivityResponseMessage: ConnectivityResponse | undefined
39
37
  const host = connectivityRequest.host ?? connection.getRemoteAddress()
40
38
  const ipAddress = connection.getRemoteIp()
39
+ const connectivityResponse = await connectivityProbe(connectivityRequest, ipAddress, host)
40
+ const msg: Message = {
41
+ serviceId: CONNECTIVITY_CHECKER_SERVICE_ID,
42
+ messageType: MessageType.CONNECTIVITY_RESPONSE,
43
+ messageId: v4(),
44
+ body: {
45
+ oneofKind: 'connectivityResponse',
46
+ connectivityResponse
47
+ }
48
+ }
49
+ connection.send(Message.toBinary(msg))
50
+ logger.trace('ConnectivityResponse sent: ' + JSON.stringify(Message.toJson(msg)))
51
+ }
52
+
53
+ const connectivityProbe = async (connectivityRequest: ConnectivityRequest, ipAddress: string, host: string): Promise<ConnectivityResponse> => {
54
+ let outgoingConnection: IConnection | undefined
55
+ let connectivityResponseMessage: ConnectivityResponse
41
56
  try {
42
57
  const wsServerInfo = {
43
58
  host,
@@ -50,6 +65,14 @@ const handleIncomingConnectivityRequest = async (connection: ServerWebsocket, co
50
65
  url,
51
66
  selfSigned: connectivityRequest.selfSigned
52
67
  })
68
+ logger.trace('Connectivity test produced positive result, communicating reply to the requester ' + host + ':' + connectivityRequest.port)
69
+ connectivityResponseMessage = {
70
+ host,
71
+ natType: NatType.OPEN_INTERNET,
72
+ websocket: { host, port: connectivityRequest.port, tls: connectivityRequest.tls },
73
+ ipAddress: ipv4ToNumber(ipAddress),
74
+ version: localVersion
75
+ }
53
76
  } catch (err) {
54
77
  logger.debug('error', { err })
55
78
  connectivityResponseMessage = {
@@ -62,25 +85,6 @@ const handleIncomingConnectivityRequest = async (connection: ServerWebsocket, co
62
85
  if (outgoingConnection) {
63
86
  // TODO should we have some handling for this floating promise?
64
87
  outgoingConnection.close(false)
65
- logger.trace('Connectivity test produced positive result, communicating reply to the requester ' + host + ':' + connectivityRequest.port)
66
-
67
- connectivityResponseMessage = {
68
- host,
69
- natType: NatType.OPEN_INTERNET,
70
- websocket: { host, port: connectivityRequest.port, tls: connectivityRequest.tls },
71
- ipAddress: ipv4ToNumber(ipAddress),
72
- version: localVersion
73
- }
74
88
  }
75
- const msg: Message = {
76
- serviceId: CONNECTIVITY_CHECKER_SERVICE_ID,
77
- messageType: MessageType.CONNECTIVITY_RESPONSE,
78
- messageId: v4(),
79
- body: {
80
- oneofKind: 'connectivityResponse',
81
- connectivityResponse: connectivityResponseMessage!
82
- }
83
- }
84
- connection.send(Message.toBinary(msg))
85
- logger.trace('ConnectivityResponse sent: ' + JSON.stringify(Message.toJson(msg)))
89
+ return connectivityResponseMessage
86
90
  }
@@ -153,7 +153,15 @@ export class WebsocketConnector {
153
153
  } else if (action === 'connectivityProbe') {
154
154
  // no-op
155
155
  } else {
156
- this.attachHandshaker(connection)
156
+ // The localPeerDescriptor can be undefined here as the WS server is used for connectivity checks
157
+ // before the localPeerDescriptor is set during start.
158
+ // Handshaked connections should be rejected before the localPeerDescriptor is set.
159
+ if (this.localPeerDescriptor !== undefined) {
160
+ this.attachHandshaker(connection)
161
+ } else {
162
+ logger.trace('incoming Websocket connection before localPeerDescriptor was set, closing connection')
163
+ connection.close(false).catch(() => {})
164
+ }
157
165
  }
158
166
  })
159
167
  const port = await this.websocketServer.start()
@@ -12,7 +12,7 @@ import { ConnectionManager, PortRange, TlsCertificate } from '../connection/Conn
12
12
  import { DefaultConnectorFacade, DefaultConnectorFacadeConfig } from '../connection/ConnectorFacade'
13
13
  import { IceServer } from '../connection/webrtc/WebrtcConnector'
14
14
  import { isBrowserEnvironment } from '../helpers/browser/isBrowserEnvironment'
15
- import { DhtAddress, getNodeIdFromPeerDescriptor } from '../identifiers'
15
+ import { DhtAddress, KADEMLIA_ID_LENGTH_IN_BYTES, getNodeIdFromPeerDescriptor } from '../identifiers'
16
16
  import { Any } from '../proto/google/protobuf/any'
17
17
  import {
18
18
  ClosestPeersRequest,
@@ -48,9 +48,9 @@ import { StoreRpcRemote } from './store/StoreRpcRemote'
48
48
  import { createPeerDescriptor } from '../helpers/createPeerDescriptor'
49
49
 
50
50
  export interface DhtNodeEvents {
51
- newContact: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
51
+ contactAdded: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
52
52
  contactRemoved: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
53
- newRandomContact: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
53
+ randomContactAdded: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
54
54
  randomContactRemoved: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
55
55
  }
56
56
 
@@ -143,10 +143,27 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
143
143
  storageRedundancyFactor: 5,
144
144
  metricsContext: new MetricsContext()
145
145
  }, conf)
146
+ this.validateConfig()
146
147
  this.localDataStore = new LocalDataStore(this.config.storeMaxTtl)
147
148
  this.send = this.send.bind(this)
148
149
  }
149
150
 
151
+ private validateConfig(): void {
152
+ const expectedNodeIdLength = KADEMLIA_ID_LENGTH_IN_BYTES * 2
153
+ if (this.config.nodeId !== undefined ) {
154
+ if (!/^[0-9a-fA-F]+$/.test(this.config.nodeId)) {
155
+ throw new Error('Invalid nodeId, the nodeId should be a hex string')
156
+ } else if (this.config.nodeId.length !== expectedNodeIdLength) {
157
+ throw new Error(`Invalid nodeId, the length of the nodeId should be ${expectedNodeIdLength}`)
158
+ }
159
+ }
160
+ if (this.config.peerDescriptor !== undefined) {
161
+ if (this.config.peerDescriptor.nodeId.length !== KADEMLIA_ID_LENGTH_IN_BYTES) {
162
+ throw new Error(`Invalid peerDescriptor, the length of the nodeId should be ${KADEMLIA_ID_LENGTH_IN_BYTES} bytes`)
163
+ }
164
+ }
165
+ }
166
+
150
167
  public async start(): Promise<void> {
151
168
  if (this.started || this.abortController.signal.aborted) {
152
169
  return
@@ -232,7 +249,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
232
249
  rpcCommunicator: this.rpcCommunicator,
233
250
  connections: this.peerManager!.connections,
234
251
  localPeerDescriptor: this.localPeerDescriptor!,
235
- addContact: (contact: PeerDescriptor, setActive?: boolean) => this.peerManager!.handleNewPeers([contact], setActive),
252
+ addContact: (contact: PeerDescriptor, setActive?: boolean) => this.peerManager!.addContact([contact], setActive),
236
253
  connectionManager: this.connectionManager
237
254
  })
238
255
  this.recursiveOperationManager = new RecursiveOperationManager({
@@ -242,7 +259,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
242
259
  connections: this.peerManager!.connections,
243
260
  localPeerDescriptor: this.localPeerDescriptor!,
244
261
  serviceId: this.config.serviceId,
245
- addContact: (contact: PeerDescriptor) => this.peerManager!.handleNewPeers([contact]),
262
+ addContact: (contact: PeerDescriptor) => this.peerManager!.addContact([contact]),
246
263
  localDataStore: this.localDataStore
247
264
  })
248
265
  this.storeManager = new StoreManager({
@@ -266,8 +283,8 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
266
283
  )
267
284
  }
268
285
  })
269
- this.on('newContact', (peerDescriptor: PeerDescriptor) => {
270
- this.storeManager!.onNewContact(peerDescriptor)
286
+ this.on('contactAdded', (peerDescriptor: PeerDescriptor) => {
287
+ this.storeManager!.onContactAdded(peerDescriptor)
271
288
  })
272
289
  this.bindRpcLocalMethods()
273
290
  }
@@ -285,14 +302,14 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
285
302
  this.peerManager.on('contactRemoved', (peerDescriptor: PeerDescriptor, activeContacts: PeerDescriptor[]) => {
286
303
  this.emit('contactRemoved', peerDescriptor, activeContacts)
287
304
  })
288
- this.peerManager.on('newContact', (peerDescriptor: PeerDescriptor, activeContacts: PeerDescriptor[]) =>
289
- this.emit('newContact', peerDescriptor, activeContacts)
305
+ this.peerManager.on('contactAdded', (peerDescriptor: PeerDescriptor, activeContacts: PeerDescriptor[]) =>
306
+ this.emit('contactAdded', peerDescriptor, activeContacts)
290
307
  )
291
308
  this.peerManager.on('randomContactRemoved', (peerDescriptor: PeerDescriptor, activeContacts: PeerDescriptor[]) =>
292
309
  this.emit('randomContactRemoved', peerDescriptor, activeContacts)
293
310
  )
294
- this.peerManager.on('newRandomContact', (peerDescriptor: PeerDescriptor, activeContacts: PeerDescriptor[]) =>
295
- this.emit('newRandomContact', peerDescriptor, activeContacts)
311
+ this.peerManager.on('randomContactAdded', (peerDescriptor: PeerDescriptor, activeContacts: PeerDescriptor[]) =>
312
+ this.emit('randomContactAdded', peerDescriptor, activeContacts)
296
313
  )
297
314
  this.peerManager.on('kBucketEmpty', () => {
298
315
  if (!this.peerDiscovery!.isJoinOngoing()
@@ -308,15 +325,15 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
308
325
  }
309
326
  })
310
327
  this.transport!.on('connected', (peerDescriptor: PeerDescriptor) => {
311
- this.peerManager!.handleConnected(peerDescriptor)
328
+ this.peerManager!.onContactConnected(peerDescriptor)
312
329
  this.emit('connected', peerDescriptor)
313
330
  })
314
331
  this.transport!.on('disconnected', (peerDescriptor: PeerDescriptor, gracefulLeave: boolean) => {
315
- this.peerManager!.handleDisconnected(getNodeIdFromPeerDescriptor(peerDescriptor), gracefulLeave)
332
+ this.peerManager!.onContactDisconnected(getNodeIdFromPeerDescriptor(peerDescriptor), gracefulLeave)
316
333
  this.emit('disconnected', peerDescriptor, gracefulLeave)
317
334
  })
318
335
  this.transport!.getConnections().forEach((peer) => {
319
- this.peerManager!.handleConnected(peer)
336
+ this.peerManager!.onContactConnected(peer)
320
337
  })
321
338
  }
322
339
 
@@ -330,7 +347,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
330
347
  return this.peerManager!.getClosestNeighborsTo(nodeId, limit)
331
348
  .map((dhtPeer: DhtNodeRpcRemote) => dhtPeer.getPeerDescriptor())
332
349
  },
333
- addNewContact: (contact: PeerDescriptor) => this.peerManager!.handleNewPeers([contact]),
350
+ addContact: (contact: PeerDescriptor) => this.peerManager!.addContact([contact]),
334
351
  removeContact: (nodeId: DhtAddress) => this.removeContact(nodeId)
335
352
  })
336
353
  this.rpcCommunicator!.registerRpcMethod(ClosestPeersRequest, ClosestPeersResponse, 'getClosestPeers',
@@ -400,7 +417,7 @@ export class DhtNode extends EventEmitter<Events> implements ITransport {
400
417
  if (!this.started) { // the stopped state is checked in PeerManager
401
418
  return
402
419
  }
403
- this.peerManager!.handlePeerLeaving(nodeId)
420
+ this.peerManager!.removeContact(nodeId)
404
421
  }
405
422
 
406
423
  public async send(msg: Message): Promise<void> {
@@ -15,7 +15,7 @@ import { DhtAddress, getDhtAddressFromRaw, getNodeIdFromPeerDescriptor } from '.
15
15
  interface DhtNodeRpcLocalConfig {
16
16
  peerDiscoveryQueryBatchSize: number
17
17
  getClosestPeersTo: (nodeId: DhtAddress, limit: number) => PeerDescriptor[]
18
- addNewContact: (contact: PeerDescriptor) => void
18
+ addContact: (contact: PeerDescriptor) => void
19
19
  removeContact: (nodeId: DhtAddress) => void
20
20
  }
21
21
 
@@ -30,7 +30,7 @@ export class DhtNodeRpcLocal implements IDhtNodeRpc {
30
30
  }
31
31
 
32
32
  async getClosestPeers(request: ClosestPeersRequest, context: ServerCallContext): Promise<ClosestPeersResponse> {
33
- this.config.addNewContact((context as DhtCallContext).incomingSourceDescriptor!)
33
+ this.config.addContact((context as DhtCallContext).incomingSourceDescriptor!)
34
34
  const response = {
35
35
  peers: this.config.getClosestPeersTo(getDhtAddressFromRaw(request.nodeId), this.config.peerDiscoveryQueryBatchSize),
36
36
  requestId: request.requestId
@@ -41,7 +41,7 @@ export class DhtNodeRpcLocal implements IDhtNodeRpc {
41
41
  async ping(request: PingRequest, context: ServerCallContext): Promise<PingResponse> {
42
42
  logger.trace('received ping request: ' + getNodeIdFromPeerDescriptor((context as DhtCallContext).incomingSourceDescriptor!))
43
43
  setImmediate(() => {
44
- this.config.addNewContact((context as DhtCallContext).incomingSourceDescriptor!)
44
+ this.config.addContact((context as DhtCallContext).incomingSourceDescriptor!)
45
45
  })
46
46
  const response: PingResponse = {
47
47
  requestId: request.requestId
@@ -25,9 +25,9 @@ interface PeerManagerConfig {
25
25
  }
26
26
 
27
27
  export interface PeerManagerEvents {
28
- newContact: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
28
+ contactAdded: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
29
29
  contactRemoved: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
30
- newRandomContact: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
30
+ randomContactAdded: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
31
31
  randomContactRemoved: (peerDescriptor: PeerDescriptor, closestPeers: PeerDescriptor[]) => void
32
32
  kBucketEmpty: () => void
33
33
  }
@@ -81,15 +81,15 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
81
81
  this.emit('contactRemoved', removedContact.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
82
82
  this.randomPeers.addContact(this.config.createDhtNodeRpcRemote(removedContact.getPeerDescriptor()))
83
83
  })
84
- this.contacts.on('newContact', (newContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
85
- this.emit('newContact', newContact.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
84
+ this.contacts.on('contactAdded', (contactAdded: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
85
+ this.emit('contactAdded', contactAdded.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
86
86
  )
87
87
  this.randomPeers = new RandomContactList(this.config.localNodeId, this.config.maxContactListSize)
88
88
  this.randomPeers.on('contactRemoved', (removedContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
89
89
  this.emit('randomContactRemoved', removedContact.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
90
90
  )
91
- this.randomPeers.on('newContact', (newContact: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
92
- this.emit('newRandomContact', newContact.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
91
+ this.randomPeers.on('contactAdded', (contactAdded: DhtNodeRpcRemote, activeContacts: DhtNodeRpcRemote[]) =>
92
+ this.emit('randomContactAdded', contactAdded.getPeerDescriptor(), activeContacts.map((c) => c.getPeerDescriptor()))
93
93
  )
94
94
  }
95
95
 
@@ -160,7 +160,7 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
160
160
  }
161
161
  const closest = this.getClosestActiveContactNotInBucket()
162
162
  if (closest) {
163
- this.handleNewPeers([closest.getPeerDescriptor()])
163
+ this.addContact([closest.getPeerDescriptor()])
164
164
  }
165
165
  }
166
166
 
@@ -173,7 +173,7 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
173
173
  return undefined
174
174
  }
175
175
 
176
- handleConnected(peerDescriptor: PeerDescriptor): void {
176
+ onContactConnected(peerDescriptor: PeerDescriptor): void {
177
177
  const nodeId = getNodeIdFromPeerDescriptor(peerDescriptor)
178
178
  if (nodeId === this.config.localNodeId) {
179
179
  logger.error('handleConnected() to self')
@@ -188,7 +188,7 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
188
188
  logger.trace('connected: ' + nodeId + ' ' + this.connections.size)
189
189
  }
190
190
 
191
- handleDisconnected(nodeId: DhtAddress, gracefulLeave: boolean): void {
191
+ onContactDisconnected(nodeId: DhtAddress, gracefulLeave: boolean): void {
192
192
  logger.trace('disconnected: ' + nodeId)
193
193
  this.connections.delete(nodeId)
194
194
  if (this.config.isLayer0) {
@@ -202,11 +202,7 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
202
202
  }
203
203
  }
204
204
 
205
- handlePeerLeaving(nodeId: DhtAddress): void {
206
- this.removeContact(nodeId)
207
- }
208
-
209
- private removeContact(nodeId: DhtAddress): void {
205
+ removeContact(nodeId: DhtAddress): void {
210
206
  if (this.stopped) {
211
207
  return
212
208
  }
@@ -274,16 +270,11 @@ export class PeerManager extends EventEmitter<PeerManagerEvents> {
274
270
  return this.bucket.toArray().map((rpcRemote: DhtNodeRpcRemote) => rpcRemote.getPeerDescriptor())
275
271
  }
276
272
 
277
- handlePeerActive(nodeId: DhtAddress): void {
273
+ setContactActive(nodeId: DhtAddress): void {
278
274
  this.contacts.setActive(nodeId)
279
275
  }
280
276
 
281
- handlePeerUnresponsive(nodeId: DhtAddress): void {
282
- this.bucket.remove(getRawFromDhtAddress(nodeId))
283
- this.contacts.removeContact(nodeId)
284
- }
285
-
286
- handleNewPeers(peerDescriptors: PeerDescriptor[], setActive?: boolean): void {
277
+ addContact(peerDescriptors: PeerDescriptor[], setActive?: boolean): void {
287
278
  if (this.stopped) {
288
279
  return
289
280
  }
@@ -13,7 +13,7 @@ export class ContactState<C> {
13
13
 
14
14
  export interface Events<C> {
15
15
  contactRemoved: (removedContact: C, closestContacts: C[]) => void
16
- newContact: (newContact: C, closestContacts: C[]) => void
16
+ contactAdded: (contactAdded: C, closestContacts: C[]) => void
17
17
  }
18
18
 
19
19
  export class ContactList<C extends { getNodeId: () => DhtAddress }> extends EventEmitter<Events<C>> {
@@ -29,7 +29,7 @@ export class RandomContactList<C extends { getNodeId: () => DhtAddress }> extend
29
29
  this.contactIds.push(contact.getNodeId())
30
30
  this.contactsById.set(contact.getNodeId(), new ContactState(contact))
31
31
  this.emit(
32
- 'newContact',
32
+ 'contactAdded',
33
33
  contact,
34
34
  this.getContacts()
35
35
  )
@@ -40,45 +40,47 @@ export class SortedContactList<C extends { getNodeId: () => DhtAddress }> extend
40
40
  }
41
41
 
42
42
  public addContact(contact: C): void {
43
- if (this.config.excludedNodeIds !== undefined && this.config.excludedNodeIds.has(contact.getNodeId())) {
43
+ const contactId = contact.getNodeId()
44
+ if (this.config.excludedNodeIds !== undefined && this.config.excludedNodeIds.has(contactId)) {
44
45
  return
45
46
  }
46
-
47
- if ((!this.config.allowToContainReferenceId && (this.config.referenceId === contact.getNodeId())) ||
48
- (this.config.nodeIdDistanceLimit !== undefined && this.compareIds(this.config.nodeIdDistanceLimit, contact.getNodeId()) < 0)) {
47
+ if ((!this.config.allowToContainReferenceId && (this.config.referenceId === contactId)) ||
48
+ (this.config.nodeIdDistanceLimit !== undefined && this.compareIds(this.config.nodeIdDistanceLimit, contactId) < 0)) {
49
49
  return
50
50
  }
51
- if (!this.contactsById.has(contact.getNodeId())) {
51
+ if (!this.contactsById.has(contactId)) {
52
52
  if ((this.config.maxSize === undefined) || (this.contactIds.length < this.config.maxSize)) {
53
- this.contactsById.set(contact.getNodeId(), new ContactState(contact))
54
-
55
- // eslint-disable-next-line max-len
56
- const index = sortedIndexBy(this.contactIds, contact.getNodeId(), (id: DhtAddress) => { return this.distanceToReferenceId(id) })
57
- this.contactIds.splice(index, 0, contact.getNodeId())
58
- } else if (this.compareIds(this.contactIds[this.config.maxSize - 1], contact.getNodeId()) > 0) {
53
+ this.contactsById.set(contactId, new ContactState(contact))
54
+ const index = sortedIndexBy(this.contactIds, contactId, (id: DhtAddress) => { return this.distanceToReferenceId(id) })
55
+ this.contactIds.splice(index, 0, contactId)
56
+ if (this.config.emitEvents) {
57
+ this.emit(
58
+ 'contactAdded',
59
+ contact,
60
+ this.getClosestContacts()
61
+ )
62
+ }
63
+ } else if (this.compareIds(this.contactIds[this.config.maxSize - 1], contactId) > 0) {
59
64
  const removedId = this.contactIds.pop()
60
65
  const removedContact = this.contactsById.get(removedId!)!.contact
61
66
  this.contactsById.delete(removedId!)
62
- this.contactsById.set(contact.getNodeId(), new ContactState(contact))
63
-
64
- // eslint-disable-next-line max-len
65
- const index = sortedIndexBy(this.contactIds, contact.getNodeId(), (id: DhtAddress) => { return this.distanceToReferenceId(id) })
66
- this.contactIds.splice(index, 0, contact.getNodeId())
67
+ this.contactsById.set(contactId, new ContactState(contact))
68
+ const index = sortedIndexBy(this.contactIds, contactId, (id: DhtAddress) => { return this.distanceToReferenceId(id) })
69
+ this.contactIds.splice(index, 0, contactId)
67
70
  if (this.config.emitEvents) {
71
+ const closestContacts = this.getClosestContacts()
68
72
  this.emit(
69
73
  'contactRemoved',
70
74
  removedContact,
71
- this.getClosestContacts()
75
+ closestContacts
76
+ )
77
+ this.emit(
78
+ 'contactAdded',
79
+ contact,
80
+ closestContacts
72
81
  )
73
82
  }
74
83
  }
75
- if (this.config.emitEvents) {
76
- this.emit(
77
- 'newContact',
78
- contact,
79
- this.getClosestContacts()
80
- )
81
- }
82
84
  }
83
85
  }
84
86
 
@@ -34,11 +34,11 @@ export class DiscoverySession {
34
34
  this.config = config
35
35
  }
36
36
 
37
- private addNewContacts(contacts: PeerDescriptor[]): void {
37
+ private addContacts(contacts: PeerDescriptor[]): void {
38
38
  if (this.stopped) {
39
39
  return
40
40
  }
41
- this.config.peerManager.handleNewPeers(contacts)
41
+ this.config.peerManager.addContact(contacts)
42
42
  }
43
43
 
44
44
  private async getClosestPeersFromContact(contact: DhtNodeRpcRemote): Promise<PeerDescriptor[]> {
@@ -48,7 +48,7 @@ export class DiscoverySession {
48
48
  logger.trace(`Getting closest peers from contact: ${getNodeIdFromPeerDescriptor(contact.getPeerDescriptor())}`)
49
49
  this.config.contactedPeers.add(contact.getNodeId())
50
50
  const returnedContacts = await contact.getClosestPeers(this.config.targetId)
51
- this.config.peerManager.handlePeerActive(contact.getNodeId())
51
+ this.config.peerManager.setContactActive(contact.getNodeId())
52
52
  return returnedContacts
53
53
  }
54
54
 
@@ -60,7 +60,7 @@ export class DiscoverySession {
60
60
  const targetId = getRawFromDhtAddress(this.config.targetId)
61
61
  const oldClosestNeighbor = this.config.peerManager.getClosestNeighborsTo(this.config.targetId, 1)[0]
62
62
  const oldClosestDistance = getDistance(targetId, getRawFromDhtAddress(oldClosestNeighbor.getNodeId()))
63
- this.addNewContacts(contacts)
63
+ this.addContacts(contacts)
64
64
  const newClosestNeighbor = this.config.peerManager.getClosestNeighborsTo(this.config.targetId, 1)[0]
65
65
  const newClosestDistance = getDistance(targetId, getRawFromDhtAddress(newClosestNeighbor.getNodeId()))
66
66
  if (newClosestDistance >= oldClosestDistance) {
@@ -73,7 +73,7 @@ export class DiscoverySession {
73
73
  return
74
74
  }
75
75
  this.ongoingClosestPeersRequests.delete(peer.getNodeId())
76
- this.config.peerManager.handlePeerUnresponsive(peer.getNodeId())
76
+ this.config.peerManager.removeContact(peer.getNodeId())
77
77
  }
78
78
 
79
79
  private findMoreContacts(): void {
@@ -75,7 +75,7 @@ export class PeerDiscovery {
75
75
  return
76
76
  }
77
77
  this.config.connectionManager?.lockConnection(entryPointDescriptor, `${this.config.serviceId}::joinDht`)
78
- this.config.peerManager.handleNewPeers([entryPointDescriptor])
78
+ this.config.peerManager.addContact([entryPointDescriptor])
79
79
  const targetId = getNodeIdFromPeerDescriptor(this.config.localPeerDescriptor)
80
80
  const sessions = [this.createSession(targetId, contactedPeers)]
81
81
  if (additionalDistantJoin.enabled) {
@@ -162,7 +162,7 @@ export class PeerDiscovery {
162
162
  await Promise.allSettled(
163
163
  nodes.map(async (peer: DhtNodeRpcRemote) => {
164
164
  const contacts = await peer.getClosestPeers(localNodeId)
165
- this.config.peerManager.handleNewPeers(contacts)
165
+ this.config.peerManager.addContact(contacts)
166
166
  })
167
167
  )
168
168
  }
@@ -28,7 +28,6 @@ export class RecursiveOperationRpcLocal implements IRecursiveOperationRpc {
28
28
  }
29
29
  const senderId = getNodeIdFromPeerDescriptor(getPreviousPeer(routedMessage) ?? routedMessage.sourcePeer!)
30
30
  logger.trace(`Received routeRequest call from ${senderId}`)
31
- this.config.addContact(routedMessage.sourcePeer!, true)
32
31
  this.config.addToDuplicateDetector(routedMessage.requestId)
33
32
  return this.config.doRouteRequest(routedMessage)
34
33
  }
@@ -5,6 +5,7 @@ import { IRouterRpc } from '../../proto/packages/dht/protos/DhtRpc.server'
5
5
  import { DuplicateDetector } from './DuplicateDetector'
6
6
  import { RoutingMode } from './RoutingSession'
7
7
  import { areEqualPeerDescriptors, getDhtAddressFromRaw, getNodeIdFromPeerDescriptor } from '../../identifiers'
8
+ import { v4 } from 'uuid'
8
9
 
9
10
  interface RouterRpcLocalConfig {
10
11
  doRouteMessage: (routedMessage: RouteMessageWrapper, mode?: RoutingMode) => RouteMessageAck
@@ -40,7 +41,6 @@ export class RouterRpcLocal implements IRouterRpc {
40
41
  return createRouteMessageAck(routedMessage, RouteMessageError.DUPLICATE)
41
42
  }
42
43
  logger.trace(`Processing received routeMessage ${routedMessage.requestId}`)
43
- this.config.addContact(routedMessage.sourcePeer!, true)
44
44
  this.config.duplicateRequestDetector.add(routedMessage.requestId)
45
45
  if (areEqualBinaries(this.config.localPeerDescriptor.nodeId, routedMessage.target)) {
46
46
  logger.trace(`routing message targeted to self ${routedMessage.requestId}`)
@@ -59,7 +59,6 @@ export class RouterRpcLocal implements IRouterRpc {
59
59
  return createRouteMessageAck(forwardMessage, RouteMessageError.DUPLICATE)
60
60
  }
61
61
  logger.trace(`Processing received forward routeMessage ${forwardMessage.requestId}`)
62
- this.config.addContact(forwardMessage.sourcePeer!, true)
63
62
  this.config.duplicateRequestDetector.add(forwardMessage.requestId)
64
63
  if (areEqualBinaries(this.config.localPeerDescriptor.nodeId, forwardMessage.target)) {
65
64
  return this.forwardToDestination(forwardMessage)
@@ -75,7 +74,7 @@ export class RouterRpcLocal implements IRouterRpc {
75
74
  this.config.connectionManager?.handleMessage(forwardedMessage)
76
75
  return createRouteMessageAck(routedMessage)
77
76
  }
78
- return this.config.doRouteMessage({ ...routedMessage, target: forwardedMessage.targetDescriptor!.nodeId })
77
+ return this.config.doRouteMessage({ ...routedMessage, requestId: v4(), target: forwardedMessage.targetDescriptor!.nodeId })
79
78
  }
80
79
 
81
80
  }
@@ -17,6 +17,7 @@ import { pull } from 'lodash'
17
17
  const logger = new Logger(module)
18
18
 
19
19
  const MAX_FAILED_HOPS = 2
20
+ const CONTACT_LIST_MAX_SIZE = 10
20
21
 
21
22
  class RemoteContact extends Contact {
22
23
 
@@ -90,7 +91,7 @@ export class RoutingSession extends EventEmitter<RoutingSessionEvents> {
90
91
  const previousId = previousPeer ? getNodeIdFromPeerDescriptor(previousPeer) : undefined
91
92
  this.contactList = new SortedContactList({
92
93
  referenceId: getDhtAddressFromRaw(config.routedMessage.target),
93
- maxSize: 10000, // TODO use config option or named constant?
94
+ maxSize: CONTACT_LIST_MAX_SIZE,
94
95
  allowToContainReferenceId: true,
95
96
  nodeIdDistanceLimit: previousId,
96
97
  excludedNodeIds: config.excludedNodeIds,
@@ -57,11 +57,8 @@ export class LocalDataStore {
57
57
  }
58
58
  }
59
59
 
60
- public setStale(key: DhtAddress, creator: DhtAddress, stale: boolean): void {
61
- const storedEntry = this.store.get(key)?.get(creator)
62
- if (storedEntry) {
63
- storedEntry.stale = stale
64
- }
60
+ public keys(): IterableIterator<DhtAddress> {
61
+ return this.store.keys()
65
62
  }
66
63
 
67
64
  public setAllEntriesAsStale(key: DhtAddress): void {
@@ -13,7 +13,6 @@ import { Timestamp } from '../../proto/google/protobuf/timestamp'
13
13
  import { SortedContactList } from '../contact/SortedContactList'
14
14
  import { Contact } from '../contact/Contact'
15
15
  import { ServiceID } from '../../types/ServiceID'
16
- import { findIndex } from 'lodash'
17
16
  import { DhtAddress, areEqualPeerDescriptors, getDhtAddressFromRaw, getNodeIdFromPeerDescriptor, getRawFromDhtAddress } from '../../identifiers'
18
17
  import { StoreRpcLocal } from './StoreRpcLocal'
19
18
  import { getDistance } from '../PeerManager'
@@ -53,20 +52,18 @@ export class StoreManager {
53
52
  (request: ReplicateDataRequest, context: ServerCallContext) => rpcLocal.replicateData(request, context))
54
53
  }
55
54
 
56
- onNewContact(peerDescriptor: PeerDescriptor): void {
57
- for (const dataEntry of this.config.localDataStore.values()) {
58
- this.replicateAndUpdateStaleState(dataEntry, peerDescriptor)
55
+ onContactAdded(peerDescriptor: PeerDescriptor): void {
56
+ for (const key of this.config.localDataStore.keys()) {
57
+ this.replicateAndUpdateStaleState(key, peerDescriptor)
59
58
  }
60
59
  }
61
60
 
62
- private replicateAndUpdateStaleState(dataEntry: DataEntry, newNode: PeerDescriptor): void {
61
+ private replicateAndUpdateStaleState(key: DhtAddress, newNode: PeerDescriptor): void {
63
62
  const newNodeId = getNodeIdFromPeerDescriptor(newNode)
64
- const key = getDhtAddressFromRaw(dataEntry.key)
65
- // TODO use config option or named constant?
66
- const closestToData = this.config.getClosestNeighborsTo(key, 10)
63
+ const closestToData = this.config.getClosestNeighborsTo(key, this.config.redundancyFactor)
67
64
  const sortedList = new SortedContactList<Contact>({
68
65
  referenceId: key,
69
- maxSize: 20, // TODO use config option or named constant?
66
+ maxSize: this.config.redundancyFactor,
70
67
  allowToContainReferenceId: true,
71
68
  emitEvents: false
72
69
  })
@@ -79,18 +76,14 @@ export class StoreManager {
79
76
  const selfIsPrimaryStorer = (sortedList.getClosestContactId() === getNodeIdFromPeerDescriptor(this.config.localPeerDescriptor))
80
77
  if (selfIsPrimaryStorer) {
81
78
  sortedList.addContact(new Contact(newNode))
82
- const sorted = sortedList.getContactIds()
83
- // findIndex should never return -1 here because we just added the new node to the list
84
- const index = findIndex(sorted, (nodeId) => (nodeId === newNodeId))
85
- // if new node is within the storageRedundancyFactor closest nodes to the data
86
- // do replicate data to it
87
- if (index < this.config.redundancyFactor) {
79
+ if (sortedList.getContact(newNodeId) !== undefined) {
88
80
  setImmediate(async () => {
89
- await this.replicateDataToContact(dataEntry, newNode)
81
+ const dataEntries = Array.from(this.config.localDataStore.values(key))
82
+ await Promise.all(dataEntries.map(async (dataEntry) => this.replicateDataToContact(dataEntry, newNode)))
90
83
  })
91
84
  }
92
85
  } else if (!this.selfIsWithinRedundancyFactor(key)) {
93
- this.config.localDataStore.setStale(key, getDhtAddressFromRaw(dataEntry.creator), true)
86
+ this.config.localDataStore.setAllEntriesAsStale(key)
94
87
  }
95
88
  }
96
89
 
@@ -3,7 +3,7 @@ import crypto from 'crypto'
3
3
  import { PeerDescriptor } from './proto/packages/dht/protos/DhtRpc'
4
4
 
5
5
  // https://www.scs.stanford.edu/~dm/home/papers/kpos.pdf
6
- const KADEMLIA_ID_LENGTH_IN_BYTES = 20
6
+ export const KADEMLIA_ID_LENGTH_IN_BYTES = 20
7
7
 
8
8
  export type DhtAddress = BrandedString<'DhtAddress'>
9
9
  export type DhtAddressRaw = Uint8Array