helia 2.0.3 → 2.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.
Files changed (67) hide show
  1. package/README.md +17 -8
  2. package/dist/index.min.js +40 -41
  3. package/dist/src/block-brokers/bitswap.d.ts +19 -0
  4. package/dist/src/block-brokers/bitswap.d.ts.map +1 -0
  5. package/dist/src/block-brokers/bitswap.js +48 -0
  6. package/dist/src/block-brokers/bitswap.js.map +1 -0
  7. package/dist/src/block-brokers/index.d.ts +3 -0
  8. package/dist/src/block-brokers/index.d.ts.map +1 -0
  9. package/dist/src/block-brokers/index.js +3 -0
  10. package/dist/src/block-brokers/index.js.map +1 -0
  11. package/dist/src/block-brokers/trustless-gateway/broker.d.ts +14 -0
  12. package/dist/src/block-brokers/trustless-gateway/broker.d.ts.map +1 -0
  13. package/dist/src/block-brokers/trustless-gateway/broker.js +55 -0
  14. package/dist/src/block-brokers/trustless-gateway/broker.js.map +1 -0
  15. package/dist/src/block-brokers/trustless-gateway/index.d.ts +9 -0
  16. package/dist/src/block-brokers/trustless-gateway/index.d.ts.map +1 -0
  17. package/dist/src/block-brokers/trustless-gateway/index.js +15 -0
  18. package/dist/src/block-brokers/trustless-gateway/index.js.map +1 -0
  19. package/dist/src/block-brokers/trustless-gateway/trustless-gateway.d.ts +31 -0
  20. package/dist/src/block-brokers/trustless-gateway/trustless-gateway.d.ts.map +1 -0
  21. package/dist/src/block-brokers/trustless-gateway/trustless-gateway.js +114 -0
  22. package/dist/src/block-brokers/trustless-gateway/trustless-gateway.js.map +1 -0
  23. package/dist/src/helia.d.ts +0 -1
  24. package/dist/src/helia.d.ts.map +1 -1
  25. package/dist/src/helia.js +19 -25
  26. package/dist/src/helia.js.map +1 -1
  27. package/dist/src/index.d.ts +13 -9
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +6 -8
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/storage.d.ts +7 -2
  32. package/dist/src/storage.d.ts.map +1 -1
  33. package/dist/src/storage.js +14 -0
  34. package/dist/src/storage.js.map +1 -1
  35. package/dist/src/utils/default-hashers.d.ts +3 -0
  36. package/dist/src/utils/default-hashers.d.ts.map +1 -0
  37. package/dist/src/utils/default-hashers.js +11 -0
  38. package/dist/src/utils/default-hashers.js.map +1 -0
  39. package/dist/src/utils/libp2p-defaults.browser.d.ts +1 -0
  40. package/dist/src/utils/libp2p-defaults.browser.d.ts.map +1 -1
  41. package/dist/src/utils/libp2p-defaults.browser.js +2 -4
  42. package/dist/src/utils/libp2p-defaults.browser.js.map +1 -1
  43. package/dist/src/utils/libp2p-defaults.d.ts +1 -0
  44. package/dist/src/utils/libp2p-defaults.d.ts.map +1 -1
  45. package/dist/src/utils/libp2p-defaults.js +3 -4
  46. package/dist/src/utils/libp2p-defaults.js.map +1 -1
  47. package/dist/src/utils/networked-storage.d.ts +17 -9
  48. package/dist/src/utils/networked-storage.d.ts.map +1 -1
  49. package/dist/src/utils/networked-storage.js +109 -15
  50. package/dist/src/utils/networked-storage.js.map +1 -1
  51. package/dist/src/version.d.ts +1 -1
  52. package/dist/src/version.js +1 -1
  53. package/dist/typedoc-urls.json +10 -6
  54. package/package.json +32 -6
  55. package/src/block-brokers/bitswap.ts +78 -0
  56. package/src/block-brokers/index.ts +2 -0
  57. package/src/block-brokers/trustless-gateway/broker.ts +65 -0
  58. package/src/block-brokers/trustless-gateway/index.ts +28 -0
  59. package/src/block-brokers/trustless-gateway/trustless-gateway.ts +126 -0
  60. package/src/helia.ts +20 -30
  61. package/src/index.ts +14 -9
  62. package/src/storage.ts +19 -2
  63. package/src/utils/default-hashers.ts +12 -0
  64. package/src/utils/libp2p-defaults.browser.ts +3 -4
  65. package/src/utils/libp2p-defaults.ts +4 -4
  66. package/src/utils/networked-storage.ts +134 -23
  67. package/src/version.ts +1 -1
