Skip to content

Commit 5ff6998

Browse files
authored
fix!: replace dag walkers with generic CID extraction from blocks (#447)
Replace the codec-specific `.dagWalkers` property with a generic dag walker internally that uses the `Block` interface from the `multicodecs` module. - Removes the `.dagWalkers` property from the Helia interface - Adds `getCodec` and `getHasher` to retrieve codecs and hashers by code - Adds `loadCodec` and `loadHasher` options to allow sync or async loading of extra codecs/hashes in addition to staticlly configured ones in the `codecs`/`hashers` keys BREAKING CHANGE: the `.dagWalkers` property has been removed
1 parent 8805202 commit 5ff6998

23 files changed

+291
-393
lines changed

packages/block-brokers/src/bitswap.ts

+5-19
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createBitswap } from '@helia/bitswap'
22
import type { BitswapOptions, Bitswap, BitswapWantBlockProgressEvents, BitswapNotifyProgressEvents } from '@helia/bitswap'
3-
import type { BlockAnnounceOptions, BlockBroker, BlockRetrievalOptions, CreateSessionOptions, Routing } from '@helia/interface'
3+
import type { BlockAnnounceOptions, BlockBroker, BlockRetrievalOptions, CreateSessionOptions, Routing, HasherLoader } from '@helia/interface'
44
import type { Libp2p, Startable, ComponentLogger } from '@libp2p/interface'
55
import type { Blockstore } from 'interface-blockstore'
66
import type { CID } from 'multiformats/cid'
@@ -9,9 +9,9 @@ import type { MultihashHasher } from 'multiformats/hashes/interface'
99
interface BitswapComponents {
1010
libp2p: Libp2p
1111
blockstore: Blockstore
12-
hashers: Record<string, MultihashHasher>
1312
routing: Routing
1413
logger: ComponentLogger
14+
getHasher: HasherLoader
1515
}
1616

