Skip to content

Commit cd2cd40

Browse files
committed
refactor(core): CheckPoint takes a generic WIP
1 parent 23bae3e commit cd2cd40

File tree

2 files changed

+189
-80
lines changed

2 files changed

+189
-80
lines changed

crates/bitcoind_rpc/tests/test_emitter.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,10 @@ fn process_block(
280280
block: Block,
281281
block_height: u32,
282282
) -> anyhow::Result<()> {
283-
recv_chain.apply_update(CheckPoint::from_header(&block.header, block_height))?;
283+
recv_chain.apply_update(CheckPoint::blockhash_checkpoint_from_header(
284+
&block.header,
285+
block_height,
286+
))?;
284287
let _ = recv_graph.apply_block(block, block_height);
285288
Ok(())
286289
}

crates/core/src/checkpoint.rs

Lines changed: 185 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,79 @@
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

88
/// A checkpoint is a node of a reference-counted linked list of [`BlockId`]s.
99
///
1010
/// Checkpoints are cheaply cloneable and are useful to find the agreement point between two sparse
1111
/// block chains.
12-
#[derive(Debug, Clone)]
13-
pub struct CheckPoint(Arc<CPInner>);
12+
#[derive(Debug)]
13+
pub struct CheckPoint<B = BlockHash>(Arc<CPInner<B>>);
14+
15+
impl<B> Clone for CheckPoint<B> {
16+
fn clone(&self) -> Self {
17+
CheckPoint(Arc::clone(&self.0))
18+
}
19+
}
1420

1521
/// The internal contents of [`CheckPoint`].
1622
#[derive(Debug, Clone)]
17-
struct CPInner {
18-
/// Block id (hash and height).
19-
block: BlockId,
23+
struct CPInner<B> {
24+
/// Block data.
25+
block_id: BlockId,
26+
/// Data.
27+
data: B,
2028
/// Previous checkpoint (if any).
21-
prev: Option<Arc<CPInner>>,
29+
prev: Option<Arc<CPInner<B>>>,
2230
}
2331

24-
impl PartialEq for CheckPoint {
32+
/// TODO: ToBlockHash doc
33+
pub trait ToBlockHash {
34+
/// TODO: to_blockhash doc
35+
fn to_blockhash(&self) -> BlockHash;
36+
}
37+
38+
impl ToBlockHash for BlockHash {
39+
fn to_blockhash(&self) -> BlockHash {
40+
*self
41+
}
42+
}
43+
44+
impl ToBlockHash for Header {
45+
fn to_blockhash(&self) -> BlockHash {
46+
self.block_hash()
47+
}
48+
}
49+
50+
impl<B> PartialEq for CheckPoint<B>
51+
where
52+
B: Copy + core::cmp::PartialEq,
53+
{
2554
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());
55+
let self_cps = self.iter().map(|cp| cp.0.block_id);
56+
let other_cps = other.iter().map(|cp| cp.0.block_id);
2857
self_cps.eq(other_cps)
2958
}
3059
}
3160

32-
impl CheckPoint {
33-
/// Construct a new base block at the front of a linked list.
61+
impl CheckPoint<BlockHash> {
62+
/// Construct a new base [`CheckPoint`] at the front of a linked list.
3463
pub fn new(block: BlockId) -> Self {
35-
Self(Arc::new(CPInner { block, prev: None }))
64+
CheckPoint::from_data(block.height, block.hash)
3665
}
3766

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.
67+
/// Construct a checkpoint from the given `header` and block `height`.
4768
///
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)
69+
/// If `header` is of the genesis block, the checkpoint won't have a `prev` node. Otherwise,
70+
/// we return a checkpoint linked with the previous block.
71+
#[deprecated(
72+
since = "0.1.1",
73+
note = "Please use [`CheckPoint::blockhash_checkpoint_from_header`] instead. To create a CheckPoint<Header>, please use [`CheckPoint::from_data`]."
74+
)]
75+
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
76+
CheckPoint::blockhash_checkpoint_from_header(header, height)
5877
}
5978

