Merge branch 'capella' into unstable

This commit is contained in:
Michael Sproul
2023-02-22 10:25:45 +11:00
267 changed files with 11892 additions and 2096 deletions
+1 -1
View File
@@ -33,7 +33,7 @@ slot_clock = { path = "../../common/slot_clock" }
eth2_hashing = "0.3.0"
eth2_ssz = "0.4.1"
eth2_ssz_types = "0.2.2"
eth2_ssz_derive = "0.3.0"
eth2_ssz_derive = "0.3.1"
state_processing = { path = "../../consensus/state_processing" }
tree_hash = "0.4.1"
types = { path = "../../consensus/types" }
@@ -15,12 +15,12 @@ use store::{
consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR},
RelativeEpoch,
};
use types::{BeaconBlockRef, BeaconState, BeaconStateError, ExecPayload, Hash256};
use types::{AbstractExecPayload, BeaconBlockRef, BeaconState, BeaconStateError, Hash256};
type BeaconBlockSubRewardValue = u64;
impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn compute_beacon_block_reward<Payload: ExecPayload<T::EthSpec>>(
pub fn compute_beacon_block_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
block_root: Hash256,
@@ -97,7 +97,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
})
}
fn compute_beacon_block_sync_aggregate_reward<Payload: ExecPayload<T::EthSpec>>(
fn compute_beacon_block_sync_aggregate_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
@@ -111,7 +111,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
}
fn compute_beacon_block_proposer_slashing_reward<Payload: ExecPayload<T::EthSpec>>(
fn compute_beacon_block_proposer_slashing_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
@@ -132,7 +132,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(proposer_slashing_reward)
}
fn compute_beacon_block_attester_slashing_reward<Payload: ExecPayload<T::EthSpec>>(
fn compute_beacon_block_attester_slashing_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &BeaconState<T::EthSpec>,
@@ -155,7 +155,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(attester_slashing_reward)
}
fn compute_beacon_block_attestation_reward_base<Payload: ExecPayload<T::EthSpec>>(
fn compute_beacon_block_attestation_reward_base<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
block_root: Hash256,
@@ -173,7 +173,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Ok(block_attestation_reward)
}
fn compute_beacon_block_attestation_reward_altair<Payload: ExecPayload<T::EthSpec>>(
fn compute_beacon_block_attestation_reward_altair<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &mut BeaconState<T::EthSpec>,
+211 -43
View File
@@ -12,6 +12,7 @@ use crate::block_verification::{
signature_verify_chain_segment, BlockError, ExecutionPendingBlock, GossipVerifiedBlock,
IntoExecutionPendingBlock, PayloadVerificationOutcome, POS_PANDA_BANNER,
};
pub use crate::canonical_head::{CanonicalHead, CanonicalHeadRwLock};
use crate::chain_config::ChainConfig;
use crate::early_attester_cache::EarlyAttesterCache;
use crate::errors::{BeaconChainError as Error, BlockProductionError};
@@ -58,8 +59,10 @@ use crate::validator_pubkey_cache::ValidatorPubkeyCache;
use crate::{metrics, BeaconChainError, BeaconForkChoiceStore, BeaconSnapshot, CachedHead};
use eth2::types::{EventKind, SseBlock, SyncDuty};
use execution_layer::{
BuilderParams, ChainHealth, ExecutionLayer, FailedCondition, PayloadAttributes, PayloadStatus,
BlockProposalContents, BuilderParams, ChainHealth, ExecutionLayer, FailedCondition,
PayloadAttributes, PayloadStatus,
};
pub use fork_choice::CountUnrealized;
use fork_choice::{
AttestationFromBlock, ExecutionStatus, ForkChoice, ForkchoiceUpdateParameters,
InvalidationOperation, PayloadVerificationStatus, ResetPayloadStatuses,
@@ -67,7 +70,7 @@ use fork_choice::{
use futures::channel::mpsc::Sender;
use itertools::process_results;
use itertools::Itertools;
use operation_pool::{AttestationRef, OperationPool, PersistedOperationPool};
use operation_pool::{AttestationRef, OperationPool, PersistedOperationPool, ReceivedPreCapella};
use parking_lot::{Mutex, RwLock};
use proto_array::{CountUnrealizedFull, DoNotReOrg, ProposerHeadError};
use safe_arith::SafeArith;
@@ -79,8 +82,8 @@ use state_processing::{
common::get_attesting_indices_from_state,
per_block_processing,
per_block_processing::{
errors::AttestationValidationError, verify_attestation_for_block_inclusion,
VerifySignatures,
errors::AttestationValidationError, get_expected_withdrawals,
verify_attestation_for_block_inclusion, VerifySignatures,
},
per_slot_processing,
state_advance::{complete_state_advance, partial_state_advance},
@@ -103,9 +106,6 @@ use types::beacon_state::CloneConfig;
use types::consts::merge::INTERVALS_PER_SLOT;
use types::*;
pub use crate::canonical_head::{CanonicalHead, CanonicalHeadRwLock};
pub use fork_choice::CountUnrealized;
pub type ForkChoiceError = fork_choice::Error<crate::ForkChoiceStoreError>;
/// Alias to appease clippy.
@@ -269,7 +269,7 @@ pub trait BeaconChainTypes: Send + Sync + 'static {
}
/// Used internally to split block production into discrete functions.
struct PartialBeaconBlock<E: EthSpec, Payload> {
struct PartialBeaconBlock<E: EthSpec, Payload: AbstractExecPayload<E>> {
state: BeaconState<E>,
slot: Slot,
proposer_index: u64,
@@ -283,7 +283,8 @@ struct PartialBeaconBlock<E: EthSpec, Payload> {
deposits: Vec<Deposit>,
voluntary_exits: Vec<SignedVoluntaryExit>,
sync_aggregate: Option<SyncAggregate<E>>,
prepare_payload_handle: Option<PreparePayloadHandle<Payload>>,
prepare_payload_handle: Option<PreparePayloadHandle<E, Payload>>,
bls_to_execution_changes: Vec<SignedBlsToExecutionChange>,
}
pub type BeaconForkChoice<T> = ForkChoice<
@@ -360,6 +361,9 @@ pub struct BeaconChain<T: BeaconChainTypes> {
/// Maintains a record of which validators we've seen attester slashings for.
pub(crate) observed_attester_slashings:
Mutex<ObservedOperations<AttesterSlashing<T::EthSpec>, T::EthSpec>>,
/// Maintains a record of which validators we've seen BLS to execution changes for.
pub(crate) observed_bls_to_execution_changes:
Mutex<ObservedOperations<SignedBlsToExecutionChange, T::EthSpec>>,
/// The most recently validated light client finality update received on gossip.
pub latest_seen_finality_update: Mutex<Option<LightClientFinalityUpdate<T::EthSpec>>>,
/// The most recently validated light client optimistic update received on gossip.
@@ -959,21 +963,22 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Some(DatabaseBlock::Blinded(block)) => block,
None => return Ok(None),
};
let fork = blinded_block.fork_name(&self.spec)?;
// If we only have a blinded block, load the execution payload from the EL.
let block_message = blinded_block.message();
let execution_payload_header = &block_message
let execution_payload_header = block_message
.execution_payload()
.map_err(|_| Error::BlockVariantLacksExecutionPayload(*block_root))?
.execution_payload_header;
.to_execution_payload_header();
let exec_block_hash = execution_payload_header.block_hash;
let exec_block_hash = execution_payload_header.block_hash();
let execution_payload = self
.execution_layer
.as_ref()
.ok_or(Error::ExecutionLayerMissing)?
.get_payload_by_block_hash(exec_block_hash)
.get_payload_by_block_hash(exec_block_hash, fork)
.await
.map_err(|e| {
Error::ExecutionLayerErrorPayloadReconstruction(exec_block_hash, Box::new(e))
@@ -981,9 +986,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.ok_or(Error::BlockHashMissingFromExecutionLayer(exec_block_hash))?;
// Verify payload integrity.
let header_from_payload = ExecutionPayloadHeader::from(&execution_payload);
if header_from_payload != *execution_payload_header {
for txn in &execution_payload.transactions {
let header_from_payload = ExecutionPayloadHeader::from(execution_payload.to_ref());
if header_from_payload != execution_payload_header {
for txn in execution_payload.transactions() {
debug!(
self.log,
"Reconstructed txn";
@@ -994,8 +999,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
return Err(Error::InconsistentPayloadReconstructed {
slot: blinded_block.slot(),
exec_block_hash,
canonical_transactions_root: execution_payload_header.transactions_root,
reconstructed_transactions_root: header_from_payload.transactions_root,
canonical_transactions_root: execution_payload_header.transactions_root(),
reconstructed_transactions_root: header_from_payload.transactions_root(),
});
}
@@ -2218,6 +2223,79 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
}
/// Verify a signed BLS to execution change before allowing it to propagate on the gossip network.
pub fn verify_bls_to_execution_change_for_http_api(
&self,
bls_to_execution_change: SignedBlsToExecutionChange,
) -> Result<ObservationOutcome<SignedBlsToExecutionChange, T::EthSpec>, Error> {
// Before checking the gossip duplicate filter, check that no prior change is already
// in our op pool. Ignore these messages: do not gossip, do not try to override the pool.
match self
.op_pool
.bls_to_execution_change_in_pool_equals(&bls_to_execution_change)
{
Some(true) => return Ok(ObservationOutcome::AlreadyKnown),
Some(false) => return Err(Error::BlsToExecutionConflictsWithPool),
None => (),
}
// Use the head state to save advancing to the wall-clock slot unnecessarily. The message is
// signed with respect to the genesis fork version, and the slot check for gossip is applied
// separately. This `Arc` clone of the head is nice and cheap.
let head_snapshot = self.head().snapshot;
let head_state = &head_snapshot.beacon_state;
Ok(self
.observed_bls_to_execution_changes
.lock()
.verify_and_observe(bls_to_execution_change, head_state, &self.spec)?)
}
/// Verify a signed BLS to execution change before allowing it to propagate on the gossip network.
pub fn verify_bls_to_execution_change_for_gossip(
&self,
bls_to_execution_change: SignedBlsToExecutionChange,
) -> Result<ObservationOutcome<SignedBlsToExecutionChange, T::EthSpec>, Error> {
// Ignore BLS to execution changes on gossip prior to Capella.
if !self.current_slot_is_post_capella()? {
return Err(Error::BlsToExecutionPriorToCapella);
}
self.verify_bls_to_execution_change_for_http_api(bls_to_execution_change)
.or_else(|e| {
// On gossip treat conflicts the same as duplicates [IGNORE].
match e {
Error::BlsToExecutionConflictsWithPool => Ok(ObservationOutcome::AlreadyKnown),
e => Err(e),
}
})
}
/// Check if the current slot is greater than or equal to the Capella fork epoch.
pub fn current_slot_is_post_capella(&self) -> Result<bool, Error> {
let current_fork = self.spec.fork_name_at_slot::<T::EthSpec>(self.slot()?);
if let ForkName::Base | ForkName::Altair | ForkName::Merge = current_fork {
Ok(false)
} else {
Ok(true)
}
}
/// Import a BLS to execution change to the op pool.
///
/// Return `true` if the change was added to the pool.
pub fn import_bls_to_execution_change(
&self,
bls_to_execution_change: SigVerifiedOp<SignedBlsToExecutionChange, T::EthSpec>,
received_pre_capella: ReceivedPreCapella,
) -> bool {
if self.eth1_chain.is_some() {
self.op_pool
.insert_bls_to_execution_change(bls_to_execution_change, received_pre_capella)
} else {
false
}
}
/// Attempt to obtain sync committee duties from the head.
pub fn sync_committee_duties_from_head(
&self,
@@ -3444,7 +3522,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
///
/// The produced block will not be inherently valid, it must be signed by a block producer.
/// Block signing is out of the scope of this function and should be done by a separate program.
pub async fn produce_block<Payload: ExecPayload<T::EthSpec>>(
pub async fn produce_block<Payload: AbstractExecPayload<T::EthSpec> + 'static>(
self: &Arc<Self>,
randao_reveal: Signature,
slot: Slot,
@@ -3460,7 +3538,9 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
}
/// Same as `produce_block` but allowing for configuration of RANDAO-verification.
pub async fn produce_block_with_verification<Payload: ExecPayload<T::EthSpec>>(
pub async fn produce_block_with_verification<
Payload: AbstractExecPayload<T::EthSpec> + 'static,
>(
self: &Arc<Self>,
randao_reveal: Signature,
slot: Slot,
@@ -3980,7 +4060,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// The provided `state_root_opt` should only ever be set to `Some` if the contained value is
/// equal to the root of `state`. Providing this value will serve as an optimization to avoid
/// performing a tree hash in some scenarios.
pub async fn produce_block_on_state<Payload: ExecPayload<T::EthSpec>>(
pub async fn produce_block_on_state<Payload: AbstractExecPayload<T::EthSpec> + 'static>(
self: &Arc<Self>,
state: BeaconState<T::EthSpec>,
state_root_opt: Option<Hash256>,
@@ -4015,16 +4095,20 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
//
// Wait for the execution layer to return an execution payload (if one is required).
let prepare_payload_handle = partial_beacon_block.prepare_payload_handle.take();
let execution_payload = if let Some(prepare_payload_handle) = prepare_payload_handle {
let execution_payload = prepare_payload_handle
.await
.map_err(BlockProductionError::TokioJoin)?
.ok_or(BlockProductionError::ShuttingDown)??;
Some(execution_payload)
let block_contents = if let Some(prepare_payload_handle) = prepare_payload_handle {
Some(
prepare_payload_handle
.await
.map_err(BlockProductionError::TokioJoin)?
.ok_or(BlockProductionError::ShuttingDown)??,
)
} else {
None
};
//FIXME(sean) waiting for the BN<>EE api for this to stabilize
let kzg_commitments = vec![];
// Part 3/3 (blocking)
//
// Perform the final steps of combining all the parts and computing the state root.
@@ -4034,7 +4118,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
move || {
chain.complete_partial_beacon_block(
partial_beacon_block,
execution_payload,
block_contents,
kzg_commitments,
verification,
)
},
@@ -4045,7 +4130,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.map_err(BlockProductionError::TokioJoin)?
}
fn produce_partial_beacon_block<Payload: ExecPayload<T::EthSpec>>(
fn produce_partial_beacon_block<Payload: AbstractExecPayload<T::EthSpec> + 'static>(
self: &Arc<Self>,
mut state: BeaconState<T::EthSpec>,
state_root_opt: Option<Hash256>,
@@ -4105,7 +4190,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// allows it to run concurrently with things like attestation packing.
let prepare_payload_handle = match &state {
BeaconState::Base(_) | BeaconState::Altair(_) => None,
BeaconState::Merge(_) => {
BeaconState::Merge(_) | BeaconState::Capella(_) | BeaconState::Eip4844(_) => {
let prepare_payload_handle =
get_execution_payload(self.clone(), &state, proposer_index, builder_params)?;
Some(prepare_payload_handle)
@@ -4118,6 +4203,10 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let eth1_data = eth1_chain.eth1_data_for_block_production(&state, &self.spec)?;
let deposits = eth1_chain.deposits_for_block_inclusion(&state, &eth1_data, &self.spec)?;
let bls_to_execution_changes = self
.op_pool
.get_bls_to_execution_changes(&state, &self.spec);
// Iterate through the naive aggregation pool and ensure all the attestations from there
// are included in the operation pool.
let unagg_import_timer =
@@ -4276,13 +4365,15 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
voluntary_exits,
sync_aggregate,
prepare_payload_handle,
bls_to_execution_changes,
})
}
fn complete_partial_beacon_block<Payload: ExecPayload<T::EthSpec>>(
fn complete_partial_beacon_block<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
partial_beacon_block: PartialBeaconBlock<T::EthSpec, Payload>,
execution_payload: Option<Payload>,
block_contents: Option<BlockProposalContents<T::EthSpec, Payload>>,
kzg_commitments: Vec<KzgCommitment>,
verification: ProduceBlockVerification,
) -> Result<BeaconBlockAndState<T::EthSpec, Payload>, BlockProductionError> {
let PartialBeaconBlock {
@@ -4303,6 +4394,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
// this function. We can assume that the handle has already been consumed in order to
// produce said `execution_payload`.
prepare_payload_handle: _,
bls_to_execution_changes,
} = partial_beacon_block;
let inner_block = match &state {
@@ -4358,8 +4450,60 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
voluntary_exits: voluntary_exits.into(),
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: execution_payload
.ok_or(BlockProductionError::MissingExecutionPayload)?,
execution_payload: block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.to_payload()
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
},
}),
BeaconState::Capella(_) => BeaconBlock::Capella(BeaconBlockCapella {
slot,
proposer_index,
parent_root,
state_root: Hash256::zero(),
body: BeaconBlockBodyCapella {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.into(),
attestations: attestations.into(),
deposits: deposits.into(),
voluntary_exits: voluntary_exits.into(),
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.to_payload()
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
bls_to_execution_changes: bls_to_execution_changes.into(),
},
}),
BeaconState::Eip4844(_) => BeaconBlock::Eip4844(BeaconBlockEip4844 {
slot,
proposer_index,
parent_root,
state_root: Hash256::zero(),
body: BeaconBlockBodyEip4844 {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings: proposer_slashings.into(),
attester_slashings: attester_slashings.into(),
attestations: attestations.into(),
deposits: deposits.into(),
voluntary_exits: voluntary_exits.into(),
sync_aggregate: sync_aggregate
.ok_or(BlockProductionError::MissingSyncAggregate)?,
execution_payload: block_contents
.ok_or(BlockProductionError::MissingExecutionPayload)?
.to_payload()
.try_into()
.map_err(|_| BlockProductionError::InvalidPayloadFork)?,
bls_to_execution_changes: bls_to_execution_changes.into(),
blob_kzg_commitments: VariableList::from(kzg_commitments),
},
}),
};
@@ -4614,16 +4758,40 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
return Ok(());
}
let withdrawals = match self.spec.fork_name_at_slot::<T::EthSpec>(prepare_slot) {
ForkName::Base | ForkName::Altair | ForkName::Merge => None,
ForkName::Capella | ForkName::Eip4844 => {
// We must use the advanced state because balances can change at epoch boundaries
// and balances affect withdrawals.
// FIXME(mark)
// Might implement caching here in the future..
let prepare_state = self
.state_at_slot(prepare_slot, StateSkipConfig::WithoutStateRoots)
.map_err(|e| {
error!(self.log, "State advance for withdrawals failed"; "error" => ?e);
e
})?;
Some(get_expected_withdrawals(&prepare_state, &self.spec))
}
}
.transpose()
.map_err(|e| {
error!(self.log, "Error preparing beacon proposer"; "error" => ?e);
e
})
.map(|withdrawals_opt| withdrawals_opt.map(|w| w.into()))
.map_err(Error::PrepareProposerFailed)?;
let head_root = forkchoice_update_params.head_root;
let payload_attributes = PayloadAttributes {
timestamp: self
.slot_clock
let payload_attributes = PayloadAttributes::new(
self.slot_clock
.start_of(prepare_slot)
.ok_or(Error::InvalidSlot(prepare_slot))?
.as_secs(),
prev_randao: pre_payload_attributes.prev_randao,
suggested_fee_recipient: execution_layer.get_suggested_fee_recipient(proposer).await,
};
pre_payload_attributes.prev_randao,
execution_layer.get_suggested_fee_recipient(proposer).await,
withdrawals,
);
debug!(
self.log,
@@ -4772,7 +4940,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
{
// We are a proposer, check for terminal_pow_block_hash
if let Some(terminal_pow_block_hash) = execution_layer
.get_terminal_pow_block_hash(&self.spec, payload_attributes.timestamp)
.get_terminal_pow_block_hash(&self.spec, payload_attributes.timestamp())
.await
.map_err(Error::ForkchoiceUpdate)?
{
@@ -4947,7 +5115,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns `Ok(false)` if the block is pre-Bellatrix, or has `ExecutionStatus::Valid`.
/// Returns `Ok(true)` if the block has `ExecutionStatus::Optimistic` or has
/// `ExecutionStatus::Invalid`.
pub fn is_optimistic_or_invalid_block<Payload: ExecPayload<T::EthSpec>>(
pub fn is_optimistic_or_invalid_block<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: &SignedBeaconBlock<T::EthSpec, Payload>,
) -> Result<bool, BeaconChainError> {
@@ -4973,7 +5141,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
///
/// There is a potential race condition when syncing where the block_root of `head_block` could
/// be pruned from the fork choice store before being read.
pub fn is_optimistic_or_invalid_head_block<Payload: ExecPayload<T::EthSpec>>(
pub fn is_optimistic_or_invalid_head_block<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
head_block: &SignedBeaconBlock<T::EthSpec, Payload>,
) -> Result<bool, BeaconChainError> {
@@ -16,7 +16,7 @@ use std::sync::Arc;
use store::{Error as StoreError, HotColdDB, ItemStore};
use superstruct::superstruct;
use types::{
BeaconBlockRef, BeaconState, BeaconStateError, Checkpoint, Epoch, EthSpec, ExecPayload,
AbstractExecPayload, BeaconBlockRef, BeaconState, BeaconStateError, Checkpoint, Epoch, EthSpec,
Hash256, Slot,
};
@@ -260,7 +260,7 @@ where
self.time = slot
}
fn on_verified_block<Payload: ExecPayload<E>>(
fn on_verified_block<Payload: AbstractExecPayload<E>>(
&mut self,
_block: BeaconBlockRef<E, Payload>,
block_root: Hash256,
@@ -1,20 +1,20 @@
use serde_derive::Serialize;
use std::sync::Arc;
use types::{
beacon_state::CloneConfig, BeaconState, EthSpec, ExecPayload, FullPayload, Hash256,
beacon_state::CloneConfig, AbstractExecPayload, BeaconState, EthSpec, FullPayload, Hash256,
SignedBeaconBlock,
};
/// Represents some block and its associated state. Generally, this will be used for tracking the
/// head, justified head and finalized head.
#[derive(Clone, Serialize, PartialEq, Debug)]
pub struct BeaconSnapshot<E: EthSpec, Payload: ExecPayload<E> = FullPayload<E>> {
pub struct BeaconSnapshot<E: EthSpec, Payload: AbstractExecPayload<E> = FullPayload<E>> {
pub beacon_block: Arc<SignedBeaconBlock<E, Payload>>,
pub beacon_block_root: Hash256,
pub beacon_state: BeaconState<E>,
}
impl<E: EthSpec, Payload: ExecPayload<E>> BeaconSnapshot<E, Payload> {
impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconSnapshot<E, Payload> {
/// Create a new checkpoint.
pub fn new(
beacon_block: Arc<SignedBeaconBlock<E, Payload>>,
@@ -0,0 +1,136 @@
use derivative::Derivative;
use slot_clock::SlotClock;
use crate::beacon_chain::{BeaconChain, BeaconChainTypes, MAXIMUM_GOSSIP_CLOCK_DISPARITY};
use crate::BeaconChainError;
use bls::PublicKey;
use types::{consts::eip4844::BLS_MODULUS, BeaconStateError, BlobsSidecar, Slot};
pub enum BlobError {
/// The blob sidecar is from a slot that is later than the current slot (with respect to the
/// gossip clock disparity).
///
/// ## Peer scoring
///
/// Assuming the local clock is correct, the peer has sent an invalid message.
FutureSlot {
message_slot: Slot,
latest_permissible_slot: Slot,
},
/// The blob sidecar is from a slot that is prior to the earliest permissible slot (with
/// respect to the gossip clock disparity).
///
/// ## Peer scoring
///
/// Assuming the local clock is correct, the peer has sent an invalid message.
PastSlot {
message_slot: Slot,
earliest_permissible_slot: Slot,
},
/// The blob sidecar contains an incorrectly formatted `BLSFieldElement` > `BLS_MODULUS`.
///
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
BlobOutOfRange { blob_index: usize },
/// The blob sidecar contains a KZGCommitment that is not a valid G1 point on
/// the bls curve.
///
/// ## Peer scoring
///
/// The peer has sent an invalid message.
InvalidKZGCommitment,
/// The proposal signature in invalid.
///
/// ## Peer scoring
///
/// The signature on the blob sidecar invalid and the peer is faulty.
ProposalSignatureInvalid,
/// A blob sidecar for this proposer and slot has already been observed.
///
/// ## Peer scoring
///
/// The `proposer` has already proposed a sidecar at this slot. The existing sidecar may or may not
/// be equal to the given sidecar.
RepeatSidecar { proposer: u64, slot: Slot },
/// There was an error whilst processing the sync contribution. It is not known if it is valid or invalid.
///
/// ## Peer scoring
///
/// We were unable to process this sync committee message due to an internal error. It's unclear if the
/// sync committee message is valid.
BeaconChainError(BeaconChainError),
}
impl From<BeaconChainError> for BlobError {
fn from(e: BeaconChainError) -> Self {
BlobError::BeaconChainError(e)
}
}
impl From<BeaconStateError> for BlobError {
fn from(e: BeaconStateError) -> Self {
BlobError::BeaconChainError(BeaconChainError::BeaconStateError(e))
}
}
/// A wrapper around a `BlobsSidecar` that indicates it has been verified w.r.t the corresponding
/// `SignedBeaconBlock`.
#[derive(Derivative)]
#[derivative(Debug(bound = "T: BeaconChainTypes"))]
pub struct VerifiedBlobsSidecar<'a, T: BeaconChainTypes> {
pub blob_sidecar: &'a BlobsSidecar<T::EthSpec>,
}
impl<'a, T: BeaconChainTypes> VerifiedBlobsSidecar<'a, T> {
pub fn verify(
blob_sidecar: &'a BlobsSidecar<T::EthSpec>,
chain: &BeaconChain<T>,
) -> Result<Self, BlobError> {
let blob_slot = blob_sidecar.beacon_block_slot;
// Do not gossip or process blobs from future or past slots.
let latest_permissible_slot = chain
.slot_clock
.now_with_future_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
.ok_or(BeaconChainError::UnableToReadSlot)?;
if blob_slot > latest_permissible_slot {
return Err(BlobError::FutureSlot {
message_slot: latest_permissible_slot,
latest_permissible_slot: blob_slot,
});
}
let earliest_permissible_slot = chain
.slot_clock
.now_with_past_tolerance(MAXIMUM_GOSSIP_CLOCK_DISPARITY)
.ok_or(BeaconChainError::UnableToReadSlot)?;
if blob_slot > earliest_permissible_slot {
return Err(BlobError::PastSlot {
message_slot: earliest_permissible_slot,
earliest_permissible_slot: blob_slot,
});
}
// Verify that blobs are properly formatted
//TODO: add the check while constructing a Blob type from bytes instead of after
for (i, blob) in blob_sidecar.blobs.iter().enumerate() {
if blob.iter().any(|b| *b >= *BLS_MODULUS) {
return Err(BlobError::BlobOutOfRange { blob_index: i });
}
}
// Verify that the KZG proof is a valid G1 point
if PublicKey::deserialize(&blob_sidecar.kzg_aggregate_proof.0).is_err() {
return Err(BlobError::InvalidKZGCommitment);
}
// TODO: Check that we have not already received a sidecar with a valid signature for this slot.
Ok(Self { blob_sidecar })
}
}
+2 -2
View File
@@ -5,10 +5,10 @@ use state_processing::{
common::get_attesting_indices_from_state,
per_block_processing::altair::sync_committee::compute_sync_aggregate_rewards,
};
use types::{BeaconBlockRef, BeaconState, EthSpec, ExecPayload, Hash256};
use types::{AbstractExecPayload, BeaconBlockRef, BeaconState, EthSpec, Hash256};
impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn compute_block_reward<Payload: ExecPayload<T::EthSpec>>(
pub fn compute_block_reward<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
block_root: Hash256,
@@ -88,6 +88,7 @@ use std::time::Duration;
use store::{Error as DBError, HotStateSummary, KeyValueStore, StoreOp};
use task_executor::JoinHandle;
use tree_hash::TreeHash;
use types::ExecPayload;
use types::{
BeaconBlockRef, BeaconState, BeaconStateError, BlindedPayload, ChainSpec, CloneConfig, Epoch,
EthSpec, ExecutionBlockHash, Hash256, InconsistentFork, PublicKey, PublicKeyBytes,
@@ -1185,7 +1186,7 @@ impl<T: BeaconChainTypes> ExecutionPendingBlock<T> {
.message()
.body()
.execution_payload()
.map(|full_payload| full_payload.execution_payload.block_hash);
.map(|full_payload| full_payload.block_hash());
// Ensure the block is a candidate for optimistic import.
if !is_optimistic_candidate_block(&chain, block.slot(), block.parent_root()).await?
@@ -1850,7 +1851,7 @@ fn cheap_state_advance_to_obtain_committees<'a, E: EthSpec>(
}
/// Obtains a read-locked `ValidatorPubkeyCache` from the `chain`.
fn get_validator_pubkey_cache<T: BeaconChainTypes>(
pub fn get_validator_pubkey_cache<T: BeaconChainTypes>(
chain: &BeaconChain<T>,
) -> Result<RwLockReadGuard<ValidatorPubkeyCache<T>>, BlockError<T::EthSpec>> {
chain
+1
View File
@@ -800,6 +800,7 @@ where
observed_voluntary_exits: <_>::default(),
observed_proposer_slashings: <_>::default(),
observed_attester_slashings: <_>::default(),
observed_bls_to_execution_changes: <_>::default(),
latest_seen_finality_update: <_>::default(),
latest_seen_optimistic_update: <_>::default(),
eth1_chain: self.eth1_chain,
@@ -930,8 +930,12 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
.execution_status
.is_optimistic_or_invalid();
self.op_pool
.prune_all(&new_snapshot.beacon_state, self.epoch()?);
self.op_pool.prune_all(
&new_snapshot.beacon_block,
&new_snapshot.beacon_state,
self.epoch()?,
&self.spec,
);
self.observed_block_producers.write().prune(
new_view
@@ -0,0 +1,122 @@
//! Provides tools for checking if a node is ready for the Capella upgrade and following merge
//! transition.
use crate::{BeaconChain, BeaconChainTypes};
use execution_layer::http::{
ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_GET_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V2,
};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::time::Duration;
use types::*;
/// The time before the Capella fork when we will start issuing warnings about preparation.
use super::merge_readiness::SECONDS_IN_A_WEEK;
pub const CAPELLA_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2;
pub const ENGINE_CAPABILITIES_REFRESH_INTERVAL: u64 = 300;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[serde(tag = "type")]
pub enum CapellaReadiness {
/// The execution engine is capella-enabled (as far as we can tell)
Ready,
/// We are connected to an execution engine which doesn't support the V2 engine api methods
V2MethodsNotSupported { error: String },
/// The transition configuration with the EL failed, there might be a problem with
/// connectivity, authentication or a difference in configuration.
ExchangeCapabilitiesFailed { error: String },
/// The user has not configured an execution endpoint
NoExecutionEndpoint,
}
impl fmt::Display for CapellaReadiness {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CapellaReadiness::Ready => {
write!(f, "This node appears ready for Capella.")
}
CapellaReadiness::ExchangeCapabilitiesFailed { error } => write!(
f,
"Could not exchange capabilities with the \
execution endpoint: {}",
error
),
CapellaReadiness::NoExecutionEndpoint => write!(
f,
"The --execution-endpoint flag is not specified, this is a \
requirement post-merge"
),
CapellaReadiness::V2MethodsNotSupported { error } => write!(
f,
"Execution endpoint does not support Capella methods: {}",
error
),
}
}
}
impl<T: BeaconChainTypes> BeaconChain<T> {
/// Returns `true` if capella epoch is set and Capella fork has occurred or will
/// occur within `CAPELLA_READINESS_PREPARATION_SECONDS`
pub fn is_time_to_prepare_for_capella(&self, current_slot: Slot) -> bool {
if let Some(capella_epoch) = self.spec.capella_fork_epoch {
let capella_slot = capella_epoch.start_slot(T::EthSpec::slots_per_epoch());
let capella_readiness_preparation_slots =
CAPELLA_READINESS_PREPARATION_SECONDS / self.spec.seconds_per_slot;
// Return `true` if Capella has happened or is within the preparation time.
current_slot + capella_readiness_preparation_slots > capella_slot
} else {
// The Capella fork epoch has not been defined yet, no need to prepare.
false
}
}
/// Attempts to connect to the EL and confirm that it is ready for capella.
pub async fn check_capella_readiness(&self) -> CapellaReadiness {
if let Some(el) = self.execution_layer.as_ref() {
match el
.get_engine_capabilities(Some(Duration::from_secs(
ENGINE_CAPABILITIES_REFRESH_INTERVAL,
)))
.await
{
Err(e) => {
// The EL was either unreachable or responded with an error
CapellaReadiness::ExchangeCapabilitiesFailed {
error: format!("{:?}", e),
}
}
Ok(capabilities) => {
let mut missing_methods = String::from("Required Methods Unsupported:");
let mut all_good = true;
if !capabilities.get_payload_v2 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_GET_PAYLOAD_V2);
all_good = false;
}
if !capabilities.forkchoice_updated_v2 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_FORKCHOICE_UPDATED_V2);
all_good = false;
}
if !capabilities.new_payload_v2 {
missing_methods.push(' ');
missing_methods.push_str(ENGINE_NEW_PAYLOAD_V2);
all_good = false;
}
if all_good {
CapellaReadiness::Ready
} else {
CapellaReadiness::V2MethodsNotSupported {
error: missing_methods,
}
}
}
}
} else {
CapellaReadiness::NoExecutionEndpoint
}
}
}
@@ -91,6 +91,7 @@ impl Default for ChainConfig {
count_unrealized_full: CountUnrealizedFull::default(),
checkpoint_sync_url_timeout: 60,
prepare_payload_lookahead: Duration::from_secs(4),
// This value isn't actually read except in tests.
optimistic_finalized_sync: true,
}
}
+16 -3
View File
@@ -17,8 +17,9 @@ use ssz_types::Error as SszTypesError;
use state_processing::{
block_signature_verifier::Error as BlockSignatureVerifierError,
per_block_processing::errors::{
AttestationValidationError, AttesterSlashingValidationError, ExitValidationError,
ProposerSlashingValidationError, SyncCommitteeMessageValidationError,
AttestationValidationError, AttesterSlashingValidationError,
BlsExecutionChangeValidationError, ExitValidationError, ProposerSlashingValidationError,
SyncCommitteeMessageValidationError,
},
signature_sets::Error as SignatureSetError,
state_advance::Error as StateAdvanceError,
@@ -69,6 +70,7 @@ pub enum BeaconChainError {
ExitValidationError(ExitValidationError),
ProposerSlashingValidationError(ProposerSlashingValidationError),
AttesterSlashingValidationError(AttesterSlashingValidationError),
BlsExecutionChangeValidationError(BlsExecutionChangeValidationError),
StateSkipTooLarge {
start_slot: Slot,
requested_slot: Slot,
@@ -150,7 +152,7 @@ pub enum BeaconChainError {
},
AddPayloadLogicError,
ExecutionForkChoiceUpdateFailed(execution_layer::Error),
PrepareProposerBlockingFailed(execution_layer::Error),
PrepareProposerFailed(BlockProcessingError),
ExecutionForkChoiceUpdateInvalid {
status: PayloadStatus,
},
@@ -204,6 +206,9 @@ pub enum BeaconChainError {
MissingPersistedForkChoice,
CommitteePromiseFailed(oneshot_broadcast::Error),
MaxCommitteePromises(usize),
BlsToExecutionPriorToCapella,
BlsToExecutionConflictsWithPool,
InconsistentFork(InconsistentFork),
ProposerHeadForkChoiceError(fork_choice::Error<proto_array::Error>),
}
@@ -213,6 +218,7 @@ easy_from_to!(SyncCommitteeMessageValidationError, BeaconChainError);
easy_from_to!(ExitValidationError, BeaconChainError);
easy_from_to!(ProposerSlashingValidationError, BeaconChainError);
easy_from_to!(AttesterSlashingValidationError, BeaconChainError);
easy_from_to!(BlsExecutionChangeValidationError, BeaconChainError);
easy_from_to!(SszTypesError, BeaconChainError);
easy_from_to!(OpPoolError, BeaconChainError);
easy_from_to!(NaiveAggregationError, BeaconChainError);
@@ -227,6 +233,7 @@ easy_from_to!(ForkChoiceStoreError, BeaconChainError);
easy_from_to!(HistoricalBlockError, BeaconChainError);
easy_from_to!(StateAdvanceError, BeaconChainError);
easy_from_to!(BlockReplayError, BeaconChainError);
easy_from_to!(InconsistentFork, BeaconChainError);
#[derive(Debug)]
pub enum BlockProductionError {
@@ -251,6 +258,11 @@ pub enum BlockProductionError {
BlockingFailed(execution_layer::Error),
TerminalPoWBlockLookupFailed(execution_layer::Error),
GetPayloadFailed(execution_layer::Error),
GetBlobsFailed(execution_layer::Error),
BlobPayloadMismatch {
blob_block_hash: ExecutionBlockHash,
payload_block_hash: ExecutionBlockHash,
},
FailedToReadFinalizedBlock(store::Error),
MissingFinalizedBlock(Hash256),
BlockTooLarge(usize),
@@ -259,6 +271,7 @@ pub enum BlockProductionError {
MissingExecutionPayload,
TokioJoin(tokio::task::JoinError),
BeaconChain(BeaconChainError),
InvalidPayloadFork,
}
easy_from_to!(BlockProcessingError, BlockProductionError);
@@ -12,22 +12,23 @@ use crate::{
BeaconChain, BeaconChainError, BeaconChainTypes, BlockError, BlockProductionError,
ExecutionPayloadError,
};
use execution_layer::{BuilderParams, PayloadStatus};
use execution_layer::{BlockProposalContents, BuilderParams, PayloadAttributes, PayloadStatus};
use fork_choice::{InvalidationOperation, PayloadVerificationStatus};
use proto_array::{Block as ProtoBlock, ExecutionStatus};
use slog::{debug, warn};
use slot_clock::SlotClock;
use state_processing::per_block_processing::{
compute_timestamp_at_slot, is_execution_enabled, is_merge_transition_complete,
partially_verify_execution_payload,
compute_timestamp_at_slot, get_expected_withdrawals, is_execution_enabled,
is_merge_transition_complete, partially_verify_execution_payload,
};
use std::sync::Arc;
use tokio::task::JoinHandle;
use tree_hash::TreeHash;
use types::*;
pub type PreparePayloadResult<Payload> = Result<Payload, BlockProductionError>;
pub type PreparePayloadHandle<Payload> = JoinHandle<Option<PreparePayloadResult<Payload>>>;
pub type PreparePayloadResult<E, Payload> =
Result<BlockProposalContents<E, Payload>, BlockProductionError>;
pub type PreparePayloadHandle<E, Payload> = JoinHandle<Option<PreparePayloadResult<E, Payload>>>;
#[derive(PartialEq)]
pub enum AllowOptimisticImport {
@@ -68,8 +69,13 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
// where we do not send the block to the EL at all.
let block_message = block.message();
let payload = block_message.execution_payload()?;
partially_verify_execution_payload(state, block.slot(), payload, &chain.spec)
.map_err(BlockError::PerBlockProcessingError)?;
partially_verify_execution_payload::<_, FullPayload<_>>(
state,
block.slot(),
payload,
&chain.spec,
)
.map_err(BlockError::PerBlockProcessingError)?;
match notify_execution_layer {
NotifyExecutionLayer::No if chain.config.optimistic_finalized_sync => {
@@ -81,7 +87,7 @@ impl<T: BeaconChainTypes> PayloadNotifier<T> {
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
if let Err(e) =
execution_layer.verify_payload_block_hash(&payload.execution_payload)
execution_layer.verify_payload_block_hash(payload.execution_payload_ref())
{
warn!(
chain.log,
@@ -140,7 +146,7 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>(
.ok_or(ExecutionPayloadError::NoExecutionConnection)?;
let new_payload_response = execution_layer
.notify_new_payload(&execution_payload.execution_payload)
.notify_new_payload(&execution_payload.into())
.await;
match new_payload_response {
@@ -158,7 +164,7 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>(
"Invalid execution payload";
"validation_error" => ?validation_error,
"latest_valid_hash" => ?latest_valid_hash,
"execution_block_hash" => ?execution_payload.execution_payload.block_hash,
"execution_block_hash" => ?execution_payload.block_hash(),
"root" => ?block.tree_hash_root(),
"graffiti" => block.body().graffiti().as_utf8_lossy(),
"proposer_index" => block.proposer_index(),
@@ -191,7 +197,7 @@ async fn notify_new_payload<'a, T: BeaconChainTypes>(
chain.log,
"Invalid execution payload block hash";
"validation_error" => ?validation_error,
"execution_block_hash" => ?execution_payload.execution_payload.block_hash,
"execution_block_hash" => ?execution_payload.block_hash(),
"root" => ?block.tree_hash_root(),
"graffiti" => block.body().graffiti().as_utf8_lossy(),
"proposer_index" => block.proposer_index(),
@@ -344,7 +350,7 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
}
};
if is_merge_transition_complete || execution_payload != &<_>::default() {
if is_merge_transition_complete || !execution_payload.is_default_with_empty_roots() {
let expected_timestamp = chain
.slot_clock
.start_of(block.slot())
@@ -382,13 +388,13 @@ pub fn validate_execution_payload_for_gossip<T: BeaconChainTypes>(
/// https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/merge/validator.md#block-proposal
pub fn get_execution_payload<
T: BeaconChainTypes,
Payload: ExecPayload<T::EthSpec> + Default + Send + 'static,
Payload: AbstractExecPayload<T::EthSpec> + 'static,
>(
chain: Arc<BeaconChain<T>>,
state: &BeaconState<T::EthSpec>,
proposer_index: u64,
builder_params: BuilderParams,
) -> Result<PreparePayloadHandle<Payload>, BlockProductionError> {
) -> Result<PreparePayloadHandle<T::EthSpec, Payload>, BlockProductionError> {
// Compute all required values from the `state` now to avoid needing to pass it into a spawned
// task.
let spec = &chain.spec;
@@ -398,7 +404,15 @@ pub fn get_execution_payload<
compute_timestamp_at_slot(state, state.slot(), spec).map_err(BeaconStateError::from)?;
let random = *state.get_randao_mix(current_epoch)?;
let latest_execution_payload_header_block_hash =
state.latest_execution_payload_header()?.block_hash;
state.latest_execution_payload_header()?.block_hash();
let withdrawals = match state {
&BeaconState::Capella(_) | &BeaconState::Eip4844(_) => {
Some(get_expected_withdrawals(state, spec)?.into())
}
&BeaconState::Merge(_) => None,
// These shouldn't happen but they're here to make the pattern irrefutable
&BeaconState::Base(_) | &BeaconState::Altair(_) => None,
};
// Spawn a task to obtain the execution payload from the EL via a series of async calls. The
// `join_handle` can be used to await the result of the function.
@@ -415,6 +429,7 @@ pub fn get_execution_payload<
proposer_index,
latest_execution_payload_header_block_hash,
builder_params,
withdrawals,
)
.await
},
@@ -448,13 +463,15 @@ pub async fn prepare_execution_payload<T, Payload>(
proposer_index: u64,
latest_execution_payload_header_block_hash: ExecutionBlockHash,
builder_params: BuilderParams,
) -> Result<Payload, BlockProductionError>
withdrawals: Option<Vec<Withdrawal>>,
) -> Result<BlockProposalContents<T::EthSpec, Payload>, BlockProductionError>
where
T: BeaconChainTypes,
Payload: ExecPayload<T::EthSpec> + Default,
Payload: AbstractExecPayload<T::EthSpec>,
{
let current_epoch = builder_params.slot.epoch(T::EthSpec::slots_per_epoch());
let spec = &chain.spec;
let fork = spec.fork_name_at_slot::<T::EthSpec>(builder_params.slot);
let execution_layer = chain
.execution_layer
.as_ref()
@@ -468,7 +485,7 @@ where
if is_terminal_block_hash_set && !is_activation_epoch_reached {
// Use the "empty" payload if there's a terminal block hash, but we haven't reached the
// terminal block epoch yet.
return Ok(<_>::default());
return BlockProposalContents::default_at_fork(fork).map_err(Into::into);
}
let terminal_pow_block_hash = execution_layer
@@ -481,7 +498,7 @@ where
} else {
// If the merge transition hasn't occurred yet and the EL hasn't found the terminal
// block, return an "empty" payload.
return Ok(<_>::default());
return BlockProposalContents::default_at_fork(fork).map_err(Into::into);
}
} else {
latest_execution_payload_header_block_hash
@@ -505,21 +522,26 @@ where
.await
.map_err(BlockProductionError::BeaconChain)?;
let suggested_fee_recipient = execution_layer
.get_suggested_fee_recipient(proposer_index)
.await;
let payload_attributes =
PayloadAttributes::new(timestamp, random, suggested_fee_recipient, withdrawals);
// Note: the suggested_fee_recipient is stored in the `execution_layer`, it will add this parameter.
//
// This future is not executed here, it's up to the caller to await it.
let execution_payload = execution_layer
let block_contents = execution_layer
.get_payload::<Payload>(
parent_hash,
timestamp,
random,
proposer_index,
&payload_attributes,
forkchoice_update_params,
builder_params,
fork,
&chain.spec,
)
.await
.map_err(BlockProductionError::GetPayloadFailed)?;
Ok(execution_payload)
Ok(block_contents)
}
+2
View File
@@ -7,11 +7,13 @@ mod beacon_chain;
mod beacon_fork_choice_store;
pub mod beacon_proposer_cache;
mod beacon_snapshot;
pub mod blob_verification;
pub mod block_reward;
mod block_times_cache;
mod block_verification;
pub mod builder;
pub mod canonical_head;
pub mod capella_readiness;
pub mod chain_config;
mod early_attester_cache;
mod errors;
@@ -8,7 +8,7 @@ use std::fmt::Write;
use types::*;
/// The time before the Bellatrix fork when we will start issuing warnings about preparation.
const SECONDS_IN_A_WEEK: u64 = 604800;
pub const SECONDS_IN_A_WEEK: u64 = 604800;
pub const MERGE_READINESS_PREPARATION_SECONDS: u64 = SECONDS_IN_A_WEEK * 2;
#[derive(Default, Debug, Serialize, Deserialize)]
+16
View File
@@ -972,6 +972,22 @@ lazy_static! {
"beacon_pre_finalization_block_lookup_count",
"Number of block roots subject to single block lookups"
);
/*
* Blob sidecar Verification
*/
pub static ref BLOBS_SIDECAR_PROCESSING_REQUESTS: Result<IntCounter> = try_create_int_counter(
"beacon_blobs_sidecar_processing_requests_total",
"Count of all blob sidecars submitted for processing"
);
pub static ref BLOBS_SIDECAR_PROCESSING_SUCCESSES: Result<IntCounter> = try_create_int_counter(
"beacon_blobs_sidecar_processing_successes_total",
"Number of blob sidecars verified for gossip"
);
pub static ref BLOBS_SIDECAR_GOSSIP_VERIFICATION_TIMES: Result<Histogram> = try_create_histogram(
"beacon_blobs_sidecar_gossip_verification_seconds",
"Full runtime of blob sidecars gossip verification"
);
}
// Fifth lazy-static block is used to account for macro recursion limit.
@@ -1,12 +1,12 @@
use derivative::Derivative;
use smallvec::SmallVec;
use smallvec::{smallvec, SmallVec};
use ssz::{Decode, Encode};
use state_processing::{SigVerifiedOp, VerifyOperation};
use std::collections::HashSet;
use std::marker::PhantomData;
use types::{
AttesterSlashing, BeaconState, ChainSpec, EthSpec, ForkName, ProposerSlashing,
SignedVoluntaryExit, Slot,
SignedBlsToExecutionChange, SignedVoluntaryExit, Slot,
};
/// Number of validator indices to store on the stack in `observed_validators`.
@@ -39,7 +39,7 @@ pub enum ObservationOutcome<T: Encode + Decode, E: EthSpec> {
AlreadyKnown,
}
/// Trait for exits and slashings which can be observed using `ObservedOperations`.
/// Trait for operations which can be observed using `ObservedOperations`.
pub trait ObservableOperation<E: EthSpec>: VerifyOperation<E> + Sized {
/// The set of validator indices involved in this operation.
///
@@ -49,13 +49,13 @@ pub trait ObservableOperation<E: EthSpec>: VerifyOperation<E> + Sized {
impl<E: EthSpec> ObservableOperation<E> for SignedVoluntaryExit {
fn observed_validators(&self) -> SmallVec<[u64; SMALL_VEC_SIZE]> {
std::iter::once(self.message.validator_index).collect()
smallvec![self.message.validator_index]
}
}
impl<E: EthSpec> ObservableOperation<E> for ProposerSlashing {
fn observed_validators(&self) -> SmallVec<[u64; SMALL_VEC_SIZE]> {
std::iter::once(self.signed_header_1.message.proposer_index).collect()
smallvec![self.signed_header_1.message.proposer_index]
}
}
@@ -80,6 +80,12 @@ impl<E: EthSpec> ObservableOperation<E> for AttesterSlashing<E> {
}
}
impl<E: EthSpec> ObservableOperation<E> for SignedBlsToExecutionChange {
fn observed_validators(&self) -> SmallVec<[u64; SMALL_VEC_SIZE]> {
smallvec![self.message.validator_index]
}
}
impl<T: ObservableOperation<E>, E: EthSpec> ObservedOperations<T, E> {
pub fn verify_and_observe(
&mut self,
@@ -1,6 +1,8 @@
//! Utilities for managing database schema changes.
mod migration_schema_v12;
mod migration_schema_v13;
mod migration_schema_v14;
mod migration_schema_v15;
use crate::beacon_chain::{BeaconChainTypes, ETH1_CACHE_DB_KEY};
use crate::eth1_chain::SszEth1;
@@ -114,6 +116,22 @@ pub fn migrate_schema<T: BeaconChainTypes>(
Ok(())
}
(SchemaVersion(13), SchemaVersion(14)) => {
let ops = migration_schema_v14::upgrade_to_v14::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
(SchemaVersion(14), SchemaVersion(13)) => {
let ops = migration_schema_v14::downgrade_from_v14::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
(SchemaVersion(14), SchemaVersion(15)) => {
let ops = migration_schema_v15::upgrade_to_v15::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
(SchemaVersion(15), SchemaVersion(14)) => {
let ops = migration_schema_v15::downgrade_from_v15::<T>(db.clone(), log)?;
db.store_schema_version_atomically(to, ops)
}
// Anything else is an error.
(_, _) => Err(HotColdDBError::UnsupportedSchemaVersion {
target_version: to,
@@ -168,16 +168,14 @@ pub fn downgrade_from_v12<T: BeaconChainTypes>(
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
// Load a V12 op pool and transform it to V5.
let PersistedOperationPoolV12 {
let PersistedOperationPoolV12::<T::EthSpec> {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
} = if let Some(PersistedOperationPool::<T::EthSpec>::V12(op_pool)) =
db.get_item(&OP_POOL_DB_KEY)?
{
op_pool
} = if let Some(op_pool_v12) = db.get_item(&OP_POOL_DB_KEY)? {
op_pool_v12
} else {
debug!(log, "Nothing to do, no operation pool stored");
return Ok(vec![]);
@@ -0,0 +1,125 @@
use crate::beacon_chain::{BeaconChainTypes, OP_POOL_DB_KEY};
use operation_pool::{
PersistedOperationPool, PersistedOperationPoolV12, PersistedOperationPoolV14,
};
use slog::{debug, error, info, Logger};
use slot_clock::SlotClock;
use std::sync::Arc;
use std::time::Duration;
use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem};
use types::{EthSpec, Hash256, Slot};
/// The slot clock isn't usually available before the database is initialized, so we construct a
/// temporary slot clock by reading the genesis state. It should always exist if the database is
/// initialized at a prior schema version, however we still handle the lack of genesis state
/// gracefully.
fn get_slot_clock<T: BeaconChainTypes>(
db: &HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>,
log: &Logger,
) -> Result<Option<T::SlotClock>, Error> {
let spec = db.get_chain_spec();
let genesis_block = if let Some(block) = db.get_blinded_block(&Hash256::zero())? {
block
} else {
error!(log, "Missing genesis block");
return Ok(None);
};
let genesis_state =
if let Some(state) = db.get_state(&genesis_block.state_root(), Some(Slot::new(0)))? {
state
} else {
error!(log, "Missing genesis state"; "state_root" => ?genesis_block.state_root());
return Ok(None);
};
Ok(Some(T::SlotClock::new(
spec.genesis_slot,
Duration::from_secs(genesis_state.genesis_time()),
Duration::from_secs(spec.seconds_per_slot),
)))
}
pub fn upgrade_to_v14<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
// Load a V12 op pool and transform it to V14.
let PersistedOperationPoolV12::<T::EthSpec> {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
} = if let Some(op_pool_v12) = db.get_item(&OP_POOL_DB_KEY)? {
op_pool_v12
} else {
debug!(log, "Nothing to do, no operation pool stored");
return Ok(vec![]);
};
// initialize with empty vector
let bls_to_execution_changes = vec![];
let v14 = PersistedOperationPool::V14(PersistedOperationPoolV14 {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
bls_to_execution_changes,
});
Ok(vec![v14.as_kv_store_op(OP_POOL_DB_KEY)])
}
pub fn downgrade_from_v14<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
// We cannot downgrade from V14 once the Capella fork has been reached because there will
// be HistoricalSummaries stored in the database instead of HistoricalRoots and prior versions
// of Lighthouse can't handle that.
if let Some(capella_fork_epoch) = db.get_chain_spec().capella_fork_epoch {
let current_epoch = get_slot_clock::<T>(&db, &log)?
.and_then(|clock| clock.now())
.map(|slot| slot.epoch(T::EthSpec::slots_per_epoch()))
.ok_or(Error::SlotClockUnavailableForMigration)?;
if current_epoch >= capella_fork_epoch {
error!(
log,
"Capella already active: v14+ is mandatory";
"current_epoch" => current_epoch,
"capella_fork_epoch" => capella_fork_epoch,
);
return Err(Error::UnableToDowngrade);
}
}
// Load a V14 op pool and transform it to V12.
let PersistedOperationPoolV14::<T::EthSpec> {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
bls_to_execution_changes,
} = if let Some(op_pool) = db.get_item(&OP_POOL_DB_KEY)? {
op_pool
} else {
debug!(log, "Nothing to do, no operation pool stored");
return Ok(vec![]);
};
info!(
log,
"Dropping bls_to_execution_changes from pool";
"count" => bls_to_execution_changes.len(),
);
let v12 = PersistedOperationPoolV12 {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
};
Ok(vec![v12.as_kv_store_op(OP_POOL_DB_KEY)])
}
@@ -0,0 +1,76 @@
use crate::beacon_chain::{BeaconChainTypes, OP_POOL_DB_KEY};
use operation_pool::{
PersistedOperationPool, PersistedOperationPoolV14, PersistedOperationPoolV15,
};
use slog::{debug, info, Logger};
use std::sync::Arc;
use store::{Error, HotColdDB, KeyValueStoreOp, StoreItem};
pub fn upgrade_to_v15<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
// Load a V14 op pool and transform it to V15.
let PersistedOperationPoolV14::<T::EthSpec> {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
bls_to_execution_changes,
} = if let Some(op_pool_v14) = db.get_item(&OP_POOL_DB_KEY)? {
op_pool_v14
} else {
debug!(log, "Nothing to do, no operation pool stored");
return Ok(vec![]);
};
let v15 = PersistedOperationPool::V15(PersistedOperationPoolV15 {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
bls_to_execution_changes,
// Initialize with empty set
capella_bls_change_broadcast_indices: <_>::default(),
});
Ok(vec![v15.as_kv_store_op(OP_POOL_DB_KEY)])
}
pub fn downgrade_from_v15<T: BeaconChainTypes>(
db: Arc<HotColdDB<T::EthSpec, T::HotStore, T::ColdStore>>,
log: Logger,
) -> Result<Vec<KeyValueStoreOp>, Error> {
// Load a V15 op pool and transform it to V14.
let PersistedOperationPoolV15::<T::EthSpec> {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
bls_to_execution_changes,
capella_bls_change_broadcast_indices,
} = if let Some(op_pool) = db.get_item(&OP_POOL_DB_KEY)? {
op_pool
} else {
debug!(log, "Nothing to do, no operation pool stored");
return Ok(vec![]);
};
info!(
log,
"Forgetting address changes for Capella broadcast";
"count" => capella_bls_change_broadcast_indices.len(),
);
let v14 = PersistedOperationPoolV14 {
attestations,
sync_contributions,
attester_slashings,
proposer_slashings,
voluntary_exits,
bls_to_execution_changes,
};
Ok(vec![v14.as_kv_store_op(OP_POOL_DB_KEY)])
}
@@ -6,10 +6,10 @@ use slog::error;
use state_processing::per_block_processing::altair::sync_committee::compute_sync_aggregate_rewards;
use std::collections::HashMap;
use store::RelativeEpoch;
use types::{BeaconBlockRef, BeaconState, ExecPayload};
use types::{AbstractExecPayload, BeaconBlockRef, BeaconState};
impl<T: BeaconChainTypes> BeaconChain<T> {
pub fn compute_sync_committee_rewards<Payload: ExecPayload<T::EthSpec>>(
pub fn compute_sync_committee_rewards<Payload: AbstractExecPayload<T::EthSpec>>(
&self,
block: BeaconBlockRef<'_, T::EthSpec, Payload>,
state: &mut BeaconState<T::EthSpec>,
+118 -12
View File
@@ -13,17 +13,17 @@ use crate::{
StateSkipConfig,
};
use bls::get_withdrawal_credentials;
use execution_layer::test_utils::DEFAULT_JWT_SECRET;
use execution_layer::{
auth::JwtKey,
test_utils::{
ExecutionBlockGenerator, MockExecutionLayer, TestingBuilder, DEFAULT_TERMINAL_BLOCK,
ExecutionBlockGenerator, MockExecutionLayer, TestingBuilder, DEFAULT_JWT_SECRET,
DEFAULT_TERMINAL_BLOCK,
},
ExecutionLayer,
};
use fork_choice::CountUnrealized;
use futures::channel::mpsc::Receiver;
pub use genesis::{interop_genesis_state, DEFAULT_ETH1_BLOCK_HASH};
pub use genesis::{interop_genesis_state_with_eth1, DEFAULT_ETH1_BLOCK_HASH};
use int_to_bytes::int_to_bytes32;
use merkle_proof::MerkleTree;
use parking_lot::Mutex;
@@ -149,6 +149,7 @@ pub struct Builder<T: BeaconChainTypes> {
eth_spec_instance: T::EthSpec,
spec: Option<ChainSpec>,
validator_keypairs: Option<Vec<Keypair>>,
withdrawal_keypairs: Vec<Option<Keypair>>,
chain_config: Option<ChainConfig>,
store_config: Option<StoreConfig>,
#[allow(clippy::type_complexity)]
@@ -180,7 +181,7 @@ impl<E: EthSpec> Builder<EphemeralHarnessType<E>> {
.unwrap(),
);
let mutator = move |builder: BeaconChainBuilder<_>| {
let genesis_state = interop_genesis_state::<E>(
let genesis_state = interop_genesis_state_with_eth1::<E>(
&validator_keypairs,
HARNESS_GENESIS_TIME,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
@@ -241,7 +242,7 @@ impl<E: EthSpec> Builder<DiskHarnessType<E>> {
.expect("cannot build without validator keypairs");
let mutator = move |builder: BeaconChainBuilder<_>| {
let genesis_state = interop_genesis_state::<E>(
let genesis_state = interop_genesis_state_with_eth1::<E>(
&validator_keypairs,
HARNESS_GENESIS_TIME,
Hash256::from_slice(DEFAULT_ETH1_BLOCK_HASH),
@@ -283,6 +284,7 @@ where
eth_spec_instance,
spec: None,
validator_keypairs: None,
withdrawal_keypairs: vec![],
chain_config: None,
store_config: None,
store: None,
@@ -308,6 +310,26 @@ where
self
}
/// Initializes the BLS withdrawal keypairs for `num_keypairs` validators to
/// the "determistic" values, regardless of wether or not the validator has
/// a BLS or execution address in the genesis deposits.
///
/// This aligns with the withdrawal commitments used in the "interop"
/// genesis states.
pub fn deterministic_withdrawal_keypairs(self, num_keypairs: usize) -> Self {
self.withdrawal_keypairs(
types::test_utils::generate_deterministic_keypairs(num_keypairs)
.into_iter()
.map(Option::Some)
.collect(),
)
}
pub fn withdrawal_keypairs(mut self, withdrawal_keypairs: Vec<Option<Keypair>>) -> Self {
self.withdrawal_keypairs = withdrawal_keypairs;
self
}
pub fn default_spec(self) -> Self {
self.spec_or_default(None)
}
@@ -385,15 +407,43 @@ where
self
}
pub fn recalculate_fork_times_with_genesis(mut self, genesis_time: u64) -> Self {
let mock = self
.mock_execution_layer
.as_mut()
.expect("must have mock execution layer to recalculate fork times");
let spec = self
.spec
.clone()
.expect("cannot recalculate fork times without spec");
mock.server.execution_block_generator().shanghai_time =
spec.capella_fork_epoch.map(|epoch| {
genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
mock.server.execution_block_generator().eip4844_time =
spec.eip4844_fork_epoch.map(|epoch| {
genesis_time + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
self
}
pub fn mock_execution_layer(mut self) -> Self {
let spec = self.spec.clone().expect("cannot build without spec");
let shanghai_time = spec.capella_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
let eip4844_time = spec.eip4844_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
let mock = MockExecutionLayer::new(
self.runtime.task_executor.clone(),
spec.terminal_total_difficulty,
DEFAULT_TERMINAL_BLOCK,
spec.terminal_block_hash,
spec.terminal_block_hash_activation_epoch,
shanghai_time,
eip4844_time,
None,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
spec,
None,
);
self.execution_layer = Some(mock.el.clone());
@@ -401,19 +451,30 @@ where
self
}
pub fn mock_execution_layer_with_builder(mut self, beacon_url: SensitiveUrl) -> Self {
pub fn mock_execution_layer_with_builder(
mut self,
beacon_url: SensitiveUrl,
builder_threshold: Option<u128>,
) -> Self {
// Get a random unused port
let port = unused_port::unused_tcp_port().unwrap();
let builder_url = SensitiveUrl::parse(format!("http://127.0.0.1:{port}").as_str()).unwrap();
let spec = self.spec.clone().expect("cannot build without spec");
let shanghai_time = spec.capella_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
let eip4844_time = spec.eip4844_fork_epoch.map(|epoch| {
HARNESS_GENESIS_TIME + spec.seconds_per_slot * E::slots_per_epoch() * epoch.as_u64()
});
let mock_el = MockExecutionLayer::new(
self.runtime.task_executor.clone(),
spec.terminal_total_difficulty,
DEFAULT_TERMINAL_BLOCK,
spec.terminal_block_hash,
spec.terminal_block_hash_activation_epoch,
shanghai_time,
eip4844_time,
builder_threshold,
Some(JwtKey::from_slice(&DEFAULT_JWT_SECRET).unwrap()),
spec.clone(),
Some(builder_url.clone()),
)
.move_to_terminal_block();
@@ -505,6 +566,7 @@ where
spec: chain.spec.clone(),
chain: Arc::new(chain),
validator_keypairs,
withdrawal_keypairs: self.withdrawal_keypairs,
shutdown_receiver: Arc::new(Mutex::new(shutdown_receiver)),
runtime: self.runtime,
mock_execution_layer: self.mock_execution_layer,
@@ -520,6 +582,12 @@ where
/// Used for testing.
pub struct BeaconChainHarness<T: BeaconChainTypes> {
pub validator_keypairs: Vec<Keypair>,
/// Optional BLS withdrawal keys for each validator.
///
/// If a validator index is missing from this vec or their entry is `None` then either
/// no BLS withdrawal key was set for them (they had an address from genesis) or the test
/// initializer neglected to set this field.
pub withdrawal_keypairs: Vec<Option<Keypair>>,
pub chain: Arc<BeaconChain<T>>,
pub spec: ChainSpec,
@@ -1431,6 +1499,44 @@ where
.sign(sk, &fork, genesis_validators_root, &self.chain.spec)
}
pub fn make_bls_to_execution_change(
&self,
validator_index: u64,
address: Address,
) -> SignedBlsToExecutionChange {
let keypair = self.get_withdrawal_keypair(validator_index);
self.make_bls_to_execution_change_with_keys(
validator_index,
address,
&keypair.pk,
&keypair.sk,
)
}
pub fn make_bls_to_execution_change_with_keys(
&self,
validator_index: u64,
address: Address,
pubkey: &PublicKey,
secret_key: &SecretKey,
) -> SignedBlsToExecutionChange {
let genesis_validators_root = self.chain.genesis_validators_root;
BlsToExecutionChange {
validator_index,
from_bls_pubkey: pubkey.compress(),
to_execution_address: address,
}
.sign(secret_key, genesis_validators_root, &self.chain.spec)
}
pub fn get_withdrawal_keypair(&self, validator_index: u64) -> &Keypair {
self.withdrawal_keypairs
.get(validator_index as usize)
.expect("BLS withdrawal key missing from harness")
.as_ref()
.expect("no withdrawal key for validator")
}
pub fn add_voluntary_exit(
&self,
block: &mut BeaconBlock<E>,
+167
View File
@@ -0,0 +1,167 @@
#![cfg(not(debug_assertions))] // Tests run too slow in debug.
use beacon_chain::test_utils::BeaconChainHarness;
use execution_layer::test_utils::Block;
use types::*;
const VALIDATOR_COUNT: usize = 32;
type E = MainnetEthSpec;
fn verify_execution_payload_chain<T: EthSpec>(chain: &[FullPayload<T>]) {
let mut prev_ep: Option<FullPayload<T>> = None;
for ep in chain {
assert!(!ep.is_default_with_empty_roots());
assert!(ep.block_hash() != ExecutionBlockHash::zero());
// Check against previous `ExecutionPayload`.
if let Some(prev_ep) = prev_ep {
assert_eq!(prev_ep.block_hash(), ep.parent_hash());
assert_eq!(prev_ep.block_number() + 1, ep.block_number());
assert!(ep.timestamp() > prev_ep.timestamp());
}
prev_ep = Some(ep.clone());
}
}
#[tokio::test]
async fn base_altair_merge_capella() {
let altair_fork_epoch = Epoch::new(4);
let altair_fork_slot = altair_fork_epoch.start_slot(E::slots_per_epoch());
let bellatrix_fork_epoch = Epoch::new(8);
let merge_fork_slot = bellatrix_fork_epoch.start_slot(E::slots_per_epoch());
let capella_fork_epoch = Epoch::new(12);
let capella_fork_slot = capella_fork_epoch.start_slot(E::slots_per_epoch());
let mut spec = E::default_spec();
spec.altair_fork_epoch = Some(altair_fork_epoch);
spec.bellatrix_fork_epoch = Some(bellatrix_fork_epoch);
spec.capella_fork_epoch = Some(capella_fork_epoch);
let harness = BeaconChainHarness::builder(E::default())
.spec(spec)
.logger(logging::test_logger())
.deterministic_keypairs(VALIDATOR_COUNT)
.fresh_ephemeral_store()
.mock_execution_layer()
.build();
/*
* Start with the base fork.
*/
assert!(harness.chain.head_snapshot().beacon_block.as_base().is_ok());
/*
* Do the Altair fork.
*/
harness.extend_to_slot(altair_fork_slot).await;
let altair_head = &harness.chain.head_snapshot().beacon_block;
assert!(altair_head.as_altair().is_ok());
assert_eq!(altair_head.slot(), altair_fork_slot);
/*
* Do the merge fork, without a terminal PoW block.
*/
harness.extend_to_slot(merge_fork_slot).await;
let merge_head = &harness.chain.head_snapshot().beacon_block;
assert!(merge_head.as_merge().is_ok());
assert_eq!(merge_head.slot(), merge_fork_slot);
assert!(
merge_head
.message()
.body()
.execution_payload()
.unwrap()
.is_default_with_empty_roots(),
"Merge head is default payload"
);
/*
* Next merge block shouldn't include an exec payload.
*/
harness.extend_slots(1).await;
let one_after_merge_head = &harness.chain.head_snapshot().beacon_block;
assert!(
one_after_merge_head
.message()
.body()
.execution_payload()
.unwrap()
.is_default_with_empty_roots(),
"One after merge head is default payload"
);
assert_eq!(one_after_merge_head.slot(), merge_fork_slot + 1);
/*
* Trigger the terminal PoW block.
*/
harness
.execution_block_generator()
.move_to_terminal_block()
.unwrap();
// Add a slot duration to get to the next slot
let timestamp = harness.get_timestamp_at_slot() + harness.spec.seconds_per_slot;
harness
.execution_block_generator()
.modify_last_block(|block| {
if let Block::PoW(terminal_block) = block {
terminal_block.timestamp = timestamp;
}
});
harness.extend_slots(1).await;
let two_after_merge_head = &harness.chain.head_snapshot().beacon_block;
assert!(
two_after_merge_head
.message()
.body()
.execution_payload()
.unwrap()
.is_default_with_empty_roots(),
"Two after merge head is default payload"
);
assert_eq!(two_after_merge_head.slot(), merge_fork_slot + 2);
/*
* Next merge block should include an exec payload.
*/
let mut execution_payloads = vec![];
for _ in (merge_fork_slot.as_u64() + 3)..capella_fork_slot.as_u64() {
harness.extend_slots(1).await;
let block = &harness.chain.head_snapshot().beacon_block;
let full_payload: FullPayload<E> = block
.message()
.body()
.execution_payload()
.unwrap()
.clone()
.into();
// pre-capella shouldn't have withdrawals
assert!(full_payload.withdrawals_root().is_err());
execution_payloads.push(full_payload);
}
/*
* Should enter capella fork now.
*/
for _ in 0..16 {
harness.extend_slots(1).await;
let block = &harness.chain.head_snapshot().beacon_block;
let full_payload: FullPayload<E> = block
.message()
.body()
.execution_payload()
.unwrap()
.clone()
.into();
// post-capella should have withdrawals
assert!(full_payload.withdrawals_root().is_ok());
execution_payloads.push(full_payload);
}
verify_execution_payload_chain(execution_payloads.as_slice());
}
+1
View File
@@ -1,6 +1,7 @@
mod attestation_production;
mod attestation_verification;
mod block_verification;
mod capella;
mod merge;
mod op_verification;
mod payload_invalidation;
+34 -23
View File
@@ -12,17 +12,14 @@ fn verify_execution_payload_chain<T: EthSpec>(chain: &[FullPayload<T>]) {
let mut prev_ep: Option<FullPayload<T>> = None;
for ep in chain {
assert!(*ep != FullPayload::default());
assert!(!ep.is_default_with_empty_roots());
assert!(ep.block_hash() != ExecutionBlockHash::zero());
// Check against previous `ExecutionPayload`.
if let Some(prev_ep) = prev_ep {
assert_eq!(prev_ep.block_hash(), ep.execution_payload.parent_hash);
assert_eq!(
prev_ep.execution_payload.block_number + 1,
ep.execution_payload.block_number
);
assert!(ep.execution_payload.timestamp > prev_ep.execution_payload.timestamp);
assert_eq!(prev_ep.block_hash(), ep.parent_hash());
assert_eq!(prev_ep.block_number() + 1, ep.block_number());
assert!(ep.timestamp() > prev_ep.timestamp());
}
prev_ep = Some(ep.clone());
}
@@ -89,7 +86,7 @@ async fn merge_with_terminal_block_hash_override() {
if i == 0 {
assert_eq!(execution_payload.block_hash(), genesis_pow_block_hash);
}
execution_payloads.push(execution_payload);
execution_payloads.push(execution_payload.into());
}
verify_execution_payload_chain(execution_payloads.as_slice());
@@ -141,9 +138,14 @@ async fn base_altair_merge_with_terminal_block_after_fork() {
let merge_head = &harness.chain.head_snapshot().beacon_block;
assert!(merge_head.as_merge().is_ok());
assert_eq!(merge_head.slot(), merge_fork_slot);
assert_eq!(
*merge_head.message().body().execution_payload().unwrap(),
FullPayload::default()
assert!(
merge_head
.message()
.body()
.execution_payload()
.unwrap()
.is_default_with_empty_roots(),
"Merge head is default payload"
);
/*
@@ -153,13 +155,14 @@ async fn base_altair_merge_with_terminal_block_after_fork() {
harness.extend_slots(1).await;
let one_after_merge_head = &harness.chain.head_snapshot().beacon_block;
assert_eq!(
*one_after_merge_head
assert!(
one_after_merge_head
.message()
.body()
.execution_payload()
.unwrap(),
FullPayload::default()
.unwrap()
.is_default_with_empty_roots(),
"One after merge head is default payload"
);
assert_eq!(one_after_merge_head.slot(), merge_fork_slot + 1);
@@ -185,26 +188,34 @@ async fn base_altair_merge_with_terminal_block_after_fork() {
harness.extend_slots(1).await;
let one_after_merge_head = &harness.chain.head_snapshot().beacon_block;
assert_eq!(
*one_after_merge_head
let two_after_merge_head = &harness.chain.head_snapshot().beacon_block;
assert!(
two_after_merge_head
.message()
.body()
.execution_payload()
.unwrap(),
FullPayload::default()
.unwrap()
.is_default_with_empty_roots(),
"Two after merge head is default payload"
);
assert_eq!(one_after_merge_head.slot(), merge_fork_slot + 2);
assert_eq!(two_after_merge_head.slot(), merge_fork_slot + 2);
/*
* Next merge block should include an exec payload.
*/
for _ in 0..4 {
harness.extend_slots(1).await;
let block = &harness.chain.head_snapshot().beacon_block;
execution_payloads.push(block.message().body().execution_payload().unwrap().clone());
execution_payloads.push(
block
.message()
.body()
.execution_payload()
.unwrap()
.clone()
.into(),
);
}
verify_execution_payload_chain(execution_payloads.as_slice());
@@ -13,9 +13,9 @@ use beacon_chain::{
INVALID_JUSTIFIED_PAYLOAD_SHUTDOWN_REASON,
};
use execution_layer::{
json_structures::{JsonForkChoiceStateV1, JsonPayloadAttributesV1},
json_structures::{JsonForkchoiceStateV1, JsonPayloadAttributes, JsonPayloadAttributesV1},
test_utils::ExecutionBlockGenerator,
ExecutionLayer, ForkChoiceState, PayloadAttributes,
ExecutionLayer, ForkchoiceState, PayloadAttributes,
};
use fork_choice::{
CountUnrealized, Error as ForkChoiceError, InvalidationOperation, PayloadVerificationStatus,
@@ -120,7 +120,7 @@ impl InvalidPayloadRig {
&self.harness.chain.canonical_head
}
fn previous_forkchoice_update_params(&self) -> (ForkChoiceState, PayloadAttributes) {
fn previous_forkchoice_update_params(&self) -> (ForkchoiceState, PayloadAttributes) {
let mock_execution_layer = self.harness.mock_execution_layer.as_ref().unwrap();
let json = mock_execution_layer
.server
@@ -129,14 +129,17 @@ impl InvalidPayloadRig {
let params = json.get("params").expect("no params");
let fork_choice_state_json = params.get(0).expect("no payload param");
let fork_choice_state: JsonForkChoiceStateV1 =
let fork_choice_state: JsonForkchoiceStateV1 =
serde_json::from_value(fork_choice_state_json.clone()).unwrap();
let payload_param_json = params.get(1).expect("no payload param");
let attributes: JsonPayloadAttributesV1 =
serde_json::from_value(payload_param_json.clone()).unwrap();
(fork_choice_state.into(), attributes.into())
(
fork_choice_state.into(),
JsonPayloadAttributes::V1(attributes).into(),
)
}
fn previous_payload_attributes(&self) -> PayloadAttributes {
@@ -991,20 +994,20 @@ async fn payload_preparation() {
.await
.unwrap();
let payload_attributes = PayloadAttributes {
timestamp: rig
.harness
let payload_attributes = PayloadAttributes::new(
rig.harness
.chain
.slot_clock
.start_of(next_slot)
.unwrap()
.as_secs(),
prev_randao: *head
*head
.beacon_state
.get_randao_mix(head.beacon_state.current_epoch())
.unwrap(),
suggested_fee_recipient: fee_recipient,
};
fee_recipient,
None,
);
assert_eq!(rig.previous_payload_attributes(), payload_attributes);
}
@@ -1138,7 +1141,7 @@ async fn payload_preparation_before_transition_block() {
let (fork_choice_state, payload_attributes) = rig.previous_forkchoice_update_params();
let latest_block_hash = rig.latest_execution_block_hash();
assert_eq!(payload_attributes.suggested_fee_recipient, fee_recipient);
assert_eq!(payload_attributes.suggested_fee_recipient(), fee_recipient);
assert_eq!(fork_choice_state.head_block_hash, latest_block_hash);
}
@@ -1385,18 +1388,16 @@ async fn build_optimistic_chain(
.body()
.execution_payload()
.unwrap()
.execution_payload
== <_>::default(),
.is_default_with_empty_roots(),
"the block *has not* undergone the merge transition"
);
assert!(
post_transition_block
!post_transition_block
.message()
.body()
.execution_payload()
.unwrap()
.execution_payload
!= <_>::default(),
.is_default_with_empty_roots(),
"the block *has* undergone the merge transition"
);
+100 -12
View File
@@ -2,6 +2,7 @@
use beacon_chain::attestation_verification::Error as AttnError;
use beacon_chain::builder::BeaconChainBuilder;
use beacon_chain::schema_change::migrate_schema;
use beacon_chain::test_utils::{
test_spec, AttestationStrategy, BeaconChainHarness, BlockStrategy, DiskHarnessType,
};
@@ -22,6 +23,7 @@ use std::collections::HashSet;
use std::convert::TryInto;
use std::sync::Arc;
use std::time::Duration;
use store::metadata::{SchemaVersion, CURRENT_SCHEMA_VERSION};
use store::{
iter::{BlockRootsIterator, StateRootsIterator},
HotColdDB, LevelDB, StoreConfig,
@@ -68,6 +70,7 @@ fn get_harness(
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(KEYPAIRS[0..validator_count].to_vec())
.logger(store.logger().clone())
.fresh_disk_store(store)
.mock_execution_layer()
.build();
@@ -1013,8 +1016,8 @@ fn check_shuffling_compatible(
// Ensure blocks from abandoned forks are pruned from the Hot DB
#[tokio::test]
async fn prunes_abandoned_fork_between_two_finalized_checkpoints() {
const HONEST_VALIDATOR_COUNT: usize = 16 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
@@ -1123,8 +1126,8 @@ async fn prunes_abandoned_fork_between_two_finalized_checkpoints() {
#[tokio::test]
async fn pruning_does_not_touch_abandoned_block_shared_with_canonical_chain() {
const HONEST_VALIDATOR_COUNT: usize = 16 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
@@ -1255,8 +1258,8 @@ async fn pruning_does_not_touch_abandoned_block_shared_with_canonical_chain() {
#[tokio::test]
async fn pruning_does_not_touch_blocks_prior_to_finalization() {
const HONEST_VALIDATOR_COUNT: usize = 16;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 8;
const HONEST_VALIDATOR_COUNT: usize = 32;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
@@ -1350,8 +1353,8 @@ async fn pruning_does_not_touch_blocks_prior_to_finalization() {
#[tokio::test]
async fn prunes_fork_growing_past_youngest_finalized_checkpoint() {
const HONEST_VALIDATOR_COUNT: usize = 16 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
@@ -1495,8 +1498,8 @@ async fn prunes_fork_growing_past_youngest_finalized_checkpoint() {
// This is to check if state outside of normal block processing are pruned correctly.
#[tokio::test]
async fn prunes_skipped_slots_states() {
const HONEST_VALIDATOR_COUNT: usize = 16 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
@@ -1624,8 +1627,8 @@ async fn prunes_skipped_slots_states() {
// This is to check if state outside of normal block processing are pruned correctly.
#[tokio::test]
async fn finalizes_non_epoch_start_slot() {
const HONEST_VALIDATOR_COUNT: usize = 16 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 8 - 0;
const HONEST_VALIDATOR_COUNT: usize = 32 + 0;
const ADVERSARIAL_VALIDATOR_COUNT: usize = 16 - 0;
const VALIDATOR_COUNT: usize = HONEST_VALIDATOR_COUNT + ADVERSARIAL_VALIDATOR_COUNT;
let validators_keypairs = types::test_utils::generate_deterministic_keypairs(VALIDATOR_COUNT);
let honest_validators: Vec<usize> = (0..HONEST_VALIDATOR_COUNT).collect();
@@ -2529,6 +2532,91 @@ async fn revert_minority_fork_on_resume() {
assert_eq!(heads.len(), 1);
}
// This test checks whether the schema downgrade from the latest version to some minimum supported
// version is correct. This is the easiest schema test to write without historic versions of
// Lighthouse on-hand, but has the disadvantage that the min version needs to be adjusted manually
// as old downgrades are deprecated.
#[tokio::test]
async fn schema_downgrade_to_min_version() {
let num_blocks_produced = E::slots_per_epoch() * 4;
let db_path = tempdir().unwrap();
let store = get_store(&db_path);
let harness = get_harness(store.clone(), LOW_VALIDATOR_COUNT);
let spec = &harness.chain.spec.clone();
harness
.extend_chain(
num_blocks_produced as usize,
BlockStrategy::OnCanonicalHead,
AttestationStrategy::AllValidators,
)
.await;
let min_version = if harness.spec.capella_fork_epoch.is_some() {
// Can't downgrade beyond V14 once Capella is reached, for simplicity don't test that
// at all if Capella is enabled.
SchemaVersion(14)
} else {
SchemaVersion(11)
};
// Close the database to ensure everything is written to disk.
drop(store);
drop(harness);
// Re-open the store.
let store = get_store(&db_path);
// Downgrade.
let deposit_contract_deploy_block = 0;
migrate_schema::<DiskHarnessType<E>>(
store.clone(),
deposit_contract_deploy_block,
CURRENT_SCHEMA_VERSION,
min_version,
store.logger().clone(),
spec,
)
.expect("schema downgrade to minimum version should work");
// Upgrade back.
migrate_schema::<DiskHarnessType<E>>(
store.clone(),
deposit_contract_deploy_block,
min_version,
CURRENT_SCHEMA_VERSION,
store.logger().clone(),
spec,
)
.expect("schema upgrade from minimum version should work");
// Rescreate the harness.
let harness = BeaconChainHarness::builder(MinimalEthSpec)
.default_spec()
.keypairs(KEYPAIRS[0..LOW_VALIDATOR_COUNT].to_vec())
.logger(store.logger().clone())
.resumed_disk_store(store.clone())
.mock_execution_layer()
.build();
check_finalization(&harness, num_blocks_produced);
check_split_slot(&harness, store.clone());
check_chain_dump(&harness, num_blocks_produced + 1);
check_iterators(&harness);
// Check that downgrading beyond the minimum version fails (bound is *tight*).
let min_version_sub_1 = SchemaVersion(min_version.as_u64().checked_sub(1).unwrap());
migrate_schema::<DiskHarnessType<E>>(
store.clone(),
deposit_contract_deploy_block,
CURRENT_SCHEMA_VERSION,
min_version_sub_1,
harness.logger().clone(),
spec,
)
.expect_err("should not downgrade below minimum version");
}
/// Checks that two chains are the same, for the purpose of these tests.
///
/// Several fields that are hard/impossible to check are ignored (e.g., the store).
@@ -45,6 +45,7 @@ fn get_valid_sync_committee_message(
harness: &BeaconChainHarness<EphemeralHarnessType<E>>,
slot: Slot,
relative_sync_committee: RelativeSyncCommittee,
message_index: usize,
) -> (SyncCommitteeMessage, usize, SecretKey, SyncSubnetId) {
let head_state = harness.chain.head_beacon_state_cloned();
let head_block_root = harness.chain.head_snapshot().beacon_block_root;
@@ -52,7 +53,7 @@ fn get_valid_sync_committee_message(
.make_sync_committee_messages(&head_state, head_block_root, slot, relative_sync_committee)
.get(0)
.expect("sync messages should exist")
.get(0)
.get(message_index)
.expect("first sync message should exist")
.clone();
@@ -494,7 +495,7 @@ async fn unaggregated_gossip_verification() {
let current_slot = harness.chain.slot().expect("should get slot");
let (valid_sync_committee_message, expected_validator_index, validator_sk, subnet_id) =
get_valid_sync_committee_message(&harness, current_slot, RelativeSyncCommittee::Current);
get_valid_sync_committee_message(&harness, current_slot, RelativeSyncCommittee::Current, 0);
macro_rules! assert_invalid {
($desc: tt, $attn_getter: expr, $subnet_getter: expr, $($error: pat_param) |+ $( if $guard: expr )?) => {
@@ -644,7 +645,7 @@ async fn unaggregated_gossip_verification() {
// **Incorrectly** create a sync message using the current sync committee
let (next_valid_sync_committee_message, _, _, next_subnet_id) =
get_valid_sync_committee_message(&harness, target_slot, RelativeSyncCommittee::Current);
get_valid_sync_committee_message(&harness, target_slot, RelativeSyncCommittee::Current, 1);
assert_invalid!(
"sync message on incorrect subnet",
+1 -1
View File
@@ -19,7 +19,7 @@ use types::{
};
// Should ideally be divisible by 3.
pub const VALIDATOR_COUNT: usize = 24;
pub const VALIDATOR_COUNT: usize = 48;
lazy_static! {
/// A cached set of keys.