Skip to content

Commit a3060ad

Browse files
committed
Prune at the end of the sync
1 parent e4973aa commit a3060ad

File tree

2 files changed

+33
-30
lines changed

2 files changed

+33
-30
lines changed

crates/blockchain/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,11 @@ impl BlockChainServer {
299299
while let Some(block) = queue.pop_front() {
300300
self.process_or_pend_block(block, &mut queue);
301301
}
302+
303+
// Prune old states and blocks AFTER the entire cascade completes.
304+
// Running this mid-cascade would delete states that pending children
305+
// still need, causing re-processing loops when fallback pruning is active.
306+
self.store.prune_old_data();
302307
}
303308

304309
/// Try to process a single block. If its parent state is missing, store it

crates/storage/src/store.rs

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)