@@ -11,7 +11,7 @@ use bdk_chain::{
1111 BlockId ,
1212} ;
1313use bdk_testenv:: { chain_update, hash, local_chain} ;
14- use bitcoin:: { block:: Header , hashes:: Hash , BlockHash } ;
14+ use bitcoin:: { block:: Header , hashes:: Hash , BlockHash , CompactTarget , TxMerkleNode } ;
1515use proptest:: prelude:: * ;
1616
1717#[ derive( Debug ) ]
@@ -474,6 +474,151 @@ fn local_chain_insert_header() {
474474 }
475475}
476476
477+ // TODO: Docs.
478+ #[ test]
479+ fn test_merge_chains ( ) {
480+ fn header ( prev_blockhash : bitcoin:: BlockHash , nonce : u32 ) -> Header {
481+ Header {
482+ version : bitcoin:: block:: Version :: default ( ) ,
483+ prev_blockhash,
484+ merkle_root : TxMerkleNode :: all_zeros ( ) ,
485+ time : 0 ,
486+ bits : CompactTarget :: default ( ) ,
487+ nonce,
488+ }
489+ }
490+
491+ fn local_chain ( blocks : Vec < ( u32 , Header ) > ) -> LocalChain < Header > {
492+ LocalChain :: from_blocks ( blocks. into_iter ( ) . collect :: < BTreeMap < _ , _ > > ( ) )
493+ . expect ( "chain must have genesis block" )
494+ }
495+
496+ fn update_chain ( blocks : & [ ( u32 , Header ) ] ) -> CheckPoint < Header > {
497+ CheckPoint :: from_blocks ( blocks. iter ( ) . copied ( ) ) . expect ( "checkpoint must be valid" )
498+ }
499+
500+ let a = header ( hash ! ( "genesis" ) , 0 ) ;
501+ let b = header ( a. block_hash ( ) , 0 ) ;
502+ let c = header ( b. block_hash ( ) , 0 ) ;
503+ let d = header ( c. block_hash ( ) , 0 ) ;
504+
505+ // Set a different `nonce` for conflicting `Header`s to ensure different `BlockHash`.
506+ let c_conflict = header ( b. block_hash ( ) , 1 ) ;
507+ let _d_conflict = header ( c_conflict. block_hash ( ) , 1 ) ;
508+
509+ struct TestCase {
510+ name : & ' static str ,
511+ updates : Vec < CheckPoint < Header > > ,
512+ invalidate_heights : Vec < u32 > ,
513+ expected_placeholder_heights : Vec < u32 > ,
514+ expected_chain : LocalChain < Header > ,
515+ }
516+
517+ let test_cases = [
518+ // Test case 1: Create a placeholder for B via C.
519+ TestCase {
520+ name : "insert_placeholder" ,
521+ updates : vec ! [ update_chain( & [ ( 0 , a) , ( 2 , c) ] ) ] ,
522+ invalidate_heights : vec ! [ ] ,
523+ expected_placeholder_heights : vec ! [ 1 ] ,
524+ expected_chain : local_chain ( vec ! [ ( 0 , a) , ( 2 , c) ] ) ,
525+ } ,
526+ // Test cast 2: Create a placeholder for B via C, then update provides conflicting C'.
527+ TestCase {
528+ name : "conflict_at_tip_keeps_placeholder" ,
529+ updates : vec ! [
530+ update_chain( & [ ( 0 , a) , ( 2 , c) ] ) ,
531+ update_chain( & [ ( 2 , c_conflict) ] ) ,
532+ ] ,
533+ invalidate_heights : vec ! [ ] ,
534+ expected_placeholder_heights : vec ! [ 1 ] ,
535+ expected_chain : local_chain ( vec ! [ ( 0 , a) , ( 1 , b) , ( 2 , c_conflict) ] ) ,
536+ } ,
537+ // Test case 3: Create placeholder for C via D.
538+ TestCase {
539+ name : "conflict_at_filled_height" ,
540+ updates : vec ! [ update_chain( & [ ( 0 , a) , ( 3 , d) ] ) ] ,
541+ invalidate_heights : vec ! [ ] ,
542+ expected_placeholder_heights : vec ! [ 2 ] ,
543+ expected_chain : local_chain ( vec ! [ ( 0 , a) , ( 3 , d) ] ) ,
544+ } ,
545+ // Test case 4: Create placeholder for C via D, then insert conflicting C' which should
546+ // drop D and replace C.
547+ TestCase {
548+ name : "conflict_at_filled_height" ,
549+ updates : vec ! [
550+ update_chain( & [ ( 0 , a) , ( 3 , d) ] ) ,
551+ update_chain( & [ ( 0 , a) , ( 2 , c_conflict) ] ) ,
552+ ] ,
553+ invalidate_heights : vec ! [ ] ,
554+ expected_placeholder_heights : vec ! [ ] ,
555+ expected_chain : local_chain ( vec ! [ ( 0 , a) , ( 2 , c_conflict) ] ) ,
556+ } ,
557+ // Test case 5: Create placeholder for B via C, then invalidate C.
558+ TestCase {
559+ name : "invalidate_tip_falls_back" ,
560+ updates : vec ! [ update_chain( & [ ( 0 , a) , ( 2 , c) ] ) ] ,
561+ invalidate_heights : vec ! [ 2 ] ,
562+ expected_placeholder_heights : vec ! [ 1 ] ,
563+ expected_chain : local_chain ( vec ! [ ( 0 , a) ] ) ,
564+ } ,
565+ // Test case 6: Create placeholder for C via D, then insert D' which has `prev_blockhash`
566+ // that does not point to C. TODO: Handle error?
567+ // TestCase {
568+ // name: "expected_error",
569+ // updates: vec![
570+ // update_chain(&[(0, a), (3, d)]),
571+ // update_chain(&[(3, d_conflict)]),
572+ // ],
573+ // invalidate_heights: vec![],
574+ // expected_placeholder_heights: vec![],
575+ // expected_chain: local_chain(vec![(0, a), (3, d)]),
576+ // },
577+ ] ;
578+
579+ for ( i, t) in test_cases. into_iter ( ) . enumerate ( ) {
580+ let mut chain = local_chain ( vec ! [ ( 0 , a) ] ) ;
581+ for upd in t. updates {
582+ chain. apply_update ( upd) . expect ( "update should apply" ) ;
583+
584+ for & height in & t. expected_placeholder_heights {
585+ let has_placeholder = chain
586+ . tip ( )
587+ . iter ( )
588+ . any ( |cp| cp. height ( ) == height && cp. data_ref ( ) . is_none ( ) ) ;
589+ assert ! (
590+ has_placeholder,
591+ "[{}] {}: expected placeholder at height {}" ,
592+ i, t. name, height
593+ ) ;
594+ }
595+
596+ if !t. invalidate_heights . is_empty ( ) {
597+ let cs: ChangeSet < Header > = t
598+ . invalidate_heights
599+ . iter ( )
600+ . copied ( )
601+ . map ( |h| ( h, None ) )
602+ . collect ( ) ;
603+ chain. apply_changeset ( & cs) . expect ( "changeset should apply" ) ;
604+ }
605+
606+ // Ensure we never end up with a placeholder tip.
607+ assert ! (
608+ chain. tip( ) . data_ref( ) . is_some( ) ,
609+ "[{}] {}: tip must always be materialized" ,
610+ i,
611+ t. name
612+ ) ;
613+ }
614+ assert_eq ! (
615+ chain, t. expected_chain,
616+ "[{}] {}: unexpected final chain" ,
617+ i, t. name
618+ ) ;
619+ }
620+ }
621+
477622#[ test]
478623fn local_chain_disconnect_from ( ) {
479624 struct TestCase {
0 commit comments