@helia/bitswap 0.0.0-7cd012a → 0.0.0-9c8a2c0
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 +1 -1
- package/dist/src/bitswap.d.ts.map +1 -1
- package/dist/src/bitswap.js +4 -1
- package/dist/src/bitswap.js.map +1 -1
- package/dist/src/network.d.ts +3 -4
- package/dist/src/network.d.ts.map +1 -1
- package/dist/src/network.js +19 -27
- package/dist/src/network.js.map +1 -1
- package/dist/src/peer-want-lists/index.d.ts +2 -2
- package/dist/src/peer-want-lists/index.d.ts.map +1 -1
- package/dist/src/peer-want-lists/index.js +1 -1
- package/dist/src/peer-want-lists/index.js.map +1 -1
- package/dist/src/session.d.ts.map +1 -1
- package/dist/src/session.js +4 -0
- package/dist/src/session.js.map +1 -1
- package/dist/src/stats.d.ts +2 -2
- package/dist/src/stats.d.ts.map +1 -1
- package/dist/src/stats.js +4 -4
- package/dist/src/stats.js.map +1 -1
- package/dist/src/want-list.d.ts +15 -13
- package/dist/src/want-list.d.ts.map +1 -1
- package/dist/src/want-list.js +106 -102
- package/dist/src/want-list.js.map +1 -1
- package/package.json +2 -2
- package/src/bitswap.ts +4 -1
- package/src/network.ts +23 -33
- package/src/peer-want-lists/index.ts +3 -3
- package/src/session.ts +5 -0
- package/src/stats.ts +6 -6
- package/src/want-list.ts +173 -167
package/src/want-list.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { TypedEventEmitter, setMaxListeners } from '@libp2p/interface'
|
|
2
2
|
import { trackedPeerMap, PeerSet } from '@libp2p/peer-collections'
|
|
3
3
|
import { trackedMap } from '@libp2p/utils/tracked-map'
|
|
4
4
|
import all from 'it-all'
|
|
@@ -8,14 +8,16 @@ import { pipe } from 'it-pipe'
|
|
|
8
8
|
import { CID } from 'multiformats/cid'
|
|
9
9
|
import { sha256 } from 'multiformats/hashes/sha2'
|
|
10
10
|
import pDefer from 'p-defer'
|
|
11
|
+
import { raceEvent } from 'race-event'
|
|
12
|
+
import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
|
|
11
13
|
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
|
|
12
14
|
import { DEFAULT_MESSAGE_SEND_DELAY } from './constants.js'
|
|
13
15
|
import { BlockPresenceType, WantType } from './pb/message.js'
|
|
14
16
|
import vd from './utils/varint-decoder.js'
|
|
15
|
-
import type { MultihashHasherLoader } from './index.js'
|
|
17
|
+
import type { BitswapNotifyProgressEvents, MultihashHasherLoader } from './index.js'
|
|
16
18
|
import type { BitswapNetworkWantProgressEvents, Network } from './network.js'
|
|
17
19
|
import type { BitswapMessage } from './pb/message.js'
|
|
18
|
-
import type { ComponentLogger,
|
|
20
|
+
import type { ComponentLogger, PeerId, Startable, AbortOptions, Libp2p, TypedEventTarget } from '@libp2p/interface'
|
|
19
21
|
import type { Logger } from '@libp2p/logger'
|
|
20
22
|
import type { PeerMap } from '@libp2p/peer-collections'
|
|
21
23
|
import type { DeferredPromise } from 'p-defer'
|
|
@@ -24,7 +26,7 @@ import type { ProgressOptions } from 'progress-events'
|
|
|
24
26
|
export interface WantListComponents {
|
|
25
27
|
network: Network
|
|
26
28
|
logger: ComponentLogger
|
|
27
|
-
|
|
29
|
+
libp2p: Libp2p
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
export interface WantListInit {
|
|
@@ -63,16 +65,6 @@ export interface WantListEntry {
|
|
|
63
65
|
* If this set has members, the want will only be sent to these peers
|
|
64
66
|
*/
|
|
65
67
|
session: PeerSet
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Promises returned from `.wantBlock` for this block
|
|
69
|
-
*/
|
|
70
|
-
blockWantListeners: Array<DeferredPromise<WantBlockResult>>
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Promises returned from `.wantPresence` for this block
|
|
74
|
-
*/
|
|
75
|
-
blockPresenceListeners: Array<DeferredPromise<WantPresenceResult>>
|
|
76
68
|
}
|
|
77
69
|
|
|
78
70
|
export interface WantOptions extends AbortOptions, ProgressOptions<BitswapNetworkWantProgressEvents> {
|
|
@@ -108,7 +100,12 @@ export interface WantHaveResult {
|
|
|
108
100
|
|
|
109
101
|
export type WantPresenceResult = WantDontHaveResult | WantHaveResult
|
|
110
102
|
|
|
111
|
-
export
|
|
103
|
+
export interface WantListEvents {
|
|
104
|
+
block: CustomEvent<WantBlockResult>
|
|
105
|
+
presence: CustomEvent<WantPresenceResult>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class WantList extends TypedEventEmitter<WantListEvents> implements Startable, TypedEventTarget<WantListEvents> {
|
|
112
109
|
/**
|
|
113
110
|
* Tracks what CIDs we've previously sent to which peers
|
|
114
111
|
*/
|
|
@@ -119,15 +116,19 @@ export class WantList implements Startable {
|
|
|
119
116
|
private readonly sendMessagesDelay: number
|
|
120
117
|
private sendMessagesTimeout?: ReturnType<typeof setTimeout>
|
|
121
118
|
private readonly hashLoader?: MultihashHasherLoader
|
|
119
|
+
private sendingMessages?: DeferredPromise<void>
|
|
122
120
|
|
|
123
121
|
constructor (components: WantListComponents, init: WantListInit = {}) {
|
|
122
|
+
super()
|
|
123
|
+
|
|
124
|
+
setMaxListeners(Infinity, this)
|
|
124
125
|
this.peers = trackedPeerMap({
|
|
125
126
|
name: 'ipfs_bitswap_peers',
|
|
126
|
-
metrics: components.metrics
|
|
127
|
+
metrics: components.libp2p.metrics
|
|
127
128
|
})
|
|
128
129
|
this.wants = trackedMap({
|
|
129
130
|
name: 'ipfs_bitswap_wantlist',
|
|
130
|
-
metrics: components.metrics
|
|
131
|
+
metrics: components.libp2p.metrics
|
|
131
132
|
})
|
|
132
133
|
this.network = components.network
|
|
133
134
|
this.sendMessagesDelay = init.sendMessagesDelay ?? DEFAULT_MESSAGE_SEND_DELAY
|
|
@@ -164,9 +165,7 @@ export class WantList implements Startable {
|
|
|
164
165
|
priority: options.priority ?? 1,
|
|
165
166
|
wantType: options.wantType ?? WantType.WantBlock,
|
|
166
167
|
cancel: false,
|
|
167
|
-
sendDontHave: true
|
|
168
|
-
blockWantListeners: [],
|
|
169
|
-
blockPresenceListeners: []
|
|
168
|
+
sendDontHave: true
|
|
170
169
|
}
|
|
171
170
|
|
|
172
171
|
if (options.peerId != null) {
|
|
@@ -198,30 +197,41 @@ export class WantList implements Startable {
|
|
|
198
197
|
}
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
//
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (options.wantType === WantType.WantBlock) {
|
|
205
|
-
const p = deferred = pDefer<WantBlockResult>()
|
|
200
|
+
// broadcast changes
|
|
201
|
+
await this.sendMessagesDebounced()
|
|
206
202
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
203
|
+
try {
|
|
204
|
+
if (options.wantType === WantType.WantBlock) {
|
|
205
|
+
const event = await raceEvent<CustomEvent<WantBlockResult>>(this, 'block', options?.signal, {
|
|
206
|
+
filter: (event) => {
|
|
207
|
+
return uint8ArrayEquals(cid.multihash.digest, event.detail.cid.multihash.digest)
|
|
208
|
+
},
|
|
209
|
+
errorMessage: 'Want was aborted'
|
|
210
|
+
})
|
|
210
211
|
|
|
211
|
-
|
|
212
|
-
|
|
212
|
+
return event.detail
|
|
213
|
+
}
|
|
213
214
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
215
|
+
const event = await raceEvent<CustomEvent<WantPresenceResult>>(this, 'presence', options?.signal, {
|
|
216
|
+
filter: (event) => {
|
|
217
|
+
return uint8ArrayEquals(cid.multihash.digest, event.detail.cid.multihash.digest)
|
|
218
|
+
},
|
|
219
|
+
errorMessage: 'Want was aborted'
|
|
220
|
+
})
|
|
217
221
|
|
|
218
|
-
|
|
222
|
+
return event.detail
|
|
223
|
+
} finally {
|
|
224
|
+
if (options.signal?.aborted === true) {
|
|
225
|
+
this.log('want for %c was aborted, cancelling want', cid)
|
|
219
226
|
entry.cancel = true
|
|
227
|
+
// broadcast changes
|
|
228
|
+
await this.sendMessagesDebounced()
|
|
220
229
|
}
|
|
221
|
-
|
|
222
|
-
deferred.reject(new AbortError('Want was aborted'))
|
|
223
230
|
}
|
|
224
|
-
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async sendMessagesDebounced (): Promise<void> {
|
|
234
|
+
await this.sendingMessages?.promise
|
|
225
235
|
|
|
226
236
|
// broadcast changes
|
|
227
237
|
clearTimeout(this.sendMessagesTimeout)
|
|
@@ -231,77 +241,70 @@ export class WantList implements Startable {
|
|
|
231
241
|
this.log('error sending messages to peers', err)
|
|
232
242
|
})
|
|
233
243
|
}, this.sendMessagesDelay)
|
|
234
|
-
|
|
235
|
-
try {
|
|
236
|
-
return await deferred.promise
|
|
237
|
-
} finally {
|
|
238
|
-
// remove listener
|
|
239
|
-
options.signal?.removeEventListener('abort', abortListener)
|
|
240
|
-
// remove deferred promise
|
|
241
|
-
if (options.wantType === WantType.WantBlock) {
|
|
242
|
-
entry.blockWantListeners = entry.blockWantListeners.filter(recipient => recipient !== deferred)
|
|
243
|
-
} else {
|
|
244
|
-
entry.blockPresenceListeners = entry.blockPresenceListeners.filter(recipient => recipient !== deferred)
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
244
|
}
|
|
248
245
|
|
|
249
246
|
private async sendMessages (): Promise<void> {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
247
|
+
this.sendingMessages = pDefer()
|
|
248
|
+
|
|
249
|
+
await Promise.all(
|
|
250
|
+
[...this.peers.entries()].map(async ([peerId, sentWants]) => {
|
|
251
|
+
const sent = new Set<string>()
|
|
252
|
+
const message: Partial<BitswapMessage> = {
|
|
253
|
+
wantlist: {
|
|
254
|
+
full: false,
|
|
255
|
+
entries: pipe(
|
|
256
|
+
this.wants.entries(),
|
|
257
|
+
(source) => filter(source, ([key, entry]) => {
|
|
258
|
+
// skip session-only wants
|
|
259
|
+
if (entry.session.size > 0 && !entry.session.has(peerId)) {
|
|
260
|
+
return false
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const sentPreviously = sentWants.has(key)
|
|
264
|
+
|
|
265
|
+
// don't cancel if we've not sent it to them before
|
|
266
|
+
if (entry.cancel) {
|
|
267
|
+
return sentPreviously
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// only send if we've not sent it to them before
|
|
271
|
+
return !sentPreviously
|
|
272
|
+
}),
|
|
273
|
+
(source) => map(source, ([key, entry]) => {
|
|
274
|
+
sent.add(key)
|
|
275
|
+
|
|
276
|
+
return {
|
|
277
|
+
cid: entry.cid.bytes,
|
|
278
|
+
priority: entry.priority,
|
|
279
|
+
wantType: entry.wantType,
|
|
280
|
+
cancel: entry.cancel,
|
|
281
|
+
sendDontHave: entry.sendDontHave
|
|
282
|
+
}
|
|
283
|
+
}),
|
|
284
|
+
(source) => all(source)
|
|
285
|
+
)
|
|
286
|
+
}
|
|
286
287
|
}
|
|
287
|
-
}
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
289
|
+
if (message.wantlist?.entries.length === 0) {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
292
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
// add message to send queue
|
|
294
|
+
try {
|
|
295
|
+
await this.network.sendMessage(peerId, message)
|
|
296
296
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
// update list of messages sent to remote
|
|
298
|
+
for (const key of sent) {
|
|
299
|
+
sentWants.add(key)
|
|
300
|
+
}
|
|
301
|
+
} catch (err: any) {
|
|
302
|
+
this.log.error('error sending full wantlist to new peer', err)
|
|
300
303
|
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
304
|
+
})
|
|
305
|
+
).catch(err => {
|
|
306
|
+
this.log.error('error sending messages', err)
|
|
307
|
+
})
|
|
305
308
|
|
|
306
309
|
// queued all message sends, remove cancelled wants from wantlist and sent
|
|
307
310
|
// wants
|
|
@@ -314,6 +317,8 @@ export class WantList implements Startable {
|
|
|
314
317
|
}
|
|
315
318
|
}
|
|
316
319
|
}
|
|
320
|
+
|
|
321
|
+
this.sendingMessages.resolve()
|
|
317
322
|
}
|
|
318
323
|
|
|
319
324
|
has (cid: CID): boolean {
|
|
@@ -325,31 +330,30 @@ export class WantList implements Startable {
|
|
|
325
330
|
* Add a CID to the wantlist
|
|
326
331
|
*/
|
|
327
332
|
async wantPresence (cid: CID, options: WantOptions = {}): Promise<WantPresenceResult> {
|
|
328
|
-
if (options.peerId != null
|
|
329
|
-
const
|
|
333
|
+
if (options.peerId != null) {
|
|
334
|
+
const peer = options.peerId
|
|
330
335
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
336
|
+
// sending WantHave directly to peer
|
|
337
|
+
await this.network.sendMessage(options.peerId, {
|
|
338
|
+
wantlist: {
|
|
339
|
+
full: false,
|
|
340
|
+
entries: [{
|
|
341
|
+
cid: cid.bytes,
|
|
342
|
+
sendDontHave: true,
|
|
343
|
+
wantType: WantType.WantHave,
|
|
344
|
+
priority: 1
|
|
345
|
+
}]
|
|
346
|
+
}
|
|
347
|
+
})
|
|
334
348
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
sendDontHave: true,
|
|
342
|
-
wantType: WantType.WantHave,
|
|
343
|
-
priority: 1
|
|
344
|
-
}]
|
|
345
|
-
}
|
|
346
|
-
})
|
|
347
|
-
} catch (err) {
|
|
348
|
-
// sending failed, remove them as a peer
|
|
349
|
-
this.peers.delete(options.peerId)
|
|
349
|
+
// wait for peer response
|
|
350
|
+
const event = await raceEvent<CustomEvent<WantHaveResult | WantDontHaveResult>>(this, 'presence', options.signal, {
|
|
351
|
+
filter: (event) => {
|
|
352
|
+
return peer.equals(event.detail.sender) && uint8ArrayEquals(cid.multihash.digest, event.detail.cid.multihash.digest)
|
|
353
|
+
}
|
|
354
|
+
})
|
|
350
355
|
|
|
351
|
-
|
|
352
|
-
}
|
|
356
|
+
return event.detail
|
|
353
357
|
}
|
|
354
358
|
|
|
355
359
|
return this.addEntry(cid, {
|
|
@@ -368,15 +372,29 @@ export class WantList implements Startable {
|
|
|
368
372
|
})
|
|
369
373
|
}
|
|
370
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Invoked when a block has been received from an external source
|
|
377
|
+
*/
|
|
378
|
+
async receivedBlock (cid: CID, options: ProgressOptions<BitswapNotifyProgressEvents> & AbortOptions): Promise<void> {
|
|
379
|
+
const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
|
|
380
|
+
|
|
381
|
+
const entry = this.wants.get(cidStr)
|
|
382
|
+
|
|
383
|
+
if (entry == null) {
|
|
384
|
+
return
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
entry.cancel = true
|
|
388
|
+
|
|
389
|
+
await this.sendMessagesDebounced()
|
|
390
|
+
}
|
|
391
|
+
|
|
371
392
|
/**
|
|
372
393
|
* Invoked when a message is received from a bitswap peer
|
|
373
394
|
*/
|
|
374
395
|
private async receiveMessage (sender: PeerId, message: BitswapMessage): Promise<void> {
|
|
375
396
|
this.log('received message from %p', sender)
|
|
376
|
-
|
|
377
|
-
// blocks received
|
|
378
|
-
const blockResults: WantBlockResult[] = []
|
|
379
|
-
const presenceResults: WantPresenceResult[] = []
|
|
397
|
+
let blocksCancelled = false
|
|
380
398
|
|
|
381
399
|
// process blocks
|
|
382
400
|
for (const block of message.blocks) {
|
|
@@ -384,7 +402,6 @@ export class WantList implements Startable {
|
|
|
384
402
|
continue
|
|
385
403
|
}
|
|
386
404
|
|
|
387
|
-
this.log('received block')
|
|
388
405
|
const values = vd(block.prefix)
|
|
389
406
|
const cidVersion = values[0]
|
|
390
407
|
const multicodec = values[1]
|
|
@@ -403,66 +420,55 @@ export class WantList implements Startable {
|
|
|
403
420
|
|
|
404
421
|
this.log('received block from %p for %c', sender, cid)
|
|
405
422
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
423
|
+
this.safeDispatchEvent<WantBlockResult>('block', {
|
|
424
|
+
detail: {
|
|
425
|
+
sender,
|
|
426
|
+
cid,
|
|
427
|
+
block: block.data
|
|
428
|
+
}
|
|
410
429
|
})
|
|
411
430
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
// process block presences
|
|
420
|
-
for (const { cid: cidBytes, type } of message.blockPresences) {
|
|
421
|
-
const cid = CID.decode(cidBytes)
|
|
422
|
-
|
|
423
|
-
this.log('received %s from %p for %c', type, sender, cid)
|
|
424
|
-
|
|
425
|
-
presenceResults.push({
|
|
426
|
-
sender,
|
|
427
|
-
cid,
|
|
428
|
-
has: type === BlockPresenceType.HaveBlock
|
|
431
|
+
this.safeDispatchEvent<WantHaveResult | WantDontHaveResult>('presence', {
|
|
432
|
+
detail: {
|
|
433
|
+
sender,
|
|
434
|
+
cid,
|
|
435
|
+
has: true,
|
|
436
|
+
block: block.data
|
|
437
|
+
}
|
|
429
438
|
})
|
|
430
|
-
}
|
|
431
439
|
|
|
432
|
-
|
|
433
|
-
const cidStr = uint8ArrayToString(result.cid.multihash.bytes, 'base64')
|
|
440
|
+
const cidStr = uint8ArrayToString(cid.multihash.bytes, 'base64')
|
|
434
441
|
const entry = this.wants.get(cidStr)
|
|
435
442
|
|
|
436
443
|
if (entry == null) {
|
|
437
444
|
return
|
|
438
445
|
}
|
|
439
446
|
|
|
440
|
-
const recipients = entry.blockWantListeners
|
|
441
|
-
entry.blockWantListeners = []
|
|
442
|
-
recipients.forEach((p) => {
|
|
443
|
-
p.resolve(result)
|
|
444
|
-
})
|
|
445
|
-
|
|
446
447
|
// since we received the block, flip the cancel flag to send cancels to
|
|
447
448
|
// any peers on the next message sending iteration, this will remove it
|
|
448
449
|
// from the internal want list
|
|
449
450
|
entry.cancel = true
|
|
451
|
+
blocksCancelled = true
|
|
450
452
|
}
|
|
451
453
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
const
|
|
454
|
+
// process block presences
|
|
455
|
+
for (const { cid: cidBytes, type } of message.blockPresences) {
|
|
456
|
+
const cid = CID.decode(cidBytes)
|
|
455
457
|
|
|
456
|
-
|
|
457
|
-
return
|
|
458
|
-
}
|
|
458
|
+
this.log('received %s from %p for %c', type, sender, cid)
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
460
|
+
this.safeDispatchEvent<WantHaveResult | WantDontHaveResult>('presence', {
|
|
461
|
+
detail: {
|
|
462
|
+
sender,
|
|
463
|
+
cid,
|
|
464
|
+
has: type === BlockPresenceType.HaveBlock
|
|
465
|
+
}
|
|
464
466
|
})
|
|
465
467
|
}
|
|
468
|
+
|
|
469
|
+
if (blocksCancelled) {
|
|
470
|
+
await this.sendMessagesDebounced()
|
|
471
|
+
}
|
|
466
472
|
}
|
|
467
473
|
|
|
468
474
|
/**
|