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.
Describe the bug
LocalChain::apply_changesetaccepts aChangeSetcontaining(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 viaLocalChain::from_blocks, which never compares against the previous genesis.The other mutation entrypoints enforce this invariant:
apply_update(viamerge_chains) returnsCannotConnectErrorwhen the update disagrees on genesis, andinsert_block/disconnect_fromalso refuse to alter height 0.apply_changesetis 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.rsand runcargo test -p bdk_chain --test test_genesis_changeset:Current output:
Expected behavior
apply_changesetshould not silently replace an existing genesis block; a changeset that disagrees on height 0 should be handled consistently with the otherLocalChainmutation methods.