@helia/bitswap 0.0.0-b67ac5f → 0.0.0-ba4b7ba

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