Electra alpha8 spec updates (#6496)

* Fix partial withdrawals count

* Remove get_active_balance

* Remove queue_entire_balance_and_reset_validator

* Switch to compounding when consolidating with source==target

* Queue deposit requests and apply them during epoch processing

* Fix ef tests

* Clear todos

* Fix engine api formatting issues

* Merge branch 'unstable' into electra-alpha7

* Make add_validator_to_registry more in line with the spec

* Address some review comments

* Cleanup

* Update initialize_beacon_state_from_eth1

* Merge branch 'unstable' into electra-alpha7

* Fix rpc decoding for blobs by range/root

* Fix block hash computation

* Fix process_deposits bug

* Merge branch 'unstable' into electra-alpha7

* Fix topup deposit processing bug

* Update builder api for electra

* Refactor mock builder to separate functionality

* Merge branch 'unstable' into electra-alpha7

* Address review comments

* Use copied for reference rather than cloned

* Optimise and simplify PendingDepositsContext::new

* Merge remote-tracking branch 'origin/unstable' into electra-alpha7

* Fix processing of deposits with invalid signatures

* Remove redundant code in genesis init

* Revert "Refactor mock builder to separate functionality"

This reverts commit 6d10456912.

* Revert "Update builder api for electra"

This reverts commit c5c9aca6db.

* Simplify pre-activation sorting

* Fix stale validators used in upgrade_to_electra

* Merge branch 'unstable' into electra-alpha7
This commit is contained in:
Pawan Dhananjay
2024-12-17 07:44:24 +05:30
committed by GitHub
parent 02cb2d68ff
commit d74b2d96f5
25 changed files with 519 additions and 337 deletions

View File

@@ -509,7 +509,7 @@ where
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Electra))]
pub pending_balance_deposits: List<PendingBalanceDeposit, E::PendingBalanceDepositsLimit>,
pub pending_deposits: List<PendingDeposit, E::PendingDepositsLimit>,
#[compare_fields(as_iter)]
#[test_random(default)]
#[superstruct(only(Electra))]
@@ -1547,19 +1547,23 @@ impl<E: EthSpec> BeaconState<E> {
.ok_or(Error::UnknownValidator(validator_index))
}
/// Add a validator to the registry and return the validator index that was allocated for it.
pub fn add_validator_to_registry(
&mut self,
deposit_data: &DepositData,
pubkey: PublicKeyBytes,
withdrawal_credentials: Hash256,
amount: u64,
spec: &ChainSpec,
) -> Result<(), Error> {
let fork = self.fork_name_unchecked();
let amount = if fork.electra_enabled() {
0
} else {
deposit_data.amount
};
self.validators_mut()
.push(Validator::from_deposit(deposit_data, amount, fork, spec))?;
) -> Result<usize, Error> {
let index = self.validators().len();
let fork_name = self.fork_name_unchecked();
self.validators_mut().push(Validator::from_deposit(
pubkey,
withdrawal_credentials,
amount,
fork_name,
spec,
))?;
self.balances_mut().push(amount)?;
// Altair or later initializations.
@@ -1573,7 +1577,20 @@ impl<E: EthSpec> BeaconState<E> {
inactivity_scores.push(0)?;
}
Ok(())
// Keep the pubkey cache up to date if it was up to date prior to this call.
//
// Doing this here while we know the pubkey and index is marginally quicker than doing it in
// a call to `update_pubkey_cache` later because we don't need to index into the validators
// tree again.
let pubkey_cache = self.pubkey_cache_mut();
if pubkey_cache.len() == index {
let success = pubkey_cache.insert(pubkey, index);
if !success {
return Err(Error::PubkeyCacheInconsistent);
}
}
Ok(index)
}
/// Safe copy-on-write accessor for the `validators` list.
@@ -1780,19 +1797,6 @@ impl<E: EthSpec> BeaconState<E> {
}
}
/// Get the number of outstanding deposits.
///
/// Returns `Err` if the state is invalid.
pub fn get_outstanding_deposit_len(&self) -> Result<u64, Error> {
self.eth1_data()
.deposit_count
.checked_sub(self.eth1_deposit_index())
.ok_or(Error::InvalidDepositState {
deposit_count: self.eth1_data().deposit_count,
deposit_index: self.eth1_deposit_index(),
})
}
/// Build all caches (except the tree hash cache), if they need to be built.
pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> {
self.build_all_committee_caches(spec)?;
@@ -2149,27 +2153,6 @@ impl<E: EthSpec> BeaconState<E> {
.map_err(Into::into)
}
/// Get active balance for the given `validator_index`.
pub fn get_active_balance(
&self,
validator_index: usize,
spec: &ChainSpec,
current_fork: ForkName,
) -> Result<u64, Error> {
let max_effective_balance = self
.validators()
.get(validator_index)
.map(|validator| validator.get_max_effective_balance(spec, current_fork))
.ok_or(Error::UnknownValidator(validator_index))?;
Ok(std::cmp::min(
*self
.balances()
.get(validator_index)
.ok_or(Error::UnknownValidator(validator_index))?,
max_effective_balance,
))
}
pub fn get_pending_balance_to_withdraw(&self, validator_index: usize) -> Result<u64, Error> {
let mut pending_balance = 0;
for withdrawal in self
@@ -2196,42 +2179,18 @@ impl<E: EthSpec> BeaconState<E> {
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,
})?;
let validator = self.get_validator(validator_index)?.clone();
self.pending_deposits_mut()?.push(PendingDeposit {
pubkey: validator.pubkey,
withdrawal_credentials: validator.withdrawal_credentials,
amount: excess_balance,
signature: Signature::infinity()?.into(),
slot: spec.genesis_slot,
})?;
}
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)
}
/// Change the withdrawal prefix of the given `validator_index` to the compounding withdrawal validator prefix.
pub fn switch_to_compounding_validator(
&mut self,
@@ -2242,12 +2201,10 @@ impl<E: EthSpec> BeaconState<E> {
.validators_mut()
.get_mut(validator_index)
.ok_or(Error::UnknownValidator(validator_index))?;
if validator.has_eth1_withdrawal_credential(spec) {
AsMut::<[u8; 32]>::as_mut(&mut validator.withdrawal_credentials)[0] =
spec.compounding_withdrawal_prefix_byte;
AsMut::<[u8; 32]>::as_mut(&mut validator.withdrawal_credentials)[0] =
spec.compounding_withdrawal_prefix_byte;
self.queue_excess_active_balance(validator_index, spec)?;
}
self.queue_excess_active_balance(validator_index, spec)?;
Ok(())
}

View File

@@ -307,43 +307,6 @@ mod committees {
}
}
mod get_outstanding_deposit_len {
use super::*;
async fn state() -> BeaconState<MinimalEthSpec> {
get_harness(16, Slot::new(0))
.await
.chain
.head_beacon_state_cloned()
}
#[tokio::test]
async fn returns_ok() {
let mut state = state().await;
assert_eq!(state.get_outstanding_deposit_len(), Ok(0));
state.eth1_data_mut().deposit_count = 17;
*state.eth1_deposit_index_mut() = 16;
assert_eq!(state.get_outstanding_deposit_len(), Ok(1));
}
#[tokio::test]
async fn returns_err_if_the_state_is_invalid() {
let mut state = state().await;
// The state is invalid, deposit count is lower than deposit index.
state.eth1_data_mut().deposit_count = 16;
*state.eth1_deposit_index_mut() = 17;
assert_eq!(
state.get_outstanding_deposit_len(),
Err(BeaconStateError::InvalidDepositState {
deposit_count: 16,
deposit_index: 17,
})
);
}
}
#[test]
fn decode_base_and_altair() {
type E = MainnetEthSpec;

View File

@@ -1,5 +1,6 @@
use crate::test_utils::TestRandom;
use crate::{Hash256, PublicKeyBytes, Signature};
use crate::{Hash256, PublicKeyBytes};
use bls::SignatureBytes;
use serde::{Deserialize, Serialize};
use ssz::Encode;
use ssz_derive::{Decode, Encode};
@@ -10,7 +11,6 @@ use tree_hash_derive::TreeHash;
arbitrary::Arbitrary,
Debug,
PartialEq,
Eq,
Hash,
Clone,
Serialize,
@@ -25,7 +25,7 @@ pub struct DepositRequest {
pub withdrawal_credentials: Hash256,
#[serde(with = "serde_utils::quoted_u64")]
pub amount: u64,
pub signature: Signature,
pub signature: SignatureBytes,
#[serde(with = "serde_utils::quoted_u64")]
pub index: u64,
}
@@ -36,7 +36,7 @@ impl DepositRequest {
pubkey: PublicKeyBytes::empty(),
withdrawal_credentials: Hash256::ZERO,
amount: 0,
signature: Signature::empty(),
signature: SignatureBytes::empty(),
index: 0,
}
.as_ssz_bytes()

View File

@@ -151,7 +151,7 @@ pub trait EthSpec:
/*
* New in Electra
*/
type PendingBalanceDepositsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type PendingDepositsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type PendingPartialWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type PendingConsolidationsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type MaxConsolidationRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq;
@@ -159,6 +159,7 @@ pub trait EthSpec:
type MaxAttesterSlashingsElectra: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type MaxAttestationsElectra: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type MaxWithdrawalRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq;
type MaxPendingDepositsPerEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq;
fn default_spec() -> ChainSpec;
@@ -331,9 +332,9 @@ pub trait EthSpec:
.expect("Preset values are not configurable and never result in non-positive block body depth")
}
/// Returns the `PENDING_BALANCE_DEPOSITS_LIMIT` constant for this specification.
fn pending_balance_deposits_limit() -> usize {
Self::PendingBalanceDepositsLimit::to_usize()
/// Returns the `PENDING_DEPOSITS_LIMIT` constant for this specification.
fn pending_deposits_limit() -> usize {
Self::PendingDepositsLimit::to_usize()
}
/// Returns the `PENDING_PARTIAL_WITHDRAWALS_LIMIT` constant for this specification.
@@ -371,6 +372,11 @@ pub trait EthSpec:
Self::MaxWithdrawalRequestsPerPayload::to_usize()
}
/// Returns the `MAX_PENDING_DEPOSITS_PER_EPOCH` constant for this specification.
fn max_pending_deposits_per_epoch() -> usize {
Self::MaxPendingDepositsPerEpoch::to_usize()
}
fn kzg_commitments_inclusion_proof_depth() -> usize {
Self::KzgCommitmentsInclusionProofDepth::to_usize()
}
@@ -430,7 +436,7 @@ impl EthSpec for MainnetEthSpec {
type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch
type MaxBlsToExecutionChanges = U16;
type MaxWithdrawalsPerPayload = U16;
type PendingBalanceDepositsLimit = U134217728;
type PendingDepositsLimit = U134217728;
type PendingPartialWithdrawalsLimit = U134217728;
type PendingConsolidationsLimit = U262144;
type MaxConsolidationRequestsPerPayload = U1;
@@ -438,6 +444,7 @@ impl EthSpec for MainnetEthSpec {
type MaxAttesterSlashingsElectra = U1;
type MaxAttestationsElectra = U8;
type MaxWithdrawalRequestsPerPayload = U16;
type MaxPendingDepositsPerEpoch = U16;
fn default_spec() -> ChainSpec {
ChainSpec::mainnet()
@@ -500,7 +507,8 @@ impl EthSpec for MinimalEthSpec {
MaxBlsToExecutionChanges,
MaxBlobsPerBlock,
BytesPerFieldElement,
PendingBalanceDepositsLimit,
PendingDepositsLimit,
MaxPendingDepositsPerEpoch,
MaxConsolidationRequestsPerPayload,
MaxAttesterSlashingsElectra,
MaxAttestationsElectra
@@ -557,7 +565,7 @@ impl EthSpec for GnosisEthSpec {
type BytesPerFieldElement = U32;
type BytesPerBlob = U131072;
type KzgCommitmentInclusionProofDepth = U17;
type PendingBalanceDepositsLimit = U134217728;
type PendingDepositsLimit = U134217728;
type PendingPartialWithdrawalsLimit = U134217728;
type PendingConsolidationsLimit = U262144;
type MaxConsolidationRequestsPerPayload = U1;
@@ -565,6 +573,7 @@ impl EthSpec for GnosisEthSpec {
type MaxAttesterSlashingsElectra = U1;
type MaxAttestationsElectra = U8;
type MaxWithdrawalRequestsPerPayload = U16;
type MaxPendingDepositsPerEpoch = U16;
type FieldElementsPerCell = U64;
type FieldElementsPerExtBlob = U8192;
type BytesPerCell = U2048;

View File

@@ -52,9 +52,11 @@ pub struct ExecutionBlockHeader {
pub blob_gas_used: Option<u64>,
pub excess_blob_gas: Option<u64>,
pub parent_beacon_block_root: Option<Hash256>,
pub requests_root: Option<Hash256>,
}
impl ExecutionBlockHeader {
#[allow(clippy::too_many_arguments)]
pub fn from_payload<E: EthSpec>(
payload: ExecutionPayloadRef<E>,
rlp_empty_list_root: Hash256,
@@ -63,6 +65,7 @@ impl ExecutionBlockHeader {
rlp_blob_gas_used: Option<u64>,
rlp_excess_blob_gas: Option<u64>,
rlp_parent_beacon_block_root: Option<Hash256>,
rlp_requests_root: Option<Hash256>,
) -> Self {
// Most of these field mappings are defined in EIP-3675 except for `mixHash`, which is
// defined in EIP-4399.
@@ -87,6 +90,7 @@ impl ExecutionBlockHeader {
blob_gas_used: rlp_blob_gas_used,
excess_blob_gas: rlp_excess_blob_gas,
parent_beacon_block_root: rlp_parent_beacon_block_root,
requests_root: rlp_requests_root,
}
}
}
@@ -114,6 +118,7 @@ pub struct EncodableExecutionBlockHeader<'a> {
pub blob_gas_used: Option<u64>,
pub excess_blob_gas: Option<u64>,
pub parent_beacon_block_root: Option<&'a [u8]>,
pub requests_root: Option<&'a [u8]>,
}
impl<'a> From<&'a ExecutionBlockHeader> for EncodableExecutionBlockHeader<'a> {
@@ -139,6 +144,7 @@ impl<'a> From<&'a ExecutionBlockHeader> for EncodableExecutionBlockHeader<'a> {
blob_gas_used: header.blob_gas_used,
excess_blob_gas: header.excess_blob_gas,
parent_beacon_block_root: None,
requests_root: None,
};
if let Some(withdrawals_root) = &header.withdrawals_root {
encodable.withdrawals_root = Some(withdrawals_root.as_slice());
@@ -146,6 +152,9 @@ impl<'a> From<&'a ExecutionBlockHeader> for EncodableExecutionBlockHeader<'a> {
if let Some(parent_beacon_block_root) = &header.parent_beacon_block_root {
encodable.parent_beacon_block_root = Some(parent_beacon_block_root.as_slice())
}
if let Some(requests_root) = &header.requests_root {
encodable.requests_root = Some(requests_root.as_slice())
}
encodable
}
}

View File

@@ -1,7 +1,8 @@
use crate::test_utils::TestRandom;
use crate::{ConsolidationRequest, DepositRequest, EthSpec, WithdrawalRequest};
use crate::{ConsolidationRequest, DepositRequest, EthSpec, Hash256, WithdrawalRequest};
use alloy_primitives::Bytes;
use derivative::Derivative;
use ethereum_hashing::{DynamicContext, Sha256Context};
use serde::{Deserialize, Serialize};
use ssz::Encode;
use ssz_derive::{Decode, Encode};
@@ -47,6 +48,43 @@ impl<E: EthSpec> ExecutionRequests<E> {
let consolidation_bytes = Bytes::from(self.consolidations.as_ssz_bytes());
vec![deposit_bytes, withdrawal_bytes, consolidation_bytes]
}
/// Generate the execution layer `requests_hash` based on EIP-7685.
///
/// `sha256(sha256(requests_0) ++ sha256(requests_1) ++ ...)`
pub fn requests_hash(&self) -> Hash256 {
let mut hasher = DynamicContext::new();
for (i, request) in self.get_execution_requests_list().iter().enumerate() {
let mut request_hasher = DynamicContext::new();
request_hasher.update(&[i as u8]);
request_hasher.update(request);
let request_hash = request_hasher.finalize();
hasher.update(&request_hash);
}
hasher.finalize().into()
}
}
/// This is used to index into the `execution_requests` array.
#[derive(Debug, Copy, Clone)]
pub enum RequestPrefix {
Deposit,
Withdrawal,
Consolidation,
}
impl RequestPrefix {
pub fn from_prefix(prefix: u8) -> Option<Self> {
match prefix {
0 => Some(Self::Deposit),
1 => Some(Self::Withdrawal),
2 => Some(Self::Consolidation),
_ => None,
}
}
}
#[cfg(test)]

View File

@@ -54,8 +54,8 @@ pub mod light_client_finality_update;
pub mod light_client_optimistic_update;
pub mod light_client_update;
pub mod pending_attestation;
pub mod pending_balance_deposit;
pub mod pending_consolidation;
pub mod pending_deposit;
pub mod pending_partial_withdrawal;
pub mod proposer_preparation_data;
pub mod proposer_slashing;
@@ -170,7 +170,7 @@ pub use crate::execution_payload_header::{
ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderRef,
ExecutionPayloadHeaderRefMut,
};
pub use crate::execution_requests::ExecutionRequests;
pub use crate::execution_requests::{ExecutionRequests, RequestPrefix};
pub use crate::fork::Fork;
pub use crate::fork_context::ForkContext;
pub use crate::fork_data::ForkData;
@@ -210,8 +210,8 @@ pub use crate::payload::{
FullPayloadRef, OwnedExecPayload,
};
pub use crate::pending_attestation::PendingAttestation;
pub use crate::pending_balance_deposit::PendingBalanceDeposit;
pub use crate::pending_consolidation::PendingConsolidation;
pub use crate::pending_deposit::PendingDeposit;
pub use crate::pending_partial_withdrawal::PendingPartialWithdrawal;
pub use crate::preset::{
AltairPreset, BasePreset, BellatrixPreset, CapellaPreset, DenebPreset, ElectraPreset,

View File

@@ -1,4 +1,5 @@
use crate::test_utils::TestRandom;
use crate::*;
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
use test_random_derive::TestRandom;
@@ -8,7 +9,6 @@ use tree_hash_derive::TreeHash;
arbitrary::Arbitrary,
Debug,
PartialEq,
Eq,
Hash,
Clone,
Serialize,
@@ -18,16 +18,18 @@ use tree_hash_derive::TreeHash;
TreeHash,
TestRandom,
)]
pub struct PendingBalanceDeposit {
#[serde(with = "serde_utils::quoted_u64")]
pub index: u64,
pub struct PendingDeposit {
pub pubkey: PublicKeyBytes,
pub withdrawal_credentials: Hash256,
#[serde(with = "serde_utils::quoted_u64")]
pub amount: u64,
pub signature: SignatureBytes,
pub slot: Slot,
}
#[cfg(test)]
mod tests {
use super::*;
ssz_and_tree_hash_tests!(PendingBalanceDeposit);
ssz_and_tree_hash_tests!(PendingDeposit);
}

View File

@@ -263,7 +263,7 @@ impl ElectraPreset {
whistleblower_reward_quotient_electra: spec.whistleblower_reward_quotient_electra,
max_pending_partials_per_withdrawals_sweep: spec
.max_pending_partials_per_withdrawals_sweep,
pending_balance_deposits_limit: E::pending_balance_deposits_limit() as u64,
pending_balance_deposits_limit: E::pending_deposits_limit() as u64,
pending_partial_withdrawals_limit: E::pending_partial_withdrawals_limit() as u64,
pending_consolidations_limit: E::pending_consolidations_limit() as u64,
max_consolidation_requests_per_payload: E::max_consolidation_requests_per_payload()

View File

@@ -1,6 +1,6 @@
use crate::{
test_utils::TestRandom, Address, BeaconState, ChainSpec, Checkpoint, DepositData, Epoch,
EthSpec, FixedBytesExtended, ForkName, Hash256, PublicKeyBytes,
test_utils::TestRandom, Address, BeaconState, ChainSpec, Checkpoint, Epoch, EthSpec,
FixedBytesExtended, ForkName, Hash256, PublicKeyBytes,
};
use serde::{Deserialize, Serialize};
use ssz_derive::{Decode, Encode};
@@ -38,14 +38,15 @@ pub struct Validator {
impl Validator {
#[allow(clippy::arithmetic_side_effects)]
pub fn from_deposit(
deposit_data: &DepositData,
pubkey: PublicKeyBytes,
withdrawal_credentials: Hash256,
amount: u64,
fork_name: ForkName,
spec: &ChainSpec,
) -> Self {
let mut validator = Validator {
pubkey: deposit_data.pubkey,
withdrawal_credentials: deposit_data.withdrawal_credentials,
pubkey,
withdrawal_credentials,
activation_eligibility_epoch: spec.far_future_epoch,
activation_epoch: spec.far_future_epoch,
exit_epoch: spec.far_future_epoch,
@@ -291,16 +292,6 @@ impl Validator {
spec.max_effective_balance
}
}
pub fn get_active_balance(
&self,
validator_balance: u64,
spec: &ChainSpec,
current_fork: ForkName,
) -> u64 {
let max_effective_balance = self.get_max_effective_balance(spec, current_fork);
std::cmp::min(validator_balance, max_effective_balance)
}
}
impl Default for Validator {