@@ -6,8 +6,8 @@ use core::ops::RangeBounds;
6
6
7
7
use crate :: collections:: BTreeMap ;
8
8
use crate :: { BlockId , ChainOracle , Merge } ;
9
- use bdk_core:: ToBlockHash ;
10
9
pub use bdk_core:: { CheckPoint , CheckPointIter } ;
10
+ use bdk_core:: { CheckPointEntry , ToBlockHash } ;
11
11
use bitcoin:: block:: Header ;
12
12
use bitcoin:: BlockHash ;
13
13
@@ -69,7 +69,10 @@ impl<D> PartialEq for LocalChain<D> {
69
69
}
70
70
}
71
71
72
- impl < D > ChainOracle for LocalChain < D > {
72
+ impl < D > ChainOracle for LocalChain < D >
73
+ where
74
+ D : ToBlockHash + Copy ,
75
+ {
73
76
type Error = Infallible ;
74
77
75
78
fn is_block_in_chain (
@@ -83,10 +86,18 @@ impl<D> ChainOracle for LocalChain<D> {
83
86
Some ( cp) if cp. hash ( ) == chain_tip. hash => cp,
84
87
_ => return Ok ( None ) ,
85
88
} ;
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 ) ) ;
89
92
}
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 )
90
101
}
91
102
92
103
fn get_chain_tip ( & self ) -> Result < BlockId , Self :: Error > {
@@ -653,59 +664,132 @@ where
653
664
}
654
665
}
655
666
( 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().
665
718
return Err ( CannotConnectError {
666
- try_include_height : prev_orig . height ( ) ,
719
+ try_include_height : o . height ( ) ,
667
720
} ) ;
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 ;
668
727
}
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 ;
669
734
}
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 ( ) ;
677
761
} 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 ( ) ;
683
765
}
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 ( ) ;
684
770
}
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 ;
685
777
} 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.
688
779
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 ( ) ;
693
781
}
694
- prev_update = curr_update. take ( ) ;
695
- prev_orig = curr_orig. take ( ) ;
696
782
}
697
783
( None , None ) => {
698
784
break ;
699
785
}
700
786
_ => {
701
- unreachable ! ( "compiler cannot tell that everything has been covered " )
787
+ unreachable ! ( "should have been handled above " )
702
788
}
703
789
}
704
790
}
705
791
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
709
793
if !prev_orig_was_invalidated && !point_of_agreement_found {
710
794
if let Some ( prev_orig) = prev_orig {
711
795
return Err ( CannotConnectError {
0 commit comments