Skip to content

Commit 7c13781

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

File tree

1 file changed

+202
-71
lines changed

1 file changed

+202
-71
lines changed

crates/core/src/checkpoint.rs

+202-71
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, consensus::Encodable, hashes::Hash, BlockHash};
55

66
use crate::BlockId;
77

@@ -10,29 +10,83 @@ 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 height.
19+
height: u32,
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<B: ToBlockHash> ToBlockHash for CheckPoint<B> {
33+
fn to_blockhash(&self) -> BlockHash {
34+
self.0.data.to_blockhash()
35+
}
36+
}
37+
38+
impl ToBlockHash for Header {
39+
fn to_blockhash(&self) -> BlockHash {
40+
let mut bytes = vec![];
41+
self.consensus_encode(&mut bytes).unwrap_or_default();
42+
BlockHash::hash(&bytes)
43+
}
44+
}
45+
46+
impl<B> PartialEq for CheckPoint<B>
47+
where
48+
B: Copy + core::cmp::PartialEq,
49+
{
2550
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());
51+
let self_cps = self.iter().map(|cp| *cp.inner());
52+
let other_cps = other.iter().map(|cp| *cp.inner());
2853
self_cps.eq(other_cps)
2954
}
3055
}
3156

32-
impl CheckPoint {
33-
/// Construct a new base block at the front of a linked list.
57+
impl CheckPoint<BlockHash> {
58+
/// Construct a new [`CheckPoint`] at the front of a linked list.
3459
pub fn new(block: BlockId) -> Self {
35-
Self(Arc::new(CPInner { block, prev: None }))
60+
Self(Arc::new(CPInner {
61+
height: block.height,
62+
data: block.hash,
63+
prev: None,
64+
}))
65+
}
66+
67+
/// Construct a checkpoint from the given `header` and block `height`.
68+
///
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+
///
72+
/// [`prev`]: CheckPoint::prev
73+
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
74+
let hash = header.block_hash();
75+
let this_block_id = BlockId { height, hash };
76+
77+
let prev_height = match height.checked_sub(1) {
78+
Some(h) => h,
79+
None => return Self::new(this_block_id),
80+
};
81+
82+
let prev_block_id = BlockId {
83+
height: prev_height,
84+
hash: header.prev_blockhash,
85+
};
86+
87+
CheckPoint::new(prev_block_id)
88+
.push(this_block_id)
89+
.expect("must construct checkpoint")
3690
}
3791

3892
/// Construct a checkpoint from a list of [`BlockId`]s in ascending height order.
@@ -50,36 +104,74 @@ impl CheckPoint {
50104
block_ids: impl IntoIterator<Item = BlockId>,
51105
) -> Result<Self, Option<Self>> {
52106
let mut blocks = block_ids.into_iter();
53-
let mut acc = CheckPoint::new(blocks.next().ok_or(None)?);
107+
let block = blocks.next().ok_or(None)?;
108+
let mut acc = CheckPoint::new(block);
54109
for id in blocks {
55110
acc = acc.push(id).map_err(Some)?;
56111
}
57112
Ok(acc)
58113
}
59114

60-
/// Construct a checkpoint from the given `header` and block `height`.
115+
/// Extends the checkpoint linked list by a iterator of block ids.
61116
///
62-
/// If `header` is of the genesis block, the checkpoint won't have a [`prev`] node. Otherwise,
63-
/// we return a checkpoint linked with the previous block.
117+
/// Returns an `Err(self)` if there is block which does not have a greater height than the
118+
/// previous one.
119+
pub fn extend(self, blockdata: impl IntoIterator<Item = BlockId>) -> Result<Self, Self> {
120+
let mut curr = self.clone();
121+
for block in blockdata {
122+
curr = curr.push(block).map_err(|_| self.clone())?;
123+
}
124+
Ok(curr)
125+
}
126+
127+
/// Get the block hash of the checkpoint.
128+
pub fn hash(&self) -> BlockHash {
129+
self.0.data
130+
}
131+
132+
/// Get the [`BlockId`] of the checkpoint.
133+
pub fn block_id(&self) -> BlockId {
134+
BlockId {
135+
height: self.height(),
136+
hash: self.hash(),
137+
}
138+
}
139+
140+
/// Inserts `block_id` at its height within the chain.
64141
///
65-
/// [`prev`]: CheckPoint::prev
66-
pub fn from_header(header: &bitcoin::block::Header, height: u32) -> Self {
67-
let hash = header.block_hash();
68-
let this_block_id = BlockId { height, hash };
142+
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
143+
/// `block_id` we inserted and all pre-existing blocks higher than it will be re-inserted after
144+
/// it. If the height already existed and has a conflicting block hash then it will be purged
145+
/// along with all block followin it. The returned chain will have a tip of the `block_id`
146+
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
147+
#[must_use]
148+
pub fn insert(self, block_id: BlockId) -> Self {
149+
assert_ne!(block_id.height, 0, "cannot insert the genesis block");
69150

70-
let prev_height = match height.checked_sub(1) {
71-
Some(h) => h,
72-
None => return Self::new(this_block_id),
73-
};
151+
let mut cp = self.clone();
152+
let mut tail = vec![];
153+
let base = loop {
154+
if cp.height() == block_id.height {
155+
if cp.hash() == block_id.hash {
156+
return self;
157+
}
158+
// if we have a conflict we just return the inserted block because the tail is by
159+
// implication invalid.
160+
tail = vec![];
161+
break cp.prev().expect("can't be called on genesis block");
162+
}
74163

75-
let prev_block_id = BlockId {
76-
height: prev_height,
77-
hash: header.prev_blockhash,
164+
if cp.height() < block_id.height {
165+
break cp;
166+
}
167+
168+
tail.push(cp.block_id());
169+
cp = cp.prev().expect("will break before genesis block");
78170
};
79171

80-
CheckPoint::new(prev_block_id)
81-
.push(this_block_id)
82-
.expect("must construct checkpoint")
172+
let new_cp = core::iter::once(block_id).chain(tail.into_iter().rev());
173+
174+
base.extend(new_cp).expect("tail is in order")
83175
}
84176

85177
/// Puts another checkpoint onto the linked list representing the blockchain.
@@ -89,48 +181,46 @@ impl CheckPoint {
89181
pub fn push(self, block: BlockId) -> Result<Self, Self> {
90182
if self.height() < block.height {
91183
Ok(Self(Arc::new(CPInner {
92-
block,
184+
height: block.height,
185+
data: block.hash,
93186
prev: Some(self.0),
94187
})))
95188
} else {
96189
Err(self)
97190
}
98191
}
192+
}
99193

100-
/// Extends the checkpoint linked list by a iterator of block ids.
101-
///
102-
/// Returns an `Err(self)` if there is block which does not have a greater height than the
103-
/// 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)
194+
impl<B> CheckPoint<B>
195+
where
196+
B: Copy,
197+
{
198+
/// Construct a new [`CheckPoint`] at the front of a linked list.
199+
pub fn from_data(height: u32, data: B) -> Self {
200+
Self(Arc::new(CPInner {
201+
height,
202+
data,
203+
prev: None,
204+
}))
110205
}
111206

112-
/// Get the [`BlockId`] of the checkpoint.
113-
pub fn block_id(&self) -> BlockId {
114-
self.0.block
207+
/// Get reference to the inner type.
208+
pub fn inner(&self) -> &B {
209+
&self.0.data
115210
}
116211

117212
/// Get the height of the checkpoint.
118213
pub fn height(&self) -> u32 {
119-
self.0.block.height
120-
}
121-
122-
/// Get the block hash of the checkpoint.
123-
pub fn hash(&self) -> BlockHash {
124-
self.0.block.hash
214+
self.0.height
125215
}
126216

127217
/// Get the previous checkpoint in the chain
128-
pub fn prev(&self) -> Option<CheckPoint> {
218+
pub fn prev(&self) -> Option<CheckPoint<B>> {
129219
self.0.prev.clone().map(CheckPoint)
130220
}
131221

132222
/// Iterate from this checkpoint in descending height.
133-
pub fn iter(&self) -> CheckPointIter {
223+
pub fn iter(&self) -> CheckPointIter<B> {
134224
self.clone().into_iter()
135225
}
136226

@@ -145,7 +235,7 @@ impl CheckPoint {
145235
///
146236
/// Note that we always iterate checkpoints in reverse height order (iteration starts at tip
147237
/// height).
148-
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint>
238+
pub fn range<R>(&self, range: R) -> impl Iterator<Item = CheckPoint<B>>
149239
where
150240
R: RangeBounds<u32>,
151241
{
@@ -164,6 +254,28 @@ impl CheckPoint {
164254
})
165255
}
166256

257+
/// This method tests for `self` and `other` to have equal internal pointers.
258+
pub fn eq_ptr(&self, other: &Self) -> bool {
259+
Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
260+
}
261+
}
262+
263+
impl<B> CheckPoint<B>
264+
where
265+
B: Copy + core::fmt::Debug + ToBlockHash + From<BlockHash>,
266+
{
267+
/// Extends the checkpoint linked list by a iterator of block ids.
268+
///
269+
/// Returns an `Err(self)` if there is block which does not have a greater height than the
270+
/// previous one.
271+
pub fn extend_data(self, blockdata: impl IntoIterator<Item = (u32, B)>) -> Result<Self, Self> {
272+
let mut curr = self.clone();
273+
for (height, data) in blockdata {
274+
curr = curr.push_data(height, data).map_err(|_| self.clone())?;
275+
}
276+
Ok(curr)
277+
}
278+
167279
/// Inserts `block_id` at its height within the chain.
168280
///
169281
/// The effect of `insert` depends on whether a height already exists. If it doesn't the
@@ -172,14 +284,14 @@ impl CheckPoint {
172284
/// along with all block followin it. The returned chain will have a tip of the `block_id`
173285
/// passed in. Of course, if the `block_id` was already present then this just returns `self`.
174286
#[must_use]
175-
pub fn insert(self, block_id: BlockId) -> Self {
176-
assert_ne!(block_id.height, 0, "cannot insert the genesis block");
287+
pub fn insert_data(self, height: u32, data: B) -> Self {
288+
assert_ne!(height, 0, "cannot insert the genesis block");
177289

178290
let mut cp = self.clone();
179291
let mut tail = vec![];
180292
let base = loop {
181-
if cp.height() == block_id.height {
182-
if cp.hash() == block_id.hash {
293+
if cp.height() == height {
294+
if cp.to_blockhash() == data.to_blockhash() {
183295
return self;
184296
}
185297
// if we have a conflict we just return the inserted block because the tail is by
@@ -188,31 +300,50 @@ impl CheckPoint {
188300
break cp.prev().expect("can't be called on genesis block");
189301
}
190302

191-
if cp.height() < block_id.height {
303+
if cp.height() < height {
192304
break cp;
193305
}
194306

195-
tail.push(cp.block_id());
307+
tail.push(BlockId {
308+
height,
309+
hash: data.to_blockhash(),
310+
});
196311
cp = cp.prev().expect("will break before genesis block");
197312
};
198313

199-
base.extend(core::iter::once(block_id).chain(tail.into_iter().rev()))
200-
.expect("tail is in order")
314+
let new_cp = core::iter::once((height, data)).chain(
315+
tail.into_iter()
316+
.rev()
317+
.map(|block| (block.height, B::from(block.hash))),
318+
);
319+
320+
base.extend_data(new_cp).expect("tail is in order")
201321
}
202322

203-
/// This method tests for `self` and `other` to have equal internal pointers.
204-
pub fn eq_ptr(&self, other: &Self) -> bool {
205-
Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
323+
/// Puts another checkpoint onto the linked list representing the blockchain.
324+
///
325+
/// Returns an `Err(self)` if the block you are pushing on is not at a greater height that the one you
326+
/// are pushing on to.
327+
pub fn push_data(self, height: u32, data: B) -> Result<Self, Self> {
328+
if self.height() < height {
329+
Ok(Self(Arc::new(CPInner {
330+
height,
331+
data,
332+
prev: Some(self.0),
333+
})))
334+
} else {
335+
Err(self)
336+
}
206337
}
207338
}
208339

209340
/// Iterates over checkpoints backwards.
210-
pub struct CheckPointIter {
211-
current: Option<Arc<CPInner>>,
341+
pub struct CheckPointIter<B = BlockHash> {
342+
current: Option<Arc<CPInner<B>>>,
212343
}
213344

214-
impl Iterator for CheckPointIter {
215-
type Item = CheckPoint;
345+
impl<B> Iterator for CheckPointIter<B> {
346+
type Item = CheckPoint<B>;
216347

217348
fn next(&mut self) -> Option<Self::Item> {
218349
let current = self.current.clone()?;
@@ -221,9 +352,9 @@ impl Iterator for CheckPointIter {
221352
}
222353
}
223354

224-
impl IntoIterator for CheckPoint {
225-
type Item = CheckPoint;
226-
type IntoIter = CheckPointIter;
355+
impl<B> IntoIterator for CheckPoint<B> {
356+
type Item = CheckPoint<B>;
357+
type IntoIter = CheckPointIter<B>;
227358

228359
fn into_iter(self) -> Self::IntoIter {
229360
CheckPointIter {

0 commit comments

Comments
 (0)