Fixes to make EF Capella tests pass (#3719)

* Fixes to make EF Capella tests pass

* Clippy for state_processing
This commit is contained in:
Michael Sproul 2022-11-15 06:14:31 +11:00 committed by GitHub
parent 276e1845fd
commit 0cdd049da9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 434 additions and 196 deletions

View File

@ -20,6 +20,9 @@ CROSS_FEATURES ?= gnosis,slasher-lmdb,slasher-mdbx
# Cargo profile for Cross builds. Default is for local builds, CI uses an override. # Cargo profile for Cross builds. Default is for local builds, CI uses an override.
CROSS_PROFILE ?= release CROSS_PROFILE ?= release
# List of features to use when running EF tests.
EF_TEST_FEATURES ?= beacon_chain/withdrawals,beacon_chain/withdrawals-processing
# Cargo profile for regular builds. # Cargo profile for regular builds.
PROFILE ?= release PROFILE ?= release
@ -108,9 +111,9 @@ check-consensus:
# Runs only the ef-test vectors. # Runs only the ef-test vectors.
run-ef-tests: run-ef-tests:
rm -rf $(EF_TESTS)/.accessed_file_log.txt rm -rf $(EF_TESTS)/.accessed_file_log.txt
cargo test --release -p ef_tests --features "ef_tests" cargo test --release -p ef_tests --features "ef_tests,$(EF_TEST_FEATURES)"
cargo test --release -p ef_tests --features "ef_tests,fake_crypto" cargo test --release -p ef_tests --features "ef_tests,$(EF_TEST_FEATURES),fake_crypto"
cargo test --release -p ef_tests --features "ef_tests,milagro" cargo test --release -p ef_tests --features "ef_tests,$(EF_TEST_FEATURES),milagro"
./$(EF_TESTS)/check_all_files_accessed.py $(EF_TESTS)/.accessed_file_log.txt $(EF_TESTS)/consensus-spec-tests ./$(EF_TESTS)/check_all_files_accessed.py $(EF_TESTS)/.accessed_file_log.txt $(EF_TESTS)/consensus-spec-tests
# Run the tests in the `beacon_chain` crate for all known forks. # Run the tests in the `beacon_chain` crate for all known forks.

View File

@ -683,8 +683,8 @@ mod tests {
}; };
use std::sync::Arc; use std::sync::Arc;
use types::{ use types::{
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockMerge, Epoch, ForkContext, BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockMerge, EmptyBlock, Epoch,
FullPayload, Hash256, Signature, SignedBeaconBlock, Slot, ForkContext, FullPayload, Hash256, Signature, SignedBeaconBlock, Slot,
}; };
use snap::write::FrameEncoder; use snap::write::FrameEncoder;

View File