@@ -1,36 +1,71 @@
1
+ import { CodeError } from '@libp2p/interface/errors'
2
+ import { start, stop, type Startable } from '@libp2p/interface/startable'
3
+ import { logger } from '@libp2p/logger'
4
+ import { anySignal } from 'any-signal'
1
5
  import filter from 'it-filter'
2
6
  import forEach from 'it-foreach'
3
7
  import { CustomProgressEvent, type ProgressOptions } from 'progress-events'
4
- import type { Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions } from '@helia/interface/blocks'
8
+ 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'
5
10
  import type { AbortOptions } from '@libp2p/interface'
6
11
  import type { Blockstore } from 'interface-blockstore'
7
12
  import type { AwaitIterable } from 'interface-store'
8
- import type { Bitswap } from 'ipfs-bitswap'
9
13
  import type { CID } from 'multiformats/cid'
14
+ import type { MultihashHasher } from 'multiformats/hashes/interface'
10
15
 
11
- export interface BlockStorageInit {
12
- holdGcLock?: boolean
13
- bitswap?: Bitswap
16
+ const log = logger('helia:networked-storage')
17
+
18
+ export interface NetworkedStorageStorageInit {
19
+ blockBrokers?: BlockBroker[]
20
+ hashers?: MultihashHasher[]
14
21
  }
15
22
 
16
23
  export interface GetOptions extends AbortOptions {
17
- progress?: (evt: Event) => void
24
+ progress?(evt: Event): void
25
+ }
26
+
27
+ function isBlockRetriever (b: any): b is BlockRetriever {
28
+ return typeof b.retrieve === 'function'
29
+ }
30
+
31
+ function isBlockAnnouncer (b: any): b is BlockAnnouncer {
32
+ return typeof b.announce === 'function'
18
33
  }
19
34
 
20
35
  /**
21
36
  * Networked storage wraps a regular blockstore - when getting blocks if the
22
37
  * blocks are not present Bitswap will be used to fetch them from network peers.
23
38
  */
24
- export class NetworkedStorage implements Blocks {
39
+ export class NetworkedStorage implements Blocks, Startable {
25
40
  private readonly child: Blockstore
26
- private readonly bitswap?: Bitswap
41
+ private readonly blockRetrievers: BlockRetriever[]
42
+ private readonly blockAnnouncers: BlockAnnouncer[]
43
+ private readonly hashers: MultihashHasher[]
44
+ private started: boolean
27
45
 
28
46
  /**
29
47
  * Create a new BlockStorage
30
48
  */
31
- constructor (blockstore: Blockstore, options: BlockStorageInit = {}) {
49
+ constructor (blockstore: Blockstore, init: NetworkedStorageStorageInit) {
32
50
  this.child = blockstore
33
- this.bitswap = options.bitswap
51
+ this.blockRetrievers = (init.blockBrokers ?? []).filter(isBlockRetriever)
52
+ this.blockAnnouncers = (init.blockBrokers ?? []).filter(isBlockAnnouncer)
53
+ this.hashers = init.hashers ?? []
54
+ this.started = false
55
+ }
56
+
57
+ isStarted (): boolean {
58
+ return this.started
59
+ }
60
+
61
+ async start (): Promise<void> {
62
+ await start(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
63
+ this.started = true
64
+ }
65
+
66
+ async stop (): Promise<void> {
67
+ await stop(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
68
+ this.started = false
34
69
  }
35
70
 
36
71
  unwrap (): Blockstore {
@@ -46,10 +81,11 @@ export class NetworkedStorage implements Blocks {
46
81
  return cid
47
82
  }
48
83
 
49
- if (this.bitswap?.isStarted() === true) {
50
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:bitswap:notify', cid))
51
- this.bitswap.notify(cid, block, options)
52
- }
84
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:providers:notify', cid))
85
+
86
+ this.blockAnnouncers.forEach(provider => {
87
+ provider.announce(cid, block, options)
88
+ })
53
89
 
54
90
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:blockstore:put', cid))
55
91
 
@@ -71,8 +107,10 @@ export class NetworkedStorage implements Blocks {
71
107
  })
72
108
 
73
109
  const notifyEach = forEach(missingBlocks, ({ cid, block }): void => {
74
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:bitswap:notify', cid))
75
- this.bitswap?.notify(cid, block, options)
110
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:providers:notify', cid))
111
+ this.blockAnnouncers.forEach(provider => {
112
+ provider.announce(cid, block, options)
113
+ })
76
114
  })
77
115
 
78
116
  options.onProgress?.(new CustomProgressEvent('blocks:put-many:blockstore:put-many'))
@@ -83,13 +121,19 @@ export class NetworkedStorage implements Blocks {
83
121
  * Get a block by cid
84
122
  */
85
123
  async get (cid: CID, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetBlockProgressEvents> = {}): Promise<Uint8Array> {
86
- if (options.offline !== true && this.bitswap?.isStarted() != null && !(await this.child.has(cid))) {
87
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:bitswap:get', cid))
88
- const block = await this.bitswap.want(cid, options)
89
-
124
+ if (options.offline !== true && !(await this.child.has(cid))) {
125
+ // we do not have the block locally, get it from a block provider
126
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:get', cid))
127
+ const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers, options)
90
128
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:blockstore:put', cid))
91
129
  await this.child.put(cid, block, options)
92
130
 
131
+ // notify other block providers of the new block
132
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:notify', cid))
133
+ this.blockAnnouncers.forEach(provider => {
134
+ provider.announce(cid, block, options)
135
+ })
136
+
93
137
  return block
94
138
  }
95
139
 
@@ -105,11 +149,18 @@ export class NetworkedStorage implements Blocks {
105
149
  options.onProgress?.(new CustomProgressEvent('blocks:get-many:blockstore:get-many'))
106
150
 
107
151
  yield * this.child.getMany(forEach(cids, async (cid): Promise<void> => {
108
- if (options.offline !== true && this.bitswap?.isStarted() === true && !(await this.child.has(cid))) {
109
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:bitswap:get', cid))
110
- const block = await this.bitswap.want(cid, options)
152
+ if (options.offline !== true && !(await this.child.has(cid))) {
153
+ // we do not have the block locally, get it from a block provider
154
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:get', cid))
155
+ const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers, options)
111
156
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:blockstore:put', cid))
112
157
  await this.child.put(cid, block, options)
158
+
159
+ // notify other block providers of the new block
160
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:notify', cid))
161
+ this.blockAnnouncers.forEach(provider => {
162
+ provider.announce(cid, block, options)
163
+ })
113
164
  }
114
165
  }))
115
166
  }
@@ -144,3 +195,63 @@ export class NetworkedStorage implements Blocks {
144
195
  yield * this.child.getAll(options)
145
196
  }
146
197
  }
198
+
199
+ export const getCidBlockVerifierFunction = (cid: CID, hashers: MultihashHasher[]): Required<BlockRetrievalOptions>['validateFn'] => {
200
+ const hasher = hashers.find(hasher => hasher.code === cid.multihash.code)
201
+
202
+ if (hasher == null) {
203
+ 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')
204
+ }
205
+
206
+ return async (block: Uint8Array): Promise<void> => {
207
+ // verify block
208
+ const hash = await hasher.digest(block)
209
+
210
+ if (!uint8ArrayEquals(hash.digest, cid.multihash.digest)) {
211
+ // if a hash mismatch occurs for a TrustlessGatewayBlockBroker, we should try another gateway
212
+ throw new CodeError('Hash of downloaded block did not match multihash from passed CID', 'ERR_HASH_MISMATCH')
213
+ }
214
+ }
215
+ }
216
+
217
+ /**
218
+ * Race block providers cancelling any pending requests once the block has been
219
+ * found.
220
+ */
221
+ async function raceBlockRetrievers (cid: CID, providers: BlockRetriever[], hashers: MultihashHasher[], options: AbortOptions): Promise<Uint8Array> {
222
+ const validateFn = getCidBlockVerifierFunction(cid, hashers)
223
+
224
+ const controller = new AbortController()
225
+ const signal = anySignal([controller.signal, options.signal])
226
+
227
+ try {
228
+ return await Promise.any(
229
+ providers.map(async provider => {
230
+ try {
231
+ let blocksWereValidated = false
232
+ const block = await provider.retrieve(cid, {
233
+ ...options,
234
+ signal,
235
+ validateFn: async (block: Uint8Array): Promise<void> => {
236
+ await validateFn(block)
237
+ blocksWereValidated = true
238
+ }
239
+ })
240
+
241
+ if (!blocksWereValidated) {
242
+ // the blockBroker either did not throw an error when attempting to validate the block
243
+ // or did not call the validateFn at all. We should validate the block ourselves
244
+ await validateFn(block)
245
+ }
246
+
247
+ return block
248
+ } catch (err) {
249
+ log.error('could not retrieve verified block for %c', cid, err)
250
+ throw err
251
+ }
252
+ })
253
+ )
254
+ } finally {
255
+ signal.clear()
256
+ }
257
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '2.0.3'
1
+ export const version = '2.1.0'
2
2
  export const name = 'helia'