]> Untitled Git - bdk/commitdiff
fix(core): calling `CheckPoint::insert` with existing block must succeed
author志宇 <hello@evanlinjin.me>
Thu, 12 Sep 2024 08:44:21 +0000 (16:44 +0800)
committer志宇 <hello@evanlinjin.me>
Thu, 12 Sep 2024 13:16:46 +0000 (21:16 +0800)
Previously, we were panicking when the caller tried to insert a block at
height 0. However, this should succeed if the block hash does not
change.

crates/core/src/checkpoint.rs
crates/core/tests/common.rs [new file with mode: 0644]
crates/core/tests/test_checkpoint.rs [new file with mode: 0644]

index 0abadda1da6d17b0d542e34323766c8369d97298..089db230d84925519cb5ca04d516455e7ecf7cc9 100644 (file)
@@ -173,8 +173,6 @@ impl CheckPoint {
     /// 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 {
@@ -182,6 +180,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 (file)
index 0000000..347a90b
--- /dev/null
@@ -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 (file)
index 0000000..66f945f
--- /dev/null
@@ -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");
+        }
+    }
+}