Merge branch 'capella' into unstable
This commit is contained in:
@@ -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>,
|
||||
|
||||
@@ -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, ð1_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 })
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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,6 +1,7 @@
|
||||
mod attestation_production;
|
||||
mod attestation_verification;
|
||||
mod block_verification;
|
||||
mod capella;
|
||||
mod merge;
|
||||
mod op_verification;
|
||||
mod payload_invalidation;
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user