/// passed in. Of course, if the `block_id` was already present then this just returns `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 {
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![];
--- /dev/null
+#[allow(unused_macros)]
+macro_rules! block_id {
+ ($height:expr, $hash:literal) => {{
+ bdk_chain::BlockId {
+ height: $height,
+ hash: bitcoin::hashes::Hash::hash($hash.as_bytes()),
+ }
+ }};
+}
--- /dev/null
+#[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");
+ }
+ }
+}