Skip to content

Commit 2a80bc5

Browse files
committed
feat(core): Introduce CheckPointEntry enum
1 parent 97c5610 commit 2a80bc5

File tree

1 file changed

+140
-0
lines changed

1 file changed

+140
-0
lines changed

crates/core/src/checkpoint.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,31 @@ where
216216
}))
217217
}
218218

219+
/// Get the previous [`BlockId`] of this checkpoint.
220+
///
221+
/// I.e. the height and hash of the block immediately preceding this one by consensus,
222+
/// if it can be inferred from the [`prev_blockhash`](ToBlockHash::prev_blockhash) of the inner
223+
/// block data.
224+
///
225+
/// Will be `None` if this is the genesis checkpoint, or the
226+
/// [`prev_blockhash`](ToBlockHash::prev_blockhash) returns `None`.
227+
fn prev_block_id(&self) -> Option<BlockId> {
228+
let prev_height = self.height().checked_sub(1)?;
229+
let prev_hash = self.0.data.prev_blockhash()?;
230+
Some(BlockId {
231+
height: prev_height,
232+
hash: prev_hash,
233+
})
234+
}
235+
236+
/// Get an iterator over the [`CheckPointEntry`] items of this [`CheckPoint`].
237+
pub fn entries(&self) -> CheckPointEntryIter<D> {
238+
CheckPointEntryIter {
239+
current_cp: Some(self.clone()),
240+
next_block_id: None,
241+
}
242+
}
243+
219244
/// Construct from an iterator of block data.
220245
///
221246
/// Returns `Err(None)` if `blocks` doesn't yield any data. If the blocks are not in ascending
@@ -371,9 +396,52 @@ impl<D> IntoIterator for CheckPoint<D> {
371396
}
372397
}
373398

