Skip to content

feat: add migrate command - #652

Draft
SgtPooki wants to merge 8 commits into
masterfrom
feat/migrate
Draft

feat: add migrate command#652
SgtPooki wants to merge 8 commits into
masterfrom
feat/migrate

Conversation

@SgtPooki

@SgtPooki SgtPooki commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

Important

Superseded by a stacked split for review. This PR stays open as the umbrella: full pipeline description below, plus the pending calibnet validation. Review and merge the stack in order instead of this branch:

  1. feat: add migrate state store and CLI parsers #653 state store and CLI parsers
  2. feat: add migrate gateway fetch and CAR verify #654 gateway fetch and CAR verify (also fixes @ipld/dag-cbor shipped as a devDependency)
  3. feat: add migrate piece packing and GC windows #655 piece packing and GC windows
  4. feat: add migrate batched upload with resume #656 batched upload with resume
  5. feat: add migrate command and pipeline #657 command wiring, pipeline, docs

Each PR's base is the previous branch; the command activates in #657. Close this PR once #657 merges.

What changed

Adds filecoin-pin migrate <cid-list-file>: bulk-migrate IPFS CIDs onto FOC in batched pieces. Refs #651. Ported from FilOzone/foc-migrate@e52218a, then reworked into the pipeline documented in documentation/migrate.md after three rounds of multi-agent review.

One pipeline, clocked by a disk budget: each CID downloads once into a verified member CAR on disk (every block hash-checked, DAG walked for completeness from the requested root, CommP computed in the same pass), members bin-pack into ~1000MiB multi-root pieces assembled disk-to-disk in constant memory, and uploads run concurrently with GC-aware batched addPieces commits (cap 40). Committed pieces evict and free budget, so a disk smaller than the migration cycles instead of failing; --max-staged-bytes caps it explicitly.

Resume is receipt-first from a per-network, per-owner migrate.db: unknown-outcome commits resolve via the PiecesAdded event (or findPieceIdsByCid when no tx hash was captured), staged files re-verify against recorded hashes on startup, and a blind on-chain re-add is never issued. Egress defaults to none.

How to verify

pnpm install && pnpm run build && pnpm test
node dist/cli.js migrate --help

A calibnet validation run (kill mid-run, resume, confirm no duplicate adds) is still pending before undraft.

Notes / risks

  • node:sqlite (DatabaseSync, WAL) is unflagged since Node 23.4; the lts/* CI lane covers it. No engines change.
  • Concurrent runs against one migrate.db are unsupported.
  • New dependency: @ipld/dag-pb (already in the tree transitively).
  • Exit 0 only when every CID is committed at the requested copies; anything short (failed, over-cap, unresolved commits, missing copies) exits 1 and re-running is safe.

Port ipfs2foc's direct-upload flow into filecoin-pin as
`filecoin-pin migrate <cid-list-file>`. The command fetches each CID
as a canonical block-verified CAR from a trustless gateway, computes
its PieceCID, packs CIDs into multi-root CAR pieces under
--pack-target-size, and uploads them with GC-aware batched addPieces
commits. State lives in a per-network migrate.db (node:sqlite) under
the platform data directory, so interrupted runs resume without
re-adding pieces on chain.

Streaming mode (default) uploads packed pieces while later CIDs are
still downloading; --mode staged packs everything first. Egress
defaults to none for bulk archival. A migration manifest (source CID
to piece CID, data set ids, tx hashes) is imported through the
UnixFS path and committed as a final piece tagged migrationManifest.

Port source: FilOzone/foc-migrate@e52218a.

refs #651
@FilOzzy FilOzzy added team/filecoin-pin "Filecoin Pin" project is a stakeholder for this work. team/fs-wg FOC working group is a stakeholder for this work, and thus wants to track it on their project board. labels Aug 12, 2026
@FilOzzy FilOzzy added this to FOC Aug 12, 2026
@github-project-automation github-project-automation Bot moved this to 📌 Triage in FOC Aug 12, 2026
@SgtPooki SgtPooki self-assigned this Aug 14, 2026
Explain how migrate works for people running bulk migrations: the
four-stage pipeline (download and verify, pack, upload, commit and
evict), the disk-budget flow control, the GC-window commit batching,
resume semantics, data-set separation, and the exit-code contract.
Links key terms to the glossary.

refs #651
Replace the two-pass architecture (commP pass, then a pack stage that
re-downloaded every member) with one pipeline matching
documentation/migrate.md: each CID downloads once into a verified
member file on disk (every block hash-checked, DAG walked for
completeness, commP computed in the same pass), pieces assemble from
local files, and uploads, batched commits, and eviction run
concurrently. A staging byte budget (statfs-derived, capped by
--max-staged-bytes) gates downloads, so disk usage is hard-bounded
and a disk smaller than the migration cycles instead of failing.

Also closes the review findings: streaming multi-root assembly
(constant memory), store() result verified against the expected
PieceCID, missing secondary copies repaired on resume, reconcile
uses the row's own data set id, revert clears stale tx hashes,
eviction requires only the committed primary (secondary retries pull
provider-to-provider), add_unconfirmed counts as incomplete, state
scoped per network and owner address, and CIDv0/v1 aliases dedupe at
ingest. The canonical re-serialization stack, streaming/staged modes,
and the manifest are removed; the LIVE_TESTS gate is replaced by
hermetic truncation and corruption fixtures.

refs #651
Resume correctness: packed members clear their member-file columns in
the same transaction that records the piece, so a resumed run no
longer re-downloads already-packed CIDs; staged pieces re-hash on
startup and a corrupt one is deleted with its source CIDs re-queued;
an add_unconfirmed row that never captured its transaction hash now
resolves against the data set on chain (findPieceIdsByCid) instead of
being blindly re-parked or stuck forever.

Verification: the DAG completeness walk starts from the requested
root, not the first declared root, so a response with a complete
decoy root cannot smuggle an incomplete requested DAG through.

Pipeline liveness and accounting: a commP-mismatched piece drops out
of the upload queue via its failed row instead of re-storing in a
loop; over-cap CIDs are marked oversized, leave the free pool, and
return their staged bytes; a pack-stage failure fails the budget
waiters and drains the uploader instead of hanging both sides; the
uploader re-checks for work in the same synchronous block that parks
it, closing the lost-wakeup window; failed assemblies wake budget
waiters when refunding bytes; budget is returned only for files
actually removed from disk.

refs #651
The corrupt-CAR rebuild path in the startup sweep could delete an
add_unconfirmed breadcrumb before reconciliation resolved it against
the chain, re-queueing source CIDs whose commit may have landed (a
duplicate-add hazard). A rebuild now refuses while any upload row is
parked or add_unconfirmed, or the primary is committed; the piece
keeps its rows and budget bytes until a later run finds it
rebuild-safe.

refs #651
An add_unconfirmed row with no transaction hash that is absent from
its data set could still have a transaction in flight; re-queueing it
immediately risks adding the piece twice. Such a row now re-enters
the flow only once the breadcrumb is older than an hour (any real
transaction has landed or died by then); younger rows stay unresolved
and the run exits incomplete, resolving automatically on a later
re-run.

refs #651
The migrate modules carried their own byte formatter with the same
KiB/MiB/GiB semantics as utils/cli-helpers formatFileSize; the shared
helper formats all migrate byte counts now, and metrics.ts shrinks to
the duration formatter and Timer.

refs #651
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

team/filecoin-pin "Filecoin Pin" project is a stakeholder for this work. team/fs-wg FOC working group is a stakeholder for this work, and thus wants to track it on their project board.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants