merge with unstable

This commit is contained in:
realbigsean
2022-11-01 13:18:00 -04:00
143 changed files with 4773 additions and 1876 deletions

View File

@@ -18,6 +18,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;
use crate::execution_payload::PreparePayloadHandle;
@@ -85,7 +86,7 @@ use state_processing::{
},
per_slot_processing,
state_advance::{complete_state_advance, partial_state_advance},
BlockSignatureStrategy, SigVerifiedOp, VerifyBlockRoot, VerifyOperation,
BlockSignatureStrategy, ConsensusContext, SigVerifiedOp, VerifyBlockRoot, VerifyOperation,
};
use std::cmp::Ordering;
use std::collections::HashMap;
@@ -119,6 +120,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();
@@ -361,6 +365,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`.
@@ -2013,60 +2019,75 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
target_epoch: Epoch,
state: &BeaconState<T::EthSpec>,
) -> bool {
let slots_per_epoch = T::EthSpec::slots_per_epoch();
let shuffling_lookahead = 1 + self.spec.min_seed_lookahead.as_u64();
// Shuffling can't have changed if we're in the first few epochs
if state.current_epoch() < shuffling_lookahead {
return true;
}
// Otherwise the shuffling is determined by the block at the end of the target epoch
// minus the shuffling lookahead (usually 2). We call this the "pivot".
let pivot_slot =
if target_epoch == state.previous_epoch() || target_epoch == state.current_epoch() {
(target_epoch - shuffling_lookahead).end_slot(slots_per_epoch)
} else {
return false;
};
let state_pivot_block_root = match state.get_block_root(pivot_slot) {
Ok(root) => *root,
Err(e) => {
warn!(
&self.log,
"Missing pivot block root for attestation";
"slot" => pivot_slot,
"error" => ?e,
);
return false;
}
};
// Use fork choice's view of the block DAG to quickly evaluate whether the attestation's
// pivot block is the same as the current state's pivot block. If it is, then the
// attestation's shuffling is the same as the current state's.
// To account for skipped slots, find the first block at *or before* the pivot slot.
let fork_choice_lock = self.canonical_head.fork_choice_read_lock();
let pivot_block_root = fork_choice_lock
.proto_array()
.core_proto_array()
.iter_block_roots(block_root)
.find(|(_, slot)| *slot <= pivot_slot)
.map(|(block_root, _)| block_root);
drop(fork_choice_lock);
match pivot_block_root {
Some(root) => root == state_pivot_block_root,
None => {
self.shuffling_is_compatible_result(block_root, target_epoch, state)
.unwrap_or_else(|e| {
debug!(
&self.log,
"Discarding attestation because of missing ancestor";
"pivot_slot" => pivot_slot.as_u64(),
self.log,
"Skipping attestation with incompatible shuffling";
"block_root" => ?block_root,
"target_epoch" => target_epoch,
"reason" => ?e,
);
false
})
}
fn shuffling_is_compatible_result(
&self,
block_root: &Hash256,
target_epoch: Epoch,
state: &BeaconState<T::EthSpec>,
) -> Result<bool, Error> {
// Compute the shuffling ID for the head state in the `target_epoch`.
let relative_epoch = RelativeEpoch::from_epoch(state.current_epoch(), target_epoch)
.map_err(|e| Error::BeaconStateError(e.into()))?;
let head_shuffling_id =
AttestationShufflingId::new(self.genesis_block_root, state, relative_epoch)?;
// Load the block's shuffling ID from fork choice. We use the variant of `get_block` that
// checks descent from the finalized block, so there's one case where we'll spuriously
// return `false`: where an attestation for the previous epoch nominates the pivot block
// which is the parent block of the finalized block. Such attestations are not useful, so
// this doesn't matter.
let fork_choice_lock = self.canonical_head.fork_choice_read_lock();
let block = fork_choice_lock
.get_block(block_root)
.ok_or(Error::AttestationHeadNotInForkChoice(*block_root))?;
drop(fork_choice_lock);
let block_shuffling_id = if target_epoch == block.current_epoch_shuffling_id.shuffling_epoch
{
block.current_epoch_shuffling_id
} else if target_epoch == block.next_epoch_shuffling_id.shuffling_epoch {
block.next_epoch_shuffling_id
} else if target_epoch > block.next_epoch_shuffling_id.shuffling_epoch {
AttestationShufflingId {
shuffling_epoch: target_epoch,
shuffling_decision_block: *block_root,
}
} else {
debug!(
self.log,
"Skipping attestation with incompatible shuffling";
"block_root" => ?block_root,
"target_epoch" => target_epoch,
"reason" => "target epoch less than block epoch"
);
return Ok(false);
};
if head_shuffling_id == block_shuffling_id {
Ok(true)
} else {
debug!(
self.log,
"Skipping attestation with incompatible shuffling";
"block_root" => ?block_root,
"target_epoch" => target_epoch,
"head_shuffling_id" => ?head_shuffling_id,
"block_shuffling_id" => ?block_shuffling_id,
);
Ok(false)
}
}
@@ -2538,9 +2559,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 {
@@ -2592,6 +2614,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
confirmed_state_roots,
payload_verification_status,
count_unrealized,
parent_block,
parent_eth1_finalization_data,
)
},
"payload_verification_handle",
@@ -2606,6 +2630,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>>,
@@ -2614,6 +2639,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());
@@ -2994,6 +3021,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)
@@ -3067,6 +3099,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(&current_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);
@@ -3525,7 +3608,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
let slot = state.slot();
let proposer_index = state.get_beacon_proposer_index(state.slot(), &self.spec)? as u64;
let sync_aggregate = if matches!(&state, BeaconState::Base(_)) {
None
@@ -3721,12 +3803,14 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
ProduceBlockVerification::VerifyRandao => BlockSignatureStrategy::VerifyRandao,
ProduceBlockVerification::NoVerification => BlockSignatureStrategy::NoVerification,
};
// Use a context without block root or proposer index so that both are checked.
let mut ctxt = ConsensusContext::new(block.slot());
per_block_processing(
&mut state,
&block,
None,
signature_strategy,
VerifyBlockRoot::True,
&mut ctxt,
&self.spec,
)?;
drop(process_timer);
@@ -4552,7 +4636,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
///
/// If the committee for `(head_block_root, shuffling_epoch)` isn't found in the
/// `shuffling_cache`, we will read a state from disk and then update the `shuffling_cache`.
pub(crate) fn with_committee_cache<F, R>(
pub fn with_committee_cache<F, R>(
&self,
head_block_root: Hash256,
shuffling_epoch: Epoch,

View File

@@ -42,6 +42,7 @@
//! END
//!
//! ```
use crate::eth1_finalization_cache::Eth1FinalizationData;
use crate::execution_payload::{
is_optimistic_candidate_block, validate_execution_payload_for_gossip, validate_merge_block,
AllowOptimisticImport, PayloadNotifier,
@@ -71,7 +72,8 @@ use state_processing::{
block_signature_verifier::{BlockSignatureVerifier, Error as BlockSignatureVerifierError},
per_block_processing, per_slot_processing,
state_advance::partial_state_advance,
BlockProcessingError, BlockSignatureStrategy, SlotProcessingError, VerifyBlockRoot,
BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError,
VerifyBlockRoot,
};
use std::borrow::Cow;
use std::fs;
@@ -550,7 +552,7 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
for (block_root, block) in &chain_segment {
signature_verifier.include_all_signatures(block, Some(*block_root))?;
signature_verifier.include_all_signatures(block, Some(*block_root), None)?;
}
if signature_verifier.verify().is_err() {
@@ -561,10 +563,17 @@ pub fn signature_verify_chain_segment<T: BeaconChainTypes>(
let mut signature_verified_blocks = chain_segment
.into_iter()
.map(|(block_root, block)| SignatureVerifiedBlock {
block,
block_root,
parent: None,
.map(|(block_root, block)| {
// Proposer index has already been verified above during signature verification.
let consensus_context = ConsensusContext::new(block.slot())
.set_current_block_root(block_root)
.set_proposer_index(block.message().proposer_index());
SignatureVerifiedBlock {
block,
block_root,
parent: None,
consensus_context,
}
})
.collect::<Vec<_>>();
@@ -583,6 +592,7 @@ pub struct GossipVerifiedBlock<T: BeaconChainTypes> {
pub block: Arc<SignedBeaconBlock<T::EthSpec>>,
pub block_root: Hash256,
parent: Option<PreProcessingSnapshot<T::EthSpec>>,
consensus_context: ConsensusContext<T::EthSpec>,
}
/// A wrapper around a `SignedBeaconBlock` that indicates that all signatures (except the deposit
@@ -591,6 +601,7 @@ pub struct SignatureVerifiedBlock<T: BeaconChainTypes> {
block: Arc<SignedBeaconBlock<T::EthSpec>>,
block_root: Hash256,
parent: Option<PreProcessingSnapshot<T::EthSpec>>,
consensus_context: ConsensusContext<T::EthSpec>,
}
/// Used to await the result of executing payload with a remote EE.
@@ -613,6 +624,7 @@ pub struct ExecutionPendingBlock<T: BeaconChainTypes> {
pub block_root: Hash256,
pub state: BeaconState<T::EthSpec>,
pub parent_block: SignedBeaconBlock<T::EthSpec, BlindedPayload<T::EthSpec>>,
pub parent_eth1_finalization_data: Eth1FinalizationData,
pub confirmed_state_roots: Vec<Hash256>,
pub payload_verification_handle: PayloadVerificationHandle<T::EthSpec>,
}
@@ -864,10 +876,16 @@ impl<T: BeaconChainTypes> GossipVerifiedBlock<T> {
// Validate the block's execution_payload (if any).
validate_execution_payload_for_gossip(&parent_block, block.message(), chain)?;
// Having checked the proposer index and the block root we can cache them.
let consensus_context = ConsensusContext::new(block.slot())
.set_current_block_root(block_root)
.set_proposer_index(block.message().proposer_index());
Ok(Self {
block,
block_root,
parent,
consensus_context,
})
}
@@ -927,10 +945,13 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
signature_verifier.include_all_signatures(&block, Some(block_root))?;
signature_verifier.include_all_signatures(&block, Some(block_root), None)?;
if signature_verifier.verify().is_ok() {
Ok(Self {
consensus_context: ConsensusContext::new(block.slot())
.set_current_block_root(block_root)
.set_proposer_index(block.message().proposer_index()),
block,
block_root,
parent: Some(parent),
@@ -973,13 +994,18 @@ impl<T: BeaconChainTypes> SignatureVerifiedBlock<T> {
let mut signature_verifier = get_signature_verifier(&state, &pubkey_cache, &chain.spec);
signature_verifier.include_all_signatures_except_proposal(&block)?;
// Gossip verification has already checked the proposer index. Use it to check the RANDAO
// signature.
let verified_proposer_index = Some(block.message().proposer_index());
signature_verifier
.include_all_signatures_except_proposal(&block, verified_proposer_index)?;
if signature_verifier.verify().is_ok() {
Ok(Self {
block,
block_root: from.block_root,
parent: Some(parent),
consensus_context: from.consensus_context,
})
} else {
Err(BlockError::InvalidSignature)
@@ -1016,8 +1042,14 @@ impl<T: BeaconChainTypes> IntoExecutionPendingBlock<T> for SignatureVerifiedBloc
.map_err(|e| BlockSlashInfo::SignatureValid(header.clone(), e))?
};
ExecutionPendingBlock::from_signature_verified_components(block, block_root, parent, chain)
.map_err(|e| BlockSlashInfo::SignatureValid(header, e))
ExecutionPendingBlock::from_signature_verified_components(
block,
block_root,
parent,
self.consensus_context,
chain,
)
.map_err(|e| BlockSlashInfo::SignatureValid(header, e))
}
fn block(&self) -> &SignedBeaconBlock<T::EthSpec> {
@@ -1058,6 +1090,7 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
block: Arc<SignedBeaconBlock<T::EthSpec>>,
block_root: Hash256,
parent: PreProcessingSnapshot<T::EthSpec>,
mut consensus_context: ConsensusContext<T::EthSpec>,
chain: &Arc<BeaconChain<T>>,
) -> Result<Self, BlockError<T::EthSpec>> {
if let Some(parent) = chain
@@ -1134,6 +1167,11 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
.into());
}
let parent_eth1_finalization_data = Eth1FinalizationData {
eth1_data: state.eth1_data().clone(),
eth1_deposit_index: state.eth1_deposit_index(),
};
let distance = block.slot().as_u64().saturating_sub(state.slot().as_u64());
for _ in 0..distance {
let state_root = if parent.beacon_block.slot() == state.slot() {
@@ -1341,10 +1379,10 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
if let Err(err) = per_block_processing(
&mut state,
&block,
Some(block_root),
// Signatures were verified earlier in this function.
BlockSignatureStrategy::NoVerification,
VerifyBlockRoot::True,
&mut consensus_context,
&chain.spec,
) {
match err {
@@ -1389,6 +1427,7 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
block_root,
state,
parent_block: parent.beacon_block,
parent_eth1_finalization_data,
confirmed_state_roots,
payload_verification_handle,
})

View File

@@ -1,5 +1,6 @@
use crate::beacon_chain::{CanonicalHead, BEACON_CHAIN_DB_KEY, ETH1_CACHE_DB_KEY, OP_POOL_DB_KEY};
use crate::eth1_chain::{CachingEth1Backend, SszEth1};
use crate::eth1_finalization_cache::Eth1FinalizationCache;
use crate::fork_choice_signal::ForkChoiceSignalTx;
use crate::fork_revert::{reset_fork_choice_to_finalization, revert_to_fork_boundary};
use crate::head_tracker::HeadTracker;
@@ -795,6 +796,7 @@ where
head_for_snapshot_cache,
)),
shuffling_cache: TimeoutRwLock::new(ShufflingCache::new()),
eth1_finalization_cache: TimeoutRwLock::new(Eth1FinalizationCache::new(log.clone())),
beacon_proposer_cache: <_>::default(),
block_times_cache: <_>::default(),
pre_finalization_block_cache: <_>::default(),
@@ -897,7 +899,7 @@ where
.ok_or("dummy_eth1_backend requires a log")?;
let backend =
CachingEth1Backend::new(Eth1Config::default(), log.clone(), self.spec.clone());
CachingEth1Backend::new(Eth1Config::default(), log.clone(), self.spec.clone())?;
self.eth1_chain = Some(Eth1Chain::new_dummy(backend));

View File

@@ -16,7 +16,6 @@ use store::{DBColumn, Error as StoreError, StoreItem};
use task_executor::TaskExecutor;
use types::{
BeaconState, BeaconStateError, ChainSpec, Deposit, Eth1Data, EthSpec, Hash256, Slot, Unsigned,
DEPOSIT_TREE_DEPTH,
};
type BlockNumber = u64;
@@ -170,8 +169,8 @@ fn get_sync_status<T: EthSpec>(
#[derive(Encode, Decode, Clone)]
pub struct SszEth1 {
use_dummy_backend: bool,
backend_bytes: Vec<u8>,
pub use_dummy_backend: bool,
pub backend_bytes: Vec<u8>,
}
impl StoreItem for SszEth1 {
@@ -305,6 +304,12 @@ where
}
}
/// Set in motion the finalization of `Eth1Data`. This method is called during block import
/// so it should be fast.
pub fn finalize_eth1_data(&self, eth1_data: Eth1Data) {
self.backend.finalize_eth1_data(eth1_data);
}
/// Consumes `self`, returning the backend.
pub fn into_backend(self) -> T {
self.backend
@@ -335,6 +340,10 @@ pub trait Eth1ChainBackend<T: EthSpec>: Sized + Send + Sync {
/// beacon node eth1 cache is.
fn latest_cached_block(&self) -> Option<Eth1Block>;
/// Set in motion the finalization of `Eth1Data`. This method is called during block import
/// so it should be fast.
fn finalize_eth1_data(&self, eth1_data: Eth1Data);
/// Returns the block at the head of the chain (ignoring follow distance, etc). Used to obtain
/// an idea of how up-to-date the remote eth1 node is.
fn head_block(&self) -> Option<Eth1Block>;
@@ -389,6 +398,8 @@ impl<T: EthSpec> Eth1ChainBackend<T> for DummyEth1ChainBackend<T> {
None
}
fn finalize_eth1_data(&self, _eth1_data: Eth1Data) {}
fn head_block(&self) -> Option<Eth1Block> {
None
}
@@ -431,12 +442,13 @@ impl<T: EthSpec> CachingEth1Backend<T> {
/// Instantiates `self` with empty caches.
///
/// Does not connect to the eth1 node or start any tasks to keep the cache updated.
pub fn new(config: Eth1Config, log: Logger, spec: ChainSpec) -> Self {
Self {
core: HttpService::new(config, log.clone(), spec),
pub fn new(config: Eth1Config, log: Logger, spec: ChainSpec) -> Result<Self, String> {
Ok(Self {
core: HttpService::new(config, log.clone(), spec)
.map_err(|e| format!("Failed to create eth1 http service: {:?}", e))?,
log,
_phantom: PhantomData,
}
})
}
/// Starts the routine which connects to the external eth1 node and updates the caches.
@@ -546,7 +558,7 @@ impl<T: EthSpec> Eth1ChainBackend<T> for CachingEth1Backend<T> {
.deposits()
.read()
.cache
.get_deposits(next, last, deposit_count, DEPOSIT_TREE_DEPTH)
.get_deposits(next, last, deposit_count)
.map_err(|e| Error::BackendError(format!("Failed to get deposits: {:?}", e)))
.map(|(_deposit_root, deposits)| deposits)
}
@@ -557,6 +569,12 @@ impl<T: EthSpec> Eth1ChainBackend<T> for CachingEth1Backend<T> {
self.core.latest_cached_block()
}
/// This only writes the eth1_data to a temporary cache so that the service
/// thread can later do the actual finalizing of the deposit tree.
fn finalize_eth1_data(&self, eth1_data: Eth1Data) {
self.core.set_to_finalize(Some(eth1_data));
}
fn head_block(&self) -> Option<Eth1Block> {
self.core.head_block()
}
@@ -730,11 +748,9 @@ mod test {
};
let log = null_logger().unwrap();
Eth1Chain::new(CachingEth1Backend::new(
eth1_config,
log,
MainnetEthSpec::default_spec(),
))
Eth1Chain::new(
CachingEth1Backend::new(eth1_config, log, MainnetEthSpec::default_spec()).unwrap(),
)
}
fn get_deposit_log(i: u64, spec: &ChainSpec) -> DepositLog {

View File

@@ -0,0 +1,498 @@
use slog::{debug, Logger};
use std::cmp;
use std::collections::BTreeMap;
use types::{Checkpoint, Epoch, Eth1Data, Hash256 as Root};
/// The default size of the cache.
/// The beacon chain only looks at the last 4 epochs for finalization.
/// Add 1 for current epoch and 4 earlier epochs.
pub const DEFAULT_ETH1_CACHE_SIZE: usize = 5;
/// These fields are named the same as the corresponding fields in the `BeaconState`
/// as this structure stores these values from the `BeaconState` at a `Checkpoint`
#[derive(Clone)]
pub struct Eth1FinalizationData {
pub eth1_data: Eth1Data,
pub eth1_deposit_index: u64,
}
impl Eth1FinalizationData {
/// Ensures the deposit finalization conditions have been met. See:
/// https://eips.ethereum.org/EIPS/eip-4881#deposit-finalization-conditions
fn fully_imported(&self) -> bool {
self.eth1_deposit_index >= self.eth1_data.deposit_count
}
}
/// Implements map from Checkpoint -> Eth1CacheData
pub struct CheckpointMap {
capacity: usize,
// There shouldn't be more than a couple of potential checkpoints at the same
// epoch. Searching through a vector for the matching Root should be faster
// than using another map from Root->Eth1CacheData
store: BTreeMap<Epoch, Vec<(Root, Eth1FinalizationData)>>,
}
impl Default for CheckpointMap {
fn default() -> Self {
Self::new()
}
}
/// Provides a map of `Eth1CacheData` referenced by `Checkpoint`
///
/// ## Cache Queuing
///
/// The cache keeps a maximum number of (`capacity`) epochs. Because there may be
/// forks at the epoch boundary, it's possible that there exists more than one
/// `Checkpoint` for the same `Epoch`. This cache will store all checkpoints for
/// a given `Epoch`. When adding data for a new `Checkpoint` would cause the number
/// of `Epoch`s stored to exceed `capacity`, the data for oldest `Epoch` is dropped
impl CheckpointMap {
pub fn new() -> Self {
CheckpointMap {
capacity: DEFAULT_ETH1_CACHE_SIZE,
store: BTreeMap::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
CheckpointMap {
capacity: cmp::max(1, capacity),
store: BTreeMap::new(),
}
}
pub fn insert(&mut self, checkpoint: Checkpoint, eth1_finalization_data: Eth1FinalizationData) {
self.store
.entry(checkpoint.epoch)
.or_insert_with(Vec::new)
.push((checkpoint.root, eth1_finalization_data));
// faster to reduce size after the fact than do pre-checking to see
// if the current data would increase the size of the BTreeMap
while self.store.len() > self.capacity {
let oldest_stored_epoch = self.store.keys().next().cloned().unwrap();
self.store.remove(&oldest_stored_epoch);
}
}
pub fn get(&self, checkpoint: &Checkpoint) -> Option<&Eth1FinalizationData> {
match self.store.get(&checkpoint.epoch) {
Some(vec) => {
for (root, data) in vec {
if *root == checkpoint.root {
return Some(data);
}
}
None
}
None => None,
}
}
#[cfg(test)]
pub fn len(&self) -> usize {
self.store.len()
}
}
/// This cache stores `Eth1CacheData` that could potentially be finalized within 4
/// future epochs.
pub struct Eth1FinalizationCache {
by_checkpoint: CheckpointMap,
pending_eth1: BTreeMap<u64, Eth1Data>,
last_finalized: Option<Eth1Data>,
log: Logger,
}
/// Provides a cache of `Eth1CacheData` at epoch boundaries. This is used to
/// finalize deposits when a new epoch is finalized.
///
impl Eth1FinalizationCache {
pub fn new(log: Logger) -> Self {
Eth1FinalizationCache {
by_checkpoint: CheckpointMap::new(),
pending_eth1: BTreeMap::new(),
last_finalized: None,
log,
}
}
pub fn with_capacity(log: Logger, capacity: usize) -> Self {
Eth1FinalizationCache {
by_checkpoint: CheckpointMap::with_capacity(capacity),
pending_eth1: BTreeMap::new(),
last_finalized: None,
log,
}
}
pub fn insert(&mut self, checkpoint: Checkpoint, eth1_finalization_data: Eth1FinalizationData) {
if !eth1_finalization_data.fully_imported() {
self.pending_eth1.insert(
eth1_finalization_data.eth1_data.deposit_count,
eth1_finalization_data.eth1_data.clone(),
);
debug!(
self.log,
"Eth1Cache: inserted pending eth1";
"eth1_data.deposit_count" => eth1_finalization_data.eth1_data.deposit_count,
"eth1_deposit_index" => eth1_finalization_data.eth1_deposit_index,
);
}
self.by_checkpoint
.insert(checkpoint, eth1_finalization_data);
}
pub fn finalize(&mut self, checkpoint: &Checkpoint) -> Option<Eth1Data> {
if let Some(eth1_finalized_data) = self.by_checkpoint.get(checkpoint) {
let finalized_deposit_index = eth1_finalized_data.eth1_deposit_index;
let mut result = None;
while let Some(pending_count) = self.pending_eth1.keys().next().cloned() {
if finalized_deposit_index >= pending_count {
result = self.pending_eth1.remove(&pending_count);
debug!(
self.log,
"Eth1Cache: dropped pending eth1";
"pending_count" => pending_count,
"finalized_deposit_index" => finalized_deposit_index,
);
} else {
break;
}
}
if eth1_finalized_data.fully_imported() {
result = Some(eth1_finalized_data.eth1_data.clone())
}
if result.is_some() {
self.last_finalized = result;
}
self.last_finalized.clone()
} else {
debug!(
self.log,
"Eth1Cache: cache miss";
"epoch" => checkpoint.epoch,
);
None
}
}
#[cfg(test)]
pub fn by_checkpoint(&self) -> &CheckpointMap {
&self.by_checkpoint
}
#[cfg(test)]
pub fn pending_eth1(&self) -> &BTreeMap<u64, Eth1Data> {
&self.pending_eth1
}
}
#[cfg(test)]
pub mod tests {
use super::*;
use sloggers::null::NullLoggerBuilder;
use sloggers::Build;
use std::collections::HashMap;
const SLOTS_PER_EPOCH: u64 = 32;
const MAX_DEPOSITS: u64 = 16;
const EPOCHS_PER_ETH1_VOTING_PERIOD: u64 = 64;
fn eth1cache() -> Eth1FinalizationCache {
let log_builder = NullLoggerBuilder;
Eth1FinalizationCache::new(log_builder.build().expect("should build log"))
}
fn random_eth1_data(deposit_count: u64) -> Eth1Data {
Eth1Data {
deposit_root: Root::random(),
deposit_count,
block_hash: Root::random(),
}
}
fn random_checkpoint(epoch: u64) -> Checkpoint {
Checkpoint {
epoch: epoch.into(),
root: Root::random(),
}
}
fn random_checkpoints(n: usize) -> Vec<Checkpoint> {
let mut result = Vec::with_capacity(n);
for epoch in 0..n {
result.push(random_checkpoint(epoch as u64))
}
result
}
#[test]
fn fully_imported_deposits() {
let epochs = 16;
let deposits_imported = 128;
let eth1data = random_eth1_data(deposits_imported);
let checkpoints = random_checkpoints(epochs as usize);
let mut eth1cache = eth1cache();
for epoch in 4..epochs {
assert_eq!(
eth1cache.by_checkpoint().len(),
cmp::min((epoch - 4) as usize, DEFAULT_ETH1_CACHE_SIZE),
"Unexpected cache size"
);
let checkpoint = checkpoints
.get(epoch as usize)
.expect("should get checkpoint");
eth1cache.insert(
*checkpoint,
Eth1FinalizationData {
eth1_data: eth1data.clone(),
eth1_deposit_index: deposits_imported,
},
);
let finalized_checkpoint = checkpoints
.get((epoch - 4) as usize)
.expect("should get finalized checkpoint");
assert!(
eth1cache.pending_eth1().is_empty(),
"Deposits are fully imported so pending cache should be empty"
);
if epoch < 8 {
assert_eq!(
eth1cache.finalize(finalized_checkpoint),
None,
"Should have cache miss"
);
} else {
assert_eq!(
eth1cache.finalize(finalized_checkpoint),
Some(eth1data.clone()),
"Should have cache hit"
)
}
}
}
#[test]
fn partially_imported_deposits() {
let epochs = 16;
let initial_deposits_imported = 1024;
let deposits_imported_per_epoch = MAX_DEPOSITS * SLOTS_PER_EPOCH;
let full_import_epoch = 13;
let total_deposits =
initial_deposits_imported + deposits_imported_per_epoch * full_import_epoch;
let eth1data = random_eth1_data(total_deposits);
let checkpoints = random_checkpoints(epochs as usize);
let mut eth1cache = eth1cache();
for epoch in 0..epochs {
assert_eq!(
eth1cache.by_checkpoint().len(),
cmp::min(epoch as usize, DEFAULT_ETH1_CACHE_SIZE),
"Unexpected cache size"
);
let checkpoint = checkpoints
.get(epoch as usize)
.expect("should get checkpoint");
let deposits_imported = cmp::min(
total_deposits,
initial_deposits_imported + deposits_imported_per_epoch * epoch,
);
eth1cache.insert(
*checkpoint,
Eth1FinalizationData {
eth1_data: eth1data.clone(),
eth1_deposit_index: deposits_imported,
},
);
if epoch >= 4 {
let finalized_epoch = epoch - 4;
let finalized_checkpoint = checkpoints
.get(finalized_epoch as usize)
.expect("should get finalized checkpoint");
if finalized_epoch < full_import_epoch {
assert_eq!(
eth1cache.finalize(finalized_checkpoint),
None,
"Deposits not fully finalized so cache should return no Eth1Data",
);
assert_eq!(
eth1cache.pending_eth1().len(),
1,
"Deposits not fully finalized. Pending eth1 cache should have 1 entry"
);
} else {
assert_eq!(
eth1cache.finalize(finalized_checkpoint),
Some(eth1data.clone()),
"Deposits fully imported and finalized. Cache should return Eth1Data. finalized_deposits[{}]",
(initial_deposits_imported + deposits_imported_per_epoch * finalized_epoch),
);
assert!(
eth1cache.pending_eth1().is_empty(),
"Deposits fully imported and finalized. Pending cache should be empty"
);
}
}
}
}
#[test]
fn fork_at_epoch_boundary() {
let epochs = 12;
let deposits_imported = 128;
let eth1data = random_eth1_data(deposits_imported);
let checkpoints = random_checkpoints(epochs as usize);
let mut forks = HashMap::new();
let mut eth1cache = eth1cache();
for epoch in 0..epochs {
assert_eq!(
eth1cache.by_checkpoint().len(),
cmp::min(epoch as usize, DEFAULT_ETH1_CACHE_SIZE),
"Unexpected cache size"
);
let checkpoint = checkpoints
.get(epoch as usize)
.expect("should get checkpoint");
eth1cache.insert(
*checkpoint,
Eth1FinalizationData {
eth1_data: eth1data.clone(),
eth1_deposit_index: deposits_imported,
},
);
// lets put a fork at every third epoch
if epoch % 3 == 0 {
let fork = random_checkpoint(epoch);
eth1cache.insert(
fork,
Eth1FinalizationData {
eth1_data: eth1data.clone(),
eth1_deposit_index: deposits_imported,
},
);
forks.insert(epoch as usize, fork);
}
assert!(
eth1cache.pending_eth1().is_empty(),
"Deposits are fully imported so pending cache should be empty"
);
if epoch >= 4 {
let finalized_epoch = (epoch - 4) as usize;
let finalized_checkpoint = if finalized_epoch % 3 == 0 {
forks.get(&finalized_epoch).expect("should get fork")
} else {
checkpoints
.get(finalized_epoch)
.expect("should get checkpoint")
};
assert_eq!(
eth1cache.finalize(finalized_checkpoint),
Some(eth1data.clone()),
"Should have cache hit"
);
if finalized_epoch >= 3 {
let dropped_epoch = finalized_epoch - 3;
if let Some(dropped_checkpoint) = forks.get(&dropped_epoch) {
// got checkpoint for an old fork that should no longer
// be in the cache because it is from too long ago
assert_eq!(
eth1cache.finalize(dropped_checkpoint),
None,
"Should have cache miss"
);
}
}
}
}
}
#[test]
fn massive_deposit_queue() {
// Simulating a situation where deposits don't get imported within an eth1 voting period
let eth1_voting_periods = 8;
let initial_deposits_imported = 1024;
let deposits_imported_per_epoch = MAX_DEPOSITS * SLOTS_PER_EPOCH;
let initial_deposit_queue =
deposits_imported_per_epoch * EPOCHS_PER_ETH1_VOTING_PERIOD * 2 + 32;
let new_deposits_per_voting_period =
EPOCHS_PER_ETH1_VOTING_PERIOD * deposits_imported_per_epoch / 2;
let mut epoch_data = BTreeMap::new();
let mut eth1s_by_count = BTreeMap::new();
let mut eth1cache = eth1cache();
let mut last_period_deposits = initial_deposits_imported;
for period in 0..eth1_voting_periods {
let period_deposits = initial_deposits_imported
+ initial_deposit_queue
+ period * new_deposits_per_voting_period;
let period_eth1_data = random_eth1_data(period_deposits);
eth1s_by_count.insert(period_eth1_data.deposit_count, period_eth1_data.clone());
for epoch_mod_period in 0..EPOCHS_PER_ETH1_VOTING_PERIOD {
let epoch = period * EPOCHS_PER_ETH1_VOTING_PERIOD + epoch_mod_period;
let checkpoint = random_checkpoint(epoch);
let deposits_imported = cmp::min(
period_deposits,
last_period_deposits + deposits_imported_per_epoch * epoch_mod_period,
);
eth1cache.insert(
checkpoint,
Eth1FinalizationData {
eth1_data: period_eth1_data.clone(),
eth1_deposit_index: deposits_imported,
},
);
epoch_data.insert(epoch, (checkpoint, deposits_imported));
if epoch >= 4 {
let finalized_epoch = epoch - 4;
let (finalized_checkpoint, finalized_deposits) = epoch_data
.get(&finalized_epoch)
.expect("should get epoch data");
let pending_eth1s = eth1s_by_count.range((finalized_deposits + 1)..).count();
let last_finalized_eth1 = eth1s_by_count
.range(0..(finalized_deposits + 1))
.map(|(_, eth1)| eth1)
.last()
.cloned();
assert_eq!(
eth1cache.finalize(finalized_checkpoint),
last_finalized_eth1,
"finalized checkpoint mismatch",
);
assert_eq!(
eth1cache.pending_eth1().len(),
pending_eth1s,
"pending eth1 mismatch"
);
}
}
// remove unneeded stuff from old epochs
while epoch_data.len() > DEFAULT_ETH1_CACHE_SIZE {
let oldest_stored_epoch = epoch_data
.keys()
.next()
.cloned()
.expect("should get oldest epoch");
epoch_data.remove(&oldest_stored_epoch);
}
last_period_deposits = period_deposits;
}
}
}

View File

@@ -5,7 +5,8 @@ use proto_array::CountUnrealizedFull;
use slog::{info, warn, Logger};
use state_processing::state_advance::complete_state_advance;
use state_processing::{
per_block_processing, per_block_processing::BlockSignatureStrategy, VerifyBlockRoot,
per_block_processing, per_block_processing::BlockSignatureStrategy, ConsensusContext,
VerifyBlockRoot,
};
use std::sync::Arc;
use std::time::Duration;
@@ -172,12 +173,14 @@ pub fn reset_fork_choice_to_finalization<E: EthSpec, Hot: ItemStore<E>, Cold: It
complete_state_advance(&mut state, None, block.slot(), spec)
.map_err(|e| format!("State advance failed: {:?}", e))?;
let mut ctxt = ConsensusContext::new(block.slot())
.set_proposer_index(block.message().proposer_index());
per_block_processing(
&mut state,
&block,
None,
BlockSignatureStrategy::NoVerification,
VerifyBlockRoot::True,
&mut ctxt,
spec,
)
.map_err(|e| format!("Error replaying block: {:?}", e))?;

View File

@@ -15,6 +15,7 @@ pub mod chain_config;
mod early_attester_cache;
mod errors;
pub mod eth1_chain;
mod eth1_finalization_cache;
pub mod events;
pub mod execution_payload;
pub mod fork_choice_signal;

View File

@@ -2,13 +2,15 @@
mod migration_schema_v10;
mod migration_schema_v11;
mod migration_schema_v12;
mod migration_schema_v13;
mod migration_schema_v6;
mod migration_schema_v7;
mod migration_schema_v8;
mod migration_schema_v9;
mod types;
use crate::beacon_chain::{BeaconChainTypes, FORK_CHOICE_DB_KEY};
use crate::beacon_chain::{BeaconChainTypes, ETH1_CACHE_DB_KEY, FORK_CHOICE_DB_KEY};
use crate::eth1_chain::SszEth1;
use crate::persisted_fork_choice::{
PersistedForkChoiceV1, PersistedForkChoiceV10, PersistedForkChoiceV11, PersistedForkChoiceV7,
PersistedForkChoiceV8,
@@ -24,6 +26,7 @@ use store::{Error as StoreError, StoreItem};
/// Migrate the database from one schema version to another, applying all requisite mutations.
pub fn migrate_schema<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
deposit_contract_deploy_block: u64,
datadir: &Path,
from: SchemaVersion,
to: SchemaVersion,
@@ -31,19 +34,51 @@ pub fn migrate_schema<T: BeaconChainTypes>(
spec: &ChainSpec,
) -> Result<(), StoreError> {
match (from, to) {
// Migrating from the current schema version to iself is always OK, a no-op.
// Migrating from the current schema version to itself is always OK, a no-op.
(_, _) if from == to && to == CURRENT_SCHEMA_VERSION => Ok(()),
// Upgrade across multiple versions by recursively migrating one step at a time.
(_, _) if from.as_u64() + 1 < to.as_u64() => {
let next = SchemaVersion(from.as_u64() + 1);
migrate_schema::<T>(db.clone(), datadir, from, next, log.clone(), spec)?;
migrate_schema::<T>(db, datadir, next, to, log, spec)
migrate_schema::<T>(
db.clone(),
deposit_contract_deploy_block,
datadir,
from,
next,
log.clone(),
spec,
)?;
migrate_schema::<T>(
db,
deposit_contract_deploy_block,
datadir,
next,
to,
log,
spec,
)
}
// Downgrade across multiple versions by recursively migrating one step at a time.
(_, _) if to.as_u64() + 1 < from.as_u64() => {
let next = SchemaVersion(from.as_u64() - 1);
migrate_schema::<T>(db.clone(), datadir, from, next, log.clone(), spec)?;
migrate_schema::<T>(db, datadir, next, to, log, spec)
migrate_schema::<T>(
db.clone(),
deposit_contract_deploy_block,
datadir,
from,
next,
log.clone(),
spec,
)?;
migrate_schema::<T>(
db,
deposit_contract_deploy_block,
datadir,
next,
to,
log,
spec,
)
}
//
@@ -207,6 +242,55 @@ pub fn migrate_schema<T: BeaconChainTypes>(
let ops = migration_schema_v12::downgrade_from_v12::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
(SchemaVersion(12), SchemaVersion(13)) => {
let mut ops = vec![];
if let Some(persisted_eth1_v1) = db.get_item::<SszEth1>(&ETH1_CACHE_DB_KEY)? {
let upgraded_eth1_cache =
match migration_schema_v13::update_eth1_cache(persisted_eth1_v1) {
Ok(upgraded_eth1) => upgraded_eth1,
Err(e) => {
warn!(log, "Failed to deserialize SszEth1CacheV1"; "error" => ?e);
warn!(log, "Reinitializing eth1 cache");
migration_schema_v13::reinitialized_eth1_cache_v13(
deposit_contract_deploy_block,
)
}
};
ops.push(upgraded_eth1_cache.as_kv_store_op(ETH1_CACHE_DB_KEY));
}
db.store_schema_version_atomically(to, ops)?;
Ok(())
}
(SchemaVersion(13), SchemaVersion(12)) => {
let mut ops = vec![];
if let Some(persisted_eth1_v13) = db.get_item::<SszEth1>(&ETH1_CACHE_DB_KEY)? {
let downgraded_eth1_cache = match migration_schema_v13::downgrade_eth1_cache(
persisted_eth1_v13,
) {
Ok(Some(downgraded_eth1)) => downgraded_eth1,
Ok(None) => {
warn!(log, "Unable to downgrade eth1 cache from newer version: reinitializing eth1 cache");
migration_schema_v13::reinitialized_eth1_cache_v1(
deposit_contract_deploy_block,
)
}
Err(e) => {
warn!(log, "Unable to downgrade eth1 cache from newer version: failed to deserialize SszEth1CacheV13"; "error" => ?e);
warn!(log, "Reinitializing eth1 cache");
migration_schema_v13::reinitialized_eth1_cache_v1(
deposit_contract_deploy_block,
)
}
};
ops.push(downgraded_eth1_cache.as_kv_store_op(ETH1_CACHE_DB_KEY));
}
db.store_schema_version_atomically(to, ops)?;
Ok(())
}
// Anything else is an error.
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
target_version: to,

View File

@@ -0,0 +1,150 @@
use crate::eth1_chain::SszEth1;
use eth1::{BlockCache, SszDepositCacheV1, SszDepositCacheV13, SszEth1CacheV1, SszEth1CacheV13};
use ssz::{Decode, Encode};
use state_processing::common::DepositDataTree;
use store::Error;
use types::DEPOSIT_TREE_DEPTH;
pub fn update_eth1_cache(persisted_eth1_v1: SszEth1) -> Result<SszEth1, Error> {
if persisted_eth1_v1.use_dummy_backend {
// backend_bytes is empty when using dummy backend
return Ok(persisted_eth1_v1);
}
let SszEth1 {
use_dummy_backend,
backend_bytes,
} = persisted_eth1_v1;
let ssz_eth1_cache_v1 = SszEth1CacheV1::from_ssz_bytes(&backend_bytes)?;
let SszEth1CacheV1 {
block_cache,
deposit_cache: deposit_cache_v1,
last_processed_block,
} = ssz_eth1_cache_v1;
let SszDepositCacheV1 {
logs,
leaves,
deposit_contract_deploy_block,
deposit_roots,
} = deposit_cache_v1;
let deposit_cache_v13 = SszDepositCacheV13 {
logs,
leaves,
deposit_contract_deploy_block,
finalized_deposit_count: 0,
finalized_block_height: deposit_contract_deploy_block.saturating_sub(1),
deposit_tree_snapshot: None,
deposit_roots,
};
let ssz_eth1_cache_v13 = SszEth1CacheV13 {
block_cache,
deposit_cache: deposit_cache_v13,
last_processed_block,
};
let persisted_eth1_v13 = SszEth1 {
use_dummy_backend,
backend_bytes: ssz_eth1_cache_v13.as_ssz_bytes(),
};
Ok(persisted_eth1_v13)
}
pub fn downgrade_eth1_cache(persisted_eth1_v13: SszEth1) -> Result<Option<SszEth1>, Error> {
if persisted_eth1_v13.use_dummy_backend {
// backend_bytes is empty when using dummy backend
return Ok(Some(persisted_eth1_v13));
}
let SszEth1 {
use_dummy_backend,
backend_bytes,
} = persisted_eth1_v13;
let ssz_eth1_cache_v13 = SszEth1CacheV13::from_ssz_bytes(&backend_bytes)?;
let SszEth1CacheV13 {
block_cache,
deposit_cache: deposit_cache_v13,
last_processed_block,
} = ssz_eth1_cache_v13;
let SszDepositCacheV13 {
logs,
leaves,
deposit_contract_deploy_block,
finalized_deposit_count,
finalized_block_height: _,
deposit_tree_snapshot,
deposit_roots,
} = deposit_cache_v13;
if finalized_deposit_count == 0 && deposit_tree_snapshot.is_none() {
// This tree was never finalized and can be directly downgraded to v1 without re-initializing
let deposit_cache_v1 = SszDepositCacheV1 {
logs,
leaves,
deposit_contract_deploy_block,
deposit_roots,
};
let ssz_eth1_cache_v1 = SszEth1CacheV1 {
block_cache,
deposit_cache: deposit_cache_v1,
last_processed_block,
};
return Ok(Some(SszEth1 {
use_dummy_backend,
backend_bytes: ssz_eth1_cache_v1.as_ssz_bytes(),
}));
}
// deposit cache was finalized; can't downgrade
Ok(None)
}
pub fn reinitialized_eth1_cache_v13(deposit_contract_deploy_block: u64) -> SszEth1 {
let empty_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH);
let deposit_cache_v13 = SszDepositCacheV13 {
logs: vec![],
leaves: vec![],
deposit_contract_deploy_block,
finalized_deposit_count: 0,
finalized_block_height: deposit_contract_deploy_block.saturating_sub(1),
deposit_tree_snapshot: empty_tree.get_snapshot(),
deposit_roots: vec![empty_tree.root()],
};
let ssz_eth1_cache_v13 = SszEth1CacheV13 {
block_cache: BlockCache::default(),
deposit_cache: deposit_cache_v13,
last_processed_block: None,
};
SszEth1 {
use_dummy_backend: false,
backend_bytes: ssz_eth1_cache_v13.as_ssz_bytes(),
}
}
pub fn reinitialized_eth1_cache_v1(deposit_contract_deploy_block: u64) -> SszEth1 {
let empty_tree = DepositDataTree::create(&[], 0, DEPOSIT_TREE_DEPTH);
let deposit_cache_v1 = SszDepositCacheV1 {
logs: vec![],
leaves: vec![],
deposit_contract_deploy_block,
deposit_roots: vec![empty_tree.root()],
};
let ssz_eth1_cache_v1 = SszEth1CacheV1 {
block_cache: BlockCache::default(),
deposit_cache: deposit_cache_v1,
last_processed_block: None,
};
SszEth1 {
use_dummy_backend: false,
backend_bytes: ssz_eth1_cache_v1.as_ssz_bytes(),
}
}

View File

@@ -1432,8 +1432,9 @@ where
// Building proofs
let mut proofs = vec![];
for i in 0..leaves.len() {
let (_, mut proof) =
tree.generate_proof(i, self.spec.deposit_contract_tree_depth as usize);
let (_, mut proof) = tree
.generate_proof(i, self.spec.deposit_contract_tree_depth as usize)
.expect("should generate proof");
proof.push(Hash256::from_slice(&int_to_bytes32(leaves.len() as u64)));
proofs.push(proof);
}