bdk_core/
checkpoint.rs

1use core::ops::RangeBounds;
2
3use alloc::sync::Arc;
4use bitcoin::BlockHash;
5
6use crate::BlockId;
7
8/// A checkpoint is a node of a reference-counted linked list of [`BlockId`]s.
9///
10/// Checkpoints are cheaply cloneable and are useful to find the agreement point between two sparse
11/// block chains.
12#[derive(Debug, Clone)]
13pub struct CheckPoint(Arc<CPInner>);
14
15/// The internal contents of [`CheckPoint`].
16#[derive(Debug, Clone)]
17struct CPInner {
18    /// Block id (hash and height).
19    block: BlockId,
20    /// Previous checkpoint (if any).
21    prev: Option<Arc<CPInner>>,
22}
23
24/// When a `CPInner` is dropped we need to go back down the chain and manually remove any
25/// no-longer referenced checkpoints. Letting the default rust dropping mechanism handle this
26/// leads to recursive logic and stack overflows
27///
28/// https://github.com/bitcoindevkit/bdk/issues/1634
29impl Drop for CPInner {
30    fn drop(&mut self) {
31        // Take out `prev` so its `drop` won't be called when this drop is finished
32        let mut current = self.prev.take();
33        while let Some(arc_node) = current {
34            // Get rid of the Arc around `prev` if we're the only one holding a ref
35            // So the `drop` on it won't be called when the `Arc` is dropped.
36            //
37            // FIXME: When MSRV > 1.70.0 this should use Arc::into_inner which actually guarantees
38            // that no recursive drop calls can happen even with multiple threads.
39            match Arc::try_unwrap(arc_node).ok() {
40                Some(mut node) => {
41                    // Keep going backwards
42                    current = node.prev.take();
43                    // Don't call `drop` on `CPInner` since that risks it becoming recursive.
44                    core::mem::forget(node);
45                }
46                None => break,
47            }
48        }
49    }
50}
51
52impl PartialEq for CheckPoint {
53    fn eq(&self, other: &Self) -> bool {
54        let self_cps = self.iter().map(|cp| cp.block_id());
55        let other_cps = other.iter().map(|cp| cp.block_id());
56        self_cps.eq(other_cps)
57    }
58}
59
60impl CheckPoint {
61    /// Construct a new base block at the front of a linked list.
62    pub fn new(block: BlockId) -> Self {
63        Self(Arc::new(CPInner { block, prev: None }))
64    }
65
66    /// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
67    ///
68    /// # Errors
69    ///
70    /// This method will error if any of the follow occurs:
71    ///
72    /// - The `blocks` iterator is empty, in which case, the error will be `None`.
73    /// - The `blocks` iterator is not in ascending height order.
74    /// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
75    ///
76    /// The error type is the last successful checkpoint constructed (if any).
77    pub fn from_block_ids(
78        block_ids: impl IntoIterator<Item = BlockId>,
79    ) -> Result<Self, Option<Self>> {
80        let mut blocks = block_ids.into_iter();
81        let mut acc = CheckPoint::new(blocks.next().ok_or(None)?);
82        for id in blocks {
83            acc = acc.push(id).map_err(Some)?;
84        }
85        Ok(acc)
86    }
87
88    /// Construct a checkpoint from the given `header` and block `height`.
89    ///
90    /// If `header` is of the genesis block, the checkpoint won't have a [`prev`] node. Otherwise,
91    /// we return a checkpoint linked with the previous block.
92    ///
93    /// [`prev`]: CheckPoint::prev
94    pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
95        let hash = header.block_hash();
96        let this_block_id = BlockId { height, hash };
97
98        let prev_height = match height.checked_sub(1) {
99            Some(h) => h,
100            None => return Self::new(this_block_id),
101        };
102
103        let prev_block_id = BlockId {
104            height: prev_height,
105            hash: header.prev_blockhash,
106        };
107
108        CheckPoint::new(prev_block_id)
109            .push(this_block_id)
110            .expect("must construct checkpoint")
111    }
112
113    /// Puts another checkpoint onto the linked list representing the blockchain.
114    ///
115    /// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the
116    /// one you are pushing on to.
117    pub fn push(self, block: BlockId) -> Result<Self, Self> {
118        if self.height() < block.height {
119            Ok(Self(Arc::new(CPInner {
120                block,
121                prev: Some(self.0),
122            })))
123        } else {
124            Err(self)
125        }
126    }
127
128    /// Extends the checkpoint linked list by a iterator of block ids.
129    ///
130    /// Returns an `Err(self)` if there is block which does not have a greater height than the
131    /// previous one.
132    pub fn extend(self, blocks: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
133        let mut curr = self.clone();
134        for block in blocks {
135            curr = curr.push(block).map_err(|_| self.clone())?;
136        }
137        Ok(curr)
138    }
139
140    /// Get the [`BlockId`] of the checkpoint.
141    pub fn block_id(&self) -> BlockId {
142        self.0.block
143    }
144
145    /// Get the height of the checkpoint.
146    pub fn height(&self) -> u32 {
147        self.0.block.height
148    }
149
150    /// Get the block hash of the checkpoint.
151    pub fn hash(&self) -> BlockHash {
152        self.0.block.hash
153    }
154
155    /// Get the previous checkpoint in the chain
156    pub fn prev(&self) -> Option<CheckPoint> {
157        self.0.prev.clone().map(CheckPoint)
158    }
159
160    /// Iterate from this checkpoint in descending height.
161    pub fn iter(&self) -> CheckPointIter {
162        self.clone().into_iter()
163    }
164
165    /// Get checkpoint at `height`.
166    ///
167    /// Returns `None` if checkpoint at `height` does not exist`.
168    pub fn get(&self, height: u32) -> Option<Self> {
169        self.range(height..=height).next()
170    }
171
172    /// Iterate checkpoints over a height range.
173    ///
174    /// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
175    /// height).
176    pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
177    where
178        R: RangeBounds<u32>,
179    {
180        let start_bound = range.start_bound().cloned();
181        let end_bound = range.end_bound().cloned();
182        self.iter()
183            .skip_while(move |cp| match end_bound {
184                core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound,
185                core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound,
186                core::ops::Bound::Unbounded => false,
187            })
188            .take_while(move |cp| match start_bound {
189                core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound,
190                core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound,
191                core::ops::Bound::Unbounded => true,
192            })
193    }
194
195    /// Returns the checkpoint at `height` if one exists, otherwise the nearest checkpoint at a
196    /// lower height.
197    ///
198    /// This is equivalent to taking the "floor" of `height` over this checkpoint chain.
199    ///
200    /// Returns `None` if no checkpoint exists at or below the given height.
201    pub fn floor_at(&self, height: u32) -> Option<Self> {
202        self.range(..=height).next()
203    }
204
205    /// Returns the checkpoint located a number of heights below this one.
206    ///
207    /// This is a convenience wrapper for [`CheckPoint::floor_at`], subtracting `to_subtract` from
208    /// the current height.
209    ///
210    /// - If a checkpoint exists exactly `offset` heights below, it is returned.
211    /// - Otherwise, the nearest checkpoint *below that target height* is returned.
212    ///
213    /// Returns `None` if `to_subtract` is greater than the current height, or if there is no
214    /// checkpoint at or below the target height.
215    pub fn floor_below(&self, offset: u32) -> Option<Self> {
216        self.floor_at(self.height().checked_sub(offset)?)
217    }
218
219    /// Inserts `block_id` at its height within the chain.
220    ///
221    /// The effect of `insert` depends on whether a height already exists. If it doesn't the
222    /// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
223    /// it. If the height already existed and has a conflicting block hash then it will be purged
224    /// along with all block following it. The returned chain will have a tip of the `block_id`
225    /// passed in. Of course, if the `block_id` was already present then this just returns `self`.
226    ///
227    /// # Panics
228    ///
229    /// This panics if called with a genesis block that differs from that of `self`.
230    #[must_use]
231    pub fn insert(self, block_id: BlockId) -> Self {
232        let mut cp = self.clone();
233        let mut tail = vec![];
234        let base = loop {
235            if cp.height() == block_id.height {
236                if cp.hash() == block_id.hash {
237                    return self;
238                }
239                assert_ne!(cp.height(), 0, "cannot replace genesis block");
240                // if we have a conflict we just return the inserted block because the tail is by
241                // implication invalid.
242                tail = vec![];
243                break cp.prev().expect("can't be called on genesis block");
244            }
245
246            if cp.height() < block_id.height {
247                break cp;
248            }
249
250            tail.push(cp.block_id());
251            cp = cp.prev().expect("will break before genesis block");
252        };
253
254        base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
255            .expect("tail is in order")
256    }
257
258    /// This method tests for `self` and `other` to have equal internal pointers.
259    pub fn eq_ptr(&self, other: &Self) -> bool {
260        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
261    }
262}
263
264/// Iterates over checkpoints backwards.
265pub struct CheckPointIter {
266    current: Option<Arc<CPInner>>,
267}
268
269impl Iterator for CheckPointIter {
270    type Item = CheckPoint;
271
272    fn next(&mut self) -> Option<Self::Item> {
273        let current = self.current.clone()?;
274        self.current.clone_from(&current.prev);
275        Some(CheckPoint(current))
276    }
277}
278
279impl IntoIterator for CheckPoint {
280    type Item = CheckPoint;
281    type IntoIter = CheckPointIter;
282
283    fn into_iter(self) -> Self::IntoIter {
284        CheckPointIter {
285            current: Some(self.0),
286        }
287    }
288}