6079
/// Construct a checkpoint from the given `header` and block `height`.
@@ -63,7 +82,7 @@ impl CheckPoint {
6382
/// we return a checkpoint linked with the previous block.
6483
///
6584
/// [`prev`]: CheckPoint::prev
66-
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
85+
pub fn blockhash_checkpoint_from_header(header: &bitcoin::block::Header, height: u32) -> Self {
6786
let hash = header.block_hash();
6887
let this_block_id = BlockId { height, hash };
6988

@@ -82,55 +101,93 @@ impl CheckPoint {
82101
.expect("must construct checkpoint")
83102
}
84103

85-
/// Puts another checkpoint onto the linked list representing the blockchain.
104+
/// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
86105
///
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)
106+
/// # Errors
107+
///
108+
/// This method will error if any of the follow occurs:
109+
///
110+
/// - The `blocks` iterator is empty, in which case, the error will be `None`.
111+
/// - The `blocks` iterator is not in ascending height order.
112+
/// - The `blocks` iterator contains multiple [`BlockId`]s of the same height.
113+
///
114+
/// The error type is the last successful checkpoint constructed (if any).
115+
pub fn from_block_ids(
116+
block_ids: impl IntoIterator<Item = BlockId>,
117+
) -> Result<Self, Option<Self>> {
118+
let mut blocks = block_ids.into_iter();
119+
let block = blocks.next().ok_or(None)?;
120+
let mut acc = CheckPoint::new(block);
121+
for id in blocks {
122+
acc = acc.push(id).map_err(Some)?;
97123
}
124+
Ok(acc)
98125
}
99126

100127
/// Extends the checkpoint linked list by a iterator of block ids.
101128
///
102129
/// Returns an `Err(self)` if there is block which does not have a greater height than the
103130
/// 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)
131+
pub fn extend(self, blockdata: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
132+
self.extend_data(
133+
blockdata
134+
.into_iter()
135+
.map(|block| (block.height, block.hash)),
136+
)
137+
}
138+
139+
/// Inserts `block_id` at its height within the chain.
140+
///
141+
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
142+
/// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
143+
/// it. If the height already existed and has a conflicting block hash then it will be purged
144+
/// along with all block followin it. The returned chain will have a tip of the `block_id`
145+
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
146+
#[must_use]
147+
pub fn insert(self, block_id: BlockId) -> Self {
148+
self.insert_data(block_id.height, block_id.hash)
149+
}
150+
151+
/// Puts another checkpoint onto the linked list representing the blockchain.
152+
///
153+
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
154+
/// are pushing on to.
155+
pub fn push(self, block: BlockId) -> Result<Self, Self> {
156+
self.push_data(block.height, block.hash)
157+
}
158+
}
159+
160+
impl<B> CheckPoint<B>
161+
where
162+
B: Copy,
163+
{
164+
/// Get the `data` of the checkpoint.
165+
pub fn data(&self) -> &B {
166+
&self.0.data
110167
}
111168

112169
/// Get the [`BlockId`] of the checkpoint.
113170
pub fn block_id(&self) -> BlockId {
114-
self.0.block
171+
self.0.block_id
115172
}
116173

117-
/// Get the height of the checkpoint.
174+
/// Get the `height` of the checkpoint.
118175
pub fn height(&self) -> u32 {
119-
self.0.block.height
176+
self.0.block_id.height
120177
}
121178

122179
/// Get the block hash of the checkpoint.
123180
pub fn hash(&self) -> BlockHash {
124-
self.0.block.hash
181+
self.0.block_id.hash
125182
}
126183

127-
/// Get the previous checkpoint in the chain
128-
pub fn prev(&self) -> Option<CheckPoint> {
184+
/// Get the previous checkpoint in the chain.
185+
pub fn prev(&self) -> Option<CheckPoint<B>> {
129186
self.0.prev.clone().map(CheckPoint)
130187
}
131188

132189
/// Iterate from this checkpoint in descending height.
133-
pub fn iter(&self) -> CheckPointIter {
190+
pub fn iter(&self) -> CheckPointIter<B> {
134191
self.clone().into_iter()
135192
}
136193

@@ -145,7 +202,7 @@ impl CheckPoint {
145202
///
146203
/// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
147204
/// height).
148-
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
205+
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint<B>>
149206
where
150207
R: RangeBounds<u32>,
151208
{
@@ -163,56 +220,105 @@ impl CheckPoint {
163220
core::ops::Bound::Unbounded => true,
164221
})
165222
}
223+
}
166224

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

178265
let mut cp = self.clone();
179266
let mut tail = vec![];
180267
let base = loop {
181-
if cp.height() == block_id.height {
182-
if cp.hash() == block_id.hash {
268+
if cp.height() == height {
269+
if cp.hash() == data.to_blockhash() {
183270
return self;
184271
}
185-
// if we have a conflict we just return the inserted block because the tail is by
272+
// if we have a conflict we just return the inserted data because the tail is by
186273
// implication invalid.
187274
tail = vec![];
188275
break cp.prev().expect("can't be called on genesis block");
189276
}
190277

191-
if cp.height() < block_id.height {
278+
if cp.height() < height {
192279
break cp;
193280
}
194281

195-
tail.push(cp.block_id());
282+
tail.push((cp.height(), *cp.data()));
196283
cp = cp.prev().expect("will break before genesis block");
197284
};
198285

199-
base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
286+
base.extend_data(core::iter::once((height, data)).chain(tail.into_iter().rev()))
200287
.expect("tail is in order")
201288
}
202289

290+
/// Puts another checkpoint onto the linked list representing the blockchain.
291+
///
292+
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
293+
/// are pushing on to.
294+
pub fn push_data(self, height: u32, data: B) -> Result<Self, Self> {
295+
if self.height() < height {
296+
Ok(Self(Arc::new(CPInner {
297+
block_id: BlockId {
298+
height,
299+
hash: data.to_blockhash(),
300+
},
301+
data,
302+
prev: Some(self.0),
303+
})))
304+
} else {
305+
Err(self)
306+
}
307+
}
308+
203309
/// This method tests for `self` and `other` to have equal internal pointers.
204310
pub fn eq_ptr(&self, other: &Self) -> bool {
205311
Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
206312
}
207313
}
208314

209315
/// Iterates over checkpoints backwards.
210-
pub struct CheckPointIter {
211-
current: Option<Arc<CPInner>>,
316+
pub struct CheckPointIter<B = BlockHash> {
317+
current: Option<Arc<CPInner<B>>>,
212318
}
213319

214-
impl Iterator for CheckPointIter {
215-
type Item = CheckPoint;
320+
impl<B> Iterator for CheckPointIter<B> {
321+
type Item = CheckPoint<B>;
216322

217323
fn next(&mut self) -> Option<Self::Item> {
218324
let current = self.current.clone()?;
@@ -221,9 +327,9 @@ impl Iterator for CheckPointIter {
221327
}
222328
}
223329

224-
impl IntoIterator for CheckPoint {
225-
type Item = CheckPoint;
226-
type IntoIter = CheckPointIter;
330+
impl<B> IntoIterator for CheckPoint<B> {
331+
type Item = CheckPoint<B>;
332+
type IntoIter = CheckPointIter<B>;
227333

228334
fn into_iter(self) -> Self::IntoIter {
229335
CheckPointIter {

0 commit comments

Comments
 (0)