Skip to content

fix(chain)!: reject changesets that replace the genesis block - #2331

Open
Brijesh-Thakkar wants to merge 1 commit into
bitcoindevkit:masterfrom
Brijesh-Thakkar:fix/local-chain-genesis-replace
Open

Brijesh-Thakkar wants to merge 1 commit into
bitcoindevkit:masterfrom
Brijesh-Thakkar:fix/local-chain-genesis-replace

Conversation

@Brijesh-Thakkar

Copy link
Copy Markdown

Fixes #2309

Description

LocalChain::apply_changeset was the only mutation entry point that did not protect the genesis block. A ChangeSet containing (0, Some(hash)) with a hash different from the chain's genesis made apply_changeset_to_checkpoint rebuild the chain via LocalChain::from_blocks without comparing against the old genesis, so the chain silently moved onto a different genesis. apply_update (via merge_chains), insert_block and disconnect_from already refuse this.

This adds a check at the top of apply_changeset_to_checkpoint. If the changeset has a height-0 block whose hash differs from the chain's genesis, it returns the new ApplyBlockError::CannotReplaceGenesis { expected } before anything is applied, so the chain is left unchanged. A matching genesis entry still applies, and from_changeset is unaffected because it seeds genesis from the same changeset.

Notes to the reviewers

  • ApplyBlockError is not #[non_exhaustive], so adding a variant is a breaking change for exhaustive matches. I can add #[non_exhaustive] or use a different approach if you prefer.
  • merge_chains already rejects a differing genesis before reaching this code, so its new match arm only satisfies the exhaustive match and maps to CannotConnectError { try_include_height: 0 }.
  • The check uses get(0), which only returns checkpoints with data. This matches the !is_placeholder() check in merge_chains. A LocalChain cannot hold a placeholder genesis, so I could not write a test for that case.
  • (0, None) already fails with MissingGenesis and is unchanged. A test pins that behavior.
  • The error carries only expected, consistent with PrevBlockhashMismatch.
  • A closed PR (fix(chain): reject a changeset that replaces the genesis block #2311) proposed a similar approach.
  • AI assistance: I used Claude Code to help analyze the issue and draft the fix. I reviewed the diff and ran the checks myself.
  • I ran fmt, clippy, the workspace tests, the bdk_chain feature and no_std builds, cargo doc with -D warnings, and the 1.85.0 MSRV build and tests locally. The other crates' per-feature builds are left to CI.

Changelog notice

Changed

Checklists

All Submissions:

Bugfixes:

  • This pull request breaks the existing API
  • I've added tests to reproduce the issue which are now passing
  • I'm linking the issue being fixed by this PR

Copilot AI balanced review requested due to automatic review settings September 29, 2026 21:08
@github-project-automation github-project-automation Bot moved this to Triage in BDK Chain Sep 29, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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

Labels

None yet

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

LocalChain::apply_changeset silently replaces the genesis block

2 participants