mirror of
https://github.com/sigp/lighthouse.git
synced 2026-06-30 19:34:37 +00:00
Deposit Cache Finalization & Fast WS Sync (#2915)
## Summary The deposit cache now has the ability to finalize deposits. This will cause it to drop unneeded deposit logs and hashes in the deposit Merkle tree that are no longer required to construct deposit proofs. The cache is finalized whenever the latest finalized checkpoint has a new `Eth1Data` with all deposits imported. This has three benefits: 1. Improves the speed of constructing Merkle proofs for deposits as we can just replay deposits since the last finalized checkpoint instead of all historical deposits when re-constructing the Merkle tree. 2. Significantly faster weak subjectivity sync as the deposit cache can be transferred to the newly syncing node in compressed form. The Merkle tree that stores `N` finalized deposits requires a maximum of `log2(N)` hashes. The newly syncing node then only needs to download deposits since the last finalized checkpoint to have a full tree. 3. Future proofing in preparation for [EIP-4444](https://eips.ethereum.org/EIPS/eip-4444) as execution nodes will no longer be required to store logs permanently so we won't always have all historical logs available to us. ## More Details Image to illustrate how the deposit contract merkle tree evolves and finalizes along with the resulting `DepositTreeSnapshot`  ## Other Considerations I've changed the structure of the `SszDepositCache` so once you load & save your database from this version of lighthouse, you will no longer be able to load it from older versions. Co-authored-by: ethDreamer <37123614+ethDreamer@users.noreply.github.com>
This commit is contained in:
@@ -16,6 +16,7 @@ use crate::chain_config::ChainConfig;
|
||||
use crate::early_attester_cache::EarlyAttesterCache;
|
||||
use crate::errors::{BeaconChainError as Error, BlockProductionError};
|
||||
use crate::eth1_chain::{Eth1Chain, Eth1ChainBackend};
|
||||
use crate::eth1_finalization_cache::{Eth1FinalizationCache, Eth1FinalizationData};
|
||||
use crate::events::ServerSentEventHandler;
|
||||
use crate::execution_payload::{get_execution_payload, PreparePayloadHandle};
|
||||
use crate::fork_choice_signal::{ForkChoiceSignalRx, ForkChoiceSignalTx, ForkChoiceWaitResult};
|
||||
@@ -117,6 +118,9 @@ pub const ATTESTATION_CACHE_LOCK_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
/// validator pubkey cache.
|
||||
pub const VALIDATOR_PUBKEY_CACHE_LOCK_TIMEOUT: Duration = Duration::from_secs(1);
|
||||
|
||||
/// The timeout for the eth1 finalization cache
|
||||
pub const ETH1_FINALIZATION_CACHE_LOCK_TIMEOUT: Duration = Duration::from_millis(200);
|
||||
|
||||
// These keys are all zero because they get stored in different columns, see `DBColumn` type.
|
||||
pub const BEACON_CHAIN_DB_KEY: Hash256 = Hash256::zero();
|
||||
pub const OP_POOL_DB_KEY: Hash256 = Hash256::zero();
|
||||
@@ -359,6 +363,8 @@ pub struct BeaconChain<T: BeaconChainTypes> {
|
||||
pub(crate) snapshot_cache: TimeoutRwLock<SnapshotCache<T::EthSpec>>,
|
||||
/// Caches the attester shuffling for a given epoch and shuffling key root.
|
||||
pub shuffling_cache: TimeoutRwLock<ShufflingCache>,
|
||||
/// A cache of eth1 deposit data at epoch boundaries for deposit finalization
|
||||
pub eth1_finalization_cache: TimeoutRwLock<Eth1FinalizationCache>,
|
||||
/// Caches the beacon block proposer shuffling for a given epoch and shuffling key root.
|
||||
pub beacon_proposer_cache: Mutex<BeaconProposerCache>,
|
||||
/// Caches a map of `validator_index -> validator_pubkey`.
|
||||
@@ -2531,9 +2537,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
block,
|
||||
block_root,
|
||||
state,
|
||||
parent_block: _,
|
||||
parent_block,
|
||||
confirmed_state_roots,
|
||||
payload_verification_handle,
|
||||
parent_eth1_finalization_data,
|
||||
} = execution_pending_block;
|
||||
|
||||
let PayloadVerificationOutcome {
|
||||
@@ -2585,6 +2592,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
confirmed_state_roots,
|
||||
payload_verification_status,
|
||||
count_unrealized,
|
||||
parent_block,
|
||||
parent_eth1_finalization_data,
|
||||
)
|
||||
},
|
||||
"payload_verification_handle",
|
||||
@@ -2599,6 +2608,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
///
|
||||
/// An error is returned if the block was unable to be imported. It may be partially imported
|
||||
/// (i.e., this function is not atomic).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn import_block(
|
||||
&self,
|
||||
signed_block: Arc<SignedBeaconBlock<T::EthSpec>>,
|
||||
@@ -2607,6 +2617,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
confirmed_state_roots: Vec<Hash256>,
|
||||
payload_verification_status: PayloadVerificationStatus,
|
||||
count_unrealized: CountUnrealized,
|
||||
parent_block: SignedBlindedBeaconBlock<T::EthSpec>,
|
||||
parent_eth1_finalization_data: Eth1FinalizationData,
|
||||
) -> Result<Hash256, BlockError<T::EthSpec>> {
|
||||
let current_slot = self.slot()?;
|
||||
let current_epoch = current_slot.epoch(T::EthSpec::slots_per_epoch());
|
||||
@@ -2987,6 +2999,11 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
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(),
|
||||
};
|
||||
let current_finalized_checkpoint = state.finalized_checkpoint();
|
||||
self.snapshot_cache
|
||||
.try_write_for(BLOCK_PROCESSING_CACHE_LOCK_TIMEOUT)
|
||||
.ok_or(Error::SnapshotCacheLockTimeout)
|
||||
@@ -3060,6 +3077,57 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
|
||||
);
|
||||
}
|
||||
|
||||
// Do not write to eth1 finalization cache for blocks older than 5 epochs
|
||||
// this helps reduce noise during sync
|
||||
if block_delay_total
|
||||
< self.slot_clock.slot_duration() * 5 * (T::EthSpec::slots_per_epoch() as u32)
|
||||
{
|
||||
let parent_block_epoch = parent_block.slot().epoch(T::EthSpec::slots_per_epoch());
|
||||
if parent_block_epoch < current_epoch {
|
||||
// we've crossed epoch boundary, store Eth1FinalizationData
|
||||
let (checkpoint, eth1_finalization_data) =
|
||||
if current_slot % T::EthSpec::slots_per_epoch() == 0 {
|
||||
// current block is the checkpoint
|
||||
(
|
||||
Checkpoint {
|
||||
epoch: current_epoch,
|
||||
root: block_root,
|
||||
},
|
||||
current_eth1_finalization_data,
|
||||
)
|
||||
} else {
|
||||
// parent block is the checkpoint
|
||||
(
|
||||
Checkpoint {
|
||||
epoch: current_epoch,
|
||||
root: parent_block.canonical_root(),
|
||||
},
|
||||
parent_eth1_finalization_data,
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(finalized_eth1_data) = self
|
||||
.eth1_finalization_cache
|
||||
.try_write_for(ETH1_FINALIZATION_CACHE_LOCK_TIMEOUT)
|
||||
.and_then(|mut cache| {
|
||||
cache.insert(checkpoint, eth1_finalization_data);
|
||||
cache.finalize(¤t_finalized_checkpoint)
|
||||
})
|
||||
{
|
||||
if let Some(eth1_chain) = self.eth1_chain.as_ref() {
|
||||
let finalized_deposit_count = finalized_eth1_data.deposit_count;
|
||||
eth1_chain.finalize_eth1_data(finalized_eth1_data);
|
||||
debug!(
|
||||
self.log,
|
||||
"called eth1_chain.finalize_eth1_data()";
|
||||
"epoch" => current_finalized_checkpoint.epoch,
|
||||
"deposit count" => finalized_deposit_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Inform the unknown block cache, in case it was waiting on this block.
|
||||
self.pre_finalization_block_cache
|
||||
.block_processed(block_root);
|
||||
|
||||
Reference in New Issue
Block a user