helia 2.0.3 → 2.1.0-299bb09

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 (77) hide show
  1. package/README.md +17 -8
  2. package/dist/index.min.js +52 -42
  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 +15 -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 +13 -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 +3 -2
  24. package/dist/src/helia.d.ts.map +1 -1
  25. package/dist/src/helia.js +28 -31
  26. package/dist/src/helia.js.map +1 -1
  27. package/dist/src/index.d.ts +25 -10
  28. package/dist/src/index.d.ts.map +1 -1
  29. package/dist/src/index.js +13 -30
  30. package/dist/src/index.js.map +1 -1
  31. package/dist/src/pins.js.map +1 -1
  32. package/dist/src/storage.d.ts +7 -3
  33. package/dist/src/storage.d.ts.map +1 -1
  34. package/dist/src/storage.js +14 -0
  35. package/dist/src/storage.js.map +1 -1
  36. package/dist/src/utils/dag-walkers.js.map +1 -1
  37. package/dist/src/utils/datastore-version.js.map +1 -1
  38. package/dist/src/utils/default-hashers.d.ts +3 -0
  39. package/dist/src/utils/default-hashers.d.ts.map +1 -0
  40. package/dist/src/utils/default-hashers.js +11 -0
  41. package/dist/src/utils/default-hashers.js.map +1 -0
  42. package/dist/src/utils/libp2p-defaults.browser.d.ts +11 -7
  43. package/dist/src/utils/libp2p-defaults.browser.d.ts.map +1 -1
  44. package/dist/src/utils/libp2p-defaults.browser.js +20 -15
  45. package/dist/src/utils/libp2p-defaults.browser.js.map +1 -1
  46. package/dist/src/utils/libp2p-defaults.d.ts +13 -9
  47. package/dist/src/utils/libp2p-defaults.d.ts.map +1 -1
  48. package/dist/src/utils/libp2p-defaults.js +23 -17
  49. package/dist/src/utils/libp2p-defaults.js.map +1 -1
  50. package/dist/src/utils/libp2p.d.ts +13 -5
  51. package/dist/src/utils/libp2p.d.ts.map +1 -1
  52. package/dist/src/utils/libp2p.js +21 -4
  53. package/dist/src/utils/libp2p.js.map +1 -1
  54. package/dist/src/utils/networked-storage.d.ts +22 -10
  55. package/dist/src/utils/networked-storage.d.ts.map +1 -1
  56. package/dist/src/utils/networked-storage.js +115 -16
  57. package/dist/src/utils/networked-storage.js.map +1 -1
  58. package/dist/src/version.d.ts +1 -1
  59. package/dist/src/version.d.ts.map +1 -1
  60. package/dist/src/version.js +1 -1
  61. package/dist/src/version.js.map +1 -1
  62. package/package.json +60 -23
  63. package/src/block-brokers/bitswap.ts +77 -0
  64. package/src/block-brokers/index.ts +2 -0
  65. package/src/block-brokers/trustless-gateway/broker.ts +65 -0
  66. package/src/block-brokers/trustless-gateway/index.ts +33 -0
  67. package/src/block-brokers/trustless-gateway/trustless-gateway.ts +126 -0
  68. package/src/helia.ts +31 -39
  69. package/src/index.ts +35 -36
  70. package/src/storage.ts +20 -3
  71. package/src/utils/default-hashers.ts +12 -0
  72. package/src/utils/libp2p-defaults.browser.ts +27 -19
  73. package/src/utils/libp2p-defaults.ts +31 -22
  74. package/src/utils/libp2p.ts +40 -8
  75. package/src/utils/networked-storage.ts +145 -25
  76. package/src/version.ts +1 -1
  77. package/dist/typedoc-urls.json +0 -8
