mirror of
https://github.com/sigp/lighthouse.git
synced 2026-05-30 04:37:13 +00:00
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:
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
94
consensus/state_processing/src/upgrade/fulu.rs
Normal file
94
consensus/state_processing/src/upgrade/fulu.rs
Normal 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)
|
||||
}
|
||||
3
consensus/types/presets/gnosis/fulu.yaml
Normal file
3
consensus/types/presets/gnosis/fulu.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Gnosis preset - Fulu
|
||||
|
||||
FULU_PLACEHOLDER: 0
|
||||
3
consensus/types/presets/mainnet/fulu.yaml
Normal file
3
consensus/types/presets/mainnet/fulu.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Mainnet preset - Fulu
|
||||
|
||||
FULU_PLACEHOLDER: 0
|
||||
3
consensus/types/presets/minimal/fulu.yaml
Normal file
3
consensus/types/presets/minimal/fulu.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Minimal preset - Fulu
|
||||
|
||||
FULU_PLACEHOLDER: 0
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>>,
|
||||
|
||||
@@ -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",),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 '{}'",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 '{}'",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
))),
|
||||
|
||||
@@ -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>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user