1717
export interface BitswapInit extends BitswapOptions {
@@ -23,26 +23,12 @@ class BitswapBlockBroker implements BlockBroker<BitswapWantBlockProgressEvents,
2323
private started: boolean
2424

2525
constructor (components: BitswapComponents, init: BitswapInit = {}) {
26-
const { hashers } = components
26+
const { getHasher } = components
2727

2828
this.bitswap = createBitswap(components, {
2929
hashLoader: {
30-
getHasher: async (codecOrName: string | number): Promise<MultihashHasher<number>> => {
31-
let hasher: MultihashHasher | undefined
32-
33-
if (typeof codecOrName === 'string') {
34-
hasher = Object.values(hashers).find(hasher => {
35-
return hasher.name === codecOrName
36-
})
37-
} else {
38-
hasher = hashers[codecOrName]
39-
}
40-
41-
if (hasher != null) {
42-
return hasher
43-
}
44-
45-
throw new Error(`Could not load hasher for code/name "${codecOrName}"`)
30+
getHasher: async (codecOrName: number): Promise<MultihashHasher<number>> => {
31+
return getHasher(codecOrName)
4632
}
4733
},
4834
...init

packages/car/src/index.ts

+9-11
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,10 @@
6161
import { CarWriter } from '@ipld/car'
6262
import drain from 'it-drain'
6363
import map from 'it-map'
64+
import { createUnsafe } from 'multiformats/block'
6465
import defer from 'p-defer'
6566
import PQueue from 'p-queue'
66-
import type { DAGWalker } from '@helia/interface'
67+
import type { CodecLoader } from '@helia/interface'
6768
import type { GetBlockProgressEvents, PutManyBlocksProgressEvents } from '@helia/interface/blocks'
6869
import type { CarReader } from '@ipld/car'
6970
import type { AbortOptions } from '@libp2p/interface'
@@ -74,7 +75,7 @@ import type { ProgressOptions } from 'progress-events'
7475

7576
export interface CarComponents {
7677
blockstore: Blockstore
77-
dagWalkers: Record<number, DAGWalker>
78+
getCodec: CodecLoader
7879
}
7980

8081
interface ExportCarOptions extends AbortOptions, ProgressOptions<GetBlockProgressEvents> {
@@ -235,18 +236,15 @@ class DefaultCar implements Car {
235236
* and update the pin count for them
236237
*/
237238
async #walkDag (cid: CID, queue: PQueue, withBlock: (cid: CID, block: Uint8Array) => Promise<void>, options?: AbortOptions & ProgressOptions<GetBlockProgressEvents>): Promise<void> {
238-
const dagWalker = this.components.dagWalkers[cid.code]
239+
const codec = await this.components.getCodec(cid.code)
240+
const bytes = await this.components.blockstore.get(cid, options)
239241

240-
if (dagWalker == null) {
241-
throw new Error(`No dag walker found for cid codec ${cid.code}`)
242-
}
243-
244-
const block = await this.components.blockstore.get(cid, options)
242+
await withBlock(cid, bytes)
245243

246-
await withBlock(cid, block)
244+
const block = createUnsafe({ bytes, cid, codec })
247245

248246
// walk dag, ensure all blocks are present
249-
for await (const cid of dagWalker.walk(block)) {
247+
for await (const [,cid] of block.links()) {
250248
void queue.add(async () => {
251249
await this.#walkDag(cid, queue, withBlock, options)
252250
})
@@ -257,6 +255,6 @@ class DefaultCar implements Car {
257255
/**
258256
* Create a {@link Car} instance for use with {@link https://github.com/ipfs/helia Helia}
259257
*/
260-
export function car (helia: { blockstore: Blockstore, dagWalkers: Record<number, DAGWalker> }, init: any = {}): Car {
258+
export function car (helia: CarComponents, init: any = {}): Car {
261259
return new DefaultCar(helia, init)
262260
}

packages/car/test/fixtures/dag-walkers.ts

-27
This file was deleted.
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/* eslint-env mocha */
2+
3+
import * as dagPb from '@ipld/dag-pb'
4+
import * as raw from 'multiformats/codecs/raw'
5+
import type { BlockCodec } from 'multiformats'
6+
7+
export function getCodec (code: number): BlockCodec<any, any> {
8+
if (code === dagPb.code) {
9+
return dagPb
10+
}
11+
12+
if (code === raw.code) {
13+
return raw
14+
}
15+
16+
throw new Error(`Unknown codec ${code}`)
17+
}

packages/car/test/index.spec.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import { MemoryDatastore } from 'datastore-core'
1010
import { fixedSize } from 'ipfs-unixfs-importer/chunker'
1111
import toBuffer from 'it-to-buffer'
1212
import { car, type Car } from '../src/index.js'
13-
import { dagWalkers } from './fixtures/dag-walkers.js'
1413
import { largeFile, smallFile } from './fixtures/files.js'
14+
import { getCodec } from './fixtures/get-codec.js'
1515
import { memoryCarWriter } from './fixtures/memory-car.js'
1616
import type { Blockstore } from 'interface-blockstore'
1717

@@ -23,14 +23,14 @@ describe('import/export car file', () => {
2323
beforeEach(async () => {
2424
blockstore = new MemoryBlockstore()
2525

26-
c = car({ blockstore, dagWalkers })
26+
c = car({ blockstore, getCodec })
2727
u = unixfs({ blockstore })
2828
})
2929

3030
it('exports and imports a car file', async () => {
3131
const otherBlockstore = new MemoryBlockstore()
3232
const otherUnixFS = unixfs({ blockstore: otherBlockstore })
33-
const otherCar = car({ blockstore: otherBlockstore, dagWalkers })
33+
const otherCar = car({ blockstore: otherBlockstore, getCodec })
3434
const cid = await otherUnixFS.addBytes(smallFile)
3535

3636
const writer = memoryCarWriter(cid)
@@ -50,7 +50,7 @@ describe('import/export car file', () => {
5050

5151
const otherBlockstore = new MemoryBlockstore()
5252
const otherUnixFS = unixfs({ blockstore: otherBlockstore })
53-
const otherCar = car({ blockstore: otherBlockstore, dagWalkers })
53+
const otherCar = car({ blockstore: otherBlockstore, getCodec })
5454
const cid1 = await otherUnixFS.addBytes(fileData1)
5555
const cid2 = await otherUnixFS.addBytes(fileData2)
5656
const cid3 = await otherUnixFS.addBytes(fileData3)
@@ -70,7 +70,7 @@ describe('import/export car file', () => {
7070
it('exports and imports a multiple block car file', async () => {
7171
const otherBlockstore = new MemoryBlockstore()
7272
const otherUnixFS = unixfs({ blockstore: otherBlockstore })
73-
const otherCar = car({ blockstore: otherBlockstore, dagWalkers })
73+
const otherCar = car({ blockstore: otherBlockstore, getCodec })
7474
const cid = await otherUnixFS.addBytes(largeFile)
7575

7676
const writer = memoryCarWriter(cid)
@@ -90,7 +90,7 @@ describe('import/export car file', () => {
9090

9191
const otherBlockstore = new MemoryBlockstore()
9292
const otherUnixFS = unixfs({ blockstore: otherBlockstore })
93-
const otherCar = car({ blockstore: otherBlockstore, dagWalkers })
93+
const otherCar = car({ blockstore: otherBlockstore, getCodec })
9494
const cid1 = await otherUnixFS.addBytes(fileData1, {
9595
chunker: fixedSize({
9696
chunkSize: 2
@@ -124,7 +124,7 @@ describe('import/export car file', () => {
124124
const otherUnixFS = unixfs({ blockstore: otherBlockstore })
125125
const otherDatastore = new MemoryDatastore()
126126
const otherMFS = mfs({ blockstore: otherBlockstore, datastore: otherDatastore })
127-
const otherCar = car({ blockstore: otherBlockstore, dagWalkers })
127+
const otherCar = car({ blockstore: otherBlockstore, getCodec })
128128

129129
await otherMFS.mkdir('/testDups')
130130
await otherMFS.mkdir('/testDups/sub')
@@ -151,7 +151,7 @@ describe('import/export car file', () => {
151151
const otherUnixFS = unixfs({ blockstore: otherBlockstore })
152152
const otherDatastore = new MemoryDatastore()
153153
const otherMFS = mfs({ blockstore: otherBlockstore, datastore: otherDatastore })
154-
const otherCar = car({ blockstore: otherBlockstore, dagWalkers })
154+
const otherCar = car({ blockstore: otherBlockstore, getCodec })
155155

156156
await otherMFS.mkdir('/testDups')
157157
await otherMFS.mkdir('/testDups/sub')

packages/car/test/stream.spec.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { expect } from 'aegir/chai'
55
import { MemoryBlockstore } from 'blockstore-core'
66
import toBuffer from 'it-to-buffer'
77
import { car, type Car } from '../src/index.js'
8-
import { dagWalkers } from './fixtures/dag-walkers.js'
98
import { smallFile } from './fixtures/files.js'
9+
import { getCodec } from './fixtures/get-codec.js'
1010
import { memoryCarWriter } from './fixtures/memory-car.js'
1111
import type { Blockstore } from 'interface-blockstore'
1212

@@ -18,7 +18,7 @@ describe('stream car file', () => {
1818
beforeEach(async () => {
1919
blockstore = new MemoryBlockstore()
2020

21-
c = car({ blockstore, dagWalkers })
21+
c = car({ blockstore, getCodec })
2222
u = unixfs({ blockstore })
2323
})
2424

packages/interface/src/errors.ts

+18
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,21 @@ export class NoRoutersAvailableError extends Error {
1515
this.name = 'NoRoutersAvailableError'
1616
}
1717
}
18+
19+
export class UnknownHashAlgorithmError extends Error {
20+
static name = 'UnknownHashAlgorithmError'
21+
22+
constructor (message = 'Unknown hash algorithm') {
23+
super(message)
24+
this.name = 'UnknownHashAlgorithmError'
25+
}
26+
}
27+
28+
export class UnknownCodecError extends Error {
29+
static name = 'UnknownCodecError'
30+
31+
constructor (message = 'Unknown codec') {
32+
super(message)
33+
this.name = 'UnknownCodecError'
34+
}
35+
}

packages/interface/src/index.ts

+23-21
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,21 @@ import type { Routing } from './routing.js'
2020
import type { AbortOptions, ComponentLogger, Metrics } from '@libp2p/interface'
2121
import type { DNS } from '@multiformats/dns'
2222
import type { Datastore } from 'interface-datastore'
23-
import type { MultihashHasher } from 'multiformats'
23+
import type { Await } from 'interface-store'
24+
import type { BlockCodec, MultihashHasher } from 'multiformats'
2425
import type { CID } from 'multiformats/cid'
2526
import type { ProgressEvent, ProgressOptions } from 'progress-events'
2627

2728
export type { Await, AwaitIterable } from 'interface-store'
2829

30+
export interface CodecLoader {
31+
<T = any, Code extends number = any>(code: Code): Await<BlockCodec<Code, T>>
32+
}
33+
34+
export interface HasherLoader {
35+
(code: number): Await<MultihashHasher>
36+
}
37+
2938
/**
3039
* The API presented by a Helia node
3140
*/
@@ -56,18 +65,6 @@ export interface Helia {
5665
*/
5766
routing: Routing
5867

59-
/**
60-
* DAGWalkers are codec-specific implementations that know how to yield all
61-
* CIDs contained within a block that corresponds to that codec.
62-
*/
63-
dagWalkers: Record<number, DAGWalker>
64-
65-
/**
66-
* Hashers can be used to hash a piece of data with the specified hashing
67-
* algorithm.
68-
*/
69-
hashers: Record<number, MultihashHasher>
70-
7168
/**
7269
* The DNS property can be used to perform lookups of various record types and
7370
* will use a resolver appropriate to the current platform.
@@ -94,6 +91,19 @@ export interface Helia {
9491
* Remove any unpinned blocks from the blockstore
9592
*/
9693
gc(options?: GCOptions): Promise<void>
94+
95+
/**
96+
* Load an IPLD codec. Implementations may return a promise if, for example,
97+
* the codec is being fetched from the network.
98+
*/
99+
getCodec: CodecLoader
100+
101+
/**
102+
* Hashers can be used to hash a piece of data with the specified hashing
103+
* algorithm. Implementations may return a promise if, for example,
104+
* the hasher is being fetched from the network.
105+
*/
106+
getHasher: HasherLoader
97107
}
98108

99109
export type GcEvents =
@@ -104,14 +114,6 @@ export interface GCOptions extends AbortOptions, ProgressOptions<GcEvents> {
104114

105115
}
106116

107-
/**
108-
* DAGWalkers take a block and yield CIDs encoded in that block
109-
*/
110-
export interface DAGWalker {
111-
codec: number
112-
walk(block: Uint8Array): Generator<CID, void, undefined>
113-
}
114-
115117
export * from './blocks.js'
116118
export * from './errors.js'
117119
export * from './pins.js'

0 commit comments

Comments
 (0)