@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.
- package/dist/src/connection/ConnectionLockRpcRemote.js +1 -25
- package/dist/src/connection/ConnectionLockRpcRemote.js.map +1 -1
- package/dist/src/connection/ConnectionManager.d.ts +0 -1
- package/dist/src/connection/ConnectionManager.js +7 -6
- package/dist/src/connection/ConnectionManager.js.map +1 -1
- package/dist/src/connection/ConnectorFacade.d.ts +2 -2
- package/dist/src/connection/ConnectorFacade.js +2 -3
- package/dist/src/connection/ConnectorFacade.js.map +1 -1
- package/dist/src/connection/ManagedConnection.d.ts +1 -0
- package/dist/src/connection/ManagedConnection.js +11 -1
- package/dist/src/connection/ManagedConnection.js.map +1 -1
- package/dist/src/connection/connectivityChecker.js +3 -2
- package/dist/src/connection/connectivityChecker.js.map +1 -1
- package/dist/src/connection/websocket/ClientWebsocket.d.ts +1 -0
- package/dist/src/connection/websocket/ClientWebsocket.js +6 -3
- package/dist/src/connection/websocket/ClientWebsocket.js.map +1 -1
- package/dist/src/connection/websocket/ServerWebsocket.d.ts +4 -0
- package/dist/src/connection/websocket/ServerWebsocket.js +32 -21
- package/dist/src/connection/websocket/ServerWebsocket.js.map +1 -1
- package/dist/src/connection/websocket/WebsocketConnector.d.ts +0 -1
- package/dist/src/connection/websocket/WebsocketConnector.js +21 -10
- package/dist/src/connection/websocket/WebsocketConnector.js.map +1 -1
- package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.d.ts +1 -1
- package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.js +8 -11
- package/dist/src/connection/websocket/WebsocketConnectorRpcLocal.js.map +1 -1
- package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.d.ts +2 -2
- package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.js +3 -37
- package/dist/src/connection/websocket/WebsocketConnectorRpcRemote.js.map +1 -1
- package/dist/src/connection/websocket/WebsocketServer.js +21 -4
- package/dist/src/connection/websocket/WebsocketServer.js.map +1 -1
- package/dist/src/dht/DhtNode.d.ts +4 -4
- package/dist/src/dht/DhtNode.js +31 -20
- package/dist/src/dht/DhtNode.js.map +1 -1
- package/dist/src/dht/DhtNodeRpcLocal.d.ts +1 -4
- package/dist/src/dht/DhtNodeRpcLocal.js +1 -5
- package/dist/src/dht/DhtNodeRpcLocal.js.map +1 -1
- package/dist/src/dht/PeerManager.d.ts +10 -6
- package/dist/src/dht/PeerManager.js +95 -30
- package/dist/src/dht/PeerManager.js.map +1 -1
- package/dist/src/dht/contact/SortedContactList.d.ts +20 -6
- package/dist/src/dht/contact/SortedContactList.js +55 -24
- package/dist/src/dht/contact/SortedContactList.js.map +1 -1
- package/dist/src/dht/discovery/DiscoverySession.d.ts +3 -5
- package/dist/src/dht/discovery/DiscoverySession.js +11 -9
- package/dist/src/dht/discovery/DiscoverySession.js.map +1 -1
- package/dist/src/dht/discovery/PeerDiscovery.d.ts +4 -2
- package/dist/src/dht/discovery/PeerDiscovery.js +17 -16
- package/dist/src/dht/discovery/PeerDiscovery.js.map +1 -1
- package/dist/src/dht/find/FindSession.js +6 -1
- package/dist/src/dht/find/FindSession.js.map +1 -1
- package/dist/src/dht/find/Finder.js +6 -1
- package/dist/src/dht/find/Finder.js.map +1 -1
- package/dist/src/dht/routing/Router.d.ts +1 -1
- package/dist/src/dht/routing/Router.js +8 -4
- package/dist/src/dht/routing/Router.js.map +1 -1
- package/dist/src/dht/routing/RoutingSession.js +8 -1
- package/dist/src/dht/routing/RoutingSession.js.map +1 -1
- package/dist/src/dht/store/StoreRpcLocal.js +19 -5
- package/dist/src/dht/store/StoreRpcLocal.js.map +1 -1
- package/dist/src/helpers/PeerID.d.ts +1 -0
- package/dist/src/helpers/PeerID.js +7 -2
- package/dist/src/helpers/PeerID.js.map +1 -1
- package/dist/src/helpers/peerIdFromPeerDescriptor.js +2 -2
- package/dist/src/helpers/peerIdFromPeerDescriptor.js.map +1 -1
- package/package.json +5 -5
- package/src/connection/ConnectionLockRpcRemote.ts +1 -2
- package/src/connection/ConnectionManager.ts +16 -17
- package/src/connection/ConnectorFacade.ts +1 -4
- package/src/connection/ManagedConnection.ts +12 -1
- package/src/connection/connectivityChecker.ts +3 -2
- package/src/connection/websocket/ClientWebsocket.ts +5 -2
- package/src/connection/websocket/ServerWebsocket.ts +40 -25
- package/src/connection/websocket/WebsocketConnector.ts +23 -12
- package/src/connection/websocket/WebsocketConnectorRpcLocal.ts +9 -11
- package/src/connection/websocket/WebsocketConnectorRpcRemote.ts +5 -14
- package/src/connection/websocket/WebsocketServer.ts +20 -5
- package/src/dht/DhtNode.ts +32 -24
- package/src/dht/DhtNodeRpcLocal.ts +2 -9
- package/src/dht/PeerManager.ts +110 -36
- package/src/dht/contact/SortedContactList.ts +87 -44
- package/src/dht/discovery/DiscoverySession.ts +15 -14
- package/src/dht/discovery/PeerDiscovery.ts +37 -22
- package/src/dht/find/FindSession.ts +6 -1
- package/src/dht/find/Finder.ts +6 -7
- package/src/dht/routing/Router.ts +8 -4
- package/src/dht/routing/RoutingSession.ts +8 -8
- package/src/dht/store/StoreRpcLocal.ts +19 -7
- package/src/helpers/PeerID.ts +6 -2
- package/src/helpers/peerIdFromPeerDescriptor.ts +4 -4
- package/test/benchmark/Find.test.ts +1 -1
- package/test/benchmark/KademliaCorrectness.test.ts +1 -1
- package/test/benchmark/SortedContactListBenchmark.test.ts +150 -0
- package/test/benchmark/WebsocketServerMemoryLeak.test.ts +41 -0
- package/test/benchmark/kademlia-simulation/SimulationNode.ts +6 -1
- package/test/end-to-end/Layer0.test.ts +4 -4
- package/test/end-to-end/Layer0MixedConnectionTypes.test.ts +10 -10
- package/test/end-to-end/Layer0Webrtc-Layer1.test.ts +4 -4
- package/test/end-to-end/Layer1-Scale-WebSocket.test.ts +2 -2
- package/test/end-to-end/Layer1-Scale-Webrtc.test.ts +2 -2
- package/test/end-to-end/RecoveryFromFailedAutoCertification.test.ts +1 -1
- package/test/end-to-end/memory-leak.test.ts +1 -0
- package/test/integration/DhtJoinPeerDiscovery.test.ts +2 -2
- package/test/integration/Layer1-scale.test.ts +1 -1
- package/test/integration/Mock-Layer1-Layer0.test.ts +15 -15
- package/test/integration/MultipleEntryPointJoining.test.ts +7 -7
- package/test/integration/ReplicateData.test.ts +6 -1
- package/test/integration/SimultaneousConnections.test.ts +81 -49
- package/test/integration/Store.test.ts +1 -1
- package/test/integration/StoreOnDhtWithTwoNodes.test.ts +1 -1
- package/test/integration/WebrtcConnectionManagement.test.ts +29 -0
- package/test/integration/WebsocketConnectionManagement.test.ts +65 -4
- package/test/integration/WebsocketConnectorRpc.test.ts +3 -5
- package/test/unit/SortedContactList.test.ts +15 -10
package/src/dht/DhtNode.ts
CHANGED
|
@@ -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
|
|
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.
|
|
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!.
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
386
|
-
const distance2 =
|
|
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(
|
|
412
|
-
return this.peerManager!.
|
|
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
|
|
420
|
-
return this.peerManager!.
|
|
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.
|
|
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
|
|
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.
|
|
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
|
-
|
|
512
|
-
|
|
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(() =>
|
|
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.
|
|
548
|
+
if (this.abortController.signal.aborted || !this.started) {
|
|
541
549
|
return
|
|
542
550
|
}
|
|
543
551
|
logger.trace('stop()')
|
|
544
|
-
this.
|
|
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.
|
|
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(() => {
|
package/src/dht/PeerManager.ts
CHANGED
|
@@ -21,7 +21,7 @@ const logger = new Logger(module)
|
|
|
21
21
|
|
|
22
22
|
interface PeerManagerConfig {
|
|
23
23
|
numberOfNodesPerKBucket: number
|
|
24
|
-
|
|
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
|
-
//
|
|
43
|
-
|
|
44
|
-
//
|
|
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
|
-
//
|
|
47
|
-
|
|
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.
|
|
71
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
155
|
-
if (!this.bucket!.get(contactId.value) && this.
|
|
156
|
-
return this.
|
|
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.
|
|
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.
|
|
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.
|
|
296
|
+
this.contacts!.setActive(peerId)
|
|
223
297
|
}
|
|
224
298
|
|
|
225
299
|
handlePeerUnresponsive(peerId: PeerID): void {
|
|
226
300
|
this.bucket!.remove(peerId.value)
|
|
227
|
-
this.
|
|
301
|
+
this.contacts!.removeContact(peerId)
|
|
228
302
|
}
|
|
229
303
|
|
|
230
|
-
handleNewPeers(
|
|
304
|
+
handleNewPeers(peerDescriptors: PeerDescriptor[], setActive?: boolean): void {
|
|
231
305
|
if (this.stopped) {
|
|
232
306
|
return
|
|
233
307
|
}
|
|
234
|
-
|
|
235
|
-
|
|
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
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
) {
|
|
241
|
-
this.
|
|
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 (
|
|
251
|
-
this.
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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
|
|
20
|
+
export class SortedContactList<C extends { getPeerId: () => PeerID }> extends EventEmitter<Events<C>> {
|
|
6
21
|
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
private
|
|
22
|
+
private config: SortedContactListConfig
|
|
23
|
+
private contactsById: Map<PeerIDKey, ContactState<C>> = new Map()
|
|
24
|
+
private contactIds: PeerID[] = []
|
|
10
25
|
|
|
11
26
|
constructor(
|
|
12
|
-
|
|
13
|
-
maxSize: number,
|
|
14
|
-
defaultContactQueryLimit?: number,
|
|
15
|
-
allowLocalPeerId = false,
|
|
16
|
-
peerIdDistanceLimit?: PeerID,
|
|
17
|
-
excludedPeerIDs?: PeerID[]
|
|
27
|
+
config: SortedContactListConfig
|
|
18
28
|
) {
|
|
19
|
-
super(
|
|
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.
|
|
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
|
-
|
|
48
|
-
this.contactIds.
|
|
49
|
-
|
|
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
|
-
|
|
55
|
-
this.contactIds.
|
|
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
|
-
'
|
|
58
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
128
|
-
const distance2 =
|
|
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.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
}
|