399+
/// [`CheckPointEntry`]
400+
#[derive(Debug, Clone, PartialEq)]
401+
pub enum CheckPointEntry<D> {
402+
/// A node in the [`CheckPoint`] chain.
403+
CheckPoint(CheckPoint<D>),
404+
/// An entry which represents a checkpoint we know to exist but don't yet have data for.
405+
BlockId(BlockId),
406+
}
407+
408+
/// Iterator of [`CheckPointEntry`]s.
409+
pub struct CheckPointEntryIter<D> {
410+
current_cp: Option<CheckPoint<D>>,
411+
next_block_id: Option<BlockId>,
412+
}
413+
414+
impl<D> Iterator for CheckPointEntryIter<D>
415+
where
416+
D: ToBlockHash + fmt::Debug + Copy,
417+
{
418+
type Item = CheckPointEntry<D>;
419+
420+
fn next(&mut self) -> Option<Self::Item> {
421+
// There's a next inferred entry, return it.
422+
if let Some(block_id) = self.next_block_id.take() {
423+
return Some(CheckPointEntry::BlockId(block_id));
424+
}
425+
// Get the current `CheckPoint` in the chain
426+
let cp = self.current_cp.take()?;
427+
// If a gap is detected (height_diff > 1), prepare the next `BlockId`.
428+
if let Some(prev_cp) = cp.prev() {
429+
if cp.height().saturating_sub(prev_cp.height()) > 1 {
430+
self.next_block_id = cp.prev_block_id();
431+
}
432+
// Store the next `CheckPoint`.
433+
self.current_cp = Some(prev_cp);
434+
}
435+
436+
Some(CheckPointEntry::CheckPoint(cp))
437+
}
438+
}
439+
374440
#[cfg(test)]
441+
#[allow(unused)]
375442
mod tests {
376443
use super::*;
444+
use alloc::vec::Vec;
377445

378446
/// Make sure that dropping checkpoints does not result in recursion and stack overflow.
379447
#[test]
@@ -500,4 +568,76 @@ mod tests {
500568
cp = cp.push(1, header_1).unwrap();
501569
let _cp = cp.insert(1, header_2a);
502570
}
571+
572+
#[test]
573+
fn test_checkpoint_entries() {
574+
// ```
575+
// #!/bin/bash
576+
// for n in $(seq 0 11); do bitcoin-cli -regtest getblockheader $(bitcoin-cli -regtest getblockhash $n) false
577+
// done
578+
//```
579+
let headers: Vec<Header> = [
580+
"0100000000000000000000000000000000000000000000000000000000000000000000003ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4adae5494dffff7f2002000000",
581+
"0000002006226e46111a0b59caaf126043eb5bbf28c34f3a5e332a1fc7b2b73cf188910f23b4e61b625f26a50e33aa5a14cef81b92db912079dbdac9bce1dd5e6f02a20bae686069ffff7f2002000000",
582+
"000000201f9b439e2274e1df4a70c3b3b28d6f1412a16aca043e8ddee1a561ba2483f70aeae2c2251894bcaa1b12e46bf72a0e3114a3d3c9d24c13439161b6c3afb236acaf686069ffff7f2000000000",
583+
"000000202c070770b9033e3b76edafa29369774675f4efce93b6879fe732f6b7166e387a2ff70a22d5b7532e9bd509746f742c0004f3bd2c8664ea5a952f5ff851641dbdaf686069ffff7f2001000000",
584+
"00000020e0c5f58ac674b56e87cf0234702b4a233df89d53a56092296ac7c085f9f0740cda05a8377da366739ae73895c9d6d6e5accce1529d421ff611f667c0fc192052b0686069ffff7f2001000000",
585+
"00000020eba5058fdbffdab1c3643a0b0ac740657382ef1ff30a98e361fc2e17d84e745a671d728d09d3e55b88e544c9de3d79210738c0987b7cb26d32dd808ece338146b0686069ffff7f2001000000",
586+
"00000020a949e23d8b64ccf0a1ea870ea37fdeb71451f5b1de0fa7615a16355b678fee492afb7b27b4ea020c786945e9d5e513aa300c03cf29b2abc70a1af6465caf2b65b0686069ffff7f2001000000",
587+
"000000208d2ca4b8fcbffe858c4ad39dc57c60a32ec19eb5edfb74ede0da920b4388a631aab1f8ff0ab584ddfbd6c7da67693fe74a9badc2a6c78ad03ace46b1950700e5b0686069ffff7f2000000000",
588+
"0000002087be4922d644dfcd43cf1f8de7138850b366111503229435b10c3fc4c24bf83ec9a3db521a416a01daa114bbafae98b92a70f0603e81a93e251cb7a48b8e90c2b1686069ffff7f2000000000",
589+
"0000002064677da4c9971de66fc1a7d8284c460c61c8c2d399c2c3f8da52671f466f8070ed356a19b9507bdaba11145a11f4684d07c53dbf800c278511e63762f7b77724b1686069ffff7f2000000000",
590+
"00000020f71012139fc64029711780370e8b2b29ad33fa073a74a6a2d0124013fdcce94f1ca1f097e0ca7991373a951543619c3a2130ca889b2fc498a83ddbb61d9fd3d5b1686069ffff7f2001000000",
591+
"000000202ffc839d282fdc2a6f89f86ab63683e0e3f95d9fef3b0934a643864e4f7fd02087f71bec4c960d8f526022cbb02694ba4820af03f3087a1cd5e7958037dcca79b1686069ffff7f2002000000",
592+
]
593+
.into_iter()
594+
.map(|s| {
595+
let header: Header = bitcoin::consensus::encode::deserialize_hex(s).unwrap();
596+
header
597+
})
598+
.collect();
599+
600+
// A checkpoint chain of blocks
601+
// 0--3--5--11
602+
// Should yield entries with heights
603+
// 11--10--5--4--3--2--0
604+
// ..assuming the block data `D` has a `prev_blockhash`.
605+
let header_0 = headers[0];
606+
let header_3 = headers[3];
607+
let header_5 = headers[5];
608+
let header_11 = headers[11];
609+
let mut cp = CheckPoint::new(0, header_0);
610+
cp = cp.push(3, header_3).unwrap();
611+
cp = cp.push(5, header_5).unwrap();
612+
cp = cp.push(11, header_11).unwrap();
613+
614+
let cp_0 = cp.get(0).unwrap();
615+
let cp_3 = cp.get(3).unwrap();
616+
let cp_5 = cp.get(5).unwrap();
617+
let cp_11 = cp.get(11).unwrap();
618+
619+
let entries: Vec<CheckPointEntry<Header>> = cp.entries().collect();
620+
assert_eq!(entries.len(), 7);
621+
assert_eq!(
622+
entries,
623+
vec![
624+
CheckPointEntry::CheckPoint(cp_11),
625+
CheckPointEntry::BlockId(BlockId {
626+
height: 10,
627+
hash: headers[11].prev_blockhash
628+
}),
629+
CheckPointEntry::CheckPoint(cp_5),
630+
CheckPointEntry::BlockId(BlockId {
631+
height: 4,
632+
hash: headers[5].prev_blockhash
633+
}),
634+
CheckPointEntry::CheckPoint(cp_3),
635+
CheckPointEntry::BlockId(BlockId {
636+
height: 2,
637+
hash: headers[3].prev_blockhash
638+
}),
639+
CheckPointEntry::CheckPoint(cp_0),
640+
]
641+
);
642+
}
503643
}

0 commit comments

Comments
 (0)