@ -22,8 +22,8 @@ use tokio_util::{
}; };
use types::BlobsSidecar; use types::BlobsSidecar;
use types::{ use types::{
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockMerge, Blob, EthSpec, ForkContext, BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockMerge, Blob, EmptyBlock, EthSpec,
ForkName, Hash256, MainnetEthSpec, Signature, SignedBeaconBlock, ForkContext, ForkName, Hash256, MainnetEthSpec, Signature, SignedBeaconBlock,
}; };
lazy_static! { lazy_static! {

View File

@ -492,17 +492,15 @@ impl<E: EthSpec, Hot: ItemStore<E>, Cold: ItemStore<E>> HotColdDB<E, Hot, Cold>
pub fn get_blobs(&self, block_root: &Hash256) -> Result<Option<BlobsSidecar<E>>, Error> { pub fn get_blobs(&self, block_root: &Hash256) -> Result<Option<BlobsSidecar<E>>, Error> {
if let Some(blobs) = self.blob_cache.lock().get(block_root) { if let Some(blobs) = self.blob_cache.lock().get(block_root) {
Ok(Some(blobs.clone())) Ok(Some(blobs.clone()))
} else if let Some(bytes) = self
.hot_db
.get_bytes(DBColumn::BeaconBlob.into(), block_root.as_bytes())?
{
let ret = BlobsSidecar::from_ssz_bytes(&bytes)?;
self.blob_cache.lock().put(*block_root, ret.clone());
Ok(Some(ret))
} else { } else {
if let Some(bytes) = self Ok(None)
.hot_db
.get_bytes(DBColumn::BeaconBlob.into(), block_root.as_bytes())?
{
let ret = BlobsSidecar::from_ssz_bytes(&bytes)?;
self.blob_cache.lock().put(*block_root, ret.clone());
Ok(Some(ret))
} else {
Ok(None)
}
} }
} }

View File

@ -1,7 +1,7 @@
use std::marker::PhantomData; use std::marker::PhantomData;
use tree_hash::TreeHash; use tree_hash::TreeHash;
use types::{ use types::{
AbstractExecPayload, BeaconState, BeaconStateError, ChainSpec, EthSpec, ExecPayload, Hash256, AbstractExecPayload, BeaconState, BeaconStateError, ChainSpec, EthSpec, Hash256,
SignedBeaconBlock, Slot, SignedBeaconBlock, Slot,
}; };

View File

@ -42,7 +42,9 @@ mod verify_deposit;
mod verify_exit; mod verify_exit;
mod verify_proposer_slashing; mod verify_proposer_slashing;
#[cfg(feature = "withdrawals-processing")]
use crate::common::decrease_balance; use crate::common::decrease_balance;
#[cfg(feature = "arbitrary-fuzz")] #[cfg(feature = "arbitrary-fuzz")]
use arbitrary::Arbitrary; use arbitrary::Arbitrary;
@ -466,21 +468,22 @@ pub fn get_expected_withdrawals<T: EthSpec>(
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<Withdrawals<T>, BlockProcessingError> { ) -> Result<Withdrawals<T>, BlockProcessingError> {
let epoch = state.current_epoch(); let epoch = state.current_epoch();
let mut withdrawal_index = *state.next_withdrawal_index()?; let mut withdrawal_index = state.next_withdrawal_index()?;
let mut validator_index = *state.next_withdrawal_validator_index()?; let mut validator_index = state.next_withdrawal_validator_index()?;
let mut withdrawals = vec![]; let mut withdrawals = vec![];
for _ in 0..state.validators().len() { for _ in 0..state.validators().len() {
let validator = state.get_validator(validator_index as usize)?; let validator = state.get_validator(validator_index as usize)?;
let balance = *state let balance = *state.balances().get(validator_index as usize).ok_or(
.balances() BeaconStateError::BalancesOutOfBounds(validator_index as usize),
.get(validator_index as usize) )?;
.ok_or_else(|| BeaconStateError::BalancesOutOfBounds(validator_index as usize))?;
if validator.is_fully_withdrawable_at(balance, epoch, spec) { if validator.is_fully_withdrawable_at(balance, epoch, spec) {
withdrawals.push(Withdrawal { withdrawals.push(Withdrawal {
index: withdrawal_index, index: withdrawal_index,
validator_index, validator_index,
address: Address::from_slice(&validator.withdrawal_credentials[12..]), address: validator
.get_eth1_withdrawal_address(spec)
.ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?,
amount: balance, amount: balance,
}); });
withdrawal_index.safe_add_assign(1)?; withdrawal_index.safe_add_assign(1)?;
@ -488,7 +491,9 @@ pub fn get_expected_withdrawals<T: EthSpec>(
withdrawals.push(Withdrawal { withdrawals.push(Withdrawal {
index: withdrawal_index, index: withdrawal_index,
validator_index, validator_index,
address: Address::from_slice(&validator.withdrawal_credentials[12..]), address: validator
.get_eth1_withdrawal_address(spec)
.ok_or(BlockProcessingError::WithdrawalCredentialsInvalid)?,
amount: balance.safe_sub(spec.max_effective_balance)?, amount: balance.safe_sub(spec.max_effective_balance)?,
}); });
withdrawal_index.safe_add_assign(1)?; withdrawal_index.safe_add_assign(1)?;
@ -496,7 +501,9 @@ pub fn get_expected_withdrawals<T: EthSpec>(
if withdrawals.len() == T::max_withdrawals_per_payload() { if withdrawals.len() == T::max_withdrawals_per_payload() {
break; break;
} }
validator_index = validator_index.safe_add(1)? % state.validators().len() as u64; validator_index = validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
} }
Ok(withdrawals.into()) Ok(withdrawals.into())
@ -513,12 +520,13 @@ pub fn process_withdrawals<'payload, T: EthSpec, Payload: AbstractExecPayload<T>
BeaconState::Merge(_) => Ok(()), BeaconState::Merge(_) => Ok(()),
BeaconState::Capella(_) | BeaconState::Eip4844(_) => { BeaconState::Capella(_) | BeaconState::Eip4844(_) => {
let expected_withdrawals = get_expected_withdrawals(state, spec)?; let expected_withdrawals = get_expected_withdrawals(state, spec)?;
let expected_root = expected_withdrawals.tree_hash_root();
let withdrawals_root = payload.withdrawals_root()?; let withdrawals_root = payload.withdrawals_root()?;
if expected_withdrawals.tree_hash_root() != payload.withdrawals_root()? { if expected_root != withdrawals_root {
return Err(BlockProcessingError::WithdrawalsRootMismatch { return Err(BlockProcessingError::WithdrawalsRootMismatch {
expected: expected_withdrawals.tree_hash_root(), expected: expected_root,
found: payload.withdrawals_root()?, found: withdrawals_root,
}); });
} }
@ -531,9 +539,11 @@ pub fn process_withdrawals<'payload, T: EthSpec, Payload: AbstractExecPayload<T>
} }
if let Some(latest_withdrawal) = expected_withdrawals.last() { if let Some(latest_withdrawal) = expected_withdrawals.last() {
*state.next_withdrawal_index_mut()? = latest_withdrawal.index + 1; *state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
let next_validator_index = let next_validator_index = latest_withdrawal
(latest_withdrawal.validator_index + 1) % state.validators().len() as u64; .validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index; *state.next_withdrawal_validator_index_mut()? = next_validator_index;
} }

View File

@ -1 +1,2 @@
#[allow(clippy::module_inception)]
pub mod eip4844; pub mod eip4844;

View File

@ -6,8 +6,8 @@ use ssz::Decode;
use ssz_types::VariableList; use ssz_types::VariableList;
use types::consts::eip4844::{BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG}; use types::consts::eip4844::{BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG};
use types::{ use types::{
AbstractExecPayload, BeaconBlockBodyRef, EthSpec, ExecPayload, FullPayload, FullPayloadRef, AbstractExecPayload, BeaconBlockBodyRef, EthSpec, ExecPayload, KzgCommitment, Transaction,
KzgCommitment, Transaction, Transactions, VersionedHash, Transactions, VersionedHash,
}; };
pub fn process_blob_kzg_commitments<T: EthSpec, Payload: AbstractExecPayload<T>>( pub fn process_blob_kzg_commitments<T: EthSpec, Payload: AbstractExecPayload<T>>(
@ -34,7 +34,7 @@ pub fn verify_kzg_commitments_against_transactions<T: EthSpec>(
let nested_iter = transactions let nested_iter = transactions
.into_iter() .into_iter()
.filter(|tx| { .filter(|tx| {
tx.get(0) tx.first()
.map(|tx_type| *tx_type == BLOB_TX_TYPE) .map(|tx_type| *tx_type == BLOB_TX_TYPE)
.unwrap_or(false) .unwrap_or(false)
}) })

View File

@ -94,6 +94,7 @@ pub enum BlockProcessingError {
index: usize, index: usize,
length: usize, length: usize,
}, },
WithdrawalCredentialsInvalid,
} }
impl From<BeaconStateError> for BlockProcessingError { impl From<BeaconStateError> for BlockProcessingError {

View File

@ -33,8 +33,11 @@ pub fn process_operations<'a, T: EthSpec, Payload: AbstractExecPayload<T>>(
process_attestations(state, block_body, verify_signatures, ctxt, spec)?; process_attestations(state, block_body, verify_signatures, ctxt, spec)?;
process_deposits(state, block_body.deposits(), spec)?; process_deposits(state, block_body.deposits(), spec)?;
process_exits(state, block_body.voluntary_exits(), verify_signatures, spec)?; process_exits(state, block_body.voluntary_exits(), verify_signatures, spec)?;
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))] #[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
process_bls_to_execution_changes(state, block_body, verify_signatures, spec)?; if let Ok(bls_to_execution_changes) = block_body.bls_to_execution_changes() {
process_bls_to_execution_changes(state, bls_to_execution_changes, verify_signatures, spec)?;
}
Ok(()) Ok(())
} }
@ -287,39 +290,25 @@ pub fn process_exits<T: EthSpec>(
/// Returns `Ok(())` if the validation and state updates completed successfully. Otherwise returs /// Returns `Ok(())` if the validation and state updates completed successfully. Otherwise returs
/// an `Err` describing the invalid object or cause of failure. /// an `Err` describing the invalid object or cause of failure.
#[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))] #[cfg(all(feature = "withdrawals", feature = "withdrawals-processing"))]
pub fn process_bls_to_execution_changes<'a, T: EthSpec, Payload: AbstractExecPayload<T>>( pub fn process_bls_to_execution_changes<T: EthSpec>(
state: &mut BeaconState<T>, state: &mut BeaconState<T>,
block_body: BeaconBlockBodyRef<'a, T, Payload>, bls_to_execution_changes: &[SignedBlsToExecutionChange],
verify_signatures: VerifySignatures, verify_signatures: VerifySignatures,
spec: &ChainSpec, spec: &ChainSpec,
) -> Result<(), BlockProcessingError> { ) -> Result<(), BlockProcessingError> {
match block_body { for (i, signed_address_change) in bls_to_execution_changes.iter().enumerate() {
BeaconBlockBodyRef::Base(_) verify_bls_to_execution_change(state, signed_address_change, verify_signatures, spec)
| BeaconBlockBodyRef::Altair(_) .map_err(|e| e.into_with_index(i))?;
| BeaconBlockBodyRef::Merge(_) => Ok(()),
BeaconBlockBodyRef::Capella(_) | BeaconBlockBodyRef::Eip4844(_) => {
for (i, signed_address_change) in
block_body.bls_to_execution_changes()?.iter().enumerate()
{
verify_bls_to_execution_change(
state,
&signed_address_change,
verify_signatures,
spec,
)
.map_err(|e| e.into_with_index(i))?;
state state
.get_validator_mut(signed_address_change.message.validator_index as usize)? .get_validator_mut(signed_address_change.message.validator_index as usize)?
.change_withdrawal_credentials( .change_withdrawal_credentials(
&signed_address_change.message.to_execution_address, &signed_address_change.message.to_execution_address,
spec, spec,
); );
}
Ok(())
}
} }
Ok(())
} }
/// Validates each `Deposit` and updates the state, short-circuiting on an invalid object. /// Validates each `Deposit` and updates the state, short-circuiting on an invalid object.

View File

@ -42,13 +42,13 @@ pub fn verify_bls_to_execution_change<T: EthSpec>(
// FIXME: Should this check be put inside the verify_signatures.is_true() condition? // FIXME: Should this check be put inside the verify_signatures.is_true() condition?
// I believe that's used for fuzzing so this is a Mehdi question.. // I believe that's used for fuzzing so this is a Mehdi question..
verify!( verify!(
validator.withdrawal_credentials.as_bytes()[1..] == pubkey_hash[1..], validator.withdrawal_credentials.as_bytes().get(1..) == pubkey_hash.get(1..),
Invalid::WithdrawalCredentialsMismatch Invalid::WithdrawalCredentialsMismatch
); );
if verify_signatures.is_true() { if verify_signatures.is_true() {
verify!( verify!(
bls_execution_change_signature_set(state, signed_address_change, spec,)?.verify(), bls_execution_change_signature_set(state, signed_address_change, spec)?.verify(),
Invalid::BadSignature Invalid::BadSignature
); );
} }

View File

@ -1,4 +1,6 @@
use crate::upgrade::{upgrade_to_altair, upgrade_to_bellatrix}; use crate::upgrade::{
upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_eip4844,
};
use crate::{per_epoch_processing::EpochProcessingSummary, *}; use crate::{per_epoch_processing::EpochProcessingSummary, *};
use safe_arith::{ArithError, SafeArith}; use safe_arith::{ArithError, SafeArith};
use types::*; use types::*;
@ -55,6 +57,14 @@ pub fn per_slot_processing<T: EthSpec>(
if spec.bellatrix_fork_epoch == Some(state.current_epoch()) { if spec.bellatrix_fork_epoch == Some(state.current_epoch()) {
upgrade_to_bellatrix(state, spec)?; upgrade_to_bellatrix(state, spec)?;
} }
// Capella.
if spec.capella_fork_epoch == Some(state.current_epoch()) {
upgrade_to_capella(state, spec)?;
}
// Eip4844
if spec.eip4844_fork_epoch == Some(state.current_epoch()) {
upgrade_to_eip4844(state, spec)?;
}
} }
Ok(summary) Ok(summary)

View File

@ -1,4 +1,3 @@
use ssz_types::VariableList;
use std::mem; use std::mem;
use types::{BeaconState, BeaconStateCapella, BeaconStateError as Error, ChainSpec, EthSpec, Fork}; use types::{BeaconState, BeaconStateCapella, BeaconStateError as Error, ChainSpec, EthSpec, Fork};

View File

@ -78,17 +78,20 @@ impl<'a, T: EthSpec, Payload: AbstractExecPayload<T>> SignedRoot
{ {
} }
/// Empty block trait for each block variant to implement.
pub trait EmptyBlock {
/// Returns an empty block to be used during genesis.
fn empty(spec: &ChainSpec) -> Self;
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlock<T, Payload> { impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlock<T, Payload> {
// FIXME: deal with capella / eip4844 forks here as well
/// Returns an empty block to be used during genesis. /// Returns an empty block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self { pub fn empty(spec: &ChainSpec) -> Self {
if spec.bellatrix_fork_epoch == Some(T::genesis_epoch()) { map_fork_name!(
Self::Merge(BeaconBlockMerge::empty(spec)) spec.fork_name_at_epoch(T::genesis_epoch()),
} else if spec.altair_fork_epoch == Some(T::genesis_epoch()) { Self,
Self::Altair(BeaconBlockAltair::empty(spec)) EmptyBlock::empty(spec)
} else { )
Self::Base(BeaconBlockBase::empty(spec))
}
} }
/// Custom SSZ decoder that takes a `ChainSpec` as context. /// Custom SSZ decoder that takes a `ChainSpec` as context.
@ -117,13 +120,12 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlock<T, Payload> {
/// Usually it's better to prefer `from_ssz_bytes` which will decode the correct variant based /// Usually it's better to prefer `from_ssz_bytes` which will decode the correct variant based
/// on the fork slot. /// on the fork slot.
pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> { pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
BeaconBlockMerge::from_ssz_bytes(bytes) BeaconBlockEip4844::from_ssz_bytes(bytes)
.map(BeaconBlock::Merge) .map(BeaconBlock::Eip4844)
.or_else(|_| { .or_else(|_| BeaconBlockCapella::from_ssz_bytes(bytes).map(BeaconBlock::Capella))
BeaconBlockAltair::from_ssz_bytes(bytes) .or_else(|_| BeaconBlockMerge::from_ssz_bytes(bytes).map(BeaconBlock::Merge))
.map(BeaconBlock::Altair) .or_else(|_| BeaconBlockAltair::from_ssz_bytes(bytes).map(BeaconBlock::Altair))
.or_else(|_| BeaconBlockBase::from_ssz_bytes(bytes).map(BeaconBlock::Base)) .or_else(|_| BeaconBlockBase::from_ssz_bytes(bytes).map(BeaconBlock::Base))
})
} }
/// Convenience accessor for the `body` as a `BeaconBlockBodyRef`. /// Convenience accessor for the `body` as a `BeaconBlockBodyRef`.
@ -266,9 +268,8 @@ impl<'a, T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockRefMut<'a, T, P
} }
} }
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBase<T, Payload> { impl<T: EthSpec, Payload: AbstractExecPayload<T>> EmptyBlock for BeaconBlockBase<T, Payload> {
/// Returns an empty block to be used during genesis. fn empty(spec: &ChainSpec) -> Self {
pub fn empty(spec: &ChainSpec) -> Self {
BeaconBlockBase { BeaconBlockBase {
slot: spec.genesis_slot, slot: spec.genesis_slot,
proposer_index: 0, proposer_index: 0,
@ -291,7 +292,9 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBase<T, Payload> {
}, },
} }
} }
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBase<T, Payload> {
/// Return a block where the block has maximum size. /// Return a block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self { pub fn full(spec: &ChainSpec) -> Self {
let header = BeaconBlockHeader { let header = BeaconBlockHeader {
@ -387,9 +390,9 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBase<T, Payload> {
} }
} }
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockAltair<T, Payload> { impl<T: EthSpec, Payload: AbstractExecPayload<T>> EmptyBlock for BeaconBlockAltair<T, Payload> {
/// Returns an empty Altair block to be used during genesis. /// Returns an empty Altair block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self { fn empty(spec: &ChainSpec) -> Self {
BeaconBlockAltair { BeaconBlockAltair {
slot: spec.genesis_slot, slot: spec.genesis_slot,
proposer_index: 0, proposer_index: 0,
@ -413,7 +416,9 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockAltair<T, Payload>
}, },
} }
} }
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockAltair<T, Payload> {
/// Return an Altair block where the block has maximum size. /// Return an Altair block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self { pub fn full(spec: &ChainSpec) -> Self {
let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec); let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec);
@ -446,9 +451,9 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockAltair<T, Payload>
} }
} }
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockMerge<T, Payload> { impl<T: EthSpec, Payload: AbstractExecPayload<T>> EmptyBlock for BeaconBlockMerge<T, Payload> {
/// Returns an empty Merge block to be used during genesis. /// Returns an empty Merge block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self { fn empty(spec: &ChainSpec) -> Self {
BeaconBlockMerge { BeaconBlockMerge {
slot: spec.genesis_slot, slot: spec.genesis_slot,
proposer_index: 0, proposer_index: 0,
@ -474,6 +479,67 @@ impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockMerge<T, Payload> {
} }
} }
impl<T: EthSpec, Payload: AbstractExecPayload<T>> EmptyBlock for BeaconBlockCapella<T, Payload> {
/// Returns an empty Capella block to be used during genesis.
fn empty(spec: &ChainSpec) -> Self {
BeaconBlockCapella {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyCapella {
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
proposer_slashings: VariableList::empty(),
attester_slashings: VariableList::empty(),
attestations: VariableList::empty(),
deposits: VariableList::empty(),
voluntary_exits: VariableList::empty(),
sync_aggregate: SyncAggregate::empty(),
execution_payload: Payload::Capella::default(),
#[cfg(feature = "withdrawals")]
bls_to_execution_changes: VariableList::empty(),
},
}
}
}
impl<T: EthSpec, Payload: AbstractExecPayload<T>> EmptyBlock for BeaconBlockEip4844<T, Payload> {
/// Returns an empty Eip4844 block to be used during genesis.
fn empty(spec: &ChainSpec) -> Self {
BeaconBlockEip4844 {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyEip4844 {
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
proposer_slashings: VariableList::empty(),
attester_slashings: VariableList::empty(),
attestations: VariableList::empty(),
deposits: VariableList::empty(),
voluntary_exits: VariableList::empty(),
sync_aggregate: SyncAggregate::empty(),
execution_payload: Payload::Eip4844::default(),
#[cfg(feature = "withdrawals")]
bls_to_execution_changes: VariableList::empty(),
blob_kzg_commitments: VariableList::empty(),
},
}
}
}
// We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads. // We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads.
impl<E: EthSpec> From<BeaconBlockBase<E, BlindedPayload<E>>> impl<E: EthSpec> From<BeaconBlockBase<E, BlindedPayload<E>>>
for BeaconBlockBase<E, FullPayload<E>> for BeaconBlockBase<E, FullPayload<E>>

View File

@ -76,7 +76,7 @@ pub struct BeaconBlockBody<T: EthSpec, Payload: AbstractExecPayload<T> = FullPay
} }
impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBody<T, Payload> { impl<T: EthSpec, Payload: AbstractExecPayload<T>> BeaconBlockBody<T, Payload> {
pub fn execution_payload<'a>(&'a self) -> Result<Payload::Ref<'a>, Error> { pub fn execution_payload(&self) -> Result<Payload::Ref<'_>, Error> {
self.to_ref().execution_payload() self.to_ref().execution_payload()
} }
} }

