Add Fulu boilerplate (#6695)

* Add Fulu boilerplate

* Add more boilerplate

* Change fulu_time to osaka_time

* Merge branch 'unstable' into fulu-boilerplate

* Fix tests

* Merge branch 'unstable' into fulu-boilerplate

* More test fixes

* Apply suggestions

* Remove `get_payload` boilerplate

* Add lightclient fulu types and fix beacon-chain-tests

* Disable Fulu in ef-tests

* Reduce boilerplate for future forks

* Small fixes

* One more fix

* Apply suggestions

* Merge branch 'unstable' into fulu-boilerplate

* Fix lints
This commit is contained in:
Mac L
2025-01-10 09:25:23 +04:00
committed by GitHub
parent 722573f7ed
commit ecdf2d891f
91 changed files with 2365 additions and 674 deletions

View File

@@ -755,20 +755,15 @@ where
if let Some((parent_justified, parent_finalized)) = parent_checkpoints {
(parent_justified, parent_finalized)
} else {
let justification_and_finalization_state = match block {
BeaconBlockRef::Electra(_)
| BeaconBlockRef::Deneb(_)
| BeaconBlockRef::Capella(_)
| BeaconBlockRef::Bellatrix(_)
| BeaconBlockRef::Altair(_) => {
let justification_and_finalization_state =
if block.fork_name_unchecked().altair_enabled() {
// NOTE: Processing justification & finalization requires the progressive
// balances cache, but we cannot initialize it here as we only have an
// immutable reference. The state *should* have come straight from block
// processing, which initialises the cache, but if we add other `on_block`
// calls in future it could be worth passing a mutable reference.
per_epoch_processing::altair::process_justification_and_finalization(state)?
}
BeaconBlockRef::Base(_) => {
} else {
let mut validator_statuses =
per_epoch_processing::base::ValidatorStatuses::new(state, spec)
.map_err(Error::ValidatorStatuses)?;
@@ -780,8 +775,7 @@ where
&validator_statuses.total_balances,
spec,
)?
}
};
};
(
justification_and_finalization_state.current_justified_checkpoint(),

View File

@@ -1263,7 +1263,7 @@ async fn progressive_balances_cache_proposer_slashing() {
// (`HeaderInvalid::ProposerSlashed`). The harness should be re-worked to successfully skip
// the slot in this scenario rather than panic-ing. The same applies to
// `progressive_balances_cache_attester_slashing`.
.apply_blocks(2)
.apply_blocks(1)
.await
.add_previous_epoch_proposer_slashing(MainnetEthSpec::slots_per_epoch())
.await

View File

@@ -44,22 +44,15 @@ pub fn get_attestation_participation_flag_indices<E: EthSpec>(
if is_matching_source && inclusion_delay <= E::slots_per_epoch().integer_sqrt() {
participation_flag_indices.push(TIMELY_SOURCE_FLAG_INDEX);
}
match state {
&BeaconState::Base(_)
| &BeaconState::Altair(_)
| &BeaconState::Bellatrix(_)
| &BeaconState::Capella(_) => {
if is_matching_target && inclusion_delay <= E::slots_per_epoch() {
participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX);
}
}
&BeaconState::Deneb(_) | &BeaconState::Electra(_) => {
if is_matching_target {
// [Modified in Deneb:EIP7045]
participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX);
}
if state.fork_name_unchecked().deneb_enabled() {
if is_matching_target {
// [Modified in Deneb:EIP7045]
participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX);
}
} else if is_matching_target && inclusion_delay <= E::slots_per_epoch() {
participation_flag_indices.push(TIMELY_TARGET_FLAG_INDEX);
}
if is_matching_head && inclusion_delay == spec.min_attestation_inclusion_delay {
participation_flag_indices.push(TIMELY_HEAD_FLAG_INDEX);
}

View File

@@ -55,15 +55,12 @@ pub fn slash_validator<E: EthSpec>(
let whistleblower_index = opt_whistleblower_index.unwrap_or(proposer_index);
let whistleblower_reward = validator_effective_balance
.safe_div(spec.whistleblower_reward_quotient_for_state(state))?;
let proposer_reward = match state {
BeaconState::Base(_) => whistleblower_reward.safe_div(spec.proposer_reward_quotient)?,
BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_) => whistleblower_reward
let proposer_reward = if state.fork_name_unchecked().altair_enabled() {
whistleblower_reward
.safe_mul(PROPOSER_WEIGHT)?
.safe_div(WEIGHT_DENOMINATOR)?,
.safe_div(WEIGHT_DENOMINATOR)?
} else {
whistleblower_reward.safe_div(spec.proposer_reward_quotient)?
};
// Ensure the whistleblower index is in the validator registry.

View File

@@ -4,7 +4,7 @@ use super::per_block_processing::{
use crate::common::DepositDataTree;
use crate::upgrade::electra::upgrade_state_to_electra;
use crate::upgrade::{
upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb,
upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb, upgrade_to_fulu,
};
use safe_arith::{ArithError, SafeArith};
use std::sync::Arc;
@@ -135,11 +135,27 @@ pub fn initialize_beacon_state_from_eth1<E: EthSpec>(
// Override latest execution payload header.
// See https://github.com/ethereum/consensus-specs/blob/dev/specs/capella/beacon-chain.md#testing
if let Some(ExecutionPayloadHeader::Electra(header)) = execution_payload_header {
if let Some(ExecutionPayloadHeader::Electra(ref header)) = execution_payload_header {
*state.latest_execution_payload_header_electra_mut()? = header.clone();
}
}
// Upgrade to fulu if configured from genesis.
if spec
.fulu_fork_epoch
.is_some_and(|fork_epoch| fork_epoch == E::genesis_epoch())
{
upgrade_to_fulu(&mut state, spec)?;
// Remove intermediate Electra fork from `state.fork`.
state.fork_mut().previous_version = spec.fulu_fork_version;
// Override latest execution payload header.
if let Some(ExecutionPayloadHeader::Fulu(header)) = execution_payload_header {
*state.latest_execution_payload_header_fulu_mut()? = header.clone();
}
}
// Now that we have our validators, initialize the caches (including the committees)
state.build_caches(spec)?;

View File

@@ -442,6 +442,12 @@ pub fn process_execution_payload<E: EthSpec, Payload: AbstractExecPayload<E>>(
_ => return Err(BlockProcessingError::IncorrectStateType),
}
}
ExecutionPayloadHeaderRefMut::Fulu(header_mut) => {
match payload.to_execution_payload_header() {
ExecutionPayloadHeader::Fulu(header) => *header_mut = header,
_ => return Err(BlockProcessingError::IncorrectStateType),
}
}
}
Ok(())
@@ -453,15 +459,17 @@ pub fn process_execution_payload<E: EthSpec, Payload: AbstractExecPayload<E>>(
/// repeatedly write code to treat these errors as false.
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_merge_transition_complete
pub fn is_merge_transition_complete<E: EthSpec>(state: &BeaconState<E>) -> bool {
match state {
if state.fork_name_unchecked().capella_enabled() {
true
} else if state.fork_name_unchecked().bellatrix_enabled() {
// We must check defaultness against the payload header with 0x0 roots, as that's what's meant
// by `ExecutionPayloadHeader()` in the spec.
BeaconState::Bellatrix(_) => state
state
.latest_execution_payload_header()
.map(|header| !header.is_default_with_zero_roots())
.unwrap_or(false),
BeaconState::Electra(_) | BeaconState::Deneb(_) | BeaconState::Capella(_) => true,
BeaconState::Base(_) | BeaconState::Altair(_) => false,
.unwrap_or(false)
} else {
false
}
}
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/bellatrix/beacon-chain.md#is_merge_transition_block
@@ -603,66 +611,65 @@ pub fn process_withdrawals<E: EthSpec, Payload: AbstractExecPayload<E>>(
payload: Payload::Ref<'_>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
match state {
BeaconState::Capella(_) | BeaconState::Deneb(_) | BeaconState::Electra(_) => {
let (expected_withdrawals, partial_withdrawals_count) =
get_expected_withdrawals(state, spec)?;
let expected_root = expected_withdrawals.tree_hash_root();
let withdrawals_root = payload.withdrawals_root()?;
if state.fork_name_unchecked().capella_enabled() {
let (expected_withdrawals, partial_withdrawals_count) =
get_expected_withdrawals(state, spec)?;
let expected_root = expected_withdrawals.tree_hash_root();
let withdrawals_root = payload.withdrawals_root()?;
if expected_root != withdrawals_root {
return Err(BlockProcessingError::WithdrawalsRootMismatch {
expected: expected_root,
found: withdrawals_root,
});
}
if expected_root != withdrawals_root {
return Err(BlockProcessingError::WithdrawalsRootMismatch {
expected: expected_root,
found: withdrawals_root,
});
}
for withdrawal in expected_withdrawals.iter() {
decrease_balance(
state,
withdrawal.validator_index as usize,
withdrawal.amount,
)?;
}
for withdrawal in expected_withdrawals.iter() {
decrease_balance(
state,
withdrawal.validator_index as usize,
withdrawal.amount,
)?;
}
// Update pending partial withdrawals [New in Electra:EIP7251]
if let Some(partial_withdrawals_count) = partial_withdrawals_count {
// TODO(electra): Use efficient pop_front after milhouse release https://github.com/sigp/milhouse/pull/38
let new_partial_withdrawals = state
.pending_partial_withdrawals()?
.iter_from(partial_withdrawals_count)?
.cloned()
.collect::<Vec<_>>();
*state.pending_partial_withdrawals_mut()? = List::new(new_partial_withdrawals)?;
}
// Update pending partial withdrawals [New in Electra:EIP7251]
if let Some(partial_withdrawals_count) = partial_withdrawals_count {
// TODO(electra): Use efficient pop_front after milhouse release https://github.com/sigp/milhouse/pull/38
let new_partial_withdrawals = state
.pending_partial_withdrawals()?
.iter_from(partial_withdrawals_count)?
.cloned()
.collect::<Vec<_>>();
*state.pending_partial_withdrawals_mut()? = List::new(new_partial_withdrawals)?;
}
// Update the next withdrawal index if this block contained withdrawals
if let Some(latest_withdrawal) = expected_withdrawals.last() {
*state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
// Update the next withdrawal index if this block contained withdrawals
if let Some(latest_withdrawal) = expected_withdrawals.last() {
*state.next_withdrawal_index_mut()? = latest_withdrawal.index.safe_add(1)?;
// Update the next validator index to start the next withdrawal sweep
if expected_withdrawals.len() == E::max_withdrawals_per_payload() {
// Next sweep starts after the latest withdrawal's validator index
let next_validator_index = latest_withdrawal
.validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
}
}
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
if expected_withdrawals.len() != E::max_withdrawals_per_payload() {
let next_validator_index = state
.next_withdrawal_validator_index()?
.safe_add(spec.max_validators_per_withdrawals_sweep)?
// Update the next validator index to start the next withdrawal sweep
if expected_withdrawals.len() == E::max_withdrawals_per_payload() {
// Next sweep starts after the latest withdrawal's validator index
let next_validator_index = latest_withdrawal
.validator_index
.safe_add(1)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
}
Ok(())
}
// Advance sweep by the max length of the sweep if there was not a full set of withdrawals
if expected_withdrawals.len() != E::max_withdrawals_per_payload() {
let next_validator_index = state
.next_withdrawal_validator_index()?
.safe_add(spec.max_validators_per_withdrawals_sweep)?
.safe_rem(state.validators().len() as u64)?;
*state.next_withdrawal_validator_index_mut()? = next_validator_index;
}
Ok(())
} else {
// these shouldn't even be encountered but they're here for completeness
BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()),
Ok(())
}
}

View File

@@ -284,29 +284,22 @@ pub fn process_attestations<E: EthSpec, Payload: AbstractExecPayload<E>>(
ctxt: &mut ConsensusContext<E>,
spec: &ChainSpec,
) -> Result<(), BlockProcessingError> {
match block_body {
BeaconBlockBodyRef::Base(_) => {
base::process_attestations(
state,
block_body.attestations(),
verify_signatures,
ctxt,
spec,
)?;
}
BeaconBlockBodyRef::Altair(_)
| BeaconBlockBodyRef::Bellatrix(_)
| BeaconBlockBodyRef::Capella(_)
| BeaconBlockBodyRef::Deneb(_)
| BeaconBlockBodyRef::Electra(_) => {
altair_deneb::process_attestations(
state,
block_body.attestations(),
verify_signatures,
ctxt,
spec,
)?;
}
if state.fork_name_unchecked().altair_enabled() {
altair_deneb::process_attestations(
state,
block_body.attestations(),
verify_signatures,
ctxt,
spec,
)?;
} else {
base::process_attestations(
state,
block_body.attestations(),
verify_signatures,
ctxt,
spec,
)?;
}
Ok(())
}

View File

@@ -387,22 +387,20 @@ where
let exit = &signed_exit.message;
let proposer_index = exit.validator_index as usize;
let domain = match state {
BeaconState::Base(_)
| BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_) => spec.get_domain(
let domain = if state.fork_name_unchecked().deneb_enabled() {
// EIP-7044
spec.compute_domain(
Domain::VoluntaryExit,
spec.capella_fork_version,
state.genesis_validators_root(),
)
} else {
spec.get_domain(
exit.epoch,
Domain::VoluntaryExit,
&state.fork(),
state.genesis_validators_root(),
),
// EIP-7044
BeaconState::Deneb(_) | BeaconState::Electra(_) => spec.compute_domain(
Domain::VoluntaryExit,
spec.capella_fork_version,
state.genesis_validators_root(),
),
)
};
let message = exit.signing_root(domain);

View File

@@ -32,21 +32,16 @@ pub fn verify_attestation_for_block_inclusion<'ctxt, E: EthSpec>(
attestation: data.slot,
}
);
match state {
BeaconState::Base(_)
| BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_) => {
verify!(
state.slot() <= data.slot.safe_add(E::slots_per_epoch())?,
Invalid::IncludedTooLate {
state: state.slot(),
attestation: data.slot,
}
);
}
if state.fork_name_unchecked().deneb_enabled() {
// [Modified in Deneb:EIP7045]
BeaconState::Deneb(_) | BeaconState::Electra(_) => {}
} else {
verify!(
state.slot() <= data.slot.safe_add(E::slots_per_epoch())?,
Invalid::IncludedTooLate {
state: state.slot(),
attestation: data.slot,
}
);
}
verify_attestation_for_state(state, attestation, ctxt, verify_signatures, spec)

View File

@@ -41,13 +41,10 @@ pub fn process_epoch<E: EthSpec>(
.fork_name(spec)
.map_err(Error::InconsistentStateFork)?;
match state {
BeaconState::Base(_) => base::process_epoch(state, spec),
BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_) => altair::process_epoch(state, spec),
if state.fork_name_unchecked().altair_enabled() {
altair::process_epoch(state, spec)
} else {
base::process_epoch(state, spec)
}
}

View File

@@ -1,6 +1,6 @@
use crate::upgrade::{
upgrade_to_altair, upgrade_to_bellatrix, upgrade_to_capella, upgrade_to_deneb,
upgrade_to_electra,
upgrade_to_electra, upgrade_to_fulu,
};
use crate::{per_epoch_processing::EpochProcessingSummary, *};
use safe_arith::{ArithError, SafeArith};
@@ -71,6 +71,11 @@ pub fn per_slot_processing<E: EthSpec>(
upgrade_to_electra(state, spec)?;
}
// Fulu.
if spec.fulu_fork_epoch == Some(state.current_epoch()) {
upgrade_to_fulu(state, spec)?;
}
// Additionally build all caches so that all valid states that are advanced always have
// committee caches built, and we don't have to worry about initialising them at higher
// layers.

View File

@@ -3,9 +3,11 @@ pub mod bellatrix;
pub mod capella;
pub mod deneb;
pub mod electra;
pub mod fulu;
pub use altair::upgrade_to_altair;
pub use bellatrix::upgrade_to_bellatrix;
pub use capella::upgrade_to_capella;
pub use deneb::upgrade_to_deneb;
pub use electra::upgrade_to_electra;
pub use fulu::upgrade_to_fulu;

View File

@@ -0,0 +1,94 @@
use std::mem;
use types::{BeaconState, BeaconStateError as Error, BeaconStateFulu, ChainSpec, EthSpec, Fork};
/// Transform a `Electra` state into an `Fulu` state.
pub fn upgrade_to_fulu<E: EthSpec>(
pre_state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<(), Error> {
let _epoch = pre_state.current_epoch();
let post = upgrade_state_to_fulu(pre_state, spec)?;
*pre_state = post;
Ok(())
}
pub fn upgrade_state_to_fulu<E: EthSpec>(
pre_state: &mut BeaconState<E>,
spec: &ChainSpec,
) -> Result<BeaconState<E>, Error> {
let epoch = pre_state.current_epoch();
let pre = pre_state.as_electra_mut()?;
// Where possible, use something like `mem::take` to move fields from behind the &mut
// reference. For other fields that don't have a good default value, use `clone`.
//
// Fixed size vectors get cloned because replacing them would require the same size
// allocation as cloning.
let post = BeaconState::Fulu(BeaconStateFulu {
// Versioning
genesis_time: pre.genesis_time,
genesis_validators_root: pre.genesis_validators_root,
slot: pre.slot,
fork: Fork {
previous_version: pre.fork.current_version,
current_version: spec.fulu_fork_version,
epoch,
},
// History
latest_block_header: pre.latest_block_header.clone(),
block_roots: pre.block_roots.clone(),
state_roots: pre.state_roots.clone(),
historical_roots: mem::take(&mut pre.historical_roots),
// Eth1
eth1_data: pre.eth1_data.clone(),
eth1_data_votes: mem::take(&mut pre.eth1_data_votes),
eth1_deposit_index: pre.eth1_deposit_index,
// Registry
validators: mem::take(&mut pre.validators),
balances: mem::take(&mut pre.balances),
// Randomness
randao_mixes: pre.randao_mixes.clone(),
// Slashings
slashings: pre.slashings.clone(),
// `Participation
previous_epoch_participation: mem::take(&mut pre.previous_epoch_participation),
current_epoch_participation: mem::take(&mut pre.current_epoch_participation),
// Finality
justification_bits: pre.justification_bits.clone(),
previous_justified_checkpoint: pre.previous_justified_checkpoint,
current_justified_checkpoint: pre.current_justified_checkpoint,
finalized_checkpoint: pre.finalized_checkpoint,
// Inactivity
inactivity_scores: mem::take(&mut pre.inactivity_scores),
// Sync committees
current_sync_committee: pre.current_sync_committee.clone(),
next_sync_committee: pre.next_sync_committee.clone(),
// Execution
latest_execution_payload_header: pre.latest_execution_payload_header.upgrade_to_fulu(),
// Capella
next_withdrawal_index: pre.next_withdrawal_index,
next_withdrawal_validator_index: pre.next_withdrawal_validator_index,
historical_summaries: pre.historical_summaries.clone(),
// Electra
deposit_requests_start_index: pre.deposit_requests_start_index,
deposit_balance_to_consume: pre.deposit_balance_to_consume,
exit_balance_to_consume: pre.exit_balance_to_consume,
earliest_exit_epoch: pre.earliest_exit_epoch,
consolidation_balance_to_consume: pre.consolidation_balance_to_consume,
earliest_consolidation_epoch: pre.earliest_consolidation_epoch,
pending_deposits: pre.pending_deposits.clone(),
pending_partial_withdrawals: pre.pending_partial_withdrawals.clone(),
pending_consolidations: pre.pending_consolidations.clone(),
// Caches
total_active_balance: pre.total_active_balance,
progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache),
committee_caches: mem::take(&mut pre.committee_caches),
pubkey_cache: mem::take(&mut pre.pubkey_cache),
exit_cache: mem::take(&mut pre.exit_cache),
slashings_cache: mem::take(&mut pre.slashings_cache),
epoch_cache: mem::take(&mut pre.epoch_cache),
});
Ok(post)
}

View File

@@ -0,0 +1,3 @@
# Gnosis preset - Fulu
FULU_PLACEHOLDER: 0

View File

@@ -0,0 +1,3 @@
# Mainnet preset - Fulu
FULU_PLACEHOLDER: 0

View File

@@ -0,0 +1,3 @@
# Minimal preset - Fulu
FULU_PLACEHOLDER: 0

View File

@@ -16,7 +16,7 @@ use self::indexed_attestation::{IndexedAttestationBase, IndexedAttestationElectr
/// A block of the `BeaconChain`.
#[superstruct(
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra),
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -75,6 +75,8 @@ pub struct BeaconBlock<E: EthSpec, Payload: AbstractExecPayload<E> = FullPayload
pub body: BeaconBlockBodyDeneb<E, Payload>,
#[superstruct(only(Electra), partial_getter(rename = "body_electra"))]
pub body: BeaconBlockBodyElectra<E, Payload>,
#[superstruct(only(Fulu), partial_getter(rename = "body_fulu"))]
pub body: BeaconBlockBodyFulu<E, Payload>,
}
pub type BlindedBeaconBlock<E> = BeaconBlock<E, BlindedPayload<E>>;
@@ -127,8 +129,9 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlock<E, 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> {
BeaconBlockElectra::from_ssz_bytes(bytes)
.map(BeaconBlock::Electra)
BeaconBlockFulu::from_ssz_bytes(bytes)
.map(BeaconBlock::Fulu)
.or_else(|_| BeaconBlockElectra::from_ssz_bytes(bytes).map(BeaconBlock::Electra))
.or_else(|_| BeaconBlockDeneb::from_ssz_bytes(bytes).map(BeaconBlock::Deneb))
.or_else(|_| BeaconBlockCapella::from_ssz_bytes(bytes).map(BeaconBlock::Capella))
.or_else(|_| BeaconBlockBellatrix::from_ssz_bytes(bytes).map(BeaconBlock::Bellatrix))
@@ -226,6 +229,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockRef<'a, E, Payl
BeaconBlockRef::Capella { .. } => ForkName::Capella,
BeaconBlockRef::Deneb { .. } => ForkName::Deneb,
BeaconBlockRef::Electra { .. } => ForkName::Electra,
BeaconBlockRef::Fulu { .. } => ForkName::Fulu,
}
}
@@ -704,6 +708,110 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockElec
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockFulu<E, Payload> {
/// Return a Fulu block where the block has maximum size.
pub fn full(spec: &ChainSpec) -> Self {
let base_block: BeaconBlockBase<_, Payload> = BeaconBlockBase::full(spec);
let indexed_attestation: IndexedAttestationElectra<E> = IndexedAttestationElectra {
attesting_indices: VariableList::new(vec![0_u64; E::MaxValidatorsPerSlot::to_usize()])
.unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
};
let attester_slashings = vec![
AttesterSlashingElectra {
attestation_1: indexed_attestation.clone(),
attestation_2: indexed_attestation,
};
E::max_attester_slashings_electra()
]
.into();
let attestation = AttestationElectra {
aggregation_bits: BitList::with_capacity(E::MaxValidatorsPerSlot::to_usize()).unwrap(),
data: AttestationData::default(),
signature: AggregateSignature::empty(),
committee_bits: BitVector::new(),
};
let mut attestations_electra = vec![];
for _ in 0..E::MaxAttestationsElectra::to_usize() {
attestations_electra.push(attestation.clone());
}
let bls_to_execution_changes = vec![
SignedBlsToExecutionChange {
message: BlsToExecutionChange {
validator_index: 0,
from_bls_pubkey: PublicKeyBytes::empty(),
to_execution_address: Address::ZERO,
},
signature: Signature::empty()
};
E::max_bls_to_execution_changes()
]
.into();
let sync_aggregate = SyncAggregate {
sync_committee_signature: AggregateSignature::empty(),
sync_committee_bits: BitVector::default(),
};
BeaconBlockFulu {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyFulu {
proposer_slashings: base_block.body.proposer_slashings,
attester_slashings,
attestations: attestations_electra.into(),
deposits: base_block.body.deposits,
voluntary_exits: base_block.body.voluntary_exits,
bls_to_execution_changes,
sync_aggregate,
randao_reveal: Signature::empty(),
eth1_data: Eth1Data {
deposit_root: Hash256::zero(),
block_hash: Hash256::zero(),
deposit_count: 0,
},
graffiti: Graffiti::default(),
execution_payload: Payload::Fulu::default(),
blob_kzg_commitments: VariableList::empty(),
execution_requests: ExecutionRequests::default(),
},
}
}
}
impl<E: EthSpec, Payload: AbstractExecPayload<E>> EmptyBlock for BeaconBlockFulu<E, Payload> {
/// Returns an empty Fulu block to be used during genesis.
fn empty(spec: &ChainSpec) -> Self {
BeaconBlockFulu {
slot: spec.genesis_slot,
proposer_index: 0,
parent_root: Hash256::zero(),
state_root: Hash256::zero(),
body: BeaconBlockBodyFulu {
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::Fulu::default(),
bls_to_execution_changes: VariableList::empty(),
blob_kzg_commitments: VariableList::empty(),
execution_requests: ExecutionRequests::default(),
},
}
}
}
// 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>>
@@ -785,6 +893,7 @@ impl_from!(BeaconBlockBellatrix, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |b
impl_from!(BeaconBlockCapella, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyCapella<_, _>| body.into());
impl_from!(BeaconBlockDeneb, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyDeneb<_, _>| body.into());
impl_from!(BeaconBlockElectra, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyElectra<_, _>| body.into());
impl_from!(BeaconBlockFulu, <E, FullPayload<E>>, <E, BlindedPayload<E>>, |body: BeaconBlockBodyFulu<_, _>| body.into());
// We can clone blocks with payloads to blocks without payloads, without cloning the payload.
macro_rules! impl_clone_as_blinded {
@@ -818,6 +927,7 @@ impl_clone_as_blinded!(BeaconBlockBellatrix, <E, FullPayload<E>>, <E, BlindedPay
impl_clone_as_blinded!(BeaconBlockCapella, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
impl_clone_as_blinded!(BeaconBlockDeneb, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
impl_clone_as_blinded!(BeaconBlockElectra, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
impl_clone_as_blinded!(BeaconBlockFulu, <E, FullPayload<E>>, <E, BlindedPayload<E>>);
// A reference to a full beacon block can be cloned into a blinded beacon block, without cloning the
// execution payload.
@@ -988,6 +1098,26 @@ mod tests {
});
}
#[test]
fn roundtrip_fulu_block() {
let rng = &mut XorShiftRng::from_seed([42; 16]);
let spec = &ForkName::Fulu.make_genesis_spec(MainnetEthSpec::default_spec());
let inner_block = BeaconBlockFulu {
slot: Slot::random_for_test(rng),
proposer_index: u64::random_for_test(rng),
parent_root: Hash256::random_for_test(rng),
state_root: Hash256::random_for_test(rng),
body: BeaconBlockBodyFulu::random_for_test(rng),
};
let block = BeaconBlock::Fulu(inner_block.clone());
test_ssz_tree_hash_pair_with(&block, &inner_block, |bytes| {
BeaconBlock::from_ssz_bytes(bytes, spec)
});
}
#[test]
fn decode_base_and_altair() {
type E = MainnetEthSpec;
@@ -1007,11 +1137,14 @@ mod tests {
let deneb_slot = deneb_epoch.start_slot(E::slots_per_epoch());
let electra_epoch = deneb_epoch + 1;
let electra_slot = electra_epoch.start_slot(E::slots_per_epoch());
let fulu_epoch = electra_epoch + 1;
let fulu_slot = fulu_epoch.start_slot(E::slots_per_epoch());
spec.altair_fork_epoch = Some(altair_epoch);
spec.capella_fork_epoch = Some(capella_epoch);
spec.deneb_fork_epoch = Some(deneb_epoch);
spec.electra_fork_epoch = Some(electra_epoch);
spec.fulu_fork_epoch = Some(fulu_epoch);
// BeaconBlockBase
{
@@ -1122,5 +1255,29 @@ mod tests {
BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec)
.expect_err("bad electra block cannot be decoded");
}
// BeaconBlockFulu
{
let good_block = BeaconBlock::Fulu(BeaconBlockFulu {
slot: fulu_slot,
..<_>::random_for_test(rng)
});
// It's invalid to have a Fulu block with a epoch lower than the fork epoch.
let _bad_block = {
let mut bad = good_block.clone();
*bad.slot_mut() = electra_slot;
bad
};
assert_eq!(
BeaconBlock::from_ssz_bytes(&good_block.as_ssz_bytes(), &spec)
.expect("good fulu block can be decoded"),
good_block
);
// TODO(fulu): Uncomment once Fulu has features since without features
// and with an Electra slot it decodes successfully to Electra.
//BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec)
// .expect_err("bad fulu block cannot be decoded");
}
}
}

View File

@@ -30,7 +30,7 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11;
///
/// This *superstruct* abstracts over the hard-fork.
#[superstruct(
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra),
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -58,6 +58,7 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11;
Capella(metastruct(mappings(beacon_block_body_capella_fields(groups(fields))))),
Deneb(metastruct(mappings(beacon_block_body_deneb_fields(groups(fields))))),
Electra(metastruct(mappings(beacon_block_body_electra_fields(groups(fields))))),
Fulu(metastruct(mappings(beacon_block_body_fulu_fields(groups(fields)))))
),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant")
@@ -77,7 +78,10 @@ pub struct BeaconBlockBody<E: EthSpec, Payload: AbstractExecPayload<E> = FullPay
partial_getter(rename = "attester_slashings_base")
)]
pub attester_slashings: VariableList<AttesterSlashingBase<E>, E::MaxAttesterSlashings>,
#[superstruct(only(Electra), partial_getter(rename = "attester_slashings_electra"))]
#[superstruct(
only(Electra, Fulu),
partial_getter(rename = "attester_slashings_electra")
)]
pub attester_slashings:
VariableList<AttesterSlashingElectra<E>, E::MaxAttesterSlashingsElectra>,
#[superstruct(
@@ -85,11 +89,11 @@ pub struct BeaconBlockBody<E: EthSpec, Payload: AbstractExecPayload<E> = FullPay
partial_getter(rename = "attestations_base")
)]
pub attestations: VariableList<AttestationBase<E>, E::MaxAttestations>,
#[superstruct(only(Electra), partial_getter(rename = "attestations_electra"))]
#[superstruct(only(Electra, Fulu), partial_getter(rename = "attestations_electra"))]
pub attestations: VariableList<AttestationElectra<E>, E::MaxAttestationsElectra>,
pub deposits: VariableList<Deposit, E::MaxDeposits>,
pub voluntary_exits: VariableList<SignedVoluntaryExit, E::MaxVoluntaryExits>,
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))]
pub sync_aggregate: SyncAggregate<E>,
// We flatten the execution payload so that serde can use the name of the inner type,
// either `execution_payload` for full payloads, or `execution_payload_header` for blinded
@@ -109,12 +113,15 @@ pub struct BeaconBlockBody<E: EthSpec, Payload: AbstractExecPayload<E> = FullPay
#[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))]
#[serde(flatten)]
pub execution_payload: Payload::Electra,
#[superstruct(only(Capella, Deneb, Electra))]
#[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))]
#[serde(flatten)]
pub execution_payload: Payload::Fulu,
#[superstruct(only(Capella, Deneb, Electra, Fulu))]
pub bls_to_execution_changes:
VariableList<SignedBlsToExecutionChange, E::MaxBlsToExecutionChanges>,
#[superstruct(only(Deneb, Electra))]
#[superstruct(only(Deneb, Electra, Fulu))]
pub blob_kzg_commitments: KzgCommitments<E>,
#[superstruct(only(Electra))]
#[superstruct(only(Electra, Fulu))]
pub execution_requests: ExecutionRequests<E>,
#[superstruct(only(Base, Altair))]
#[metastruct(exclude_from(fields))]
@@ -144,6 +151,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E,
Self::Capella(body) => Ok(Payload::Ref::from(&body.execution_payload)),
Self::Deneb(body) => Ok(Payload::Ref::from(&body.execution_payload)),
Self::Electra(body) => Ok(Payload::Ref::from(&body.execution_payload)),
Self::Fulu(body) => Ok(Payload::Ref::from(&body.execution_payload)),
}
}
@@ -174,6 +182,10 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E,
beacon_block_body_electra_fields!(body, |_, field| leaves
.push(field.tree_hash_root()));
}
Self::Fulu(body) => {
beacon_block_body_fulu_fields!(body, |_, field| leaves
.push(field.tree_hash_root()));
}
}
leaves
}
@@ -202,7 +214,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E,
Self::Base(_) | Self::Altair(_) | Self::Bellatrix(_) | Self::Capella(_) => {
Err(Error::IncorrectStateVariant)
}
Self::Deneb(_) | Self::Electra(_) => {
Self::Deneb(_) | Self::Electra(_) | Self::Fulu(_) => {
// We compute the branches by generating 2 merkle trees:
// 1. Merkle tree for the `blob_kzg_commitments` List object
// 2. Merkle tree for the `BeaconBlockBody` container
@@ -294,6 +306,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E,
Self::Capella(body) => body.attestations.len(),
Self::Deneb(body) => body.attestations.len(),
Self::Electra(body) => body.attestations.len(),
Self::Fulu(body) => body.attestations.len(),
}
}
@@ -305,6 +318,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E,
Self::Capella(body) => body.attester_slashings.len(),
Self::Deneb(body) => body.attester_slashings.len(),
Self::Electra(body) => body.attester_slashings.len(),
Self::Fulu(body) => body.attester_slashings.len(),
}
}
@@ -316,6 +330,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E,
Self::Capella(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)),
Self::Deneb(body) => Box::new(body.attestations.iter().map(AttestationRef::Base)),
Self::Electra(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)),
Self::Fulu(body) => Box::new(body.attestations.iter().map(AttestationRef::Electra)),
}
}
@@ -351,6 +366,11 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'a, E,
.iter()
.map(AttesterSlashingRef::Electra),
),
Self::Fulu(body) => Box::new(
body.attester_slashings
.iter()
.map(AttesterSlashingRef::Electra),
),
}
}
}
@@ -376,6 +396,9 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRefMut<'a,
Self::Electra(body) => {
Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra))
}
Self::Fulu(body) => {
Box::new(body.attestations.iter_mut().map(AttestationRefMut::Electra))
}
}
}
}
@@ -390,6 +413,7 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> BeaconBlockBodyRef<'_, E, Payl
BeaconBlockBodyRef::Capella { .. } => ForkName::Capella,
BeaconBlockBodyRef::Deneb { .. } => ForkName::Deneb,
BeaconBlockBodyRef::Electra { .. } => ForkName::Electra,
BeaconBlockBodyRef::Fulu { .. } => ForkName::Fulu,
}
}
}
@@ -704,6 +728,52 @@ impl<E: EthSpec> From<BeaconBlockBodyElectra<E, FullPayload<E>>>
}
}
impl<E: EthSpec> From<BeaconBlockBodyFulu<E, FullPayload<E>>>
for (
BeaconBlockBodyFulu<E, BlindedPayload<E>>,
Option<ExecutionPayloadFulu<E>>,
)
{
fn from(body: BeaconBlockBodyFulu<E, FullPayload<E>>) -> Self {
let BeaconBlockBodyFulu {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: FullPayloadFulu { execution_payload },
bls_to_execution_changes,
blob_kzg_commitments,
execution_requests,
} = body;
(
BeaconBlockBodyFulu {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: BlindedPayloadFulu {
execution_payload_header: From::from(&execution_payload),
},
bls_to_execution_changes,
blob_kzg_commitments: blob_kzg_commitments.clone(),
execution_requests,
},
Some(execution_payload),
)
}
}
// We can clone a full block into a blinded block, without cloning the payload.
impl<E: EthSpec> BeaconBlockBodyBase<E, FullPayload<E>> {
pub fn clone_as_blinded(&self) -> BeaconBlockBodyBase<E, BlindedPayload<E>> {
@@ -859,6 +929,44 @@ impl<E: EthSpec> BeaconBlockBodyElectra<E, FullPayload<E>> {
}
}
impl<E: EthSpec> BeaconBlockBodyFulu<E, FullPayload<E>> {
pub fn clone_as_blinded(&self) -> BeaconBlockBodyFulu<E, BlindedPayload<E>> {
let BeaconBlockBodyFulu {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: FullPayloadFulu { execution_payload },
bls_to_execution_changes,
blob_kzg_commitments,
execution_requests,
} = self;
BeaconBlockBodyFulu {
randao_reveal: randao_reveal.clone(),
eth1_data: eth1_data.clone(),
graffiti: *graffiti,
proposer_slashings: proposer_slashings.clone(),
attester_slashings: attester_slashings.clone(),
attestations: attestations.clone(),
deposits: deposits.clone(),
voluntary_exits: voluntary_exits.clone(),
sync_aggregate: sync_aggregate.clone(),
execution_payload: BlindedPayloadFulu {
execution_payload_header: execution_payload.into(),
},
bls_to_execution_changes: bls_to_execution_changes.clone(),
blob_kzg_commitments: blob_kzg_commitments.clone(),
execution_requests: execution_requests.clone(),
}
}
}
impl<E: EthSpec> From<BeaconBlockBody<E, FullPayload<E>>>
for (
BeaconBlockBody<E, BlindedPayload<E>>,

View File

@@ -223,7 +223,7 @@ impl From<BeaconStateHash> for Hash256 {
///
/// https://github.com/sigp/milhouse/issues/43
#[superstruct(
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra),
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Derivative,
@@ -326,6 +326,20 @@ impl From<BeaconStateHash> for Hash256 {
groups(tree_lists)
)),
num_fields(all()),
)),
Fulu(metastruct(
mappings(
map_beacon_state_fulu_fields(),
map_beacon_state_fulu_tree_list_fields(mutable, fallible, groups(tree_lists)),
map_beacon_state_fulu_tree_list_fields_immutable(groups(tree_lists)),
),
bimappings(bimap_beacon_state_fulu_tree_list_fields(
other_type = "BeaconStateFulu",
self_mutable,
fallible,
groups(tree_lists)
)),
num_fields(all()),
))
),
cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"),
@@ -408,11 +422,11 @@ where
// Participation (Altair and later)
#[compare_fields(as_iter)]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))]
#[test_random(default)]
#[compare_fields(as_iter)]
pub previous_epoch_participation: List<ParticipationFlags, E::ValidatorRegistryLimit>,
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))]
#[test_random(default)]
pub current_epoch_participation: List<ParticipationFlags, E::ValidatorRegistryLimit>,
@@ -432,15 +446,15 @@ where
// Inactivity
#[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))]
#[test_random(default)]
pub inactivity_scores: List<u64, E::ValidatorRegistryLimit>,
// Light-client sync committees
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))]
#[metastruct(exclude_from(tree_lists))]
pub current_sync_committee: Arc<SyncCommittee<E>>,
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra))]
#[superstruct(only(Altair, Bellatrix, Capella, Deneb, Electra, Fulu))]
#[metastruct(exclude_from(tree_lists))]
pub next_sync_committee: Arc<SyncCommittee<E>>,
@@ -469,56 +483,62 @@ where
)]
#[metastruct(exclude_from(tree_lists))]
pub latest_execution_payload_header: ExecutionPayloadHeaderElectra<E>,
#[superstruct(
only(Fulu),
partial_getter(rename = "latest_execution_payload_header_fulu")
)]
#[metastruct(exclude_from(tree_lists))]
pub latest_execution_payload_header: ExecutionPayloadHeaderFulu<E>,
// Capella
#[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))]
#[serde(with = "serde_utils::quoted_u64")]
#[metastruct(exclude_from(tree_lists))]
pub next_withdrawal_index: u64,
#[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))]
#[serde(with = "serde_utils::quoted_u64")]
#[metastruct(exclude_from(tree_lists))]
pub next_withdrawal_validator_index: u64,
// Deep history valid from Capella onwards.
#[superstruct(only(Capella, Deneb, Electra))]
#[superstruct(only(Capella, Deneb, Electra, Fulu))]
#[test_random(default)]
pub historical_summaries: List<HistoricalSummary, E::HistoricalRootsLimit>,
// Electra
#[superstruct(only(Electra), partial_getter(copy))]
#[superstruct(only(Electra, Fulu), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
pub deposit_requests_start_index: u64,
#[superstruct(only(Electra), partial_getter(copy))]
#[superstruct(only(Electra, Fulu), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
pub deposit_balance_to_consume: u64,
#[superstruct(only(Electra), partial_getter(copy))]
#[superstruct(only(Electra, Fulu), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
pub exit_balance_to_consume: u64,
#[superstruct(only(Electra), partial_getter(copy))]
#[superstruct(only(Electra, Fulu), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
pub earliest_exit_epoch: Epoch,
#[superstruct(only(Electra), partial_getter(copy))]
#[superstruct(only(Electra, Fulu), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
pub consolidation_balance_to_consume: u64,
#[superstruct(only(Electra), partial_getter(copy))]
#[superstruct(only(Electra, Fulu), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
pub earliest_consolidation_epoch: Epoch,
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Electra))]
#[superstruct(only(Electra, Fulu))]
pub pending_deposits: List<PendingDeposit, E::PendingDepositsLimit>,
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Electra))]
#[superstruct(only(Electra, Fulu))]
pub pending_partial_withdrawals:
List<PendingPartialWithdrawal, E::PendingPartialWithdrawalsLimit>,
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Electra))]
#[superstruct(only(Electra, Fulu))]
pub pending_consolidations: List<PendingConsolidation, E::PendingConsolidationsLimit>,
// Caching (not in the spec)
@@ -659,6 +679,7 @@ impl<E: EthSpec> BeaconState<E> {
BeaconState::Capella { .. } => ForkName::Capella,
BeaconState::Deneb { .. } => ForkName::Deneb,
BeaconState::Electra { .. } => ForkName::Electra,
BeaconState::Fulu { .. } => ForkName::Fulu,
}
}
@@ -948,6 +969,9 @@ impl<E: EthSpec> BeaconState<E> {
BeaconState::Electra(state) => Ok(ExecutionPayloadHeaderRef::Electra(
&state.latest_execution_payload_header,
)),
BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRef::Fulu(
&state.latest_execution_payload_header,
)),
}
}
@@ -968,6 +992,9 @@ impl<E: EthSpec> BeaconState<E> {
BeaconState::Electra(state) => Ok(ExecutionPayloadHeaderRefMut::Electra(
&mut state.latest_execution_payload_header,
)),
BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRefMut::Fulu(
&mut state.latest_execution_payload_header,
)),
}
}
@@ -1481,6 +1508,16 @@ impl<E: EthSpec> BeaconState<E> {
&mut state.exit_cache,
&mut state.epoch_cache,
)),
BeaconState::Fulu(state) => Ok((
&mut state.validators,
&mut state.balances,
&state.previous_epoch_participation,
&state.current_epoch_participation,
&mut state.inactivity_scores,
&mut state.progressive_balances_cache,
&mut state.exit_cache,
&mut state.epoch_cache,
)),
}
}
@@ -1662,10 +1699,12 @@ impl<E: EthSpec> BeaconState<E> {
| BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_) => self.get_validator_churn_limit(spec)?,
BeaconState::Deneb(_) | BeaconState::Electra(_) => std::cmp::min(
spec.max_per_epoch_activation_churn_limit,
self.get_validator_churn_limit(spec)?,
),
BeaconState::Deneb(_) | BeaconState::Electra(_) | BeaconState::Fulu(_) => {
std::cmp::min(
spec.max_per_epoch_activation_churn_limit,
self.get_validator_churn_limit(spec)?,
)
}
})
}
@@ -1783,6 +1822,7 @@ impl<E: EthSpec> BeaconState<E> {
BeaconState::Capella(state) => Ok(&mut state.current_epoch_participation),
BeaconState::Deneb(state) => Ok(&mut state.current_epoch_participation),
BeaconState::Electra(state) => Ok(&mut state.current_epoch_participation),
BeaconState::Fulu(state) => Ok(&mut state.current_epoch_participation),
}
} else if epoch == previous_epoch {
match self {
@@ -1792,6 +1832,7 @@ impl<E: EthSpec> BeaconState<E> {
BeaconState::Capella(state) => Ok(&mut state.previous_epoch_participation),
BeaconState::Deneb(state) => Ok(&mut state.previous_epoch_participation),
BeaconState::Electra(state) => Ok(&mut state.previous_epoch_participation),
BeaconState::Fulu(state) => Ok(&mut state.previous_epoch_participation),
}
} else {
Err(BeaconStateError::EpochOutOfBounds)
@@ -2045,6 +2086,11 @@ impl<E: EthSpec> BeaconState<E> {
}
);
}
Self::Fulu(self_inner) => {
map_beacon_state_fulu_tree_list_fields_immutable!(self_inner, |_, self_field| {
any_pending_mutations |= self_field.has_pending_updates();
});
}
};
any_pending_mutations
}
@@ -2238,12 +2284,29 @@ impl<E: EthSpec> BeaconState<E> {
exit_balance_to_consume
.safe_add_assign(additional_epochs.safe_mul(per_epoch_churn)?)?;
}
let state = self.as_electra_mut()?;
// Consume the balance and update state variables
state.exit_balance_to_consume = exit_balance_to_consume.safe_sub(exit_balance)?;
state.earliest_exit_epoch = earliest_exit_epoch;
match self {
BeaconState::Base(_)
| BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_) => Err(Error::IncorrectStateVariant),
BeaconState::Electra(_) => {
let state = self.as_electra_mut()?;
Ok(state.earliest_exit_epoch)
// Consume the balance and update state variables
state.exit_balance_to_consume = exit_balance_to_consume.safe_sub(exit_balance)?;
state.earliest_exit_epoch = earliest_exit_epoch;
Ok(state.earliest_exit_epoch)
}
BeaconState::Fulu(_) => {
let state = self.as_fulu_mut()?;
// Consume the balance and update state variables
state.exit_balance_to_consume = exit_balance_to_consume.safe_sub(exit_balance)?;
state.earliest_exit_epoch = earliest_exit_epoch;
Ok(state.earliest_exit_epoch)
}
}
}
pub fn compute_consolidation_epoch_and_update_churn(
@@ -2277,13 +2340,31 @@ impl<E: EthSpec> BeaconState<E> {
consolidation_balance_to_consume
.safe_add_assign(additional_epochs.safe_mul(per_epoch_consolidation_churn)?)?;
}
// Consume the balance and update state variables
let state = self.as_electra_mut()?;
state.consolidation_balance_to_consume =
consolidation_balance_to_consume.safe_sub(consolidation_balance)?;
state.earliest_consolidation_epoch = earliest_consolidation_epoch;
match self {
BeaconState::Base(_)
| BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_) => Err(Error::IncorrectStateVariant),
BeaconState::Electra(_) => {
let state = self.as_electra_mut()?;
Ok(state.earliest_consolidation_epoch)
// Consume the balance and update state variables.
state.consolidation_balance_to_consume =
consolidation_balance_to_consume.safe_sub(consolidation_balance)?;
state.earliest_consolidation_epoch = earliest_consolidation_epoch;
Ok(state.earliest_consolidation_epoch)
}
BeaconState::Fulu(_) => {
let state = self.as_fulu_mut()?;
// Consume the balance and update state variables.
state.consolidation_balance_to_consume =
consolidation_balance_to_consume.safe_sub(consolidation_balance)?;
state.earliest_consolidation_epoch = earliest_consolidation_epoch;
Ok(state.earliest_consolidation_epoch)
}
}
}
#[allow(clippy::arithmetic_side_effects)]
@@ -2339,6 +2420,14 @@ impl<E: EthSpec> BeaconState<E> {
);
}
(Self::Electra(_), _) => (),
(Self::Fulu(self_inner), Self::Fulu(base_inner)) => {
bimap_beacon_state_fulu_tree_list_fields!(
self_inner,
base_inner,
|_, self_field, base_field| { self_field.rebase_on(base_field) }
);
}
(Self::Fulu(_), _) => (),
}
// Use sync committees from `base` if they are equal.
@@ -2411,6 +2500,7 @@ impl<E: EthSpec> BeaconState<E> {
ForkName::Capella => BeaconStateCapella::<E>::NUM_FIELDS.next_power_of_two(),
ForkName::Deneb => BeaconStateDeneb::<E>::NUM_FIELDS.next_power_of_two(),
ForkName::Electra => BeaconStateElectra::<E>::NUM_FIELDS.next_power_of_two(),
ForkName::Fulu => BeaconStateFulu::<E>::NUM_FIELDS.next_power_of_two(),
}
}
@@ -2459,6 +2549,9 @@ impl<E: EthSpec> BeaconState<E> {
Self::Electra(inner) => {
map_beacon_state_electra_tree_list_fields!(inner, |_, x| { x.apply_updates() })
}
Self::Fulu(inner) => {
map_beacon_state_fulu_tree_list_fields!(inner, |_, x| { x.apply_updates() })
}
}
Ok(())
}
@@ -2554,6 +2647,11 @@ impl<E: EthSpec> BeaconState<E> {
leaves.push(field.tree_hash_root());
});
}
BeaconState::Fulu(state) => {
map_beacon_state_fulu_fields!(state, |_, field| {
leaves.push(field.tree_hash_root());
});
}
};
leaves
@@ -2611,6 +2709,7 @@ impl<E: EthSpec> CompareFields for BeaconState<E> {
(BeaconState::Capella(x), BeaconState::Capella(y)) => x.compare_fields(y),
(BeaconState::Deneb(x), BeaconState::Deneb(y)) => x.compare_fields(y),
(BeaconState::Electra(x), BeaconState::Electra(y)) => x.compare_fields(y),
(BeaconState::Fulu(x), BeaconState::Fulu(y)) => x.compare_fields(y),
_ => panic!("compare_fields: mismatched state variants",),
}
}

