`LocalChain::apply_changeset` silently replaces the genesis block

Open Beginner friendly
#2,309 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
84/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
blockchain

Research direction

Start in crates/chain/src/local_chain.rs, focusing on apply_changeset_to_checkpoint and LocalChain::apply_changeset, then add the reproduction in crates/chain/tests/test_genesis_changeset.rs. Run cargo test -p bdk_chain --test test_genesis_changeset; done means a changeset with a conflicting height-0 hash is rejected consistently with the other mutation methods and the original genesis remains unchanged.

Written by the indexing model from the issue text.

Description

bug

Describe the bug

LocalChain::apply_changeset accepts a ChangeSet containing (0, Some(hash)) with a hash different from the chain's current genesis and silently rewrites the genesis block. apply_changeset_to_checkpoint (crates/chain/src/local_chain.rs) collects the existing checkpoints from the changeset's lowest height upwards, overwrites height 0 with the changeset entry, and rebuilds the chain via LocalChain::from_blocks, which never compares against the previous genesis.

The other mutation entrypoints enforce this invariant: apply_update (via merge_chains) returns CannotConnectError when the update disagrees on genesis, and insert_block / disconnect_from also refuse to alter height 0. apply_changeset is the only one that does not, so a persisted or externally constructed changeset with a wrong height-0 entry can move a chain onto a different genesis (e.g. another network) without any error.

This issue was found by AI.

To Reproduce

Add crates/chain/tests/test_genesis_changeset.rs and run cargo test -p bdk_chain --test test_genesis_changeset:

use bdk_chain::local_chain::{ChangeSet, LocalChain};
use bdk_testenv::{hash, local_chain};
use bitcoin::BlockHash;

#[test]
fn apply_changeset_does_not_replace_genesis() {
    let mut chain: LocalChain = local_chain![(0, hash!("G")), (1, hash!("A"))];
    let changeset: ChangeSet<BlockHash> = [(0, Some(hash!("not_G")))].into_iter().collect();

    let result = chain.apply_changeset(&changeset);

    assert_eq!(chain.genesis_hash(), hash!("G"), "apply_changeset returned {result:?}");
}

Current output:

assertion `left == right` failed: apply_changeset returned Ok(())
  left: 7fa96a46d03a598e64ddfaadb1525023753ae80470cacac37ebb5fbc837ebeb9
 right: dc4e3f5d6f1c6736abbdfa7a86e999ebdc7b132b82411571fc3909ac0ebc41fa

Expected behavior

apply_changeset should not silently replace an existing genesis block; a changeset that disagrees on height 0 should be handled consistently with the other LocalChain mutation methods.

Dominant language
Rust
Stars
1.1k
Forks
491
Avg merge
1d 5h
Merged PRs (30d)
1

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from bitcoindevkit/bdk

All issues in bitcoindevkit/bdk

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.