View File

@ -296,10 +296,10 @@ where
// Withdrawals // Withdrawals
#[cfg(feature = "withdrawals")] #[cfg(feature = "withdrawals")]
#[superstruct(only(Capella, Eip4844))] #[superstruct(only(Capella, Eip4844), partial_getter(copy))]
pub next_withdrawal_index: u64, pub next_withdrawal_index: u64,
#[cfg(feature = "withdrawals")] #[cfg(feature = "withdrawals")]
#[superstruct(only(Capella, Eip4844))] #[superstruct(only(Capella, Eip4844), partial_getter(copy))]
pub next_withdrawal_validator_index: u64, pub next_withdrawal_validator_index: u64,
// Caching (not in the spec) // Caching (not in the spec)
@ -1784,6 +1784,8 @@ impl<T: EthSpec> CompareFields for BeaconState<T> {
(BeaconState::Base(x), BeaconState::Base(y)) => x.compare_fields(y), (BeaconState::Base(x), BeaconState::Base(y)) => x.compare_fields(y),
(BeaconState::Altair(x), BeaconState::Altair(y)) => x.compare_fields(y), (BeaconState::Altair(x), BeaconState::Altair(y)) => x.compare_fields(y),
(BeaconState::Merge(x), BeaconState::Merge(y)) => x.compare_fields(y), (BeaconState::Merge(x), BeaconState::Merge(y)) => x.compare_fields(y),
(BeaconState::Capella(x), BeaconState::Capella(y)) => x.compare_fields(y),
(BeaconState::Eip4844(x), BeaconState::Eip4844(y)) => x.compare_fields(y),
_ => panic!("compare_fields: mismatched state variants",), _ => panic!("compare_fields: mismatched state variants",),
} }
} }

