@libp2p/kad-dht 12.0.14 → 12.0.15-43046b9ae
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/index.min.js +4 -4
- package/dist/src/constants.d.ts.map +1 -1
- package/dist/src/constants.js +1 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/content-routing/index.d.ts.map +1 -1
- package/dist/src/content-routing/index.js +3 -2
- package/dist/src/content-routing/index.js.map +1 -1
- package/dist/src/index.d.ts +34 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/kad-dht.d.ts.map +1 -1
- package/dist/src/kad-dht.js +3 -19
- package/dist/src/kad-dht.js.map +1 -1
- package/dist/src/network.d.ts +3 -0
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +33 -8
- package/dist/src/network.js.map +1 -1
- package/dist/src/peer-list/peer-distance-list.d.ts +13 -4
- package/dist/src/peer-list/peer-distance-list.d.ts.map +1 -1
- package/dist/src/peer-list/peer-distance-list.js +29 -21
- package/dist/src/peer-list/peer-distance-list.js.map +1 -1
- package/dist/src/peer-routing/index.d.ts +5 -5
- package/dist/src/peer-routing/index.d.ts.map +1 -1
- package/dist/src/peer-routing/index.js +16 -25
- package/dist/src/peer-routing/index.js.map +1 -1
- package/dist/src/query/manager.d.ts +3 -0
- package/dist/src/query/manager.d.ts.map +1 -1
- package/dist/src/query/manager.js +14 -5
- package/dist/src/query/manager.js.map +1 -1
- package/dist/src/query/query-path.d.ts +6 -6
- package/dist/src/query/query-path.d.ts.map +1 -1
- package/dist/src/query/query-path.js +32 -20
- package/dist/src/query/query-path.js.map +1 -1
- package/dist/src/routing-table/index.d.ts +11 -5
- package/dist/src/routing-table/index.d.ts.map +1 -1
- package/dist/src/routing-table/index.js +84 -42
- package/dist/src/routing-table/index.js.map +1 -1
- package/dist/src/routing-table/k-bucket.d.ts +80 -115
- package/dist/src/routing-table/k-bucket.d.ts.map +1 -1
- package/dist/src/routing-table/k-bucket.js +165 -311
- package/dist/src/routing-table/k-bucket.js.map +1 -1
- package/dist/src/routing-table/refresh.d.ts +1 -1
- package/dist/src/routing-table/refresh.d.ts.map +1 -1
- package/dist/src/routing-table/refresh.js +10 -5
- package/dist/src/routing-table/refresh.js.map +1 -1
- package/dist/src/rpc/handlers/find-node.d.ts +3 -0
- package/dist/src/rpc/handlers/find-node.d.ts.map +1 -1
- package/dist/src/rpc/handlers/find-node.js +10 -1
- package/dist/src/rpc/handlers/find-node.js.map +1 -1
- package/package.json +14 -16
- package/src/constants.ts +1 -1
- package/src/content-routing/index.ts +3 -2
- package/src/index.ts +37 -1
- package/src/kad-dht.ts +19 -21
- package/src/network.ts +38 -9
- package/src/peer-list/peer-distance-list.ts +36 -25
- package/src/peer-routing/index.ts +20 -29
- package/src/query/manager.ts +18 -5
- package/src/query/query-path.ts +46 -30
- package/src/routing-table/index.ts +100 -46
- package/src/routing-table/k-bucket.ts +214 -359
- package/src/routing-table/refresh.ts +11 -5
- package/src/rpc/handlers/find-node.ts +13 -1
- package/dist/src/query/utils.d.ts +0 -6
- package/dist/src/query/utils.d.ts.map +0 -1
- package/dist/src/query/utils.js +0 -53
- package/dist/src/query/utils.js.map +0 -1
- package/dist/typedoc-urls.json +0 -55
- package/src/query/utils.ts +0 -64
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from '../query/events.js'
|
|
13
13
|
import { verifyRecord } from '../record/validators.js'
|
|
14
14
|
import * as utils from '../utils.js'
|
|
15
|
-
import type { KadDHTComponents, DHTRecord,
|
|
15
|
+
import type { KadDHTComponents, DHTRecord, FinalPeerEvent, QueryEvent, Validators } from '../index.js'
|
|
16
16
|
import type { Message } from '../message/dht.js'
|
|
17
17
|
import type { Network } from '../network.js'
|
|
18
18
|
import type { QueryManager, QueryOptions } from '../query/manager.js'
|
|
@@ -195,17 +195,17 @@ export class PeerRouting {
|
|
|
195
195
|
}
|
|
196
196
|
|
|
197
197
|
/**
|
|
198
|
-
* Kademlia '
|
|
199
|
-
*
|
|
198
|
+
* Kademlia 'FIND_NODE' operation on a key, which could be the bytes from
|
|
199
|
+
* a multihash or a peer ID
|
|
200
200
|
*/
|
|
201
|
-
async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}): AsyncGenerator<
|
|
201
|
+
async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}): AsyncGenerator<QueryEvent> {
|
|
202
202
|
this.log('getClosestPeers to %b', key)
|
|
203
|
-
const
|
|
204
|
-
const tablePeers = this.routingTable.closestPeers(
|
|
203
|
+
const kadId = await utils.convertBuffer(key)
|
|
204
|
+
const tablePeers = this.routingTable.closestPeers(kadId)
|
|
205
205
|
const self = this // eslint-disable-line @typescript-eslint/no-this-alias
|
|
206
206
|
|
|
207
|
-
const peers = new PeerDistanceList(
|
|
208
|
-
await Promise.all(tablePeers.map(async peer => { await peers.add(peer) }))
|
|
207
|
+
const peers = new PeerDistanceList(kadId, this.routingTable.kBucketSize)
|
|
208
|
+
await Promise.all(tablePeers.map(async peer => { await peers.add({ id: peer, multiaddrs: [] }) }))
|
|
209
209
|
|
|
210
210
|
const getCloserPeersQuery: QueryFunc = async function * ({ peer, signal }) {
|
|
211
211
|
self.log('closerPeersSingle %s from %p', uint8ArrayToString(key, 'base32'), peer)
|
|
@@ -221,31 +221,22 @@ export class PeerRouting {
|
|
|
221
221
|
}
|
|
222
222
|
|
|
223
223
|
for await (const event of this.queryManager.run(key, getCloserPeersQuery, options)) {
|
|
224
|
-
yield event
|
|
225
|
-
|
|
226
224
|
if (event.name === 'PEER_RESPONSE') {
|
|
227
|
-
await Promise.all(event.closer.map(async peerData => {
|
|
225
|
+
await Promise.all(event.closer.map(async peerData => {
|
|
226
|
+
await peers.add(peerData)
|
|
227
|
+
}))
|
|
228
228
|
}
|
|
229
|
+
|
|
230
|
+
yield event
|
|
229
231
|
}
|
|
230
232
|
|
|
231
233
|
this.log('found %d peers close to %b', peers.length, key)
|
|
232
234
|
|
|
233
|
-
for (const
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
from: this.peerId,
|
|
239
|
-
peer: {
|
|
240
|
-
id: peerId,
|
|
241
|
-
multiaddrs: peer.addresses.map(({ multiaddr }) => multiaddr)
|
|
242
|
-
}
|
|
243
|
-
}, options)
|
|
244
|
-
} catch (err: any) {
|
|
245
|
-
if (err.code !== 'ERR_NOT_FOUND') {
|
|
246
|
-
throw err
|
|
247
|
-
}
|
|
248
|
-
}
|
|
235
|
+
for (const peer of peers.peers) {
|
|
236
|
+
yield finalPeerEvent({
|
|
237
|
+
from: this.peerId,
|
|
238
|
+
peer
|
|
239
|
+
}, options)
|
|
249
240
|
}
|
|
250
241
|
}
|
|
251
242
|
|
|
@@ -255,7 +246,7 @@ export class PeerRouting {
|
|
|
255
246
|
*
|
|
256
247
|
* Note: The peerStore is updated with new addresses found for the given peer.
|
|
257
248
|
*/
|
|
258
|
-
async * getValueOrPeers (peer: PeerId, key: Uint8Array, options: RoutingOptions = {}): AsyncGenerator<
|
|
249
|
+
async * getValueOrPeers (peer: PeerId, key: Uint8Array, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
|
|
259
250
|
for await (const event of this._getValueSingle(peer, key, options)) {
|
|
260
251
|
if (event.name === 'PEER_RESPONSE') {
|
|
261
252
|
if (event.record != null) {
|
|
@@ -319,7 +310,7 @@ export class PeerRouting {
|
|
|
319
310
|
if (output.length > 0) {
|
|
320
311
|
this.log('getCloserPeersOffline found %d peer(s) closer to %b than %p', output.length, key, closerThan)
|
|
321
312
|
} else {
|
|
322
|
-
this.log('getCloserPeersOffline could not find peer closer to %b than %p', key, closerThan)
|
|
313
|
+
this.log('getCloserPeersOffline could not find peer closer to %b than %p with %d peers in the routing table', key, closerThan, this.routingTable.size)
|
|
323
314
|
}
|
|
324
315
|
|
|
325
316
|
return output
|
package/src/query/manager.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setMaxListeners } from '@libp2p/interface'
|
|
2
2
|
import { PeerSet } from '@libp2p/peer-collections'
|
|
3
3
|
import { anySignal } from 'any-signal'
|
|
4
4
|
import merge from 'it-merge'
|
|
@@ -13,6 +13,7 @@ import type { QueryFunc } from './types.js'
|
|
|
13
13
|
import type { QueryEvent } from '../index.js'
|
|
14
14
|
import type { RoutingTable } from '../routing-table/index.js'
|
|
15
15
|
import type { ComponentLogger, Metric, Metrics, PeerId, RoutingOptions, Startable } from '@libp2p/interface'
|
|
16
|
+
import type { ConnectionManager } from '@libp2p/interface-internal'
|
|
16
17
|
import type { DeferredPromise } from 'p-defer'
|
|
17
18
|
|
|
18
19
|
export interface CleanUpEvents {
|
|
@@ -31,6 +32,7 @@ export interface QueryManagerComponents {
|
|
|
31
32
|
peerId: PeerId
|
|
32
33
|
metrics?: Metrics
|
|
33
34
|
logger: ComponentLogger
|
|
35
|
+
connectionManager: ConnectionManager
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
export interface QueryOptions extends RoutingOptions {
|
|
@@ -53,6 +55,7 @@ export class QueryManager implements Startable {
|
|
|
53
55
|
private queries: number
|
|
54
56
|
private readonly logger: ComponentLogger
|
|
55
57
|
private readonly peerId: PeerId
|
|
58
|
+
private readonly connectionManager: ConnectionManager
|
|
56
59
|
private readonly routingTable: RoutingTable
|
|
57
60
|
private initialQuerySelfHasRun?: DeferredPromise<void>
|
|
58
61
|
private readonly logPrefix: string
|
|
@@ -73,6 +76,7 @@ export class QueryManager implements Startable {
|
|
|
73
76
|
this.routingTable = init.routingTable
|
|
74
77
|
this.logger = components.logger
|
|
75
78
|
this.peerId = components.peerId
|
|
79
|
+
this.connectionManager = components.connectionManager
|
|
76
80
|
|
|
77
81
|
if (components.metrics != null) {
|
|
78
82
|
this.metrics = {
|
|
@@ -151,7 +155,6 @@ export class QueryManager implements Startable {
|
|
|
151
155
|
|
|
152
156
|
// query a subset of peers up to `kBucketSize / 2` in length
|
|
153
157
|
const startTime = Date.now()
|
|
154
|
-
const cleanUp = new TypedEventEmitter<CleanUpEvents>()
|
|
155
158
|
let queryFinished = false
|
|
156
159
|
|
|
157
160
|
try {
|
|
@@ -190,11 +193,11 @@ export class QueryManager implements Startable {
|
|
|
190
193
|
pathIndex: index,
|
|
191
194
|
numPaths: peersToQuery.length,
|
|
192
195
|
alpha: this.alpha,
|
|
193
|
-
cleanUp,
|
|
194
196
|
queryFuncTimeout: options.queryFuncTimeout,
|
|
195
197
|
log,
|
|
196
198
|
peersSeen,
|
|
197
|
-
onProgress: options.onProgress
|
|
199
|
+
onProgress: options.onProgress,
|
|
200
|
+
connectionManager: this.connectionManager
|
|
198
201
|
})
|
|
199
202
|
})
|
|
200
203
|
|
|
@@ -204,6 +207,17 @@ export class QueryManager implements Startable {
|
|
|
204
207
|
log.error('query error', event.error)
|
|
205
208
|
}
|
|
206
209
|
|
|
210
|
+
if (event.name === 'PEER_RESPONSE') {
|
|
211
|
+
for (const peer of [...event.closer, ...event.providers]) {
|
|
212
|
+
// eslint-disable-next-line max-depth
|
|
213
|
+
if (!(await this.connectionManager.isDialable(peer.multiaddrs))) {
|
|
214
|
+
continue
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
await this.routingTable.add(peer.id)
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
207
221
|
yield event
|
|
208
222
|
}
|
|
209
223
|
|
|
@@ -229,7 +243,6 @@ export class QueryManager implements Startable {
|
|
|
229
243
|
stopQueryTimer()
|
|
230
244
|
}
|
|
231
245
|
|
|
232
|
-
cleanUp.dispatchEvent(new CustomEvent('cleanup'))
|
|
233
246
|
log('query:done in %dms', Date.now() - startTime)
|
|
234
247
|
}
|
|
235
248
|
}
|
package/src/query/query-path.ts
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
import { setMaxListeners } from '@libp2p/interface'
|
|
1
|
+
import { CodeError, setMaxListeners } from '@libp2p/interface'
|
|
2
|
+
import { Queue } from '@libp2p/utils/queue'
|
|
2
3
|
import { anySignal } from 'any-signal'
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import { xor } from 'uint8arrays/xor'
|
|
4
|
+
import { xor as uint8ArrayXor } from 'uint8arrays/xor'
|
|
5
|
+
import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
|
|
6
6
|
import { convertPeerId, convertBuffer } from '../utils.js'
|
|
7
7
|
import { queryErrorEvent } from './events.js'
|
|
8
|
-
import { queueToGenerator } from './utils.js'
|
|
9
|
-
import type { CleanUpEvents } from './manager.js'
|
|
10
8
|
import type { QueryEvent } from '../index.js'
|
|
11
9
|
import type { QueryFunc } from '../query/types.js'
|
|
12
|
-
import type { Logger,
|
|
10
|
+
import type { Logger, PeerId, RoutingOptions, AbortOptions } from '@libp2p/interface'
|
|
11
|
+
import type { ConnectionManager } from '@libp2p/interface-internal'
|
|
13
12
|
import type { PeerSet } from '@libp2p/peer-collections'
|
|
14
13
|
|
|
15
|
-
const MAX_XOR = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF')
|
|
16
|
-
|
|
17
14
|
export interface QueryPathOptions extends RoutingOptions {
|
|
18
15
|
/**
|
|
19
16
|
* What are we trying to find
|
|
@@ -55,11 +52,6 @@ export interface QueryPathOptions extends RoutingOptions {
|
|
|
55
52
|
*/
|
|
56
53
|
numPaths: number
|
|
57
54
|
|
|
58
|
-
/**
|
|
59
|
-
* will emit a 'cleanup' event if the caller exits the for..await of early
|
|
60
|
-
*/
|
|
61
|
-
cleanUp: TypedEventTarget<CleanUpEvents>
|
|
62
|
-
|
|
63
55
|
/**
|
|
64
56
|
* A timeout for queryFunc in ms
|
|
65
57
|
*/
|
|
@@ -74,6 +66,15 @@ export interface QueryPathOptions extends RoutingOptions {
|
|
|
74
66
|
* Set of peers seen by this and other paths
|
|
75
67
|
*/
|
|
76
68
|
peersSeen: PeerSet
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The libp2p connection manager
|
|
72
|
+
*/
|
|
73
|
+
connectionManager: ConnectionManager
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface QueryQueueOptions extends AbortOptions {
|
|
77
|
+
distance: Uint8Array
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
/**
|
|
@@ -81,11 +82,12 @@ export interface QueryPathOptions extends RoutingOptions {
|
|
|
81
82
|
* every peer encountered that we have not seen before
|
|
82
83
|
*/
|
|
83
84
|
export async function * queryPath (options: QueryPathOptions): AsyncGenerator<QueryEvent, void, undefined> {
|
|
84
|
-
const { key, startingPeer, ourPeerId, signal, query, alpha, pathIndex, numPaths,
|
|
85
|
+
const { key, startingPeer, ourPeerId, signal, query, alpha, pathIndex, numPaths, queryFuncTimeout, log, peersSeen, connectionManager } = options
|
|
85
86
|
// Only ALPHA node/value lookups are allowed at any given time for each process
|
|
86
87
|
// https://github.com/libp2p/specs/tree/master/kad-dht#alpha-concurrency-parameter-%CE%B1
|
|
87
|
-
const queue = new Queue({
|
|
88
|
-
concurrency: alpha
|
|
88
|
+
const queue = new Queue<QueryEvent | undefined, QueryQueueOptions>({
|
|
89
|
+
concurrency: alpha,
|
|
90
|
+
sort: (a, b) => uint8ArrayXorCompare(a.options.distance, b.options.distance)
|
|
89
91
|
})
|
|
90
92
|
|
|
91
93
|
// perform lookups on kadId, not the actual value
|
|
@@ -102,7 +104,7 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
|
|
|
102
104
|
|
|
103
105
|
peersSeen.add(peer)
|
|
104
106
|
|
|
105
|
-
const peerXor =
|
|
107
|
+
const peerXor = uint8ArrayXor(peerKadId, kadId)
|
|
106
108
|
|
|
107
109
|
queue.add(async () => {
|
|
108
110
|
const signals = [signal]
|
|
@@ -141,11 +143,16 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
|
|
|
141
143
|
continue
|
|
142
144
|
}
|
|
143
145
|
|
|
146
|
+
if (!(await connectionManager.isDialable(closerPeer.multiaddrs))) { // eslint-disable-line max-depth
|
|
147
|
+
log('not querying undialable peer')
|
|
148
|
+
continue
|
|
149
|
+
}
|
|
150
|
+
|
|
144
151
|
const closerPeerKadId = await convertPeerId(closerPeer.id)
|
|
145
|
-
const closerPeerXor =
|
|
152
|
+
const closerPeerXor = uint8ArrayXor(closerPeerKadId, kadId)
|
|
146
153
|
|
|
147
154
|
// only continue query if closer peer is actually closer
|
|
148
|
-
if (closerPeerXor
|
|
155
|
+
if (uint8ArrayXorCompare(closerPeerXor, peerXor) !== -1) { // eslint-disable-line max-depth
|
|
149
156
|
log('skipping %p as they are not closer to %b than %p', closerPeer.id, key, peer)
|
|
150
157
|
continue
|
|
151
158
|
}
|
|
@@ -154,7 +161,10 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
|
|
|
154
161
|
queryPeer(closerPeer.id, closerPeerKadId)
|
|
155
162
|
}
|
|
156
163
|
}
|
|
157
|
-
|
|
164
|
+
|
|
165
|
+
queue.safeDispatchEvent('completed', {
|
|
166
|
+
detail: event
|
|
167
|
+
})
|
|
158
168
|
}
|
|
159
169
|
} catch (err: any) {
|
|
160
170
|
if (!signal.aborted) {
|
|
@@ -167,13 +177,7 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
|
|
|
167
177
|
compoundSignal.clear()
|
|
168
178
|
}
|
|
169
179
|
}, {
|
|
170
|
-
|
|
171
|
-
// subtract it from MAX_XOR because higher priority values execute sooner
|
|
172
|
-
|
|
173
|
-
// @ts-expect-error this is supposed to be a Number but it's ok to use BigInts
|
|
174
|
-
// as long as all priorities are BigInts since we won't mix BigInts and Number
|
|
175
|
-
// values in arithmetic operations
|
|
176
|
-
priority: MAX_XOR - peerXor
|
|
180
|
+
distance: peerXor
|
|
177
181
|
}).catch(err => {
|
|
178
182
|
log.error(err)
|
|
179
183
|
})
|
|
@@ -182,6 +186,18 @@ export async function * queryPath (options: QueryPathOptions): AsyncGenerator<Qu
|
|
|
182
186
|
// begin the query with the starting peer
|
|
183
187
|
queryPeer(startingPeer, await convertPeerId(startingPeer))
|
|
184
188
|
|
|
185
|
-
|
|
186
|
-
|
|
189
|
+
try {
|
|
190
|
+
// yield results as they come in
|
|
191
|
+
for await (const event of queue.toGenerator({ signal })) {
|
|
192
|
+
if (event != null) {
|
|
193
|
+
yield event
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
if (signal.aborted) {
|
|
198
|
+
throw new CodeError('Query aborted', 'ERR_QUERY_ABORTED')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
throw err
|
|
202
|
+
}
|
|
187
203
|
}
|
|
@@ -4,19 +4,22 @@ import { PeerQueue } from '@libp2p/utils/peer-queue'
|
|
|
4
4
|
import { pbStream } from 'it-protobuf-stream'
|
|
5
5
|
import { Message, MessageType } from '../message/dht.js'
|
|
6
6
|
import * as utils from '../utils.js'
|
|
7
|
-
import { KBucket, type PingEventDetails } from './k-bucket.js'
|
|
7
|
+
import { KBucket, isLeafBucket, type Bucket, type PingEventDetails } from './k-bucket.js'
|
|
8
8
|
import type { ComponentLogger, Logger, Metric, Metrics, PeerId, PeerStore, Startable, Stream } from '@libp2p/interface'
|
|
9
9
|
import type { ConnectionManager } from '@libp2p/interface-internal'
|
|
10
10
|
|
|
11
11
|
export const KAD_CLOSE_TAG_NAME = 'kad-close'
|
|
12
12
|
export const KAD_CLOSE_TAG_VALUE = 50
|
|
13
13
|
export const KBUCKET_SIZE = 20
|
|
14
|
+
export const PREFIX_LENGTH = 32
|
|
14
15
|
export const PING_TIMEOUT = 10000
|
|
15
16
|
export const PING_CONCURRENCY = 10
|
|
16
17
|
|
|
17
18
|
export interface RoutingTableInit {
|
|
18
19
|
logPrefix: string
|
|
19
20
|
protocol: string
|
|
21
|
+
prefixLength?: number
|
|
22
|
+
splitThreshold?: number
|
|
20
23
|
kBucketSize?: number
|
|
21
24
|
pingTimeout?: number
|
|
22
25
|
pingConcurrency?: number
|
|
@@ -48,6 +51,8 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
48
51
|
|
|
49
52
|
private readonly log: Logger
|
|
50
53
|
private readonly components: RoutingTableComponents
|
|
54
|
+
private readonly prefixLength: number
|
|
55
|
+
private readonly splitThreshold: number
|
|
51
56
|
private readonly pingTimeout: number
|
|
52
57
|
private readonly pingConcurrency: number
|
|
53
58
|
private running: boolean
|
|
@@ -56,26 +61,29 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
56
61
|
private readonly tagValue: number
|
|
57
62
|
private readonly metrics?: {
|
|
58
63
|
routingTableSize: Metric
|
|
64
|
+
routingTableKadBucketTotal: Metric
|
|
65
|
+
routingTableKadBucketAverageOccupancy: Metric
|
|
66
|
+
routingTableKadBucketMaxDepth: Metric
|
|
59
67
|
}
|
|
60
68
|
|
|
61
69
|
constructor (components: RoutingTableComponents, init: RoutingTableInit) {
|
|
62
70
|
super()
|
|
63
71
|
|
|
64
|
-
const { kBucketSize, pingTimeout, logPrefix, pingConcurrency, protocol, tagName, tagValue } = init
|
|
65
|
-
|
|
66
72
|
this.components = components
|
|
67
|
-
this.log = components.logger.forComponent(`${logPrefix}:routing-table`)
|
|
68
|
-
this.kBucketSize = kBucketSize ?? KBUCKET_SIZE
|
|
69
|
-
this.pingTimeout = pingTimeout ?? PING_TIMEOUT
|
|
70
|
-
this.pingConcurrency = pingConcurrency ?? PING_CONCURRENCY
|
|
73
|
+
this.log = components.logger.forComponent(`${init.logPrefix}:routing-table`)
|
|
74
|
+
this.kBucketSize = init.kBucketSize ?? KBUCKET_SIZE
|
|
75
|
+
this.pingTimeout = init.pingTimeout ?? PING_TIMEOUT
|
|
76
|
+
this.pingConcurrency = init.pingConcurrency ?? PING_CONCURRENCY
|
|
71
77
|
this.running = false
|
|
72
|
-
this.protocol = protocol
|
|
73
|
-
this.tagName = tagName ?? KAD_CLOSE_TAG_NAME
|
|
74
|
-
this.tagValue = tagValue ?? KAD_CLOSE_TAG_VALUE
|
|
78
|
+
this.protocol = init.protocol
|
|
79
|
+
this.tagName = init.tagName ?? KAD_CLOSE_TAG_NAME
|
|
80
|
+
this.tagValue = init.tagValue ?? KAD_CLOSE_TAG_VALUE
|
|
81
|
+
this.prefixLength = init.prefixLength ?? PREFIX_LENGTH
|
|
82
|
+
this.splitThreshold = init.splitThreshold ?? KBUCKET_SIZE
|
|
75
83
|
|
|
76
84
|
this.pingQueue = new PeerQueue({
|
|
77
85
|
concurrency: this.pingConcurrency,
|
|
78
|
-
metricName: `${logPrefix.replaceAll(':', '_')}_ping_queue`,
|
|
86
|
+
metricName: `${init.logPrefix.replaceAll(':', '_')}_ping_queue`,
|
|
79
87
|
metrics: this.components.metrics
|
|
80
88
|
})
|
|
81
89
|
this.pingQueue.addEventListener('error', evt => {
|
|
@@ -84,7 +92,10 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
84
92
|
|
|
85
93
|
if (this.components.metrics != null) {
|
|
86
94
|
this.metrics = {
|
|
87
|
-
routingTableSize: this.components.metrics.registerMetric(`${logPrefix.replaceAll(':', '_')}_routing_table_size`)
|
|
95
|
+
routingTableSize: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_size`),
|
|
96
|
+
routingTableKadBucketTotal: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_total`),
|
|
97
|
+
routingTableKadBucketAverageOccupancy: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_average_occupancy`),
|
|
98
|
+
routingTableKadBucketMaxDepth: this.components.metrics.registerMetric(`${init.logPrefix.replaceAll(':', '_')}_routing_table_kad_bucket_max_depth`)
|
|
88
99
|
}
|
|
89
100
|
}
|
|
90
101
|
}
|
|
@@ -97,8 +108,13 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
97
108
|
this.running = true
|
|
98
109
|
|
|
99
110
|
const kBuck = new KBucket({
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
localPeer: {
|
|
112
|
+
kadId: await utils.convertPeerId(this.components.peerId),
|
|
113
|
+
peerId: this.components.peerId
|
|
114
|
+
},
|
|
115
|
+
kBucketSize: this.kBucketSize,
|
|
116
|
+
prefixLength: this.prefixLength,
|
|
117
|
+
splitThreshold: this.splitThreshold,
|
|
102
118
|
numberOfNodesToPing: 1
|
|
103
119
|
})
|
|
104
120
|
this.kb = kBuck
|
|
@@ -110,6 +126,20 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
110
126
|
})
|
|
111
127
|
})
|
|
112
128
|
|
|
129
|
+
let peerStorePeers = 0
|
|
130
|
+
|
|
131
|
+
// add existing peers from the peer store to routing table
|
|
132
|
+
for (const peer of await this.components.peerStore.all()) {
|
|
133
|
+
if (peer.protocols.includes(this.protocol)) {
|
|
134
|
+
const id = await utils.convertPeerId(peer.id)
|
|
135
|
+
|
|
136
|
+
this.kb.add({ kadId: id, peerId: peer.id })
|
|
137
|
+
peerStorePeers++
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
this.log('added %d peer store peers to the routing table', peerStorePeers)
|
|
142
|
+
|
|
113
143
|
// tag kad-close peers
|
|
114
144
|
this._tagPeers(kBuck)
|
|
115
145
|
}
|
|
@@ -130,7 +160,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
130
160
|
|
|
131
161
|
const updatePeerTags = utils.debounce(() => {
|
|
132
162
|
const newClosest = new PeerSet(
|
|
133
|
-
kBuck.closest(kBuck.
|
|
163
|
+
kBuck.closest(kBuck.localPeer.kadId, KBUCKET_SIZE)
|
|
134
164
|
)
|
|
135
165
|
const addedPeers = newClosest.difference(kClosest)
|
|
136
166
|
const removedPeers = kClosest.difference(newClosest)
|
|
@@ -165,12 +195,12 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
165
195
|
kBuck.addEventListener('added', (evt) => {
|
|
166
196
|
updatePeerTags()
|
|
167
197
|
|
|
168
|
-
this.safeDispatchEvent('peer:add', { detail: evt.detail.
|
|
198
|
+
this.safeDispatchEvent('peer:add', { detail: evt.detail.peerId })
|
|
169
199
|
})
|
|
170
200
|
kBuck.addEventListener('removed', (evt) => {
|
|
171
201
|
updatePeerTags()
|
|
172
202
|
|
|
173
|
-
this.safeDispatchEvent('peer:remove', { detail: evt.detail.
|
|
203
|
+
this.safeDispatchEvent('peer:remove', { detail: evt.detail.peerId })
|
|
174
204
|
})
|
|
175
205
|
}
|
|
176
206
|
|
|
@@ -197,7 +227,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
197
227
|
const results = await Promise.all(
|
|
198
228
|
oldContacts.map(async oldContact => {
|
|
199
229
|
// if a previous ping wants us to ping this contact, re-use the result
|
|
200
|
-
const pingJob = this.pingQueue.find(oldContact.
|
|
230
|
+
const pingJob = this.pingQueue.find(oldContact.peerId)
|
|
201
231
|
|
|
202
232
|
if (pingJob != null) {
|
|
203
233
|
return pingJob.join()
|
|
@@ -211,8 +241,8 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
211
241
|
signal: AbortSignal.timeout(this.pingTimeout)
|
|
212
242
|
}
|
|
213
243
|
|
|
214
|
-
this.log('pinging old contact %p', oldContact.
|
|
215
|
-
const connection = await this.components.connectionManager.openConnection(oldContact.
|
|
244
|
+
this.log('pinging old contact %p', oldContact.peerId)
|
|
245
|
+
const connection = await this.components.connectionManager.openConnection(oldContact.peerId, options)
|
|
216
246
|
stream = await connection.newStream(this.protocol, options)
|
|
217
247
|
|
|
218
248
|
const pb = pbStream(stream)
|
|
@@ -232,9 +262,9 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
232
262
|
if (this.running && this.kb != null) {
|
|
233
263
|
// only evict peers if we are still running, otherwise we evict
|
|
234
264
|
// when dialing is cancelled due to shutdown in progress
|
|
235
|
-
this.log.error('could not ping peer %p', oldContact.
|
|
236
|
-
this.log('evicting old contact after ping failed %p', oldContact.
|
|
237
|
-
this.kb.remove(oldContact.
|
|
265
|
+
this.log.error('could not ping peer %p', oldContact.peerId, err)
|
|
266
|
+
this.log('evicting old contact after ping failed %p', oldContact.peerId)
|
|
267
|
+
this.kb.remove(oldContact.kadId)
|
|
238
268
|
}
|
|
239
269
|
|
|
240
270
|
stream?.abort(err)
|
|
@@ -244,7 +274,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
244
274
|
this.metrics?.routingTableSize.update(this.size)
|
|
245
275
|
}
|
|
246
276
|
}, {
|
|
247
|
-
peerId: oldContact.
|
|
277
|
+
peerId: oldContact.peerId
|
|
248
278
|
})
|
|
249
279
|
})
|
|
250
280
|
)
|
|
@@ -254,7 +284,7 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
254
284
|
.length
|
|
255
285
|
|
|
256
286
|
if (this.running && responded < oldContacts.length && this.kb != null) {
|
|
257
|
-
this.log('adding new contact %p', newContact.
|
|
287
|
+
this.log('adding new contact %p', newContact.peerId)
|
|
258
288
|
this.kb.add(newContact)
|
|
259
289
|
}
|
|
260
290
|
}
|
|
@@ -277,20 +307,14 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
277
307
|
*/
|
|
278
308
|
async find (peer: PeerId): Promise<PeerId | undefined> {
|
|
279
309
|
const key = await utils.convertPeerId(peer)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (closest != null && peer.equals(closest)) {
|
|
283
|
-
return closest
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
return undefined
|
|
310
|
+
return this.kb?.get(key)?.peerId
|
|
287
311
|
}
|
|
288
312
|
|
|
289
313
|
/**
|
|
290
|
-
* Retrieve the closest peers to the given
|
|
314
|
+
* Retrieve the closest peers to the given kadId
|
|
291
315
|
*/
|
|
292
|
-
closestPeer (
|
|
293
|
-
const res = this.closestPeers(
|
|
316
|
+
closestPeer (kadId: Uint8Array): PeerId | undefined {
|
|
317
|
+
const res = this.closestPeers(kadId, 1)
|
|
294
318
|
|
|
295
319
|
if (res.length > 0) {
|
|
296
320
|
return res[0]
|
|
@@ -300,33 +324,31 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
300
324
|
}
|
|
301
325
|
|
|
302
326
|
/**
|
|
303
|
-
* Retrieve the `count`-closest peers to the given
|
|
327
|
+
* Retrieve the `count`-closest peers to the given kadId
|
|
304
328
|
*/
|
|
305
|
-
closestPeers (
|
|
329
|
+
closestPeers (kadId: Uint8Array, count = this.kBucketSize): PeerId[] {
|
|
306
330
|
if (this.kb == null) {
|
|
307
331
|
return []
|
|
308
332
|
}
|
|
309
333
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return closest.map(p => p.peer)
|
|
334
|
+
return [...this.kb.closest(kadId, count)]
|
|
313
335
|
}
|
|
314
336
|
|
|
315
337
|
/**
|
|
316
338
|
* Add or update the routing table with the given peer
|
|
317
339
|
*/
|
|
318
|
-
async add (
|
|
340
|
+
async add (peerId: PeerId): Promise<void> {
|
|
319
341
|
if (this.kb == null) {
|
|
320
342
|
throw new Error('RoutingTable is not started')
|
|
321
343
|
}
|
|
322
344
|
|
|
323
|
-
const
|
|
345
|
+
const kadId = await utils.convertPeerId(peerId)
|
|
324
346
|
|
|
325
|
-
this.kb.add({
|
|
347
|
+
this.kb.add({ kadId, peerId })
|
|
326
348
|
|
|
327
|
-
this.log('added %p with kad id %b',
|
|
349
|
+
this.log('added %p with kad id %b', peerId, kadId)
|
|
328
350
|
|
|
329
|
-
this.
|
|
351
|
+
this.updateMetrics()
|
|
330
352
|
}
|
|
331
353
|
|
|
332
354
|
/**
|
|
@@ -341,6 +363,38 @@ export class RoutingTable extends TypedEventEmitter<RoutingTableEvents> implemen
|
|
|
341
363
|
|
|
342
364
|
this.kb.remove(id)
|
|
343
365
|
|
|
344
|
-
this.
|
|
366
|
+
this.updateMetrics()
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private updateMetrics (): void {
|
|
370
|
+
if (this.metrics == null || this.kb == null) {
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let size = 0
|
|
375
|
+
let buckets = 0
|
|
376
|
+
let maxDepth = 0
|
|
377
|
+
|
|
378
|
+
function count (bucket: Bucket): void {
|
|
379
|
+
if (isLeafBucket(bucket)) {
|
|
380
|
+
if (bucket.depth > maxDepth) {
|
|
381
|
+
maxDepth = bucket.depth
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
buckets++
|
|
385
|
+
size += bucket.peers.length
|
|
386
|
+
return
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
count(bucket.left)
|
|
390
|
+
count(bucket.right)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
count(this.kb.root)
|
|
394
|
+
|
|
395
|
+
this.metrics.routingTableSize.update(size)
|
|
396
|
+
this.metrics.routingTableKadBucketTotal.update(buckets)
|
|
397
|
+
this.metrics.routingTableKadBucketAverageOccupancy.update(Math.round(size / buckets))
|
|
398
|
+
this.metrics.routingTableKadBucketMaxDepth.update(maxDepth)
|
|
345
399
|
}
|
|
346
400
|
}
|