View File

@@ -285,12 +285,5 @@ impl ProgressiveBalancesCache {
/// `ProgressiveBalancesCache` is only enabled from `Altair` as it uses Altair-specific logic.
pub fn is_progressive_balances_enabled<E: EthSpec>(state: &BeaconState<E>) -> bool {
match state {
BeaconState::Base(_) => false,
BeaconState::Altair(_)
| BeaconState::Bellatrix(_)
| BeaconState::Capella(_)
| BeaconState::Deneb(_)
| BeaconState::Electra(_) => true,
}
state.fork_name_unchecked().altair_enabled()
}

View File

@@ -1,8 +1,9 @@
use crate::beacon_block_body::KzgCommitments;
use crate::{
ChainSpec, EthSpec, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella,
ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderRef,
ExecutionPayloadHeaderRefMut, ForkName, ForkVersionDeserialize, SignedRoot, Uint256,
ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu,
ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ForkName, ForkVersionDeserialize,
SignedRoot, Uint256,
};
use bls::PublicKeyBytes;
use bls::Signature;
@@ -11,7 +12,7 @@ use superstruct::superstruct;
use tree_hash_derive::TreeHash;
#[superstruct(
variants(Bellatrix, Capella, Deneb, Electra),
variants(Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(PartialEq, Debug, Serialize, Deserialize, TreeHash, Clone),
serde(bound = "E: EthSpec", deny_unknown_fields)
@@ -31,7 +32,9 @@ pub struct BuilderBid<E: EthSpec> {
pub header: ExecutionPayloadHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "header_electra"))]
pub header: ExecutionPayloadHeaderElectra<E>,
#[superstruct(only(Deneb, Electra))]
#[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))]
pub header: ExecutionPayloadHeaderFulu<E>,
#[superstruct(only(Deneb, Electra, Fulu))]
pub blob_kzg_commitments: KzgCommitments<E>,
#[serde(with = "serde_utils::quoted_u256")]
pub value: Uint256,
@@ -85,6 +88,7 @@ impl<E: EthSpec> ForkVersionDeserialize for BuilderBid<E> {
ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Deneb => Self::Deneb(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Electra => Self::Electra(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Fulu => Self::Fulu(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"BuilderBid failed to deserialize: unsupported fork '{}'",

View File

@@ -192,6 +192,14 @@ pub struct ChainSpec {
pub min_per_epoch_churn_limit_electra: u64,
pub max_per_epoch_activation_exit_churn_limit: u64,
/*
* Fulu hard fork params
*/
pub fulu_fork_version: [u8; 4],
/// The Fulu fork epoch is optional, with `None` representing "Fulu never happens".
pub fulu_fork_epoch: Option<Epoch>,
pub fulu_placeholder: u64,
/*
* DAS params
*/
@@ -313,17 +321,20 @@ impl ChainSpec {
/// Returns the name of the fork which is active at `epoch`.
pub fn fork_name_at_epoch(&self, epoch: Epoch) -> ForkName {
match self.electra_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Electra,
_ => match self.deneb_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Deneb,
_ => match self.capella_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Capella,
_ => match self.bellatrix_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Bellatrix,
_ => match self.altair_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Altair,
_ => ForkName::Base,
match self.fulu_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Fulu,
_ => match self.electra_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Electra,
_ => match self.deneb_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Deneb,
_ => match self.capella_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Capella,
_ => match self.bellatrix_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Bellatrix,
_ => match self.altair_fork_epoch {
Some(fork_epoch) if epoch >= fork_epoch => ForkName::Altair,
_ => ForkName::Base,
},
},
},
},
@@ -340,6 +351,7 @@ impl ChainSpec {
ForkName::Capella => self.capella_fork_version,
ForkName::Deneb => self.deneb_fork_version,
ForkName::Electra => self.electra_fork_version,
ForkName::Fulu => self.fulu_fork_version,
}
}
@@ -352,6 +364,7 @@ impl ChainSpec {
ForkName::Capella => self.capella_fork_epoch,
ForkName::Deneb => self.deneb_fork_epoch,
ForkName::Electra => self.electra_fork_epoch,
ForkName::Fulu => self.fulu_fork_epoch,
}
}
@@ -802,6 +815,13 @@ impl ChainSpec {
})
.expect("calculation does not overflow"),
/*
* Fulu hard fork params
*/
fulu_fork_version: [0x06, 0x00, 0x00, 0x00],
fulu_fork_epoch: None,
fulu_placeholder: 0,
/*
* DAS params
*/
@@ -917,6 +937,9 @@ impl ChainSpec {
u64::checked_pow(2, 7)?.checked_mul(u64::checked_pow(10, 9)?)
})
.expect("calculation does not overflow"),
// Fulu
fulu_fork_version: [0x06, 0x00, 0x00, 0x01],
fulu_fork_epoch: None,
// PeerDAS
eip7594_fork_epoch: None,
// Other
@@ -1121,6 +1144,13 @@ impl ChainSpec {
})
.expect("calculation does not overflow"),
/*
* Fulu hard fork params
*/
fulu_fork_version: [0x06, 0x00, 0x00, 0x64],
fulu_fork_epoch: None,
fulu_placeholder: 0,
/*
* DAS params
*/
@@ -1255,6 +1285,14 @@ pub struct Config {
#[serde(deserialize_with = "deserialize_fork_epoch")]
pub electra_fork_epoch: Option<MaybeQuoted<Epoch>>,
#[serde(default = "default_fulu_fork_version")]
#[serde(with = "serde_utils::bytes_4_hex")]
fulu_fork_version: [u8; 4],
#[serde(default)]
#[serde(serialize_with = "serialize_fork_epoch")]
#[serde(deserialize_with = "deserialize_fork_epoch")]
pub fulu_fork_epoch: Option<MaybeQuoted<Epoch>>,
#[serde(default)]
#[serde(serialize_with = "serialize_fork_epoch")]
#[serde(deserialize_with = "deserialize_fork_epoch")]
@@ -1392,6 +1430,11 @@ fn default_electra_fork_version() -> [u8; 4] {
[0xff, 0xff, 0xff, 0xff]
}
fn default_fulu_fork_version() -> [u8; 4] {
// This value shouldn't be used.
[0xff, 0xff, 0xff, 0xff]
}
/// Placeholder value: 2^256-2^10 (115792089237316195423570985008687907853269984665640564039457584007913129638912).
///
/// Taken from https://github.com/ethereum/consensus-specs/blob/d5e4828aecafaf1c57ef67a5f23c4ae7b08c5137/configs/mainnet.yaml#L15-L16
@@ -1655,6 +1698,11 @@ impl Config {
.electra_fork_epoch
.map(|epoch| MaybeQuoted { value: epoch }),
fulu_fork_version: spec.fulu_fork_version,
fulu_fork_epoch: spec
.fulu_fork_epoch
.map(|epoch| MaybeQuoted { value: epoch }),
eip7594_fork_epoch: spec
.eip7594_fork_epoch
.map(|epoch| MaybeQuoted { value: epoch }),
@@ -1738,6 +1786,8 @@ impl Config {
deneb_fork_version,
electra_fork_epoch,
electra_fork_version,
fulu_fork_epoch,
fulu_fork_version,
eip7594_fork_epoch,
seconds_per_slot,
seconds_per_eth1_block,
@@ -1801,6 +1851,8 @@ impl Config {
deneb_fork_version,
electra_fork_epoch: electra_fork_epoch.map(|q| q.value),
electra_fork_version,
fulu_fork_epoch: fulu_fork_epoch.map(|q| q.value),
fulu_fork_version,
eip7594_fork_epoch: eip7594_fork_epoch.map(|q| q.value),
seconds_per_slot,
seconds_per_eth1_block,

View File

@@ -1,6 +1,6 @@
use crate::{
consts::altair, consts::deneb, AltairPreset, BasePreset, BellatrixPreset, CapellaPreset,
ChainSpec, Config, DenebPreset, ElectraPreset, EthSpec, ForkName,
ChainSpec, Config, DenebPreset, ElectraPreset, EthSpec, ForkName, FuluPreset,
};
use maplit::hashmap;
use serde::{Deserialize, Serialize};
@@ -12,7 +12,7 @@ use superstruct::superstruct;
///
/// Mostly useful for the API.
#[superstruct(
variants(Capella, Deneb, Electra),
variants(Deneb, Electra, Fulu),
variant_attributes(derive(Serialize, Deserialize, Debug, PartialEq, Clone))
)]
#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
@@ -29,12 +29,14 @@ pub struct ConfigAndPreset {
pub bellatrix_preset: BellatrixPreset,
#[serde(flatten)]
pub capella_preset: CapellaPreset,
#[superstruct(only(Deneb, Electra))]
#[serde(flatten)]
pub deneb_preset: DenebPreset,
#[superstruct(only(Electra))]
#[superstruct(only(Electra, Fulu))]
#[serde(flatten)]
pub electra_preset: ElectraPreset,
#[superstruct(only(Fulu))]
#[serde(flatten)]
pub fulu_preset: FuluPreset,
/// The `extra_fields` map allows us to gracefully decode fields intended for future hard forks.
#[serde(flatten)]
pub extra_fields: HashMap<String, Value>,
@@ -48,13 +50,31 @@ impl ConfigAndPreset {
let altair_preset = AltairPreset::from_chain_spec::<E>(spec);
let bellatrix_preset = BellatrixPreset::from_chain_spec::<E>(spec);
let capella_preset = CapellaPreset::from_chain_spec::<E>(spec);
let deneb_preset = DenebPreset::from_chain_spec::<E>(spec);
let extra_fields = get_extra_fields(spec);
if spec.electra_fork_epoch.is_some()
if spec.fulu_fork_epoch.is_some()
|| fork_name.is_none()
|| fork_name == Some(ForkName::Fulu)
{
let electra_preset = ElectraPreset::from_chain_spec::<E>(spec);
let fulu_preset = FuluPreset::from_chain_spec::<E>(spec);
ConfigAndPreset::Fulu(ConfigAndPresetFulu {
config,
base_preset,
altair_preset,
bellatrix_preset,
capella_preset,
deneb_preset,
electra_preset,
fulu_preset,
extra_fields,
})
} else if spec.electra_fork_epoch.is_some()
|| fork_name.is_none()
|| fork_name == Some(ForkName::Electra)
{
let deneb_preset = DenebPreset::from_chain_spec::<E>(spec);
let electra_preset = ElectraPreset::from_chain_spec::<E>(spec);
ConfigAndPreset::Electra(ConfigAndPresetElectra {
@@ -67,11 +87,7 @@ impl ConfigAndPreset {
electra_preset,
extra_fields,
})
} else if spec.deneb_fork_epoch.is_some()
|| fork_name.is_none()
|| fork_name == Some(ForkName::Deneb)
{
let deneb_preset = DenebPreset::from_chain_spec::<E>(spec);
} else {
ConfigAndPreset::Deneb(ConfigAndPresetDeneb {
config,
base_preset,
@@ -81,15 +97,6 @@ impl ConfigAndPreset {
deneb_preset,
extra_fields,
})
} else {
ConfigAndPreset::Capella(ConfigAndPresetCapella {
config,
base_preset,
altair_preset,
bellatrix_preset,
capella_preset,
extra_fields,
})
}
}
}
@@ -164,8 +171,8 @@ mod test {
.write(false)
.open(tmp_file.as_ref())
.expect("error while opening the file");
let from: ConfigAndPresetElectra =
let from: ConfigAndPresetFulu =
serde_yaml::from_reader(reader).expect("error while deserializing");
assert_eq!(ConfigAndPreset::Electra(from), yamlconfig);
assert_eq!(ConfigAndPreset::Fulu(from), yamlconfig);
}
}

View File

@@ -15,7 +15,7 @@ pub type Transactions<E> = VariableList<
pub type Withdrawals<E> = VariableList<Withdrawal, <E as EthSpec>::MaxWithdrawalsPerPayload>;
#[superstruct(
variants(Bellatrix, Capella, Deneb, Electra),
variants(Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Default,
@@ -82,12 +82,12 @@ pub struct ExecutionPayload<E: EthSpec> {
pub block_hash: ExecutionBlockHash,
#[serde(with = "ssz_types::serde_utils::list_of_hex_var_list")]
pub transactions: Transactions<E>,
#[superstruct(only(Capella, Deneb, Electra))]
#[superstruct(only(Capella, Deneb, Electra, Fulu))]
pub withdrawals: Withdrawals<E>,
#[superstruct(only(Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))]
#[serde(with = "serde_utils::quoted_u64")]
pub blob_gas_used: u64,
#[superstruct(only(Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))]
#[serde(with = "serde_utils::quoted_u64")]
pub excess_blob_gas: u64,
}
@@ -114,6 +114,7 @@ impl<E: EthSpec> ExecutionPayload<E> {
ForkName::Capella => ExecutionPayloadCapella::from_ssz_bytes(bytes).map(Self::Capella),
ForkName::Deneb => ExecutionPayloadDeneb::from_ssz_bytes(bytes).map(Self::Deneb),
ForkName::Electra => ExecutionPayloadElectra::from_ssz_bytes(bytes).map(Self::Electra),
ForkName::Fulu => ExecutionPayloadFulu::from_ssz_bytes(bytes).map(Self::Fulu),
}
}
@@ -166,6 +167,19 @@ impl<E: EthSpec> ExecutionPayload<E> {
// Max size of variable length `withdrawals` field
+ (E::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
#[allow(clippy::arithmetic_side_effects)]
/// Returns the maximum size of an execution payload.
pub fn max_execution_payload_fulu_size() -> usize {
// Fixed part
ExecutionPayloadFulu::<E>::default().as_ssz_bytes().len()
// Max size of variable length `extra_data` field
+ (E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len())
// Max size of variable length `transactions` field
+ (E::max_transactions_per_payload() * (ssz::BYTES_PER_LENGTH_OFFSET + E::max_bytes_per_transaction()))
// Max size of variable length `withdrawals` field
+ (E::max_withdrawals_per_payload() * <Withdrawal as Encode>::ssz_fixed_len())
}
}
impl<E: EthSpec> ForkVersionDeserialize for ExecutionPayload<E> {
@@ -184,6 +198,7 @@ impl<E: EthSpec> ForkVersionDeserialize for ExecutionPayload<E> {
ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Deneb => Self::Deneb(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Electra => Self::Electra(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Fulu => Self::Fulu(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"ExecutionPayload failed to deserialize: unsupported fork '{}'",
@@ -201,6 +216,7 @@ impl<E: EthSpec> ExecutionPayload<E> {
ExecutionPayload::Capella(_) => ForkName::Capella,
ExecutionPayload::Deneb(_) => ForkName::Deneb,
ExecutionPayload::Electra(_) => ForkName::Electra,
ExecutionPayload::Fulu(_) => ForkName::Fulu,
}
}
}

View File

@@ -8,7 +8,7 @@ use tree_hash::TreeHash;
use tree_hash_derive::TreeHash;
#[superstruct(
variants(Bellatrix, Capella, Deneb, Electra),
variants(Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Default,
@@ -78,12 +78,12 @@ pub struct ExecutionPayloadHeader<E: EthSpec> {
pub block_hash: ExecutionBlockHash,
#[superstruct(getter(copy))]
pub transactions_root: Hash256,
#[superstruct(only(Capella, Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))]
pub withdrawals_root: Hash256,
#[superstruct(only(Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))]
#[serde(with = "serde_utils::quoted_u64")]
pub blob_gas_used: u64,
#[superstruct(only(Deneb, Electra), partial_getter(copy))]
#[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))]
#[serde(with = "serde_utils::quoted_u64")]
pub excess_blob_gas: u64,
}
@@ -108,18 +108,18 @@ impl<E: EthSpec> ExecutionPayloadHeader<E> {
ForkName::Electra => {
ExecutionPayloadHeaderElectra::from_ssz_bytes(bytes).map(Self::Electra)
}
ForkName::Fulu => ExecutionPayloadHeaderFulu::from_ssz_bytes(bytes).map(Self::Fulu),
}
}
#[allow(clippy::arithmetic_side_effects)]
pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize {
// Matching here in case variable fields are added in future forks.
match fork_name {
ForkName::Base | ForkName::Altair => 0,
ForkName::Bellatrix | ForkName::Capella | ForkName::Deneb | ForkName::Electra => {
// Max size of variable length `extra_data` field
E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len()
}
// TODO(newfork): Add a new case here if there are new variable fields
if fork_name.bellatrix_enabled() {
// Max size of variable length `extra_data` field
E::max_extra_data_bytes() * <u8 as Encode>::ssz_fixed_len()
} else {
0
}
}
@@ -129,6 +129,7 @@ impl<E: EthSpec> ExecutionPayloadHeader<E> {
ExecutionPayloadHeader::Capella(_) => ForkName::Capella,
ExecutionPayloadHeader::Deneb(_) => ForkName::Deneb,
ExecutionPayloadHeader::Electra(_) => ForkName::Electra,
ExecutionPayloadHeader::Fulu(_) => ForkName::Fulu,
}
}
}
@@ -212,6 +213,30 @@ impl<E: EthSpec> ExecutionPayloadHeaderDeneb<E> {
}
}
impl<E: EthSpec> ExecutionPayloadHeaderElectra<E> {
pub fn upgrade_to_fulu(&self) -> ExecutionPayloadHeaderFulu<E> {
ExecutionPayloadHeaderFulu {
parent_hash: self.parent_hash,
fee_recipient: self.fee_recipient,
state_root: self.state_root,
receipts_root: self.receipts_root,
logs_bloom: self.logs_bloom.clone(),
prev_randao: self.prev_randao,
block_number: self.block_number,
gas_limit: self.gas_limit,
gas_used: self.gas_used,
timestamp: self.timestamp,
extra_data: self.extra_data.clone(),
base_fee_per_gas: self.base_fee_per_gas,
block_hash: self.block_hash,
transactions_root: self.transactions_root,
withdrawals_root: self.withdrawals_root,
blob_gas_used: self.blob_gas_used,
excess_blob_gas: self.excess_blob_gas,
}
}
}
impl<'a, E: EthSpec> From<&'a ExecutionPayloadBellatrix<E>> for ExecutionPayloadHeaderBellatrix<E> {
fn from(payload: &'a ExecutionPayloadBellatrix<E>) -> Self {
Self {
@@ -303,6 +328,30 @@ impl<'a, E: EthSpec> From<&'a ExecutionPayloadElectra<E>> for ExecutionPayloadHe
}
}
impl<'a, E: EthSpec> From<&'a ExecutionPayloadFulu<E>> for ExecutionPayloadHeaderFulu<E> {
fn from(payload: &'a ExecutionPayloadFulu<E>) -> Self {
Self {
parent_hash: payload.parent_hash,
fee_recipient: payload.fee_recipient,
state_root: payload.state_root,
receipts_root: payload.receipts_root,
logs_bloom: payload.logs_bloom.clone(),
prev_randao: payload.prev_randao,
block_number: payload.block_number,
gas_limit: payload.gas_limit,
gas_used: payload.gas_used,
timestamp: payload.timestamp,
extra_data: payload.extra_data.clone(),
base_fee_per_gas: payload.base_fee_per_gas,
block_hash: payload.block_hash,
transactions_root: payload.transactions.tree_hash_root(),
withdrawals_root: payload.withdrawals.tree_hash_root(),
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas,
}
}
}
// These impls are required to work around an inelegance in `to_execution_payload_header`.
// They only clone headers so they should be relatively cheap.
impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderBellatrix<E> {
@@ -329,6 +378,12 @@ impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderElectra<E> {
}
}
impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderFulu<E> {
fn from(payload: &'a Self) -> Self {
payload.clone()
}
}
impl<'a, E: EthSpec> From<ExecutionPayloadRef<'a, E>> for ExecutionPayloadHeader<E> {
fn from(payload: ExecutionPayloadRef<'a, E>) -> Self {
map_execution_payload_ref_into_execution_payload_header!(
@@ -387,6 +442,9 @@ impl<E: EthSpec> ExecutionPayloadHeaderRefMut<'_, E> {
ExecutionPayloadHeaderRefMut::Electra(mut_ref) => {
*mut_ref = header.try_into()?;
}
ExecutionPayloadHeaderRefMut::Fulu(mut_ref) => {
*mut_ref = header.try_into()?;
}
}
Ok(())
}
@@ -404,6 +462,16 @@ impl<E: EthSpec> TryFrom<ExecutionPayloadHeader<E>> for ExecutionPayloadHeaderEl
}
}
impl<E: EthSpec> TryFrom<ExecutionPayloadHeader<E>> for ExecutionPayloadHeaderFulu<E> {
type Error = BeaconStateError;
fn try_from(header: ExecutionPayloadHeader<E>) -> Result<Self, Self::Error> {
match header {
ExecutionPayloadHeader::Fulu(execution_payload_header) => Ok(execution_payload_header),
_ => Err(BeaconStateError::IncorrectStateVariant),
}
}
}
impl<E: EthSpec> ForkVersionDeserialize for ExecutionPayloadHeader<E> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
@@ -423,6 +491,7 @@ impl<E: EthSpec> ForkVersionDeserialize for ExecutionPayloadHeader<E> {
ForkName::Capella => Self::Capella(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Deneb => Self::Deneb(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Electra => Self::Electra(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Fulu => Self::Fulu(serde_json::from_value(value).map_err(convert_err)?),
ForkName::Base | ForkName::Altair => {
return Err(serde::de::Error::custom(format!(
"ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'",

View File

@@ -69,6 +69,13 @@ impl ForkContext {
));
}
if spec.fulu_fork_epoch.is_some() {
fork_to_digest.push((
ForkName::Fulu,
ChainSpec::compute_fork_digest(spec.fulu_fork_version, genesis_validators_root),
));
}
let fork_to_digest: HashMap<ForkName, [u8; 4]> = fork_to_digest.into_iter().collect();
let digest_to_fork = fork_to_digest

View File

@@ -17,6 +17,7 @@ pub enum ForkName {
Capella,
Deneb,
Electra,
Fulu,
}
impl ForkName {
@@ -28,6 +29,7 @@ impl ForkName {
ForkName::Capella,
ForkName::Deneb,
ForkName::Electra,
ForkName::Fulu,
]
}
@@ -38,6 +40,7 @@ impl ForkName {
(ForkName::Capella, spec.capella_fork_epoch),
(ForkName::Deneb, spec.deneb_fork_epoch),
(ForkName::Electra, spec.electra_fork_epoch),
(ForkName::Fulu, spec.fulu_fork_epoch),
]
}
@@ -57,6 +60,7 @@ impl ForkName {
spec.capella_fork_epoch = None;
spec.deneb_fork_epoch = None;
spec.electra_fork_epoch = None;
spec.fulu_fork_epoch = None;
spec
}
ForkName::Altair => {
@@ -65,6 +69,7 @@ impl ForkName {
spec.capella_fork_epoch = None;
spec.deneb_fork_epoch = None;
spec.electra_fork_epoch = None;
spec.fulu_fork_epoch = None;
spec
}
ForkName::Bellatrix => {
@@ -73,6 +78,7 @@ impl ForkName {
spec.capella_fork_epoch = None;
spec.deneb_fork_epoch = None;
spec.electra_fork_epoch = None;
spec.fulu_fork_epoch = None;
spec
}
ForkName::Capella => {
@@ -81,6 +87,7 @@ impl ForkName {
spec.capella_fork_epoch = Some(Epoch::new(0));
spec.deneb_fork_epoch = None;
spec.electra_fork_epoch = None;
spec.fulu_fork_epoch = None;
spec
}
ForkName::Deneb => {
@@ -89,6 +96,7 @@ impl ForkName {
spec.capella_fork_epoch = Some(Epoch::new(0));
spec.deneb_fork_epoch = Some(Epoch::new(0));
spec.electra_fork_epoch = None;
spec.fulu_fork_epoch = None;
spec
}
ForkName::Electra => {
@@ -97,6 +105,16 @@ impl ForkName {
spec.capella_fork_epoch = Some(Epoch::new(0));
spec.deneb_fork_epoch = Some(Epoch::new(0));
spec.electra_fork_epoch = Some(Epoch::new(0));
spec.fulu_fork_epoch = None;
spec
}
ForkName::Fulu => {
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.deneb_fork_epoch = Some(Epoch::new(0));
spec.electra_fork_epoch = Some(Epoch::new(0));
spec.fulu_fork_epoch = Some(Epoch::new(0));
spec
}
}
@@ -113,6 +131,7 @@ impl ForkName {
ForkName::Capella => Some(ForkName::Bellatrix),
ForkName::Deneb => Some(ForkName::Capella),
ForkName::Electra => Some(ForkName::Deneb),
ForkName::Fulu => Some(ForkName::Electra),
}
}
@@ -126,7 +145,8 @@ impl ForkName {
ForkName::Bellatrix => Some(ForkName::Capella),
ForkName::Capella => Some(ForkName::Deneb),
ForkName::Deneb => Some(ForkName::Electra),
ForkName::Electra => None,
ForkName::Electra => Some(ForkName::Fulu),
ForkName::Fulu => None,
}
}
@@ -149,6 +169,10 @@ impl ForkName {
pub fn electra_enabled(self) -> bool {
self >= ForkName::Electra
}
pub fn fulu_enabled(self) -> bool {
self >= ForkName::Fulu
}
}
/// Map a fork name into a fork-versioned superstruct type like `BeaconBlock`.
@@ -200,6 +224,10 @@ macro_rules! map_fork_name_with {
let (value, extra_data) = $body;
($t::Electra(value), extra_data)
}
ForkName::Fulu => {
let (value, extra_data) = $body;
($t::Fulu(value), extra_data)
}
}
};
}
@@ -215,6 +243,7 @@ impl FromStr for ForkName {
"capella" => ForkName::Capella,
"deneb" => ForkName::Deneb,
"electra" => ForkName::Electra,
"fulu" => ForkName::Fulu,
_ => return Err(format!("unknown fork name: {}", fork_name)),
})
}
@@ -229,6 +258,7 @@ impl Display for ForkName {
ForkName::Capella => "capella".fmt(f),
ForkName::Deneb => "deneb".fmt(f),
ForkName::Electra => "electra".fmt(f),
ForkName::Fulu => "fulu".fmt(f),
}
}
}

View File

@@ -126,13 +126,13 @@ pub use crate::attester_slashing::{
};
pub use crate::beacon_block::{
BeaconBlock, BeaconBlockAltair, BeaconBlockBase, BeaconBlockBellatrix, BeaconBlockCapella,
BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockRef, BeaconBlockRefMut, BlindedBeaconBlock,
BlockImportSource, EmptyBlock,
BeaconBlockDeneb, BeaconBlockElectra, BeaconBlockFulu, BeaconBlockRef, BeaconBlockRefMut,
BlindedBeaconBlock, BlockImportSource, EmptyBlock,
};
pub use crate::beacon_block_body::{
BeaconBlockBody, BeaconBlockBodyAltair, BeaconBlockBodyBase, BeaconBlockBodyBellatrix,
BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyRef,
BeaconBlockBodyRefMut,
BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu,
BeaconBlockBodyRef, BeaconBlockBodyRefMut,
};
pub use crate::beacon_block_header::BeaconBlockHeader;
pub use crate::beacon_committee::{BeaconCommittee, OwnedBeaconCommittee};
@@ -142,7 +142,7 @@ pub use crate::bls_to_execution_change::BlsToExecutionChange;
pub use crate::chain_spec::{ChainSpec, Config, Domain};
pub use crate::checkpoint::Checkpoint;
pub use crate::config_and_preset::{
ConfigAndPreset, ConfigAndPresetCapella, ConfigAndPresetDeneb, ConfigAndPresetElectra,
ConfigAndPreset, ConfigAndPresetDeneb, ConfigAndPresetElectra, ConfigAndPresetFulu,
};
pub use crate::consolidation_request::ConsolidationRequest;
pub use crate::contribution_and_proof::ContributionAndProof;
@@ -163,12 +163,13 @@ pub use crate::execution_block_hash::ExecutionBlockHash;
pub use crate::execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader};
pub use crate::execution_payload::{
ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb,
ExecutionPayloadElectra, ExecutionPayloadRef, Transaction, Transactions, Withdrawals,
ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadRef, Transaction, Transactions,
Withdrawals,
};
pub use crate::execution_payload_header::{
ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella,
ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderRef,
ExecutionPayloadHeaderRefMut,
ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu,
ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut,
};
pub use crate::execution_requests::{ExecutionRequests, RequestPrefix};
pub use crate::fork::Fork;
@@ -183,31 +184,33 @@ pub use crate::indexed_attestation::{
};
pub use crate::light_client_bootstrap::{
LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella,
LightClientBootstrapDeneb, LightClientBootstrapElectra,
LightClientBootstrapDeneb, LightClientBootstrapElectra, LightClientBootstrapFulu,
};
pub use crate::light_client_finality_update::{
LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella,
LightClientFinalityUpdateDeneb, LightClientFinalityUpdateElectra,
LightClientFinalityUpdateFulu,
};
pub use crate::light_client_header::{
LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb,
LightClientHeaderElectra,
LightClientHeaderElectra, LightClientHeaderFulu,
};
pub use crate::light_client_optimistic_update::{
LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair,
LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb,
LightClientOptimisticUpdateElectra,
LightClientOptimisticUpdateElectra, LightClientOptimisticUpdateFulu,
};
pub use crate::light_client_update::{
Error as LightClientUpdateError, LightClientUpdate, LightClientUpdateAltair,
LightClientUpdateCapella, LightClientUpdateDeneb, LightClientUpdateElectra, MerkleProof,
LightClientUpdateCapella, LightClientUpdateDeneb, LightClientUpdateElectra,
LightClientUpdateFulu, MerkleProof,
};
pub use crate::participation_flags::ParticipationFlags;
pub use crate::payload::{
AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella,
BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadRef, BlockType, ExecPayload,
FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra,
FullPayloadRef, OwnedExecPayload,
BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadRef, BlockType,
ExecPayload, FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb,
FullPayloadElectra, FullPayloadFulu, FullPayloadRef, OwnedExecPayload,
};
pub use crate::pending_attestation::PendingAttestation;
pub use crate::pending_consolidation::PendingConsolidation;
@@ -215,6 +218,7 @@ pub use crate::pending_deposit::PendingDeposit;
pub use crate::pending_partial_withdrawal::PendingPartialWithdrawal;
pub use crate::preset::{
AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, DenebPreset, ElectraPreset,
FuluPreset,
};
pub use crate::proposer_preparation_data::ProposerPreparationData;
pub use crate::proposer_slashing::ProposerSlashing;
@@ -229,7 +233,7 @@ pub use crate::signed_beacon_block::{
ssz_tagged_signed_beacon_block, ssz_tagged_signed_beacon_block_arc, SignedBeaconBlock,
SignedBeaconBlockAltair, SignedBeaconBlockBase, SignedBeaconBlockBellatrix,
SignedBeaconBlockCapella, SignedBeaconBlockDeneb, SignedBeaconBlockElectra,
SignedBeaconBlockHash, SignedBlindedBeaconBlock,
SignedBeaconBlockFulu, SignedBeaconBlockHash, SignedBlindedBeaconBlock,
};
pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader;
pub use crate::signed_bls_to_execution_change::SignedBlsToExecutionChange;

View File

@@ -2,7 +2,7 @@ use crate::{
light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, EthSpec, FixedVector,
ForkName, ForkVersionDeserialize, Hash256, LightClientHeader, LightClientHeaderAltair,
LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra,
SignedBlindedBeaconBlock, Slot, SyncCommittee,
LightClientHeaderFulu, SignedBlindedBeaconBlock, Slot, SyncCommittee,
};
use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize};
@@ -17,7 +17,7 @@ use tree_hash_derive::TreeHash;
/// A LightClientBootstrap is the initializer we send over to light_client nodes
/// that are trying to generate their basic storage when booting up.
#[superstruct(
variants(Altair, Capella, Deneb, Electra),
variants(Altair, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -54,6 +54,8 @@ pub struct LightClientBootstrap<E: EthSpec> {
pub header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "header_electra"))]
pub header: LightClientHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))]
pub header: LightClientHeaderFulu<E>,
/// The `SyncCommittee` used in the requested period.
pub current_sync_committee: Arc<SyncCommittee<E>>,
/// Merkle proof for sync committee
@@ -63,7 +65,7 @@ pub struct LightClientBootstrap<E: EthSpec> {
)]
pub current_sync_committee_branch: FixedVector<Hash256, CurrentSyncCommitteeProofLen>,
#[superstruct(
only(Electra),
only(Electra, Fulu),
partial_getter(rename = "current_sync_committee_branch_electra")
)]
pub current_sync_committee_branch: FixedVector<Hash256, CurrentSyncCommitteeProofLenElectra>,
@@ -79,6 +81,7 @@ impl<E: EthSpec> LightClientBootstrap<E> {
Self::Capella(_) => func(ForkName::Capella),
Self::Deneb(_) => func(ForkName::Deneb),
Self::Electra(_) => func(ForkName::Electra),
Self::Fulu(_) => func(ForkName::Fulu),
}
}
@@ -97,6 +100,7 @@ impl<E: EthSpec> LightClientBootstrap<E> {
ForkName::Capella => Self::Capella(LightClientBootstrapCapella::from_ssz_bytes(bytes)?),
ForkName::Deneb => Self::Deneb(LightClientBootstrapDeneb::from_ssz_bytes(bytes)?),
ForkName::Electra => Self::Electra(LightClientBootstrapElectra::from_ssz_bytes(bytes)?),
ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu::from_ssz_bytes(bytes)?),
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientBootstrap decoding for {fork_name} not implemented"
@@ -117,6 +121,7 @@ impl<E: EthSpec> LightClientBootstrap<E> {
ForkName::Capella => <LightClientBootstrapCapella<E> as Encode>::ssz_fixed_len(),
ForkName::Deneb => <LightClientBootstrapDeneb<E> as Encode>::ssz_fixed_len(),
ForkName::Electra => <LightClientBootstrapElectra<E> as Encode>::ssz_fixed_len(),
ForkName::Fulu => <LightClientBootstrapFulu<E> as Encode>::ssz_fixed_len(),
};
fixed_len + LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
}
@@ -152,6 +157,11 @@ impl<E: EthSpec> LightClientBootstrap<E> {
current_sync_committee,
current_sync_committee_branch: current_sync_committee_branch.into(),
}),
ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu {
header: LightClientHeaderFulu::block_to_light_client_header(block)?,
current_sync_committee,
current_sync_committee_branch: current_sync_committee_branch.into(),
}),
};
Ok(light_client_bootstrap)
@@ -192,6 +202,11 @@ impl<E: EthSpec> LightClientBootstrap<E> {
current_sync_committee,
current_sync_committee_branch: current_sync_committee_branch.into(),
}),
ForkName::Fulu => Self::Fulu(LightClientBootstrapFulu {
header: LightClientHeaderFulu::block_to_light_client_header(block)?,
current_sync_committee,
current_sync_committee_branch: current_sync_committee_branch.into(),
}),
};
Ok(light_client_bootstrap)
@@ -241,4 +256,10 @@ mod tests {
use crate::{LightClientBootstrapElectra, MainnetEthSpec};
ssz_tests!(LightClientBootstrapElectra<MainnetEthSpec>);
}
#[cfg(test)]
mod fulu {
use crate::{LightClientBootstrapFulu, MainnetEthSpec};
ssz_tests!(LightClientBootstrapFulu<MainnetEthSpec>);
}
}

