Electra other containers (#5652)

* add new fields to execution payload and header

* beacon state changes

* partial beacon state

* safe arith in upgrade to electra

* initialize balances cache in interop genesis state

* Revert "initialize balances cache in interop genesis state"

This reverts commit c60b522865.

* always initialize balances cache if necessary in electra upgrade

* build cache earlier

* fix block test

* per fork NUM_FIELDS_POW2

* Merge branch 'unstable' of https://github.com/sigp/lighthouse into electra-other-containers

* fix lints

* get fields based on post state, as is spec'd

* fix type and move cache build
This commit is contained in:
realbigsean
2024-04-26 16:17:23 -04:00
committed by GitHub
parent a1141ea1ef
commit 000a4fdf4d
12 changed files with 275 additions and 38 deletions

View File

@@ -1074,9 +1074,8 @@ mod tests {
.expect("good electra block can be decoded"),
good_block
);
// TODO(electra): once the Electra block is changed from Deneb, update this to match
// the other forks.
assert!(BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec).is_ok());
BeaconBlock::from_ssz_bytes(&bad_block.as_ssz_bytes(), &spec)
.expect_err("bad electra block cannot be decoded");
}
}
}

View File

@@ -467,6 +467,40 @@ where
#[test_random(default)]
pub historical_summaries: List<HistoricalSummary, E::HistoricalRootsLimit>,
// Electra
#[superstruct(only(Electra), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
pub deposit_receipts_start_index: u64,
#[superstruct(only(Electra), 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))]
#[metastruct(exclude_from(tree_lists))]
#[serde(with = "serde_utils::quoted_u64")]
pub exit_balance_to_consume: u64,
#[superstruct(only(Electra), partial_getter(copy))]
#[metastruct(exclude_from(tree_lists))]
pub earliest_exit_epoch: Epoch,
#[superstruct(only(Electra), 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))]
#[metastruct(exclude_from(tree_lists))]
pub earliest_consolidation_epoch: Epoch,
#[test_random(default)]
#[superstruct(only(Electra))]
pub pending_balance_deposits: List<PendingBalanceDeposit, E::PendingBalanceDepositsLimit>,
#[test_random(default)]
#[superstruct(only(Electra))]
pub pending_partial_withdrawals:
List<PendingPartialWithdrawal, E::PendingPartialWithdrawalsLimit>,
#[test_random(default)]
#[superstruct(only(Electra))]
pub pending_consolidations: List<PendingConsolidation, E::PendingConsolidationsLimit>,
// Caching (not in the spec)
#[serde(skip_serializing, skip_deserializing)]
#[ssz(skip_serializing, skip_deserializing)]
@@ -2031,6 +2065,83 @@ impl<E: EthSpec> BeaconState<E> {
self.epoch_cache().get_base_reward(validator_index)
}
// ******* Electra accessors *******
/// Return the churn limit for the current epoch.
pub fn get_balance_churn_limit(&self, spec: &ChainSpec) -> Result<u64, Error> {
let total_active_balance = self.get_total_active_balance()?;
let churn = std::cmp::max(
spec.min_per_epoch_churn_limit_electra,
total_active_balance.safe_div(spec.churn_limit_quotient)?,
);
Ok(churn.safe_sub(churn.safe_rem(spec.effective_balance_increment)?)?)
}
/// Return the churn limit for the current epoch dedicated to activations and exits.
pub fn get_activation_exit_churn_limit(&self, spec: &ChainSpec) -> Result<u64, Error> {
Ok(std::cmp::min(
spec.max_per_epoch_activation_exit_churn_limit,
self.get_balance_churn_limit(spec)?,
))
}
pub fn get_consolidation_churn_limit(&self, spec: &ChainSpec) -> Result<u64, Error> {
self.get_balance_churn_limit(spec)?
.safe_sub(self.get_activation_exit_churn_limit(spec)?)
.map_err(Into::into)
}
// ******* Electra mutators *******
pub fn queue_excess_active_balance(
&mut self,
validator_index: usize,
spec: &ChainSpec,
) -> Result<(), Error> {
let balance = self
.balances_mut()
.get_mut(validator_index)
.ok_or(Error::UnknownValidator(validator_index))?;
if *balance > spec.min_activation_balance {
let excess_balance = balance.safe_sub(spec.min_activation_balance)?;
*balance = spec.min_activation_balance;
self.pending_balance_deposits_mut()?
.push(PendingBalanceDeposit {
index: validator_index as u64,
amount: excess_balance,
})?;
}
Ok(())
}
pub fn queue_entire_balance_and_reset_validator(
&mut self,
validator_index: usize,
spec: &ChainSpec,
) -> Result<(), Error> {
let balance = self
.balances_mut()
.get_mut(validator_index)
.ok_or(Error::UnknownValidator(validator_index))?;
let balance_copy = *balance;
*balance = 0_u64;
let validator = self
.validators_mut()
.get_mut(validator_index)
.ok_or(Error::UnknownValidator(validator_index))?;
validator.effective_balance = 0;
validator.activation_eligibility_epoch = spec.far_future_epoch;
self.pending_balance_deposits_mut()?
.push(PendingBalanceDeposit {
index: validator_index as u64,
amount: balance_copy,
})
.map_err(Into::into)
}
#[allow(clippy::arithmetic_side_effects)]
pub fn rebase_on(&mut self, base: &Self, spec: &ChainSpec) -> Result<(), Error> {
// Required for macros (which use type-hints internally).
@@ -2147,10 +2258,17 @@ impl<E: EthSpec> BeaconState<E> {
/// The number of fields of the `BeaconState` rounded up to the nearest power of two.
///
/// This is relevant to tree-hashing of the `BeaconState`.
///
/// We assume this value is stable across forks. This assumption is checked in the
/// `check_num_fields_pow2` test.
pub const NUM_FIELDS_POW2: usize = BeaconStateBellatrix::<E>::NUM_FIELDS.next_power_of_two();
pub fn num_fields_pow2(&self) -> usize {
let fork_name = self.fork_name_unchecked();
match fork_name {
ForkName::Base => BeaconStateBase::<E>::NUM_FIELDS.next_power_of_two(),
ForkName::Altair => BeaconStateAltair::<E>::NUM_FIELDS.next_power_of_two(),
ForkName::Bellatrix => BeaconStateBellatrix::<E>::NUM_FIELDS.next_power_of_two(),
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(),
}
}
/// Specialised deserialisation method that uses the `ChainSpec` as context.
#[allow(clippy::arithmetic_side_effects)]
@@ -2211,7 +2329,7 @@ impl<E: EthSpec> BeaconState<E> {
// in the `BeaconState`:
// https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/beacon-chain.md#beaconstate
generalized_index
.checked_sub(Self::NUM_FIELDS_POW2)
.checked_sub(self.num_fields_pow2())
.ok_or(Error::IndexNotSupported(generalized_index))?
}
light_client_update::FINALIZED_ROOT_INDEX => {
@@ -2221,7 +2339,7 @@ impl<E: EthSpec> BeaconState<E> {
// Subtract off the internal nodes. Result should be 105/2 - 32 = 20 which matches
// position of `finalized_checkpoint` in `BeaconState`.
finalized_checkpoint_generalized_index
.checked_sub(Self::NUM_FIELDS_POW2)
.checked_sub(self.num_fields_pow2())
.ok_or(Error::IndexNotSupported(generalized_index))?
}
_ => return Err(Error::IndexNotSupported(generalized_index)),

View File

@@ -1,10 +1,10 @@
#![cfg(test)]
use crate::{test_utils::*, ForkName};
use crate::test_utils::*;
use beacon_chain::test_utils::{BeaconChainHarness, EphemeralHarnessType};
use beacon_chain::types::{
test_utils::TestRandom, BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateBellatrix,
BeaconStateCapella, BeaconStateDeneb, BeaconStateElectra, BeaconStateError, ChainSpec, Domain,
Epoch, EthSpec, Hash256, Keypair, MainnetEthSpec, MinimalEthSpec, RelativeEpoch, Slot, Vector,
test_utils::TestRandom, BeaconState, BeaconStateAltair, BeaconStateBase, BeaconStateError,
ChainSpec, Domain, Epoch, EthSpec, Hash256, Keypair, MainnetEthSpec, MinimalEthSpec,
RelativeEpoch, Slot, Vector,
};
use ssz::Encode;
use std::ops::Mul;
@@ -403,24 +403,3 @@ fn decode_base_and_altair() {
.expect_err("bad altair state cannot be decoded");
}
}
#[test]
fn check_num_fields_pow2() {
use metastruct::NumFields;
pub type E = MainnetEthSpec;
for fork_name in ForkName::list_all() {
let num_fields = match fork_name {
ForkName::Base => BeaconStateBase::<E>::NUM_FIELDS,
ForkName::Altair => BeaconStateAltair::<E>::NUM_FIELDS,
ForkName::Bellatrix => BeaconStateBellatrix::<E>::NUM_FIELDS,
ForkName::Capella => BeaconStateCapella::<E>::NUM_FIELDS,
ForkName::Deneb => BeaconStateDeneb::<E>::NUM_FIELDS,
ForkName::Electra => BeaconStateElectra::<E>::NUM_FIELDS,
};
assert_eq!(
num_fields.next_power_of_two(),
BeaconState::<E>::NUM_FIELDS_POW2
);
}
}

View File

@@ -89,6 +89,11 @@ pub struct ExecutionPayload<E: EthSpec> {
#[superstruct(only(Deneb, Electra), partial_getter(copy))]
#[serde(with = "serde_utils::quoted_u64")]
pub excess_blob_gas: u64,
#[superstruct(only(Electra))]
pub deposit_receipts: VariableList<DepositReceipt, E::MaxDepositReceiptsPerPayload>,
#[superstruct(only(Electra))]
pub withdrawal_requests:
VariableList<ExecutionLayerWithdrawalRequest, E::MaxWithdrawalRequestsPerPayload>,
}
impl<'a, E: EthSpec> ExecutionPayloadRef<'a, E> {

View File

@@ -88,6 +88,10 @@ pub struct ExecutionPayloadHeader<E: EthSpec> {
#[serde(with = "serde_utils::quoted_u64")]
#[superstruct(getter(copy))]
pub excess_blob_gas: u64,
#[superstruct(only(Electra), partial_getter(copy))]
pub deposit_receipts_root: Hash256,
#[superstruct(only(Electra), partial_getter(copy))]
pub withdrawal_requests_root: Hash256,
}
impl<E: EthSpec> ExecutionPayloadHeader<E> {
@@ -206,6 +210,8 @@ impl<E: EthSpec> ExecutionPayloadHeaderDeneb<E> {
withdrawals_root: self.withdrawals_root,
blob_gas_used: self.blob_gas_used,
excess_blob_gas: self.excess_blob_gas,
deposit_receipts_root: Hash256::zero(),
withdrawal_requests_root: Hash256::zero(),
}
}
}
@@ -297,6 +303,8 @@ impl<'a, E: EthSpec> From<&'a ExecutionPayloadElectra<E>> for ExecutionPayloadHe
withdrawals_root: payload.withdrawals.tree_hash_root(),
blob_gas_used: payload.blob_gas_used,
excess_blob_gas: payload.excess_blob_gas,
deposit_receipts_root: payload.deposit_receipts.tree_hash_root(),
withdrawal_requests_root: payload.withdrawal_requests.tree_hash_root(),
}
}
}

View File

@@ -102,6 +102,11 @@ impl Validator {
.unwrap_or(false)
}
/// Check if ``validator`` has an 0x02 prefixed "compounding" withdrawal credential.
pub fn has_compounding_withdrawal_credential(&self, spec: &ChainSpec) -> bool {
is_compounding_withdrawal_credential(self.withdrawal_credentials, spec)
}
/// Get the eth1 withdrawal address if this validator has one initialized.
pub fn get_eth1_withdrawal_address(&self, spec: &ChainSpec) -> Option<Address> {
self.has_eth1_withdrawal_credential(spec)
@@ -153,6 +158,17 @@ impl Default for Validator {
}
}
pub fn is_compounding_withdrawal_credential(
withdrawal_credentials: Hash256,
spec: &ChainSpec,
) -> bool {
withdrawal_credentials
.as_bytes()
.first()
.map(|prefix_byte| *prefix_byte == spec.compounding_withdrawal_prefix_byte)
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;