View File

@ -363,6 +363,16 @@ impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
hasher.write(payload_header.tree_hash_root().as_bytes())?; hasher.write(payload_header.tree_hash_root().as_bytes())?;
} }
// Withdrawal indices (Capella and later).
#[cfg(feature = "withdrawals")]
if let Ok(next_withdrawal_index) = state.next_withdrawal_index() {
hasher.write(next_withdrawal_index.tree_hash_root().as_bytes())?;
}
#[cfg(feature = "withdrawals")]
if let Ok(next_withdrawal_validator_index) = state.next_withdrawal_validator_index() {
hasher.write(next_withdrawal_validator_index.tree_hash_root().as_bytes())?;
}
let root = hasher.finish()?; let root = hasher.finish()?;
self.previous_state = Some((root, state.slot())); self.previous_state = Some((root, state.slot()));

View File

@ -4,7 +4,6 @@ use serde_derive::{Deserialize, Serialize};
use ssz::Encode; use ssz::Encode;
use ssz_derive::{Decode, Encode}; use ssz_derive::{Decode, Encode};
use ssz_types::VariableList; use ssz_types::VariableList;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash; use tree_hash_derive::TreeHash;
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))] #[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
@ -23,6 +22,7 @@ impl<T: EthSpec> BlobsSidecar<T> {
pub fn empty() -> Self { pub fn empty() -> Self {
Self::default() Self::default()
} }
#[allow(clippy::integer_arithmetic)]
pub fn max_size() -> usize { pub fn max_size() -> usize {
// Fixed part // Fixed part
Self::empty().as_ssz_bytes().len() Self::empty().as_ssz_bytes().len()

View File

@ -324,6 +324,7 @@ impl EthSpec for MinimalEthSpec {
type SyncSubcommitteeSize = U8; // 32 committee size / 4 sync committee subnet count type SyncSubcommitteeSize = U8; // 32 committee size / 4 sync committee subnet count
type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch type MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch
type SlotsPerEth1VotingPeriod = U32; // 4 epochs * 8 slots per epoch type SlotsPerEth1VotingPeriod = U32; // 4 epochs * 8 slots per epoch
type MaxWithdrawalsPerPayload = U4;
params_from_eth_spec!(MainnetEthSpec { params_from_eth_spec!(MainnetEthSpec {
JustificationBitsLength, JustificationBitsLength,
@ -345,7 +346,6 @@ impl EthSpec for MinimalEthSpec {
MinGasLimit, MinGasLimit,
MaxExtraDataBytes, MaxExtraDataBytes,
MaxBlsToExecutionChanges, MaxBlsToExecutionChanges,
MaxWithdrawalsPerPayload,
MaxBlobsPerBlock, MaxBlobsPerBlock,
FieldElementsPerBlob FieldElementsPerBlob
}); });

View File

@ -1,7 +1,7 @@
use crate::{test_utils::TestRandom, *}; use crate::{test_utils::TestRandom, *};
use derivative::Derivative; use derivative::Derivative;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use ssz::Encode; use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode}; use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom; use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash; use tree_hash_derive::TreeHash;
@ -87,6 +87,17 @@ pub struct ExecutionPayload<T: EthSpec> {
} }
impl<T: EthSpec> ExecutionPayload<T> { impl<T: EthSpec> ExecutionPayload<T> {
pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
match fork_name {
ForkName::Base | ForkName::Altair => Err(ssz::DecodeError::BytesInvalid(format!(
"unsupported fork for ExecutionPayload: {fork_name}",
))),
ForkName::Merge => ExecutionPayloadMerge::from_ssz_bytes(bytes).map(Self::Merge),
ForkName::Capella => ExecutionPayloadCapella::from_ssz_bytes(bytes).map(Self::Capella),
ForkName::Eip4844 => ExecutionPayloadEip4844::from_ssz_bytes(bytes).map(Self::Eip4844),
}
}
#[allow(clippy::integer_arithmetic)] #[allow(clippy::integer_arithmetic)]
/// Returns the maximum size of an execution payload. /// Returns the maximum size of an execution payload.
pub fn max_execution_payload_merge_size() -> usize { pub fn max_execution_payload_merge_size() -> usize {

View File

@ -1,6 +1,7 @@
use crate::{test_utils::TestRandom, *}; use crate::{test_utils::TestRandom, *};
use derivative::Derivative; use derivative::Derivative;
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use ssz::Decode;
use ssz_derive::{Decode, Encode}; use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom; use test_random_derive::TestRandom;
use tree_hash::TreeHash; use tree_hash::TreeHash;
@ -84,31 +85,34 @@ impl<T: EthSpec> ExecutionPayloadHeader<T> {
pub fn transactions(&self) -> Option<&Transactions<T>> { pub fn transactions(&self) -> Option<&Transactions<T>> {
None None
} }
}
impl<'a, T: EthSpec> ExecutionPayloadHeaderRef<'a, T> { pub fn from_ssz_bytes(bytes: &[u8], fork_name: ForkName) -> Result<Self, ssz::DecodeError> {
// FIXME: maybe this could be a derived trait.. match fork_name {
pub fn is_default(self) -> bool { ForkName::Base | ForkName::Altair => Err(ssz::DecodeError::BytesInvalid(format!(
match self { "unsupported fork for ExecutionPayloadHeader: {fork_name}",
ExecutionPayloadHeaderRef::Merge(header) => { ))),
*header == ExecutionPayloadHeaderMerge::default() ForkName::Merge => ExecutionPayloadHeaderMerge::from_ssz_bytes(bytes).map(Self::Merge),
ForkName::Capella => {
ExecutionPayloadHeaderCapella::from_ssz_bytes(bytes).map(Self::Capella)
} }
ExecutionPayloadHeaderRef::Capella(header) => { ForkName::Eip4844 => {
*header == ExecutionPayloadHeaderCapella::default() ExecutionPayloadHeaderEip4844::from_ssz_bytes(bytes).map(Self::Eip4844)
}
ExecutionPayloadHeaderRef::Eip4844(header) => {
*header == ExecutionPayloadHeaderEip4844::default()
} }
} }
} }
} }
impl<'a, T: EthSpec> ExecutionPayloadHeaderRef<'a, T> {
pub fn is_default(self) -> bool {
map_execution_payload_header_ref!(&'a _, self, |inner, cons| {
let _ = cons(inner);
*inner == Default::default()
})
}
}
impl<T: EthSpec> ExecutionPayloadHeaderMerge<T> { impl<T: EthSpec> ExecutionPayloadHeaderMerge<T> {
pub fn upgrade_to_capella(&self) -> ExecutionPayloadHeaderCapella<T> { pub fn upgrade_to_capella(&self) -> ExecutionPayloadHeaderCapella<T> {
#[cfg(feature = "withdrawals")]
// TODO: if this is correct we should calculate and hardcode this..
let empty_withdrawals_root =
VariableList::<Withdrawal, T::MaxWithdrawalsPerPayload>::empty().tree_hash_root();
ExecutionPayloadHeaderCapella { ExecutionPayloadHeaderCapella {
parent_hash: self.parent_hash, parent_hash: self.parent_hash,
fee_recipient: self.fee_recipient, fee_recipient: self.fee_recipient,
@ -125,8 +129,7 @@ impl<T: EthSpec> ExecutionPayloadHeaderMerge<T> {
block_hash: self.block_hash, block_hash: self.block_hash,
transactions_root: self.transactions_root, transactions_root: self.transactions_root,
#[cfg(feature = "withdrawals")] #[cfg(feature = "withdrawals")]
// FIXME: the spec doesn't seem to define what to do here.. withdrawals_root: Hash256::zero(),
withdrawals_root: empty_withdrawals_root,
} }
} }
} }

View File

@ -14,7 +14,7 @@ pub struct KzgCommitment(#[serde(with = "BigArray")] pub [u8; 48]);
impl Display for KzgCommitment { impl Display for KzgCommitment {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", eth2_serde_utils::hex::encode(&self.0)) write!(f, "{}", eth2_serde_utils::hex::encode(self.0))
} }
} }

View File

@ -1,7 +1,6 @@
use crate::test_utils::{RngCore, TestRandom}; use crate::test_utils::{RngCore, TestRandom};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_big_array::BigArray; use serde_big_array::BigArray;
use ssz::{Decode, DecodeError, Encode};
use ssz_derive::{Decode, Encode}; use ssz_derive::{Decode, Encode};
use std::fmt; use std::fmt;
use tree_hash::{PackedEncoding, TreeHash}; use tree_hash::{PackedEncoding, TreeHash};
@ -15,7 +14,7 @@ pub struct KzgProof(#[serde(with = "BigArray")] pub [u8; KZG_PROOF_BYTES_LEN]);
impl fmt::Display for KzgProof { impl fmt::Display for KzgProof {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", eth2_serde_utils::hex::encode(&self.0)) write!(f, "{}", eth2_serde_utils::hex::encode(self.0))
} }
} }

View File

@ -109,7 +109,7 @@ pub use crate::attestation_duty::AttestationDuty;
pub use crate::attester_slashing::AttesterSlashing; pub use crate::attester_slashing::AttesterSlashing;
pub use crate::beacon_block::{ pub use crate::beacon_block::{
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockEip4844, BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockEip4844,
BeaconBlockMerge, BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, BeaconBlockMerge, BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, EmptyBlock,
}; };
pub use crate::beacon_block_body::{ pub use crate::beacon_block_body::{
BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyCapella, BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyCapella,

View File

@ -221,7 +221,7 @@ impl<T: EthSpec> ExecPayload<T> for FullPayload<T> {
}) })
} }
fn transactions<'a>(&'a self) -> Option<&Transactions<T>> { fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| { map_full_payload_ref!(&'a _, self.to_ref(), move |payload, cons| {
cons(payload); cons(payload);
Some(&payload.execution_payload.transactions) Some(&payload.execution_payload.transactions)
@ -265,7 +265,7 @@ impl<'b, T: EthSpec> ExecPayload<T> for FullPayloadRef<'b, T> {
fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader<T> { fn to_execution_payload_header<'a>(&'a self) -> ExecutionPayloadHeader<T> {
map_full_payload_ref!(&'a _, self, move |payload, cons| { map_full_payload_ref!(&'a _, self, move |payload, cons| {
cons(payload); cons(payload);
ExecutionPayloadHeader::from(payload.to_execution_payload_header()) payload.to_execution_payload_header()
}) })
} }
@ -318,7 +318,7 @@ impl<'b, T: EthSpec> ExecPayload<T> for FullPayloadRef<'b, T> {
}) })
} }
fn transactions<'a>(&'a self) -> Option<&Transactions<T>> { fn transactions<'a>(&'a self) -> Option<&'a Transactions<T>> {
map_full_payload_ref!(&'a _, self, move |payload, cons| { map_full_payload_ref!(&'a _, self, move |payload, cons| {
cons(payload); cons(payload);
Some(&payload.execution_payload.transactions) Some(&payload.execution_payload.transactions)
@ -488,7 +488,7 @@ impl<T: EthSpec> ExecPayload<T> for BlindedPayload<T> {
}) })
} }
fn transactions<'a>(&'a self) -> Option<&Transactions<T>> { fn transactions(&self) -> Option<&Transactions<T>> {
None None
} }
@ -574,7 +574,7 @@ impl<'b, T: EthSpec> ExecPayload<T> for BlindedPayloadRef<'b, T> {
}) })
} }
fn transactions<'a>(&'a self) -> Option<&Transactions<T>> { fn transactions(&self) -> Option<&Transactions<T>> {
None None
} }