View File

@@ -3,7 +3,7 @@ use crate::ChainSpec;
use crate::{
light_client_update::*, test_utils::TestRandom, ForkName, ForkVersionDeserialize,
LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb,
LightClientHeaderElectra, SignedBlindedBeaconBlock,
LightClientHeaderElectra, LightClientHeaderFulu, SignedBlindedBeaconBlock,
};
use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize};
@@ -16,7 +16,7 @@ use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
#[superstruct(
variants(Altair, Capella, Deneb, Electra),
variants(Altair, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -53,6 +53,8 @@ pub struct LightClientFinalityUpdate<E: EthSpec> {
pub attested_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))]
pub attested_header: LightClientHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))]
pub attested_header: LightClientHeaderFulu<E>,
/// The last `BeaconBlockHeader` from the last attested finalized block (end of epoch).
#[superstruct(only(Altair), partial_getter(rename = "finalized_header_altair"))]
pub finalized_header: LightClientHeaderAltair<E>,
@@ -62,13 +64,18 @@ pub struct LightClientFinalityUpdate<E: EthSpec> {
pub finalized_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "finalized_header_electra"))]
pub finalized_header: LightClientHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))]
pub finalized_header: LightClientHeaderFulu<E>,
/// Merkle proof attesting finalized header.
#[superstruct(
only(Altair, Capella, Deneb),
partial_getter(rename = "finality_branch_altair")
)]
pub finality_branch: FixedVector<Hash256, FinalizedRootProofLen>,
#[superstruct(only(Electra), partial_getter(rename = "finality_branch_electra"))]
#[superstruct(
only(Electra, Fulu),
partial_getter(rename = "finality_branch_electra")
)]
pub finality_branch: FixedVector<Hash256, FinalizedRootProofLenElectra>,
/// current sync aggregate
pub sync_aggregate: SyncAggregate<E>,
@@ -135,6 +142,17 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
sync_aggregate,
signature_slot,
}),
ForkName::Fulu => Self::Fulu(LightClientFinalityUpdateFulu {
attested_header: LightClientHeaderFulu::block_to_light_client_header(
attested_block,
)?,
finalized_header: LightClientHeaderFulu::block_to_light_client_header(
finalized_block,
)?,
finality_branch: finality_branch.into(),
sync_aggregate,
signature_slot,
}),
ForkName::Base => return Err(Error::AltairForkNotActive),
};
@@ -151,6 +169,7 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
Self::Capella(_) => func(ForkName::Capella),
Self::Deneb(_) => func(ForkName::Deneb),
Self::Electra(_) => func(ForkName::Electra),
Self::Fulu(_) => func(ForkName::Fulu),
}
}
@@ -173,6 +192,7 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
ForkName::Electra => {
Self::Electra(LightClientFinalityUpdateElectra::from_ssz_bytes(bytes)?)
}
ForkName::Fulu => Self::Fulu(LightClientFinalityUpdateFulu::from_ssz_bytes(bytes)?),
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientFinalityUpdate decoding for {fork_name} not implemented"
@@ -193,6 +213,7 @@ impl<E: EthSpec> LightClientFinalityUpdate<E> {
ForkName::Capella => <LightClientFinalityUpdateCapella<E> as Encode>::ssz_fixed_len(),
ForkName::Deneb => <LightClientFinalityUpdateDeneb<E> as Encode>::ssz_fixed_len(),
ForkName::Electra => <LightClientFinalityUpdateElectra<E> as Encode>::ssz_fixed_len(),
ForkName::Fulu => <LightClientFinalityUpdateFulu<E> as Encode>::ssz_fixed_len(),
};
// `2 *` because there are two headers in the update
fixed_size + 2 * LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
@@ -255,4 +276,10 @@ mod tests {
use crate::{LightClientFinalityUpdateElectra, MainnetEthSpec};
ssz_tests!(LightClientFinalityUpdateElectra<MainnetEthSpec>);
}
#[cfg(test)]
mod fulu {
use crate::{LightClientFinalityUpdateFulu, MainnetEthSpec};
ssz_tests!(LightClientFinalityUpdateFulu<MainnetEthSpec>);
}
}

