Skip to content

Commit c278804

Browse files
committed
refactor(core): CheckPoint takes a generic WIP
1 parent 775e4ae commit c278804

File tree

2 files changed

+182
-79
lines changed

2 files changed

+182
-79
lines changed

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,10 @@ fn process_block(
284284
block: Block,
285285
block_height: u32,
286286
) -> anyhow::Result<()> {
287-
recv_chain.apply_update(CheckPoint::from_header(&block.header, block_height))?;
287+
recv_chain.apply_update(CheckPoint::blockhash_checkpoint_from_header(
288+
&block.header,
289+
block_height,
290+
))?;
288291
let _ = recv_graph.apply_block(block, block_height);
289292
Ok(())
290293
}

crates/core/src/checkpoint.rs

Lines changed: 178 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use core::ops::RangeBounds;
22

33
use alloc::sync::Arc;
4-
use bitcoin::BlockHash;
4+
use bitcoin::{block::Header, BlockHash};
55

66
use crate::BlockId;
77

@@ -10,51 +10,64 @@ use crate::BlockId;
1010
/// Checkpoints are cheaply cloneable and are useful to find the agreement point between two sparse
1111
/// block chains.
1212
#[derive(Debug, Clone)]
13-
pub struct CheckPoint(Arc<CPInner>);
13+
pub struct CheckPoint<B = BlockHash>(Arc<CPInner<B>>);
1414