View File

@ -17,7 +17,7 @@ impl CachedTreeHash<TreeHashCache> for Validator {
/// Efficiently tree hash a `Validator`, assuming it was updated by a valid state transition. /// Efficiently tree hash a `Validator`, assuming it was updated by a valid state transition.
/// ///
/// Specifically, we assume that the `pubkey` and `withdrawal_credentials` fields are constant. /// Specifically, we assume that the `pubkey` field is constant.
fn recalculate_tree_hash_root( fn recalculate_tree_hash_root(
&self, &self,
arena: &mut CacheArena, arena: &mut CacheArena,
@ -29,8 +29,8 @@ impl CachedTreeHash<TreeHashCache> for Validator {
.iter_mut(arena)? .iter_mut(arena)?
.enumerate() .enumerate()
.flat_map(|(i, leaf)| { .flat_map(|(i, leaf)| {
// Fields pubkey and withdrawal_credentials are constant // Pubkey field (index 0) is constant.
if (i == 0 || i == 1) && cache.initialized { if i == 0 && cache.initialized {
None None
} else if process_field_by_index(self, i, leaf, !cache.initialized) { } else if process_field_by_index(self, i, leaf, !cache.initialized) {
Some(i) Some(i)

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
test_utils::TestRandom, Address, BeaconState, BlsToExecutionChange, ChainSpec, Epoch, EthSpec, test_utils::TestRandom, Address, BeaconState, ChainSpec, Epoch, EthSpec, Hash256,
Hash256, PublicKeyBytes, PublicKeyBytes,
}; };
use serde_derive::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode}; use ssz_derive::{Decode, Encode};
@ -76,6 +76,18 @@ impl Validator {
.unwrap_or(false) .unwrap_or(false)
} }
/// Get the eth1 withdrawal address if this validator has one initialized.
pub fn get_eth1_withdrawal_address(&self, spec: &ChainSpec) -> Option<Address> {
self.has_eth1_withdrawal_credential(spec)
.then(|| {
self.withdrawal_credentials
.as_bytes()
.get(12..)
.map(Address::from_slice)
})
.flatten()
}
/// Changes withdrawal credentials to the provided eth1 execution address /// Changes withdrawal credentials to the provided eth1 execution address
/// ///
/// WARNING: this function does NO VALIDATION - it just does it! /// WARNING: this function does NO VALIDATION - it just does it!

View File

@ -1,4 +1,4 @@
TESTS_TAG := v1.2.0 TESTS_TAG := f5c7cf78
TESTS = general minimal mainnet TESTS = general minimal mainnet
TARBALLS = $(patsubst %,%-$(TESTS_TAG).tar.gz,$(TESTS)) TARBALLS = $(patsubst %,%-$(TESTS_TAG).tar.gz,$(TESTS))

View File

@ -289,8 +289,9 @@ impl<E: EthSpec, T: EpochTransition<E>> Case for EpochProcessing<E, T> {
&& T::name() != "participation_flag_updates" && T::name() != "participation_flag_updates"
} }
// No phase0 tests for Altair and later. // No phase0 tests for Altair and later.
ForkName::Altair | ForkName::Merge => T::name() != "participation_record_updates", ForkName::Altair | ForkName::Merge | ForkName::Capella => {
ForkName::Capella => false, // TODO: revisit when tests are out T::name() != "participation_record_updates"
}
ForkName::Eip4844 => false, // TODO: revisit when tests are out ForkName::Eip4844 => false, // TODO: revisit when tests are out
} }
} }

View File

@ -3,7 +3,7 @@ use crate::case_result::compare_beacon_state_results_without_caches;
use crate::cases::common::previous_fork; use crate::cases::common::previous_fork;
use crate::decode::{ssz_decode_state, yaml_decode_file}; use crate::decode::{ssz_decode_state, yaml_decode_file};
use serde_derive::Deserialize; use serde_derive::Deserialize;
use state_processing::upgrade::{upgrade_to_altair, upgrade_to_bellatrix}; use state_processing::upgrade::{upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella};
use types::{BeaconState, ForkName}; use types::{BeaconState, ForkName};
#[derive(Debug, Clone, Default, Deserialize)] #[derive(Debug, Clone, Default, Deserialize)]
@ -61,8 +61,8 @@ impl<E: EthSpec> Case for ForkTest<E> {
ForkName::Base => panic!("phase0 not supported"), ForkName::Base => panic!("phase0 not supported"),
ForkName::Altair => upgrade_to_altair(&mut result_state, spec).map(|_| result_state), ForkName::Altair => upgrade_to_altair(&mut result_state, spec).map(|_| result_state),
ForkName::Merge => upgrade_to_bellatrix(&mut result_state, spec).map(|_| result_state), ForkName::Merge => upgrade_to_bellatrix(&mut result_state, spec).map(|_| result_state),
ForkName::Capella => upgrade_to_capella(&mut result_state, spec).map(|_| result_state),
ForkName::Eip4844 => panic!("eip4844 not supported"), ForkName::Eip4844 => panic!("eip4844 not supported"),
ForkName::Capella => panic!("capella not supported"),
}; };
compare_beacon_state_results_without_caches(&mut result, &mut expected) compare_beacon_state_results_without_caches(&mut result, &mut expected)

