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