bitcoindevkit / bitcoindevkit/bdk

`LocalChain::apply_changeset` silently replaces the genesis block

Open Beginner friendly
#2,309 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
Rust
Stars
1.1k
Forks
483
Avg merge
20d 3h
Merged PRs (30d)
3

Description

**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`:

```rust
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 = [(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.

Contributor guide

Open the contributing guide

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.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.