View File

@@ -4,7 +4,8 @@ use crate::ForkVersionDeserialize;
use crate::{light_client_update::*, BeaconBlockBody};
use crate::{
test_utils::TestRandom, EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb,
ExecutionPayloadHeaderElectra, FixedVector, Hash256, SignedBlindedBeaconBlock,
ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, FixedVector, Hash256,
SignedBlindedBeaconBlock,
};
use crate::{BeaconBlockHeader, ExecutionPayloadHeader};
use derivative::Derivative;
@@ -17,7 +18,7 @@ use test_random_derive::TestRandom;
use tree_hash_derive::TreeHash;
#[superstruct(
variants(Altair, Capella, Deneb, Electra),
variants(Altair, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -59,8 +60,10 @@ pub struct LightClientHeader<E: EthSpec> {
partial_getter(rename = "execution_payload_header_electra")
)]
pub execution: ExecutionPayloadHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "execution_payload_header_fulu"))]
pub execution: ExecutionPayloadHeaderFulu<E>,
#[superstruct(only(Capella, Deneb, Electra))]
#[superstruct(only(Capella, Deneb, Electra, Fulu))]
pub execution_branch: FixedVector<Hash256, ExecutionPayloadProofLen>,
#[ssz(skip_serializing, skip_deserializing)]
@@ -92,6 +95,9 @@ impl<E: EthSpec> LightClientHeader<E> {
ForkName::Electra => LightClientHeader::Electra(
LightClientHeaderElectra::block_to_light_client_header(block)?,
),
ForkName::Fulu => {
LightClientHeader::Fulu(LightClientHeaderFulu::block_to_light_client_header(block)?)
}
};
Ok(header)
}
@@ -110,6 +116,9 @@ impl<E: EthSpec> LightClientHeader<E> {
ForkName::Electra => {
LightClientHeader::Electra(LightClientHeaderElectra::from_ssz_bytes(bytes)?)
}
ForkName::Fulu => {
LightClientHeader::Fulu(LightClientHeaderFulu::from_ssz_bytes(bytes)?)
}
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientHeader decoding for {fork_name} not implemented"
@@ -283,6 +292,48 @@ impl<E: EthSpec> Default for LightClientHeaderElectra<E> {
}
}
impl<E: EthSpec> LightClientHeaderFulu<E> {
pub fn block_to_light_client_header(
block: &SignedBlindedBeaconBlock<E>,
) -> Result<Self, Error> {
let payload = block
.message()
.execution_payload()?
.execution_payload_fulu()?;
let header = ExecutionPayloadHeaderFulu::from(payload);
let beacon_block_body = BeaconBlockBody::from(
block
.message()
.body_fulu()
.map_err(|_| Error::BeaconBlockBodyError)?
.to_owned(),
);
let execution_branch = beacon_block_body
.to_ref()
.block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?;
Ok(LightClientHeaderFulu {
beacon: block.message().block_header(),
execution: header,
execution_branch: FixedVector::new(execution_branch)?,
_phantom_data: PhantomData,
})
}
}
impl<E: EthSpec> Default for LightClientHeaderFulu<E> {
fn default() -> Self {
Self {
beacon: BeaconBlockHeader::empty(),
execution: ExecutionPayloadHeaderFulu::default(),
execution_branch: FixedVector::default(),
_phantom_data: PhantomData,
}
}
}
impl<E: EthSpec> ForkVersionDeserialize for LightClientHeader<E> {
fn deserialize_by_fork<'de, D: serde::Deserializer<'de>>(
value: serde_json::value::Value,
@@ -301,6 +352,9 @@ impl<E: EthSpec> ForkVersionDeserialize for LightClientHeader<E> {
ForkName::Electra => serde_json::from_value(value)
.map(|light_client_header| Self::Electra(light_client_header))
.map_err(serde::de::Error::custom),
ForkName::Fulu => serde_json::from_value(value)
.map(|light_client_header| Self::Fulu(light_client_header))
.map_err(serde::de::Error::custom),
ForkName::Base => Err(serde::de::Error::custom(format!(
"LightClientHeader deserialization for {fork_name} not implemented"
))),

View File

@@ -2,7 +2,8 @@ use super::{EthSpec, ForkName, ForkVersionDeserialize, LightClientHeader, Slot,
use crate::test_utils::TestRandom;
use crate::{
light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella,
LightClientHeaderDeneb, LightClientHeaderElectra, SignedBlindedBeaconBlock,
LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu,
SignedBlindedBeaconBlock,
};
use derivative::Derivative;
use serde::{Deserialize, Deserializer, Serialize};
@@ -18,7 +19,7 @@ use tree_hash_derive::TreeHash;
/// A LightClientOptimisticUpdate is the update we send on each slot,
/// it is based off the current unfinalized epoch is verified only against BLS signature.
#[superstruct(
variants(Altair, Capella, Deneb, Electra),
variants(Altair, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -55,6 +56,8 @@ pub struct LightClientOptimisticUpdate<E: EthSpec> {
pub attested_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))]
pub attested_header: LightClientHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))]
pub attested_header: LightClientHeaderFulu<E>,
/// current sync aggregate
pub sync_aggregate: SyncAggregate<E>,
/// Slot of the sync aggregated signature
@@ -102,6 +105,13 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
sync_aggregate,
signature_slot,
}),
ForkName::Fulu => Self::Fulu(LightClientOptimisticUpdateFulu {
attested_header: LightClientHeaderFulu::block_to_light_client_header(
attested_block,
)?,
sync_aggregate,
signature_slot,
}),
ForkName::Base => return Err(Error::AltairForkNotActive),
};
@@ -117,6 +127,7 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
Self::Capella(_) => func(ForkName::Capella),
Self::Deneb(_) => func(ForkName::Deneb),
Self::Electra(_) => func(ForkName::Electra),
Self::Fulu(_) => func(ForkName::Fulu),
}
}
@@ -155,6 +166,7 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
ForkName::Electra => {
Self::Electra(LightClientOptimisticUpdateElectra::from_ssz_bytes(bytes)?)
}
ForkName::Fulu => Self::Fulu(LightClientOptimisticUpdateFulu::from_ssz_bytes(bytes)?),
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientOptimisticUpdate decoding for {fork_name} not implemented"
@@ -175,6 +187,7 @@ impl<E: EthSpec> LightClientOptimisticUpdate<E> {
ForkName::Capella => <LightClientOptimisticUpdateCapella<E> as Encode>::ssz_fixed_len(),
ForkName::Deneb => <LightClientOptimisticUpdateDeneb<E> as Encode>::ssz_fixed_len(),
ForkName::Electra => <LightClientOptimisticUpdateElectra<E> as Encode>::ssz_fixed_len(),
ForkName::Fulu => <LightClientOptimisticUpdateFulu<E> as Encode>::ssz_fixed_len(),
};
fixed_len + LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
}
@@ -238,4 +251,10 @@ mod tests {
use crate::{LightClientOptimisticUpdateElectra, MainnetEthSpec};
ssz_tests!(LightClientOptimisticUpdateElectra<MainnetEthSpec>);
}
#[cfg(test)]
mod fulu {
use crate::{LightClientOptimisticUpdateFulu, MainnetEthSpec};
ssz_tests!(LightClientOptimisticUpdateFulu<MainnetEthSpec>);
}
}

