With proof of work skipped, chaining headers from a checkpoint up to a transaction's block proves nothing: a server can fabricate a header and fake headers after a real checkpoint. The header must now hash-chain up to the nearest checkpoint at or above its height, whose hash is hardcoded. This also anchors heights below the first checkpoint. The cached anchor state is renamed AnchoredDownTo and caches without it are re-verified once. Blocks above the newest checkpoint remain a documented limit. See ADR 0040.
1.5 KiB
1.5 KiB
0006. Anchor headers to hardcoded checkpoints and skip PoW validation
- Status: Accepted (the anchoring direction is superseded by 0040)
- Area: SPV
- Recorded: 2026-11-03 (retroactive: reconstructed from code, comments and changelog)
Context
Palladium uses LWMA difficulty with 2-minute blocks. An SPV client cannot recompute LWMA without the full header history, and NBitcoin assumes Bitcoin's retargeting rules, so checking proof of work client-side would either be wrong or require downloading every header.
Decision
Set SkipPowValidation = true in the profile and anchor trust to hardcoded checkpoints instead. For each header used in a Merkle proof, WalletSynchronizer.AnchorToCheckpointAsync downloads the intervening headers, verifies the prev-hash chain back to the nearest checkpoint at or below that height, and requires that checkpoint's hash to match exactly. Mainnet carries a checkpoint every 20,000 blocks.
Consequences
- A server cannot feed an alternative history below the newest checkpoint.
- Headers above the newest checkpoint are linked but their work is not verified; this is an accepted limitation documented in
SECURITY.md. - Testnet/regtest have no checkpoints, so headers there are trusted at face value.
- Checkpoints must be extended with releases; a wrong checkpoint would reject every honest server.
References
src/Core/Spv/WalletSynchronizer.cssrc/Core/Spv/BlockHeaderInfo.cssrc/Core/Chain/ChainProfiles.csSECURITY.md