@libp2p/kad-dht 15.0.2 → 15.1.0
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 +2 -15
- package/dist/index.min.js.map +7 -0
- package/dist/src/constants.d.ts +1 -1
- 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-fetching/index.d.ts +4 -4
- package/dist/src/content-fetching/index.d.ts.map +1 -1
- package/dist/src/content-fetching/index.js +39 -12
- package/dist/src/content-fetching/index.js.map +1 -1
- package/dist/src/content-routing/index.d.ts.map +1 -1
- package/dist/src/content-routing/index.js +87 -37
- package/dist/src/content-routing/index.js.map +1 -1
- package/dist/src/index.d.ts +57 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/kad-dht.d.ts +3 -1
- package/dist/src/kad-dht.d.ts.map +1 -1
- package/dist/src/kad-dht.js +14 -10
- package/dist/src/kad-dht.js.map +1 -1
- package/dist/src/network.d.ts +12 -6
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +17 -16
- package/dist/src/network.js.map +1 -1
- package/dist/src/peer-distance-list.d.ts +10 -3
- package/dist/src/peer-distance-list.d.ts.map +1 -1
- package/dist/src/peer-distance-list.js +13 -5
- package/dist/src/peer-distance-list.js.map +1 -1
- package/dist/src/peer-routing/index.d.ts +10 -8
- package/dist/src/peer-routing/index.d.ts.map +1 -1
- package/dist/src/peer-routing/index.js +78 -48
- package/dist/src/peer-routing/index.js.map +1 -1
- package/dist/src/providers.d.ts +4 -4
- package/dist/src/providers.d.ts.map +1 -1
- package/dist/src/providers.js +12 -12
- package/dist/src/providers.js.map +1 -1
- package/dist/src/query/events.d.ts +15 -3
- package/dist/src/query/events.d.ts.map +1 -1
- package/dist/src/query/events.js +9 -0
- package/dist/src/query/events.js.map +1 -1
- package/dist/src/query/manager.d.ts +2 -4
- package/dist/src/query/manager.d.ts.map +1 -1
- package/dist/src/query/manager.js +42 -16
- package/dist/src/query/manager.js.map +1 -1
- package/dist/src/query/query-path.d.ts +10 -14
- package/dist/src/query/query-path.d.ts.map +1 -1
- package/dist/src/query/query-path.js +67 -53
- package/dist/src/query/query-path.js.map +1 -1
- package/dist/src/query/types.d.ts +6 -5
- package/dist/src/query/types.d.ts.map +1 -1
- package/dist/src/query-self.d.ts +2 -3
- package/dist/src/query-self.d.ts.map +1 -1
- package/dist/src/query-self.js +11 -14
- package/dist/src/query-self.js.map +1 -1
- package/dist/src/routing-table/closest-peers.js +1 -1
- package/dist/src/routing-table/closest-peers.js.map +1 -1
- package/dist/src/routing-table/k-bucket.js +1 -1
- package/dist/src/routing-table/k-bucket.js.map +1 -1
- package/dist/src/rpc/handlers/get-providers.js +1 -1
- package/dist/src/rpc/handlers/get-providers.js.map +1 -1
- package/dist/src/utils.d.ts +1 -0
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +4 -0
- package/dist/src/utils.js.map +1 -1
- package/dist/typedoc-urls.json +4 -0
- package/package.json +12 -11
- package/src/constants.ts +1 -1
- package/src/content-fetching/index.ts +40 -13
- package/src/content-routing/index.ts +92 -44
- package/src/index.ts +64 -5
- package/src/kad-dht.ts +14 -10
- package/src/network.ts +33 -20
- package/src/peer-distance-list.ts +19 -7
- package/src/peer-routing/index.ts +83 -52
- package/src/providers.ts +13 -13
- package/src/query/events.ts +27 -3
- package/src/query/manager.ts +48 -21
- package/src/query/query-path.ts +86 -76
- package/src/query/types.ts +7 -5
- package/src/query-self.ts +15 -17
- package/src/routing-table/closest-peers.ts +1 -1
- package/src/routing-table/k-bucket.ts +1 -1
- package/src/rpc/handlers/get-providers.ts +1 -1
- package/src/utils.ts +9 -0
|
@@ -3,7 +3,6 @@ import { InvalidPublicKeyError, NotFoundError } from '@libp2p/interface'
|
|
|
3
3
|
import { peerIdFromPublicKey, peerIdFromMultihash } from '@libp2p/peer-id'
|
|
4
4
|
import { Libp2pRecord } from '@libp2p/record'
|
|
5
5
|
import * as Digest from 'multiformats/hashes/digest'
|
|
6
|
-
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
7
6
|
import { xor as uint8ArrayXor } from 'uint8arrays/xor'
|
|
8
7
|
import { xorCompare as uint8ArrayXorCompare } from 'uint8arrays/xor-compare'
|
|
9
8
|
import { QueryError, InvalidRecordError } from '../errors.js'
|
|
@@ -18,17 +17,19 @@ import { verifyRecord } from '../record/validators.js'
|
|
|
18
17
|
import { convertBuffer, convertPeerId, keyForPublicKey } from '../utils.js'
|
|
19
18
|
import type { DHTRecord, FinalPeerEvent, QueryEvent, Validators } from '../index.js'
|
|
20
19
|
import type { Message } from '../message/dht.js'
|
|
21
|
-
import type { Network } from '../network.js'
|
|
20
|
+
import type { Network, SendMessageOptions } from '../network.js'
|
|
22
21
|
import type { QueryManager, QueryOptions } from '../query/manager.js'
|
|
23
22
|
import type { QueryFunc } from '../query/types.js'
|
|
24
23
|
import type { RoutingTable } from '../routing-table/index.js'
|
|
25
24
|
import type { ComponentLogger, Logger, Metrics, PeerId, PeerInfo, PeerStore, RoutingOptions } from '@libp2p/interface'
|
|
25
|
+
import type { ConnectionManager } from '@libp2p/interface-internal'
|
|
26
26
|
|
|
27
27
|
export interface PeerRoutingComponents {
|
|
28
28
|
peerId: PeerId
|
|
29
29
|
peerStore: PeerStore
|
|
30
30
|
logger: ComponentLogger
|
|
31
31
|
metrics?: Metrics
|
|
32
|
+
connectionManager: ConnectionManager
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
export interface PeerRoutingInit {
|
|
@@ -45,16 +46,14 @@ export class PeerRouting {
|
|
|
45
46
|
private readonly network: Network
|
|
46
47
|
private readonly validators: Validators
|
|
47
48
|
private readonly queryManager: QueryManager
|
|
48
|
-
private readonly
|
|
49
|
-
private readonly peerId: PeerId
|
|
49
|
+
private readonly components: PeerRoutingComponents
|
|
50
50
|
|
|
51
51
|
constructor (components: PeerRoutingComponents, init: PeerRoutingInit) {
|
|
52
52
|
this.routingTable = init.routingTable
|
|
53
53
|
this.network = init.network
|
|
54
54
|
this.validators = init.validators
|
|
55
55
|
this.queryManager = init.queryManager
|
|
56
|
-
this.
|
|
57
|
-
this.peerId = components.peerId
|
|
56
|
+
this.components = components
|
|
58
57
|
this.log = components.logger.forComponent(`${init.logPrefix}:peer-routing`)
|
|
59
58
|
|
|
60
59
|
this.findPeer = components.metrics?.traceFunction('libp2p.kadDHT.findPeer', this.findPeer.bind(this), {
|
|
@@ -77,7 +76,7 @@ export class PeerRouting {
|
|
|
77
76
|
this.log('findPeerLocal found %p in routing table', peer)
|
|
78
77
|
|
|
79
78
|
try {
|
|
80
|
-
peerData = await this.peerStore.get(p)
|
|
79
|
+
peerData = await this.components.peerStore.get(p)
|
|
81
80
|
} catch (err: any) {
|
|
82
81
|
if (err.name !== 'NotFoundError') {
|
|
83
82
|
throw err
|
|
@@ -87,7 +86,7 @@ export class PeerRouting {
|
|
|
87
86
|
|
|
88
87
|
if (peerData == null) {
|
|
89
88
|
try {
|
|
90
|
-
peerData = await this.peerStore.get(peer)
|
|
89
|
+
peerData = await this.components.peerStore.get(peer)
|
|
91
90
|
} catch (err: any) {
|
|
92
91
|
if (err.name !== 'NotFoundError') {
|
|
93
92
|
throw err
|
|
@@ -110,7 +109,7 @@ export class PeerRouting {
|
|
|
110
109
|
/**
|
|
111
110
|
* Get a value via rpc call for the given parameters
|
|
112
111
|
*/
|
|
113
|
-
async * _getValueSingle (peer: PeerId, key: Uint8Array, options:
|
|
112
|
+
async * _getValueSingle (peer: PeerId, key: Uint8Array, options: SendMessageOptions): AsyncGenerator<QueryEvent> {
|
|
114
113
|
const msg: Partial<Message> = {
|
|
115
114
|
type: MessageType.GET_VALUE,
|
|
116
115
|
key
|
|
@@ -124,8 +123,17 @@ export class PeerRouting {
|
|
|
124
123
|
*/
|
|
125
124
|
async * getPublicKeyFromNode (peer: PeerId, options: RoutingOptions = {}): AsyncGenerator<QueryEvent> {
|
|
126
125
|
const pkKey = keyForPublicKey(peer)
|
|
126
|
+
const path = {
|
|
127
|
+
index: -1,
|
|
128
|
+
queued: 0,
|
|
129
|
+
running: 0,
|
|
130
|
+
total: 0
|
|
131
|
+
}
|
|
127
132
|
|
|
128
|
-
for await (const event of this._getValueSingle(peer, pkKey,
|
|
133
|
+
for await (const event of this._getValueSingle(peer, pkKey, {
|
|
134
|
+
...options,
|
|
135
|
+
path
|
|
136
|
+
})) {
|
|
129
137
|
yield event
|
|
130
138
|
|
|
131
139
|
if (event.name === 'PEER_RESPONSE' && event.record != null) {
|
|
@@ -143,7 +151,8 @@ export class PeerRouting {
|
|
|
143
151
|
|
|
144
152
|
yield valueEvent({
|
|
145
153
|
from: peer,
|
|
146
|
-
value: event.record.value
|
|
154
|
+
value: event.record.value,
|
|
155
|
+
path
|
|
147
156
|
}, options)
|
|
148
157
|
}
|
|
149
158
|
}
|
|
@@ -165,8 +174,14 @@ export class PeerRouting {
|
|
|
165
174
|
if (pi != null) {
|
|
166
175
|
this.log('found local')
|
|
167
176
|
yield finalPeerEvent({
|
|
168
|
-
from: this.peerId,
|
|
169
|
-
peer: pi
|
|
177
|
+
from: this.components.peerId,
|
|
178
|
+
peer: pi,
|
|
179
|
+
path: {
|
|
180
|
+
index: -1,
|
|
181
|
+
queued: 0,
|
|
182
|
+
running: 0,
|
|
183
|
+
total: 0
|
|
184
|
+
}
|
|
170
185
|
}, options)
|
|
171
186
|
return
|
|
172
187
|
}
|
|
@@ -177,15 +192,16 @@ export class PeerRouting {
|
|
|
177
192
|
if (options.useNetwork !== false) {
|
|
178
193
|
const self = this // eslint-disable-line @typescript-eslint/no-this-alias
|
|
179
194
|
|
|
180
|
-
const findPeerQuery: QueryFunc = async function * ({ peer, signal }) {
|
|
195
|
+
const findPeerQuery: QueryFunc = async function * ({ peer, signal, path }) {
|
|
181
196
|
const request: Partial<Message> = {
|
|
182
197
|
type: MessageType.FIND_NODE,
|
|
183
198
|
key: id.toMultihash().bytes
|
|
184
199
|
}
|
|
185
200
|
|
|
186
|
-
for await (const event of self.network.sendRequest(peer, request, {
|
|
201
|
+
for await (const event of self.network.sendRequest(peer.id, request, {
|
|
187
202
|
...options,
|
|
188
|
-
signal
|
|
203
|
+
signal,
|
|
204
|
+
path
|
|
189
205
|
})) {
|
|
190
206
|
yield event
|
|
191
207
|
|
|
@@ -194,7 +210,11 @@ export class PeerRouting {
|
|
|
194
210
|
|
|
195
211
|
// found the peer
|
|
196
212
|
if (match != null) {
|
|
197
|
-
yield finalPeerEvent({
|
|
213
|
+
yield finalPeerEvent({
|
|
214
|
+
from: event.from,
|
|
215
|
+
peer: match,
|
|
216
|
+
path: event.path
|
|
217
|
+
}, options)
|
|
198
218
|
}
|
|
199
219
|
}
|
|
200
220
|
}
|
|
@@ -210,53 +230,64 @@ export class PeerRouting {
|
|
|
210
230
|
}
|
|
211
231
|
|
|
212
232
|
if (!foundPeer) {
|
|
213
|
-
|
|
233
|
+
throw new NotFoundError('Not found')
|
|
214
234
|
}
|
|
215
235
|
}
|
|
216
236
|
|
|
217
237
|
/**
|
|
218
|
-
* Kademlia 'FIND_NODE' operation on a key, which could be the bytes from
|
|
219
|
-
*
|
|
238
|
+
* Kademlia 'FIND_NODE' operation on a key, which could be the bytes from a
|
|
239
|
+
* multihash or a peer ID
|
|
220
240
|
*/
|
|
221
241
|
async * getClosestPeers (key: Uint8Array, options: QueryOptions = {}): AsyncGenerator<QueryEvent> {
|
|
222
242
|
this.log('getClosestPeers to %b', key)
|
|
223
243
|
const kadId = await convertBuffer(key)
|
|
224
|
-
const tablePeers = this.routingTable.closestPeers(kadId)
|
|
225
|
-
const self = this // eslint-disable-line @typescript-eslint/no-this-alias
|
|
226
|
-
|
|
227
244
|
const peers = new PeerDistanceList(kadId, this.routingTable.kBucketSize)
|
|
228
|
-
|
|
245
|
+
const self = this // eslint-disable-line @typescript-eslint/no-this-alias
|
|
229
246
|
|
|
230
|
-
const getCloserPeersQuery: QueryFunc = async function * ({ peer, signal }) {
|
|
231
|
-
self.log('
|
|
247
|
+
const getCloserPeersQuery: QueryFunc = async function * ({ peer, path, peerKadId, signal }) {
|
|
248
|
+
self.log('getClosestPeers asking %p', peer)
|
|
232
249
|
const request: Partial<Message> = {
|
|
233
250
|
type: MessageType.FIND_NODE,
|
|
234
251
|
key
|
|
235
252
|
}
|
|
236
253
|
|
|
237
|
-
yield * self.network.sendRequest(peer, request, {
|
|
254
|
+
yield * self.network.sendRequest(peer.id, request, {
|
|
238
255
|
...options,
|
|
239
|
-
signal
|
|
256
|
+
signal,
|
|
257
|
+
path
|
|
240
258
|
})
|
|
241
|
-
}
|
|
242
259
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
await Promise.all(event.closer.map(async peerData => {
|
|
246
|
-
await peers.add(peerData)
|
|
247
|
-
}))
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
yield event
|
|
260
|
+
// add the peer to the list if we've managed to contact it successfully
|
|
261
|
+
peers.addWithKadId(peer, peerKadId, path)
|
|
251
262
|
}
|
|
252
263
|
|
|
264
|
+
yield * this.queryManager.run(key, getCloserPeersQuery, options)
|
|
265
|
+
|
|
253
266
|
this.log('found %d peers close to %b', peers.length, key)
|
|
254
267
|
|
|
255
|
-
for (
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
for (let { peer, path } of peers.peers) {
|
|
269
|
+
try {
|
|
270
|
+
if (peer.multiaddrs.length === 0) {
|
|
271
|
+
peer = await self.components.peerStore.getInfo(peer.id)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (peer.multiaddrs.length === 0) {
|
|
275
|
+
continue
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
yield finalPeerEvent({
|
|
279
|
+
from: this.components.peerId,
|
|
280
|
+
peer: await self.components.peerStore.getInfo(peer.id),
|
|
281
|
+
path: {
|
|
282
|
+
index: path.index,
|
|
283
|
+
queued: 0,
|
|
284
|
+
running: 0,
|
|
285
|
+
total: 0
|
|
286
|
+
}
|
|
287
|
+
}, options)
|
|
288
|
+
} catch {
|
|
289
|
+
continue
|
|
290
|
+
}
|
|
260
291
|
}
|
|
261
292
|
}
|
|
262
293
|
|
|
@@ -266,7 +297,7 @@ export class PeerRouting {
|
|
|
266
297
|
*
|
|
267
298
|
* Note: The peerStore is updated with new addresses found for the given peer.
|
|
268
299
|
*/
|
|
269
|
-
async * getValueOrPeers (peer: PeerId, key: Uint8Array, options:
|
|
300
|
+
async * getValueOrPeers (peer: PeerId, key: Uint8Array, options: SendMessageOptions): AsyncGenerator<QueryEvent> {
|
|
270
301
|
for await (const event of this._getValueSingle(peer, key, options)) {
|
|
271
302
|
if (event.name === 'PEER_RESPONSE') {
|
|
272
303
|
if (event.record != null) {
|
|
@@ -277,7 +308,11 @@ export class PeerRouting {
|
|
|
277
308
|
const errMsg = 'invalid record received, discarded'
|
|
278
309
|
this.log(errMsg)
|
|
279
310
|
|
|
280
|
-
yield queryErrorEvent({
|
|
311
|
+
yield queryErrorEvent({
|
|
312
|
+
from: event.from,
|
|
313
|
+
error: new QueryError(errMsg),
|
|
314
|
+
path: options.path
|
|
315
|
+
}, options)
|
|
281
316
|
continue
|
|
282
317
|
}
|
|
283
318
|
}
|
|
@@ -300,7 +335,8 @@ export class PeerRouting {
|
|
|
300
335
|
}
|
|
301
336
|
|
|
302
337
|
/**
|
|
303
|
-
* Get the
|
|
338
|
+
* Get the peers in our routing table that are closer than the passed PeerId
|
|
339
|
+
* to the passed key
|
|
304
340
|
*/
|
|
305
341
|
async getCloserPeersOffline (key: Uint8Array, closerThan: PeerId): Promise<PeerInfo[]> {
|
|
306
342
|
const output: PeerInfo[] = []
|
|
@@ -310,7 +346,7 @@ export class PeerRouting {
|
|
|
310
346
|
const multihash = Digest.decode(key)
|
|
311
347
|
const targetPeerId = peerIdFromMultihash(multihash)
|
|
312
348
|
|
|
313
|
-
const peer = await this.peerStore.get(targetPeerId)
|
|
349
|
+
const peer = await this.components.peerStore.get(targetPeerId)
|
|
314
350
|
|
|
315
351
|
output.push({
|
|
316
352
|
id: peer.id,
|
|
@@ -333,12 +369,7 @@ export class PeerRouting {
|
|
|
333
369
|
}
|
|
334
370
|
|
|
335
371
|
try {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
output.push({
|
|
339
|
-
id: peerId,
|
|
340
|
-
multiaddrs: peer.addresses.map(({ multiaddr }) => multiaddr)
|
|
341
|
-
})
|
|
372
|
+
output.push(await this.components.peerStore.getInfo(peerId))
|
|
342
373
|
} catch (err: any) {
|
|
343
374
|
if (err.name !== 'NotFoundError') {
|
|
344
375
|
throw err
|
package/src/providers.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { PeerMap } from '@libp2p/peer-collections'
|
|
2
2
|
import * as varint from 'uint8-varint'
|
|
3
3
|
import { parseProviderKey, readProviderTime, toProviderKey } from './utils.js'
|
|
4
|
-
import type { ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
|
|
4
|
+
import type { AbortOptions, ComponentLogger, Logger, Metrics, PeerId } from '@libp2p/interface'
|
|
5
5
|
import type { Datastore } from 'interface-datastore'
|
|
6
6
|
import type { Mortice } from 'mortice'
|
|
7
7
|
import type { CID } from 'multiformats'
|
|
@@ -37,11 +37,11 @@ export class Providers {
|
|
|
37
37
|
/**
|
|
38
38
|
* Add a new provider for the given CID
|
|
39
39
|
*/
|
|
40
|
-
async addProvider (cid: CID, provider: PeerId): Promise<void> {
|
|
40
|
+
async addProvider (cid: CID, provider: PeerId, options?: AbortOptions): Promise<void> {
|
|
41
41
|
const release = await this.lock.readLock()
|
|
42
42
|
|
|
43
43
|
try {
|
|
44
|
-
this.log('%p provides %s', provider, cid)
|
|
44
|
+
this.log.trace('%p provides %s', provider, cid)
|
|
45
45
|
await this.writeProviderEntry(cid, provider)
|
|
46
46
|
} finally {
|
|
47
47
|
release()
|
|
@@ -51,12 +51,12 @@ export class Providers {
|
|
|
51
51
|
/**
|
|
52
52
|
* Remove a provider for the given CID
|
|
53
53
|
*/
|
|
54
|
-
async removeProvider (cid: CID, provider: PeerId): Promise<void> {
|
|
54
|
+
async removeProvider (cid: CID, provider: PeerId, options?: AbortOptions): Promise<void> {
|
|
55
55
|
const release = await this.lock.writeLock()
|
|
56
56
|
|
|
57
57
|
try {
|
|
58
58
|
const key = toProviderKey(this.datastorePrefix, cid, provider)
|
|
59
|
-
this.log('%p no longer provides %s', provider, cid)
|
|
59
|
+
this.log.trace('%p no longer provides %s', provider, cid)
|
|
60
60
|
await this.datastore.delete(key)
|
|
61
61
|
} finally {
|
|
62
62
|
release()
|
|
@@ -66,13 +66,13 @@ export class Providers {
|
|
|
66
66
|
/**
|
|
67
67
|
* Get a list of providers for the given CID
|
|
68
68
|
*/
|
|
69
|
-
async getProviders (cid: CID): Promise<PeerId[]> {
|
|
69
|
+
async getProviders (cid: CID, options?: AbortOptions): Promise<PeerId[]> {
|
|
70
70
|
const release = await this.lock.readLock()
|
|
71
71
|
|
|
72
72
|
try {
|
|
73
|
-
this.log('get providers for %c', cid)
|
|
74
|
-
const provs = await this.loadProviders(cid)
|
|
75
|
-
this.log('got %d providers for %c', provs.size, cid)
|
|
73
|
+
this.log.trace('get providers for %c', cid)
|
|
74
|
+
const provs = await this.loadProviders(cid, options)
|
|
75
|
+
this.log.trace('got %d providers for %c', provs.size, cid)
|
|
76
76
|
|
|
77
77
|
return [...provs.keys()]
|
|
78
78
|
} finally {
|
|
@@ -83,21 +83,21 @@ export class Providers {
|
|
|
83
83
|
/**
|
|
84
84
|
* Write a provider into the given store
|
|
85
85
|
*/
|
|
86
|
-
private async writeProviderEntry (cid: CID, peerId: PeerId, time: Date = new Date()): Promise<void> {
|
|
86
|
+
private async writeProviderEntry (cid: CID, peerId: PeerId, time: Date = new Date(), options?: AbortOptions): Promise<void> {
|
|
87
87
|
const key = toProviderKey(this.datastorePrefix, cid, peerId)
|
|
88
88
|
const buffer = varint.encode(time.getTime())
|
|
89
89
|
|
|
90
|
-
await this.datastore.put(key, buffer)
|
|
90
|
+
await this.datastore.put(key, buffer, options)
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
/**
|
|
94
94
|
* Load providers for the given CID from the store
|
|
95
95
|
*/
|
|
96
|
-
private async loadProviders (cid: CID): Promise<PeerMap<Date>> {
|
|
96
|
+
private async loadProviders (cid: CID, options?: AbortOptions): Promise<PeerMap<Date>> {
|
|
97
97
|
const providers = new PeerMap<Date>()
|
|
98
98
|
const key = toProviderKey(this.datastorePrefix, cid)
|
|
99
99
|
|
|
100
|
-
for await (const entry of this.datastore.query({ prefix: key.toString() })) {
|
|
100
|
+
for await (const entry of this.datastore.query({ prefix: key.toString() }, options)) {
|
|
101
101
|
const { peerId } = parseProviderKey(entry.key)
|
|
102
102
|
providers.set(peerId, readProviderTime(entry.value))
|
|
103
103
|
}
|
package/src/query/events.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MessageType, SendQueryEvent, PeerResponseEvent,
|
|
1
|
+
import type { MessageType, SendQueryEvent, PeerResponseEvent, AddPeerEvent, ValueEvent, ProviderEvent, QueryErrorEvent, FinalPeerEvent, DisjointPath, PathEndedEvent, DialPeerEvent } from '../index.js'
|
|
2
2
|
import type { PeerId, PeerInfo } from '@libp2p/interface'
|
|
3
3
|
import type { Libp2pRecord } from '@libp2p/record'
|
|
4
4
|
import type { ProgressOptions } from 'progress-events'
|
|
@@ -6,6 +6,7 @@ import type { ProgressOptions } from 'progress-events'
|
|
|
6
6
|
export interface QueryEventFields {
|
|
7
7
|
to: PeerId
|
|
8
8
|
type: MessageType
|
|
9
|
+
path: DisjointPath
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
export function sendQueryEvent (fields: QueryEventFields, options: ProgressOptions = {}): SendQueryEvent {
|
|
@@ -25,6 +26,7 @@ export function sendQueryEvent (fields: QueryEventFields, options: ProgressOptio
|
|
|
25
26
|
export interface PeerResponseEventFields {
|
|
26
27
|
from: PeerId
|
|
27
28
|
messageType: MessageType
|
|
29
|
+
path: DisjointPath
|
|
28
30
|
closer?: PeerInfo[]
|
|
29
31
|
providers?: PeerInfo[]
|
|
30
32
|
record?: Libp2pRecord
|
|
@@ -48,6 +50,7 @@ export function peerResponseEvent (fields: PeerResponseEventFields, options: Pro
|
|
|
48
50
|
export interface FinalPeerEventFields {
|
|
49
51
|
from: PeerId
|
|
50
52
|
peer: PeerInfo
|
|
53
|
+
path: DisjointPath
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export function finalPeerEvent (fields: FinalPeerEventFields, options: ProgressOptions = {}): FinalPeerEvent {
|
|
@@ -65,6 +68,7 @@ export function finalPeerEvent (fields: FinalPeerEventFields, options: ProgressO
|
|
|
65
68
|
export interface ErrorEventFields {
|
|
66
69
|
from: PeerId
|
|
67
70
|
error: Error
|
|
71
|
+
path: DisjointPath
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
export function queryErrorEvent (fields: ErrorEventFields, options: ProgressOptions = {}): QueryErrorEvent {
|
|
@@ -82,6 +86,7 @@ export function queryErrorEvent (fields: ErrorEventFields, options: ProgressOpti
|
|
|
82
86
|
export interface ProviderEventFields {
|
|
83
87
|
from: PeerId
|
|
84
88
|
providers: PeerInfo[]
|
|
89
|
+
path: DisjointPath
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
export function providerEvent (fields: ProviderEventFields, options: ProgressOptions = {}): ProviderEvent {
|
|
@@ -99,6 +104,7 @@ export function providerEvent (fields: ProviderEventFields, options: ProgressOpt
|
|
|
99
104
|
export interface ValueEventFields {
|
|
100
105
|
from: PeerId
|
|
101
106
|
value: Uint8Array
|
|
107
|
+
path: DisjointPath
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
export function valueEvent (fields: ValueEventFields, options: ProgressOptions = {}): ValueEvent {
|
|
@@ -113,11 +119,12 @@ export function valueEvent (fields: ValueEventFields, options: ProgressOptions =
|
|
|
113
119
|
return event
|
|
114
120
|
}
|
|
115
121
|
|
|
116
|
-
export interface
|
|
122
|
+
export interface AddPeerEventFields {
|
|
117
123
|
peer: PeerId
|
|
124
|
+
path: DisjointPath
|
|
118
125
|
}
|
|
119
126
|
|
|
120
|
-
export function addPeerEvent (fields:
|
|
127
|
+
export function addPeerEvent (fields: AddPeerEventFields, options: ProgressOptions = {}): AddPeerEvent {
|
|
121
128
|
const event: AddPeerEvent = {
|
|
122
129
|
...fields,
|
|
123
130
|
name: 'ADD_PEER',
|
|
@@ -131,6 +138,7 @@ export function addPeerEvent (fields: PeerEventFields, options: ProgressOptions
|
|
|
131
138
|
|
|
132
139
|
export interface DialPeerEventFields {
|
|
133
140
|
peer: PeerId
|
|
141
|
+
path: DisjointPath
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
export function dialPeerEvent (fields: DialPeerEventFields, options: ProgressOptions = {}): DialPeerEvent {
|
|
@@ -144,3 +152,19 @@ export function dialPeerEvent (fields: DialPeerEventFields, options: ProgressOpt
|
|
|
144
152
|
|
|
145
153
|
return event
|
|
146
154
|
}
|
|
155
|
+
|
|
156
|
+
export interface PathEndedEventFields {
|
|
157
|
+
path: DisjointPath
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function pathEndedEvent (fields: PathEndedEventFields, options: ProgressOptions = {}): PathEndedEvent {
|
|
161
|
+
const event: PathEndedEvent = {
|
|
162
|
+
...fields,
|
|
163
|
+
name: 'PATH_ENDED',
|
|
164
|
+
type: 8
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
options.onProgress?.(new CustomEvent('kad-dht:query:path-ended', { detail: event }))
|
|
168
|
+
|
|
169
|
+
return event
|
|
170
|
+
}
|
package/src/query/manager.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
/* eslint-disable complexity */
|
|
1
2
|
import { setMaxListeners } from '@libp2p/interface'
|
|
2
|
-
import {
|
|
3
|
+
import { createScalableCuckooFilter } from '@libp2p/utils/filters'
|
|
3
4
|
import { anySignal } from 'any-signal'
|
|
4
5
|
import merge from 'it-merge'
|
|
6
|
+
import { pEvent } from 'p-event'
|
|
5
7
|
import { raceSignal } from 'race-signal'
|
|
6
8
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
7
9
|
import {
|
|
@@ -26,6 +28,7 @@ export interface QueryManagerInit {
|
|
|
26
28
|
disjointPaths?: number
|
|
27
29
|
alpha?: number
|
|
28
30
|
initialQuerySelfHasRun: DeferredPromise<void>
|
|
31
|
+
allowQueryWithZeroPeers?: boolean
|
|
29
32
|
routingTable: RoutingTable
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -37,11 +40,6 @@ export interface QueryManagerComponents {
|
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
export interface QueryOptions extends RoutingOptions {
|
|
40
|
-
/**
|
|
41
|
-
* A timeout for subqueries executed as part of the main query
|
|
42
|
-
*/
|
|
43
|
-
queryFuncTimeout?: number
|
|
44
|
-
|
|
45
43
|
isSelfQuery?: boolean
|
|
46
44
|
}
|
|
47
45
|
|
|
@@ -59,6 +57,7 @@ export class QueryManager implements Startable {
|
|
|
59
57
|
private readonly routingTable: RoutingTable
|
|
60
58
|
private initialQuerySelfHasRun?: DeferredPromise<void>
|
|
61
59
|
private readonly logPrefix: string
|
|
60
|
+
private readonly allowQueryWithZeroPeers: boolean
|
|
62
61
|
|
|
63
62
|
constructor (components: QueryManagerComponents, init: QueryManagerInit) {
|
|
64
63
|
this.logPrefix = init.logPrefix
|
|
@@ -69,6 +68,7 @@ export class QueryManager implements Startable {
|
|
|
69
68
|
this.logger = components.logger
|
|
70
69
|
this.peerId = components.peerId
|
|
71
70
|
this.connectionManager = components.connectionManager
|
|
71
|
+
this.allowQueryWithZeroPeers = init.allowQueryWithZeroPeers ?? false
|
|
72
72
|
|
|
73
73
|
// allow us to stop queries on shut down
|
|
74
74
|
this.shutDownController = new AbortController()
|
|
@@ -146,8 +146,18 @@ export class QueryManager implements Startable {
|
|
|
146
146
|
let queryFinished = false
|
|
147
147
|
|
|
148
148
|
try {
|
|
149
|
+
if (this.routingTable.size === 0 && !this.allowQueryWithZeroPeers) {
|
|
150
|
+
log('routing table was empty, waiting for some peers before running%s query', options.isSelfQuery === true ? ' self' : '')
|
|
151
|
+
// wait to discover at least one DHT peer that isn't us
|
|
152
|
+
await pEvent(this.routingTable, 'peer:add', {
|
|
153
|
+
signal,
|
|
154
|
+
filter: (event) => !this.peerId.equals(event.detail)
|
|
155
|
+
})
|
|
156
|
+
log('routing table has peers, continuing with%s query', options.isSelfQuery === true ? ' self' : '')
|
|
157
|
+
}
|
|
158
|
+
|
|
149
159
|
if (options.isSelfQuery !== true && this.initialQuerySelfHasRun != null) {
|
|
150
|
-
log('waiting for initial
|
|
160
|
+
log('waiting for initial self query before continuing')
|
|
151
161
|
|
|
152
162
|
await raceSignal(this.initialQuerySelfHasRun.promise, signal)
|
|
153
163
|
|
|
@@ -157,30 +167,43 @@ export class QueryManager implements Startable {
|
|
|
157
167
|
log('query:start')
|
|
158
168
|
|
|
159
169
|
const id = await convertBuffer(key)
|
|
160
|
-
const peers = this.routingTable.closestPeers(id)
|
|
161
|
-
|
|
170
|
+
const peers = this.routingTable.closestPeers(id, this.routingTable.kBucketSize)
|
|
171
|
+
|
|
172
|
+
// split peers into d buckets evenly(ish)
|
|
173
|
+
const peersToQuery = peers.sort(() => {
|
|
174
|
+
if (Math.random() > 0.5) {
|
|
175
|
+
return 1
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return -1
|
|
179
|
+
})
|
|
180
|
+
.reduce((acc: PeerId[][], curr, index) => {
|
|
181
|
+
acc[index % this.disjointPaths].push(curr)
|
|
182
|
+
|
|
183
|
+
return acc
|
|
184
|
+
}, new Array(this.disjointPaths).fill(0).map(() => []))
|
|
185
|
+
.filter(peers => peers.length > 0)
|
|
162
186
|
|
|
163
187
|
if (peers.length === 0) {
|
|
164
|
-
log.error('
|
|
188
|
+
log.error('running query with no peers')
|
|
165
189
|
return
|
|
166
190
|
}
|
|
167
191
|
|
|
168
192
|
// make sure we don't get trapped in a loop
|
|
169
|
-
const peersSeen =
|
|
193
|
+
const peersSeen = createScalableCuckooFilter(1024)
|
|
170
194
|
|
|
171
195
|
// Create query paths from the starting peers
|
|
172
196
|
const paths = peersToQuery.map((peer, index) => {
|
|
173
197
|
return queryPath({
|
|
174
198
|
...options,
|
|
175
199
|
key,
|
|
176
|
-
|
|
200
|
+
startingPeers: peer,
|
|
177
201
|
ourPeerId: this.peerId,
|
|
178
202
|
signal,
|
|
179
203
|
query: queryFunc,
|
|
180
|
-
|
|
204
|
+
path: index,
|
|
181
205
|
numPaths: peersToQuery.length,
|
|
182
206
|
alpha: this.alpha,
|
|
183
|
-
queryFuncTimeout: options.queryFuncTimeout,
|
|
184
207
|
log,
|
|
185
208
|
peersSeen,
|
|
186
209
|
onProgress: options.onProgress,
|
|
@@ -197,22 +220,26 @@ export class QueryManager implements Startable {
|
|
|
197
220
|
if (event.name === 'PEER_RESPONSE') {
|
|
198
221
|
for (const peer of [...event.closer, ...event.providers]) {
|
|
199
222
|
// eslint-disable-next-line max-depth
|
|
200
|
-
if (!(await this.connectionManager.isDialable(peer.multiaddrs
|
|
223
|
+
if (!(await this.connectionManager.isDialable(peer.multiaddrs, {
|
|
224
|
+
signal
|
|
225
|
+
}))) {
|
|
201
226
|
continue
|
|
202
227
|
}
|
|
203
228
|
|
|
204
|
-
await this.routingTable.add(peer.id
|
|
229
|
+
await this.routingTable.add(peer.id, {
|
|
230
|
+
signal
|
|
231
|
+
})
|
|
205
232
|
}
|
|
206
233
|
}
|
|
207
234
|
|
|
235
|
+
signal.throwIfAborted()
|
|
208
236
|
yield event
|
|
209
237
|
}
|
|
210
238
|
|
|
211
239
|
queryFinished = true
|
|
212
|
-
} catch (err
|
|
213
|
-
if (
|
|
214
|
-
// ignore
|
|
215
|
-
} else {
|
|
240
|
+
} catch (err) {
|
|
241
|
+
if (this.running) {
|
|
242
|
+
// ignore errors thrown during shut down
|
|
216
243
|
throw err
|
|
217
244
|
}
|
|
218
245
|
} finally {
|
|
@@ -223,7 +250,7 @@ export class QueryManager implements Startable {
|
|
|
223
250
|
|
|
224
251
|
signal.clear()
|
|
225
252
|
|
|
226
|
-
log('query
|
|
253
|
+
log('query finished')
|
|
227
254
|
}
|
|
228
255
|
}
|
|
229
256
|
}
|