Checkpoint warp sync: mining-ready node in minutes (--sync warp) - #660
Checkpoint warp sync: mining-ready node in minutes (--sync warp)#660n13 wants to merge 2 commits into
Conversation
A new node resolves a checkpoint (the network's finalized head, fetched from agreeing RPC endpoints with a release-anchor fallback), warp-syncs to it via WarpSyncConfig::WithTarget, downloads the state with Merkle proofs against the checkpoint's state_root, and fully verifies only checkpoint -> tip. History below the checkpoint is not downloaded; verified backfill is a follow-up. - PowBlockImport classifies imports: the proof-verified state target (finalized on import, work seeded, no block gap recorded) and skip-execution imports below the finalized head are exempt from runtime seal verification, which requires parent state; everything else keeps full verification. - Release anchor (checkpointHeader chain spec property) is enforced at import: any block contradicting it at its height is rejected. - finalize_canonical_at_depth early-returns when the target is at or below the finalized head. - --sync fast/fast-unsafe rejected (unverifiable for QPoW); warp nodes use windowed state pruning instead of forced archive. Validated live against Heisenberg: fresh node to mining-ready tip with zero log errors; depth finalization correct over the warp base.
Flags the run path used to accept but silently override or ignore now either work or fail loudly: - --node-key: honored (was stomped by the file-identity override; sc_cli already parses it as a Dilithium secret and applies the authority safety check). --node-key-file behavior unchanged. - --state-pruning: must match what the node enforces. Full-sync nodes are fixed to archive-canonical; warp nodes accept a numeric window >= 256 (new capability) and reject archive modes. - --blocks-pruning: fixed to archive-canonical; other values rejected. - --checkpoint-header/--checkpoint-url: rejected without --sync warp. Verified fine as-is: --network-backend and --node-key-type are already single-variant (litep2p / dilithium), --unsafe-force-node-key-generation runs live in sc_cli config creation, and all subcommands honor their own flags (pruning flags legitimately apply to import/check DB creation, so rejection is run-path only).
|
Added a CLI sweep commit (0ec0b29): audited every run-path flag for silently-ignored or silently-overridden behavior. Now honored: Now rejected loudly instead of silently overridden:
Audited and fine as-is: All five rejection paths smoke-tested against the release binary with clear error messages and exit 1. |
Adds checkpoint-based warp sync: a new node reaches the network tip and can start mining in minutes — bounded by state-download bandwidth, not chain age — instead of re-executing the whole chain (currently 1.5–2 h and growing).
How it works
--sync warp) the node resolves a checkpoint — a single trusted header, normally the network's current finalized head (best − max_reorg_depth).WarpSyncConfig::WithTargetmachinery (the parachain path — no GRANDPA needed) downloads the checkpoint block, then state-syncs the full state at that block, Merkle-proof-verified against the header'sstate_root.is_major_syncingalready covers warp/state phases).History below the checkpoint is not downloaded; the node serves chain data from the checkpoint forward (see "Backfill" below).
Checkpoint resolution (documented in
docs/FAST_SYNC.md)First match wins:
--checkpoint-header 0x…— operator-pinned SCALE header.--checkpoint-url …, or the chain speccheckpointUrlsproperty, now set for Heisenberg and Planck). Each endpoint's returned header must hash (Poseidon) to its reported finalized head; the candidate is the lowest finalized height among responders; every other responder must confirm that hash viachain_getBlockHash. Any disagreement aborts.checkpointHeaderchain spec property refreshed by CI at release cut (CI wiring is a follow-up; stale anchors still sync, catch-up just re-executes blocks mined since the release).A fetched target may never be older than the anchor, and at the anchor's height it must equal the anchor. Independently,
PowBlockImportrejects any imported block that contradicts the anchor at its height.Consensus changes (
client/consensus/qpow)Imports are classified by where their state can come from:
StateAction::ApplyChanges(StorageChanges::Import)) — the proof-verified state-sync target. No parent state exists by construction, so inherent/seal runtime checks are skipped; cumulative work is seeded (like genesis — post-target fork choice only compares its descendants); the block is finalized on import;create_gap = false(see below).StateAction::Skipat or below the finalized head) — defense-in-depth for skip-execution imports of already-finalized heights: no work accounting, never best.Skipabove the finalized head does not grant a verification skip, so unsolicited near-tip blocks still get their seals runtime-verified.finalize_canonical_at_depthgained an early return when the target is at or below the current finalized head (post-warp, best − 100 points below the finalized target for the first 100 blocks).Why backfill is disabled (
create_gap = false)Upstream gap backfill fills ascending from genesis and relies on the verifier to validate headers statelessly — possible for BABE (epoch data chains forward from genesis), impossible for QPoW (target difficulty lives in state, and retarget rules changed across runtime upgrades). Ascending fill would let a malicious peer write fabricated, unverified history into the canonical number→hash index. Until backfill verifies linkage (descending fill, or a pre-verified header chain walked down from the checkpoint), warp-synced nodes simply don't backfill. Nodes needing full history full-sync as today.
--sync fast/fast-unsafeare rejected with a pointer to--sync warpfor the same reason (and fast sync's pivot trigger is incompatible with depth-based finality anyway). Warp nodes get a rolling 256-block state window instead of the forcedArchiveCanonical(archive refuses warp by design).Validated live against Heisenberg
Fresh node,
--sync warp, default spec (checkpoint fetched from the a1/a2 endpoints, both agreeing):finalized = best − 100one second after reaching the tip); catch-up of the 153 blocks mined during state download executed in ~1 s with full seal verification.chain_getBlockHash(1) → null(no pre-checkpoint history), tip queries andsystem_syncStatetrack the live chain.Wall clock was 13 min, essentially all state download at the ~150 KiB/s the public endpoints currently serve — on unthrottled state serving this is minutes. Chain-age-independent either way.
Unit tests cover the import-policy classification, checkpoint hex parsing, and anchor validation.
Follow-ups
checkpointHeaderanchor into the bundled chain specs at release cut.create_gap.