@@ -393,11 +393,7 @@ fn check_pow_limit_and_continuity(
393393 let retarget_interval = handles. network . retarget_interval ( ) ;
394394 let is_retarget = retarget_interval != 0 && height. is_multiple_of ( retarget_interval) ;
395395 if is_retarget {
396- // Retarget heights compute a new target from the last 2016 blocks'
397- // timespan; full computation is deferred to a follow-up. For now
398- // we already verified `declared <= max_target` above, so we let
399- // any retarget-height nBits through.
400- return Ok ( ( ) ) ;
396+ return check_daa_retarget ( handles, block, height, retarget_interval) ;
401397 }
402398
403399 // Non-retarget: look up the parent header via the BlockTree.
@@ -421,6 +417,81 @@ fn check_pow_limit_and_continuity(
421417 Ok ( ( ) )
422418}
423419
420+ fn check_daa_retarget (
421+ handles : & ApplyHandles ,
422+ block : & bitcoin:: Block ,
423+ height : u32 ,
424+ retarget_interval : u32 ,
425+ ) -> core:: result:: Result < ( ) , ApplyError > {
426+ let prior_tip = handles. chain_tip . load_full ( ) ;
427+ let Some ( prior_tip) = prior_tip else {
428+ return Ok ( ( ) ) ;
429+ } ;
430+
431+ let tree = handles. block_tree . read ( ) ;
432+ let Some ( anchor_height) = height. checked_sub ( retarget_interval) else {
433+ return Ok ( ( ) ) ;
434+ } ;
435+ let Some ( anchor_id) = tree. node_at_height_from ( prior_tip. tip_id , anchor_height) else {
436+ return Ok ( ( ) ) ;
437+ } ;
438+ let Ok ( anchor_node) = tree. node ( anchor_id) else {
439+ return Ok ( ( ) ) ;
440+ } ;
441+ let Ok ( prev_node) = tree. node ( prior_tip. tip_id ) else {
442+ return Ok ( ( ) ) ;
443+ } ;
444+
445+ let actual_timespan = prev_node
446+ . header
447+ . time
448+ . saturating_sub ( anchor_node. header . time ) ;
449+ let expected_timespan = retarget_interval. saturating_mul ( 600 ) ;
450+ if expected_timespan == 0 {
451+ return Ok ( ( ) ) ;
452+ }
453+
454+ let min_timespan = expected_timespan / 4 ;
455+ let max_timespan = expected_timespan. saturating_mul ( 4 ) ;
456+ let actual_clamped = actual_timespan. clamp ( min_timespan, max_timespan) ;
457+
458+ let prev_target_be = prev_node. header . target ( ) . to_be_bytes ( ) ;
459+ let prev_target = bitcoin_rs_chain:: node:: ChainWork :: from_be_bytes ( prev_target_be) ;
460+ let actual_u256 = bitcoin_rs_chain:: node:: ChainWork :: from ( actual_clamped) ;
461+ let expected_u256 = bitcoin_rs_chain:: node:: ChainWork :: from ( expected_timespan) ;
462+ let max_target = handles. network . max_target ( ) ;
463+ let quotient = prev_target / expected_u256;
464+ let remainder = prev_target % expected_u256;
465+ let Some ( scaled_quotient) = quotient. checked_mul ( actual_u256) else {
466+ return compare_retarget_bits ( block, height, max_target) ;
467+ } ;
468+ let scaled_remainder = remainder. saturating_mul ( actual_u256) / expected_u256;
469+ let new_target_raw = scaled_quotient. saturating_add ( scaled_remainder) ;
470+ let new_target = new_target_raw. min ( max_target) ;
471+ compare_retarget_bits ( block, height, new_target)
472+ }
473+
474+ fn compare_retarget_bits (
475+ block : & bitcoin:: Block ,
476+ height : u32 ,
477+ expected_target : bitcoin_rs_chain:: node:: ChainWork ,
478+ ) -> core:: result:: Result < ( ) , ApplyError > {
479+ let expected = bitcoin:: Target :: from_be_bytes ( expected_target. to_be_bytes :: < 32 > ( ) )
480+ . to_compact_lossy ( )
481+ . to_consensus ( ) ;
482+ let actual = block. header . bits . to_consensus ( ) ;
483+
484+ if actual != expected {
485+ return Err ( ApplyError :: NbitsNonRetargetMismatch {
486+ actual,
487+ expected,
488+ height,
489+ } ) ;
490+ }
491+
492+ Ok ( ( ) )
493+ }
494+
424495fn build_utxo_changes (
425496 block : & bitcoin:: Block ,
426497 height : u32 ,
0 commit comments