Conversation
CI Summary
Release - PassedTest this PR Download artifact (GitHub CLI required): gh run download 23238233460 -n cli-preview-0.0.0-pr.36 -R paritytech/dotns-sdkInstall globally:
Verify: dotns --helpDeploy Example — Passed
Labelspkg: cli, scope: bulletin, type: docs, type: test |
Adds `--use-car` to `dotns bulletin upload` so the CLI can merkleize a directory with the IPFS CLI, export a CAR file, and upload it to Bulletin — replacing the three separate workflow steps (Setup IPFS Kubo, Initialize IPFS, Merkleize into CAR file).
49d6456 to
776a309
Compare
There was a problem hiding this comment.
Left some comments regarding only the code changes. Most are small nits. A small refactor would be nice where mentioned just to help with testability but that isn't required.
The main missing piece is that the --use-car path seems to be missing in the test suite.
On the more subjective side, --use-car may not be the most intuitive, athough I have mixed feelings about the suggestions off the top of my head
--archive(-as-car?)--bundle(-as-car?)--bundled--car(may be the best if we can assume familiarity with IPFS, but may not be the best assumption to make)
|
@BigTava Outside of the code specific changes, the CAR stuff makes complete sense in transit, but do you realize that we are referencing the CAR bytes directly with the CID instead of the unpacked root CID? I would normally assume this is a mistake, but I remember some old CAR handling code in the past. My assumption is that we just want to do something like this with out script ipfs dag import archive.carI don't have a ton of ipfs via js experience but claude suggests import { CarReader } from '@ipld/car';
// ... after exportCarFile(ipfsCid, tempCarPath) ...
const carBytes = await filesystem.readFile(tempCarPath);
const reader = await CarReader.fromBytes(new Uint8Array(carBytes));
const roots = await reader.getRoots();
const rootCid = roots[0].toString(); // should match ipfsCid
const blocks = [];
for await (const block of reader.blocks()) {
blocks.push(block);
}
// Upload each block individually (reuse existing wave logic from storeDirectory)
for (const block of blocks) {
await storeBlockToBulletin({
rpc: bulletinRpc,
signer,
contentBytes: new Uint8Array(block.bytes),
contentCid: block.cid.toString(),
codecValue: block.cid.code,
hashCodeValue: block.cid.multihash.code,
client: sharedClient,
});
}
return { cid: rootCid, ipfsCid: rootCid, size: totalSize }; Something like this will give you the benefits of CAR when sending without any special handling of Content Addressable aRchives for reading. |
| storageCid = await uploadSingleBlock(bulletinRpc, context.signer, carBytes); | ||
| } | ||
|
|
||
| return { cid: storageCid, ipfsCid, size: carBytes.length }; |
There was a problem hiding this comment.
This will return the cid of the car blob which is non-standard for anyone accessing the data as CAR is meant for transport.
We likely want something like
const reader = await CarReader.fromBytes(new Uint8Array(carBytes));
const roots = await reader.getRoots();
const rootCid = roots[0].toString(); // should match ipfsCid
assert.isEqual(ipfsCid, rootCid);
return { cid: rootCid, ipfsCid: rootCid, size: totalSize };edit: better context in this comment #36 (comment)
There was a problem hiding this comment.
The CAR bytes are stored as chunked content with a DAG-PB root via uploadChunkedBlocks(). The cid field returns the DAG-PB root (the Bulletin storage handle the gateway resolves), and ipfsCid carries the IPFS directory root for reference.
There was a problem hiding this comment.
The CAR bytes are stored as chunked content with a DAG-PB root
This is what I see as the problem. The CAR is meant for transport. I'll throw an example together. I may just be misreading the code and the only issue is the CID that is output
ipfsCid carries the IPFS directory root for reference.
This part is a little misleading in the current code in master tbh. ipfsCid is only used in getPreviewUrl() by the looks of it. Which is probably the root of the issue
Aside: We should probably consolidate ipfsCid and cid because currently (outside this PR) every cid == ipfsCid. That is outside the scope here and I can follow up with it after. (edit: added issue here https://github.com/paritytech/dotns/issues/97)
There was a problem hiding this comment.
I don't know what you were doing before. As I far as I went, querying gateway/<cid> works. gateway/<storageCid> doesn't work.
There was a problem hiding this comment.
My example is failing due to possible bulletin chain issues
dotns-sdk/packages/cli/src/bulletin$ bun run dev -- bulletin upload ~/dev/andrew/moonwalker/ --car --parallel
$ bun run src/cli/index.ts bulletin upload /home/andrew/dev/andrew/moonwalker/ --car --parallel
════════════════════════════════════════
dotns developer CLI
════════════════════════════════════════
✔ Directory validated
• Connecting RPC wss://paseo-bulletin-rpc.polkadot.io
✓ Connecting RPC wss://paseo-bulletin-rpc.polkadot.io
• Resolving account
✓ Resolving account
• Loading keypair
✓ Loading keypair
📋 Configuration
RPC: wss://paseo-bulletin-rpc.polkadot.io
Chain: Bulletin
Substrate: 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
Auth: default
Account: default
Balance: 0.000000000000
▶ Bulletin Upload
path: /home/andrew/dev/andrew/moonwalker
rpc: wss://paseo-bulletin-rpc.polkadot.io
mode: car (ipfs cli)
✔ Merkleized: bafybeictaikgnxa6aiumef2rfheygvdz2dgmh7sb6f7dumkizodzqdgbua
✔ CAR exported: 3.23 MB
chunks: 2
✖ Upload failed
✗ Error: Descriptor for tx TransactionStorage.store_with_cid_config does not exist
error: script "dev" exited with code 1which makes this hard to test out at the moment. I don't believe this is related to this PR
edit: 🤦 I forgot
bun run prepare
which explains the issues.
There was a problem hiding this comment.
Ok here is an example (sorry for delays, had some calls I had to deal with)
bun run dev -- bulletin upload ~/dev/andrew/moonwalker/ --car --parallel
$ bun run src/cli/index.ts bulletin upload /home/andrew/dev/andrew/moonwalker/ --car --parallel
════════════════════════════════════════
dotns developer CLI
════════════════════════════════════════
✔ Directory validated
• Connecting RPC wss://paseo-bulletin-rpc.polkadot.io
✓ Connecting RPC wss://paseo-bulletin-rpc.polkadot.io
• Resolving account
✓ Resolving account
• Loading keypair
✓ Loading keypair
📋 Configuration
RPC: wss://paseo-bulletin-rpc.polkadot.io
Chain: Bulletin
Substrate: 5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV
Auth: default
Account: default
Balance: 0.000000000000
▶ Bulletin Upload
path: /home/andrew/dev/andrew/moonwalker
rpc: wss://paseo-bulletin-rpc.polkadot.io
mode: car (ipfs cli)
✔ Merkleized: bafybeictaikgnxa6aiumef2rfheygvdz2dgmh7sb6f7dumkizodzqdgbua
✔ CAR exported: 3.23 MB
chunks: 2
✔ Stored
cid: bafybeibwo72gsgp4qzy7p2gypyxu2oofhnnikcmg74wpys7tchie2ylhqm
ipfs-cid: bafybeictaikgnxa6aiumef2rfheygvdz2dgmh7sb6f7dumkizodzqdgbua
preview: http://dotns.paseo.li/#/preview/YmFmeWJlaWN0YWlrZ254YTZhaXVtZWYycmZoZXlndmR6MmRnbWg3c2I2ZjdkdW1raXpvZHpxZGdidWE
✓ Upload Complete
Resulting in 2 output CIDs
cid: bafybeibwo72gsgp4qzy7p2gypyxu2oofhnnikcmg74wpys7tchie2ylhqm
ipfs-cid: bafybeictaikgnxa6aiumef2rfheygvdz2dgmh7sb6f7dumkizodzqdgbua
So it works, but cid is effectively worthless. I would simply replace
return { cid: storageCid, ipfsCid, size: carBytes.length };with
return { cid: ipfsCid, ipfsCid, size: carBytes.length };and we should be good.
outside the scope of the PR, a nice touch would also be only outputting the cid when piping, allowing easy usage in bash
There was a problem hiding this comment.
I think that the ipfsCid only works when you first fetch the storageCid. Only then the ipfsCid is cached in the gateway.
There was a problem hiding this comment.
I pushed a PR to merge into this one: #43
Hopefully that clears things up a little
There was a problem hiding this comment.
Did you change anything in the build so the CID is different? Also, did you try to fetch directly from the bulletin nodes via Helia? Did you try to fetch from the only ipfs gateway that matters - https://paseo-ipfs.polkadot.io/ipfs? Also this is not working for me https://dotns.paseo.li/#/preview/YmFmeWJlaWdreGs1eG92cmlzdzVya2NvZ3J3ZzJ2M3FzcHlscnlsem9uYnl0Y20zeHhkdnMzeGJud2k.
There was a problem hiding this comment.
I think that the
ipfsCidonly works when you first fetch thestorageCid. Only then the ipfsCid is cached in the gateway.
With the linked changes in #43, we are storing each block individually with its original CID, that two-step process shouldn't be necessary anymore. The ipfsCid blocks are stored directly on Bulletin.
| transactions?: string; | ||
| /** Number of bytes to authorize */ | ||
| bytes?: string; | ||
| /** Merkleize with IPFS CLI and upload as a CAR file instead of individual blocks */ |
There was a problem hiding this comment.
Great start, We missing helper tests and integration tests here
- Extract CAR upload logic into storeCar() in commands/bulletin.ts, mirroring deploy.js storeChunkedContent: exports a CAR, stores the bytes as chunked content with a DAG-PB root via uploadChunkedBlocks - Use randomUUID() for the temp CAR filename instead of Date.now() - Add safety comment on execSync calls in ensureIpfsInitialized() - Simplify cli/commands/bulletin.ts: useCar path is now a single call to storeCar(); removes previously inlined ora/fs/os/path/ipfs imports
…o --car - ensureIpfsInitialized() now returns the resolved binary path so callers can pass it into merkleizeWithIpfs() and exportCarFile(), avoiding repeated findIpfsBinaryPath() lookups and improving testability - merkleizeWithIpfs, merkleizeSingleFileWithIpfs, and exportCarFile each accept an optional ipfsBinaryPath parameter (falls back to findIpfsBinaryPath() for standalone callers) - storeCar() resolves the path once via ensureIpfsInitialized() and passes it through both subsequent IPFS calls - Rename --use-car flag to --car (matches --parallel, --force-chunked naming convention and is more concise for IPFS-familiar users)
- Unit: assert --car appears in bulletin upload --help output - Integration: upload directory with --car, verify mode line and ipfs-cid field in terminal output - Integration: --car with --json, verify cid (DAG-PB root) and ipfsCid (IPFS directory root) are both present - Integration: --car on a file path surfaces a clear error message - Extend JsonUploadResult type with optional ipfsCid field
|
@sphamjoli @andrew-ifrita Thanks for your comments. |
|
@andrew-ifrita what is the status? |
|
I worked out my train of thought in #43. Here is my resulting opinion #43 (comment)
If a user wants, nothing is preventing something like ipfs export ./project | dotns bulletin uploadSo this feature shouldn't be implemented on the CLI side and I would label this as a "good enough" solution that we are trying to avoid. A proper solution would include adding CAR upload support to Bulletin. And even a useful intermediate solution would be allowing batched transaction on bulletin, but this last option may not be feasible for technical reason (I am not a bulletin expert by any means). |
Description
Adds a
--use-carflag todotns bulletin uploadfor directory uploads. When set, the CLI:ipfs add -r --cid-version=1 --raw-leaves)ipfs dag export)This replaces the three separate
Setup IPFS Kubo/Initialize IPFS/Merkleize into CAR filesteps that were previously only available in the deploy workflow (PR #27). Now callers can trigger the same behaviour directly from the CLI:The IPFS repo is initialised automatically (
ipfs init) if not already present. The flag is a no-op for file uploads (errors with a clear message).Why CAR upload?
The default mode merkleizes in-memory and stores each IPFS block individually. CAR-based upload writes fewer, larger transactions, which can be faster for large sites and is required for compatibility with gateways that expect a packed DAG (e.g. the Polkadot Triangle gateway).
Type
Package
@dotns/cliRelated Issues
Closes #28 (no progress feedback during upload — CAR upload makes this more noticeable)
Fixes
N/A
Checklist
Code
bun run lintpassesbun run formatpassesbun run typecheckpassesDocumentation
Breaking Changes
Testing
How to test:
--use-car— verify IPFS CLI is invoked, a temp CAR file is created, and upload succeeds--use-car— verify it fails with a clear error messagedotns bulletin upload ./examples/deploy/site/index.html --use-car # Expected: Error: --use-car is only supported for directory uploadsipfs add -Q -r --cid-version=1 --raw-leaves ./examples/deploy/site # Should match the CID printed by dotns bulletin upload --use-carNotes
$PATH(e.g. viabrew install ipfsor the deploy workflow'sipfs/download-ipfs-distribution-action)os.tmpdir()and deleted after upload.github/workflows/deploy.yml)use-car: trueinput now delegates merkleization to this CLI flag instead of the separate shell steps — see PR feat: add use-car option to deploy workflow #27use-caris included atexamples/deploy/site/workflows/car-upload.ymlcloses https://github.com/paritytech/dotns/issues/95