@@ -1395,4 +1395,78 @@ mod tests {
13951395 }
13961396 }
13971397 }
1398+
1399+ /// Test that checkpoint building from `recent_history` handles reorgs.
1400+ ///
1401+ /// Scenario: wallet synced to height 103. A 3-block reorg replaces blocks
1402+ /// 101-103 with new ones, and `recent_history` returns {97..=106} with
1403+ /// new hashes at heights 101-103.
1404+ ///
1405+ /// The checkpoint must reflect the reorged chain: new hashes at 101-103,
1406+ /// pre-reorg blocks at ≤100 preserved, new blocks 104-106 present.
1407+ #[ test]
1408+ fn checkpoint_building_handles_reorg ( ) {
1409+ use bdk_chain:: local_chain:: LocalChain ;
1410+ use bdk_chain:: { BlockId , CheckPoint } ;
1411+ use bitcoin:: BlockHash ;
1412+ use std:: collections:: BTreeMap ;
1413+
1414+ fn hash ( seed : u32 ) -> BlockHash {
1415+ use bitcoin:: hashes:: { sha256d, Hash , HashEngine } ;
1416+ let mut engine = sha256d:: Hash :: engine ( ) ;
1417+ engine. input ( & seed. to_le_bytes ( ) ) ;
1418+ BlockHash :: from_raw_hash ( sha256d:: Hash :: from_engine ( engine) )
1419+ }
1420+
1421+ let genesis = BlockId { height : 0 , hash : hash ( 0 ) } ;
1422+
1423+ // Wallet checkpoint: 0 → 100 → 101 → 102 → 103
1424+ let wallet_cp = CheckPoint :: from_block_ids ( [
1425+ genesis,
1426+ BlockId { height : 100 , hash : hash ( 100 ) } ,
1427+ BlockId { height : 101 , hash : hash ( 101 ) } ,
1428+ BlockId { height : 102 , hash : hash ( 102 ) } ,
1429+ BlockId { height : 103 , hash : hash ( 103 ) } ,
1430+ ] )
1431+ . unwrap ( ) ;
1432+
1433+ // recent_history after reorg: 97-106, heights 101-103 have NEW hashes.
1434+ let recent_history: BTreeMap < u32 , BlockHash > = ( 97 ..=106 )
1435+ . map ( |h| {
1436+ let seed = if ( 101 ..=103 ) . contains ( & h) { h + 1000 } else { h } ;
1437+ ( h, hash ( seed) )
1438+ } )
1439+ . collect ( ) ;
1440+
1441+ // Build checkpoint using the same logic as sync_onchain_wallet.
1442+ let mut cp = wallet_cp;
1443+ for ( height, block_hash) in & recent_history {
1444+ if * height > cp. height ( ) {
1445+ let block_id = BlockId { height : * height, hash : * block_hash } ;
1446+ cp = cp. push ( block_id) . unwrap_or_else ( |old| old) ;
1447+ }
1448+ }
1449+
1450+ // Reorged blocks must have the NEW hashes.
1451+ assert_eq ! ( cp. height( ) , 106 ) ;
1452+ assert_eq ! (
1453+ cp. get( 101 ) . expect( "height 101 must exist" ) . hash( ) ,
1454+ hash( 1101 ) ,
1455+ "block 101 must have the reorged hash"
1456+ ) ;
1457+ assert_eq ! ( cp. get( 102 ) . expect( "height 102 must exist" ) . hash( ) , hash( 1102 ) ) ;
1458+ assert_eq ! ( cp. get( 103 ) . expect( "height 103 must exist" ) . hash( ) , hash( 1103 ) ) ;
1459+
1460+ // Pre-reorg blocks are preserved.
1461+ assert_eq ! ( cp. get( 100 ) . expect( "height 100 must exist" ) . hash( ) , hash( 100 ) ) ;
1462+
1463+ // New blocks above the reorg are present.
1464+ assert ! ( cp. get( 104 ) . is_some( ) ) ;
1465+ assert ! ( cp. get( 105 ) . is_some( ) ) ;
1466+ assert ! ( cp. get( 106 ) . is_some( ) ) ;
1467+
1468+ // The checkpoint must connect cleanly to a LocalChain.
1469+ let ( mut chain, _) = LocalChain :: from_genesis_hash ( genesis. hash ) ;
1470+ chain. apply_update ( cp) . expect ( "checkpoint must connect to chain" ) ;
1471+ }
13981472}
0 commit comments