@@ -557,18 +557,38 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
557557 ) ;
558558 }
559559
560- // From the DAG compute the list of roots that descend from finalized root up to the
561- // split slot.
562-
563- let finalized_and_descendant_block_roots = HashSet :: < Hash256 > :: from_iter (
564- std:: iter:: once ( new_finalized_checkpoint. root ) . chain (
565- // Note: The sanity check above for existance of at least one summary with
566- // new_finalized_checkpoint.root should ensure that this call never errors
567- block_summaries_dag
568- . descendant_block_roots_of ( & new_finalized_checkpoint. root )
569- . map_err ( PruningError :: SummariesDagError ) ?,
570- ) ,
571- ) ;
560+ // `new_finalized_state_hash` is the *state at the slot of the finalized epoch*,
561+ // rather than the state of the latest finalized block. These two values will only
562+ // differ when the first slot of the finalized epoch is a skip slot.
563+ let finalized_and_descendant_state_roots_of_finalized_checkpoint =
564+ HashSet :: < Hash256 > :: from_iter (
565+ std:: iter:: once ( new_finalized_state_hash) . chain (
566+ state_summaries_dag
567+ . descendants_of ( & new_finalized_state_hash)
568+ . map_err ( PruningError :: SummariesDagError ) ?,
569+ ) ,
570+ ) ;
571+
572+ // Collect all `latest_block_roots` of the
573+ // finalized_and_descendant_state_roots_of_finalized_checkpoint set. Includes the finalized
574+ // block as `new_finalized_state_hash` always has a latest block root the finalized block.
575+ let finalized_and_descendant_block_roots_of_finalized_checkpoint =
576+ HashSet :: < Hash256 > :: from_iter (
577+ finalized_and_descendant_state_roots_of_finalized_checkpoint
578+ . iter ( )
579+ . map ( |state_root| {
580+ // `.get()` should never error, we just constructed
581+ // finalized_and_descendant_state_roots_of_finalized_checkpoint from the
582+ // state_summaries_dag
583+ let summary = state_summaries_dag. get ( state_root) . ok_or (
584+ PruningError :: SummariesDagError (
585+ SummariesDagError :: MissingStateSummary ( * state_root) ,
586+ ) ,
587+ ) ?;
588+ Ok ( summary. latest_block_root )
589+ } )
590+ . collect :: < Result < Vec < _ > , PruningError > > ( ) ?,
591+ ) ;
572592
573593 // Note: ancestors_of includes the finalized state root
574594 let newly_finalized_state_summaries = state_summaries_dag
@@ -603,34 +623,49 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
603623 let mut blocks_to_prune: HashSet < Hash256 > = HashSet :: new ( ) ;
604624 let mut states_to_prune: HashSet < ( Slot , Hash256 ) > = HashSet :: new ( ) ;
605625
606- for ( slot, summaries) in state_summaries_dag. summaries_by_slot_ascending ( ) {
626+ // Consider the following block tree where we finalize block `[0]` at the checkpoint `(f)`.
627+ // There's a block `[3]` that descendends from the finalized block but NOT from the
628+ // finalized checkpoint. The block tree rooted in `[3]` conflicts with finality and must be
629+ // pruned. Therefore we collect all state summaries descendant of `(f)`.
630+ //
631+ // finalize epoch boundary
632+ // | /-------[2]-----
633+ // [0]-------|--(f)--[1]----------
634+ // \---[3]--|-----------------[4]
635+ // |
636+
637+ for ( _, summaries) in state_summaries_dag. summaries_by_slot_ascending ( ) {
607638 for ( state_root, summary) in summaries {
608- let should_prune =
609- if finalized_and_descendant_block_roots. contains ( & summary. latest_block_root ) {
610- // Keep this state is the post state of a viable head, or a state advance from a
611- // viable head.
612- false
613- } else {
614- // Everything else, prune
615- true
616- } ;
639+ let should_prune = if finalized_and_descendant_state_roots_of_finalized_checkpoint
640+ . contains ( & state_root)
641+ {
642+ // This state is a viable descendant of the finalized checkpoint, so does not
643+ // conflict with finality and can be built on or become a head
644+ false
645+ } else {
646+ // Everything else, prune
647+ true
648+ } ;
617649
618650 if should_prune {
619651 // States are migrated into the cold DB in the migrate step. All hot states
620652 // prior to finalized can be pruned from the hot DB columns
621- states_to_prune. insert ( ( slot, state_root) ) ;
653+ states_to_prune. insert ( ( summary . slot , state_root) ) ;
622654 }
623655 }
624656 }
625657
626658 for ( block_root, slot) in block_summaries_dag. iter ( ) {
627659 // Blocks both finalized and unfinalized are in the same DB column. We must only
628- // prune blocks from abandoned forks. Deriving block pruning from state
629- // summaries is tricky since now we keep some hot state summaries beyond
630- // finalization. We will only prune blocks that still have an associated hot
631- // state summary, are above prior finalization and not in the canonical chain.
632- let should_prune = if finalized_and_descendant_block_roots. contains ( & block_root) {
633- // Keep unfinalized blocks descendant of finalized + finalized block itself
660+ // prune blocks from abandoned forks. Note that block pruning and state pruning differ.
661+ // The blocks DB column is shared for hot and cold data, while the states have different
662+ // columns. Thus, we only prune unviable blocks or from abandoned forks.
663+ let should_prune = if finalized_and_descendant_block_roots_of_finalized_checkpoint
664+ . contains ( & block_root)
665+ {
666+ // Keep unfinalized blocks descendant of finalized checkpoint + finalized block itself
667+ // Note that we anchor this set on the finalied checkpoint instead of the finalized
668+ // block. A diagram above shows a relevant example.
634669 false
635670 } else if newly_finalized_block_roots. contains ( & block_root) {
636671 // Keep recently finalized blocks
@@ -664,7 +699,9 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> BackgroundMigrator<E, Ho
664699 "newly_finalized_state_roots" => newly_finalized_state_roots. len( ) ,
665700 "newly_finalized_states_min_slot" => newly_finalized_states_min_slot,
666701 "state_summaries_count" => state_summaries_dag. summaries_count( ) ,
667- "finalized_and_descendant_block_roots" => finalized_and_descendant_block_roots. len( ) ,
702+ "state_summaries_dag_roots" => ?state_summaries_dag_roots,
703+ "finalized_and_descendant_state_roots_of_finalized_checkpoint" => finalized_and_descendant_state_roots_of_finalized_checkpoint. len( ) ,
704+ "finalized_and_descendant_state_roots_of_finalized_checkpoint" => finalized_and_descendant_state_roots_of_finalized_checkpoint. len( ) ,
668705 "blocks_to_prune_count" => blocks_to_prune. len( ) ,
669706 "states_to_prune_count" => states_to_prune. len( ) ,
670707 "blocks_to_prune" => ?blocks_to_prune,
0 commit comments