bitcoindevkit / bitcoindevkit/bdk
`LocalChain::apply_changeset` silently replaces the genesis block
- 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
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