diff --git a/crates/core/src/checkpoint.rs b/crates/core/src/checkpoint.rs index 0abadda1d..10af13277 100644 --- a/crates/core/src/checkpoint.rs +++ b/crates/core/src/checkpoint.rs @@ -171,10 +171,12 @@ impl CheckPoint { /// it. If the height already existed and has a conflicting block hash then it will be purged /// along with all block followin it. The returned chain will have a tip of the `block_id` /// passed in. Of course, if the `block_id` was already present then this just returns `self`. + /// + /// # Panics + /// + /// This panics if called with a genesis block that differs from that of `self`. #[must_use] pub fn insert(self, block_id: BlockId) -> Self { - assert_ne!(block_id.height, 0, "cannot insert the genesis block"); - let mut cp = self.clone(); let mut tail = vec![]; let base = loop { @@ -182,6 +184,7 @@ impl CheckPoint { if cp.hash() == block_id.hash { return self; } + assert_ne!(cp.height(), 0, "cannot replace genesis block"); // if we have a conflict we just return the inserted block because the tail is by // implication invalid. tail = vec![]; diff --git a/crates/core/tests/common.rs b/crates/core/tests/common.rs new file mode 100644 index 000000000..347a90bf6 --- /dev/null +++ b/crates/core/tests/common.rs @@ -0,0 +1,9 @@ +#[allow(unused_macros)] +macro_rules! block_id { + ($height:expr, $hash:literal) => {{ + bdk_chain::BlockId { + height: $height, + hash: bitcoin::hashes::Hash::hash($hash.as_bytes()), + } + }}; +} diff --git a/crates/core/tests/test_checkpoint.rs b/crates/core/tests/test_checkpoint.rs new file mode 100644 index 000000000..705f1c474 --- /dev/null +++ b/crates/core/tests/test_checkpoint.rs @@ -0,0 +1,36 @@ +#[macro_use] +mod common; + +use bdk_core::CheckPoint; + +/// Inserting a block that already exists in the checkpoint chain must always succeed. +#[test] +fn checkpoint_insert_existing() { + let blocks = &[ + block_id!(0, "genesis"), + block_id!(1, "A"), + block_id!(2, "B"), + block_id!(3, "C"), + ]; + + // Index `i` allows us to test with chains of different length. + // Index `j` allows us to test inserting different block heights. + for i in 0..blocks.len() { + let cp_chain = CheckPoint::from_block_ids(blocks[..=i].iter().copied()) + .expect("must construct valid chain"); + + for j in 0..=i { + let block_to_insert = cp_chain + .get(j as u32) + .expect("cp of height must exist") + .block_id(); + let new_cp_chain = cp_chain.clone().insert(block_to_insert); + + assert_eq!( + new_cp_chain, cp_chain, + "must not divert from original chain" + ); + assert!(new_cp_chain.eq_ptr(&cp_chain), "pointers must still match"); + } + } +}