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(¤t.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}