1515
/// The internal contents of [`CheckPoint`].
1616
#[derive(Debug, Clone)]
17-
struct CPInner {
18-
/// Block id (hash and height).
19-
block: BlockId,
17+
struct CPInner<B> {
18+
/// Block data.
19+
block_id: BlockId,
20+
/// Data.
21+
data: B,
2022
/// Previous checkpoint (if any).
21-
prev: Option<Arc<CPInner>>,
23+
prev: Option<Arc<CPInner<B>>>,
2224
}
2325

24-
impl PartialEq for CheckPoint {
26+
/// TODO: ToBlockHash doc
27+
pub trait ToBlockHash {
28+
/// TODO: to_blockhash doc
29+
fn to_blockhash(&self) -> BlockHash;
30+
}
31+
32+
impl ToBlockHash for BlockHash {
33+
fn to_blockhash(&self) -> BlockHash {
34+
*self
35+
}
36+
}
37+
38+
impl ToBlockHash for Header {
39+
fn to_blockhash(&self) -> BlockHash {
40+
self.block_hash()
41+
}
42+
}
43+
44+
impl<B> PartialEq for CheckPoint<B>
45+
where
46+
B: Copy + core::cmp::PartialEq,
47+
{
2548
fn eq(&self, other: &Self) -> bool {
26-
let self_cps = self.iter().map(|cp| cp.block_id());
27-
let other_cps = other.iter().map(|cp| cp.block_id());
49+
let self_cps = self.iter().map(|cp| cp.0.block_id);
50+
let other_cps = other.iter().map(|cp| cp.0.block_id);
2851
self_cps.eq(other_cps)
2952
}
3053
}
3154

32-
impl CheckPoint {
33-
/// Construct a new base block at the front of a linked list.
55+
impl CheckPoint<BlockHash> {
56+
/// Construct a new base [`CheckPoint`] at the front of a linked list.
3457
pub fn new(block: BlockId) -> Self {
35-
Self(Arc::new(CPInner { block, prev: None }))
58+
CheckPoint::from_data(block.height, block.hash)
3659
}
3760

38-
/// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
39-
///
40-
/// # Errors
41-
///
42-
/// This method will error if any of the follow occurs:
43-
///
44-
/// - The `blocks` iterator is empty, in which case, the error will be `None`.
45-
/// - The `blocks` iterator is not in ascending height order.
46-
/// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
61+
/// Construct a checkpoint from the given `header` and block `height`.
4762
///
48-
/// The error type is the last successful checkpoint constructed (if any).
49-
pub fn from_block_ids(
50-
block_ids: impl IntoIterator<Item = BlockId>,
51-
) -> Result<Self, Option<Self>> {
52-
let mut blocks = block_ids.into_iter();
53-
let mut acc = CheckPoint::new(blocks.next().ok_or(None)?);
54-
for id in blocks {
55-
acc = acc.push(id).map_err(Some)?;
56-
}
57-
Ok(acc)
63+
/// If `header` is of the genesis block, the checkpoint won't have a `prev` node. Otherwise,
64+
/// we return a checkpoint linked with the previous block.
65+
#[deprecated(
66+
since = "0.1.1",
67+
note = "Please use [`CheckPoint::blockhash_checkpoint_from_header`] instead. To create a CheckPoint<Header>, please use [`CheckPoint::from_data`]."
68+
)]
69+
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
70+
CheckPoint::blockhash_checkpoint_from_header(header, height)
5871
}
5972

6073
/// Construct a checkpoint from the given `header` and block `height`.
@@ -63,7 +76,7 @@ impl CheckPoint {
6376
/// we return a checkpoint linked with the previous block.
6477
///
6578
/// [`prev`]: CheckPoint::prev
66-
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
79+
pub fn blockhash_checkpoint_from_header(header: &bitcoin::block::Header, height: u32) -> Self {
6780
let hash = header.block_hash();
6881
let this_block_id = BlockId { height, hash };
6982

@@ -82,55 +95,93 @@ impl CheckPoint {
8295
.expect("must construct checkpoint")
8396
}
8497

85-
/// Puts another checkpoint onto the linked list representing the blockchain.
98+
/// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
8699
///
87-
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
88-
/// are pushing on to.
89-
pub fn push(self, block: BlockId) -> Result<Self, Self> {
90-
if self.height() < block.height {
91-
Ok(Self(Arc::new(CPInner {
92-
block,
93-
prev: Some(self.0),
94-
})))
95-
} else {
96-
Err(self)
100+
/// # Errors
101+
///
102+
/// This method will error if any of the follow occurs:
103+
///
104+
/// - The `blocks` iterator is empty, in which case, the error will be `None`.
105+
/// - The `blocks` iterator is not in ascending height order.
106+
/// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
107+
///
108+
/// The error type is the last successful checkpoint constructed (if any).
109+
pub fn from_block_ids(
110+
block_ids: impl IntoIterator<Item = BlockId>,
111+
) -> Result<Self, Option<Self>> {
112+
let mut blocks = block_ids.into_iter();
113+
let block = blocks.next().ok_or(None)?;
114+
let mut acc = CheckPoint::new(block);
115+
for id in blocks {
116+
acc = acc.push(id).map_err(Some)?;
97117
}
118+
Ok(acc)
98119
}
99120

100121
/// Extends the checkpoint linked list by a iterator of block ids.
101122
///
102123
/// Returns an `Err(self)` if there is block which does not have a greater height than the
103124
/// previous one.
104-
pub fn extend(self, blocks: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
105-
let mut curr = self.clone();
106-
for block in blocks {
107-
curr = curr.push(block).map_err(|_| self.clone())?;
108-
}
109-
Ok(curr)
125+
pub fn extend(self, blockdata: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
126+
self.extend_data(
127+
blockdata
128+
.into_iter()
129+
.map(|block| (block.height, block.hash)),
130+
)
131+
}
132+
133+
/// Inserts `block_id` at its height within the chain.
134+
///
135+
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
136+
/// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
137+
/// it. If the height already existed and has a conflicting block hash then it will be purged
138+
/// along with all block followin it. The returned chain will have a tip of the `block_id`
139+
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
140+
#[must_use]
141+
pub fn insert(self, block_id: BlockId) -> Self {
142+
self.insert_data(block_id.height, block_id.hash)
143+
}
144+
145+
/// Puts another checkpoint onto the linked list representing the blockchain.
146+
///
147+
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
148+
/// are pushing on to.
149+
pub fn push(self, block: BlockId) -> Result<Self, Self> {
150+
self.push_data(block.height, block.hash)
151+
}
152+
}
153+
154+
impl<B> CheckPoint<B>
155+
where
156+
B: Copy,
157+
{
158+
/// Get the `data` of the checkpoint.
159+
pub fn data(&self) -> &B {
160+
&self.0.data
110161
}
111162

112163
/// Get the [`BlockId`] of the checkpoint.
113164
pub fn block_id(&self) -> BlockId {
114-
self.0.block
165+
self.0.block_id
115166
}
116167

117-
/// Get the height of the checkpoint.
168+
/// Get the `height` of the checkpoint.
118169
pub fn height(&self) -> u32 {
119-
self.0.block.height
170+
self.0.block_id.height
120171
}
121172

122173
/// Get the block hash of the checkpoint.
123174
pub fn hash(&self) -> BlockHash {
124-
self.0.block.hash
175+
self.0.block_id.hash
125176
}
126177

127-
/// Get the previous checkpoint in the chain
128-
pub fn prev(&self) -> Option<CheckPoint> {
178+
/// Get the previous checkpoint in the chain.
179+
pub fn prev(&self) -> Option<CheckPoint<B>> {
129180
self.0.prev.clone().map(CheckPoint)
130181
}
131182

132183
/// Iterate from this checkpoint in descending height.
133-
pub fn iter(&self) -> CheckPointIter {
184+
pub fn iter(&self) -> CheckPointIter<B> {
134185
self.clone().into_iter()
135186
}
136187

@@ -145,7 +196,7 @@ impl CheckPoint {
145196
///
146197
/// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
147198
/// height).
148-
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
199+
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint<B>>
149200
where
150201
R: RangeBounds<u32>,
151202
{
@@ -163,56 +214,105 @@ impl CheckPoint {
163214
core::ops::Bound::Unbounded => true,
164215
})
165216
}
217+
}
166218

167-
/// Inserts `block_id` at its height within the chain.
219+
impl<B> CheckPoint<B>
220+
where
221+
B: Copy + core::fmt::Debug + ToBlockHash,
222+
{
223+
/// Construct a new base [`CheckPoint`] from given `height` and `data` at the front of a linked
224+
/// list.
225+
pub fn from_data(height: u32, data: B) -> Self {
226+
Self(Arc::new(CPInner {
227+
block_id: BlockId {
228+
height,
229+
hash: data.to_blockhash(),
230+
},
231+
data,
232+
prev: None,
233+
}))
234+
}
235+
236+
/// Extends the checkpoint linked list by a iterator containing `height` and `data`.
168237
///
169-
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
170-
/// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
171-
/// it. If the height already existed and has a conflicting block hash then it will be purged
172-
/// along with all block followin it. The returned chain will have a tip of the `block_id`
173-
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
238+
/// Returns an `Err(self)` if there is block which does not have a greater height than the
239+
/// previous one.
240+
pub fn extend_data(self, blockdata: impl IntoIterator<Item = (u32, B)>) -> Result<Self, Self> {
241+
let mut curr = self.clone();
242+
for (height, data) in blockdata {
243+
curr = curr.push_data(height, data).map_err(|_| self.clone())?;
244+
}
245+
Ok(curr)
246+
}
247+
248+
/// Inserts `data` at its `height` within the chain.
249+
///
250+
/// The effect of `insert` depends on whether a `height` already exists. If it doesn't, the
251+
/// `data` we inserted and all pre-existing `data` at higher heights will be re-inserted after
252+
/// it. If the `height` already existed and has a conflicting block hash then it will be purged
253+
/// along with all block following it. The returned chain will have a tip with the `data`
254+
/// passed in. Of course, if the `data` was already present then this just returns `self`.
174255
#[must_use]
175-
pub fn insert(self, block_id: BlockId) -> Self {
176-
assert_ne!(block_id.height, 0, "cannot insert the genesis block");
256+
pub fn insert_data(self, height: u32, data: B) -> Self {
257+
assert_ne!(height, 0, "cannot insert the genesis block");
177258

178259
let mut cp = self.clone();
179260
let mut tail = vec![];
180261
let base = loop {
181-
if cp.height() == block_id.height {
182-
if cp.hash() == block_id.hash {
262+
if cp.height() == height {
263+
if cp.hash() == data.to_blockhash() {
183264
return self;
184265
}
185-
// if we have a conflict we just return the inserted block because the tail is by
266+
// if we have a conflict we just return the inserted data because the tail is by
186267
// implication invalid.
187268
tail = vec![];
188269
break cp.prev().expect("can't be called on genesis block");
189270
}
190271

191-
if cp.height() < block_id.height {
272+
if cp.height() < height {
192273
break cp;
193274
}
194275

195-
tail.push(cp.block_id());
276+
tail.push((cp.height(), *cp.data()));
196277
cp = cp.prev().expect("will break before genesis block");
197278
};
198279

199-
base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
280+
base.extend_data(core::iter::once((height, data)).chain(tail.into_iter().rev()))
200281
.expect("tail is in order")
201282
}
202283

284+
/// Puts another checkpoint onto the linked list representing the blockchain.
285+
///
286+
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
287+
/// are pushing on to.
288+
pub fn push_data(self, height: u32, data: B) -> Result<Self, Self> {
289+
if self.height() < height {
290+
Ok(Self(Arc::new(CPInner {
291+
block_id: BlockId {
292+
height,
293+
hash: data.to_blockhash(),
294+
},
295+
data,
296+
prev: Some(self.0),
297+
})))
298+
} else {
299+
Err(self)
300+
}
301+
}
302+
203303
/// This method tests for `self` and `other` to have equal internal pointers.
204304
pub fn eq_ptr(&self, other: &Self) -> bool {
205305
Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
206306
}
207307
}
208308

209309
/// Iterates over checkpoints backwards.
210-
pub struct CheckPointIter {
211-
current: Option<Arc<CPInner>>,
310+
pub struct CheckPointIter<B = BlockHash> {
311+
current: Option<Arc<CPInner<B>>>,
212312
}
213313

214-
impl Iterator for CheckPointIter {
215-
type Item = CheckPoint;
314+
impl<B> Iterator for CheckPointIter<B> {
315+
type Item = CheckPoint<B>;
216316

217317
fn next(&mut self) -> Option<Self::Item> {
218318
let current = self.current.clone()?;
@@ -221,9 +321,9 @@ impl Iterator for CheckPointIter {
221321
}
222322
}
223323

224-
impl IntoIterator for CheckPoint {
225-
type Item = CheckPoint;
226-
type IntoIter = CheckPointIter;
324+
impl<B> IntoIterator for CheckPoint<B> {
325+
type Item = CheckPoint<B>;
326+
type IntoIter = CheckPointIter<B>;
227327

228328
fn into_iter(self) -> Self::IntoIter {
229329
CheckPointIter {

0 commit comments

Comments
 (0)