]> Untitled Git - bdk/commitdiff
feat!: move `CheckPoint` to `bdk_core`
author志宇 <hello@evanlinjin.me>
Fri, 23 Aug 2024 15:38:10 +0000 (15:38 +0000)
committer志宇 <hello@evanlinjin.me>
Sat, 24 Aug 2024 16:15:37 +0000 (16:15 +0000)
Also add `CheckPoint::eq_ptr` which compares the internal pointers of
two `CheckPoint`s. This is used as an optimisation when comparing two
chains.

crates/chain/src/local_chain.rs
crates/chain/src/spk_client.rs
crates/core/src/checkpoint.rs [new file with mode: 0644]
crates/core/src/lib.rs
crates/testenv/src/lib.rs

index 4a2505f7016600f552e1d1a1da238bc9a5b5e6de..e16131db32f6fa4017593b3caafeb8c8887e286c 100644 (file)
@@ -5,236 +5,10 @@ use core::ops::RangeBounds;
 
 use crate::collections::BTreeMap;
 use crate::{BlockId, ChainOracle, Merge};
-use alloc::sync::Arc;
+pub use bdk_core::{CheckPoint, CheckPointIter};
 use bitcoin::block::Header;
 use bitcoin::BlockHash;
 
-/// A [`LocalChain`] checkpoint is used to find the agreement point between two chains and as a
-/// transaction anchor.
-///
-/// Each checkpoint contains the height and hash of a block ([`BlockId`]).
-///
-/// Internally, checkpoints are nodes of a reference-counted linked-list. This allows the caller to
-/// cheaply clone a [`CheckPoint`] without copying the whole list and to view the entire chain
-/// without holding a lock on [`LocalChain`].
-#[derive(Debug, Clone)]
-pub struct CheckPoint(Arc<CPInner>);
-
-/// The internal contents of [`CheckPoint`].
-#[derive(Debug, Clone)]
-struct CPInner {
-    /// Block id (hash and height).
-    block: BlockId,
-    /// Previous checkpoint (if any).
-    prev: Option<Arc<CPInner>>,
-}
-
-impl PartialEq for CheckPoint {
-    fn eq(&self, other: &Self) -> bool {
-        let self_cps = self.iter().map(|cp| cp.block_id());
-        let other_cps = other.iter().map(|cp| cp.block_id());
-        self_cps.eq(other_cps)
-    }
-}
-
-impl CheckPoint {
-    /// Construct a new base block at the front of a linked list.
-    pub fn new(block: BlockId) -> Self {
-        Self(Arc::new(CPInner { block, prev: None }))
-    }
-
-    /// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
-    ///
-    /// # Errors
-    ///
-    /// This method will error if any of the follow occurs:
-    ///
-    /// - The `blocks` iterator is empty, in which case, the error will be `None`.
-    /// - The `blocks` iterator is not in ascending height order.
-    /// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
-    ///
-    /// The error type is the last successful checkpoint constructed (if any).
-    pub fn from_block_ids(
-        block_ids: impl IntoIterator<Item = BlockId>,
-    ) -> Result<Self, Option<Self>> {
-        let mut blocks = block_ids.into_iter();
-        let mut acc = CheckPoint::new(blocks.next().ok_or(None)?);
-        for id in blocks {
-            acc = acc.push(id).map_err(Some)?;
-        }
-        Ok(acc)
-    }
-
-    /// Construct a checkpoint from the given `header` and block `height`.
-    ///
-    /// If `header` is of the genesis block, the checkpoint won't have a [`prev`] node. Otherwise,
-    /// we return a checkpoint linked with the previous block.
-    ///
-    /// [`prev`]: CheckPoint::prev
-    pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
-        let hash = header.block_hash();
-        let this_block_id = BlockId { height, hash };
-
-        let prev_height = match height.checked_sub(1) {
-            Some(h) => h,
-            None => return Self::new(this_block_id),
-        };
-
-        let prev_block_id = BlockId {
-            height: prev_height,
-            hash: header.prev_blockhash,
-        };
-
-        CheckPoint::new(prev_block_id)
-            .push(this_block_id)
-            .expect("must construct checkpoint")
-    }
-
-    /// Puts another checkpoint onto the linked list representing the blockchain.
-    ///
-    /// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
-    /// are pushing on to.
-    pub fn push(self, block: BlockId) -> Result<Self, Self> {
-        if self.height() < block.height {
-            Ok(Self(Arc::new(CPInner {
-                block,
-                prev: Some(self.0),
-            })))
-        } else {
-            Err(self)
-        }
-    }
-
-    /// Extends the checkpoint linked list by a iterator of block ids.
-    ///
-    /// Returns an `Err(self)` if there is block which does not have a greater height than the
-    /// previous one.
-    pub fn extend(self, blocks: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
-        let mut curr = self.clone();
-        for block in blocks {
-            curr = curr.push(block).map_err(|_| self.clone())?;
-        }
-        Ok(curr)
-    }
-
-    /// Get the [`BlockId`] of the checkpoint.
-    pub fn block_id(&self) -> BlockId {
-        self.0.block
-    }
-
-    /// Get the height of the checkpoint.
-    pub fn height(&self) -> u32 {
-        self.0.block.height
-    }
-
-    /// Get the block hash of the checkpoint.
-    pub fn hash(&self) -> BlockHash {
-        self.0.block.hash
-    }
-
-    /// Get the previous checkpoint in the chain
-    pub fn prev(&self) -> Option<CheckPoint> {
-        self.0.prev.clone().map(CheckPoint)
-    }
-
-    /// Iterate from this checkpoint in descending height.
-    pub fn iter(&self) -> CheckPointIter {
-        self.clone().into_iter()
-    }
-
-    /// Get checkpoint at `height`.
-    ///
-    /// Returns `None` if checkpoint at `height` does not exist`.
-    pub fn get(&self, height: u32) -> Option<Self> {
-        self.range(height..=height).next()
-    }
-
-    /// Iterate checkpoints over a height range.
-    ///
-    /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
-    /// height).
-    pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
-    where
-        R: RangeBounds<u32>,
-    {
-        let start_bound = range.start_bound().cloned();
-        let end_bound = range.end_bound().cloned();
-        self.iter()
-            .skip_while(move |cp| match end_bound {
-                core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound,
-                core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound,
-                core::ops::Bound::Unbounded => false,
-            })
-            .take_while(move |cp| match start_bound {
-                core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound,
-                core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound,
-                core::ops::Bound::Unbounded => true,
-            })
-    }
-
-    /// Inserts `block_id` at its height within the chain.
-    ///
-    /// The effect of `insert` depends on whether a height already exists. If it doesn't the
-    /// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
-    /// 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`.
-    #[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.height() == block_id.height {
-                if cp.hash() == block_id.hash {
-                    return self;
-                }
-                // if we have a conflict we just return the inserted block because the tail is by
-                // implication invalid.
-                tail = vec![];
-                break cp.prev().expect("can't be called on genesis block");
-            }
-
-            if cp.height() < block_id.height {
-                break cp;
-            }
-
-            tail.push(cp.block_id());
-            cp = cp.prev().expect("will break before genesis block");
-        };
-
-        base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
-            .expect("tail is in order")
-    }
-}
-
-/// Iterates over checkpoints backwards.
-pub struct CheckPointIter {
-    current: Option<Arc<CPInner>>,
-}
-
-impl Iterator for CheckPointIter {
-    type Item = CheckPoint;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        let current = self.current.clone()?;
-        self.current.clone_from(&current.prev);
-        Some(CheckPoint(current))
-    }
-}
-
-impl IntoIterator for CheckPoint {
-    type Item = CheckPoint;
-    type IntoIter = CheckPointIter;
-
-    fn into_iter(self) -> Self::IntoIter {
-        CheckPointIter {
-            current: Some(self.0),
-        }
-    }
-}
-
 /// Apply `changeset` to the checkpoint.
 fn apply_changeset_to_checkpoint(
     mut init_cp: CheckPoint,
@@ -582,9 +356,7 @@ impl LocalChain {
 
     /// Iterate over checkpoints in descending height order.
     pub fn iter_checkpoints(&self) -> CheckPointIter {
-        CheckPointIter {
-            current: Some(self.tip.0.clone()),
-        }
+        self.tip.iter()
     }
 
     fn _check_changeset_is_applied(&self, changeset: &ChangeSet) -> bool {
@@ -847,7 +619,7 @@ fn merge_chains(
                     prev_orig_was_invalidated = false;
                     // OPTIMIZATION 2 -- if we have the same underlying pointer at this point, we
                     // can guarantee that no older blocks are introduced.
-                    if Arc::as_ptr(&o.0) == Arc::as_ptr(&u.0) {
+                    if o.eq_ptr(u) {
                         if is_update_height_superset_of_original {
                             return Ok((update_tip, changeset));
                         } else {
index 75fb669848836e826f5b8f50fd2dac16c03c1cf2..e31b431dd8be97a9d45d15d1c585d2ea5e8b3843 100644 (file)
@@ -3,9 +3,8 @@ use crate::{
     alloc::{boxed::Box, collections::VecDeque, vec::Vec},
     collections::BTreeMap,
     local_chain::CheckPoint,
-    Indexed,
+    ConfirmationBlockTime, Indexed,
 };
-use bdk_core::ConfirmationBlockTime;
 use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
 
 type InspectSync<I> = dyn FnMut(SyncItem<I>, SyncProgress) + Send + 'static;
diff --git a/crates/core/src/checkpoint.rs b/crates/core/src/checkpoint.rs
new file mode 100644 (file)
index 0000000..0abadda
--- /dev/null
@@ -0,0 +1,233 @@
+use core::ops::RangeBounds;
+
+use alloc::sync::Arc;
+use bitcoin::BlockHash;
+
+use crate::BlockId;
+
+/// A checkpoint is a node of a reference-counted linked list of [`BlockId`]s.
+///
+/// Checkpoints are cheaply cloneable and are useful to find the agreement point between two sparse
+/// block chains.
+#[derive(Debug, Clone)]
+pub struct CheckPoint(Arc<CPInner>);
+
+/// The internal contents of [`CheckPoint`].
+#[derive(Debug, Clone)]
+struct CPInner {
+    /// Block id (hash and height).
+    block: BlockId,
+    /// Previous checkpoint (if any).
+    prev: Option<Arc<CPInner>>,
+}
+
+impl PartialEq for CheckPoint {
+    fn eq(&self, other: &Self) -> bool {
+        let self_cps = self.iter().map(|cp| cp.block_id());
+        let other_cps = other.iter().map(|cp| cp.block_id());
+        self_cps.eq(other_cps)
+    }
+}
+
+impl CheckPoint {
+    /// Construct a new base block at the front of a linked list.
+    pub fn new(block: BlockId) -> Self {
+        Self(Arc::new(CPInner { block, prev: None }))
+    }
+
+    /// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
+    ///
+    /// # Errors
+    ///
+    /// This method will error if any of the follow occurs:
+    ///
+    /// - The `blocks` iterator is empty, in which case, the error will be `None`.
+    /// - The `blocks` iterator is not in ascending height order.
+    /// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
+    ///
+    /// The error type is the last successful checkpoint constructed (if any).
+    pub fn from_block_ids(
+        block_ids: impl IntoIterator<Item = BlockId>,
+    ) -> Result<Self, Option<Self>> {
+        let mut blocks = block_ids.into_iter();
+        let mut acc = CheckPoint::new(blocks.next().ok_or(None)?);
+        for id in blocks {
+            acc = acc.push(id).map_err(Some)?;
+        }
+        Ok(acc)
+    }
+
+    /// Construct a checkpoint from the given `header` and block `height`.
+    ///
+    /// If `header` is of the genesis block, the checkpoint won't have a [`prev`] node. Otherwise,
+    /// we return a checkpoint linked with the previous block.
+    ///
+    /// [`prev`]: CheckPoint::prev
+    pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
+        let hash = header.block_hash();
+        let this_block_id = BlockId { height, hash };
+
+        let prev_height = match height.checked_sub(1) {
+            Some(h) => h,
+            None => return Self::new(this_block_id),
+        };
+
+        let prev_block_id = BlockId {
+            height: prev_height,
+            hash: header.prev_blockhash,
+        };
+
+        CheckPoint::new(prev_block_id)
+            .push(this_block_id)
+            .expect("must construct checkpoint")
+    }
+
+    /// Puts another checkpoint onto the linked list representing the blockchain.
+    ///
+    /// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
+    /// are pushing on to.
+    pub fn push(self, block: BlockId) -> Result<Self, Self> {
+        if self.height() < block.height {
+            Ok(Self(Arc::new(CPInner {
+                block,
+                prev: Some(self.0),
+            })))
+        } else {
+            Err(self)
+        }
+    }
+
+    /// Extends the checkpoint linked list by a iterator of block ids.
+    ///
+    /// Returns an `Err(self)` if there is block which does not have a greater height than the
+    /// previous one.
+    pub fn extend(self, blocks: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
+        let mut curr = self.clone();
+        for block in blocks {
+            curr = curr.push(block).map_err(|_| self.clone())?;
+        }
+        Ok(curr)
+    }
+
+    /// Get the [`BlockId`] of the checkpoint.
+    pub fn block_id(&self) -> BlockId {
+        self.0.block
+    }
+
+    /// Get the height of the checkpoint.
+    pub fn height(&self) -> u32 {
+        self.0.block.height
+    }
+
+    /// Get the block hash of the checkpoint.
+    pub fn hash(&self) -> BlockHash {
+        self.0.block.hash
+    }
+
+    /// Get the previous checkpoint in the chain
+    pub fn prev(&self) -> Option<CheckPoint> {
+        self.0.prev.clone().map(CheckPoint)
+    }
+
+    /// Iterate from this checkpoint in descending height.
+    pub fn iter(&self) -> CheckPointIter {
+        self.clone().into_iter()
+    }
+
+    /// Get checkpoint at `height`.
+    ///
+    /// Returns `None` if checkpoint at `height` does not exist`.
+    pub fn get(&self, height: u32) -> Option<Self> {
+        self.range(height..=height).next()
+    }
+
+    /// Iterate checkpoints over a height range.
+    ///
+    /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
+    /// height).
+    pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
+    where
+        R: RangeBounds<u32>,
+    {
+        let start_bound = range.start_bound().cloned();
+        let end_bound = range.end_bound().cloned();
+        self.iter()
+            .skip_while(move |cp| match end_bound {
+                core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound,
+                core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound,
+                core::ops::Bound::Unbounded => false,
+            })
+            .take_while(move |cp| match start_bound {
+                core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound,
+                core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound,
+                core::ops::Bound::Unbounded => true,
+            })
+    }
+
+    /// Inserts `block_id` at its height within the chain.
+    ///
+    /// The effect of `insert` depends on whether a height already exists. If it doesn't the
+    /// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
+    /// 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`.
+    #[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.height() == block_id.height {
+                if cp.hash() == block_id.hash {
+                    return self;
+                }
+                // if we have a conflict we just return the inserted block because the tail is by
+                // implication invalid.
+                tail = vec![];
+                break cp.prev().expect("can't be called on genesis block");
+            }
+
+            if cp.height() < block_id.height {
+                break cp;
+            }
+
+            tail.push(cp.block_id());
+            cp = cp.prev().expect("will break before genesis block");
+        };
+
+        base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
+            .expect("tail is in order")
+    }
+
+    /// This method tests for `self` and `other` to have equal internal pointers.
+    pub fn eq_ptr(&self, other: &Self) -> bool {
+        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
+    }
+}
+
+/// Iterates over checkpoints backwards.
+pub struct CheckPointIter {
+    current: Option<Arc<CPInner>>,
+}
+
+impl Iterator for CheckPointIter {
+    type Item = CheckPoint;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        let current = self.current.clone()?;
+        self.current.clone_from(&current.prev);
+        Some(CheckPoint(current))
+    }
+}
+
+impl IntoIterator for CheckPoint {
+    type Item = CheckPoint;
+    type IntoIter = CheckPointIter;
+
+    fn into_iter(self) -> Self::IntoIter {
+        CheckPointIter {
+            current: Some(self.0),
+        }
+    }
+}
index 1c5358401b970a5e7545c4803fbb085c1b54cdbd..f8ac5e328bc71dee7200a7b3c8816cef66731cc0 100644 (file)
@@ -56,3 +56,6 @@ pub mod collections {
 
 mod chain_data;
 pub use chain_data::*;
+
+mod checkpoint;
+pub use checkpoint::*;
index 6d169bdce3d4193e8d37558fff5050d6a656d41d..747acc4487affee6a4504415ae4a4d6eb5cd9f2d 100644 (file)
@@ -4,8 +4,7 @@ use bdk_chain::{
         secp256k1::rand::random, transaction, Address, Amount, Block, BlockHash, CompactTarget,
         ScriptBuf, ScriptHash, Transaction, TxIn, TxOut, Txid,
     },
-    local_chain::CheckPoint,
-    BlockId,
+    BlockId, local_chain::CheckPoint,
 };
 use bitcoincore_rpc::{
     bitcoincore_rpc_json::{GetBlockTemplateModes, GetBlockTemplateRules},