@@ -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