]> Untitled Git - bdk/commitdiff
[chain_redesign] Add `LocalChain::insert_block`
author志宇 <hello@evanlinjin.me>
Wed, 3 May 2023 07:01:39 +0000 (15:01 +0800)
committer志宇 <hello@evanlinjin.me>
Fri, 5 May 2023 08:35:35 +0000 (16:35 +0800)
crates/chain/src/local_chain.rs
crates/chain/tests/test_local_chain.rs

index 30dfe80b8a850cc9f2e09f3cc842589b176eabf9..b070c8909540fb26de231bf5fb6f4646f65078fa 100644 (file)
@@ -173,6 +173,31 @@ impl LocalChain {
     pub fn heights(&self) -> BTreeSet<u32> {
         self.blocks.keys().cloned().collect()
     }
+
+    /// Insert a block of [`BlockId`] into the [`LocalChain`].
+    ///
+    /// # Error
+    ///
+    /// If the insertion height already contains a block, and the block has a different blockhash,
+    /// this will result in an [`InsertBlockNotMatchingError`].
+    pub fn insert_block(
+        &mut self,
+        block_id: BlockId,
+    ) -> Result<ChangeSet, InsertBlockNotMatchingError> {
+        let mut update = Self::from_blocks(self.tip());
+
+        if let Some(original_hash) = update.blocks.insert(block_id.height, block_id.hash) {
+            if original_hash != block_id.hash {
+                return Err(InsertBlockNotMatchingError {
+                    height: block_id.height,
+                    original_hash,
+                    update_hash: block_id.hash,
+                });
+            }
+        }
+
+        Ok(self.apply_update(update).expect("should always connect"))
+    }
 }
 
 /// This is the return value of [`determine_changeset`] and represents changes to [`LocalChain`].
@@ -201,3 +226,24 @@ impl core::fmt::Display for UpdateNotConnectedError {
 
 #[cfg(feature = "std")]
 impl std::error::Error for UpdateNotConnectedError {}
+
+/// Represents a failure when trying to insert a checkpoint into [`LocalChain`].
+#[derive(Clone, Debug, PartialEq)]
+pub struct InsertBlockNotMatchingError {
+    pub height: u32,
+    pub original_hash: BlockHash,
+    pub update_hash: BlockHash,
+}
+
+impl core::fmt::Display for InsertBlockNotMatchingError {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        write!(
+            f,
+            "failed to insert block at height {} as blockhashes conflict: original={}, update={}",
+            self.height, self.original_hash, self.update_hash
+        )
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for InsertBlockNotMatchingError {}
index 1aea98502bb5c3180d303614035e9c9b348bf9f3..55d8af1133b66555665806c26b85d3f4c3567703 100644 (file)
@@ -1,4 +1,7 @@
-use bdk_chain::local_chain::{LocalChain, UpdateNotConnectedError};
+use bdk_chain::local_chain::{
+    ChangeSet, InsertBlockNotMatchingError, LocalChain, UpdateNotConnectedError,
+};
+use bitcoin::BlockHash;
 
 #[macro_use]
 mod common;
@@ -165,3 +168,61 @@ fn invalidation_but_no_connection() {
         Err(UpdateNotConnectedError(0))
     )
 }
+
+#[test]
+fn insert_block() {
+    struct TestCase {
+        original: LocalChain,
+        insert: (u32, BlockHash),
+        expected_result: Result<ChangeSet, InsertBlockNotMatchingError>,
+        expected_final: LocalChain,
+    }
+
+    let test_cases = [
+        TestCase {
+            original: local_chain![],
+            insert: (5, h!("block5")),
+            expected_result: Ok([(5, Some(h!("block5")))].into()),
+            expected_final: local_chain![(5, h!("block5"))],
+        },
+        TestCase {
+            original: local_chain![(3, h!("A"))],
+            insert: (4, h!("B")),
+            expected_result: Ok([(4, Some(h!("B")))].into()),
+            expected_final: local_chain![(3, h!("A")), (4, h!("B"))],
+        },
+        TestCase {
+            original: local_chain![(4, h!("B"))],
+            insert: (3, h!("A")),
+            expected_result: Ok([(3, Some(h!("A")))].into()),
+            expected_final: local_chain![(3, h!("A")), (4, h!("B"))],
+        },
+        TestCase {
+            original: local_chain![(2, h!("K"))],
+            insert: (2, h!("K")),
+            expected_result: Ok([].into()),
+            expected_final: local_chain![(2, h!("K"))],
+        },
+        TestCase {
+            original: local_chain![(2, h!("K"))],
+            insert: (2, h!("J")),
+            expected_result: Err(InsertBlockNotMatchingError {
+                height: 2,
+                original_hash: h!("K"),
+                update_hash: h!("J"),
+            }),
+            expected_final: local_chain![(2, h!("K"))],
+        },
+    ];
+
+    for (i, t) in test_cases.into_iter().enumerate() {
+        let mut chain = t.original;
+        assert_eq!(
+            chain.insert_block(t.insert.into()),
+            t.expected_result,
+            "[{}] unexpected result when inserting block",
+            i,
+        );
+        assert_eq!(chain, t.expected_final, "[{}] unexpected final chain", i,);
+    }
+}