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
37 changed files with 434 additions and 196 deletions

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> {
// FIXME: deal with capella / eip4844 forks here as well
/// Returns an empty block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self {
if spec.bellatrix_fork_epoch == Some(T::genesis_epoch()) {
Self::Merge(BeaconBlockMerge::empty(spec))
} else if spec.altair_fork_epoch == Some(T::genesis_epoch()) {
Self::Altair(BeaconBlockAltair::empty(spec))
} else {
Self::Base(BeaconBlockBase::empty(spec))
}
map_fork_name!(
spec.fork_name_at_epoch(T::genesis_epoch()),
Self,
EmptyBlock::empty(spec)
)
}
/// 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
/// on the fork slot.
pub fn any_from_ssz_bytes(bytes: &[u8]) -> Result<Self, ssz::DecodeError> {
BeaconBlockMerge::from_ssz_bytes(bytes)
.map(BeaconBlock::Merge)
.or_else(|_| {
BeaconBlockAltair::from_ssz_bytes(bytes)
.map(BeaconBlock::Altair)
.or_else(|_| BeaconBlockBase::from_ssz_bytes(bytes).map(BeaconBlock::Base))
})
BeaconBlockEip4844::from_ssz_bytes(bytes)
.map(BeaconBlock::Eip4844)
.or_else(|_| BeaconBlockCapella::from_ssz_bytes(bytes).map(BeaconBlock::Capella))
.or_else(|_| BeaconBlockMerge::from_ssz_bytes(bytes).map(BeaconBlock::Merge))
.or_else(|_| BeaconBlockAltair::from_ssz_bytes(bytes).map(BeaconBlock::Altair))
.or_else(|_| BeaconBlockBase::from_ssz_bytes(bytes).map(BeaconBlock::Base))
}
/// 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> {
/// Returns an empty block to be used during genesis.
pub fn empty(spec: &ChainSpec) -> Self {
impl<T: EthSpec, Payload: AbstractExecPayload<T>> EmptyBlock for BeaconBlockBase<T, Payload> {
fn empty(spec: &ChainSpec) -> Self {
BeaconBlockBase {
slot: spec.genesis_slot,
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.
pub fn full(spec: &ChainSpec) -> Self {
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.
pub fn empty(spec: &ChainSpec) -> Self {
fn empty(spec: &ChainSpec) -> Self {
BeaconBlockAltair {
slot: spec.genesis_slot,
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.
pub fn full(spec: &ChainSpec) -> Self {
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.
pub fn empty(spec: &ChainSpec) -> Self {
fn empty(spec: &ChainSpec) -> Self {
BeaconBlockMerge {
slot: spec.genesis_slot,
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.
impl<E: EthSpec> From<BeaconBlockBase<E, BlindedPayload<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> {
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()
}
}

View File

@@ -296,10 +296,10 @@ where
// Withdrawals
#[cfg(feature = "withdrawals")]
#[superstruct(only(Capella, Eip4844))]
#[superstruct(only(Capella, Eip4844), partial_getter(copy))]
pub next_withdrawal_index: u64,
#[cfg(feature = "withdrawals")]
#[superstruct(only(Capella, Eip4844))]
#[superstruct(only(Capella, Eip4844), partial_getter(copy))]
pub next_withdrawal_validator_index: u64,
// 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::Altair(x), BeaconState::Altair(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",),
}
}

View File

@@ -363,6 +363,16 @@ impl<T: EthSpec> BeaconTreeHashCacheInner<T> {
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()?;
self.previous_state = Some((root, state.slot()));

View File

@@ -4,7 +4,6 @@ use serde_derive::{Deserialize, Serialize};
use ssz::Encode;
use ssz_derive::{Decode, Encode};
use ssz_types::VariableList;
use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
#[cfg_attr(feature = "arbitrary-fuzz", derive(arbitrary::Arbitrary))]
@@ -23,6 +22,7 @@ impl<T: EthSpec> BlobsSidecar<T> {
pub fn empty() -> Self {
Self::default()
}
#[allow(clippy::integer_arithmetic)]
pub fn max_size() -> usize {
// Fixed part
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 MaxPendingAttestations = U1024; // 128 max attestations * 8 slots per epoch
type SlotsPerEth1VotingPeriod = U32; // 4 epochs * 8 slots per epoch
type MaxWithdrawalsPerPayload = U4;
params_from_eth_spec!(MainnetEthSpec {
JustificationBitsLength,
@@ -345,7 +346,6 @@ impl EthSpec for MinimalEthSpec {
MinGasLimit,
MaxExtraDataBytes,
MaxBlsToExecutionChanges,
MaxWithdrawalsPerPayload,
MaxBlobsPerBlock,
FieldElementsPerBlob
});

View File

@@ -1,7 +1,7 @@
use crate::{test_utils::TestRandom, *};
use derivative::Derivative;
use serde_derive::{Deserialize, Serialize};
use ssz::Encode;
use ssz::{Decode, Encode};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
@@ -87,6 +87,17 @@ pub struct ExecutionPayload<T: EthSpec> {
}
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)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_merge_size() -> usize {

View File

@@ -1,6 +1,7 @@
use crate::{test_utils::TestRandom, *};
use derivative::Derivative;
use serde_derive::{Deserialize, Serialize};
use ssz::Decode;
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
use tree_hash::TreeHash;
@@ -84,31 +85,34 @@ impl<T: EthSpec> ExecutionPayloadHeader<T> {
pub fn transactions(&self) -> Option<&Transactions<T>> {
None
}
}
impl<'a, T: EthSpec> ExecutionPayloadHeaderRef<'a, T> {
// FIXME: maybe this could be a derived trait..
pub fn is_default(self) -> bool {
match self {
ExecutionPayloadHeaderRef::Merge(header) => {
*header == ExecutionPayloadHeaderMerge::default()
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 ExecutionPayloadHeader: {fork_name}",
))),
ForkName::Merge => ExecutionPayloadHeaderMerge::from_ssz_bytes(bytes).map(Self::Merge),
ForkName::Capella => {
ExecutionPayloadHeaderCapella::from_ssz_bytes(bytes).map(Self::Capella)
}
ExecutionPayloadHeaderRef::Capella(header) => {
*header == ExecutionPayloadHeaderCapella::default()
}
ExecutionPayloadHeaderRef::Eip4844(header) => {
*header == ExecutionPayloadHeaderEip4844::default()
ForkName::Eip4844 => {
ExecutionPayloadHeaderEip4844::from_ssz_bytes(bytes).map(Self::Eip4844)
}
}
}
}
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> {
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 {
parent_hash: self.parent_hash,
fee_recipient: self.fee_recipient,
@@ -125,8 +129,7 @@ impl<T: EthSpec> ExecutionPayloadHeaderMerge<T> {
block_hash: self.block_hash,
transactions_root: self.transactions_root,
#[cfg(feature = "withdrawals")]
// FIXME: the spec doesn't seem to define what to do here..
withdrawals_root: empty_withdrawals_root,
withdrawals_root: Hash256::zero(),
}
}
}

View File

@@ -14,7 +14,7 @@ pub struct KzgCommitment(#[serde(with = "BigArray")] pub [u8; 48]);
impl Display for KzgCommitment {
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 serde::{Deserialize, Serialize};
use serde_big_array::BigArray;
use ssz::{Decode, DecodeError, Encode};
use ssz_derive::{Decode, Encode};
use std::fmt;
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 {
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::beacon_block::{
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockCapella, BeaconBlockEip4844,
BeaconBlockMerge, BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock,
BeaconBlockMerge, BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock, EmptyBlock,
};
pub use crate::beacon_block_body::{
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| {
cons(payload);
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> {
map_full_payload_ref!(&'a _, self, move |payload, cons| {
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| {
cons(payload);
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
}
@@ -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
}

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.
///
/// 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(
&self,
arena: &mut CacheArena,
@@ -29,8 +29,8 @@ impl CachedTreeHash<TreeHashCache> for Validator {
.iter_mut(arena)?
.enumerate()
.flat_map(|(i, leaf)| {
// Fields pubkey and withdrawal_credentials are constant
if (i == 0 || i == 1) && cache.initialized {
// Pubkey field (index 0) is constant.
if i == 0 && cache.initialized {
None
} else if process_field_by_index(self, i, leaf, !cache.initialized) {
Some(i)

View File

@@ -1,6 +1,6 @@
use crate::{
test_utils::TestRandom, Address, BeaconState, BlsToExecutionChange, ChainSpec, Epoch, EthSpec,
Hash256, PublicKeyBytes,
test_utils::TestRandom, Address, BeaconState, ChainSpec, Epoch, EthSpec, Hash256,
PublicKeyBytes,
};
use serde_derive::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@@ -76,6 +76,18 @@ impl Validator {
.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
///
/// WARNING: this function does NO VALIDATION - it just does it!