@helia/utils 0.1.0-9ac5909 → 0.1.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/src/storage.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { start, stop } from '@libp2p/interface'
1
+ import { CodeError, start, stop } from '@libp2p/interface'
2
2
  import createMortice from 'mortice'
3
3
  import type { Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions } from '@helia/interface/blocks'
4
4
  import type { Pins } from '@helia/interface/pins'
@@ -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({
@@ -169,4 +169,20 @@ export class BlockStorage implements Blocks, Startable {
169
169
  releaseLock()
170
170
  }
171
171
  }
172
+
173
+ async createSession (root: CID, options?: AbortOptions): Promise<Blockstore> {
174
+ const releaseLock = await this.lock.readLock()
175
+
176
+ try {
177
+ const blocks = await this.child.createSession(root, options)
178
+
179
+ if (blocks == null) {
180
+ throw new CodeError('Sessions not supported', 'ERR_UNSUPPORTED')
181
+ }
182
+
183
+ return blocks
184
+ } finally {
185
+ releaseLock()
186
+ }
187
+ }
172
188
  }
@@ -1,35 +1,30 @@
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 } 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'
13
12
  import type { CID } from 'multiformats/cid'
14
13
  import type { MultihashHasher } from 'multiformats/hashes/interface'
15
14
 
16
- export interface GetOptions extends AbortOptions {
17
- progress?(evt: Event): void
18
- }
19
-
20
- function isBlockRetriever (b: any): b is BlockRetriever {
21
- return typeof b.retrieve === 'function'
15
+ export interface NetworkedStorageInit {
16
+ root?: CID
22
17
  }
23
18
 
24
- function isBlockAnnouncer (b: any): b is BlockAnnouncer {
25
- return typeof b.announce === 'function'
19
+ export interface GetOptions extends AbortOptions {
20
+ progress?(evt: Event): void
26
21
  }
27
22
 
28
23
  export interface NetworkedStorageComponents {
29
24
  blockstore: Blockstore
30
25
  logger: ComponentLogger
31
- blockBrokers?: BlockBroker[]
32
- hashers?: Record<number, MultihashHasher>
26
+ blockBrokers: BlockBroker[]
27
+ hashers: Record<number, MultihashHasher>
33
28
  }
34
29
 
35
30
  /**
@@ -38,23 +33,20 @@ export interface NetworkedStorageComponents {
38
33
  */
39
34
  export class NetworkedStorage implements Blocks, Startable {
40
35
  private readonly child: Blockstore
41
- private readonly blockRetrievers: BlockRetriever[]
42
- private readonly blockAnnouncers: BlockAnnouncer[]
43
36
  private readonly hashers: Record<number, MultihashHasher>
44
37
  private started: boolean
45
38
  private readonly log: Logger
39
+ private readonly logger: ComponentLogger
40
+ private readonly components: NetworkedStorageComponents
46
41
 
47
42
  /**
48
43
  * Create a new BlockStorage
49
44
  */
50
- constructor (components: NetworkedStorageComponents) {
51
- 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)
45
+ constructor (components: NetworkedStorageComponents, init: NetworkedStorageInit = {}) {
46
+ this.log = components.logger.forComponent(`helia:networked-storage${init.root == null ? '' : `:${init.root}`}`)
47
+ this.logger = components.logger
48
+ this.components = components
49
+ this.child = new IdentityBlockstore(components.blockstore)
58
50
  this.hashers = components.hashers ?? {}
59
51
  this.started = false
60
52
  }
@@ -64,12 +56,12 @@ export class NetworkedStorage implements Blocks, Startable {
64
56
  }
65
57
 
66
58
  async start (): Promise<void> {
67
- await start(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
59
+ await start(this.child, ...this.components.blockBrokers)
68
60
  this.started = true
69
61
  }
70
62
 
71
63
  async stop (): Promise<void> {
72
- await stop(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
64
+ await stop(this.child, ...this.components.blockBrokers)
73
65
  this.started = false
74
66
  }
75
67
 
@@ -88,9 +80,9 @@ export class NetworkedStorage implements Blocks, Startable {
88
80
 
89
81
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:providers:notify', cid))
90
82
 
91
- this.blockAnnouncers.forEach(provider => {
92
- provider.announce(cid, block, options)
93
- })
83
+ await Promise.all(
84
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
85
+ )
94
86
 
95
87
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:blockstore:put', cid))
96
88
 
@@ -111,11 +103,11 @@ export class NetworkedStorage implements Blocks, Startable {
111
103
  return !has
112
104
  })
113
105
 
114
- const notifyEach = forEach(missingBlocks, ({ cid, block }): void => {
106
+ const notifyEach = forEach(missingBlocks, async ({ cid, block }): Promise<void> => {
115
107
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:providers:notify', cid))
116
- this.blockAnnouncers.forEach(provider => {
117
- provider.announce(cid, block, options)
118
- })
108
+ await Promise.all(
109
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
110
+ )
119
111
  })
120
112
 
121
113
  options.onProgress?.(new CustomProgressEvent('blocks:put-many:blockstore:put-many'))
@@ -129,7 +121,7 @@ export class NetworkedStorage implements Blocks, Startable {
129
121
  if (options.offline !== true && !(await this.child.has(cid))) {
130
122
  // we do not have the block locally, get it from a block provider
131
123
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:get', cid))
132
- const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers[cid.multihash.code], {
124
+ const block = await raceBlockRetrievers(cid, this.components.blockBrokers, this.hashers[cid.multihash.code], {
133
125
  ...options,
134
126
  log: this.log
135
127
  })
@@ -138,9 +130,9 @@ export class NetworkedStorage implements Blocks, Startable {
138
130
 
139
131
  // notify other block providers of the new block
140
132
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:notify', cid))
141
- this.blockAnnouncers.forEach(provider => {
142
- provider.announce(cid, block, options)
143
- })
133
+ await Promise.all(
134
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
135
+ )
144
136
 
145
137
  return block
146
138
  }
@@ -160,7 +152,7 @@ export class NetworkedStorage implements Blocks, Startable {
160
152
  if (options.offline !== true && !(await this.child.has(cid))) {
161
153
  // we do not have the block locally, get it from a block provider
162
154
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:get', cid))
163
- const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers[cid.multihash.code], {
155
+ const block = await raceBlockRetrievers(cid, this.components.blockBrokers, this.hashers[cid.multihash.code], {
164
156
  ...options,
165
157
  log: this.log
166
158
  })
@@ -169,9 +161,9 @@ export class NetworkedStorage implements Blocks, Startable {
169
161
 
170
162
  // notify other block providers of the new block
171
163
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:notify', cid))
172
- this.blockAnnouncers.forEach(provider => {
173
- provider.announce(cid, block, options)
174
- })
164
+ await Promise.all(
165
+ this.components.blockBrokers.map(async broker => broker.announce?.(cid, block, options))
166
+ )
175
167
  }
176
168
  }))
177
169
  }
@@ -205,8 +197,30 @@ export class NetworkedStorage implements Blocks, Startable {
205
197
  options.onProgress?.(new CustomProgressEvent('blocks:get-all:blockstore:get-many'))
206
198
  yield * this.child.getAll(options)
207
199
  }
200
+
201
+ async createSession (root: CID, options?: AbortOptions & ProgressOptions<GetBlockProgressEvents>): Promise<Blocks> {
202
+ const blockBrokers = await Promise.all(this.components.blockBrokers.map(async broker => {
203
+ if (broker.createSession == null) {
204
+ return broker
205
+ }
206
+
207
+ return broker.createSession(root, options)
208
+ }))
209
+
210
+ return new NetworkedStorage({
211
+ blockstore: this.child,
212
+ blockBrokers,
213
+ hashers: this.hashers,
214
+ logger: this.logger
215
+ }, {
216
+ root
217
+ })
218
+ }
208
219
  }
209
220
 
221
+ function isRetrievingBlockBroker (broker: BlockBroker): broker is Required<Pick<BlockBroker, 'retrieve'>> {
222
+ return typeof broker.retrieve === 'function'
223
+ }
210
224
  export const getCidBlockVerifierFunction = (cid: CID, hasher: MultihashHasher): Required<BlockRetrievalOptions>['validateFn'] => {
211
225
  if (hasher == null) {
212
226
  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 +241,53 @@ export const getCidBlockVerifierFunction = (cid: CID, hasher: MultihashHasher):
227
241
  * Race block providers cancelling any pending requests once the block has been
228
242
  * found.
229
243
  */
230
- async function raceBlockRetrievers (cid: CID, providers: BlockRetriever[], hasher: MultihashHasher, options: AbortOptions & LoggerOptions): Promise<Uint8Array> {
244
+ async function raceBlockRetrievers (cid: CID, blockBrokers: BlockBroker[], hasher: MultihashHasher, options: AbortOptions & LoggerOptions): Promise<Uint8Array> {
231
245
  const validateFn = getCidBlockVerifierFunction(cid, hasher)
232
246
 
233
247
  const controller = new AbortController()
234
248
  const signal = anySignal([controller.signal, options.signal])
249
+ setMaxListeners(Infinity, controller.signal, signal)
250
+
251
+ const retrievers: Array<Required<Pick<BlockBroker, 'retrieve'>>> = []
252
+
253
+ for (const broker of blockBrokers) {
254
+ if (isRetrievingBlockBroker(broker)) {
255
+ retrievers.push(broker)
256
+ }
257
+ }
235
258
 
236
259
  try {
237
260
  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> => {
261
+ retrievers
262
+ .map(async retriever => {
263
+ try {
264
+ let blocksWereValidated = false
265
+ const block = await retriever.retrieve(cid, {
266
+ ...options,
267
+ signal,
268
+ validateFn: async (block: Uint8Array): Promise<void> => {
269
+ await validateFn(block)
270
+ blocksWereValidated = true
271
+ }
272
+ })
273
+
274
+ if (!blocksWereValidated) {
275
+ // the blockBroker either did not throw an error when attempting to validate the block
276
+ // or did not call the validateFn at all. We should validate the block ourselves
245
277
  await validateFn(block)
246
- blocksWereValidated = true
247
278
  }
248
- })
249
279
 
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)
280
+ return block
281
+ } catch (err) {
282
+ options.log.error('could not retrieve verified block for %c', cid, err)
283
+ throw err
254
284
  }
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
- })
285
+ })
262
286
  )
263
287
  } finally {
288
+ // we have the block from the fastest block retriever, abort any still
289
+ // in-flight retrieve attempts
290
+ controller.abort()
264
291
  signal.clear()
265
292
  }
266
293
  }