@helia/utils 0.1.0-ecf5394 → 0.2.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/src/storage.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { start, stop } from '@libp2p/interface'
2
2
  import createMortice from 'mortice'
3
- import type { Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions } from '@helia/interface/blocks'
3
+ import type { Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions, SessionBlockstore } from '@helia/interface/blocks'
4
4
  import type { Pins } from '@helia/interface/pins'
5
5
  import type { AbortOptions, Startable } from '@libp2p/interface'
6
6
  import type { Blockstore } from 'interface-blockstore'
@@ -24,14 +24,14 @@ export interface GetOptions extends AbortOptions {
24
24
  */
25
25
  export class BlockStorage implements Blocks, Startable {
26
26
  public lock: Mortice
27
- private readonly child: Blockstore
27
+ private readonly child: Blocks
28
28
  private readonly pins: Pins
29
29
  private started: boolean
30
30
 
31
31
  /**
32
32
  * Create a new BlockStorage
33
33
  */
34
- constructor (blockstore: Blockstore, pins: Pins, options: BlockStorageInit = {}) {
34
+ constructor (blockstore: Blocks, pins: Pins, options: BlockStorageInit = {}) {
35
35
  this.child = blockstore
36
36
  this.pins = pins
37
37
  this.lock = createMortice({
@@ -62,6 +62,7 @@ export class BlockStorage implements Blocks, Startable {
62
62
  * Put a block to the underlying datastore
63
63
  */
64
64
  async put (cid: CID, block: Uint8Array, options: AbortOptions & ProgressOptions<PutBlockProgressEvents> = {}): Promise<CID> {
65
+ options?.signal?.throwIfAborted()
65
66
  const releaseLock = await this.lock.readLock()
66
67
 
67
68
  try {
@@ -75,6 +76,7 @@ export class BlockStorage implements Blocks, Startable {
75
76
  * Put a multiple blocks to the underlying datastore
76
77
  */
77
78
  async * putMany (blocks: AwaitIterable<{ cid: CID, block: Uint8Array }>, options: AbortOptions & ProgressOptions<PutManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
79
+ options?.signal?.throwIfAborted()
78
80
  const releaseLock = await this.lock.readLock()
79
81
 
80
82
  try {
@@ -88,6 +90,7 @@ export class BlockStorage implements Blocks, Startable {
88
90
  * Get a block by cid
89
91
  */
90
92
  async get (cid: CID, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetBlockProgressEvents> = {}): Promise<Uint8Array> {
93
+ options?.signal?.throwIfAborted()
91
94
  const releaseLock = await this.lock.readLock()
92
95
 
93
96
  try {
@@ -101,6 +104,7 @@ export class BlockStorage implements Blocks, Startable {
101
104
  * Get multiple blocks back from an (async) iterable of cids
102
105
  */
103
106
  async * getMany (cids: AwaitIterable<CID>, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetManyBlocksProgressEvents> = {}): AsyncIterable<Pair> {
107
+ options?.signal?.throwIfAborted()
104
108
  const releaseLock = await this.lock.readLock()
105
109
 
106
110
  try {
@@ -114,6 +118,7 @@ export class BlockStorage implements Blocks, Startable {
114
118
  * Delete a block from the blockstore
115
119
  */
116
120
  async delete (cid: CID, options: AbortOptions & ProgressOptions<DeleteBlockProgressEvents> = {}): Promise<void> {
121
+ options?.signal?.throwIfAborted()
117
122
  const releaseLock = await this.lock.writeLock()
118
123
 
119
124
  try {
@@ -131,6 +136,7 @@ export class BlockStorage implements Blocks, Startable {
131
136
  * Delete multiple blocks from the blockstore
132
137
  */
133
138
  async * deleteMany (cids: AwaitIterable<CID>, options: AbortOptions & ProgressOptions<DeleteManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
139
+ options?.signal?.throwIfAborted()
134
140
  const releaseLock = await this.lock.writeLock()
135
141
 
136
142
  try {
@@ -151,6 +157,7 @@ export class BlockStorage implements Blocks, Startable {
151
157
  }
152
158
 
153
159
  async has (cid: CID, options: AbortOptions = {}): Promise<boolean> {
160
+ options?.signal?.throwIfAborted()
154
161
  const releaseLock = await this.lock.readLock()
155
162
 
156
163
  try {
@@ -161,6 +168,7 @@ export class BlockStorage implements Blocks, Startable {
161
168
  }
162
169
 
163
170
  async * getAll (options: AbortOptions & ProgressOptions<GetAllBlocksProgressEvents> = {}): AsyncIterable<Pair> {
171
+ options?.signal?.throwIfAborted()
164
172
  const releaseLock = await this.lock.readLock()
165
173
 
166
174
  try {
@@ -169,4 +177,9 @@ export class BlockStorage implements Blocks, Startable {
169
177
  releaseLock()
170
178
  }
171
179
  }
180
+
181
+ createSession (root: CID, options?: AbortOptions): SessionBlockstore {
182
+ options?.signal?.throwIfAborted()
183
+ return this.child.createSession(root, options)
184
+ }
172
185
  }
@@ -1,12 +1,11 @@
1
- import { CodeError, start, stop } from '@libp2p/interface'
1
+ import { CodeError, setMaxListeners, start, stop } from '@libp2p/interface'
2
2
  import { anySignal } from 'any-signal'
3
3
  import { IdentityBlockstore } from 'blockstore-core/identity'
4
- import { TieredBlockstore } from 'blockstore-core/tiered'
5
4
  import filter from 'it-filter'
6
5
  import forEach from 'it-foreach'
7
6
  import { CustomProgressEvent, type ProgressOptions } from 'progress-events'
8
7
  import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
9
- import type { BlockBroker, Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions, BlockRetriever, BlockAnnouncer, BlockRetrievalOptions } from '@helia/interface/blocks'
8
+ import type { BlockBroker, Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions, BlockRetrievalOptions, CreateSessionOptions, SessionBlockstore } from '@helia/interface/blocks'
10
9
  import type { AbortOptions, ComponentLogger, Logger, LoggerOptions, Startable } from '@libp2p/interface'
11
10
  import type { Blockstore } from 'interface-blockstore'
12
11
  import type { AwaitIterable } from 'interface-store'
@@ -17,80 +16,45 @@ export interface GetOptions extends AbortOptions {
17
16
  progress?(evt: Event): void
18
17
  }
19
18
 
20
- function isBlockRetriever (b: any): b is BlockRetriever {
21
- return typeof b.retrieve === 'function'
22
- }
23
-
24
- function isBlockAnnouncer (b: any): b is BlockAnnouncer {
25
- return typeof b.announce === 'function'
26
- }
27
-
28
- export interface NetworkedStorageComponents {
19
+ export interface StorageComponents {
29
20
  blockstore: Blockstore
30
21
  logger: ComponentLogger
31
- blockBrokers?: BlockBroker[]
32
- hashers?: Record<number, MultihashHasher>
22
+ blockBrokers: BlockBroker[]
23
+ hashers: Record<number, MultihashHasher>
33
24
  }
34
25
 
35
- /**
36
- * Networked storage wraps a regular blockstore - when getting blocks if the
37
- * blocks are not present Bitswap will be used to fetch them from network peers.
38
- */
39
- export class NetworkedStorage implements Blocks, Startable {
40
- private readonly child: Blockstore
41
- private readonly blockRetrievers: BlockRetriever[]
42
- private readonly blockAnnouncers: BlockAnnouncer[]
43
- private readonly hashers: Record<number, MultihashHasher>
44
- private started: boolean
45
- private readonly log: Logger
26
+ class Storage implements Blockstore {
27
+ protected readonly child: Blockstore
28
+ protected readonly hashers: Record<number, MultihashHasher>
29
+ protected log: Logger
30
+ protected readonly logger: ComponentLogger
31
+ protected readonly components: StorageComponents
46
32
 
47
33
  /**
48
34
  * Create a new BlockStorage
49
35
  */
50
- constructor (components: NetworkedStorageComponents) {
36
+ constructor (components: StorageComponents) {
51
37
  this.log = components.logger.forComponent('helia:networked-storage')
52
- this.child = new TieredBlockstore([
53
- new IdentityBlockstore(),
54
- components.blockstore
55
- ])
56
- this.blockRetrievers = (components.blockBrokers ?? []).filter(isBlockRetriever)
57
- this.blockAnnouncers = (components.blockBrokers ?? []).filter(isBlockAnnouncer)
38
+ this.logger = components.logger
39
+ this.components = components
40
+ this.child = new IdentityBlockstore(components.blockstore)
58
41
  this.hashers = components.hashers ?? {}
59
- this.started = false
60
- }
61
-
62
- isStarted (): boolean {
63
- return this.started
64
- }
65
-
66
- async start (): Promise<void> {
67
- await start(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
68
- this.started = true
69
- }
70
-
71
- async stop (): Promise<void> {
72
- await stop(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
73
- this.started = false
74
- }
75
-
76
- unwrap (): Blockstore {
77
- return this.child
78
42
  }
79
43
 
80
44
  /**
81
45
  * Put a block to the underlying datastore
82
46
  */
83
47
  async put (cid: CID, block: Uint8Array, options: AbortOptions & ProgressOptions<PutBlockProgressEvents> = {}): Promise<CID> {
84
- if (await this.child.has(cid)) {
48
+ if (await this.child.has(cid, options)) {
85
49
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:duplicate', cid))
86
50
  return cid
87
51
  }
88
52
 
89
53
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:providers:notify', cid))
90
54
 
91
- this.blockAnnouncers.forEach(provider => {
92
- provider.announce(cid, block, options)
93
- })
55
+ await Promise.all(
56
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
57
+ )
94
58
 
95
59
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:blockstore:put', cid))
96
60
 
@@ -102,7 +66,7 @@ export class NetworkedStorage implements Blocks, Startable {
102
66
  */
103
67
  async * putMany (blocks: AwaitIterable<{ cid: CID, block: Uint8Array }>, options: AbortOptions & ProgressOptions<PutManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
104
68
  const missingBlocks = filter(blocks, async ({ cid }): Promise<boolean> => {
105
- const has = await this.child.has(cid)
69
+ const has = await this.child.has(cid, options)
106
70
 
107
71
  if (has) {
108
72
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:duplicate', cid))
@@ -111,11 +75,11 @@ export class NetworkedStorage implements Blocks, Startable {
111
75
  return !has
112
76
  })
113
77
 
114
- const notifyEach = forEach(missingBlocks, ({ cid, block }): void => {
78
+ const notifyEach = forEach(missingBlocks, async ({ cid, block }): Promise<void> => {
115
79
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:providers:notify', cid))
116
- this.blockAnnouncers.forEach(provider => {
117
- provider.announce(cid, block, options)
118
- })
80
+ await Promise.all(
81
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
82
+ )
119
83
  })
120
84
 
121
85
  options.onProgress?.(new CustomProgressEvent('blocks:put-many:blockstore:put-many'))
@@ -126,10 +90,10 @@ export class NetworkedStorage implements Blocks, Startable {
126
90
  * Get a block by cid
127
91
  */
128
92
  async get (cid: CID, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetBlockProgressEvents> = {}): Promise<Uint8Array> {
129
- if (options.offline !== true && !(await this.child.has(cid))) {
93
+ if (options.offline !== true && !(await this.child.has(cid, options))) {
130
94
  // we do not have the block locally, get it from a block provider
131
95
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:get', cid))
132
- const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers[cid.multihash.code], {
96
+ const block = await raceBlockRetrievers(cid, this.components.blockBrokers, this.hashers[cid.multihash.code], {
133
97
  ...options,
134
98
  log: this.log
135
99
  })
@@ -138,9 +102,9 @@ export class NetworkedStorage implements Blocks, Startable {
138
102
 
139
103
  // notify other block providers of the new block
140
104
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:notify', cid))
141
- this.blockAnnouncers.forEach(provider => {
142
- provider.announce(cid, block, options)
143
- })
105
+ await Promise.all(
106
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
107
+ )
144
108
 
145
109
  return block
146
110
  }
@@ -157,10 +121,10 @@ export class NetworkedStorage implements Blocks, Startable {
157
121
  options.onProgress?.(new CustomProgressEvent('blocks:get-many:blockstore:get-many'))
158
122
 
159
123
  yield * this.child.getMany(forEach(cids, async (cid): Promise<void> => {
160
- if (options.offline !== true && !(await this.child.has(cid))) {
124
+ if (options.offline !== true && !(await this.child.has(cid, options))) {
161
125
  // we do not have the block locally, get it from a block provider
162
126
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:get', cid))
163
- const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers[cid.multihash.code], {
127
+ const block = await raceBlockRetrievers(cid, this.components.blockBrokers, this.hashers[cid.multihash.code], {
164
128
  ...options,
165
129
  log: this.log
166
130
  })
@@ -169,9 +133,9 @@ export class NetworkedStorage implements Blocks, Startable {
169
133
 
170
134
  // notify other block providers of the new block
171
135
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:notify', cid))
172
- this.blockAnnouncers.forEach(provider => {
173
- provider.announce(cid, block, options)
174
- })
136
+ await Promise.all(
137
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
138
+ )
175
139
  }
176
140
  }))
177
141
  }
@@ -207,6 +171,223 @@ export class NetworkedStorage implements Blocks, Startable {
207
171
  }
208
172
  }
209
173
 
174
+ export type NetworkedStorageComponents = StorageComponents
175
+
176
+ /**
177
+ * Networked storage wraps a regular blockstore - when getting blocks if the
178
+ * blocks are not present, the configured BlockBrokers will be used to fetch them.
179
+ */
180
+ export class NetworkedStorage extends Storage implements Blocks, Startable {
181
+ private started: boolean
182
+
183
+ /**
184
+ * Create a new BlockStorage
185
+ */
186
+ constructor (components: NetworkedStorageComponents) {
187
+ super(components)
188
+
189
+ this.started = false
190
+ }
191
+
192
+ isStarted (): boolean {
193
+ return this.started
194
+ }
195
+
196
+ async start (): Promise<void> {
197
+ await start(this.child, ...this.components.blockBrokers)
198
+ this.started = true
199
+ }
200
+
201
+ async stop (): Promise<void> {
202
+ await stop(this.child, ...this.components.blockBrokers)
203
+ this.started = false
204
+ }
205
+
206
+ unwrap (): Blockstore {
207
+ return this.child
208
+ }
209
+
210
+ createSession (root: CID, options?: CreateSessionOptions): SessionBlockstore {
211
+ const blockBrokers = this.components.blockBrokers.map(broker => {
212
+ if (broker.createSession == null) {
213
+ return broker
214
+ }
215
+
216
+ return broker.createSession(options)
217
+ })
218
+
219
+ return new SessionStorage({
220
+ blockstore: this.child,
221
+ blockBrokers,
222
+ hashers: this.hashers,
223
+ logger: this.logger
224
+ }, {
225
+ root
226
+ })
227
+ }
228
+ }
229
+
230
+ interface SessionStorageInit {
231
+ root: CID
232
+ }
233
+
234
+ /**
235
+ * Storage subclass that can cancel any ongoing operation at any point.
236
+ */
237
+ class SessionStorage extends Storage implements SessionBlockstore {
238
+ private readonly closeController: AbortController
239
+
240
+ constructor (components: StorageComponents, init: SessionStorageInit) {
241
+ super(components)
242
+
243
+ // because brokers are allowed to continue searching for providers after the
244
+ // session has been created, we need a way to tell them that the user has
245
+ // finished using the session any in-flight requests should be cancelled
246
+ this.closeController = new AbortController()
247
+ setMaxListeners(Infinity, this.closeController.signal)
248
+
249
+ this.log = components.logger.forComponent(`helia:session-storage${init.root}`)
250
+ }
251
+
252
+ close (): void {
253
+ this.closeController.abort()
254
+ }
255
+
256
+ /**
257
+ * Put a block to the underlying datastore
258
+ */
259
+ async put (cid: CID, block: Uint8Array, options: AbortOptions & ProgressOptions<PutBlockProgressEvents> = {}): Promise<CID> {
260
+ const signal = anySignal([this.closeController.signal, options.signal])
261
+ setMaxListeners(Infinity, signal)
262
+
263
+ try {
264
+ return await super.put(cid, block, {
265
+ ...options,
266
+ signal
267
+ })
268
+ } finally {
269
+ signal.clear()
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Put a multiple blocks to the underlying datastore
275
+ */
276
+ async * putMany (blocks: AwaitIterable<{ cid: CID, block: Uint8Array }>, options: AbortOptions & ProgressOptions<PutManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
277
+ const signal = anySignal([this.closeController.signal, options.signal])
278
+ setMaxListeners(Infinity, signal)
279
+
280
+ try {
281
+ yield * super.putMany(blocks, {
282
+ ...options,
283
+ signal
284
+ })
285
+ } finally {
286
+ signal.clear()
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Get a block by cid
292
+ */
293
+ async get (cid: CID, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetBlockProgressEvents> = {}): Promise<Uint8Array> {
294
+ const signal = anySignal([this.closeController.signal, options.signal])
295
+ setMaxListeners(Infinity, signal)
296
+
297
+ try {
298
+ return await super.get(cid, {
299
+ ...options,
300
+ signal
301
+ })
302
+ } finally {
303
+ signal.clear()
304
+ }
305
+ }
306
+
307
+ /**
308
+ * Get multiple blocks back from an (async) iterable of cids
309
+ */
310
+ async * getMany (cids: AwaitIterable<CID>, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetManyBlocksProgressEvents> = {}): AsyncIterable<Pair> {
311
+ const signal = anySignal([this.closeController.signal, options.signal])
312
+ setMaxListeners(Infinity, signal)
313
+
314
+ try {
315
+ yield * super.getMany(cids, {
316
+ ...options,
317
+ signal
318
+ })
319
+ } finally {
320
+ signal.clear()
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Delete a block from the blockstore
326
+ */
327
+ async delete (cid: CID, options: AbortOptions & ProgressOptions<DeleteBlockProgressEvents> = {}): Promise<void> {
328
+ const signal = anySignal([this.closeController.signal, options.signal])
329
+ setMaxListeners(Infinity, signal)
330
+
331
+ try {
332
+ await super.delete(cid, {
333
+ ...options,
334
+ signal
335
+ })
336
+ } finally {
337
+ signal.clear()
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Delete multiple blocks from the blockstore
343
+ */
344
+ async * deleteMany (cids: AwaitIterable<CID>, options: AbortOptions & ProgressOptions<DeleteManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
345
+ const signal = anySignal([this.closeController.signal, options.signal])
346
+ setMaxListeners(Infinity, signal)
347
+
348
+ try {
349
+ yield * super.deleteMany(cids, {
350
+ ...options,
351
+ signal
352
+ })
353
+ } finally {
354
+ signal.clear()
355
+ }
356
+ }
357
+
358
+ async has (cid: CID, options: AbortOptions = {}): Promise<boolean> {
359
+ const signal = anySignal([this.closeController.signal, options.signal])
360
+ setMaxListeners(Infinity, signal)
361
+
362
+ try {
363
+ return await super.has(cid, {
364
+ ...options,
365
+ signal
366
+ })
367
+ } finally {
368
+ signal.clear()
369
+ }
370
+ }
371
+
372
+ async * getAll (options: AbortOptions & ProgressOptions<GetAllBlocksProgressEvents> = {}): AwaitIterable<Pair> {
373
+ const signal = anySignal([this.closeController.signal, options.signal])
374
+ setMaxListeners(Infinity, signal)
375
+
376
+ try {
377
+ yield * super.getAll({
378
+ ...options,
379
+ signal
380
+ })
381
+ } finally {
382
+ signal.clear()
383
+ }
384
+ }
385
+ }
386
+
387
+ function isRetrievingBlockBroker (broker: BlockBroker): broker is Required<Pick<BlockBroker, 'retrieve'>> {
388
+ return typeof broker.retrieve === 'function'
389
+ }
390
+
210
391
  export const getCidBlockVerifierFunction = (cid: CID, hasher: MultihashHasher): Required<BlockRetrievalOptions>['validateFn'] => {
211
392
  if (hasher == null) {
212
393
  throw new CodeError(`No hasher configured for multihash code 0x${cid.multihash.code.toString(16)}, please configure one. You can look up which hash this is at https://github.com/multiformats/multicodec/blob/master/table.csv`, 'ERR_UNKNOWN_HASH_ALG')
@@ -227,40 +408,53 @@ export const getCidBlockVerifierFunction = (cid: CID, hasher: MultihashHasher):
227
408
  * Race block providers cancelling any pending requests once the block has been
228
409
  * found.
229
410
  */
230
- async function raceBlockRetrievers (cid: CID, providers: BlockRetriever[], hasher: MultihashHasher, options: AbortOptions & LoggerOptions): Promise<Uint8Array> {
411
+ async function raceBlockRetrievers (cid: CID, blockBrokers: BlockBroker[], hasher: MultihashHasher, options: AbortOptions & LoggerOptions): Promise<Uint8Array> {
231
412
  const validateFn = getCidBlockVerifierFunction(cid, hasher)
232
413
 
233
414
  const controller = new AbortController()
234
415
  const signal = anySignal([controller.signal, options.signal])
416
+ setMaxListeners(Infinity, controller.signal, signal)
417
+
418
+ const retrievers: Array<Required<Pick<BlockBroker, 'retrieve'>>> = []
419
+
420
+ for (const broker of blockBrokers) {
421
+ if (isRetrievingBlockBroker(broker)) {
422
+ retrievers.push(broker)
423
+ }
424
+ }
235
425
 
236
426
  try {
237
427
  return await Promise.any(
238
- providers.map(async provider => {
239
- try {
240
- let blocksWereValidated = false
241
- const block = await provider.retrieve(cid, {
242
- ...options,
243
- signal,
244
- validateFn: async (block: Uint8Array): Promise<void> => {
428
+ retrievers
429
+ .map(async retriever => {
430
+ try {
431
+ let blocksWereValidated = false
432
+ const block = await retriever.retrieve(cid, {
433
+ ...options,
434
+ signal,
435
+ validateFn: async (block: Uint8Array): Promise<void> => {
436
+ await validateFn(block)
437
+ blocksWereValidated = true
438
+ }
439
+ })
440
+
441
+ if (!blocksWereValidated) {
442
+ // the blockBroker either did not throw an error when attempting to validate the block
443
+ // or did not call the validateFn at all. We should validate the block ourselves
245
444
  await validateFn(block)
246
- blocksWereValidated = true
247
445
  }
248
- })
249
446
 
250
- if (!blocksWereValidated) {
251
- // the blockBroker either did not throw an error when attempting to validate the block
252
- // or did not call the validateFn at all. We should validate the block ourselves
253
- await validateFn(block)
447
+ return block
448
+ } catch (err) {
449
+ options.log.error('could not retrieve verified block for %c', cid, err)
450
+ throw err
254
451
  }
255
-
256
- return block
257
- } catch (err) {
258
- options.log.error('could not retrieve verified block for %c', cid, err)
259
- throw err
260
- }
261
- })
452
+ })
262
453
  )
263
454
  } finally {
455
+ // we have the block from the fastest block retriever, abort any still
456
+ // in-flight retrieve attempts
457
+ controller.abort()
264
458
  signal.clear()
265
459
  }
266
460
  }