View File

@ -1,13 +1,10 @@
use super::*; use super::*;
use crate::case_result::compare_beacon_state_results_without_caches; use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, ssz_decode_state, yaml_decode_file}; use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
use serde_derive::Deserialize; use serde_derive::Deserialize;
use state_processing::initialize_beacon_state_from_eth1; use state_processing::initialize_beacon_state_from_eth1;
use std::path::PathBuf; use std::path::PathBuf;
use types::{ use types::{BeaconState, Deposit, EthSpec, ExecutionPayloadHeader, ForkName, Hash256};
BeaconState, Deposit, EthSpec, ExecutionPayloadHeader, ExecutionPayloadHeaderMerge, ForkName,
Hash256,
};
#[derive(Debug, Clone, Deserialize)] #[derive(Debug, Clone, Deserialize)]
struct Metadata { struct Metadata {
@ -41,14 +38,10 @@ impl<E: EthSpec> LoadCase for GenesisInitialization<E> {
let meta: Metadata = yaml_decode_file(&path.join("meta.yaml"))?; let meta: Metadata = yaml_decode_file(&path.join("meta.yaml"))?;
let execution_payload_header: Option<ExecutionPayloadHeader<E>> = let execution_payload_header: Option<ExecutionPayloadHeader<E>> =
if meta.execution_payload_header.unwrap_or(false) { if meta.execution_payload_header.unwrap_or(false) {
//FIXME(sean) we could decode based on timestamp - we probably don't do decode a payload Some(ssz_decode_file_with(
// without a block this elsewhere at presetn. But when we support SSZ in the builder api we may need to.
// Although that API should include fork info. Hardcoding this for now
Some(ExecutionPayloadHeader::Merge(ssz_decode_file::<
ExecutionPayloadHeaderMerge<E>,
>(
&path.join("execution_payload_header.ssz_snappy"), &path.join("execution_payload_header.ssz_snappy"),
)?)) |bytes| ExecutionPayloadHeader::from_ssz_bytes(bytes, fork_name),
)?)
} else { } else {
None None
}; };

View File

@ -3,17 +3,16 @@ use crate::bls_setting::BlsSetting;
use crate::case_result::compare_beacon_state_results_without_caches; use crate::case_result::compare_beacon_state_results_without_caches;
use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file}; use crate::decode::{ssz_decode_file, ssz_decode_file_with, ssz_decode_state, yaml_decode_file};
use crate::testing_spec; use crate::testing_spec;
use crate::type_name::TypeName;
use serde_derive::Deserialize; use serde_derive::Deserialize;
use state_processing::{ use state_processing::{
per_block_processing::{ per_block_processing::{
errors::BlockProcessingError, errors::BlockProcessingError,
process_block_header, process_execution_payload, process_block_header, process_execution_payload,
process_operations::{ process_operations::{
altair, base, process_attester_slashings, process_deposits, process_exits, altair, base, process_attester_slashings, process_bls_to_execution_changes,
process_proposer_slashings, process_deposits, process_exits, process_proposer_slashings,
}, },
process_sync_aggregate, VerifyBlockRoot, VerifySignatures, process_sync_aggregate, process_withdrawals, VerifyBlockRoot, VerifySignatures,
}, },
ConsensusContext, ConsensusContext,
}; };
@ -21,7 +20,7 @@ use std::fmt::Debug;
use std::path::Path; use std::path::Path;
use types::{ use types::{
Attestation, AttesterSlashing, BeaconBlock, BeaconState, BlindedPayload, ChainSpec, Deposit, Attestation, AttesterSlashing, BeaconBlock, BeaconState, BlindedPayload, ChainSpec, Deposit,
EthSpec, ExecutionPayload, ExecutionPayloadMerge, ForkName, FullPayload, ProposerSlashing, EthSpec, ExecutionPayload, ForkName, FullPayload, ProposerSlashing, SignedBlsToExecutionChange,
SignedVoluntaryExit, SyncAggregate, SignedVoluntaryExit, SyncAggregate,
}; };
@ -36,6 +35,12 @@ struct ExecutionMetadata {
execution_valid: bool, execution_valid: bool,
} }
/// Newtype for testing withdrawals.
#[derive(Debug, Clone, Deserialize)]
pub struct WithdrawalsPayload<T: EthSpec> {
payload: FullPayload<T>,
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Operations<E: EthSpec, O: Operation<E>> { pub struct Operations<E: EthSpec, O: Operation<E>> {
metadata: Metadata, metadata: Metadata,
@ -45,10 +50,8 @@ pub struct Operations<E: EthSpec, O: Operation<E>> {
pub post: Option<BeaconState<E>>, pub post: Option<BeaconState<E>>,
} }
pub trait Operation<E: EthSpec>: TypeName + Debug + Sync + Sized { pub trait Operation<E: EthSpec>: Debug + Sync + Sized {
fn handler_name() -> String { fn handler_name() -> String;
Self::name().to_lowercase()
}
fn filename() -> String { fn filename() -> String {
format!("{}.ssz_snappy", Self::handler_name()) format!("{}.ssz_snappy", Self::handler_name())
@ -58,7 +61,7 @@ pub trait Operation<E: EthSpec>: TypeName + Debug + Sync + Sized {
true true
} }
fn decode(path: &Path, spec: &ChainSpec) -> Result<Self, Error>; fn decode(path: &Path, fork_name: ForkName, spec: &ChainSpec) -> Result<Self, Error>;
fn apply_to( fn apply_to(
&self, &self,
@ -69,7 +72,11 @@ pub trait Operation<E: EthSpec>: TypeName + Debug + Sync + Sized {
} }
impl<E: EthSpec> Operation<E> for Attestation<E> { impl<E: EthSpec> Operation<E> for Attestation<E> {
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { fn handler_name() -> String {
"attestation".into()
}
fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path) ssz_decode_file(path)
} }
@ -109,7 +116,7 @@ impl<E: EthSpec> Operation<E> for AttesterSlashing<E> {
"attester_slashing".into() "attester_slashing".into()
} }
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path) ssz_decode_file(path)
} }
@ -131,7 +138,11 @@ impl<E: EthSpec> Operation<E> for AttesterSlashing<E> {
} }
impl<E: EthSpec> Operation<E> for Deposit { impl<E: EthSpec> Operation<E> for Deposit {
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { fn handler_name() -> String {
"deposit".into()
}
fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path) ssz_decode_file(path)
} }
@ -155,7 +166,7 @@ impl<E: EthSpec> Operation<E> for ProposerSlashing {
"proposer_slashing".into() "proposer_slashing".into()
} }
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path) ssz_decode_file(path)
} }
@ -181,7 +192,7 @@ impl<E: EthSpec> Operation<E> for SignedVoluntaryExit {
"voluntary_exit".into() "voluntary_exit".into()
} }
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path) ssz_decode_file(path)
} }
@ -204,7 +215,7 @@ impl<E: EthSpec> Operation<E> for BeaconBlock<E> {
"block.ssz_snappy".into() "block.ssz_snappy".into()
} }
fn decode(path: &Path, spec: &ChainSpec) -> Result<Self, Error> { fn decode(path: &Path, _fork_name: ForkName, spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file_with(path, |bytes| BeaconBlock::from_ssz_bytes(bytes, spec)) ssz_decode_file_with(path, |bytes| BeaconBlock::from_ssz_bytes(bytes, spec))
} }
@ -239,7 +250,7 @@ impl<E: EthSpec> Operation<E> for SyncAggregate<E> {
fork_name != ForkName::Base fork_name != ForkName::Base
} }
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path) ssz_decode_file(path)
} }
@ -267,13 +278,11 @@ impl<E: EthSpec> Operation<E> for FullPayload<E> {
fork_name != ForkName::Base && fork_name != ForkName::Altair fork_name != ForkName::Base && fork_name != ForkName::Altair
} }
//FIXME(sean) we could decode based on timestamp - we probably don't do decode a payload fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
// without a block this elsewhere at presetn. But when we support SSZ in the builder api we may need to. ssz_decode_file_with(path, |bytes| {
// Although that API should include fork info. Hardcoding this for now ExecutionPayload::from_ssz_bytes(bytes, fork_name)
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { })
ssz_decode_file::<ExecutionPayloadMerge<E>>(path) .map(Into::into)
.map(ExecutionPayload::Merge)
.map(Into::into)
} }
fn apply_to( fn apply_to(
@ -306,13 +315,11 @@ impl<E: EthSpec> Operation<E> for BlindedPayload<E> {
fork_name != ForkName::Base && fork_name != ForkName::Altair fork_name != ForkName::Base && fork_name != ForkName::Altair
} }
fn decode(path: &Path, _spec: &ChainSpec) -> Result<Self, Error> { fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
//FIXME(sean) we could decode based on timestamp - we probably don't do decode a payload ssz_decode_file_with(path, |bytes| {
// without a block this elsewhere at presetn. But when we support SSZ in the builder api we may need to. ExecutionPayload::from_ssz_bytes(bytes, fork_name)
// Although that API should include fork info. Hardcoding this for now })
let payload: Result<ExecutionPayload<E>, Error> = .map(Into::into)
ssz_decode_file::<ExecutionPayloadMerge<E>>(path).map(Into::into);
payload.map(Into::into)
} }
fn apply_to( fn apply_to(
@ -333,6 +340,65 @@ impl<E: EthSpec> Operation<E> for BlindedPayload<E> {
} }
} }
impl<E: EthSpec> Operation<E> for WithdrawalsPayload<E> {
fn handler_name() -> String {
"withdrawals".into()
}
fn filename() -> String {
"execution_payload.ssz_snappy".into()
}
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
fork_name != ForkName::Base && fork_name != ForkName::Altair && fork_name != ForkName::Merge
}
fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file_with(path, |bytes| {
ExecutionPayload::from_ssz_bytes(bytes, fork_name)
})
.map(|payload| WithdrawalsPayload {
payload: payload.into(),
})
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
_: &Operations<E, Self>,
) -> Result<(), BlockProcessingError> {
process_withdrawals::<_, FullPayload<_>>(state, self.payload.to_ref(), spec)
}
}
impl<E: EthSpec> Operation<E> for SignedBlsToExecutionChange {
fn handler_name() -> String {
"bls_to_execution_change".into()
}
fn filename() -> String {
"address_change.ssz_snappy".into()
}
fn is_enabled_for_fork(fork_name: ForkName) -> bool {
fork_name != ForkName::Base && fork_name != ForkName::Altair && fork_name != ForkName::Merge
}
fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result<Self, Error> {
ssz_decode_file(path)
}
fn apply_to(
&self,
state: &mut BeaconState<E>,
spec: &ChainSpec,
_extra: &Operations<E, Self>,
) -> Result<(), BlockProcessingError> {
process_bls_to_execution_changes(state, &[self.clone()], VerifySignatures::True, spec)
}
}
impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> { impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> { fn load_from_dir(path: &Path, fork_name: ForkName) -> Result<Self, Error> {
let spec = &testing_spec::<E>(fork_name); let spec = &testing_spec::<E>(fork_name);
@ -356,7 +422,7 @@ impl<E: EthSpec, O: Operation<E>> LoadCase for Operations<E, O> {
// Check BLS setting here before SSZ deserialization, as most types require signatures // Check BLS setting here before SSZ deserialization, as most types require signatures
// to be valid. // to be valid.
let (operation, bls_error) = if metadata.bls_setting.unwrap_or_default().check().is_ok() { let (operation, bls_error) = if metadata.bls_setting.unwrap_or_default().check().is_ok() {
match O::decode(&path.join(O::filename()), spec) { match O::decode(&path.join(O::filename()), fork_name, spec) {
Ok(op) => (Some(op), None), Ok(op) => (Some(op), None),
Err(Error::InvalidBLSInput(error)) => (None, Some(error)), Err(Error::InvalidBLSInput(error)) => (None, Some(error)),
Err(e) => return Err(e), Err(e) => return Err(e),
@ -399,9 +465,11 @@ impl<E: EthSpec, O: Operation<E>> Case for Operations<E, O> {
let mut expected = self.post.clone(); let mut expected = self.post.clone();
// Processing requires the committee caches. // Processing requires the committee caches.
state // NOTE: some of the withdrawals tests have 0 active validators, do not try
.build_all_committee_caches(spec) // to build the commitee cache in this case.
.expect("committee caches OK"); if O::handler_name() != "withdrawals" {
state.build_all_committee_caches(spec).unwrap();
}
let mut result = self let mut result = self
.operation .operation

View File

@ -42,14 +42,17 @@ impl<E: EthSpec> LoadCase for TransitionTest<E> {
spec.altair_fork_epoch = Some(Epoch::new(0)); spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(metadata.fork_epoch); spec.bellatrix_fork_epoch = Some(metadata.fork_epoch);
} }
ForkName::Eip4844 => {
spec.bellatrix_fork_epoch = Some(Epoch::new(0));
spec.eip4844_fork_epoch = Some(metadata.fork_epoch);
}
ForkName::Capella => { ForkName::Capella => {
spec.capella_fork_epoch = Some(Epoch::new(0)); spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(Epoch::new(0));
spec.capella_fork_epoch = Some(metadata.fork_epoch); spec.capella_fork_epoch = Some(metadata.fork_epoch);
} }
ForkName::Eip4844 => {
spec.altair_fork_epoch = Some(Epoch::new(0));
spec.bellatrix_fork_epoch = Some(Epoch::new(0));
spec.capella_fork_epoch = Some(Epoch::new(0));
spec.eip4844_fork_epoch = Some(metadata.fork_epoch);
}
} }
// Load blocks // Load blocks

View File

@ -24,6 +24,11 @@ pub trait Handler {
fn run(&self) { fn run(&self) {
for fork_name in ForkName::list_all() { for fork_name in ForkName::list_all() {
// FIXME(eip4844): enable eip4844
if fork_name == ForkName::Eip4844 {
continue;
}
if self.is_enabled_for_fork(fork_name) { if self.is_enabled_for_fork(fork_name) {
self.run_for_fork(fork_name) self.run_for_fork(fork_name)
} }
@ -218,6 +223,10 @@ impl<T, E> SszStaticHandler<T, E> {
Self::for_forks(vec![ForkName::Merge]) Self::for_forks(vec![ForkName::Merge])
} }
pub fn capella_only() -> Self {
Self::for_forks(vec![ForkName::Capella])
}
pub fn merge_and_later() -> Self { pub fn merge_and_later() -> Self {
Self::for_forks(ForkName::list_all()[2..].to_vec()) Self::for_forks(ForkName::list_all()[2..].to_vec())
} }
@ -533,10 +542,8 @@ impl<E: EthSpec + TypeName> Handler for ForkChoiceHandler<E> {
} }
fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool {
// Merge block tests are only enabled for Bellatrix or later. // Merge block tests are only enabled for Bellatrix.
if self.handler_name == "on_merge_block" if self.handler_name == "on_merge_block" && fork_name != ForkName::Merge {
&& (fork_name == ForkName::Base || fork_name == ForkName::Altair)
{
return false; return false;
} }

View File

@ -1,10 +1,9 @@
pub use case_result::CaseResult; pub use case_result::CaseResult;
pub use cases::Case;
pub use cases::{ pub use cases::{
EffectiveBalanceUpdates, Eth1DataReset, HistoricalRootsUpdate, InactivityUpdates, Case, EffectiveBalanceUpdates, Eth1DataReset, HistoricalRootsUpdate, InactivityUpdates,
JustificationAndFinalization, ParticipationFlagUpdates, ParticipationRecordUpdates, JustificationAndFinalization, ParticipationFlagUpdates, ParticipationRecordUpdates,
RandaoMixesReset, RegistryUpdates, RewardsAndPenalties, Slashings, SlashingsReset, RandaoMixesReset, RegistryUpdates, RewardsAndPenalties, Slashings, SlashingsReset,
SyncCommitteeUpdates, SyncCommitteeUpdates, WithdrawalsPayload,
}; };
pub use decode::log_file_access; pub use decode::log_file_access;
pub use error::Error; pub use error::Error;

View File

@ -45,6 +45,8 @@ type_name_generic!(BeaconBlockBody);
type_name_generic!(BeaconBlockBodyBase, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyBase, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyAltair, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyAltair, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyMerge, "BeaconBlockBody"); type_name_generic!(BeaconBlockBodyMerge, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyCapella, "BeaconBlockBody");
type_name_generic!(BeaconBlockBodyEip4844, "BeaconBlockBody");
type_name!(BeaconBlockHeader); type_name!(BeaconBlockHeader);
type_name_generic!(BeaconState); type_name_generic!(BeaconState);
type_name!(Checkpoint); type_name!(Checkpoint);
@ -54,8 +56,14 @@ type_name!(DepositData);
type_name!(DepositMessage); type_name!(DepositMessage);
type_name!(Eth1Data); type_name!(Eth1Data);
type_name_generic!(ExecutionPayload); type_name_generic!(ExecutionPayload);
type_name_generic!(ExecutionPayloadMerge, "ExecutionPayload");
type_name_generic!(ExecutionPayloadCapella, "ExecutionPayload");
type_name_generic!(ExecutionPayloadEip4844, "ExecutionPayload");
type_name_generic!(FullPayload, "ExecutionPayload"); type_name_generic!(FullPayload, "ExecutionPayload");
type_name_generic!(ExecutionPayloadHeader); type_name_generic!(ExecutionPayloadHeader);
type_name_generic!(ExecutionPayloadHeaderMerge, "ExecutionPayloadHeader");
type_name_generic!(ExecutionPayloadHeaderCapella, "ExecutionPayloadHeader");
type_name_generic!(ExecutionPayloadHeaderEip4844, "ExecutionPayloadHeader");
type_name_generic!(BlindedPayload, "ExecutionPayloadHeader"); type_name_generic!(BlindedPayload, "ExecutionPayloadHeader");
type_name!(Fork); type_name!(Fork);
type_name!(ForkData); type_name!(ForkData);
@ -76,3 +84,6 @@ type_name_generic!(SyncAggregate);
type_name_generic!(SyncCommittee); type_name_generic!(SyncCommittee);
type_name!(Validator); type_name!(Validator);
type_name!(VoluntaryExit); type_name!(VoluntaryExit);
type_name!(Withdrawal);
type_name!(BlsToExecutionChange, "BLSToExecutionChange");
type_name!(SignedBlsToExecutionChange, "SignedBLSToExecutionChange");

View File

@ -82,6 +82,18 @@ fn operations_execution_payload_blinded() {
OperationsHandler::<MainnetEthSpec, BlindedPayload<_>>::default().run(); OperationsHandler::<MainnetEthSpec, BlindedPayload<_>>::default().run();
} }
#[test]
fn operations_withdrawals() {
OperationsHandler::<MinimalEthSpec, WithdrawalsPayload<_>>::default().run();
OperationsHandler::<MainnetEthSpec, WithdrawalsPayload<_>>::default().run();
}
#[test]
fn operations_bls_to_execution_change() {
OperationsHandler::<MinimalEthSpec, SignedBlsToExecutionChange>::default().run();
OperationsHandler::<MainnetEthSpec, SignedBlsToExecutionChange>::default().run();
}
#[test] #[test]
fn sanity_blocks() { fn sanity_blocks() {
SanityBlocksHandler::<MinimalEthSpec>::default().run(); SanityBlocksHandler::<MinimalEthSpec>::default().run();
@ -250,6 +262,10 @@ mod ssz_static {
.run(); .run();
SszStaticHandler::<BeaconBlockBodyMerge<MainnetEthSpec>, MainnetEthSpec>::merge_only() SszStaticHandler::<BeaconBlockBodyMerge<MainnetEthSpec>, MainnetEthSpec>::merge_only()
.run(); .run();
SszStaticHandler::<BeaconBlockBodyCapella<MinimalEthSpec>, MinimalEthSpec>::capella_only()
.run();
SszStaticHandler::<BeaconBlockBodyCapella<MainnetEthSpec>, MainnetEthSpec>::capella_only()
.run();
} }
// Altair and later // Altair and later
@ -302,18 +318,44 @@ mod ssz_static {
// Merge and later // Merge and later
#[test] #[test]
fn execution_payload() { fn execution_payload() {
SszStaticHandler::<ExecutionPayload<MinimalEthSpec>, MinimalEthSpec>::merge_and_later() SszStaticHandler::<ExecutionPayloadMerge<MinimalEthSpec>, MinimalEthSpec>::merge_only()
.run(); .run();
SszStaticHandler::<ExecutionPayload<MainnetEthSpec>, MainnetEthSpec>::merge_and_later() SszStaticHandler::<ExecutionPayloadMerge<MainnetEthSpec>, MainnetEthSpec>::merge_only()
.run();
SszStaticHandler::<ExecutionPayloadCapella<MinimalEthSpec>, MinimalEthSpec>::capella_only()
.run();
SszStaticHandler::<ExecutionPayloadCapella<MainnetEthSpec>, MainnetEthSpec>::capella_only()
.run(); .run();
} }
#[test] #[test]
fn execution_payload_header() { fn execution_payload_header() {
SszStaticHandler::<ExecutionPayloadHeader<MinimalEthSpec>, MinimalEthSpec>::merge_and_later() SszStaticHandler::<ExecutionPayloadHeaderMerge<MinimalEthSpec>, MinimalEthSpec>::merge_only()
.run(); .run();
SszStaticHandler::<ExecutionPayloadHeader<MainnetEthSpec>, MainnetEthSpec>::merge_and_later() SszStaticHandler::<ExecutionPayloadHeaderMerge<MainnetEthSpec>, MainnetEthSpec>::merge_only()
.run(); .run();
SszStaticHandler::<ExecutionPayloadHeaderCapella<MinimalEthSpec>, MinimalEthSpec>
::capella_only().run();
SszStaticHandler::<ExecutionPayloadHeaderCapella<MainnetEthSpec>, MainnetEthSpec>
::capella_only().run();
}
#[test]
fn withdrawal() {
SszStaticHandler::<Withdrawal, MinimalEthSpec>::capella_only().run();
SszStaticHandler::<Withdrawal, MainnetEthSpec>::capella_only().run();
}
#[test]
fn bls_to_execution_change() {
SszStaticHandler::<BlsToExecutionChange, MinimalEthSpec>::capella_only().run();
SszStaticHandler::<BlsToExecutionChange, MainnetEthSpec>::capella_only().run();
}
#[test]
fn signed_bls_to_execution_change() {
SszStaticHandler::<SignedBlsToExecutionChange, MinimalEthSpec>::capella_only().run();
SszStaticHandler::<SignedBlsToExecutionChange, MainnetEthSpec>::capella_only().run();
} }
} }