View File

@@ -4,7 +4,7 @@ use crate::LightClientHeader;
use crate::{
beacon_state, test_utils::TestRandom, ChainSpec, Epoch, ForkName, ForkVersionDeserialize,
LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb,
SignedBlindedBeaconBlock,
LightClientHeaderFulu, SignedBlindedBeaconBlock,
};
use derivative::Derivative;
use safe_arith::ArithError;
@@ -100,7 +100,7 @@ impl From<milhouse::Error> for Error {
/// or to sync up to the last committee period, we need to have one ready for each ALTAIR period
/// we go over, note: there is no need to keep all of the updates from [ALTAIR_PERIOD, CURRENT_PERIOD].
#[superstruct(
variants(Altair, Capella, Deneb, Electra),
variants(Altair, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -137,6 +137,8 @@ pub struct LightClientUpdate<E: EthSpec> {
pub attested_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "attested_header_electra"))]
pub attested_header: LightClientHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))]
pub attested_header: LightClientHeaderFulu<E>,
/// The `SyncCommittee` used in the next period.
pub next_sync_committee: Arc<SyncCommittee<E>>,
// Merkle proof for next sync committee
@@ -146,7 +148,7 @@ pub struct LightClientUpdate<E: EthSpec> {
)]
pub next_sync_committee_branch: NextSyncCommitteeBranch,
#[superstruct(
only(Electra),
only(Electra, Fulu),
partial_getter(rename = "next_sync_committee_branch_electra")
)]
pub next_sync_committee_branch: NextSyncCommitteeBranchElectra,
@@ -159,13 +161,18 @@ pub struct LightClientUpdate<E: EthSpec> {
pub finalized_header: LightClientHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "finalized_header_electra"))]
pub finalized_header: LightClientHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))]
pub finalized_header: LightClientHeaderFulu<E>,
/// Merkle proof attesting finalized header.
#[superstruct(
only(Altair, Capella, Deneb),
partial_getter(rename = "finality_branch_altair")
)]
pub finality_branch: FinalityBranch,
#[superstruct(only(Electra), partial_getter(rename = "finality_branch_electra"))]
#[superstruct(
only(Electra, Fulu),
partial_getter(rename = "finality_branch_electra")
)]
pub finality_branch: FinalityBranchElectra,
/// current sync aggreggate
pub sync_aggregate: SyncAggregate<E>,
@@ -285,6 +292,26 @@ impl<E: EthSpec> LightClientUpdate<E> {
sync_aggregate: sync_aggregate.clone(),
signature_slot: block_slot,
})
}
ForkName::Fulu => {
let attested_header =
LightClientHeaderFulu::block_to_light_client_header(attested_block)?;
let finalized_header = if let Some(finalized_block) = finalized_block {
LightClientHeaderFulu::block_to_light_client_header(finalized_block)?
} else {
LightClientHeaderFulu::default()
};
Self::Fulu(LightClientUpdateFulu {
attested_header,
next_sync_committee,
next_sync_committee_branch: next_sync_committee_branch.into(),
finalized_header,
finality_branch: finality_branch.into(),
sync_aggregate: sync_aggregate.clone(),
signature_slot: block_slot,
})
} // To add a new fork, just append the new fork variant on the latest fork. Forks that
// have a distinct execution header will need a new LightClientUpdate variant only
// if you need to test or support lightclient usages
@@ -301,6 +328,7 @@ impl<E: EthSpec> LightClientUpdate<E> {
ForkName::Capella => Self::Capella(LightClientUpdateCapella::from_ssz_bytes(bytes)?),
ForkName::Deneb => Self::Deneb(LightClientUpdateDeneb::from_ssz_bytes(bytes)?),
ForkName::Electra => Self::Electra(LightClientUpdateElectra::from_ssz_bytes(bytes)?),
ForkName::Fulu => Self::Fulu(LightClientUpdateFulu::from_ssz_bytes(bytes)?),
ForkName::Base => {
return Err(ssz::DecodeError::BytesInvalid(format!(
"LightClientUpdate decoding for {fork_name} not implemented"
@@ -317,6 +345,7 @@ impl<E: EthSpec> LightClientUpdate<E> {
LightClientUpdate::Capella(update) => update.attested_header.beacon.slot,
LightClientUpdate::Deneb(update) => update.attested_header.beacon.slot,
LightClientUpdate::Electra(update) => update.attested_header.beacon.slot,
LightClientUpdate::Fulu(update) => update.attested_header.beacon.slot,
}
}
@@ -326,6 +355,7 @@ impl<E: EthSpec> LightClientUpdate<E> {
LightClientUpdate::Capella(update) => update.finalized_header.beacon.slot,
LightClientUpdate::Deneb(update) => update.finalized_header.beacon.slot,
LightClientUpdate::Electra(update) => update.finalized_header.beacon.slot,
LightClientUpdate::Fulu(update) => update.finalized_header.beacon.slot,
}
}
@@ -445,6 +475,7 @@ impl<E: EthSpec> LightClientUpdate<E> {
ForkName::Capella => <LightClientUpdateCapella<E> as Encode>::ssz_fixed_len(),
ForkName::Deneb => <LightClientUpdateDeneb<E> as Encode>::ssz_fixed_len(),
ForkName::Electra => <LightClientUpdateElectra<E> as Encode>::ssz_fixed_len(),
ForkName::Fulu => <LightClientUpdateFulu<E> as Encode>::ssz_fixed_len(),
};
fixed_len + 2 * LightClientHeader::<E>::ssz_max_var_len_for_fork(fork_name)
}
@@ -458,6 +489,7 @@ impl<E: EthSpec> LightClientUpdate<E> {
Self::Capella(_) => func(ForkName::Capella),
Self::Deneb(_) => func(ForkName::Deneb),
Self::Electra(_) => func(ForkName::Electra),
Self::Fulu(_) => func(ForkName::Fulu),
}
}
}
@@ -513,6 +545,13 @@ mod tests {
ssz_tests!(LightClientUpdateElectra<MainnetEthSpec>);
}
#[cfg(test)]
mod fulu {
use super::*;
use crate::MainnetEthSpec;
ssz_tests!(LightClientUpdateFulu<MainnetEthSpec>);
}
#[test]
fn finalized_root_params() {
assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX);

