@helia/block-brokers 1.0.0 → 2.0.0-e554493

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.
@@ -1,266 +0,0 @@
1
- import { CodeError, start, stop } from '@libp2p/interface'
2
- import { anySignal } from 'any-signal'
3
- import filter from 'it-filter'
4
- import forEach from 'it-foreach'
5
- import { CustomProgressEvent, type ProgressOptions } from 'progress-events'
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'
9
- import type { Blockstore } from 'interface-blockstore'
10
- import type { AwaitIterable } from 'interface-store'
11
- import type { CID } from 'multiformats/cid'
12
- import type { MultihashHasher } from 'multiformats/hashes/interface'
13
-
14
- export interface NetworkedStorageStorageInit {
15
- blockBrokers?: BlockBroker[]
16
- hashers?: MultihashHasher[]
17
- }
18
-
19
- export interface GetOptions extends AbortOptions {
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
34
- }
35
-
36
- /**
37
- * Networked storage wraps a regular blockstore - when getting blocks if the
38
- * blocks are not present Bitswap will be used to fetch them from network peers.
39
- */
40
- export class NetworkedStorage implements Blocks, Startable {
41
- private readonly child: Blockstore
42
- private readonly blockRetrievers: BlockRetriever[]
43
- private readonly blockAnnouncers: BlockAnnouncer[]
44
- private readonly hashers: MultihashHasher[]
45
- private started: boolean
46
- private readonly log: Logger
47
-
48
- /**
49
- * Create a new BlockStorage
50
- */
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
72
- }
73
-
74
- unwrap (): Blockstore {
75
- return this.child
76
- }
77
-
78
- /**
79
- * Put a block to the underlying datastore
80
- */
81
- async put (cid: CID, block: Uint8Array, options: AbortOptions & ProgressOptions<PutBlockProgressEvents> = {}): Promise<CID> {
82
- if (await this.child.has(cid)) {
83
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:duplicate', cid))
84
- return cid
85
- }
86
-
87
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:providers:notify', cid))
88
-
89
- this.blockAnnouncers.forEach(provider => {
90
- provider.announce(cid, block, options)
91
- })
92
-
93
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:put:blockstore:put', cid))
94
-
95
- return this.child.put(cid, block, options)
96
- }
97
-
98
- /**
99
- * Put a multiple blocks to the underlying datastore
100
- */
101
- async * putMany (blocks: AwaitIterable<{ cid: CID, block: Uint8Array }>, options: AbortOptions & ProgressOptions<PutManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
102
- const missingBlocks = filter(blocks, async ({ cid }): Promise<boolean> => {
103
- const has = await this.child.has(cid)
104
-
105
- if (has) {
106
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:duplicate', cid))
107
- }
108
-
109
- return !has
110
- })
111
-
112
- const notifyEach = forEach(missingBlocks, ({ cid, block }): void => {
113
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:put-many:providers:notify', cid))
114
- this.blockAnnouncers.forEach(provider => {
115
- provider.announce(cid, block, options)
116
- })
117
- })
118
-
119
- options.onProgress?.(new CustomProgressEvent('blocks:put-many:blockstore:put-many'))
120
- yield * this.child.putMany(notifyEach, options)
121
- }
122
-
123
- /**
124
- * Get a block by cid
125
- */
126
- async get (cid: CID, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetBlockProgressEvents> = {}): Promise<Uint8Array> {
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
- })
134
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:blockstore:put', cid))
135
- await this.child.put(cid, block, options)
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
-
143
- return block
144
- }
145
-
146
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:get:blockstore:get', cid))
147
-
148
- return this.child.get(cid, options)
149
- }
150
-
151
- /**
152
- * Get multiple blocks back from an (async) iterable of cids
153
- */
154
- async * getMany (cids: AwaitIterable<CID>, options: GetOfflineOptions & AbortOptions & ProgressOptions<GetManyBlocksProgressEvents> = {}): AsyncIterable<Pair> {
155
- options.onProgress?.(new CustomProgressEvent('blocks:get-many:blockstore:get-many'))
156
-
157
- yield * this.child.getMany(forEach(cids, async (cid): Promise<void> => {
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
- })
165
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:get-many:blockstore:put', cid))
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
- })
173
- }
174
- }))
175
- }
176
-
177
- /**
178
- * Delete a block from the blockstore
179
- */
180
- async delete (cid: CID, options: AbortOptions & ProgressOptions<DeleteBlockProgressEvents> = {}): Promise<void> {
181
- options.onProgress?.(new CustomProgressEvent<CID>('blocks:delete:blockstore:delete', cid))
182
-
183
- await this.child.delete(cid, options)
184
- }
185
-
186
- /**
187
- * Delete multiple blocks from the blockstore
188
- */
189
- async * deleteMany (cids: AwaitIterable<CID>, options: AbortOptions & ProgressOptions<DeleteManyBlocksProgressEvents> = {}): AsyncIterable<CID> {
190
- options.onProgress?.(new CustomProgressEvent('blocks:delete-many:blockstore:delete-many'))
191
- yield * this.child.deleteMany((async function * (): AsyncGenerator<CID> {
192
- for await (const cid of cids) {
193
- yield cid
194
- }
195
- }()), options)
196
- }
197
-
198
- async has (cid: CID, options: AbortOptions = {}): Promise<boolean> {
199
- return this.child.has(cid, options)
200
- }
201
-
202
- async * getAll (options: AbortOptions & ProgressOptions<GetAllBlocksProgressEvents> = {}): AwaitIterable<Pair> {
203
- options.onProgress?.(new CustomProgressEvent('blocks:get-all:blockstore:get-many'))
204
- yield * this.child.getAll(options)
205
- }
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
- }