@@ -6,8 +6,8 @@ use core::ops::RangeBounds;
66
77use crate :: collections:: BTreeMap ;
88use crate :: { BlockId , ChainOracle , Merge } ;
9- use bdk_core:: ToBlockHash ;
109pub use bdk_core:: { CheckPoint , CheckPointIter } ;
10+ use bdk_core:: { CheckPointEntry , ToBlockHash } ;
1111use bitcoin:: block:: Header ;
1212use bitcoin:: BlockHash ;
1313
@@ -69,7 +69,10 @@ impl<D> PartialEq for LocalChain<D> {
6969 }
7070}
7171
72- impl < D > ChainOracle for LocalChain < D > {
72+ impl < D > ChainOracle for LocalChain < D >
73+ where
74+ D : ToBlockHash + Copy ,
75+ {
7376 type Error = Infallible ;
7477
7578 fn is_block_in_chain (
@@ -83,10 +86,18 @@ impl<D> ChainOracle for LocalChain<D> {
8386 Some ( cp) if cp. hash ( ) == chain_tip. hash => cp,
8487 _ => return Ok ( None ) ,
8588 } ;
86- match chain_tip_cp . get ( block . height ) {
87- Some ( cp) => Ok ( Some ( cp . hash ( ) == block. hash ) ) ,
88- None => Ok ( None ) ,
89+
90+ if let Some ( cp) = chain_tip_cp . get ( block. height ) {
91+ return Ok ( Some ( cp . hash ( ) == block . hash ) ) ;
8992 }
93+
94+ if let Some ( next_cp) = chain_tip_cp. get ( block. height . saturating_add ( 1 ) ) {
95+ if let Some ( prev_hash) = next_cp. prev_blockhash ( ) {
96+ return Ok ( Some ( prev_hash == block. hash ) ) ;
97+ }
98+ }
99+
100+ Ok ( None )
90101 }
91102
92103 fn get_chain_tip ( & self ) -> Result < BlockId , Self :: Error > {
@@ -653,59 +664,132 @@ where
653664 }
654665 }
655666 ( Some ( o) , Some ( u) ) => {
656- if o. hash ( ) == u. hash ( ) {
657- // We have found our point of agreement 🎉 -- we require that the previous (i.e.
658- // higher because we are iterating backwards) block in the original chain was
659- // invalidated (if it exists). This ensures that there is an unambiguous point
660- // of connection to the original chain from the update chain
661- // (i.e. we know the precisely which original blocks are
662- // invalid).
663- if !prev_orig_was_invalidated && !point_of_agreement_found {
664- if let ( Some ( prev_orig) , Some ( _prev_update) ) = ( & prev_orig, & prev_update) {
667+ if o. height ( ) == u. height ( ) {
668+ if o. hash ( ) == u. hash ( ) {
669+ // We have found our point of agreement 🎉 -- we require that the previous
670+ // (i.e. higher because we are iterating backwards) block in the original
671+ // chain was invalidated (if it exists). This ensures that there is an
672+ // unambiguous point of connection to the original chain from the update
673+ // chain (i.e. we know the precisely which original blocks are invalid).
674+ if !prev_orig_was_invalidated && !point_of_agreement_found {
675+ if let ( Some ( prev_orig) , Some ( _prev_update) ) =
676+ ( & prev_orig, & prev_update)
677+ {
678+ return Err ( CannotConnectError {
679+ try_include_height : prev_orig. height ( ) ,
680+ } ) ;
681+ }
682+ }
683+ point_of_agreement_found = true ;
684+ prev_orig_was_invalidated = false ;
685+
686+ // OPTIMIZATION 2 -- if we have the same underlying pointer at this point,
687+ // we can guarantee that no older blocks are introduced.
688+ if o. eq_ptr ( u) {
689+ if is_update_height_superset_of_original {
690+ return Ok ( ( update_tip, changeset) ) ;
691+ } else {
692+ let new_tip =
693+ apply_changeset_to_checkpoint ( original_tip, & changeset)
694+ . map_err ( |_| CannotConnectError {
695+ try_include_height : 0 ,
696+ } ) ?;
697+ return Ok ( ( new_tip, changeset) ) ;
698+ }
699+ }
700+ } else {
701+ // We have an invalidation height so we set the height to the updated hash
702+ // and also purge all the original chain block hashes above this block.
703+ changeset. blocks . insert ( u. height ( ) , Some ( u. data ( ) ) ) ;
704+ for invalidated_height in potentially_invalidated_heights. drain ( ..) {
705+ changeset. blocks . insert ( invalidated_height, None ) ;
706+ }
707+ prev_orig_was_invalidated = true ;
708+ }
709+ prev_orig = curr_orig. take ( ) ;
710+ prev_update = curr_update. take ( ) ;
711+ }
712+ // Compare original and update entries when heights differ by exactly 1.
713+ else if o. height ( ) == u. height ( ) + 1 {
714+ let o_entry = CheckPointEntry :: CheckPoint ( o. clone ( ) ) ;
715+ if let Some ( o_prev) = o_entry. as_prev ( ) {
716+ if o_prev. height ( ) == u. height ( ) && o_prev. hash ( ) == u. hash ( ) {
717+ // Ambiguous: update did not provide a real checkpoint at o.height().
665718 return Err ( CannotConnectError {
666- try_include_height : prev_orig . height ( ) ,
719+ try_include_height : o . height ( ) ,
667720 } ) ;
721+ } else {
722+ // No match: treat as o > u case.
723+ potentially_invalidated_heights. push ( o. height ( ) ) ;
724+ prev_orig_was_invalidated = false ;
725+ prev_orig = curr_orig. take ( ) ;
726+ is_update_height_superset_of_original = false ;
668727 }
728+ } else {
729+ // No prev available: treat as o > u case.
730+ potentially_invalidated_heights. push ( o. height ( ) ) ;
731+ prev_orig_was_invalidated = false ;
732+ prev_orig = curr_orig. take ( ) ;
733+ is_update_height_superset_of_original = false ;
669734 }
670- point_of_agreement_found = true ;
671- prev_orig_was_invalidated = false ;
672- // OPTIMIZATION 2 -- if we have the same underlying pointer at this point, we
673- // can guarantee that no older blocks are introduced.
674- if o. eq_ptr ( u) {
675- if is_update_height_superset_of_original {
676- return Ok ( ( update_tip, changeset) ) ;
735+ } else if u. height ( ) == o. height ( ) + 1 {
736+ let u_entry = CheckPointEntry :: CheckPoint ( u. clone ( ) ) ;
737+ if let Some ( u_as_prev) = u_entry. as_prev ( ) {
738+ if u_as_prev. height ( ) == o. height ( ) && u_as_prev. hash ( ) == o. hash ( ) {
739+ // Agreement via `prev_blockhash`.
740+ if !prev_orig_was_invalidated && !point_of_agreement_found {
741+ if let ( Some ( prev_orig) , Some ( _prev_update) ) =
742+ ( & prev_orig, & prev_update)
743+ {
744+ return Err ( CannotConnectError {
745+ try_include_height : prev_orig. height ( ) ,
746+ } ) ;
747+ }
748+ }
749+ point_of_agreement_found = true ;
750+ prev_orig_was_invalidated = false ;
751+
752+ // Update is missing a real checkpoint at o.height().
753+ is_update_height_superset_of_original = false ;
754+
755+ // Record the update checkpoint one-above the agreed parent.
756+ changeset. blocks . insert ( u. height ( ) , Some ( u. data ( ) ) ) ;
757+
758+ // Advance both sides after agreement.
759+ prev_orig = curr_orig. take ( ) ;
760+ prev_update = curr_update. take ( ) ;
677761 } else {
678- let new_tip = apply_changeset_to_checkpoint ( original_tip, & changeset)
679- . map_err ( |_| CannotConnectError {
680- try_include_height : 0 ,
681- } ) ?;
682- return Ok ( ( new_tip, changeset) ) ;
762+ // No match: add update block.
763+ changeset. blocks . insert ( u. height ( ) , Some ( u. data ( ) ) ) ;
764+ prev_update = curr_update. take ( ) ;
683765 }
766+ } else {
767+ // No prev available: just add update block.
768+ changeset. blocks . insert ( u. height ( ) , Some ( u. data ( ) ) ) ;
769+ prev_update = curr_update. take ( ) ;
684770 }
771+ } else if o. height ( ) > u. height ( ) {
772+ // Original > Update: mark original as potentially invalidated.
773+ potentially_invalidated_heights. push ( o. height ( ) ) ;
774+ prev_orig_was_invalidated = false ;
775+ prev_orig = curr_orig. take ( ) ;
776+ is_update_height_superset_of_original = false ;
685777 } else {
686- // We have an invalidation height so we set the height to the updated hash and
687- // also purge all the original chain block hashes above this block.
778+ // Update > Original: add update block.
688779 changeset. blocks . insert ( u. height ( ) , Some ( u. data ( ) ) ) ;
689- for invalidated_height in potentially_invalidated_heights. drain ( ..) {
690- changeset. blocks . insert ( invalidated_height, None ) ;
691- }
692- prev_orig_was_invalidated = true ;
780+ prev_update = curr_update. take ( ) ;
693781 }
694- prev_update = curr_update. take ( ) ;
695- prev_orig = curr_orig. take ( ) ;
696782 }
697783 ( None , None ) => {
698784 break ;
699785 }
700786 _ => {
701- unreachable ! ( "compiler cannot tell that everything has been covered " )
787+ unreachable ! ( "should have been handled above " )
702788 }
703789 }
704790 }
705791
706- // When we don't have a point of agreement you can imagine it is implicitly the
707- // genesis block so we need to do the final connectivity check which in this case
708- // just means making sure the entire original chain was invalidated.
792+ // Final connectivity check
709793 if !prev_orig_was_invalidated && !point_of_agreement_found {
710794 if let Some ( prev_orig) = prev_orig {
711795 return Err ( CannotConnectError {
0 commit comments