View File

@@ -84,13 +84,15 @@ pub trait AbstractExecPayload<E: EthSpec>:
+ TryInto<Self::Capella>
+ TryInto<Self::Deneb>
+ TryInto<Self::Electra>
+ TryInto<Self::Fulu>
{
type Ref<'a>: ExecPayload<E>
+ Copy
+ From<&'a Self::Bellatrix>
+ From<&'a Self::Capella>
+ From<&'a Self::Deneb>
+ From<&'a Self::Electra>;
+ From<&'a Self::Electra>
+ From<&'a Self::Fulu>;
type Bellatrix: OwnedExecPayload<E>
+ Into<Self>
@@ -108,10 +110,14 @@ pub trait AbstractExecPayload<E: EthSpec>:
+ Into<Self>
+ for<'a> From<Cow<'a, ExecutionPayloadElectra<E>>>
+ TryFrom<ExecutionPayloadHeaderElectra<E>>;
type Fulu: OwnedExecPayload<E>
+ Into<Self>
+ for<'a> From<Cow<'a, ExecutionPayloadFulu<E>>>
+ TryFrom<ExecutionPayloadHeaderFulu<E>>;
}
#[superstruct(
variants(Bellatrix, Capella, Deneb, Electra),
variants(Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -157,6 +163,8 @@ pub struct FullPayload<E: EthSpec> {
pub execution_payload: ExecutionPayloadDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))]
pub execution_payload: ExecutionPayloadElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))]
pub execution_payload: ExecutionPayloadFulu<E>,
}
impl<E: EthSpec> From<FullPayload<E>> for ExecutionPayload<E> {
@@ -273,6 +281,9 @@ impl<E: EthSpec> ExecPayload<E> for FullPayload<E> {
FullPayload::Electra(ref inner) => {
Ok(inner.execution_payload.withdrawals.tree_hash_root())
}
FullPayload::Fulu(ref inner) => {
Ok(inner.execution_payload.withdrawals.tree_hash_root())
}
}
}
@@ -283,6 +294,7 @@ impl<E: EthSpec> ExecPayload<E> for FullPayload<E> {
}
FullPayload::Deneb(ref inner) => Ok(inner.execution_payload.blob_gas_used),
FullPayload::Electra(ref inner) => Ok(inner.execution_payload.blob_gas_used),
FullPayload::Fulu(ref inner) => Ok(inner.execution_payload.blob_gas_used),
}
}
@@ -313,6 +325,7 @@ impl<E: EthSpec> FullPayload<E> {
ForkName::Capella => Ok(FullPayloadCapella::default().into()),
ForkName::Deneb => Ok(FullPayloadDeneb::default().into()),
ForkName::Electra => Ok(FullPayloadElectra::default().into()),
ForkName::Fulu => Ok(FullPayloadFulu::default().into()),
}
}
}
@@ -412,6 +425,7 @@ impl<E: EthSpec> ExecPayload<E> for FullPayloadRef<'_, E> {
FullPayloadRef::Electra(inner) => {
Ok(inner.execution_payload.withdrawals.tree_hash_root())
}
FullPayloadRef::Fulu(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()),
}
}
@@ -422,6 +436,7 @@ impl<E: EthSpec> ExecPayload<E> for FullPayloadRef<'_, E> {
}
FullPayloadRef::Deneb(inner) => Ok(inner.execution_payload.blob_gas_used),
FullPayloadRef::Electra(inner) => Ok(inner.execution_payload.blob_gas_used),
FullPayloadRef::Fulu(inner) => Ok(inner.execution_payload.blob_gas_used),
}
}
@@ -444,6 +459,7 @@ impl<E: EthSpec> AbstractExecPayload<E> for FullPayload<E> {
type Capella = FullPayloadCapella<E>;
type Deneb = FullPayloadDeneb<E>;
type Electra = FullPayloadElectra<E>;
type Fulu = FullPayloadFulu<E>;
}
impl<E: EthSpec> From<ExecutionPayload<E>> for FullPayload<E> {
@@ -462,7 +478,7 @@ impl<E: EthSpec> TryFrom<ExecutionPayloadHeader<E>> for FullPayload<E> {
}
#[superstruct(
variants(Bellatrix, Capella, Deneb, Electra),
variants(Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -507,6 +523,8 @@ pub struct BlindedPayload<E: EthSpec> {
pub execution_payload_header: ExecutionPayloadHeaderDeneb<E>,
#[superstruct(only(Electra), partial_getter(rename = "execution_payload_electra"))]
pub execution_payload_header: ExecutionPayloadHeaderElectra<E>,
#[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))]
pub execution_payload_header: ExecutionPayloadHeaderFulu<E>,
}
impl<'a, E: EthSpec> From<BlindedPayloadRef<'a, E>> for BlindedPayload<E> {
@@ -599,6 +617,7 @@ impl<E: EthSpec> ExecPayload<E> for BlindedPayload<E> {
BlindedPayload::Electra(ref inner) => {
Ok(inner.execution_payload_header.withdrawals_root)
}
BlindedPayload::Fulu(ref inner) => Ok(inner.execution_payload_header.withdrawals_root),
}
}
@@ -609,6 +628,7 @@ impl<E: EthSpec> ExecPayload<E> for BlindedPayload<E> {
}
BlindedPayload::Deneb(ref inner) => Ok(inner.execution_payload_header.blob_gas_used),
BlindedPayload::Electra(ref inner) => Ok(inner.execution_payload_header.blob_gas_used),
BlindedPayload::Fulu(ref inner) => Ok(inner.execution_payload_header.blob_gas_used),
}
}
@@ -707,6 +727,7 @@ impl<'b, E: EthSpec> ExecPayload<E> for BlindedPayloadRef<'b, E> {
BlindedPayloadRef::Electra(inner) => {
Ok(inner.execution_payload_header.withdrawals_root)
}
BlindedPayloadRef::Fulu(inner) => Ok(inner.execution_payload_header.withdrawals_root),
}
}
@@ -717,6 +738,7 @@ impl<'b, E: EthSpec> ExecPayload<E> for BlindedPayloadRef<'b, E> {
}
BlindedPayloadRef::Deneb(inner) => Ok(inner.execution_payload_header.blob_gas_used),
BlindedPayloadRef::Electra(inner) => Ok(inner.execution_payload_header.blob_gas_used),
BlindedPayloadRef::Fulu(inner) => Ok(inner.execution_payload_header.blob_gas_used),
}
}
@@ -1020,6 +1042,13 @@ impl_exec_payload_for_fork!(
ExecutionPayloadElectra,
Electra
);
impl_exec_payload_for_fork!(
BlindedPayloadFulu,
FullPayloadFulu,
ExecutionPayloadHeaderFulu,
ExecutionPayloadFulu,
Fulu
);
impl<E: EthSpec> AbstractExecPayload<E> for BlindedPayload<E> {
type Ref<'a> = BlindedPayloadRef<'a, E>;
@@ -1027,6 +1056,7 @@ impl<E: EthSpec> AbstractExecPayload<E> for BlindedPayload<E> {
type Capella = BlindedPayloadCapella<E>;
type Deneb = BlindedPayloadDeneb<E>;
type Electra = BlindedPayloadElectra<E>;
type Fulu = BlindedPayloadFulu<E>;
}
impl<E: EthSpec> From<ExecutionPayload<E>> for BlindedPayload<E> {
@@ -1063,6 +1093,11 @@ impl<E: EthSpec> From<ExecutionPayloadHeader<E>> for BlindedPayload<E> {
execution_payload_header,
})
}
ExecutionPayloadHeader::Fulu(execution_payload_header) => {
Self::Fulu(BlindedPayloadFulu {
execution_payload_header,
})
}
}
}
}
@@ -1082,6 +1117,9 @@ impl<E: EthSpec> From<BlindedPayload<E>> for ExecutionPayloadHeader<E> {
BlindedPayload::Electra(blinded_payload) => {
ExecutionPayloadHeader::Electra(blinded_payload.execution_payload_header)
}
BlindedPayload::Fulu(blinded_payload) => {
ExecutionPayloadHeader::Fulu(blinded_payload.execution_payload_header)
}
}
}
}

