Skip to content

Commit 77e31c7

Browse files
committed
feat!: move CheckPoint to bdk_core
Also add `CheckPoint::eq_ptr` which compares the internal pointers of two `CheckPoint`s. This is used as an optimisation when comparing two chains.
1 parent a86c878 commit 77e31c7

File tree

5 files changed

+241
-235
lines changed

5 files changed

+241
-235
lines changed

crates/chain/src/local_chain.rs

Lines changed: 3 additions & 231 deletions
Original file line numberDiff line numberDiff line change
@@ -5,236 +5,10 @@ use core::ops::RangeBounds;
55

66
use crate::collections::BTreeMap;
77
use crate::{BlockId, ChainOracle, Merge};
8-
use alloc::sync::Arc;
8+
pub use bdk_core::{CheckPoint, CheckPointIter};
99
use bitcoin::block::Header;
1010
use bitcoin::BlockHash;
1111

12-
/// A [`LocalChain`] checkpoint is used to find the agreement point between two chains and as a
13-
/// transaction anchor.
14-
///
15-
/// Each checkpoint contains the height and hash of a block ([`BlockId`]).
16-
///
17-
/// Internally, checkpoints are nodes of a reference-counted linked-list. This allows the caller to
18-
/// cheaply clone a [`CheckPoint`] without copying the whole list and to view the entire chain
19-
/// without holding a lock on [`LocalChain`].
20-
#[derive(Debug, Clone)]
21-
pub struct CheckPoint(Arc<CPInner>);
22-
23-
/// The internal contents of [`CheckPoint`].
24-
#[derive(Debug, Clone)]
25-
struct CPInner {
26-
/// Block id (hash and height).
27-
block: BlockId,
28-
/// Previous checkpoint (if any).
29-
prev: Option<Arc<CPInner>>,
30-
}
31-
32-
impl PartialEq for CheckPoint {
33-
fn eq(&self, other: &Self) -> bool {
34-
let self_cps = self.iter().map(|cp| cp.block_id());
35-
let other_cps = other.iter().map(|cp| cp.block_id());
36-
self_cps.eq(other_cps)
37-
}
38-
}
39-
40-
impl CheckPoint {
41-
/// Construct a new base block at the front of a linked list.
42-
pub fn new(block: BlockId) -> Self {
43-
Self(Arc::new(CPInner { block, prev: None }))
44-
}
45-
46-
/// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
47-
///
48-
/// # Errors
49-
///
50-
/// This method will error if any of the follow occurs:
51-
///
52-
/// - The `blocks` iterator is empty, in which case, the error will be `None`.
53-
/// - The `blocks` iterator is not in ascending height order.
54-
/// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
55-
///
56-
/// The error type is the last successful checkpoint constructed (if any).
57-
pub fn from_block_ids(
58-
block_ids: impl IntoIterator<Item = BlockId>,
59-
) -> Result<Self, Option<Self>> {
60-
let mut blocks = block_ids.into_iter();
61-
let mut acc = CheckPoint::new(blocks.next().ok_or(None)?);
62-
for id in blocks {
63-
acc = acc.push(id).map_err(Some)?;
64-
}
65-
Ok(acc)
66-
}
67-
68-
/// Construct a checkpoint from the given `header` and block `height`.
69-
///
70-
/// If `header` is of the genesis block, the checkpoint won't have a [`prev`] node. Otherwise,
71-
/// we return a checkpoint linked with the previous block.
72-
///
73-
/// [`prev`]: CheckPoint::prev
74-
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
75-
let hash = header.block_hash();
76-
let this_block_id = BlockId { height, hash };
77-
78-
let prev_height = match height.checked_sub(1) {
79-
Some(h) => h,
80-
None => return Self::new(this_block_id),
81-
};
82-
83-
let prev_block_id = BlockId {
84-
height: prev_height,
85-
hash: header.prev_blockhash,
86-
};
87-
88-
CheckPoint::new(prev_block_id)
89-
.push(this_block_id)
90-
.expect("must construct checkpoint")
91-
}
92-
93-
/// Puts another checkpoint onto the linked list representing the blockchain.
94-
///
95-
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
96-
/// are pushing on to.
97-
pub fn push(self, block: BlockId) -> Result<Self, Self> {
98-
if self.height() < block.height {
99-
Ok(Self(Arc::new(CPInner {
100-
block,
101-
prev: Some(self.0),
102-
})))
103-
} else {
104-
Err(self)
105-
}
106-
}
107-
108-
/// Extends the checkpoint linked list by a iterator of block ids.
109-
///
110-
/// Returns an `Err(self)` if there is block which does not have a greater height than the
111-
/// previous one.
112-
pub fn extend(self, blocks: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
113-
let mut curr = self.clone();
114-
for block in blocks {
115-
curr = curr.push(block).map_err(|_| self.clone())?;
116-
}
117-
Ok(curr)
118-
}
119-
120-
/// Get the [`BlockId`] of the checkpoint.
121-
pub fn block_id(&self) -> BlockId {
122-
self.0.block
123-
}
124-
125-
/// Get the height of the checkpoint.
126-
pub fn height(&self) -> u32 {
127-
self.0.block.height
128-
}
129-
130-
/// Get the block hash of the checkpoint.
131-
pub fn hash(&self) -> BlockHash {
132-
self.0.block.hash
133-
}
134-
135-
/// Get the previous checkpoint in the chain
136-
pub fn prev(&self) -> Option<CheckPoint> {
137-
self.0.prev.clone().map(CheckPoint)
138-
}
139-
140-
/// Iterate from this checkpoint in descending height.
141-
pub fn iter(&self) -> CheckPointIter {
142-
self.clone().into_iter()
143-
}
144-
145-
/// Get checkpoint at `height`.
146-
///
147-
/// Returns `None` if checkpoint at `height` does not exist`.
148-
pub fn get(&self, height: u32) -> Option<Self> {
149-
self.range(height..=height).next()
150-
}
151-
152-
/// Iterate checkpoints over a height range.
153-
///
154-
/// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
155-
/// height).
156-
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
157-
where
158-
R: RangeBounds<u32>,
159-
{
160-
let start_bound = range.start_bound().cloned();
161-
let end_bound = range.end_bound().cloned();
162-
self.iter()
163-
.skip_while(move |cp| match end_bound {
164-
core::ops::Bound::Included(inc_bound) => cp.height() > inc_bound,
165-
core::ops::Bound::Excluded(exc_bound) => cp.height() >= exc_bound,
166-
core::ops::Bound::Unbounded => false,
167-
})
168-
.take_while(move |cp| match start_bound {
169-
core::ops::Bound::Included(inc_bound) => cp.height() >= inc_bound,
170-
core::ops::Bound::Excluded(exc_bound) => cp.height() > exc_bound,
171-
core::ops::Bound::Unbounded => true,
172-
})
173-
}
174-
175-
/// Inserts `block_id` at its height within the chain.
176-
///
177-
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
178-
/// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
179-
/// it. If the height already existed and has a conflicting block hash then it will be purged
180-
/// along with all block followin it. The returned chain will have a tip of the `block_id`
181-
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
182-
#[must_use]
183-
pub fn insert(self, block_id: BlockId) -> Self {
184-
assert_ne!(block_id.height, 0, "cannot insert the genesis block");
185-
186-
let mut cp = self.clone();
187-
let mut tail = vec![];
188-
let base = loop {
189-
if cp.height() == block_id.height {
190-
if cp.hash() == block_id.hash {
191-
return self;
192-
}
193-
// if we have a conflict we just return the inserted block because the tail is by
194-
// implication invalid.
195-
tail = vec![];
196-
break cp.prev().expect("can't be called on genesis block");
197-
}
198-
199-
if cp.height() < block_id.height {
200-
break cp;
201-
}
202-
203-
tail.push(cp.block_id());
204-
cp = cp.prev().expect("will break before genesis block");
205-
};
206-
207-
base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
208-
.expect("tail is in order")
209-
}
210-
}
211-
212-
/// Iterates over checkpoints backwards.
213-
pub struct CheckPointIter {
214-
current: Option<Arc<CPInner>>,
215-
}
216-
217-
impl Iterator for CheckPointIter {
218-
type Item = CheckPoint;
219-
220-
fn next(&mut self) -> Option<Self::Item> {
221-
let current = self.current.clone()?;
222-
self.current.clone_from(&current.prev);
223-
Some(CheckPoint(current))
224-
}
225-
}
226-
227-
impl IntoIterator for CheckPoint {
228-
type Item = CheckPoint;
229-
type IntoIter = CheckPointIter;
230-
231-
fn into_iter(self) -> Self::IntoIter {
232-
CheckPointIter {
233-
current: Some(self.0),
234-
}
235-
}
236-
}
237-
23812
/// Apply `changeset` to the checkpoint.
23913
fn apply_changeset_to_checkpoint(
24014
mut init_cp: CheckPoint,
@@ -582,9 +356,7 @@ impl LocalChain {
582356

583357
/// Iterate over checkpoints in descending height order.
584358
pub fn iter_checkpoints(&self) -> CheckPointIter {
585-
CheckPointIter {
586-
current: Some(self.tip.0.clone()),
587-
}
359+
self.tip.iter()
588360
}
589361

590362
fn _check_changeset_is_applied(&self, changeset: &ChangeSet) -> bool {
@@ -847,7 +619,7 @@ fn merge_chains(
847619
prev_orig_was_invalidated = false;
848620
// OPTIMIZATION 2 -- if we have the same underlying pointer at this point, we
849621
// can guarantee that no older blocks are introduced.
850-
if Arc::as_ptr(&o.0) == Arc::as_ptr(&u.0) {
622+
if o.eq_ptr(u) {
851623
if is_update_height_superset_of_original {
852624
return Ok((update_tip, changeset));
853625
} else {

crates/chain/src/spk_client.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ use crate::{
33
alloc::{boxed::Box, collections::VecDeque, vec::Vec},
44
collections::BTreeMap,
55
local_chain::CheckPoint,
6-
Indexed,
6+
ConfirmationBlockTime, Indexed,
77
};
8-
use bdk_core::ConfirmationBlockTime;
98
use bitcoin::{OutPoint, Script, ScriptBuf, Txid};
109

1110
type InspectSync<I> = dyn FnMut(SyncItem<I>, SyncProgress) + Send + 'static;

0 commit comments

Comments
 (0)