@@ -1,21 +1,53 @@
1
- import { createLibp2p as create, type Libp2pOptions } from 'libp2p'
2
- import { type DefaultLibp2pServices, libp2pDefaults } from './libp2p-defaults.js'
3
- import type { Libp2p } from '@libp2p/interface'
1
+ import { keychain } from '@libp2p/keychain'
2
+ import { defaultLogger } from '@libp2p/logger'
3
+ import { Key } from 'interface-datastore'
4
+ import { createLibp2p as create } from 'libp2p'
5
+ import { libp2pDefaults } from './libp2p-defaults.js'
6
+ import type { DefaultLibp2pServices } from './libp2p-defaults.js'
7
+ import type { ComponentLogger, Libp2p, PeerId } from '@libp2p/interface'
8
+ import type { KeychainInit } from '@libp2p/keychain'
4
9
  import type { Datastore } from 'interface-datastore'
10
+ import type { Libp2pOptions } from 'libp2p'
5
11
 
6
- export interface CreateLibp2pOptions {
12
+ export interface CreateLibp2pOptions<T extends Record<string, unknown>> {
7
13
  datastore: Datastore
14
+ libp2p?: Libp2pOptions<T>
15
+ logger?: ComponentLogger
16
+ keychain?: KeychainInit
8
17
  start?: boolean
9
18
  }
10
19
 
11
- export async function createLibp2p (datastore: Datastore, options?: Libp2pOptions<any>): Promise<Libp2p<DefaultLibp2pServices>> {
12
- const defaults = libp2pDefaults()
20
+ export interface Libp2pDefaultsOptions {
21
+ peerId?: PeerId
22
+ keychain?: KeychainInit
23
+ }
24
+
25
+ export async function createLibp2p <T extends Record<string, unknown> = DefaultLibp2pServices> (options: CreateLibp2pOptions<T>): Promise<Libp2p<T>> {
26
+ let peerId = options.libp2p?.peerId
27
+ const logger = options.logger ?? defaultLogger()
28
+
29
+ // if no peer id was passed, try to load it from the keychain
30
+ if (peerId == null) {
31
+ const chain = keychain(options.keychain)({
32
+ datastore: options.datastore,
33
+ logger
34
+ })
35
+
36
+ const selfKey = new Key('/pkcs8/self')
37
+
38
+ if (await options.datastore.has(selfKey)) {
39
+ // load the peer id from the keychain
40
+ peerId = await chain.exportPeerId('self')
41
+ }
42
+ }
43
+
44
+ const defaults = libp2pDefaults(options)
13
45
  options = options ?? {}
14
46
 
47
+ // @ts-expect-error derived ServiceMap is not compatible with ServiceFactoryMap
15
48
  return create({
16
- datastore,
17
49
  ...defaults,
18
- ...options,
50
+ ...options.libp2p,
19
51
  start: false
20
52
  })
21
53
  }
@@ -1,36 +1,74 @@
1
+ import { CodeError, start, stop } from '@libp2p/interface'
2
+ import { anySignal } from 'any-signal'
1
3
  import filter from 'it-filter'
2
4
  import forEach from 'it-foreach'
3
5
  import { CustomProgressEvent, type ProgressOptions } from 'progress-events'
4
- import type { Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions } from '@helia/interface/blocks'
5
- import type { AbortOptions } from '@libp2p/interface'
6
+ import { equals as uint8ArrayEquals } from 'uint8arrays/equals'
7
+ import type { BlockBroker, Blocks, Pair, DeleteManyBlocksProgressEvents, DeleteBlockProgressEvents, GetBlockProgressEvents, GetManyBlocksProgressEvents, PutManyBlocksProgressEvents, PutBlockProgressEvents, GetAllBlocksProgressEvents, GetOfflineOptions, BlockRetriever, BlockAnnouncer, BlockRetrievalOptions } from '@helia/interface/blocks'
8
+ import type { AbortOptions, ComponentLogger, Logger, LoggerOptions, Startable } from '@libp2p/interface'
6
9
  import type { Blockstore } from 'interface-blockstore'
7
10
  import type { AwaitIterable } from 'interface-store'
8
- import type { Bitswap } from 'ipfs-bitswap'
9
11
  import type { CID } from 'multiformats/cid'
12
+ import type { MultihashHasher } from 'multiformats/hashes/interface'
10
13
 
11
- export interface BlockStorageInit {
12
- holdGcLock?: boolean
13
- bitswap?: Bitswap
14
+ export interface NetworkedStorageStorageInit {
15
+ blockBrokers?: BlockBroker[]
16
+ hashers?: MultihashHasher[]
14
17
  }
15
18
 
16
19
  export interface GetOptions extends AbortOptions {
17
- progress?: (evt: Event) => void
20
+ progress?(evt: Event): void
21
+ }
22
+
23
+ function isBlockRetriever (b: any): b is BlockRetriever {
24
+ return typeof b.retrieve === 'function'
25
+ }
26
+
27
+ function isBlockAnnouncer (b: any): b is BlockAnnouncer {
28
+ return typeof b.announce === 'function'
29
+ }
30
+
31
+ export interface NetworkedStorageComponents {
32
+ blockstore: Blockstore
33
+ logger: ComponentLogger
18
34
  }
19
35
 
20
36
  /**
21
37
  * Networked storage wraps a regular blockstore - when getting blocks if the
22
38
  * blocks are not present Bitswap will be used to fetch them from network peers.
23
39
  */
24
- export class NetworkedStorage implements Blocks {
40
+ export class NetworkedStorage implements Blocks, Startable {
25
41
  private readonly child: Blockstore
26
- private readonly bitswap?: Bitswap
42
+ private readonly blockRetrievers: BlockRetriever[]
43
+ private readonly blockAnnouncers: BlockAnnouncer[]
44
+ private readonly hashers: MultihashHasher[]
45
+ private started: boolean
46
+ private readonly log: Logger
27
47
 
28
48
  /**
29
49
  * Create a new BlockStorage
30
50
  */
31
- constructor (blockstore: Blockstore, options: BlockStorageInit = {}) {
32
- this.child = blockstore
33
- this.bitswap = options.bitswap
51
+ constructor (components: NetworkedStorageComponents, init: NetworkedStorageStorageInit) {
52
+ this.log = components.logger.forComponent('helia:networked-storage')
53
+ this.child = components.blockstore
54
+ this.blockRetrievers = (init.blockBrokers ?? []).filter(isBlockRetriever)
55
+ this.blockAnnouncers = (init.blockBrokers ?? []).filter(isBlockAnnouncer)
56
+ this.hashers = init.hashers ?? []
57
+ this.started = false
58
+ }
59
+
60
+ isStarted (): boolean {
61
+ return this.started
62
+ }
63
+
64
+ async start (): Promise<void> {
65
+ await start(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
66
+ this.started = true
67
+ }
68
+
69
+ async stop (): Promise<void> {
70
+ await stop(this.child, ...new Set([...this.blockRetrievers, ...this.blockAnnouncers]))
71
+ this.started = false
34
72
  }
35
73
 
36
74
  unwrap (): Blockstore {
@@ -46,10 +84,11 @@ export class NetworkedStorage implements Blocks {
46
84
  return cid
47
85
  }
48
86
 
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
- }
87
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:providers:notify', cid))
88
+
89
+ this.blockAnnouncers.forEach(provider => {
90
+ provider.announce(cid, block, options)
91
+ })
53
92
 
54
93
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:blockstore:put', cid))
55
94
 
@@ -71,8 +110,10 @@ export class NetworkedStorage implements Blocks {
71
110
  })
72
111
 
73
112
  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)
113
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:providers:notify', cid))
114
+ this.blockAnnouncers.forEach(provider => {
115
+ provider.announce(cid, block, options)
116
+ })
76
117
  })
77
118
 
78
119
  options.onProgress?.(new CustomProgressEvent('blocks:put-many:blockstore:put-many'))
@@ -83,13 +124,22 @@ export class NetworkedStorage implements Blocks {
83
124
  * Get a block by cid
84
125
  */
85
126
  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
-
127
+ if (options.offline !== true && !(await this.child.has(cid))) {
128
+ // we do not have the block locally, get it from a block provider
129
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:get', cid))
130
+ const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers, {
131
+ ...options,
132
+ log: this.log
133
+ })
90
134
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:blockstore:put', cid))
91
135
  await this.child.put(cid, block, options)
92
136
 
137
+ // notify other block providers of the new block
138
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:providers:notify', cid))
139
+ this.blockAnnouncers.forEach(provider => {
140
+ provider.announce(cid, block, options)
141
+ })
142
+
93
143
  return block
94
144
  }
95
145
 
@@ -105,11 +155,21 @@ export class NetworkedStorage implements Blocks {
105
155
  options.onProgress?.(new CustomProgressEvent('blocks:get-many:blockstore:get-many'))
106
156
 
107
157
  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)
158
+ if (options.offline !== true && !(await this.child.has(cid))) {
159
+ // we do not have the block locally, get it from a block provider
160
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:get', cid))
161
+ const block = await raceBlockRetrievers(cid, this.blockRetrievers, this.hashers, {
162
+ ...options,
163
+ log: this.log
164
+ })
111
165
  options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:blockstore:put', cid))
112
166
  await this.child.put(cid, block, options)
167
+
168
+ // notify other block providers of the new block
169
+ options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:providers:notify', cid))
170
+ this.blockAnnouncers.forEach(provider => {
171
+ provider.announce(cid, block, options)
172
+ })
113
173
  }
114
174
  }))
