-
Notifications
You must be signed in to change notification settings - Fork 126
test: re-enable hamt-test in interop #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e0f8d5e
3c40080
e670eac
e3920b1
14e7022
7a41f11
851be72
2eff2c2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -164,6 +164,7 @@ | |
"@libp2p/interface": "^1.1.2", | ||
"@libp2p/logger": "^4.0.5", | ||
"@multiformats/murmur3": "^2.1.8", | ||
"err-code": "^3.0.1", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
"hamt-sharding": "^3.0.2", | ||
"interface-blockstore": "^5.2.9", | ||
"ipfs-unixfs": "^11.1.3", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
import { decode, type PBLink, type PBNode } from '@ipld/dag-pb' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all of this was copied over from |
||
import { murmur3128 } from '@multiformats/murmur3' | ||
import errCode from 'err-code' | ||
import { Bucket, type BucketPosition, createHAMT } from 'hamt-sharding' | ||
import { UnixFS } from 'ipfs-unixfs' | ||
import type { ExporterOptions, ReadableStorage, ShardTraversalContext } from 'ipfs-unixfs-exporter' | ||
import type { CID } from 'multiformats/cid' | ||
|
||
// FIXME: this is copy/pasted from ipfs-unixfs-importer/src/options.js | ||
const hashFn = async function (buf: Uint8Array): Promise<Uint8Array> { | ||
return (await murmur3128.encode(buf)) | ||
// Murmur3 outputs 128 bit but, accidentally, IPFS Go's | ||
// implementation only uses the first 64, so we must do the same | ||
// for parity.. | ||
.slice(0, 8) | ||
// Invert buffer because that's how Go impl does it | ||
.reverse() | ||
} | ||
|
||
const addLinksToHamtBucket = async (links: PBLink[], bucket: Bucket<boolean>, rootBucket: Bucket<boolean>): Promise<void> => { | ||
const padLength = (bucket.tableSize() - 1).toString(16).length | ||
await Promise.all( | ||
links.map(async link => { | ||
if (link.Name == null) { | ||
// TODO(@rvagg): what do? this is technically possible | ||
throw new Error('Unexpected Link without a Name') | ||
} | ||
if (link.Name.length === padLength) { | ||
const pos = parseInt(link.Name, 16) | ||
|
||
bucket._putObjectAt(pos, new Bucket({ | ||
hash: rootBucket._options.hash, | ||
bits: rootBucket._options.bits | ||
}, bucket, pos)) | ||
return | ||
} | ||
|
||
await rootBucket.put(link.Name.substring(2), true) | ||
}) | ||
) | ||
} | ||
|
||
const toPrefix = (position: number, padLength: number): string => { | ||
return position | ||
.toString(16) | ||
.toUpperCase() | ||
.padStart(padLength, '0') | ||
.substring(0, padLength) | ||
} | ||
|
||
const toBucketPath = (position: BucketPosition<boolean>): Array<Bucket<boolean>> => { | ||
let bucket = position.bucket | ||
const path = [] | ||
|
||
while (bucket._parent != null) { | ||
path.push(bucket) | ||
|
||
bucket = bucket._parent | ||
} | ||
|
||
path.push(bucket) | ||
|
||
return path.reverse() | ||
} | ||
|
||
export async function findShardCid (node: PBNode, name: string, blockstore: ReadableStorage, context?: ShardTraversalContext, options?: ExporterOptions): Promise<CID | undefined> { | ||
if (context == null) { | ||
if (node.Data == null) { | ||
throw errCode(new Error('no data in PBNode'), 'ERR_NOT_UNIXFS') | ||
} | ||
|
||
let dir: UnixFS | ||
try { | ||
dir = UnixFS.unmarshal(node.Data) | ||
} catch (err: any) { | ||
throw errCode(err, 'ERR_NOT_UNIXFS') | ||
} | ||
|
||
if (dir.type !== 'hamt-sharded-directory') { | ||
throw errCode(new Error('not a HAMT'), 'ERR_NOT_UNIXFS') | ||
} | ||
if (dir.fanout == null) { | ||
throw errCode(new Error('missing fanout'), 'ERR_NOT_UNIXFS') | ||
} | ||
|
||
const rootBucket = createHAMT<boolean>({ | ||
hashFn, | ||
bits: Math.log2(Number(dir.fanout)) | ||
}) | ||
|
||
context = { | ||
rootBucket, | ||
hamtDepth: 1, | ||
lastBucket: rootBucket | ||
} | ||
} | ||
|
||
const padLength = (context.lastBucket.tableSize() - 1).toString(16).length | ||
|
||
await addLinksToHamtBucket(node.Links, context.lastBucket, context.rootBucket) | ||
|
||
const position = await context.rootBucket._findNewBucketAndPos(name) | ||
let prefix = toPrefix(position.pos, padLength) | ||
const bucketPath = toBucketPath(position) | ||
|
||
if (bucketPath.length > context.hamtDepth) { | ||
context.lastBucket = bucketPath[context.hamtDepth] | ||
|
||
prefix = toPrefix(context.lastBucket._posAtParent, padLength) | ||
} | ||
|
||
const link = node.Links.find(link => { | ||
if (link.Name == null) { | ||
return false | ||
} | ||
|
||
const entryPrefix = link.Name.substring(0, padLength) | ||
const entryName = link.Name.substring(padLength) | ||
|
||
if (entryPrefix !== prefix) { | ||
// not the entry or subshard we're looking for | ||
return false | ||
} | ||
|
||
if (entryName !== '' && entryName !== name) { | ||
// not the entry we're looking for | ||
return false | ||
} | ||
|
||
return true | ||
}) | ||
|
||
if (link == null) { | ||
return | ||
} | ||
|
||
if (link.Name != null && link.Name.substring(padLength) === name) { | ||
return link.Hash | ||
} | ||
|
||
context.hamtDepth++ | ||
|
||
const block = await blockstore.get(link.Hash, options) | ||
node = decode(block) | ||
|
||
return findShardCid(node, name, blockstore, context, options) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,8 @@ import { DoesNotExistError, InvalidParametersError } from '../../errors.js' | |
import { addLink } from './add-link.js' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of the changes in here are originally from @aschmahmann in #448, except that I copied the contents of |
||
import { cidToDirectory } from './cid-to-directory.js' | ||
import { cidToPBLink } from './cid-to-pblink.js' | ||
import { findShardCid } from './find-shard-cid.js' | ||
import type { PBNode } from '@ipld/dag-pb/interface' | ||
import type { AbortOptions } from '@libp2p/interface' | ||
import type { Blockstore } from 'interface-blockstore' | ||
import type { CID } from 'multiformats/cid' | ||
|
@@ -32,6 +34,12 @@ export interface ResolveResult { | |
segments?: Segment[] | ||
} | ||
|
||
const findLinkCid = (node: PBNode, name: string): CID | undefined => { | ||
const link = node.Links.find(link => link.Name === name) | ||
|
||
return link?.Hash | ||
} | ||
|
||
export async function resolve (cid: CID, path: string | undefined, blockstore: Blockstore, options: AbortOptions): Promise<ResolveResult> { | ||
if (path == null || path === '') { | ||
return { cid } | ||
|
@@ -61,11 +69,11 @@ export async function resolve (cid: CID, path: string | undefined, blockstore: B | |
} else if (result.type === 'directory') { | ||
let dirCid: CID | undefined | ||
|
||
for await (const entry of result.content()) { | ||
if (entry.name === part) { | ||
dirCid = entry.cid | ||
break | ||
} | ||
if (result.unixfs?.type === 'hamt-sharded-directory') { | ||
// special case - unixfs v1 hamt shards | ||
dirCid = await findShardCid(result.node, part, blockstore) | ||
} else { | ||
dirCid = findLinkCid(result.node, part) | ||
} | ||
|
||
if (dirCid == null) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note that I re-enabled this test on a branch https://github.com/ipfs/helia/tree/test/re-enable-hamt-on-main and it doesn't seem to fail there (it was only failing locally previously), but the events don't show the issues that are seen in ipfs/service-worker-gateway#19 & ipfs/service-worker-gateway#18.
They both have
onProgressEvents.length === 41
(which includes blockstore & verified fetch events), so some event is not being surfaced fully from the walk without the changes here.