View File

@@ -276,6 +276,21 @@ impl ElectraPreset {
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct FuluPreset {
#[serde(with = "serde_utils::quoted_u64")]
pub fulu_placeholder: u64,
}
impl FuluPreset {
pub fn from_chain_spec<E: EthSpec>(spec: &ChainSpec) -> Self {
Self {
fulu_placeholder: spec.fulu_placeholder,
}
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct Eip7594Preset {
@@ -343,6 +358,9 @@ mod test {
let electra: ElectraPreset = preset_from_file(&preset_name, "electra.yaml");
assert_eq!(electra, ElectraPreset::from_chain_spec::<E>(&spec));
let fulu: FuluPreset = preset_from_file(&preset_name, "fulu.yaml");
assert_eq!(fulu, FuluPreset::from_chain_spec::<E>(&spec));
let eip7594: Eip7594Preset = preset_from_file(&preset_name, "eip7594.yaml");
assert_eq!(eip7594, Eip7594Preset::from_chain_spec::<E>(&spec));
}

View File

@@ -38,7 +38,7 @@ impl From<SignedBeaconBlockHash> for Hash256 {
/// A `BeaconBlock` and a signature from its proposer.
#[superstruct(
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra),
variants(Base, Altair, Bellatrix, Capella, Deneb, Electra, Fulu),
variant_attributes(
derive(
Debug,
@@ -81,6 +81,8 @@ pub struct SignedBeaconBlock<E: EthSpec, Payload: AbstractExecPayload<E> = FullP
pub message: BeaconBlockDeneb<E, Payload>,
#[superstruct(only(Electra), partial_getter(rename = "message_electra"))]
pub message: BeaconBlockElectra<E, Payload>,
#[superstruct(only(Fulu), partial_getter(rename = "message_fulu"))]
pub message: BeaconBlockFulu<E, Payload>,
pub signature: Signature,
}
@@ -163,6 +165,9 @@ impl<E: EthSpec, Payload: AbstractExecPayload<E>> SignedBeaconBlock<E, Payload>
BeaconBlock::Electra(message) => {
SignedBeaconBlock::Electra(SignedBeaconBlockElectra { message, signature })
}
BeaconBlock::Fulu(message) => {
SignedBeaconBlock::Fulu(SignedBeaconBlockFulu { message, signature })
}
}
}
@@ -570,6 +575,64 @@ impl<E: EthSpec> SignedBeaconBlockElectra<E, BlindedPayload<E>> {
}
}
impl<E: EthSpec> SignedBeaconBlockFulu<E, BlindedPayload<E>> {
pub fn into_full_block(
self,
execution_payload: ExecutionPayloadFulu<E>,
) -> SignedBeaconBlockFulu<E, FullPayload<E>> {
let SignedBeaconBlockFulu {
message:
BeaconBlockFulu {
slot,
proposer_index,
parent_root,
state_root,
body:
BeaconBlockBodyFulu {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: BlindedPayloadFulu { .. },
bls_to_execution_changes,
blob_kzg_commitments,
execution_requests,
},
},
signature,
} = self;
SignedBeaconBlockFulu {
message: BeaconBlockFulu {
slot,
proposer_index,
parent_root,
state_root,
body: BeaconBlockBodyFulu {
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
execution_payload: FullPayloadFulu { execution_payload },
bls_to_execution_changes,
blob_kzg_commitments,
execution_requests,
},
},
signature,
}
}
}
impl<E: EthSpec> SignedBeaconBlock<E, BlindedPayload<E>> {
pub fn try_into_full_block(
self,
@@ -590,12 +653,16 @@ impl<E: EthSpec> SignedBeaconBlock<E, BlindedPayload<E>> {
(SignedBeaconBlock::Electra(block), Some(ExecutionPayload::Electra(payload))) => {
SignedBeaconBlock::Electra(block.into_full_block(payload))
}
(SignedBeaconBlock::Fulu(block), Some(ExecutionPayload::Fulu(payload))) => {
SignedBeaconBlock::Fulu(block.into_full_block(payload))
}
// avoid wildcard matching forks so that compiler will
// direct us here when a new fork has been added
(SignedBeaconBlock::Bellatrix(_), _) => return None,
(SignedBeaconBlock::Capella(_), _) => return None,
(SignedBeaconBlock::Deneb(_), _) => return None,
(SignedBeaconBlock::Electra(_), _) => return None,
(SignedBeaconBlock::Fulu(_), _) => return None,
};
Some(full_block)
}
@@ -741,6 +808,9 @@ pub mod ssz_tagged_signed_beacon_block {
ForkName::Electra => Ok(SignedBeaconBlock::Electra(
SignedBeaconBlockElectra::from_ssz_bytes(body)?,
)),
ForkName::Fulu => Ok(SignedBeaconBlock::Fulu(
SignedBeaconBlockFulu::from_ssz_bytes(body)?,
)),
}
}
}
@@ -841,8 +911,9 @@ mod test {
),
SignedBeaconBlock::from_block(
BeaconBlock::Electra(BeaconBlockElectra::empty(spec)),
sig,
sig.clone(),
),
SignedBeaconBlock::from_block(BeaconBlock::Fulu(BeaconBlockFulu::empty(spec)), sig),
];
for block in blocks {