115
175
  }
@@ -144,3 +204,63 @@ export class NetworkedStorage implements Blocks {
144
204
  yield * this.child.getAll(options)
145
205
  }
146
206
  }
207
+
208
+ export const getCidBlockVerifierFunction = (cid: CID, hashers: MultihashHasher[]): Required<BlockRetrievalOptions>['validateFn'] => {
209
+ const hasher = hashers.find(hasher => hasher.code === cid.multihash.code)
210
+
211
+ if (hasher == null) {
212
+ 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')
213
+ }
214
+
215
+ return async (block: Uint8Array): Promise<void> => {
216
+ // verify block
217
+ const hash = await hasher.digest(block)
218
+
219
+ if (!uint8ArrayEquals(hash.digest, cid.multihash.digest)) {
220
+ // if a hash mismatch occurs for a TrustlessGatewayBlockBroker, we should try another gateway
221
+ throw new CodeError('Hash of downloaded block did not match multihash from passed CID', 'ERR_HASH_MISMATCH')
222
+ }
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Race block providers cancelling any pending requests once the block has been
228
+ * found.
229
+ */
230
+ async function raceBlockRetrievers (cid: CID, providers: BlockRetriever[], hashers: MultihashHasher[], options: AbortOptions & LoggerOptions): Promise<Uint8Array> {
231
+ const validateFn = getCidBlockVerifierFunction(cid, hashers)
232
+
233
+ const controller = new AbortController()
234
+ const signal = anySignal([controller.signal, options.signal])
235
+
236
+ try {
237
+ 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> => {
245
+ await validateFn(block)
246
+ blocksWereValidated = true
247
+ }
248
+ })
249
+
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)
254
+ }
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
+ })
262
+ )
263
+ } finally {
264
+ signal.clear()
265
+ }
266
+ }
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
- export const version = '2.0.3'
1
+ export const version = '2.1.0-299bb09'
2
2
  export const name = 'helia'
@@ -1,8 +0,0 @@
1
- {
2
- "DAGWalker": "https://ipfs.github.io/helia/interfaces/helia.DAGWalker.html",
3
- ".:DAGWalker": "https://ipfs.github.io/helia/interfaces/helia.DAGWalker.html",
4
- "HeliaInit": "https://ipfs.github.io/helia/interfaces/helia.HeliaInit.html",
5
- ".:HeliaInit": "https://ipfs.github.io/helia/interfaces/helia.HeliaInit.html",
6
- "createHelia": "https://ipfs.github.io/helia/functions/helia.createHelia.html",
7
- ".:createHelia": "https://ipfs.github.io/helia/functions/helia.createHelia.html"
8
- }