@@ -470,50 +470,41 @@ impl Store {
470470 batch. put_batch ( Table :: Metadata , entries) . expect ( "put" ) ;
471471 batch. commit ( ) . expect ( "commit" ) ;
472472
473- // Prune after successful checkpoint update
473+ // Lightweight pruning that should happen immediately on finalization advance:
474+ // live chain index, signatures, and attestation data. These are cheap and
475+ // affect fork choice correctness (live chain) or attestation processing.
476+ // Heavy state/block pruning is deferred to prune_old_data().
474477 if let Some ( finalized) = checkpoints. finalized
475478 && finalized. slot > old_finalized_slot
476479 {
477480 let pruned_chain = self . prune_live_chain ( finalized. slot ) ;
478-
479- // Prune signatures and attestation data for finalized slots
480481 let pruned_sigs = self . prune_gossip_signatures ( finalized. slot ) ;
481482 let pruned_att_data = self . prune_attestation_data_by_root ( finalized. slot ) ;
482- // Prune old states before blocks: state pruning uses headers for slot lookup
483- let protected_roots = [ finalized. root , self . latest_justified ( ) . root ] ;
484- let pruned_states = self . prune_old_states ( & protected_roots) ;
485- let pruned_blocks = self . prune_old_blocks ( & protected_roots) ;
486-
487- if pruned_chain > 0
488- || pruned_sigs > 0
489- || pruned_att_data > 0
490- || pruned_states > 0
491- || pruned_blocks > 0
492- {
483+
484+ if pruned_chain > 0 || pruned_sigs > 0 || pruned_att_data > 0 {
493485 info ! (
494486 finalized_slot = finalized. slot,
495487 pruned_chain,
496488 pruned_sigs,
497489 pruned_att_data,
498- pruned_states,
499- pruned_blocks,
500490 "Pruned finalized data"
501491 ) ;
502492 }
503- } else {
504- // Fallback pruning when finalization is stalled.
505- // When finalization doesn't advance, the normal pruning path above never
506- // triggers. Prune old states and blocks on every head update to keep
507- // storage bounded. The prune methods are no-ops when within retention limits.
508- let protected_roots = [ self . latest_finalized ( ) . root , self . latest_justified ( ) . root ] ;
509- let pruned_states = self . prune_old_states ( & protected_roots) ;
510- let pruned_blocks = self . prune_old_blocks ( & protected_roots) ;
511- if pruned_states > 0 || pruned_blocks > 0 {
512- info ! (
513- pruned_states,
514- pruned_blocks, "Fallback pruning (finalization stalled)"
515- ) ;
516- }
493+ }
494+ }
495+
496+ /// Prune old states and blocks to keep storage bounded.
497+ ///
498+ /// This is separated from `update_checkpoints` so callers can defer heavy
499+ /// pruning until after a batch of blocks has been fully processed. Running
500+ /// this mid-cascade would delete states that pending children still need,
501+ /// causing infinite re-processing loops when fallback pruning is active.
502+ pub fn prune_old_data ( & mut self ) {
503+ let protected_roots = [ self . latest_finalized ( ) . root , self . latest_justified ( ) . root ] ;
504+ let pruned_states = self . prune_old_states ( & protected_roots) ;
505+ let pruned_blocks = self . prune_old_blocks ( & protected_roots) ;
506+ if pruned_states > 0 || pruned_blocks > 0 {
507+ info ! ( pruned_states, pruned_blocks, "Pruned old states and blocks" ) ;
517508 }
518509 }
519510
@@ -1486,6 +1477,12 @@ mod tests {
14861477 let head_root = root ( total_states as u64 - 1 ) ;
14871478 store. update_checkpoints ( ForkCheckpoints :: head_only ( head_root) ) ;
14881479
1480+ // update_checkpoints no longer prunes states/blocks inline — the caller
1481+ // must invoke prune_old_data() separately (after a block cascade completes).
1482+ assert_eq ! ( count_entries( backend. as_ref( ) , Table :: States ) , total_states) ;
1483+
1484+ store. prune_old_data ( ) ;
1485+
14891486 // 905 headers total. Top 900 by slot are kept in the retention window,
14901487 // leaving 5 candidates. 2 are protected (finalized + justified),
14911488 // so 3 are pruned → 905 - 3 = 902 states remaining.
@@ -1530,6 +1527,7 @@ mod tests {
15301527 // Use the last inserted root as head
15311528 let head_root = root ( STATES_TO_KEEP as u64 - 1 ) ;
15321529 store. update_checkpoints ( ForkCheckpoints :: head_only ( head_root) ) ;
1530+ store. prune_old_data ( ) ;
15331531
15341532 // Nothing should be pruned (within retention window)
15351533 assert_eq ! (
0 commit comments