mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-07 00:42:42 +00:00
Drop head tracker for summaries DAG (#6744)
The head tracker is a persisted piece of state that must be kept in sync with the fork-choice. It has been a source of pruning issues in the past, so we want to remove it - see https://github.com/sigp/lighthouse/issues/1785 When implementing tree-states in the hot DB we have to change the pruning routine (more details below) so we want to do those changes first in isolation. - see https://github.com/sigp/lighthouse/issues/6580 - If you want to see the full feature of tree-states hot https://github.com/dapplion/lighthouse/pull/39 Closes https://github.com/sigp/lighthouse/issues/1785 **Current DB migration routine** - Locate abandoned heads with head tracker - Use a roots iterator to collect the ancestors of those heads can be pruned - Delete those abandoned blocks / states - Migrate the newly finalized chain to the freezer In summary, it computes what it has to delete and keeps the rest. Then it migrates data to the freezer. If the abandoned forks routine has a bug it can break the freezer migration. **Proposed migration routine (this PR)** - Migrate the newly finalized chain to the freezer - Load all state summaries from disk - From those, just knowing the head and finalized block compute two sets: (1) descendants of finalized (2) newly finalized chain - Iterate all summaries, if a summary does not belong to set (1) or (2), delete This strategy is more sound as it just checks what's there in the hot DB, computes what it has to keep and deletes the rest. Because it does not rely and 3rd pieces of data we can drop the head tracker and pruning checkpoint. Since the DB migration happens **first** now, as long as the computation of the sets to keep is correct we won't have pruning issues.
This commit is contained in:
@@ -33,7 +33,6 @@ use crate::events::ServerSentEventHandler;
|
||||
use crate::execution_payload::{get_execution_payload, NotifyExecutionLayer, PreparePayloadHandle};
|
||||
use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult};
|
||||
use crate::graffiti_calculator::GraffitiCalculator;
|
||||
use crate::head_tracker::{HeadTracker, HeadTrackerReader, SszHeadTracker};
|
||||
use crate::kzg_utils::reconstruct_blobs;
|
||||
use crate::light_client_finality_update_verification::{
|
||||
Error as LightClientFinalityUpdateError, VerifiedLightClientFinalityUpdate,
|
||||
@@ -57,7 +56,7 @@ use crate::observed_block_producers::ObservedBlockProducers;
|
||||
use crate::observed_data_sidecars::ObservedDataSidecars;
|
||||
use crate::observed_operations::{ObservationOutcome, ObservedOperations};
|
||||
use crate::observed_slashable::ObservedSlashable;
|
||||
use crate::persisted_beacon_chain::{PersistedBeaconChain, DUMMY_CANONICAL_HEAD_BLOCK_ROOT};
|
||||
use crate::persisted_beacon_chain::PersistedBeaconChain;
|
||||
use crate::persisted_fork_choice::PersistedForkChoice;
|
||||
use crate::pre_finalization_cache::PreFinalizationBlockCache;
|
||||
use crate::shuffling_cache::{BlockShufflingIds, ShufflingCache};
|
||||
@@ -454,8 +453,6 @@ pub struct BeaconChain<T: BeaconChainTypes> {
|
||||
/// A handler for events generated by the beacon chain. This is only initialized when the
|
||||
/// HTTP server is enabled.
|
||||
pub event_handler: Option<ServerSentEventHandler<T::EthSpec>>,
|
||||
/// Used to track the heads of the beacon chain.
|
||||
pub(crate) head_tracker: Arc<HeadTracker>,
|
||||
/// Caches the attester shuffling for a given epoch and shuffling key root.
|
||||
pub shuffling_cache: RwLock<ShufflingCache>,
|
||||
/// A cache of eth1 deposit data at epoch boundaries for deposit finalization
|
||||
@@ -607,57 +604,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Persists the head tracker and fork choice.
|
||||
/// Return a database operation for writing the `PersistedBeaconChain` to disk.
|
||||
///
|
||||
/// We do it atomically even though no guarantees need to be made about blocks from
|
||||
/// the head tracker also being present in fork choice.
|
||||
pub fn persist_head_and_fork_choice(&self) -> Result<(), Error> {
|
||||
let mut batch = vec![];
|
||||
|
||||
let _head_timer = metrics::start_timer(&metrics::PERSIST_HEAD);
|
||||
|
||||
// Hold a lock to head_tracker until it has been persisted to disk. Otherwise there's a race
|
||||
// condition with the pruning thread which can result in a block present in the head tracker
|
||||
// but absent in the DB. This inconsistency halts pruning and dramastically increases disk
|
||||
// size. Ref: https://github.com/sigp/lighthouse/issues/4773
|
||||
let head_tracker = self.head_tracker.0.read();
|
||||
batch.push(self.persist_head_in_batch(&head_tracker));
|
||||
|
||||
let _fork_choice_timer = metrics::start_timer(&metrics::PERSIST_FORK_CHOICE);
|
||||
batch.push(self.persist_fork_choice_in_batch());
|
||||
|
||||
self.store.hot_db.do_atomically(batch)?;
|
||||
drop(head_tracker);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return a `PersistedBeaconChain` without reference to a `BeaconChain`.
|
||||
pub fn make_persisted_head(
|
||||
genesis_block_root: Hash256,
|
||||
head_tracker_reader: &HeadTrackerReader,
|
||||
) -> PersistedBeaconChain {
|
||||
PersistedBeaconChain {
|
||||
_canonical_head_block_root: DUMMY_CANONICAL_HEAD_BLOCK_ROOT,
|
||||
genesis_block_root,
|
||||
ssz_head_tracker: SszHeadTracker::from_map(head_tracker_reader),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a database operation for writing the beacon chain head to disk.
|
||||
pub fn persist_head_in_batch(
|
||||
&self,
|
||||
head_tracker_reader: &HeadTrackerReader,
|
||||
) -> KeyValueStoreOp {
|
||||
Self::persist_head_in_batch_standalone(self.genesis_block_root, head_tracker_reader)
|
||||
}
|
||||
|
||||
pub fn persist_head_in_batch_standalone(
|
||||
genesis_block_root: Hash256,
|
||||
head_tracker_reader: &HeadTrackerReader,
|
||||
) -> KeyValueStoreOp {
|
||||
Self::make_persisted_head(genesis_block_root, head_tracker_reader)
|
||||
.as_kv_store_op(BEACON_CHAIN_DB_KEY)
|
||||
/// These days the `PersistedBeaconChain` is only used to store the genesis block root, so it
|
||||
/// should only ever be written once at startup. It used to be written more frequently, but
|
||||
/// this is no longer necessary.
|
||||
pub fn persist_head_in_batch_standalone(genesis_block_root: Hash256) -> KeyValueStoreOp {
|
||||
PersistedBeaconChain { genesis_block_root }.as_kv_store_op(BEACON_CHAIN_DB_KEY)
|
||||
}
|
||||
|
||||
/// Load fork choice from disk, returning `None` if it isn't found.
|
||||
@@ -1450,12 +1403,13 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
///
|
||||
/// Returns `(block_root, block_slot)`.
|
||||
pub fn heads(&self) -> Vec<(Hash256, Slot)> {
|
||||
self.head_tracker.heads()
|
||||
}
|
||||
|
||||
/// Only used in tests.
|
||||
pub fn knows_head(&self, block_hash: &SignedBeaconBlockHash) -> bool {
|
||||
self.head_tracker.contains_head((*block_hash).into())
|
||||
self.canonical_head
|
||||
.fork_choice_read_lock()
|
||||
.proto_array()
|
||||
.heads_descended_from_finalization::<T::EthSpec>()
|
||||
.iter()
|
||||
.map(|node| (node.root, node.slot))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the `BeaconState` at the given slot.
|
||||
@@ -1735,8 +1689,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
let notif = ManualFinalizationNotification {
|
||||
state_root: state_root.into(),
|
||||
checkpoint,
|
||||
head_tracker: self.head_tracker.clone(),
|
||||
genesis_block_root: self.genesis_block_root,
|
||||
};
|
||||
|
||||
self.store_migrator.process_manual_finalization(notif);
|
||||
@@ -3762,7 +3714,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
state,
|
||||
parent_block,
|
||||
parent_eth1_finalization_data,
|
||||
confirmed_state_roots,
|
||||
consensus_context,
|
||||
} = import_data;
|
||||
|
||||
@@ -3786,7 +3737,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
block,
|
||||
block_root,
|
||||
state,
|
||||
confirmed_state_roots,
|
||||
payload_verification_outcome.payload_verification_status,
|
||||
parent_block,
|
||||
parent_eth1_finalization_data,
|
||||
@@ -3824,7 +3774,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
signed_block: AvailableBlock<T::EthSpec>,
|
||||
block_root: Hash256,
|
||||
mut state: BeaconState<T::EthSpec>,
|
||||
confirmed_state_roots: Vec<Hash256>,
|
||||
payload_verification_status: PayloadVerificationStatus,
|
||||
parent_block: SignedBlindedBeaconBlock<T::EthSpec>,
|
||||
parent_eth1_finalization_data: Eth1FinalizationData,
|
||||
@@ -4012,11 +3961,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
|
||||
let block = signed_block.message();
|
||||
let db_write_timer = metrics::start_timer(&metrics::BLOCK_PROCESSING_DB_WRITE);
|
||||
ops.extend(
|
||||
confirmed_state_roots
|
||||
.into_iter()
|
||||
.map(StoreOp::DeleteStateTemporaryFlag),
|
||||
);
|
||||
ops.push(StoreOp::PutBlock(block_root, signed_block.clone()));
|
||||
ops.push(StoreOp::PutState(block.state_root(), &state));
|
||||
|
||||
@@ -4043,9 +3987,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
// about it.
|
||||
let block_time_imported = timestamp_now();
|
||||
|
||||
let parent_root = block.parent_root();
|
||||
let slot = block.slot();
|
||||
|
||||
let current_eth1_finalization_data = Eth1FinalizationData {
|
||||
eth1_data: state.eth1_data().clone(),
|
||||
eth1_deposit_index: state.eth1_deposit_index(),
|
||||
@@ -4066,9 +4007,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
});
|
||||
}
|
||||
|
||||
self.head_tracker
|
||||
.register_block(block_root, parent_root, slot);
|
||||
|
||||
metrics::stop_timer(db_write_timer);
|
||||
|
||||
metrics::inc_counter(&metrics::BLOCK_PROCESSING_SUCCESSES);
|
||||
@@ -7208,7 +7146,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
impl<T: BeaconChainTypes> Drop for BeaconChain<T> {
|
||||
fn drop(&mut self) {
|
||||
let drop = || -> Result<(), Error> {
|
||||
self.persist_head_and_fork_choice()?;
|
||||
self.persist_fork_choice()?;
|
||||
self.persist_op_pool()?;
|
||||
self.persist_eth1_cache()
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user