@@ -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) ]
375442mod 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