From 7dac892c7163f1b4d58a362c3da24421865ec082 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Tue, 29 Jul 2025 20:14:48 -0700 Subject: [PATCH 01/16] eip-7732 containers --- .../types/src/builder_pending_payment.rs | 34 ++++++ .../types/src/builder_pending_withdrawal.rs | 38 +++++++ consensus/types/src/chain_spec.rs | 42 +++++++ consensus/types/src/eth_spec.rs | 31 +++++- consensus/types/src/execution_bid.rs | 47 ++++++++ .../types/src/execution_payload_envelope.rs | 103 ++++++++++++++++++ .../types/src/indexed_payload_attestation.rs | 41 +++++++ consensus/types/src/lib.rs | 27 ++++- consensus/types/src/payload_attestation.rs | 36 ++++++ .../types/src/payload_attestation_data.rs | 27 +++++ .../types/src/payload_attestation_message.rs | 33 ++++++ consensus/types/src/signed_execution_bid.rs | 42 +++++++ .../src/signed_execution_payload_envelope.rs | 91 ++++++++++++++++ 13 files changed, 589 insertions(+), 3 deletions(-) create mode 100644 consensus/types/src/builder_pending_payment.rs create mode 100644 consensus/types/src/builder_pending_withdrawal.rs create mode 100644 consensus/types/src/execution_bid.rs create mode 100644 consensus/types/src/execution_payload_envelope.rs create mode 100644 consensus/types/src/indexed_payload_attestation.rs create mode 100644 consensus/types/src/payload_attestation.rs create mode 100644 consensus/types/src/payload_attestation_data.rs create mode 100644 consensus/types/src/payload_attestation_message.rs create mode 100644 consensus/types/src/signed_execution_bid.rs create mode 100644 consensus/types/src/signed_execution_payload_envelope.rs diff --git a/consensus/types/src/builder_pending_payment.rs b/consensus/types/src/builder_pending_payment.rs new file mode 100644 index 00000000000..8fa062ddeaa --- /dev/null +++ b/consensus/types/src/builder_pending_payment.rs @@ -0,0 +1,34 @@ +use crate::test_utils::TestRandom; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct BuilderPendingPayment { + #[serde(with = "serde_utils::quoted_u64")] + pub weight: u64, + pub withdrawal: BuilderPendingWithdrawal, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(BuilderPendingPayment); +} diff --git a/consensus/types/src/builder_pending_withdrawal.rs b/consensus/types/src/builder_pending_withdrawal.rs new file mode 100644 index 00000000000..707f8fcdcb7 --- /dev/null +++ b/consensus/types/src/builder_pending_withdrawal.rs @@ -0,0 +1,38 @@ +use crate::test_utils::TestRandom; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + Debug, + PartialEq, + Eq, + Hash, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct BuilderPendingWithdrawal { + #[serde(with = "serde_utils::address_hex")] + pub fee_recipient: Address, + #[serde(with = "serde_utils::quoted_u64")] + pub amount: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub builder_index: u64, + pub withdrawable_epoch: Epoch, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(BuilderPendingWithdrawal); +} diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index 709b4f28fef..b2a93c24d8f 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -28,6 +28,8 @@ pub enum Domain { SyncCommittee, ContributionAndProof, SyncCommitteeSelectionProof, + BeaconBuilder, + PTCAttester, ApplicationMask(ApplicationDomain), } @@ -81,6 +83,7 @@ pub struct ChainSpec { pub bls_withdrawal_prefix_byte: u8, pub eth1_address_withdrawal_prefix_byte: u8, pub compounding_withdrawal_prefix_byte: u8, + pub builder_withdrawal_prefix_byte: u8, /* * Time parameters @@ -113,6 +116,8 @@ pub struct ChainSpec { pub(crate) domain_voluntary_exit: u32, pub(crate) domain_selection_proof: u32, pub(crate) domain_aggregate_and_proof: u32, + pub(crate) domain_beacon_builder: u32, + pub(crate) domain_ptc_attester: u32, /* * Fork choice @@ -214,6 +219,8 @@ pub struct ChainSpec { pub gloas_fork_version: [u8; 4], /// The Gloas fork epoch is optional, with `None` representing "Gloas never happens". pub gloas_fork_epoch: Option, + pub builder_payment_threshold_numerator: u64, + pub builder_payment_threshold_denominator: u64, /* * Networking @@ -513,6 +520,8 @@ impl ChainSpec { Domain::VoluntaryExit => self.domain_voluntary_exit, Domain::SelectionProof => self.domain_selection_proof, Domain::AggregateAndProof => self.domain_aggregate_and_proof, + Domain::BeaconBuilder => self.domain_beacon_builder, + Domain::PTCAttester => self.domain_ptc_attester, Domain::SyncCommittee => self.domain_sync_committee, Domain::ContributionAndProof => self.domain_contribution_and_proof, Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, @@ -922,6 +931,7 @@ impl ChainSpec { bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, compounding_withdrawal_prefix_byte: 0x02, + builder_withdrawal_prefix_byte: 0x03, /* * Time parameters @@ -955,6 +965,8 @@ impl ChainSpec { domain_voluntary_exit: 4, domain_selection_proof: 5, domain_aggregate_and_proof: 6, + domain_beacon_builder: 0x1B, + domain_ptc_attester: 0x0C, /* * Fork choice @@ -1075,6 +1087,8 @@ impl ChainSpec { */ gloas_fork_version: [0x07, 0x00, 0x00, 0x00], gloas_fork_epoch: None, + builder_payment_threshold_numerator: 6, + builder_payment_threshold_denominator: 10, /* * Network specific @@ -1268,6 +1282,7 @@ impl ChainSpec { bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, compounding_withdrawal_prefix_byte: 0x02, + builder_withdrawal_prefix_byte: 0x03, /* * Time parameters @@ -1301,6 +1316,8 @@ impl ChainSpec { domain_voluntary_exit: 4, domain_selection_proof: 5, domain_aggregate_and_proof: 6, + domain_beacon_builder: 0x1B, + domain_ptc_attester: 0x0C, /* * Fork choice @@ -1421,6 +1438,8 @@ impl ChainSpec { */ gloas_fork_version: [0x07, 0x00, 0x00, 0x64], gloas_fork_epoch: None, + builder_payment_threshold_numerator: 6, + builder_payment_threshold_denominator: 10, /* * Network specific @@ -1690,6 +1709,12 @@ pub struct Config { #[serde(serialize_with = "serialize_fork_epoch")] #[serde(deserialize_with = "deserialize_fork_epoch")] pub gloas_fork_epoch: Option>, + #[serde(default = "default_builder_payment_threshold_numerator")] + #[serde(with = "serde_utils::quoted_u64")] + pub builder_payment_threshold_numerator: u64, + #[serde(default = "default_builder_payment_threshold_denominator")] + #[serde(with = "serde_utils::quoted_u64")] + pub builder_payment_threshold_denominator: u64, #[serde(with = "serde_utils::quoted_u64")] seconds_per_slot: u64, @@ -1853,6 +1878,14 @@ fn default_gloas_fork_version() -> [u8; 4] { [0xff, 0xff, 0xff, 0xff] } +fn default_builder_payment_threshold_numerator() -> u64 { + 6 +} + +fn default_builder_payment_threshold_denominator() -> u64 { + 10 +} + /// Placeholder value: 2^256-2^10 (115792089237316195423570985008687907853269984665640564039457584007913129638912). /// /// Taken from https://github.com/ethereum/consensus-specs/blob/d5e4828aecafaf1c57ef67a5f23c4ae7b08c5137/configs/mainnet.yaml#L15-L16 @@ -2154,6 +2187,9 @@ impl Config { .gloas_fork_epoch .map(|epoch| MaybeQuoted { value: epoch }), + builder_payment_threshold_numerator: spec.builder_payment_threshold_numerator, + builder_payment_threshold_denominator: spec.builder_payment_threshold_denominator, + seconds_per_slot: spec.seconds_per_slot, seconds_per_eth1_block: spec.seconds_per_eth1_block, min_validator_withdrawability_delay: spec.min_validator_withdrawability_delay, @@ -2245,6 +2281,8 @@ impl Config { fulu_fork_version, gloas_fork_version, gloas_fork_epoch, + builder_payment_threshold_numerator, + builder_payment_threshold_denominator, seconds_per_slot, seconds_per_eth1_block, min_validator_withdrawability_delay, @@ -2318,6 +2356,8 @@ impl Config { fulu_fork_version, gloas_fork_version, gloas_fork_epoch: gloas_fork_epoch.map(|q| q.value), + builder_payment_threshold_numerator, + builder_payment_threshold_denominator, seconds_per_slot, seconds_per_eth1_block, min_validator_withdrawability_delay, @@ -2443,6 +2483,8 @@ mod tests { spec.domain_aggregate_and_proof, &spec, ); + test_domain(Domain::BeaconBuilder, spec.domain_beacon_builder, &spec); + test_domain(Domain::PTCAttester, spec.domain_ptc_attester, &spec); test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); // The builder domain index is zero diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index e9a291f6c7d..0f1e03a73dd 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -75,6 +75,7 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type EpochsPerSlashingsVector: Unsigned + Clone + Sync + Send + Debug + PartialEq; type HistoricalRootsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type ValidatorRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; /* * Max operations per block */ @@ -165,6 +166,12 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type MaxWithdrawalRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxPendingDepositsPerEpoch: Unsigned + Clone + Sync + Send + Debug + PartialEq; + /* + * New in Gloas + */ + type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + fn default_spec() -> ChainSpec; fn spec_name() -> EthSpecId; @@ -346,6 +353,11 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::PendingConsolidationsLimit::to_usize() } + /// Returns the `BUILDER_PENDING_WITHDRAWALS_LIMIT` constant for this specification. + fn builder_pending_withdrawals_limit() -> usize { + Self::BuilderPendingWithdrawalsLimit::to_usize() + } + /// Returns the `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` constant for this specification. fn max_consolidation_requests_per_payload() -> usize { Self::MaxConsolidationRequestsPerPayload::to_usize() @@ -391,6 +403,14 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + fn proposer_lookahead_slots() -> usize { Self::ProposerLookaheadSlots::to_usize() } + + fn ptc_size() -> usize { + Self::PTCSize::to_usize() + } + + fn max_payload_attestations() -> usize { + Self::MaxPayloadAttestations::to_usize() + } } /// Macro to inherit some type values from another EthSpec. @@ -420,6 +440,7 @@ impl EthSpec for MainnetEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; @@ -460,6 +481,8 @@ impl EthSpec for MainnetEthSpec { type MaxAttestationsElectra = U8; type MaxWithdrawalRequestsPerPayload = U16; type MaxPendingDepositsPerEpoch = U16; + type PTCSize = U64; // todo: verify if needs to be U512 for some reason like in Mark's OG implementation + type MaxPayloadAttestations = U2; fn default_spec() -> ChainSpec { ChainSpec::mainnet() @@ -511,6 +534,7 @@ impl EthSpec for MinimalEthSpec { GenesisEpoch, HistoricalRootsLimit, ValidatorRegistryLimit, + BuilderPendingWithdrawalsLimit, MaxProposerSlashings, MaxAttesterSlashings, MaxAttestations, @@ -530,7 +554,9 @@ impl EthSpec for MinimalEthSpec { MaxAttesterSlashingsElectra, MaxAttestationsElectra, MaxDepositRequestsPerPayload, - MaxWithdrawalRequestsPerPayload + MaxWithdrawalRequestsPerPayload, + PTCSize, + MaxPayloadAttestations }); fn default_spec() -> ChainSpec { @@ -561,6 +587,7 @@ impl EthSpec for GnosisEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; @@ -601,6 +628,8 @@ impl EthSpec for GnosisEthSpec { type CellsPerExtBlob = U128; type NumberOfColumns = U128; type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH + type PTCSize = U64; // todo: verify if needs to be U512 for some reason like in Mark's OG implementation + type MaxPayloadAttestations = U2; fn default_spec() -> ChainSpec { ChainSpec::gnosis() diff --git a/consensus/types/src/execution_bid.rs b/consensus/types/src/execution_bid.rs new file mode 100644 index 00000000000..9bbabf10ca8 --- /dev/null +++ b/consensus/types/src/execution_bid.rs @@ -0,0 +1,47 @@ +use crate::{test_utils::TestRandom, *}; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + Default, + Debug, + Clone, + Serialize, + Encode, + Decode, + Deserialize, + TreeHash, + Derivative, + TestRandom, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derivative(PartialEq, Hash)] +#[context_deserialize(ForkName)] +// This is what Potuz' spec calls an `ExecutionPayload` even though it's clearly a bid. +pub struct ExecutionBid { + pub parent_block_hash: ExecutionBlockHash, + pub parent_block_root: Hash256, + pub block_hash: ExecutionBlockHash, + #[serde(with = "serde_utils::address_hex")] + pub fee_recipient: Address, // todo(eip-7732): verify if this needs address_hex serialization + #[serde(with = "serde_utils::quoted_u64")] + pub gas_limit: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub builder_index: u64, + pub slot: Slot, + #[serde(with = "serde_utils::quoted_u64")] + pub value: u64, + pub blob_kzg_commitments_root: Hash256, +} + +impl SignedRoot for ExecutionBid {} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(ExecutionBid); +} diff --git a/consensus/types/src/execution_payload_envelope.rs b/consensus/types/src/execution_payload_envelope.rs new file mode 100644 index 00000000000..50a7ac97299 --- /dev/null +++ b/consensus/types/src/execution_payload_envelope.rs @@ -0,0 +1,103 @@ +use crate::test_utils::TestRandom; +use crate::*; +use beacon_block_body::KzgCommitments; +use derivative::Derivative; +use serde::de::{Deserializer, Error as _}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use superstruct::superstruct; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +// in all likelihood, this will be superstructed so might as well start early eh? +#[superstruct( + variants(Gloas, NextFork), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + Derivative + ), + cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary)), + derivative(PartialEq, Hash(bound = "E: EthSpec")), + serde(bound = "E: EthSpec", deny_unknown_fields), + cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec")) + ), + ref_attributes( + derive(Debug, PartialEq, TreeHash), + tree_hash(enum_behaviour = "transparent") + ), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive(Debug, Clone, Serialize, Encode, Deserialize, TreeHash, Derivative)] +#[derivative(PartialEq, Hash(bound = "E: EthSpec"))] +#[serde(bound = "E: EthSpec", untagged)] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] +pub struct ExecutionPayloadEnvelope { + #[superstruct(only(Gloas), partial_getter(rename = "payload_gloas"))] + pub payload: ExecutionPayloadGloas, + #[superstruct(only(NextFork), partial_getter(rename = "payload_next_fork"))] + pub payload: ExecutionPayloadGloas, + pub execution_requests: ExecutionRequests, + #[serde(with = "serde_utils::quoted_u64")] + #[superstruct(getter(copy))] + pub builder_index: u64, + #[superstruct(getter(copy))] + pub beacon_block_root: Hash256, + #[superstruct(getter(copy))] + pub slot: Slot, + pub blob_kzg_commitments: KzgCommitments, + #[superstruct(getter(copy))] + pub state_root: Hash256, +} + +impl<'a, E: EthSpec> SignedRoot for ExecutionPayloadEnvelopeRef<'a, E> {} + +impl<'a, E: EthSpec> ExecutionPayloadEnvelopeRef<'a, E> { + pub fn payload(&self) -> ExecutionPayloadRef<'a, E> { + match self { + Self::Gloas(envelope) => ExecutionPayloadRef::Gloas(&envelope.payload), + Self::NextFork(envelope) => ExecutionPayloadRef::Gloas(&envelope.payload), + } + } +} + +impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadEnvelope { + fn context_deserialize(deserializer: D, context: ForkName) -> Result + where + D: Deserializer<'de>, + { + let value: Self = serde::Deserialize::deserialize(deserializer)?; + + match (context, &value) { + (ForkName::Gloas, Self::Gloas { .. }) => Ok(value), + _ => Err(D::Error::custom(format!( + "ExecutionPayloadEnvelope does not support fork {context:?}" + ))), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + mod gloas { + use super::*; + ssz_and_tree_hash_tests!(ExecutionPayloadEnvelopeGloas); + } + + mod next_fork { + use super::*; + ssz_and_tree_hash_tests!(ExecutionPayloadEnvelopeNextFork); + } +} diff --git a/consensus/types/src/indexed_payload_attestation.rs b/consensus/types/src/indexed_payload_attestation.rs new file mode 100644 index 00000000000..646bff2d455 --- /dev/null +++ b/consensus/types/src/indexed_payload_attestation.rs @@ -0,0 +1,41 @@ +use crate::test_utils::TestRandom; +use crate::*; +use core::slice::Iter; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + TestRandom, + TreeHash, + Debug, + Clone, + PartialEq, + Encode, + Decode, + Serialize, + Deserialize, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] +#[context_deserialize(ForkName)] +pub struct IndexedPayloadAttestation { + pub attesting_indices: VariableList, + pub data: PayloadAttestationData, + pub signature: AggregateSignature, +} + +impl IndexedPayloadAttestation { + pub fn attesting_indices_iter(&self) -> Iter<'_, u64> { + self.attesting_indices.iter() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(IndexedPayloadAttestation); +} diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 8e83fed1d9a..89a73cb3f25 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -26,6 +26,8 @@ pub mod beacon_response; pub mod beacon_state; pub mod bls_to_execution_change; pub mod builder_bid; +pub mod builder_pending_payment; +pub mod builder_pending_withdrawal; pub mod chain_spec; pub mod checkpoint; pub mod consolidation_request; @@ -39,8 +41,10 @@ pub mod deposit_tree_snapshot; pub mod enr_fork_id; pub mod eth1_data; pub mod eth_spec; +pub mod execution_bid; pub mod execution_block_hash; pub mod execution_payload; +pub mod execution_payload_envelope; pub mod execution_payload_header; pub mod fork; pub mod fork_data; @@ -49,10 +53,15 @@ pub mod graffiti; pub mod historical_batch; pub mod historical_summary; pub mod indexed_attestation; +pub mod indexed_payload_attestation; pub mod light_client_bootstrap; pub mod light_client_finality_update; pub mod light_client_optimistic_update; pub mod light_client_update; +pub mod payload; +pub mod payload_attestation; +pub mod payload_attestation_data; +pub mod payload_attestation_message; pub mod pending_attestation; pub mod pending_consolidation; pub mod pending_deposit; @@ -67,6 +76,8 @@ pub mod signed_beacon_block; pub mod signed_beacon_block_header; pub mod signed_bls_to_execution_change; pub mod signed_contribution_and_proof; +pub mod signed_execution_bid; +pub mod signed_execution_payload_envelope; pub mod signed_voluntary_exit; pub mod signing_data; pub mod sync_committee_subscription; @@ -84,7 +95,6 @@ pub mod execution_block_header; pub mod execution_requests; pub mod fork_context; pub mod participation_flags; -pub mod payload; pub mod preset; pub mod slot_epoch; pub mod subnet_id; @@ -144,6 +154,8 @@ pub use crate::beacon_response::{ pub use crate::beacon_state::{Error as BeaconStateError, *}; pub use crate::blob_sidecar::{BlobIdentifier, BlobSidecar, BlobSidecarList, BlobsList}; pub use crate::bls_to_execution_change::BlsToExecutionChange; +pub use crate::builder_pending_payment::BuilderPendingPayment; +pub use crate::builder_pending_withdrawal::BuilderPendingWithdrawal; pub use crate::chain_spec::{ChainSpec, Config, Domain}; pub use crate::checkpoint::Checkpoint; pub use crate::config_and_preset::{ @@ -163,8 +175,9 @@ pub use crate::deposit_request::DepositRequest; pub use crate::deposit_tree_snapshot::{DepositTreeSnapshot, FinalizedExecutionBlock}; pub use crate::enr_fork_id::EnrForkId; pub use crate::epoch_cache::{EpochCache, EpochCacheError, EpochCacheKey}; -pub use crate::eth_spec::EthSpecId; pub use crate::eth1_data::Eth1Data; +pub use crate::eth_spec::EthSpecId; +pub use crate::execution_bid::ExecutionBid; pub use crate::execution_block_hash::ExecutionBlockHash; pub use crate::execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader}; pub use crate::execution_payload::{ @@ -172,6 +185,10 @@ pub use crate::execution_payload::{ ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadRef, Transaction, Transactions, Withdrawals, }; +pub use crate::execution_payload_envelope::{ + ExecutionPayloadEnvelope, ExecutionPayloadEnvelopeGloas, ExecutionPayloadEnvelopeNextFork, + ExecutionPayloadEnvelopeRef, +}; pub use crate::execution_payload_header::{ ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, @@ -187,6 +204,7 @@ pub use crate::historical_batch::HistoricalBatch; pub use crate::indexed_attestation::{ IndexedAttestation, IndexedAttestationBase, IndexedAttestationElectra, IndexedAttestationRef, }; +pub use crate::indexed_payload_attestation::IndexedPayloadAttestation; pub use crate::light_client_bootstrap::{ LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, LightClientBootstrapDeneb, LightClientBootstrapElectra, LightClientBootstrapFulu, @@ -220,6 +238,9 @@ pub use crate::payload::{ FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, FullPayloadGloas, FullPayloadRef, OwnedExecPayload, }; +pub use crate::payload_attestation::PayloadAttestation; +pub use crate::payload_attestation_data::PayloadAttestationData; +pub use crate::payload_attestation_message::PayloadAttestationMessage; pub use crate::pending_attestation::PendingAttestation; pub use crate::pending_consolidation::PendingConsolidation; pub use crate::pending_deposit::PendingDeposit; @@ -247,6 +268,8 @@ pub use crate::signed_beacon_block::{ pub use crate::signed_beacon_block_header::SignedBeaconBlockHeader; pub use crate::signed_bls_to_execution_change::SignedBlsToExecutionChange; pub use crate::signed_contribution_and_proof::SignedContributionAndProof; +pub use crate::signed_execution_bid::SignedExecutionBid; +pub use crate::signed_execution_payload_envelope::SignedExecutionPayloadEnvelope; pub use crate::signed_voluntary_exit::SignedVoluntaryExit; pub use crate::signing_data::{SignedRoot, SigningData}; pub use crate::slot_epoch::{Epoch, Slot}; diff --git a/consensus/types/src/payload_attestation.rs b/consensus/types/src/payload_attestation.rs new file mode 100644 index 00000000000..1cc8f32d816 --- /dev/null +++ b/consensus/types/src/payload_attestation.rs @@ -0,0 +1,36 @@ +use crate::test_utils::TestRandom; +use crate::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + TestRandom, + TreeHash, + Debug, + Clone, + Encode, + Decode, + Serialize, + Deserialize, + Derivative, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[serde(bound = "E: EthSpec", deny_unknown_fields)] +#[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] +#[derivative(PartialEq, Hash)] +#[context_deserialize(ForkName)] +pub struct PayloadAttestation { + pub aggregation_bits: BitList, + pub data: PayloadAttestationData, + pub signature: AggregateSignature, +} + +#[cfg(test)] +mod payload_attestation_tests { + use super::*; + + ssz_and_tree_hash_tests!(PayloadAttestation); +} diff --git a/consensus/types/src/payload_attestation_data.rs b/consensus/types/src/payload_attestation_data.rs new file mode 100644 index 00000000000..5962bfeb23b --- /dev/null +++ b/consensus/types/src/payload_attestation_data.rs @@ -0,0 +1,27 @@ +use crate::test_utils::TestRandom; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + TestRandom, TreeHash, Debug, Clone, PartialEq, Eq, Encode, Decode, Serialize, Deserialize, Hash, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct PayloadAttestationData { + pub beacon_block_root: Hash256, + pub slot: Slot, + pub payload_present: bool, +} +// todo(eip-7732): Mark's implementation has PayloadStatus as an enum, but spec calls for a bool. Need to clarify this. + +impl SignedRoot for PayloadAttestationData {} + +#[cfg(test)] +mod payload_attestation_data_tests { + use super::*; + + ssz_and_tree_hash_tests!(PayloadAttestationData); +} diff --git a/consensus/types/src/payload_attestation_message.rs b/consensus/types/src/payload_attestation_message.rs new file mode 100644 index 00000000000..87852e0ea2c --- /dev/null +++ b/consensus/types/src/payload_attestation_message.rs @@ -0,0 +1,33 @@ +use crate::test_utils::TestRandom; +use crate::*; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + TestRandom, + TreeHash, + Debug, + Clone, + PartialEq, + Encode, + Decode, + Serialize, + Deserialize, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[context_deserialize(ForkName)] +pub struct PayloadAttestationMessage { + #[serde(with = "serde_utils::quoted_u64")] + pub validator_index: u64, + pub data: PayloadAttestationData, + pub signature: AggregateSignature, +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(PayloadAttestationMessage); +} diff --git a/consensus/types/src/signed_execution_bid.rs b/consensus/types/src/signed_execution_bid.rs new file mode 100644 index 00000000000..945e96af389 --- /dev/null +++ b/consensus/types/src/signed_execution_bid.rs @@ -0,0 +1,42 @@ +use crate::test_utils::TestRandom; +use crate::*; +use derivative::Derivative; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[derive( + TestRandom, + TreeHash, + Debug, + Clone, + Encode, + Decode, + Serialize, + Deserialize, + Derivative, +)] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derivative(PartialEq, Hash)] +#[context_deserialize(ForkName)] +pub struct SignedExecutionBid { + pub message: ExecutionBid, + pub signature: Signature, +} + +impl SignedExecutionBid { + pub fn empty() -> Self { + Self { + message: ExecutionBid::default(), + signature: Signature::empty(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(SignedExecutionBid); +} diff --git a/consensus/types/src/signed_execution_payload_envelope.rs b/consensus/types/src/signed_execution_payload_envelope.rs new file mode 100644 index 00000000000..6d5c6d1c12a --- /dev/null +++ b/consensus/types/src/signed_execution_payload_envelope.rs @@ -0,0 +1,91 @@ +use crate::test_utils::TestRandom; +use crate::*; +use derivative::Derivative; +use serde::de::{Deserializer, Error as _}; +use serde::{Deserialize, Serialize}; +use ssz_derive::{Decode, Encode}; +use superstruct::superstruct; +use test_random_derive::TestRandom; +use tree_hash_derive::TreeHash; + +#[superstruct( + variants(Gloas, NextFork), + variant_attributes( + derive( + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, + Derivative + ), + cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary)), + derivative(PartialEq, Hash(bound = "E: EthSpec")), + serde(bound = "E: EthSpec", deny_unknown_fields), + cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec")) + ), + ref_attributes( + derive(Debug, PartialEq, TreeHash), + tree_hash(enum_behaviour = "transparent") + ), + cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") +)] +#[derive(Debug, Clone, Serialize, Encode, Deserialize, TreeHash, Derivative)] +#[derivative(PartialEq, Hash(bound = "E: EthSpec"))] +#[serde(bound = "E: EthSpec", untagged)] +#[ssz(enum_behaviour = "transparent")] +#[tree_hash(enum_behaviour = "transparent")] +pub struct SignedExecutionPayloadEnvelope { + #[superstruct(only(Gloas), partial_getter(rename = "message_gloas"))] + pub message: ExecutionPayloadEnvelopeGloas, + #[superstruct(only(NextFork), partial_getter(rename = "message_next_fork"))] + pub message: crate::execution_payload_envelope::ExecutionPayloadEnvelopeNextFork, + pub signature: Signature, +} + +impl SignedExecutionPayloadEnvelope { + pub fn message(&self) -> ExecutionPayloadEnvelopeRef { + match self { + Self::Gloas(ref signed) => ExecutionPayloadEnvelopeRef::Gloas(&signed.message), + Self::NextFork(ref signed) => ExecutionPayloadEnvelopeRef::NextFork(&signed.message), + } + } + + // todo(eip-7732): implement verify_signature since spec calls for verify_execution_payload_envelope_signature +} + +impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedExecutionPayloadEnvelope { + fn context_deserialize(deserializer: D, context: ForkName) -> Result + where + D: Deserializer<'de>, + { + let value: Self = Deserialize::deserialize(deserializer)?; + + match (context, &value) { + (ForkName::Gloas, Self::Gloas { .. }) => Ok(value), + _ => Err(D::Error::custom(format!( + "SignedExecutionPayloadEnvelope does not support fork {context:?}" + ))), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::MainnetEthSpec; + + mod gloas { + use super::*; + ssz_and_tree_hash_tests!(SignedExecutionPayloadEnvelopeGloas); + } + + mod next_fork { + use super::*; + ssz_and_tree_hash_tests!(SignedExecutionPayloadEnvelopeNextFork); + } +} From 55951bb45fc3a8b58ea67ea96cce4e98bc5e0b20 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Tue, 29 Jul 2025 20:34:03 -0700 Subject: [PATCH 02/16] updates per pr review --- consensus/types/src/eth_spec.rs | 6 +++--- consensus/types/src/execution_bid.rs | 13 ++----------- consensus/types/src/payload_attestation_data.rs | 1 - 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index 0f1e03a73dd..5b8a80290ae 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -75,7 +75,6 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type EpochsPerSlashingsVector: Unsigned + Clone + Sync + Send + Debug + PartialEq; type HistoricalRootsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type ValidatorRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; - type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; /* * Max operations per block */ @@ -171,6 +170,7 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + */ type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; fn default_spec() -> ChainSpec; @@ -481,7 +481,7 @@ impl EthSpec for MainnetEthSpec { type MaxAttestationsElectra = U8; type MaxWithdrawalRequestsPerPayload = U16; type MaxPendingDepositsPerEpoch = U16; - type PTCSize = U64; // todo: verify if needs to be U512 for some reason like in Mark's OG implementation + type PTCSize = U512; type MaxPayloadAttestations = U2; fn default_spec() -> ChainSpec { @@ -628,7 +628,7 @@ impl EthSpec for GnosisEthSpec { type CellsPerExtBlob = U128; type NumberOfColumns = U128; type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH - type PTCSize = U64; // todo: verify if needs to be U512 for some reason like in Mark's OG implementation + type PTCSize = U512; type MaxPayloadAttestations = U2; fn default_spec() -> ChainSpec { diff --git a/consensus/types/src/execution_bid.rs b/consensus/types/src/execution_bid.rs index 9bbabf10ca8..80f9d7684ae 100644 --- a/consensus/types/src/execution_bid.rs +++ b/consensus/types/src/execution_bid.rs @@ -6,16 +6,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[derive( - Default, - Debug, - Clone, - Serialize, - Encode, - Decode, - Deserialize, - TreeHash, - Derivative, - TestRandom, + Default, Debug, Clone, Serialize, Encode, Decode, Deserialize, TreeHash, Derivative, TestRandom, )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derivative(PartialEq, Hash)] @@ -26,7 +17,7 @@ pub struct ExecutionBid { pub parent_block_root: Hash256, pub block_hash: ExecutionBlockHash, #[serde(with = "serde_utils::address_hex")] - pub fee_recipient: Address, // todo(eip-7732): verify if this needs address_hex serialization + pub fee_recipient: Address, #[serde(with = "serde_utils::quoted_u64")] pub gas_limit: u64, #[serde(with = "serde_utils::quoted_u64")] diff --git a/consensus/types/src/payload_attestation_data.rs b/consensus/types/src/payload_attestation_data.rs index 5962bfeb23b..a25e6763920 100644 --- a/consensus/types/src/payload_attestation_data.rs +++ b/consensus/types/src/payload_attestation_data.rs @@ -15,7 +15,6 @@ pub struct PayloadAttestationData { pub slot: Slot, pub payload_present: bool, } -// todo(eip-7732): Mark's implementation has PayloadStatus as an enum, but spec calls for a bool. Need to clarify this. impl SignedRoot for PayloadAttestationData {} From bf5f891451fbbc7e9d6b499f02c6694c3f782059 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Wed, 30 Jul 2025 12:53:55 -0700 Subject: [PATCH 03/16] fix formatting --- consensus/types/src/builder_pending_payment.rs | 12 +----------- consensus/types/src/builder_pending_withdrawal.rs | 12 +----------- consensus/types/src/indexed_payload_attestation.rs | 12 +----------- consensus/types/src/payload_attestation.rs | 10 +--------- consensus/types/src/payload_attestation_message.rs | 12 +----------- consensus/types/src/signed_execution_bid.rs | 10 +--------- 6 files changed, 6 insertions(+), 62 deletions(-) diff --git a/consensus/types/src/builder_pending_payment.rs b/consensus/types/src/builder_pending_payment.rs index 8fa062ddeaa..f35fed2830c 100644 --- a/consensus/types/src/builder_pending_payment.rs +++ b/consensus/types/src/builder_pending_payment.rs @@ -6,17 +6,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[derive( - Debug, - PartialEq, - Eq, - Hash, - Clone, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - TestRandom, + Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[context_deserialize(ForkName)] diff --git a/consensus/types/src/builder_pending_withdrawal.rs b/consensus/types/src/builder_pending_withdrawal.rs index 707f8fcdcb7..53e4b784064 100644 --- a/consensus/types/src/builder_pending_withdrawal.rs +++ b/consensus/types/src/builder_pending_withdrawal.rs @@ -6,17 +6,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[derive( - Debug, - PartialEq, - Eq, - Hash, - Clone, - Serialize, - Deserialize, - Encode, - Decode, - TreeHash, - TestRandom, + Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[context_deserialize(ForkName)] diff --git a/consensus/types/src/indexed_payload_attestation.rs b/consensus/types/src/indexed_payload_attestation.rs index 646bff2d455..45b3de95bc7 100644 --- a/consensus/types/src/indexed_payload_attestation.rs +++ b/consensus/types/src/indexed_payload_attestation.rs @@ -6,17 +6,7 @@ use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; -#[derive( - TestRandom, - TreeHash, - Debug, - Clone, - PartialEq, - Encode, - Decode, - Serialize, - Deserialize, -)] +#[derive(TestRandom, TreeHash, Debug, Clone, PartialEq, Encode, Decode, Serialize, Deserialize)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[serde(bound = "E: EthSpec", deny_unknown_fields)] #[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] diff --git a/consensus/types/src/payload_attestation.rs b/consensus/types/src/payload_attestation.rs index 1cc8f32d816..48b0a3e3b3e 100644 --- a/consensus/types/src/payload_attestation.rs +++ b/consensus/types/src/payload_attestation.rs @@ -7,15 +7,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[derive( - TestRandom, - TreeHash, - Debug, - Clone, - Encode, - Decode, - Serialize, - Deserialize, - Derivative, + TestRandom, TreeHash, Debug, Clone, Encode, Decode, Serialize, Deserialize, Derivative, )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[serde(bound = "E: EthSpec", deny_unknown_fields)] diff --git a/consensus/types/src/payload_attestation_message.rs b/consensus/types/src/payload_attestation_message.rs index 87852e0ea2c..46179e1deac 100644 --- a/consensus/types/src/payload_attestation_message.rs +++ b/consensus/types/src/payload_attestation_message.rs @@ -5,17 +5,7 @@ use ssz_derive::{Decode, Encode}; use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; -#[derive( - TestRandom, - TreeHash, - Debug, - Clone, - PartialEq, - Encode, - Decode, - Serialize, - Deserialize, -)] +#[derive(TestRandom, TreeHash, Debug, Clone, PartialEq, Encode, Decode, Serialize, Deserialize)] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[context_deserialize(ForkName)] pub struct PayloadAttestationMessage { diff --git a/consensus/types/src/signed_execution_bid.rs b/consensus/types/src/signed_execution_bid.rs index 945e96af389..4538b9c2560 100644 --- a/consensus/types/src/signed_execution_bid.rs +++ b/consensus/types/src/signed_execution_bid.rs @@ -7,15 +7,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[derive( - TestRandom, - TreeHash, - Debug, - Clone, - Encode, - Decode, - Serialize, - Deserialize, - Derivative, + TestRandom, TreeHash, Debug, Clone, Encode, Decode, Serialize, Deserialize, Derivative, )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derivative(PartialEq, Hash)] From 064e8fc23cf2914043029487776e3e97c17ddd0c Mon Sep 17 00:00:00 2001 From: shane-moore Date: Wed, 30 Jul 2025 16:28:46 -0700 Subject: [PATCH 04/16] BeaconBlockBody updates per eip-7732 --- beacon_node/beacon_chain/src/beacon_chain.rs | 30 ++--- beacon_node/beacon_chain/src/errors.rs | 1 + beacon_node/beacon_chain/src/test_utils.rs | 19 --- .../src/engine_api/new_payload_request.rs | 13 +- beacon_node/execution_layer/src/lib.rs | 45 ++++++- .../src/test_utils/mock_builder.rs | 7 +- consensus/types/src/beacon_block.rs | 6 +- consensus/types/src/beacon_block_body.rs | 125 ++++++++++-------- consensus/types/src/signed_beacon_block.rs | 16 ++- 9 files changed, 143 insertions(+), 119 deletions(-) diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index d5a2929301e..ada7bc37ace 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5727,15 +5727,12 @@ impl BeaconChain { ) } BeaconState::Gloas(_) => { - let ( - payload, - kzg_commitments, - maybe_blobs_and_proofs, - maybe_requests, - execution_payload_value, - ) = block_contents - .ok_or(BlockProductionError::MissingExecutionPayload)? - .deconstruct(); + // Gloas blocks contain execution bids, not execution payloads + let block_proposal_contents = + block_contents.ok_or(BlockProductionError::MissingExecutionBid)?; + let (signed_execution_bid, payload_attestations) = block_proposal_contents + .into_execution_bid() + .map_err(|_| BlockProductionError::InvalidPayloadFork)?; ( BeaconBlock::Gloas(BeaconBlockGloas { @@ -5754,18 +5751,15 @@ impl BeaconChain { voluntary_exits: voluntary_exits.into(), sync_aggregate: sync_aggregate .ok_or(BlockProductionError::MissingSyncAggregate)?, - execution_payload: payload - .try_into() - .map_err(|_| BlockProductionError::InvalidPayloadFork)?, bls_to_execution_changes: bls_to_execution_changes.into(), - blob_kzg_commitments: kzg_commitments - .ok_or(BlockProductionError::InvalidPayloadFork)?, - execution_requests: maybe_requests - .ok_or(BlockProductionError::MissingExecutionRequests)?, + // EIP-7732: Use actual execution bid data + signed_execution_payload_header: signed_execution_bid.clone(), + payload_attestations, + _phantom: PhantomData, }, }), - maybe_blobs_and_proofs, - execution_payload_value, + None, // blob commitments moved to `ExecutionPayloadEnvelope` + Uint256::ZERO, // No execution payload value for Gloas blocks, just bids value ) } }; diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index a1a0ec74f66..16d0faa303d 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -292,6 +292,7 @@ pub enum BlockProductionError { MissingBlobs, MissingSyncAggregate, MissingExecutionPayload, + MissingExecutionBid, MissingKzgCommitment(String), TokioJoin(JoinError), BeaconChain(Box), diff --git a/beacon_node/beacon_chain/src/test_utils.rs b/beacon_node/beacon_chain/src/test_utils.rs index c9b84f25b8b..4737d07f2e7 100644 --- a/beacon_node/beacon_chain/src/test_utils.rs +++ b/beacon_node/beacon_chain/src/test_utils.rs @@ -3252,25 +3252,6 @@ pub fn generate_rand_block_and_blobs( message.body.blob_kzg_commitments = bundle.commitments.clone(); bundle } - SignedBeaconBlock::Gloas(SignedBeaconBlockGloas { - ref mut message, .. - }) => { - // Get either zero blobs or a random number of blobs between 1 and Max Blobs. - let payload: &mut FullPayloadGloas = &mut message.body.execution_payload; - let num_blobs = match num_blobs { - NumBlobs::Random => rng.random_range(1..=max_blobs), - NumBlobs::Number(n) => n, - NumBlobs::None => 0, - }; - let (bundle, transactions) = - execution_layer::test_utils::generate_blobs::(num_blobs, fork_name).unwrap(); - payload.execution_payload.transactions = <_>::default(); - for tx in Vec::from(transactions) { - payload.execution_payload.transactions.push(tx).unwrap(); - } - message.body.blob_kzg_commitments = bundle.commitments.clone(); - bundle - } _ => return (block, blob_sidecars), }; diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index aa5261c80b0..9f5d38327e3 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -177,7 +177,7 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> fn try_from(block: BeaconBlockRef<'a, E>) -> Result { match block { - BeaconBlockRef::Base(_) | BeaconBlockRef::Altair(_) => { + BeaconBlockRef::Base(_) | BeaconBlockRef::Altair(_) | BeaconBlockRef::Gloas(_) => { Err(Self::Error::IncorrectStateVariant) } BeaconBlockRef::Bellatrix(block_ref) => { @@ -220,17 +220,6 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), - BeaconBlockRef::Gloas(block_ref) => Ok(Self::Gloas(NewPayloadRequestGloas { - execution_payload: &block_ref.body.execution_payload.execution_payload, - versioned_hashes: block_ref - .body - .blob_kzg_commitments - .iter() - .map(kzg_commitment_to_versioned_hash) - .collect(), - parent_beacon_block_root: block_ref.parent_root, - execution_requests: &block_ref.body.execution_requests, - })), } } } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index b53c4cde4e6..796c34ffe05 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -51,7 +51,7 @@ use types::non_zero_usize::new_non_zero_usize; use types::payload::BlockProductionVersion; use types::{ AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, ExecutionRequests, KzgProofs, - SignedBlindedBeaconBlock, + PayloadAttestation, SignedBlindedBeaconBlock, SignedExecutionBid, }; use types::{ BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix, @@ -219,6 +219,11 @@ pub enum BlockProposalContents> { // See: https://github.com/sigp/lighthouse/issues/6981 requests: Option>, }, + /// EIP-7732: Execution bid and payload attestations for Gloas fork + BidAndPayloadAttestations { + signed_execution_bid: SignedExecutionBid, + payload_attestations: VariableList, E::MaxPayloadAttestations>, + }, } impl From>> @@ -246,6 +251,13 @@ impl From>> blobs_and_proofs: None, requests, }, + BlockProposalContents::BidAndPayloadAttestations { + signed_execution_bid, + payload_attestations, + } => BlockProposalContents::BidAndPayloadAttestations { + signed_execution_bid, + payload_attestations, + }, } } } @@ -313,6 +325,28 @@ impl> BlockProposalContents { + panic!("Cannot deconstruct BidAndPayloadAttestations variant into execution payload components") + } + } + } + + /// Extract execution bid data for EIP-7732 Gloas blocks + pub fn into_execution_bid( + self, + ) -> Result< + ( + SignedExecutionBid, + VariableList, E::MaxPayloadAttestations>, + ), + &'static str, + > { + match self { + Self::BidAndPayloadAttestations { + signed_execution_bid, + payload_attestations, + } => Ok((signed_execution_bid, payload_attestations)), + _ => Err("Cannot extract execution bid from non-BidAndPayloadAttestations variant"), } } @@ -320,18 +354,27 @@ impl> BlockProposalContents payload, Self::PayloadAndBlobs { payload, .. } => payload, + Self::BidAndPayloadAttestations { .. } => { + panic!("BidAndPayloadAttestations variant does not contain execution payload") + } } } pub fn to_payload(self) -> Payload { match self { Self::Payload { payload, .. } => payload, Self::PayloadAndBlobs { payload, .. } => payload, + Self::BidAndPayloadAttestations { .. } => { + panic!("BidAndPayloadAttestations variant does not contain execution payload") + } } } pub fn block_value(&self) -> &Uint256 { match self { Self::Payload { block_value, .. } => block_value, Self::PayloadAndBlobs { block_value, .. } => block_value, + Self::BidAndPayloadAttestations { .. } => { + panic!("BidAndPayloadAttestations variant does not have block_value") + } } } } diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 516662b1d64..79ee1473e4a 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -464,7 +464,9 @@ impl MockBuilder { block: SignedBlindedBeaconBlock, ) -> Result, String> { let root = match &block { - SignedBlindedBeaconBlock::Base(_) | types::SignedBeaconBlock::Altair(_) => { + SignedBlindedBeaconBlock::Base(_) + | SignedBlindedBeaconBlock::Altair(_) + | SignedBlindedBeaconBlock::Gloas(_) => { return Err("invalid fork".to_string()); } SignedBlindedBeaconBlock::Bellatrix(block) => { @@ -482,9 +484,6 @@ impl MockBuilder { SignedBlindedBeaconBlock::Fulu(block) => { block.message.body.execution_payload.tree_hash_root() } - SignedBlindedBeaconBlock::Gloas(block) => { - block.message.body.execution_payload.tree_hash_root() - } }; info!( block_hash = %root, diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index f4e4e369661..62401ebefef 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -672,10 +672,10 @@ impl> EmptyBlock for BeaconBlockGloa deposits: VariableList::empty(), voluntary_exits: VariableList::empty(), sync_aggregate: SyncAggregate::empty(), - execution_payload: Payload::Gloas::default(), bls_to_execution_changes: VariableList::empty(), - blob_kzg_commitments: VariableList::empty(), - execution_requests: ExecutionRequests::default(), + signed_execution_payload_header: SignedExecutionBid::empty(), + payload_attestations: VariableList::empty(), + _phantom: PhantomData, }, } } diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 73d02dbe614..75fd303dfcc 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -61,7 +61,11 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; 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))))), - Gloas(metastruct(mappings(beacon_block_body_gloas_fields(groups(fields))))), +// we relax the trait bounds for gloas since it doesn't contain a Payload + Gloas( + derivative(PartialEq, Hash(bound = "E: EthSpec")), + metastruct(mappings(beacon_block_body_gloas_fields(groups(fields)))) + ), ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") @@ -127,17 +131,21 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] #[serde(flatten)] pub execution_payload: Payload::Fulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] - #[serde(flatten)] - pub execution_payload: Payload::Gloas, + // execution_payload removed from Gloas, replaced with signed_execution_payload_header below #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] pub bls_to_execution_changes: VariableList, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + // blob_kzg_commitments removed from Gloas, moved to `ExecutionPayloadEnvelope` + #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, - #[superstruct(only(Electra, Fulu, Gloas))] + // execution_requests removed from Gloas, moved to `ExecutionPayloadEnvelope` + #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, - #[superstruct(only(Base, Altair))] + #[superstruct(only(Gloas))] + pub signed_execution_payload_header: SignedExecutionBid, + #[superstruct(only(Gloas))] + pub payload_attestations: VariableList, E::MaxPayloadAttestations>, + #[superstruct(only(Base, Altair, Gloas))] #[metastruct(exclude_from(fields))] #[ssz(skip_serializing, skip_deserializing)] #[tree_hash(skip_hashing)] @@ -160,13 +168,12 @@ impl> BeaconBlockBody { impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Payload> { pub fn execution_payload(&self) -> Result, Error> { match self { - Self::Base(_) | Self::Altair(_) => Err(Error::IncorrectStateVariant), + Self::Base(_) | Self::Altair(_) | Self::Gloas(_) => Err(Error::IncorrectStateVariant), Self::Bellatrix(body) => Ok(Payload::Ref::from(&body.execution_payload)), 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)), - Self::Gloas(body) => Ok(Payload::Ref::from(&body.execution_payload)), } } @@ -230,10 +237,12 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, kzg_commitments_proof: &[Hash256], ) -> Result, Error> { match self { - Self::Base(_) | Self::Altair(_) | Self::Bellatrix(_) | Self::Capella(_) => { - Err(Error::IncorrectStateVariant) - } - Self::Deneb(_) | Self::Electra(_) | Self::Fulu(_) | Self::Gloas(_) => { + Self::Base(_) + | Self::Altair(_) + | Self::Bellatrix(_) + | Self::Capella(_) + | Self::Gloas(_) => Err(Error::IncorrectStateVariant), + 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 @@ -512,6 +521,45 @@ impl From>> } } +// Post-Fulu block bodies without payloads can be converted into block bodies with payloads +impl From>> + for BeaconBlockBodyGloas> +{ + fn from(body: BeaconBlockBodyGloas>) -> Self { + let BeaconBlockBodyGloas { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + signed_execution_payload_header, + payload_attestations, + _phantom, + } = body; + + BeaconBlockBodyGloas { + randao_reveal, + eth1_data, + graffiti, + proposer_slashings, + attester_slashings, + attestations, + deposits, + voluntary_exits, + sync_aggregate, + bls_to_execution_changes, + signed_execution_payload_header, + payload_attestations, + _phantom: PhantomData, + } + } +} + // Likewise bodies with payloads can be transformed into bodies without. impl From>> for ( @@ -822,10 +870,10 @@ impl From>> deposits, voluntary_exits, sync_aggregate, - execution_payload: FullPayloadGloas { execution_payload }, bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, + signed_execution_payload_header, + payload_attestations, + _phantom, } = body; ( @@ -839,14 +887,12 @@ impl From>> deposits, voluntary_exits, sync_aggregate, - execution_payload: BlindedPayloadGloas { - execution_payload_header: From::from(&execution_payload), - }, bls_to_execution_changes, - blob_kzg_commitments: blob_kzg_commitments.clone(), - execution_requests, + signed_execution_payload_header, + payload_attestations, + _phantom: PhantomData, }, - Some(execution_payload), + None, ) } } @@ -1046,39 +1092,8 @@ impl BeaconBlockBodyFulu> { impl BeaconBlockBodyGloas> { pub fn clone_as_blinded(&self) -> BeaconBlockBodyGloas> { - let BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings, - attester_slashings, - attestations, - deposits, - voluntary_exits, - sync_aggregate, - execution_payload: FullPayloadGloas { execution_payload }, - bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, - } = self; - - BeaconBlockBodyGloas { - 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: BlindedPayloadGloas { - 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(), - } + let (block_body, _payload) = self.clone().into(); + block_body } } diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index 979b91e30d5..53d34a43519 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -6,6 +6,7 @@ use merkle_proof::MerkleTree; use serde::{Deserialize, Deserializer, Serialize}; use ssz_derive::{Decode, Encode}; use std::fmt; +use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -648,10 +649,11 @@ impl SignedBeaconBlockFulu> { } } +/// Gloas doesn't support BlindedPayload, but we keep this impl for compatibility purposes of the SignedBeaconBlock type impl SignedBeaconBlockGloas> { pub fn into_full_block( self, - execution_payload: ExecutionPayloadGloas, + _execution_payload: ExecutionPayloadGloas, ) -> SignedBeaconBlockGloas> { let SignedBeaconBlockGloas { message: @@ -671,10 +673,10 @@ impl SignedBeaconBlockGloas> { deposits, voluntary_exits, sync_aggregate, - execution_payload: BlindedPayloadGloas { .. }, bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, + signed_execution_payload_header, + payload_attestations, + .. }, }, signature, @@ -695,10 +697,10 @@ impl SignedBeaconBlockGloas> { deposits, voluntary_exits, sync_aggregate, - execution_payload: FullPayloadGloas { execution_payload }, bls_to_execution_changes, - blob_kzg_commitments, - execution_requests, + signed_execution_payload_header, + payload_attestations, + _phantom: PhantomData, }, }, signature, From 4be0b3aaab56daf1fd9998386282c224cb50dba9 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Mon, 4 Aug 2025 17:46:34 -0700 Subject: [PATCH 05/16] BeaconState and BeaconBlockBody mods --- beacon_node/beacon_chain/src/beacon_chain.rs | 7 +- beacon_node/execution_layer/src/engine_api.rs | 28 ----- .../src/engine_api/new_payload_request.rs | 8 +- beacon_node/execution_layer/src/lib.rs | 18 ++- .../test_utils/execution_block_generator.rs | 8 +- .../src/test_utils/mock_builder.rs | 70 ++++-------- beacon_node/store/src/partial_beacon_state.rs | 39 +++++-- consensus/state_processing/src/genesis.rs | 5 +- .../src/per_block_processing.rs | 90 +++------------ .../process_withdrawals.rs | 105 +++++++++++++++++ .../state_processing/src/upgrade/gloas.rs | 17 ++- consensus/types/src/beacon_block.rs | 27 ++++- consensus/types/src/beacon_block_body.rs | 23 ++-- consensus/types/src/beacon_state.rs | 64 ++++++++--- consensus/types/src/builder_bid.rs | 18 +-- .../types/src/builder_pending_payment.rs | 13 ++- .../types/src/builder_pending_withdrawal.rs | 13 ++- consensus/types/src/dumb_macros.rs | 107 ++++++++++++++++++ consensus/types/src/eth_spec.rs | 4 + consensus/types/src/execution_payload.rs | 5 +- .../types/src/execution_payload_header.rs | 100 +++------------- consensus/types/src/lib.rs | 18 ++- consensus/types/src/light_client_bootstrap.rs | 43 +++---- .../types/src/light_client_finality_update.rs | 45 +++----- consensus/types/src/light_client_header.rs | 75 ++---------- .../src/light_client_optimistic_update.rs | 37 ++---- consensus/types/src/light_client_update.rs | 65 +++-------- consensus/types/src/payload.rs | 86 +++++--------- consensus/types/src/signed_beacon_block.rs | 67 ++--------- testing/ef_tests/src/cases/operations.rs | 12 +- 30 files changed, 580 insertions(+), 637 deletions(-) create mode 100644 consensus/state_processing/src/per_block_processing/process_withdrawals.rs create mode 100644 consensus/types/src/dumb_macros.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index ada7bc37ace..539f7500618 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5726,6 +5726,9 @@ impl BeaconChain { execution_payload_value, ) } + // Below is my attempt at handling the Gloas variant + // Note that Mark's implementation had this as: + // BeaconState::EIP7732(_) => todo!("EIP-7732 block production"), BeaconState::Gloas(_) => { // Gloas blocks contain execution bids, not execution payloads let block_proposal_contents = @@ -5752,8 +5755,8 @@ impl BeaconChain { sync_aggregate: sync_aggregate .ok_or(BlockProductionError::MissingSyncAggregate)?, bls_to_execution_changes: bls_to_execution_changes.into(), - // EIP-7732: Use actual execution bid data - signed_execution_payload_header: signed_execution_bid.clone(), + // Gloas: Use actual execution bid data + signed_execution_bid: signed_execution_bid.clone(), payload_attestations, _phantom: PhantomData, }, diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 98da7dbf2c7..c0314935e13 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -541,34 +541,6 @@ impl ExecutionPayloadBodyV1 { )) } } - ExecutionPayloadHeader::Gloas(header) => { - if let Some(withdrawals) = self.withdrawals { - Ok(ExecutionPayload::Gloas(ExecutionPayloadGloas { - parent_hash: header.parent_hash, - fee_recipient: header.fee_recipient, - state_root: header.state_root, - receipts_root: header.receipts_root, - logs_bloom: header.logs_bloom, - prev_randao: header.prev_randao, - block_number: header.block_number, - gas_limit: header.gas_limit, - gas_used: header.gas_used, - timestamp: header.timestamp, - extra_data: header.extra_data, - base_fee_per_gas: header.base_fee_per_gas, - block_hash: header.block_hash, - transactions: self.transactions, - withdrawals, - blob_gas_used: header.blob_gas_used, - excess_blob_gas: header.excess_blob_gas, - })) - } else { - Err(format!( - "block {} is post capella but payload body doesn't have withdrawals", - header.block_hash - )) - } - } } } } diff --git a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs index 9f5d38327e3..64ecd7f0e03 100644 --- a/beacon_node/execution_layer/src/engine_api/new_payload_request.rs +++ b/beacon_node/execution_layer/src/engine_api/new_payload_request.rs @@ -172,12 +172,13 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { } } +//TODO(EIP7732): Consider implmenting these as methods on the NewPayloadRequest struct impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> { type Error = BeaconStateError; fn try_from(block: BeaconBlockRef<'a, E>) -> Result { match block { - BeaconBlockRef::Base(_) | BeaconBlockRef::Altair(_) | BeaconBlockRef::Gloas(_) => { + BeaconBlockRef::Base(_) | BeaconBlockRef::Altair(_) => { Err(Self::Error::IncorrectStateVariant) } BeaconBlockRef::Bellatrix(block_ref) => { @@ -220,6 +221,7 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<'a, E> parent_beacon_block_root: block_ref.parent_root, execution_requests: &block_ref.body.execution_requests, })), + BeaconBlockRef::Gloas(_) => Err(Self::Error::IncorrectStateVariant), } } } @@ -240,11 +242,15 @@ impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest<' ExecutionPayloadRef::Deneb(_) => Err(Self::Error::IncorrectStateVariant), ExecutionPayloadRef::Electra(_) => Err(Self::Error::IncorrectStateVariant), ExecutionPayloadRef::Fulu(_) => Err(Self::Error::IncorrectStateVariant), + //TODO(EIP7732): Probably time to just get rid of this ExecutionPayloadRef::Gloas(_) => Err(Self::Error::IncorrectStateVariant), } } } +// TODO(EIP-7732) build out the following when it's needed like in Mark's branch +// impl<'a, E: EthSpec> TryFrom> for NewPayloadRequest { + #[cfg(test)] mod test { use crate::versioned_hashes::Error as VersionedHashError; diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 796c34ffe05..28b190a6e77 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -55,8 +55,8 @@ use types::{ }; use types::{ BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix, - ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, - FullPayload, ProposerPreparationData, PublicKeyBytes, Signature, Slot, + ExecutionPayloadCapella, ExecutionPayloadElectra, ExecutionPayloadFulu, FullPayload, + ProposerPreparationData, PublicKeyBytes, Signature, Slot, }; mod block_hash; @@ -131,13 +131,6 @@ impl TryFrom> for ProvenancedPayload BlockProposalContents::PayloadAndBlobs { - payload: ExecutionPayloadHeader::Gloas(builder_bid.header).into(), - block_value: builder_bid.value, - kzg_commitments: builder_bid.blob_kzg_commitments, - blobs_and_proofs: None, - requests: Some(builder_bid.execution_requests), - }, }; Ok(ProvenancedPayload::Builder( BlockProposalContentsType::Blinded(block_proposal_contents), @@ -219,7 +212,7 @@ pub enum BlockProposalContents> { // See: https://github.com/sigp/lighthouse/issues/6981 requests: Option>, }, - /// EIP-7732: Execution bid and payload attestations for Gloas fork + /// Gloas: Execution bid and payload attestations BidAndPayloadAttestations { signed_execution_bid: SignedExecutionBid, payload_attestations: VariableList, E::MaxPayloadAttestations>, @@ -1399,6 +1392,7 @@ impl ExecutionLayer { } /// Maps to the `engine_newPayload` JSON-RPC call. + /// TODO(EIP-7732) figure out how and why Mark relaxed new_payload_request param's typ to NewPayloadRequest pub async fn notify_new_payload( &self, new_payload_request: NewPayloadRequest<'_, E>, @@ -1870,10 +1864,12 @@ impl ExecutionLayer { ForkName::Deneb => ExecutionPayloadDeneb::default().into(), ForkName::Electra => ExecutionPayloadElectra::default().into(), ForkName::Fulu => ExecutionPayloadFulu::default().into(), - ForkName::Gloas => ExecutionPayloadGloas::default().into(), ForkName::Base | ForkName::Altair => { return Err(Error::InvalidForkForPayload); } + ForkName::Gloas => { + return Err(Error::InvalidForkForPayload); + } }; return Ok(Some(payload)); } diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 4836f9307c8..587c381e5ff 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -908,12 +908,8 @@ pub fn generate_genesis_header( *header.transactions_root_mut() = empty_transactions_root; Some(header) } - ForkName::Gloas => { - let mut header = ExecutionPayloadHeader::Gloas(<_>::default()); - *header.block_hash_mut() = genesis_block_hash.unwrap_or_default(); - *header.transactions_root_mut() = empty_transactions_root; - Some(header) - } + // TODO(EIP-7732): need to look into this + ForkName::Gloas => None, } } diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index 79ee1473e4a..eef5811f901 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -27,7 +27,7 @@ use tracing::{debug, error, info, warn}; use tree_hash::TreeHash; use types::builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, - BuilderBidFulu, BuilderBidGloas, SignedBuilderBid, + BuilderBidFulu, SignedBuilderBid, }; use types::{ Address, BeaconState, ChainSpec, Epoch, EthSpec, ExecPayload, ExecutionPayload, @@ -115,9 +115,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.fee_recipient = fee_recipient; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.fee_recipient = fee_recipient; - } } } @@ -138,9 +135,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.gas_limit = gas_limit; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.gas_limit = gas_limit; - } } } @@ -165,9 +159,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.parent_hash = ExecutionBlockHash::from_root(parent_hash); } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.parent_hash = ExecutionBlockHash::from_root(parent_hash); - } } } @@ -188,9 +179,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.prev_randao = prev_randao; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.prev_randao = prev_randao; - } } } @@ -211,9 +199,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.block_number = block_number; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.block_number = block_number; - } } } @@ -234,9 +219,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.timestamp = timestamp; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.timestamp = timestamp; - } } } @@ -257,9 +239,6 @@ impl BidStuff for BuilderBid { ExecutionPayloadHeaderRefMut::Fulu(header) => { header.withdrawals_root = withdrawals_root; } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.withdrawals_root = withdrawals_root; - } } } @@ -293,10 +272,6 @@ impl BidStuff for BuilderBid { header.extra_data = extra_data; header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root()); } - ExecutionPayloadHeaderRefMut::Gloas(header) => { - header.extra_data = extra_data; - header.block_hash = ExecutionBlockHash::from_root(header.tree_hash_root()); - } } } } @@ -464,9 +439,7 @@ impl MockBuilder { block: SignedBlindedBeaconBlock, ) -> Result, String> { let root = match &block { - SignedBlindedBeaconBlock::Base(_) - | SignedBlindedBeaconBlock::Altair(_) - | SignedBlindedBeaconBlock::Gloas(_) => { + SignedBlindedBeaconBlock::Base(_) | SignedBlindedBeaconBlock::Altair(_) => { return Err("invalid fork".to_string()); } SignedBlindedBeaconBlock::Bellatrix(block) => { @@ -484,6 +457,10 @@ impl MockBuilder { SignedBlindedBeaconBlock::Fulu(block) => { block.message.body.execution_payload.tree_hash_root() } + SignedBlindedBeaconBlock::Gloas(_) => { + // TODO(EIP7732) Check if this is how we want to do error handling for gloas + return Err("invalid fork".to_string()); + } }; info!( block_hash = %root, @@ -567,18 +544,10 @@ impl MockBuilder { ) = payload_response.into(); match fork { - ForkName::Gloas => BuilderBid::Gloas(BuilderBidGloas { - header: payload - .as_gloas() - .map_err(|_| "incorrect payload variant".to_string())? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments.clone()) - .unwrap_or_default(), - value: self.get_bid_value(value), - pubkey: self.builder_sk.public_key().compress(), - execution_requests: maybe_requests.unwrap_or_default(), - }), + ForkName::Gloas => { + // TODO(EIP7732) Check if this is how we want to do error handling for gloas + return Err("invalid fork".to_string()); + } ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { header: payload .as_fulu() @@ -877,6 +846,10 @@ impl MockBuilder { // first to avoid polluting the execution block generator with invalid payload attributes // NOTE: this was part of an effort to add payload attribute uniqueness checks, // which was abandoned because it broke too many tests in subtle ways. + ForkName::Gloas => { + // TODO(EIP7732) Check if this is how we want to do error handling for gloas + return Err("invalid fork".to_string()); + } ForkName::Bellatrix | ForkName::Capella => PayloadAttributes::new( timestamp, *prev_randao, @@ -884,16 +857,11 @@ impl MockBuilder { expected_withdrawals, None, ), - ForkName::Deneb | ForkName::Electra | ForkName::Fulu | ForkName::Gloas => { - PayloadAttributes::new( - timestamp, - *prev_randao, - fee_recipient, - expected_withdrawals, - Some(head_block_root), - ) - } - ForkName::Base | ForkName::Altair => { + ForkName::Deneb + | ForkName::Electra + | ForkName::Fulu + | ForkName::Base + | ForkName::Altair => { return Err("invalid fork".to_string()); } }; diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index 13b0dfab9f7..fe5fd2b0399 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -115,11 +115,9 @@ where partial_getter(rename = "latest_execution_payload_header_fulu") )] pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, - #[superstruct( - only(Gloas), - partial_getter(rename = "latest_execution_payload_header_gloas") - )] - pub latest_execution_payload_header: ExecutionPayloadHeaderGloas, + + #[superstruct(only(Gloas), partial_getter(rename = "latest_execution_bid_gloas"))] + pub latest_execution_bid: ExecutionBid, // Capella #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] @@ -154,6 +152,27 @@ where pub pending_consolidations: List, #[superstruct(only(Fulu, Gloas))] pub proposer_lookahead: Vector, + + // Gloas + // Gloas + #[superstruct(only(Gloas))] + pub execution_payload_availability: BitVector, + + #[superstruct(only(Gloas))] + pub builder_pending_payments: Vector, + + #[superstruct(only(Gloas))] + pub builder_pending_withdrawals: + List, + + #[superstruct(only(Gloas))] + pub latest_block_hash: ExecutionBlockHash, + + #[superstruct(only(Gloas))] + pub latest_full_slot: Slot, + + #[superstruct(only(Gloas))] + pub latest_withdrawals_root: Hash256, } impl PartialBeaconState { @@ -465,7 +484,7 @@ impl TryInto> for PartialBeaconState { current_sync_committee, next_sync_committee, inactivity_scores, - latest_execution_payload_header, + latest_execution_bid, next_withdrawal_index, next_withdrawal_validator_index, deposit_requests_start_index, @@ -477,7 +496,13 @@ impl TryInto> for PartialBeaconState { pending_deposits, pending_partial_withdrawals, pending_consolidations, - proposer_lookahead + proposer_lookahead, + execution_payload_availability, + builder_pending_payments, + builder_pending_withdrawals, + latest_block_hash, + latest_full_slot, + latest_withdrawals_root ], [historical_summaries] ), diff --git a/consensus/state_processing/src/genesis.rs b/consensus/state_processing/src/genesis.rs index 88ef79310dc..b8657676fd8 100644 --- a/consensus/state_processing/src/genesis.rs +++ b/consensus/state_processing/src/genesis.rs @@ -167,9 +167,8 @@ pub fn initialize_beacon_state_from_eth1( state.fork_mut().previous_version = spec.gloas_fork_version; // Override latest execution payload header. - if let Some(ExecutionPayloadHeader::Gloas(header)) = execution_payload_header { - *state.latest_execution_payload_header_gloas_mut()? = header.clone(); - } + // Here's where we *would* clone the header but there is no header here so.. + // TODO(EIP7732): check this } // Now that we have our validators, initialize the caches (including the committees) diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 99abbef9c1e..e8473fa6a07 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -30,6 +30,7 @@ pub mod deneb; pub mod errors; mod is_valid_indexed_attestation; pub mod process_operations; +pub mod process_withdrawals; pub mod signature_sets; pub mod tests; mod verify_attestation; @@ -39,8 +40,6 @@ mod verify_deposit; mod verify_exit; mod verify_proposer_slashing; -use crate::common::decrease_balance; - use crate::common::update_progressive_balances_cache::{ initialize_progressive_balances_cache, update_progressive_balances_metrics, }; @@ -172,10 +171,21 @@ pub fn per_block_processing>( // previous block. if is_execution_enabled(state, block.body()) { let body = block.body(); - process_withdrawals::(state, body.execution_payload()?, spec)?; - process_execution_payload::(state, body, spec)?; + if state.fork_name_unchecked().gloas_enabled() { + process_withdrawals::gloas::process_withdrawals::(state, spec)?; + } else { + process_withdrawals::capella::process_withdrawals::( + state, + body.execution_payload()?, + spec, + )?; + process_execution_payload::(state, body, spec)?; + } } + // TODO(EIP-7732): build out process_execution_bid + // process_execution_bid(state, block, verify_signatures, spec)?; + process_randao(state, block, verify_randao, ctxt, spec)?; process_eth1_data(state, block.body().eth1_data())?; process_operations(state, block.body(), verify_signatures, ctxt, spec)?; @@ -452,12 +462,6 @@ pub fn process_execution_payload>( _ => return Err(BlockProcessingError::IncorrectStateType), } } - ExecutionPayloadHeaderRefMut::Gloas(header_mut) => { - match payload.to_execution_payload_header() { - ExecutionPayloadHeader::Gloas(header) => *header_mut = header, - _ => return Err(BlockProcessingError::IncorrectStateType), - } - } } Ok(()) @@ -469,6 +473,7 @@ pub fn process_execution_payload>( /// 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(state: &BeaconState) -> bool { + // TODO(EIP7732): check this cause potuz modified this function for god knows what reason if state.fork_name_unchecked().capella_enabled() { true } else if state.fork_name_unchecked().bellatrix_enabled() { @@ -630,68 +635,3 @@ pub fn get_expected_withdrawals( Ok((withdrawals.into(), processed_partial_withdrawals_count)) } - -/// Apply withdrawals to the state. -pub fn process_withdrawals>( - state: &mut BeaconState, - payload: Payload::Ref<'_>, - spec: &ChainSpec, -) -> Result<(), BlockProcessingError> { - if state.fork_name_unchecked().capella_enabled() { - let (expected_withdrawals, processed_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, - }); - } - - 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(processed_partial_withdrawals_count) = processed_partial_withdrawals_count { - state - .pending_partial_withdrawals_mut()? - .pop_front(processed_partial_withdrawals_count)?; - } - - // 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)? - .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 - Ok(()) - } -} diff --git a/consensus/state_processing/src/per_block_processing/process_withdrawals.rs b/consensus/state_processing/src/per_block_processing/process_withdrawals.rs new file mode 100644 index 00000000000..562fcb0385b --- /dev/null +++ b/consensus/state_processing/src/per_block_processing/process_withdrawals.rs @@ -0,0 +1,105 @@ +use super::errors::BlockProcessingError; +use super::get_expected_withdrawals; +use crate::common::decrease_balance; +use safe_arith::SafeArith; +use tree_hash::TreeHash; +use types::{AbstractExecPayload, BeaconState, ChainSpec, EthSpec, ExecPayload, Withdrawals}; + +fn process_withdrawals_common( + state: &mut BeaconState, + expected_withdrawals: Withdrawals, + partial_withdrawals_count: Option, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + match state { + BeaconState::Capella(_) + | BeaconState::Deneb(_) + | BeaconState::Electra(_) + | BeaconState::Fulu(_) + | BeaconState::Gloas(_) => { + 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 { + state + .pending_partial_withdrawals_mut()? + .pop_front(partial_withdrawals_count)?; + } + + // 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)? + .safe_rem(state.validators().len() as u64)?; + *state.next_withdrawal_validator_index_mut()? = next_validator_index; + } + + Ok(()) + } + // these shouldn't even be encountered but they're here for completeness + BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()), + } +} + +pub mod capella { + use super::*; + /// Apply withdrawals to the state. + pub fn process_withdrawals>( + state: &mut BeaconState, + payload: Payload::Ref<'_>, + spec: &ChainSpec, + ) -> Result<(), BlockProcessingError> { + 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, + }); + } + + process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec) + } +} + +pub mod gloas { + use super::*; + /// Apply withdrawals to the state. + pub fn process_withdrawals( + state: &mut BeaconState, + spec: &ChainSpec, + ) -> Result<(), BlockProcessingError> { + if !state.is_parent_block_full() { + return Ok(()); + } + + let (expected_withdrawals, partial_withdrawals_count) = + get_expected_withdrawals(state, spec)?; + process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec) + } +} diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index 8bb6991bfbe..c2ec8b76006 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -1,5 +1,9 @@ +use bls::Hash256; use std::mem; -use types::{BeaconState, BeaconStateError as Error, BeaconStateGloas, ChainSpec, EthSpec, Fork}; +use types::{ + BeaconState, BeaconStateError as Error, BeaconStateGloas, BitVector, ChainSpec, EthSpec, + ExecutionBid, Fork, List, Vector, +}; /// Transform a `Fulu` state into a `Gloas` state. pub fn upgrade_to_gloas( @@ -63,8 +67,8 @@ pub fn upgrade_state_to_gloas( // 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_gloas(), + // Execution Bid + latest_execution_bid: ExecutionBid::default(), // Capella next_withdrawal_index: pre.next_withdrawal_index, next_withdrawal_validator_index: pre.next_withdrawal_validator_index, @@ -79,6 +83,13 @@ pub fn upgrade_state_to_gloas( pending_deposits: pre.pending_deposits.clone(), pending_partial_withdrawals: pre.pending_partial_withdrawals.clone(), pending_consolidations: pre.pending_consolidations.clone(), + // Gloas + execution_payload_availability: BitVector::default(), // All bits set to false initially + builder_pending_payments: Vector::default(), // Empty vector initially, + builder_pending_withdrawals: List::default(), // Empty list initially, + latest_block_hash: pre.latest_execution_payload_header.block_hash, + latest_full_slot: pre.slot, + latest_withdrawals_root: Hash256::default(), // Caches total_active_balance: pre.total_active_balance, progressive_balances_cache: mem::take(&mut pre.progressive_balances_cache), diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 62401ebefef..1483c493516 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -673,7 +673,7 @@ impl> EmptyBlock for BeaconBlockGloa voluntary_exits: VariableList::empty(), sync_aggregate: SyncAggregate::empty(), bls_to_execution_changes: VariableList::empty(), - signed_execution_payload_header: SignedExecutionBid::empty(), + signed_execution_bid: SignedExecutionBid::empty(), payload_attestations: VariableList::empty(), _phantom: PhantomData, }, @@ -681,6 +681,31 @@ impl> EmptyBlock for BeaconBlockGloa } } +/// TODO(EIP-7732) Mark's branch had the following implementation but not sure if it's needed so will just add header below for reference +// impl> BeaconBlockEIP7732 { + +impl From>> + for BeaconBlockGloas> +{ + fn from(block: BeaconBlockGloas>) -> Self { + let BeaconBlockGloas { + slot, + proposer_index, + parent_root, + state_root, + body, + } = block; + + BeaconBlockGloas { + slot, + proposer_index, + parent_root, + state_root, + body: body.into(), + } + } +} + // We can convert pre-Bellatrix blocks without payloads into blocks "with" payloads. impl From>> for BeaconBlockBase> diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 75fd303dfcc..ecbbf63b145 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -61,11 +61,7 @@ pub const BLOB_KZG_COMMITMENTS_INDEX: usize = 11; 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))))), -// we relax the trait bounds for gloas since it doesn't contain a Payload - Gloas( - derivative(PartialEq, Hash(bound = "E: EthSpec")), - metastruct(mappings(beacon_block_body_gloas_fields(groups(fields)))) - ), + Gloas(metastruct(mappings(beacon_block_body_gloas_fields(groups(fields))))), ), cast_error(ty = "Error", expr = "Error::IncorrectStateVariant"), partial_getter_error(ty = "Error", expr = "Error::IncorrectStateVariant") @@ -131,7 +127,7 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] #[serde(flatten)] pub execution_payload: Payload::Fulu, - // execution_payload removed from Gloas, replaced with signed_execution_payload_header below + // execution_payload removed from Gloas, replaced with signed_execution_bid below #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] pub bls_to_execution_changes: VariableList, @@ -142,7 +138,7 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, #[superstruct(only(Gloas))] - pub signed_execution_payload_header: SignedExecutionBid, + pub signed_execution_bid: SignedExecutionBid, #[superstruct(only(Gloas))] pub payload_attestations: VariableList, E::MaxPayloadAttestations>, #[superstruct(only(Base, Altair, Gloas))] @@ -168,12 +164,14 @@ impl> BeaconBlockBody { impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, Payload> { pub fn execution_payload(&self) -> Result, Error> { match self { - Self::Base(_) | Self::Altair(_) | Self::Gloas(_) => Err(Error::IncorrectStateVariant), + Self::Base(_) | Self::Altair(_) => Err(Error::IncorrectStateVariant), Self::Bellatrix(body) => Ok(Payload::Ref::from(&body.execution_payload)), 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)), + // TODO(eip-7732): idk if this is right there's no more execution payload + Self::Gloas(_) => Err(Error::IncorrectStateVariant), } } @@ -242,6 +240,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, | Self::Bellatrix(_) | Self::Capella(_) | Self::Gloas(_) => Err(Error::IncorrectStateVariant), + // TODO(eip-7732): Mark's impl had the Self::EIP-7732 variant below, but I think it should produce error instead since no self.blob_kzg_commitments in BeaconState for gloas 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 @@ -537,7 +536,7 @@ impl From>> voluntary_exits, sync_aggregate, bls_to_execution_changes, - signed_execution_payload_header, + signed_execution_bid, payload_attestations, _phantom, } = body; @@ -553,7 +552,7 @@ impl From>> voluntary_exits, sync_aggregate, bls_to_execution_changes, - signed_execution_payload_header, + signed_execution_bid, payload_attestations, _phantom: PhantomData, } @@ -871,7 +870,7 @@ impl From>> voluntary_exits, sync_aggregate, bls_to_execution_changes, - signed_execution_payload_header, + signed_execution_bid, payload_attestations, _phantom, } = body; @@ -888,7 +887,7 @@ impl From>> voluntary_exits, sync_aggregate, bls_to_execution_changes, - signed_execution_payload_header, + signed_execution_bid, payload_attestations, _phantom: PhantomData, }, diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 5f3dff56cc8..279f2eff181 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -516,14 +516,9 @@ where )] #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderFulu, - #[superstruct( - only(Gloas), - partial_getter(rename = "latest_execution_payload_header_gloas") - )] + #[superstruct(only(Gloas))] #[metastruct(exclude_from(tree_lists))] - pub latest_execution_payload_header: ExecutionPayloadHeaderGloas, - - // Capella + pub latest_execution_bid: ExecutionBid, #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] #[metastruct(exclude_from(tree_lists))] @@ -581,6 +576,36 @@ where pub proposer_lookahead: Vector, // Gloas + #[test_random(default)] + #[superstruct(only(Gloas))] + #[metastruct(exclude_from(tree_lists))] + pub execution_payload_availability: BitVector, + + #[compare_fields(as_iter)] + #[test_random(default)] + #[superstruct(only(Gloas))] + pub builder_pending_payments: Vector, + + #[compare_fields(as_iter)] + #[test_random(default)] + #[superstruct(only(Gloas))] + pub builder_pending_withdrawals: + List, + + #[test_random(default)] + #[superstruct(only(Gloas))] + #[metastruct(exclude_from(tree_lists))] + pub latest_block_hash: ExecutionBlockHash, + + #[test_random(default)] + #[superstruct(only(Gloas))] + #[metastruct(exclude_from(tree_lists))] + pub latest_full_slot: Slot, + + #[test_random(default)] + #[superstruct(only(Gloas))] + #[metastruct(exclude_from(tree_lists))] + pub latest_withdrawals_root: Hash256, // Caching (not in the spec) #[serde(skip_serializing, skip_deserializing)] @@ -1071,9 +1096,8 @@ impl BeaconState { BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRef::Fulu( &state.latest_execution_payload_header, )), - BeaconState::Gloas(state) => Ok(ExecutionPayloadHeaderRef::Gloas( - &state.latest_execution_payload_header, - )), + // FIXME(EIP-7732): this is only to make the code compile, needs to be written later + BeaconState::Gloas(_) => Err(Error::IncorrectStateVariant), } } @@ -1097,9 +1121,8 @@ impl BeaconState { BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRefMut::Fulu( &mut state.latest_execution_payload_header, )), - BeaconState::Gloas(state) => Ok(ExecutionPayloadHeaderRefMut::Gloas( - &mut state.latest_execution_payload_header, - )), + // FIXME(EIP-7732): this is only to make the code compile, needs to be written later + BeaconState::Gloas(_) => Err(Error::IncorrectStateVariant), } } @@ -1836,6 +1859,7 @@ impl BeaconState { | BeaconState::Altair(_) | BeaconState::Bellatrix(_) | BeaconState::Capella(_) => self.get_validator_churn_limit(spec)?, + // FIXME(EIP-7732): check this BeaconState::Deneb(_) | BeaconState::Electra(_) | BeaconState::Fulu(_) @@ -2116,6 +2140,20 @@ impl BeaconState { } } + pub fn is_parent_block_full(&self) -> bool { + match self { + BeaconState::Base(_) | BeaconState::Altair(_) => false, + BeaconState::Bellatrix(_) + | BeaconState::Capella(_) + | BeaconState::Deneb(_) + | BeaconState::Electra(_) + | BeaconState::Fulu(_) => true, + BeaconState::Gloas(state) => { + state.latest_execution_bid.block_hash == state.latest_block_hash + } + } + } + /// Get the committee cache for some `slot`. /// /// Return an error if the cache for the slot's epoch is not initialized. diff --git a/consensus/types/src/builder_bid.rs b/consensus/types/src/builder_bid.rs index 3fb7af35ca1..55ad6af6901 100644 --- a/consensus/types/src/builder_bid.rs +++ b/consensus/types/src/builder_bid.rs @@ -2,7 +2,7 @@ use crate::beacon_block_body::KzgCommitments; use crate::{ ChainSpec, ContextDeserialize, EthSpec, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, - ExecutionPayloadHeaderFulu, ExecutionPayloadHeaderGloas, ExecutionPayloadHeaderRef, + ExecutionPayloadHeaderFulu, ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, ForkVersionDecode, SignedRoot, Uint256, test_utils::TestRandom, }; @@ -16,7 +16,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( PartialEq, @@ -49,11 +49,9 @@ pub struct BuilderBid { pub header: ExecutionPayloadHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))] pub header: ExecutionPayloadHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "header_gloas"))] - pub header: ExecutionPayloadHeaderGloas, - #[superstruct(only(Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, - #[superstruct(only(Electra, Fulu, Gloas))] + #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, #[serde(with = "serde_utils::quoted_u256")] pub value: Uint256, @@ -86,7 +84,7 @@ impl ForkVersionDecode for BuilderBid { /// SSZ decode with explicit fork variant. fn from_ssz_bytes_by_fork(bytes: &[u8], fork_name: ForkName) -> Result { let builder_bid = match fork_name { - ForkName::Altair | ForkName::Base => { + ForkName::Altair | ForkName::Base | ForkName::Gloas => { return Err(ssz::DecodeError::BytesInvalid(format!( "unsupported fork for ExecutionPayloadHeader: {fork_name}", ))); @@ -98,7 +96,6 @@ impl ForkVersionDecode for BuilderBid { ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb::from_ssz_bytes(bytes)?), ForkName::Electra => BuilderBid::Electra(BuilderBidElectra::from_ssz_bytes(bytes)?), ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => BuilderBid::Gloas(BuilderBidGloas::from_ssz_bytes(bytes)?), }; Ok(builder_bid) } @@ -154,10 +151,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for BuilderBid { ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) - } - ForkName::Base | ForkName::Altair => { + ForkName::Base | ForkName::Altair | ForkName::Gloas => { return Err(serde::de::Error::custom(format!( "BuilderBid failed to deserialize: unsupported fork '{}'", context diff --git a/consensus/types/src/builder_pending_payment.rs b/consensus/types/src/builder_pending_payment.rs index f35fed2830c..0beb53ac29f 100644 --- a/consensus/types/src/builder_pending_payment.rs +++ b/consensus/types/src/builder_pending_payment.rs @@ -6,7 +6,18 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[derive( - Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, + Debug, + PartialEq, + Eq, + Hash, + Clone, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[context_deserialize(ForkName)] diff --git a/consensus/types/src/builder_pending_withdrawal.rs b/consensus/types/src/builder_pending_withdrawal.rs index 53e4b784064..951b9cad58d 100644 --- a/consensus/types/src/builder_pending_withdrawal.rs +++ b/consensus/types/src/builder_pending_withdrawal.rs @@ -6,7 +6,18 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[derive( - Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Encode, Decode, TreeHash, TestRandom, + Debug, + PartialEq, + Eq, + Hash, + Clone, + Default, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + TestRandom, )] #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[context_deserialize(ForkName)] diff --git a/consensus/types/src/dumb_macros.rs b/consensus/types/src/dumb_macros.rs new file mode 100644 index 00000000000..29bf3fb506c --- /dev/null +++ b/consensus/types/src/dumb_macros.rs @@ -0,0 +1,107 @@ +// These would usually be created by superstuct but now there's no longer a 1:1 mapping between +// the variants for ExecutionPayload and the variants for +// - ExecutionPayloadHeader +// - FullPayload +// - BlindedPayload + +#[macro_export] +macro_rules! map_execution_payload_into_full_payload { + ($value:expr, $f:expr) => { + match $value { + ExecutionPayload::Bellatrix(inner) => { + let f: fn(ExecutionPayloadBellatrix<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Bellatrix) + } + ExecutionPayload::Capella(inner) => { + let f: fn(ExecutionPayloadCapella<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Capella) + } + ExecutionPayload::Deneb(inner) => { + let f: fn(ExecutionPayloadDeneb<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Deneb) + } + ExecutionPayload::Electra(inner) => { + let f: fn(ExecutionPayloadElectra<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Electra) + } + ExecutionPayload::Fulu(inner) => { + let f: fn(ExecutionPayloadFulu<_>, fn(_) -> _) -> _ = $f; + f(inner, FullPayload::Fulu) + } + ExecutionPayload::Gloas(_) => panic!("FullPayload::Gloas does not exist!"), + } + }; +} + +#[macro_export] +macro_rules! map_execution_payload_into_blinded_payload { + ($value:expr, $f:expr) => { + match $value { + ExecutionPayload::Bellatrix(inner) => { + let f: fn(ExecutionPayloadBellatrix<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Bellatrix) + } + ExecutionPayload::Capella(inner) => { + let f: fn(ExecutionPayloadCapella<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Capella) + } + ExecutionPayload::Deneb(inner) => { + let f: fn(ExecutionPayloadDeneb<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Deneb) + } + ExecutionPayload::Electra(inner) => { + let f: fn(ExecutionPayloadElectra<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Electra) + } + ExecutionPayload::Fulu(inner) => { + let f: fn(ExecutionPayloadFulu<_>, fn(_) -> _) -> _ = $f; + f(inner, BlindedPayload::Fulu) + } + ExecutionPayload::Gloas(_) => panic!("BlindedPayload::Gloas does not exist!"), + } + }; +} + +#[macro_export] +macro_rules! map_execution_payload_ref_into_execution_payload_header { + (&$lifetime:tt _, $value:expr, $f:expr) => { + match $value { + ExecutionPayloadRef::Bellatrix(inner) => { + let f: fn( + &$lifetime ExecutionPayloadBellatrix<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Bellatrix) + } + ExecutionPayloadRef::Capella(inner) => { + let f: fn( + &$lifetime ExecutionPayloadCapella<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Capella) + } + ExecutionPayloadRef::Deneb(inner) => { + let f: fn( + &$lifetime ExecutionPayloadDeneb<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Deneb) + } + ExecutionPayloadRef::Electra(inner) => { + let f: fn( + &$lifetime ExecutionPayloadElectra<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Electra) + } + ExecutionPayloadRef::Fulu(inner) => { + let f: fn( + &$lifetime ExecutionPayloadFulu<_>, + fn(_) -> _, + ) -> _ = $f; + f(inner, ExecutionPayloadHeader::Fulu) + } + ExecutionPayloadRef::Gloas(_) => panic!("ExecutionPayloadHeader::Gloas does not exist!"), + } + } +} diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index 5b8a80290ae..9aa111c30e6 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -170,6 +170,7 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + */ type PTCSize: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type BuilderPendingPaymentsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; fn default_spec() -> ChainSpec; @@ -440,6 +441,7 @@ impl EthSpec for MainnetEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type BuilderPendingPaymentsLimit = U64; // 2 * SLOTS_PER_EPOCH = 2 * 32 = 64 type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; @@ -525,6 +527,7 @@ impl EthSpec for MinimalEthSpec { type CellsPerExtBlob = U128; type NumberOfColumns = U128; type ProposerLookaheadSlots = U16; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH + type BuilderPendingPaymentsLimit = U16; // 2 * SLOTS_PER_EPOCH = 2 * 8 = 16 params_from_eth_spec!(MainnetEthSpec { JustificationBitsLength, @@ -587,6 +590,7 @@ impl EthSpec for GnosisEthSpec { type EpochsPerSlashingsVector = U8192; type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; + type BuilderPendingPaymentsLimit = U32; // 2 * SLOTS_PER_EPOCH = 2 * 16 = 32 type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; diff --git a/consensus/types/src/execution_payload.rs b/consensus/types/src/execution_payload.rs index 7a899e5f022..74a27ede73d 100644 --- a/consensus/types/src/execution_payload.rs +++ b/consensus/types/src/execution_payload.rs @@ -39,9 +39,7 @@ pub type Withdrawals = VariableList::MaxWithdrawal ), ), cast_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), - partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant"), - map_into(FullPayload, BlindedPayload), - map_ref_into(ExecutionPayloadHeader) + partial_getter_error(ty = "Error", expr = "BeaconStateError::IncorrectStateVariant") )] #[cfg_attr( feature = "arbitrary", @@ -130,6 +128,7 @@ impl ForkVersionDecode for ExecutionPayload { impl ExecutionPayload { #[allow(clippy::arithmetic_side_effects)] /// Returns the maximum size of an execution payload. + /// TODO(EIP-7732): this seems to only exist for the Bellatrix fork, but Mark's branch has it for all the forks, i.e. max_execution_payload_eip7732_size pub fn max_execution_payload_bellatrix_size() -> usize { // Fixed part ExecutionPayloadBellatrix::::default().as_ssz_bytes().len() diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index 2f5fac87a9a..6a257638b51 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -8,7 +8,7 @@ use tree_hash::TreeHash; use tree_hash_derive::TreeHash; #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Default, @@ -84,12 +84,12 @@ pub struct ExecutionPayloadHeader { pub block_hash: ExecutionBlockHash, #[superstruct(getter(copy))] pub transactions_root: Hash256, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Capella, Deneb, Electra, Fulu), partial_getter(copy))] pub withdrawals_root: Hash256, - #[superstruct(only(Deneb, Electra, Fulu, Gloas), 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, Fulu, Gloas), partial_getter(copy))] + #[superstruct(only(Deneb, Electra, Fulu), partial_getter(copy))] #[serde(with = "serde_utils::quoted_u64")] pub excess_blob_gas: u64, } @@ -115,14 +115,19 @@ impl ExecutionPayloadHeader { ExecutionPayloadHeaderElectra::from_ssz_bytes(bytes).map(Self::Electra) } ForkName::Fulu => ExecutionPayloadHeaderFulu::from_ssz_bytes(bytes).map(Self::Fulu), - ForkName::Gloas => ExecutionPayloadHeaderGloas::from_ssz_bytes(bytes).map(Self::Gloas), + ForkName::Gloas => Err(ssz::DecodeError::BytesInvalid(format!( + "unsupported fork for ExecutionPayloadHeader: {fork_name}", + ))), } } #[allow(clippy::arithmetic_side_effects)] pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { // TODO(newfork): Add a new case here if there are new variable fields - if fork_name.bellatrix_enabled() { + if fork_name.gloas_enabled() { + // TODO(EIP7732): check this + return 0; + } else if fork_name.bellatrix_enabled() { // Max size of variable length `extra_data` field E::max_extra_data_bytes() * ::ssz_fixed_len() } else { @@ -137,7 +142,6 @@ impl ExecutionPayloadHeader { ExecutionPayloadHeader::Deneb(_) => ForkName::Deneb, ExecutionPayloadHeader::Electra(_) => ForkName::Electra, ExecutionPayloadHeader::Fulu(_) => ForkName::Fulu, - ExecutionPayloadHeader::Gloas(_) => ForkName::Gloas, } } } @@ -245,30 +249,6 @@ impl ExecutionPayloadHeaderElectra { } } -impl ExecutionPayloadHeaderFulu { - pub fn upgrade_to_gloas(&self) -> ExecutionPayloadHeaderGloas { - ExecutionPayloadHeaderGloas { - 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> for ExecutionPayloadHeaderBellatrix { fn from(payload: &'a ExecutionPayloadBellatrix) -> Self { Self { @@ -384,30 +364,6 @@ impl<'a, E: EthSpec> From<&'a ExecutionPayloadFulu> for ExecutionPayloadHeade } } -impl<'a, E: EthSpec> From<&'a ExecutionPayloadGloas> for ExecutionPayloadHeaderGloas { - fn from(payload: &'a ExecutionPayloadGloas) -> 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 { @@ -440,12 +396,6 @@ impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderFulu { } } -impl<'a, E: EthSpec> From<&'a Self> for ExecutionPayloadHeaderGloas { - fn from(payload: &'a Self) -> Self { - payload.clone() - } -} - impl<'a, E: EthSpec> From> for ExecutionPayloadHeader { fn from(payload: ExecutionPayloadRef<'a, E>) -> Self { map_execution_payload_ref_into_execution_payload_header!( @@ -507,9 +457,6 @@ impl ExecutionPayloadHeaderRefMut<'_, E> { ExecutionPayloadHeaderRefMut::Fulu(mut_ref) => { *mut_ref = header.try_into()?; } - ExecutionPayloadHeaderRefMut::Gloas(mut_ref) => { - *mut_ref = header.try_into()?; - } } Ok(()) } @@ -537,16 +484,6 @@ impl TryFrom> for ExecutionPayloadHeaderFu } } -impl TryFrom> for ExecutionPayloadHeaderGloas { - type Error = BeaconStateError; - fn try_from(header: ExecutionPayloadHeader) -> Result { - match header { - ExecutionPayloadHeader::Gloas(execution_payload_header) => Ok(execution_payload_header), - _ => Err(BeaconStateError::IncorrectStateVariant), - } - } -} - impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHeader { fn context_deserialize(deserializer: D, context: ForkName) -> Result where @@ -559,12 +496,6 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHead )) }; Ok(match context { - ForkName::Base | ForkName::Altair => { - return Err(serde::de::Error::custom(format!( - "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", - context - ))); - } ForkName::Bellatrix => { Self::Bellatrix(Deserialize::deserialize(deserializer).map_err(convert_err)?) } @@ -580,8 +511,13 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHead ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + + // FIXME(EIP7732): Check this + ForkName::Base | ForkName::Altair | ForkName::Gloas => { + return Err(serde::de::Error::custom(format!( + "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", + context + ))) } }) } diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 89a73cb3f25..e4cb84e2919 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -38,6 +38,7 @@ pub mod deposit_data; pub mod deposit_message; pub mod deposit_request; pub mod deposit_tree_snapshot; +pub mod dumb_macros; pub mod enr_fork_id; pub mod eth1_data; pub mod eth_spec; @@ -192,7 +193,7 @@ pub use crate::execution_payload_envelope::{ pub use crate::execution_payload_header::{ ExecutionPayloadHeader, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - ExecutionPayloadHeaderGloas, ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, }; pub use crate::execution_requests::{ExecutionRequests, RequestType}; pub use crate::fork::Fork; @@ -208,35 +209,32 @@ pub use crate::indexed_payload_attestation::IndexedPayloadAttestation; pub use crate::light_client_bootstrap::{ LightClientBootstrap, LightClientBootstrapAltair, LightClientBootstrapCapella, LightClientBootstrapDeneb, LightClientBootstrapElectra, LightClientBootstrapFulu, - LightClientBootstrapGloas, }; pub use crate::light_client_finality_update::{ LightClientFinalityUpdate, LightClientFinalityUpdateAltair, LightClientFinalityUpdateCapella, LightClientFinalityUpdateDeneb, LightClientFinalityUpdateElectra, - LightClientFinalityUpdateFulu, LightClientFinalityUpdateGloas, + LightClientFinalityUpdateFulu, }; pub use crate::light_client_header::{ LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, LightClientHeaderFulu, LightClientHeaderGloas, + LightClientHeaderElectra, LightClientHeaderFulu, }; pub use crate::light_client_optimistic_update::{ LightClientOptimisticUpdate, LightClientOptimisticUpdateAltair, LightClientOptimisticUpdateCapella, LightClientOptimisticUpdateDeneb, LightClientOptimisticUpdateElectra, LightClientOptimisticUpdateFulu, - LightClientOptimisticUpdateGloas, }; pub use crate::light_client_update::{ Error as LightClientUpdateError, LightClientUpdate, LightClientUpdateAltair, LightClientUpdateCapella, LightClientUpdateDeneb, LightClientUpdateElectra, - LightClientUpdateFulu, LightClientUpdateGloas, MerkleProof, + LightClientUpdateFulu, MerkleProof, }; pub use crate::participation_flags::ParticipationFlags; pub use crate::payload::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, - BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadGloas, - BlindedPayloadRef, BlockType, ExecPayload, FullPayload, FullPayloadBellatrix, - FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, FullPayloadGloas, - FullPayloadRef, OwnedExecPayload, + BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, BlindedPayloadRef, BlockType, + ExecPayload, FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, + FullPayloadElectra, FullPayloadFulu, FullPayloadRef, OwnedExecPayload, }; pub use crate::payload_attestation::PayloadAttestation; pub use crate::payload_attestation_data::PayloadAttestationData; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index 5850db876c2..bd6616492e3 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,9 +1,9 @@ use crate::context_deserialize; use crate::{ - BeaconState, ChainSpec, ContextDeserialize, EthSpec, FixedVector, ForkName, Hash256, - LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, LightClientHeaderFulu, LightClientHeaderGloas, - SignedBlindedBeaconBlock, Slot, SyncCommittee, light_client_update::*, test_utils::TestRandom, + light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, ContextDeserialize, + EthSpec, FixedVector, ForkName, Hash256, LightClientHeader, LightClientHeaderAltair, + LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra, + 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, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -62,8 +62,6 @@ pub struct LightClientBootstrap { pub header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "header_fulu"))] pub header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "header_gloas"))] - pub header: LightClientHeaderGloas, /// The `SyncCommittee` used in the requested period. pub current_sync_committee: Arc>, /// Merkle proof for sync committee @@ -73,7 +71,7 @@ pub struct LightClientBootstrap { )] pub current_sync_committee_branch: FixedVector, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "current_sync_committee_branch_electra") )] pub current_sync_committee_branch: FixedVector, @@ -90,7 +88,6 @@ impl LightClientBootstrap { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } @@ -110,7 +107,7 @@ impl LightClientBootstrap { 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::Gloas => Self::Gloas(LightClientBootstrapGloas::from_ssz_bytes(bytes)?), + ForkName::Gloas => todo!("Gloas light client not implemented"), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientBootstrap decoding for {fork_name} not implemented" @@ -132,7 +129,7 @@ impl LightClientBootstrap { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + ForkName::Gloas => todo!("Gloas light client not implemented"), }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -173,11 +170,7 @@ impl LightClientBootstrap { current_sync_committee, current_sync_committee_branch: current_sync_committee_branch.into(), }), - ForkName::Gloas => Self::Gloas(LightClientBootstrapGloas { - header: LightClientHeaderGloas::block_to_light_client_header(block)?, - current_sync_committee, - current_sync_committee_branch: current_sync_committee_branch.into(), - }), + ForkName::Gloas => todo!("Gloas light client not implemented"), }; Ok(light_client_bootstrap) @@ -223,11 +216,7 @@ impl LightClientBootstrap { current_sync_committee, current_sync_committee_branch: current_sync_committee_branch.into(), }), - ForkName::Gloas => Self::Gloas(LightClientBootstrapGloas { - header: LightClientHeaderGloas::block_to_light_client_header(block)?, - current_sync_committee, - current_sync_committee_branch: current_sync_committee_branch.into(), - }), + ForkName::Gloas => todo!("Gloas light client not implemented"), }; Ok(light_client_bootstrap) @@ -268,7 +257,11 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientBootstrap Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + // TODO(EIP-7732): check if this is correct + return Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + context + ))); } }) } @@ -306,10 +299,4 @@ mod tests { use crate::{LightClientBootstrapFulu, MainnetEthSpec}; ssz_tests!(LightClientBootstrapFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientBootstrapGloas, MainnetEthSpec}; - ssz_tests!(LightClientBootstrapGloas); - } } diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index 4fa98de40be..f61cfb4cb55 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -2,10 +2,9 @@ use super::{EthSpec, FixedVector, Hash256, LightClientHeader, Slot, SyncAggregat use crate::ChainSpec; use crate::context_deserialize; use crate::{ - ContextDeserialize, ForkName, LightClientHeaderAltair, LightClientHeaderCapella, - LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, - LightClientHeaderGloas, SignedBlindedBeaconBlock, light_client_update::*, - test_utils::TestRandom, + light_client_update::*, test_utils::TestRandom, ContextDeserialize, ForkName, + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, + LightClientHeaderElectra, LightClientHeaderFulu, SignedBlindedBeaconBlock, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; @@ -17,7 +16,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[superstruct( - variants(Altair, Capella, Deneb, Electra, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -62,8 +61,6 @@ pub struct LightClientFinalityUpdate { pub attested_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] pub attested_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "attested_header_gloas"))] - pub attested_header: LightClientHeaderGloas, /// 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, @@ -75,8 +72,6 @@ pub struct LightClientFinalityUpdate { pub finalized_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))] pub finalized_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "finalized_header_gloas"))] - pub finalized_header: LightClientHeaderGloas, /// Merkle proof attesting finalized header. #[superstruct( only(Altair, Capella, Deneb), @@ -84,7 +79,7 @@ pub struct LightClientFinalityUpdate { )] pub finality_branch: FixedVector, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "finality_branch_electra") )] pub finality_branch: FixedVector, @@ -165,18 +160,7 @@ impl LightClientFinalityUpdate { sync_aggregate, signature_slot, }), - ForkName::Gloas => Self::Gloas(LightClientFinalityUpdateGloas { - attested_header: LightClientHeaderGloas::block_to_light_client_header( - attested_block, - )?, - finalized_header: LightClientHeaderGloas::block_to_light_client_header( - finalized_block, - )?, - finality_branch: finality_branch.into(), - sync_aggregate, - signature_slot, - }), - + ForkName::Gloas => todo!("Gloas light client not implemented"), ForkName::Base => return Err(Error::AltairForkNotActive), }; @@ -193,7 +177,6 @@ impl LightClientFinalityUpdate { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } @@ -231,7 +214,7 @@ impl LightClientFinalityUpdate { Self::Electra(LightClientFinalityUpdateElectra::from_ssz_bytes(bytes)?) } ForkName::Fulu => Self::Fulu(LightClientFinalityUpdateFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => Self::Gloas(LightClientFinalityUpdateGloas::from_ssz_bytes(bytes)?), + ForkName::Gloas => todo!("Gloas light client not implemented"), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientFinalityUpdate decoding for {fork_name} not implemented" @@ -253,7 +236,7 @@ impl LightClientFinalityUpdate { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + ForkName::Gloas => todo!("Gloas light client not implemented"), }; // `2 *` because there are two headers in the update fixed_size + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) @@ -307,7 +290,11 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientFinalityU Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + // TODO(EIP-7732): check if this is correct + return Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + context + ))); } }) } @@ -345,10 +332,4 @@ mod tests { use crate::{LightClientFinalityUpdateFulu, MainnetEthSpec}; ssz_tests!(LightClientFinalityUpdateFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientFinalityUpdateGloas, MainnetEthSpec}; - ssz_tests!(LightClientFinalityUpdateGloas); - } } diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index 162203138ab..dbd03cf5013 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,11 +1,11 @@ use crate::ChainSpec; use crate::context_deserialize; -use crate::{BeaconBlockBody, light_client_update::*}; +use crate::{light_client_update::*, BeaconBlockBody}; use crate::{BeaconBlockHeader, ExecutionPayloadHeader}; use crate::{ContextDeserialize, ForkName}; use crate::{ EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, ExecutionPayloadHeaderGloas, + ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, FixedVector, Hash256, SignedBlindedBeaconBlock, test_utils::TestRandom, }; use derivative::Derivative; @@ -18,7 +18,7 @@ use test_random_derive::TestRandom; use tree_hash_derive::TreeHash; #[superstruct( - variants(Altair, Capella, Deneb, Electra, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu,), variant_attributes( derive( Debug, @@ -68,10 +68,8 @@ pub struct LightClientHeader { pub execution: ExecutionPayloadHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_header_fulu"))] pub execution: ExecutionPayloadHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_header_gloas"))] - pub execution: ExecutionPayloadHeaderGloas, - #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] + #[superstruct(only(Capella, Deneb, Electra, Fulu))] pub execution_branch: FixedVector, #[ssz(skip_serializing, skip_deserializing)] @@ -106,9 +104,7 @@ impl LightClientHeader { ForkName::Fulu => { LightClientHeader::Fulu(LightClientHeaderFulu::block_to_light_client_header(block)?) } - ForkName::Gloas => LightClientHeader::Gloas( - LightClientHeaderGloas::block_to_light_client_header(block)?, - ), + ForkName::Gloas => todo!("Gloas light client not implemented"), }; Ok(header) } @@ -130,9 +126,7 @@ impl LightClientHeader { ForkName::Fulu => { LightClientHeader::Fulu(LightClientHeaderFulu::from_ssz_bytes(bytes)?) } - ForkName::Gloas => { - LightClientHeader::Gloas(LightClientHeaderGloas::from_ssz_bytes(bytes)?) - } + ForkName::Gloas => todo!("Gloas light client not implemented"), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientHeader decoding for {fork_name} not implemented" @@ -152,7 +146,10 @@ impl LightClientHeader { } pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { - if fork_name.capella_enabled() { + if fork_name.gloas_enabled() { + // TODO(EIP7732): check this + return 0; + } else if fork_name.capella_enabled() { ExecutionPayloadHeader::::ssz_max_var_len_for_fork(fork_name) } else { 0 @@ -348,48 +345,6 @@ impl Default for LightClientHeaderFulu { } } -impl LightClientHeaderGloas { - pub fn block_to_light_client_header( - block: &SignedBlindedBeaconBlock, - ) -> Result { - let payload = block - .message() - .execution_payload()? - .execution_payload_gloas()?; - - let header = ExecutionPayloadHeaderGloas::from(payload); - let beacon_block_body = BeaconBlockBody::from( - block - .message() - .body_gloas() - .map_err(|_| Error::BeaconBlockBodyError)? - .to_owned(), - ); - - let execution_branch = beacon_block_body - .to_ref() - .block_body_merkle_proof(EXECUTION_PAYLOAD_INDEX)?; - - Ok(LightClientHeaderGloas { - beacon: block.message().block_header(), - execution: header, - execution_branch: FixedVector::new(execution_branch)?, - _phantom_data: PhantomData, - }) - } -} - -impl Default for LightClientHeaderGloas { - fn default() -> Self { - Self { - beacon: BeaconBlockHeader::empty(), - execution: ExecutionPayloadHeaderGloas::default(), - execution_branch: FixedVector::default(), - _phantom_data: PhantomData, - } - } -} - impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientHeader { fn context_deserialize(deserializer: D, context: ForkName) -> Result where @@ -423,9 +378,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientHeader ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) - } + ForkName::Gloas => todo!("Gloas light client not implemented"), }) } } @@ -462,10 +415,4 @@ mod tests { use crate::{LightClientHeaderFulu, MainnetEthSpec}; ssz_tests!(LightClientHeaderFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientHeaderGloas, MainnetEthSpec}; - ssz_tests!(LightClientHeaderGloas); - } } diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index 7528322d567..ff1732c6994 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -2,9 +2,9 @@ use super::{ContextDeserialize, EthSpec, ForkName, LightClientHeader, Slot, Sync use crate::context_deserialize; use crate::test_utils::TestRandom; use crate::{ - ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, LightClientHeaderFulu, LightClientHeaderGloas, - SignedBlindedBeaconBlock, light_client_update::*, + light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella, + LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, + SignedBlindedBeaconBlock, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; @@ -19,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, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -64,8 +64,6 @@ pub struct LightClientOptimisticUpdate { pub attested_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] pub attested_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "attested_header_gloas"))] - pub attested_header: LightClientHeaderGloas, /// current sync aggregate pub sync_aggregate: SyncAggregate, /// Slot of the sync aggregated signature @@ -121,13 +119,7 @@ impl LightClientOptimisticUpdate { sync_aggregate, signature_slot, }), - ForkName::Gloas => Self::Gloas(LightClientOptimisticUpdateGloas { - attested_header: LightClientHeaderGloas::block_to_light_client_header( - attested_block, - )?, - sync_aggregate, - signature_slot, - }), + ForkName::Gloas => todo!("Gloas light client not implemented"), ForkName::Base => return Err(Error::AltairForkNotActive), }; @@ -144,7 +136,6 @@ impl LightClientOptimisticUpdate { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } @@ -184,9 +175,7 @@ impl LightClientOptimisticUpdate { Self::Electra(LightClientOptimisticUpdateElectra::from_ssz_bytes(bytes)?) } ForkName::Fulu => Self::Fulu(LightClientOptimisticUpdateFulu::from_ssz_bytes(bytes)?), - ForkName::Gloas => { - Self::Gloas(LightClientOptimisticUpdateGloas::from_ssz_bytes(bytes)?) - } + ForkName::Gloas => todo!("Gloas light client not implemented"), ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientOptimisticUpdate decoding for {fork_name} not implemented" @@ -208,7 +197,7 @@ impl LightClientOptimisticUpdate { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + ForkName::Gloas => todo!("Gloas light client not implemented"), }; fixed_len + LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -261,7 +250,11 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientOptimisti Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) + // TODO(EIP-7732): check if this is correct + return Err(serde::de::Error::custom(format!( + "LightClientBootstrap failed to deserialize: unsupported fork '{}'", + context + ))); } }) } @@ -299,10 +292,4 @@ mod tests { use crate::{LightClientOptimisticUpdateFulu, MainnetEthSpec}; ssz_tests!(LightClientOptimisticUpdateFulu); } - - #[cfg(test)] - mod gloas { - use crate::{LightClientOptimisticUpdateGloas, MainnetEthSpec}; - ssz_tests!(LightClientOptimisticUpdateGloas); - } } diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index bf1a8c614a7..e13f4f13c5a 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -3,9 +3,9 @@ use crate::LightClientHeader; use crate::context_deserialize; use crate::light_client_header::LightClientHeaderElectra; use crate::{ - ChainSpec, ContextDeserialize, Epoch, ForkName, LightClientHeaderAltair, - LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderFulu, - LightClientHeaderGloas, SignedBlindedBeaconBlock, beacon_state, test_utils::TestRandom, + beacon_state, test_utils::TestRandom, ChainSpec, ContextDeserialize, Epoch, ForkName, + LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, + LightClientHeaderFulu, SignedBlindedBeaconBlock, }; use derivative::Derivative; use safe_arith::ArithError; @@ -100,7 +100,7 @@ impl From 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, Fulu, Gloas), + variants(Altair, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -145,8 +145,6 @@ pub struct LightClientUpdate { pub attested_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "attested_header_fulu"))] pub attested_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "attested_header_gloas"))] - pub attested_header: LightClientHeaderGloas, /// The `SyncCommittee` used in the next period. pub next_sync_committee: Arc>, // Merkle proof for next sync committee @@ -156,7 +154,7 @@ pub struct LightClientUpdate { )] pub next_sync_committee_branch: NextSyncCommitteeBranch, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "next_sync_committee_branch_electra") )] pub next_sync_committee_branch: NextSyncCommitteeBranchElectra, @@ -171,8 +169,6 @@ pub struct LightClientUpdate { pub finalized_header: LightClientHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "finalized_header_fulu"))] pub finalized_header: LightClientHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "finalized_header_gloas"))] - pub finalized_header: LightClientHeaderGloas, /// Merkle proof attesting finalized header. #[superstruct( only(Altair, Capella, Deneb), @@ -180,7 +176,7 @@ pub struct LightClientUpdate { )] pub finality_branch: FinalityBranch, #[superstruct( - only(Electra, Fulu, Gloas), + only(Electra, Fulu), partial_getter(rename = "finality_branch_electra") )] pub finality_branch: FinalityBranchElectra, @@ -220,9 +216,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for LightClientUpdate ForkName::Fulu => { Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - ForkName::Gloas => { - Self::Gloas(Deserialize::deserialize(deserializer).map_err(convert_err)?) - } + ForkName::Gloas => todo!("Gloas light client not implemented"), }) } } @@ -364,32 +358,10 @@ impl LightClientUpdate { signature_slot: block_slot, }) } - fork_name @ ForkName::Gloas => { - let attested_header = - LightClientHeaderGloas::block_to_light_client_header(attested_block)?; - - let finalized_header = if let Some(finalized_block) = finalized_block { - if finalized_block.fork_name_unchecked() == fork_name { - LightClientHeaderGloas::block_to_light_client_header(finalized_block)? - } else { - LightClientHeaderGloas::default() - } - } else { - LightClientHeaderGloas::default() - }; - - Self::Gloas(LightClientUpdateGloas { - 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 + // 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 + ForkName::Gloas => todo!("Gloas light client not implemented"), }; Ok(light_client_update) @@ -404,7 +376,8 @@ impl LightClientUpdate { 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::Gloas => Self::Gloas(LightClientUpdateGloas::from_ssz_bytes(bytes)?), + ForkName::Gloas => todo!("Gloas light client not implemented"), + ForkName::Base => { return Err(ssz::DecodeError::BytesInvalid(format!( "LightClientUpdate decoding for {fork_name} not implemented" @@ -422,7 +395,6 @@ impl LightClientUpdate { LightClientUpdate::Deneb(update) => update.attested_header.beacon.slot, LightClientUpdate::Electra(update) => update.attested_header.beacon.slot, LightClientUpdate::Fulu(update) => update.attested_header.beacon.slot, - LightClientUpdate::Gloas(update) => update.attested_header.beacon.slot, } } @@ -433,7 +405,6 @@ impl LightClientUpdate { LightClientUpdate::Deneb(update) => update.finalized_header.beacon.slot, LightClientUpdate::Electra(update) => update.finalized_header.beacon.slot, LightClientUpdate::Fulu(update) => update.finalized_header.beacon.slot, - LightClientUpdate::Gloas(update) => update.finalized_header.beacon.slot, } } @@ -554,7 +525,7 @@ impl LightClientUpdate { ForkName::Deneb => as Encode>::ssz_fixed_len(), ForkName::Electra => as Encode>::ssz_fixed_len(), ForkName::Fulu => as Encode>::ssz_fixed_len(), - ForkName::Gloas => as Encode>::ssz_fixed_len(), + ForkName::Gloas => todo!("Gloas light client not implemented"), }; fixed_len + 2 * LightClientHeader::::ssz_max_var_len_for_fork(fork_name) } @@ -569,7 +540,6 @@ impl LightClientUpdate { Self::Deneb(_) => func(ForkName::Deneb), Self::Electra(_) => func(ForkName::Electra), Self::Fulu(_) => func(ForkName::Fulu), - Self::Gloas(_) => func(ForkName::Gloas), } } } @@ -632,13 +602,6 @@ mod tests { ssz_tests!(LightClientUpdateFulu); } - #[cfg(test)] - mod gloas { - use super::*; - use crate::MainnetEthSpec; - ssz_tests!(LightClientUpdateGloas); - } - #[test] fn finalized_root_params() { assert!(2usize.pow(FINALIZED_ROOT_PROOF_LEN as u32) <= FINALIZED_ROOT_INDEX); diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs index 28dc10f9384..2bafeccfe64 100644 --- a/consensus/types/src/payload.rs +++ b/consensus/types/src/payload.rs @@ -105,7 +105,6 @@ pub trait AbstractExecPayload: + TryInto + TryInto + TryInto - + TryInto + Sync { type Ref<'a>: ExecPayload @@ -114,8 +113,7 @@ pub trait AbstractExecPayload: + From<&'a Self::Capella> + From<&'a Self::Deneb> + From<&'a Self::Electra> - + From<&'a Self::Fulu> - + From<&'a Self::Gloas>; + + From<&'a Self::Fulu>; type Bellatrix: OwnedExecPayload + Into @@ -142,15 +140,10 @@ pub trait AbstractExecPayload: + for<'a> From>> + TryFrom> + Sync; - type Gloas: OwnedExecPayload - + Into - + for<'a> From>> - + TryFrom> - + Sync; } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -205,8 +198,6 @@ pub struct FullPayload { pub execution_payload: ExecutionPayloadElectra, #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] pub execution_payload: ExecutionPayloadFulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] - pub execution_payload: ExecutionPayloadGloas, } impl From> for ExecutionPayload { @@ -314,11 +305,18 @@ impl ExecPayload for FullPayload { fn withdrawals_root(&self) -> Result { match self { FullPayload::Bellatrix(_) => Err(Error::IncorrectStateVariant), - FullPayload::Capella(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), - FullPayload::Deneb(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), - FullPayload::Electra(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), - FullPayload::Fulu(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), - FullPayload::Gloas(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), + FullPayload::Capella(ref inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } + FullPayload::Deneb(ref inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } + FullPayload::Electra(ref inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } + FullPayload::Fulu(ref inner) => { + Ok(inner.execution_payload.withdrawals.tree_hash_root()) + } } } @@ -327,10 +325,9 @@ impl ExecPayload for FullPayload { FullPayload::Bellatrix(_) | FullPayload::Capella(_) => { Err(Error::IncorrectStateVariant) } - FullPayload::Deneb(inner) => Ok(inner.execution_payload.blob_gas_used), - FullPayload::Electra(inner) => Ok(inner.execution_payload.blob_gas_used), - FullPayload::Fulu(inner) => Ok(inner.execution_payload.blob_gas_used), - FullPayload::Gloas(inner) => Ok(inner.execution_payload.blob_gas_used), + 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), } } @@ -362,7 +359,7 @@ impl FullPayload { ForkName::Deneb => Ok(FullPayloadDeneb::default().into()), ForkName::Electra => Ok(FullPayloadElectra::default().into()), ForkName::Fulu => Ok(FullPayloadFulu::default().into()), - ForkName::Gloas => Ok(FullPayloadGloas::default().into()), + ForkName::Gloas => Err(Error::IncorrectStateVariant), } } } @@ -463,9 +460,6 @@ impl ExecPayload for FullPayloadRef<'_, E> { Ok(inner.execution_payload.withdrawals.tree_hash_root()) } FullPayloadRef::Fulu(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), - FullPayloadRef::Gloas(inner) => { - Ok(inner.execution_payload.withdrawals.tree_hash_root()) - } } } @@ -477,7 +471,6 @@ impl ExecPayload 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), - FullPayloadRef::Gloas(inner) => Ok(inner.execution_payload.blob_gas_used), } } @@ -501,7 +494,6 @@ impl AbstractExecPayload for FullPayload { type Deneb = FullPayloadDeneb; type Electra = FullPayloadElectra; type Fulu = FullPayloadFulu; - type Gloas = FullPayloadGloas; } impl From> for FullPayload { @@ -520,7 +512,7 @@ impl TryFrom> for FullPayload { } #[superstruct( - variants(Bellatrix, Capella, Deneb, Electra, Fulu, Gloas), + variants(Bellatrix, Capella, Deneb, Electra, Fulu), variant_attributes( derive( Debug, @@ -574,8 +566,6 @@ pub struct BlindedPayload { pub execution_payload_header: ExecutionPayloadHeaderElectra, #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] pub execution_payload_header: ExecutionPayloadHeaderFulu, - #[superstruct(only(Gloas), partial_getter(rename = "execution_payload_gloas"))] - pub execution_payload_header: ExecutionPayloadHeaderGloas, } impl<'a, E: EthSpec> From> for BlindedPayload { @@ -661,11 +651,14 @@ impl ExecPayload for BlindedPayload { fn withdrawals_root(&self) -> Result { match self { BlindedPayload::Bellatrix(_) => Err(Error::IncorrectStateVariant), - BlindedPayload::Capella(inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayload::Deneb(inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayload::Electra(inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayload::Fulu(inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayload::Gloas(inner) => Ok(inner.execution_payload_header.withdrawals_root), + BlindedPayload::Capella(ref inner) => { + Ok(inner.execution_payload_header.withdrawals_root) + } + BlindedPayload::Deneb(ref inner) => Ok(inner.execution_payload_header.withdrawals_root), + BlindedPayload::Electra(ref inner) => { + Ok(inner.execution_payload_header.withdrawals_root) + } + BlindedPayload::Fulu(ref inner) => Ok(inner.execution_payload_header.withdrawals_root), } } @@ -674,10 +667,9 @@ impl ExecPayload for BlindedPayload { BlindedPayload::Bellatrix(_) | BlindedPayload::Capella(_) => { Err(Error::IncorrectStateVariant) } - BlindedPayload::Deneb(inner) => Ok(inner.execution_payload_header.blob_gas_used), - BlindedPayload::Electra(inner) => Ok(inner.execution_payload_header.blob_gas_used), - BlindedPayload::Fulu(inner) => Ok(inner.execution_payload_header.blob_gas_used), - BlindedPayload::Gloas(inner) => Ok(inner.execution_payload_header.blob_gas_used), + 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), } } @@ -777,7 +769,6 @@ impl<'b, E: EthSpec> ExecPayload for BlindedPayloadRef<'b, E> { Ok(inner.execution_payload_header.withdrawals_root) } BlindedPayloadRef::Fulu(inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayloadRef::Gloas(inner) => Ok(inner.execution_payload_header.withdrawals_root), } } @@ -789,7 +780,6 @@ impl<'b, E: EthSpec> ExecPayload 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), - BlindedPayloadRef::Gloas(inner) => Ok(inner.execution_payload_header.blob_gas_used), } } @@ -1100,13 +1090,6 @@ impl_exec_payload_for_fork!( ExecutionPayloadFulu, Fulu ); -impl_exec_payload_for_fork!( - BlindedPayloadGloas, - FullPayloadGloas, - ExecutionPayloadHeaderGloas, - ExecutionPayloadGloas, - Gloas -); impl AbstractExecPayload for BlindedPayload { type Ref<'a> = BlindedPayloadRef<'a, E>; @@ -1115,7 +1098,6 @@ impl AbstractExecPayload for BlindedPayload { type Deneb = BlindedPayloadDeneb; type Electra = BlindedPayloadElectra; type Fulu = BlindedPayloadFulu; - type Gloas = BlindedPayloadGloas; } impl From> for BlindedPayload { @@ -1157,11 +1139,6 @@ impl From> for BlindedPayload { execution_payload_header, }) } - ExecutionPayloadHeader::Gloas(execution_payload_header) => { - Self::Gloas(BlindedPayloadGloas { - execution_payload_header, - }) - } } } } @@ -1184,9 +1161,6 @@ impl From> for ExecutionPayloadHeader { BlindedPayload::Fulu(blinded_payload) => { ExecutionPayloadHeader::Fulu(blinded_payload.execution_payload_header) } - BlindedPayload::Gloas(blinded_payload) => { - ExecutionPayloadHeader::Gloas(blinded_payload.execution_payload_header) - } } } } diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index 53d34a43519..98311a73e95 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -6,7 +6,6 @@ use merkle_proof::MerkleTree; use serde::{Deserialize, Deserializer, Serialize}; use ssz_derive::{Decode, Encode}; use std::fmt; -use std::marker::PhantomData; use superstruct::superstruct; use test_random_derive::TestRandom; use tree_hash::TreeHash; @@ -649,60 +648,14 @@ impl SignedBeaconBlockFulu> { } } -/// Gloas doesn't support BlindedPayload, but we keep this impl for compatibility purposes of the SignedBeaconBlock type -impl SignedBeaconBlockGloas> { - pub fn into_full_block( - self, - _execution_payload: ExecutionPayloadGloas, - ) -> SignedBeaconBlockGloas> { - let SignedBeaconBlockGloas { - message: - BeaconBlockGloas { - slot, - proposer_index, - parent_root, - state_root, - body: - BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings, - attester_slashings, - attestations, - deposits, - voluntary_exits, - sync_aggregate, - bls_to_execution_changes, - signed_execution_payload_header, - payload_attestations, - .. - }, - }, - signature, - } = self; +// We can convert gloas blocks without payloads into blocks "with" payloads. +impl From>> + for SignedBeaconBlockGloas> +{ + fn from(signed_block: SignedBeaconBlockGloas>) -> Self { + let SignedBeaconBlockGloas { message, signature } = signed_block; SignedBeaconBlockGloas { - message: BeaconBlockGloas { - slot, - proposer_index, - parent_root, - state_root, - body: BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings, - attester_slashings, - attestations, - deposits, - voluntary_exits, - sync_aggregate, - bls_to_execution_changes, - signed_execution_payload_header, - payload_attestations, - _phantom: PhantomData, - }, - }, + message: message.into(), signature, } } @@ -731,9 +684,7 @@ impl SignedBeaconBlock> { (SignedBeaconBlock::Fulu(block), Some(ExecutionPayload::Fulu(payload))) => { SignedBeaconBlock::Fulu(block.into_full_block(payload)) } - (SignedBeaconBlock::Gloas(block), Some(ExecutionPayload::Gloas(payload))) => { - SignedBeaconBlock::Gloas(block.into_full_block(payload)) - } + (SignedBeaconBlock::Gloas(block), _) => SignedBeaconBlock::Gloas(block.into()), // avoid wildcard matching forks so that compiler will // direct us here when a new fork has been added (SignedBeaconBlock::Bellatrix(_), _) => return None, @@ -741,7 +692,7 @@ impl SignedBeaconBlock> { (SignedBeaconBlock::Deneb(_), _) => return None, (SignedBeaconBlock::Electra(_), _) => return None, (SignedBeaconBlock::Fulu(_), _) => return None, - (SignedBeaconBlock::Gloas(_), _) => return None, + // TODO(EIP-7732) Determine if need a match arm for gloas here }; Some(full_block) } diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index 379fcb1bb4d..63b46945c2f 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -307,6 +307,7 @@ impl Operation for BeaconBlockBody> { ForkName::Deneb => BeaconBlockBody::Deneb(<_>::from_ssz_bytes(bytes)?), ForkName::Electra => BeaconBlockBody::Electra(<_>::from_ssz_bytes(bytes)?), ForkName::Fulu => BeaconBlockBody::Fulu(<_>::from_ssz_bytes(bytes)?), + // TODO(EIP-7732): See if we need to handle Gloas here _ => panic!(), }) }) @@ -366,6 +367,7 @@ impl Operation for BeaconBlockBody> { let inner = >>::from_ssz_bytes(bytes)?; BeaconBlockBody::Fulu(inner.clone_as_blinded()) } + // TODO(EIP-7732): See if we need to handle Gloas here _ => panic!(), }) }) @@ -417,7 +419,15 @@ impl Operation for WithdrawalsPayload { spec: &ChainSpec, _: &Operations, ) -> Result<(), BlockProcessingError> { - process_withdrawals::<_, FullPayload<_>>(state, self.payload.to_ref(), spec) + if state.fork_name_unchecked().gloas_enabled() { + process_withdrawals::gloas::process_withdrawals(state, spec) + } else { + process_withdrawals::capella::process_withdrawals::<_, FullPayload<_>>( + state, + self.payload.to_ref(), + spec, + ) + } } } From 4d9dfc4bfc3b159697f118ab3ede38e3a718b74a Mon Sep 17 00:00:00 2001 From: shane-moore Date: Thu, 7 Aug 2025 16:56:46 -0700 Subject: [PATCH 06/16] additional container logic ported --- consensus/types/src/beacon_state.rs | 12 ++++ consensus/types/src/chain_spec.rs | 2 +- consensus/types/src/eth_spec.rs | 4 +- consensus/types/src/fork_context.rs | 2 + .../types/src/indexed_payload_attestation.rs | 1 + consensus/types/src/lib.rs | 2 + consensus/types/src/ptc.rs | 57 +++++++++++++++++++ .../src/signed_execution_payload_envelope.rs | 31 +++++++++- 8 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 consensus/types/src/ptc.rs diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 279f2eff181..981d5750f38 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2004,6 +2004,18 @@ impl BeaconState { } } + /// Get the PTC + /// Requires the committee cache to be initialized. + /// TODO(EIP-7732): definitely gonna have to cache this.. + /// TODO(EIP-7732): check this implementation against the latest spec + // https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/#new-get_ptc + pub fn get_ptc(&self, slot: Slot) -> Result, Error> { + let committee_cache = self.committee_cache_at_slot(slot)?; + let committees = committee_cache.get_beacon_committees_at_slot(slot)?; + + PTC::from_committees(&committees) + } + /// Build all caches (except the tree hash cache), if they need to be built. #[instrument(skip_all, level = "debug")] pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> { diff --git a/consensus/types/src/chain_spec.rs b/consensus/types/src/chain_spec.rs index b2a93c24d8f..03fee0fda76 100644 --- a/consensus/types/src/chain_spec.rs +++ b/consensus/types/src/chain_spec.rs @@ -2483,9 +2483,9 @@ mod tests { spec.domain_aggregate_and_proof, &spec, ); + test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); test_domain(Domain::BeaconBuilder, spec.domain_beacon_builder, &spec); test_domain(Domain::PTCAttester, spec.domain_ptc_attester, &spec); - test_domain(Domain::SyncCommittee, spec.domain_sync_committee, &spec); // The builder domain index is zero let builder_domain_pre_mask = [0; 4]; diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index 9aa111c30e6..03bfffd8fc1 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -405,10 +405,12 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::ProposerLookaheadSlots::to_usize() } + /// Returns the `PTCSize` constant for this specification. fn ptc_size() -> usize { Self::PTCSize::to_usize() } + /// Returns the `MaxPayloadAttestations` constant for this specification. fn max_payload_attestations() -> usize { Self::MaxPayloadAttestations::to_usize() } @@ -484,7 +486,7 @@ impl EthSpec for MainnetEthSpec { type MaxWithdrawalRequestsPerPayload = U16; type MaxPendingDepositsPerEpoch = U16; type PTCSize = U512; - type MaxPayloadAttestations = U2; + type MaxPayloadAttestations = U4; fn default_spec() -> ChainSpec { ChainSpec::mainnet() diff --git a/consensus/types/src/fork_context.rs b/consensus/types/src/fork_context.rs index 66617326e13..6c1fb74a099 100644 --- a/consensus/types/src/fork_context.rs +++ b/consensus/types/src/fork_context.rs @@ -180,6 +180,7 @@ mod tests { spec.deneb_fork_epoch = Some(Epoch::new(4)); spec.electra_fork_epoch = Some(Epoch::new(5)); spec.fulu_fork_epoch = Some(Epoch::new(6)); + spec.gloas_fork_epoch = Some(Epoch::new(7)); spec.blob_schedule = BlobSchedule::new(blob_parameters); spec } @@ -194,6 +195,7 @@ mod tests { assert!(context.fork_exists(ForkName::Electra)); assert!(context.fork_exists(ForkName::Fulu)); + assert!(context.fork_exists(ForkName::Gloas)); } #[test] diff --git a/consensus/types/src/indexed_payload_attestation.rs b/consensus/types/src/indexed_payload_attestation.rs index 45b3de95bc7..ddc2d001854 100644 --- a/consensus/types/src/indexed_payload_attestation.rs +++ b/consensus/types/src/indexed_payload_attestation.rs @@ -12,6 +12,7 @@ use tree_hash_derive::TreeHash; #[cfg_attr(feature = "arbitrary", arbitrary(bound = "E: EthSpec"))] #[context_deserialize(ForkName)] pub struct IndexedPayloadAttestation { + #[serde(with = "ssz_types::serde_utils::quoted_u64_var_list")] pub attesting_indices: VariableList, pub data: PayloadAttestationData, pub signature: AggregateSignature, diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index e4cb84e2919..68651eb869e 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -69,6 +69,7 @@ pub mod pending_deposit; pub mod pending_partial_withdrawal; pub mod proposer_preparation_data; pub mod proposer_slashing; +pub mod ptc; pub mod relative_epoch; pub mod selection_proof; pub mod shuffling_id; @@ -249,6 +250,7 @@ pub use crate::preset::{ }; pub use crate::proposer_preparation_data::ProposerPreparationData; pub use crate::proposer_slashing::ProposerSlashing; +pub use crate::ptc::PTC; pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; pub use crate::runtime_fixed_vector::RuntimeFixedVector; pub use crate::runtime_var_list::RuntimeVariableList; diff --git a/consensus/types/src/ptc.rs b/consensus/types/src/ptc.rs new file mode 100644 index 00000000000..26cc1c2ca16 --- /dev/null +++ b/consensus/types/src/ptc.rs @@ -0,0 +1,57 @@ +use crate::*; +use safe_arith::SafeArith; + +/// TODO(EIP-7732): is it easier to return u64 or usize? +#[derive(Clone, Debug, PartialEq)] +pub struct PTC(FixedVector); + +// TODO(EIP-7732): check this implementation against the latest spec +// https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/#new-get_ptc +impl PTC { + pub fn from_committees(committees: &[BeaconCommittee]) -> Result { + // this function is only used here and + // I have no idea where else to put it + fn bit_floor(n: u64) -> u64 { + if n == 0 { + 0 + } else { + 1 << (n.leading_zeros() as u64 ^ 63) + } + } + + let committee_count_per_slot = committees.len() as u64; + let committees_per_slot = bit_floor(std::cmp::min( + committee_count_per_slot, + E::PTCSize::to_u64(), + )) as usize; + let members_per_committee = E::PTCSize::to_usize().safe_div(committees_per_slot)?; + + let mut ptc = Vec::with_capacity(E::PTCSize::to_usize()); + for idx in 0..committees_per_slot { + let beacon_committee = committees + .get(idx as usize) + .ok_or_else(|| Error::InvalidCommitteeIndex(idx as u64))?; + ptc.extend_from_slice(&beacon_committee.committee[..members_per_committee]); + } + + Ok(Self(FixedVector::from(ptc))) + } +} + +impl<'a, E: EthSpec> IntoIterator for &'a PTC { + type Item = &'a usize; + type IntoIter = std::slice::Iter<'a, usize>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + +impl IntoIterator for PTC { + type Item = usize; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} diff --git a/consensus/types/src/signed_execution_payload_envelope.rs b/consensus/types/src/signed_execution_payload_envelope.rs index 6d5c6d1c12a..6cab667d14a 100644 --- a/consensus/types/src/signed_execution_payload_envelope.rs +++ b/consensus/types/src/signed_execution_payload_envelope.rs @@ -55,7 +55,36 @@ impl SignedExecutionPayloadEnvelope { } } - // todo(eip-7732): implement verify_signature since spec calls for verify_execution_payload_envelope_signature + /// Verify `self.signature`. + /// + /// The `parent_state` is the post-state of the beacon block with + /// block_root = self.message.beacon_block_root + pub fn verify_signature( + &self, + parent_state: &BeaconState, + genesis_validators_root: Hash256, + spec: &ChainSpec, + ) -> Result { + let domain = spec.get_domain( + parent_state.current_epoch(), + Domain::BeaconBuilder, + &parent_state.fork(), + genesis_validators_root, + ); + let pubkey = parent_state + .validators() + .get(self.message().builder_index() as usize) + .and_then(|v| { + let pk: Option = v.pubkey.decompress().ok(); + pk + }) + .ok_or_else(|| { + BeaconStateError::UnknownValidator(self.message().builder_index() as usize) + })?; + let message = self.message().signing_root(domain); + + Ok(self.signature().verify(&pubkey, message)) + } } impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedExecutionPayloadEnvelope { From e90abb098138a060a25658270af49e2cd4a31768 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Mon, 11 Aug 2025 07:48:56 -0700 Subject: [PATCH 07/16] PayloadAttestationData contains blob_data_available now --- consensus/types/src/payload_attestation_data.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/consensus/types/src/payload_attestation_data.rs b/consensus/types/src/payload_attestation_data.rs index a25e6763920..01e9a1e8f8e 100644 --- a/consensus/types/src/payload_attestation_data.rs +++ b/consensus/types/src/payload_attestation_data.rs @@ -14,6 +14,7 @@ pub struct PayloadAttestationData { pub beacon_block_root: Hash256, pub slot: Slot, pub payload_present: bool, + pub blob_data_available: bool, } impl SignedRoot for PayloadAttestationData {} From 161bd2d1bf95f13e4c8c6839bc72a8eb3dc61a48 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Mon, 11 Aug 2025 14:03:10 -0700 Subject: [PATCH 08/16] updates per per review --- beacon_node/beacon_chain/src/beacon_chain.rs | 40 +------ beacon_node/execution_layer/src/lib.rs | 45 +------- beacon_node/store/src/partial_beacon_state.rs | 1 - .../src/per_block_processing.rs | 81 ++++++++++++-- .../process_withdrawals.rs | 105 ------------------ consensus/types/src/beacon_state.rs | 12 -- consensus/types/src/lib.rs | 2 - consensus/types/src/ptc.rs | 57 ---------- .../src/signed_execution_payload_envelope.rs | 31 ------ testing/ef_tests/src/cases/operations.rs | 11 +- 10 files changed, 74 insertions(+), 311 deletions(-) delete mode 100644 consensus/state_processing/src/per_block_processing/process_withdrawals.rs delete mode 100644 consensus/types/src/ptc.rs diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index 539f7500618..6002dc65248 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -5726,45 +5726,7 @@ impl BeaconChain { execution_payload_value, ) } - // Below is my attempt at handling the Gloas variant - // Note that Mark's implementation had this as: - // BeaconState::EIP7732(_) => todo!("EIP-7732 block production"), - BeaconState::Gloas(_) => { - // Gloas blocks contain execution bids, not execution payloads - let block_proposal_contents = - block_contents.ok_or(BlockProductionError::MissingExecutionBid)?; - let (signed_execution_bid, payload_attestations) = block_proposal_contents - .into_execution_bid() - .map_err(|_| BlockProductionError::InvalidPayloadFork)?; - - ( - BeaconBlock::Gloas(BeaconBlockGloas { - slot, - proposer_index, - parent_root, - state_root: Hash256::zero(), - body: BeaconBlockBodyGloas { - randao_reveal, - eth1_data, - graffiti, - proposer_slashings: proposer_slashings.into(), - attester_slashings: attester_slashings_electra.into(), - attestations: attestations_electra.into(), - deposits: deposits.into(), - voluntary_exits: voluntary_exits.into(), - sync_aggregate: sync_aggregate - .ok_or(BlockProductionError::MissingSyncAggregate)?, - bls_to_execution_changes: bls_to_execution_changes.into(), - // Gloas: Use actual execution bid data - signed_execution_bid: signed_execution_bid.clone(), - payload_attestations, - _phantom: PhantomData, - }, - }), - None, // blob commitments moved to `ExecutionPayloadEnvelope` - Uint256::ZERO, // No execution payload value for Gloas blocks, just bids value - ) - } + BeaconState::Gloas(_) => todo!("Gloas block production"), }; let block = SignedBeaconBlock::from_block( diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 28b190a6e77..e67da468d2d 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -51,7 +51,7 @@ use types::non_zero_usize::new_non_zero_usize; use types::payload::BlockProductionVersion; use types::{ AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, ExecutionRequests, KzgProofs, - PayloadAttestation, SignedBlindedBeaconBlock, SignedExecutionBid, + SignedBlindedBeaconBlock, }; use types::{ BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix, @@ -212,11 +212,6 @@ pub enum BlockProposalContents> { // See: https://github.com/sigp/lighthouse/issues/6981 requests: Option>, }, - /// Gloas: Execution bid and payload attestations - BidAndPayloadAttestations { - signed_execution_bid: SignedExecutionBid, - payload_attestations: VariableList, E::MaxPayloadAttestations>, - }, } impl From>> @@ -244,13 +239,6 @@ impl From>> blobs_and_proofs: None, requests, }, - BlockProposalContents::BidAndPayloadAttestations { - signed_execution_bid, - payload_attestations, - } => BlockProposalContents::BidAndPayloadAttestations { - signed_execution_bid, - payload_attestations, - }, } } } @@ -318,28 +306,6 @@ impl> BlockProposalContents { - panic!("Cannot deconstruct BidAndPayloadAttestations variant into execution payload components") - } - } - } - - /// Extract execution bid data for EIP-7732 Gloas blocks - pub fn into_execution_bid( - self, - ) -> Result< - ( - SignedExecutionBid, - VariableList, E::MaxPayloadAttestations>, - ), - &'static str, - > { - match self { - Self::BidAndPayloadAttestations { - signed_execution_bid, - payload_attestations, - } => Ok((signed_execution_bid, payload_attestations)), - _ => Err("Cannot extract execution bid from non-BidAndPayloadAttestations variant"), } } @@ -347,27 +313,18 @@ impl> BlockProposalContents payload, Self::PayloadAndBlobs { payload, .. } => payload, - Self::BidAndPayloadAttestations { .. } => { - panic!("BidAndPayloadAttestations variant does not contain execution payload") - } } } pub fn to_payload(self) -> Payload { match self { Self::Payload { payload, .. } => payload, Self::PayloadAndBlobs { payload, .. } => payload, - Self::BidAndPayloadAttestations { .. } => { - panic!("BidAndPayloadAttestations variant does not contain execution payload") - } } } pub fn block_value(&self) -> &Uint256 { match self { Self::Payload { block_value, .. } => block_value, Self::PayloadAndBlobs { block_value, .. } => block_value, - Self::BidAndPayloadAttestations { .. } => { - panic!("BidAndPayloadAttestations variant does not have block_value") - } } } } diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index fe5fd2b0399..2d8226bf4c4 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -153,7 +153,6 @@ where #[superstruct(only(Fulu, Gloas))] pub proposer_lookahead: Vector, - // Gloas // Gloas #[superstruct(only(Gloas))] pub execution_payload_availability: BitVector, diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index e8473fa6a07..5335c917cb3 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -30,7 +30,6 @@ pub mod deneb; pub mod errors; mod is_valid_indexed_attestation; pub mod process_operations; -pub mod process_withdrawals; pub mod signature_sets; pub mod tests; mod verify_attestation; @@ -40,6 +39,7 @@ mod verify_deposit; mod verify_exit; mod verify_proposer_slashing; +use crate::common::decrease_balance; use crate::common::update_progressive_balances_cache::{ initialize_progressive_balances_cache, update_progressive_balances_metrics, }; @@ -171,16 +171,9 @@ pub fn per_block_processing>( // previous block. if is_execution_enabled(state, block.body()) { let body = block.body(); - if state.fork_name_unchecked().gloas_enabled() { - process_withdrawals::gloas::process_withdrawals::(state, spec)?; - } else { - process_withdrawals::capella::process_withdrawals::( - state, - body.execution_payload()?, - spec, - )?; - process_execution_payload::(state, body, spec)?; - } + // TODO(EIP-7732): build out process_withdrawals variant for gloas + process_withdrawals::(state, body.execution_payload()?, spec)?; + process_execution_payload::(state, body, spec)?; } // TODO(EIP-7732): build out process_execution_bid @@ -635,3 +628,69 @@ pub fn get_expected_withdrawals( Ok((withdrawals.into(), processed_partial_withdrawals_count)) } + +/// Apply withdrawals to the state. +/// TODO(EIP-7732): abstract this out and create gloas variant +pub fn process_withdrawals>( + state: &mut BeaconState, + payload: Payload::Ref<'_>, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + if state.fork_name_unchecked().capella_enabled() { + let (expected_withdrawals, processed_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, + }); + } + + 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(processed_partial_withdrawals_count) = processed_partial_withdrawals_count { + state + .pending_partial_withdrawals_mut()? + .pop_front(processed_partial_withdrawals_count)?; + } + + // 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)? + .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 + Ok(()) + } +} diff --git a/consensus/state_processing/src/per_block_processing/process_withdrawals.rs b/consensus/state_processing/src/per_block_processing/process_withdrawals.rs deleted file mode 100644 index 562fcb0385b..00000000000 --- a/consensus/state_processing/src/per_block_processing/process_withdrawals.rs +++ /dev/null @@ -1,105 +0,0 @@ -use super::errors::BlockProcessingError; -use super::get_expected_withdrawals; -use crate::common::decrease_balance; -use safe_arith::SafeArith; -use tree_hash::TreeHash; -use types::{AbstractExecPayload, BeaconState, ChainSpec, EthSpec, ExecPayload, Withdrawals}; - -fn process_withdrawals_common( - state: &mut BeaconState, - expected_withdrawals: Withdrawals, - partial_withdrawals_count: Option, - spec: &ChainSpec, -) -> Result<(), BlockProcessingError> { - match state { - BeaconState::Capella(_) - | BeaconState::Deneb(_) - | BeaconState::Electra(_) - | BeaconState::Fulu(_) - | BeaconState::Gloas(_) => { - 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 { - state - .pending_partial_withdrawals_mut()? - .pop_front(partial_withdrawals_count)?; - } - - // 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)? - .safe_rem(state.validators().len() as u64)?; - *state.next_withdrawal_validator_index_mut()? = next_validator_index; - } - - Ok(()) - } - // these shouldn't even be encountered but they're here for completeness - BeaconState::Base(_) | BeaconState::Altair(_) | BeaconState::Bellatrix(_) => Ok(()), - } -} - -pub mod capella { - use super::*; - /// Apply withdrawals to the state. - pub fn process_withdrawals>( - state: &mut BeaconState, - payload: Payload::Ref<'_>, - spec: &ChainSpec, - ) -> Result<(), BlockProcessingError> { - 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, - }); - } - - process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec) - } -} - -pub mod gloas { - use super::*; - /// Apply withdrawals to the state. - pub fn process_withdrawals( - state: &mut BeaconState, - spec: &ChainSpec, - ) -> Result<(), BlockProcessingError> { - if !state.is_parent_block_full() { - return Ok(()); - } - - let (expected_withdrawals, partial_withdrawals_count) = - get_expected_withdrawals(state, spec)?; - process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec) - } -} diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 981d5750f38..279f2eff181 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2004,18 +2004,6 @@ impl BeaconState { } } - /// Get the PTC - /// Requires the committee cache to be initialized. - /// TODO(EIP-7732): definitely gonna have to cache this.. - /// TODO(EIP-7732): check this implementation against the latest spec - // https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/#new-get_ptc - pub fn get_ptc(&self, slot: Slot) -> Result, Error> { - let committee_cache = self.committee_cache_at_slot(slot)?; - let committees = committee_cache.get_beacon_committees_at_slot(slot)?; - - PTC::from_committees(&committees) - } - /// Build all caches (except the tree hash cache), if they need to be built. #[instrument(skip_all, level = "debug")] pub fn build_caches(&mut self, spec: &ChainSpec) -> Result<(), Error> { diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index 68651eb869e..e4cb84e2919 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -69,7 +69,6 @@ pub mod pending_deposit; pub mod pending_partial_withdrawal; pub mod proposer_preparation_data; pub mod proposer_slashing; -pub mod ptc; pub mod relative_epoch; pub mod selection_proof; pub mod shuffling_id; @@ -250,7 +249,6 @@ pub use crate::preset::{ }; pub use crate::proposer_preparation_data::ProposerPreparationData; pub use crate::proposer_slashing::ProposerSlashing; -pub use crate::ptc::PTC; pub use crate::relative_epoch::{Error as RelativeEpochError, RelativeEpoch}; pub use crate::runtime_fixed_vector::RuntimeFixedVector; pub use crate::runtime_var_list::RuntimeVariableList; diff --git a/consensus/types/src/ptc.rs b/consensus/types/src/ptc.rs deleted file mode 100644 index 26cc1c2ca16..00000000000 --- a/consensus/types/src/ptc.rs +++ /dev/null @@ -1,57 +0,0 @@ -use crate::*; -use safe_arith::SafeArith; - -/// TODO(EIP-7732): is it easier to return u64 or usize? -#[derive(Clone, Debug, PartialEq)] -pub struct PTC(FixedVector); - -// TODO(EIP-7732): check this implementation against the latest spec -// https://ethereum.github.io/consensus-specs/specs/_features/eip7732/beacon-chain/#new-get_ptc -impl PTC { - pub fn from_committees(committees: &[BeaconCommittee]) -> Result { - // this function is only used here and - // I have no idea where else to put it - fn bit_floor(n: u64) -> u64 { - if n == 0 { - 0 - } else { - 1 << (n.leading_zeros() as u64 ^ 63) - } - } - - let committee_count_per_slot = committees.len() as u64; - let committees_per_slot = bit_floor(std::cmp::min( - committee_count_per_slot, - E::PTCSize::to_u64(), - )) as usize; - let members_per_committee = E::PTCSize::to_usize().safe_div(committees_per_slot)?; - - let mut ptc = Vec::with_capacity(E::PTCSize::to_usize()); - for idx in 0..committees_per_slot { - let beacon_committee = committees - .get(idx as usize) - .ok_or_else(|| Error::InvalidCommitteeIndex(idx as u64))?; - ptc.extend_from_slice(&beacon_committee.committee[..members_per_committee]); - } - - Ok(Self(FixedVector::from(ptc))) - } -} - -impl<'a, E: EthSpec> IntoIterator for &'a PTC { - type Item = &'a usize; - type IntoIter = std::slice::Iter<'a, usize>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl IntoIterator for PTC { - type Item = usize; - type IntoIter = std::vec::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} diff --git a/consensus/types/src/signed_execution_payload_envelope.rs b/consensus/types/src/signed_execution_payload_envelope.rs index 6cab667d14a..bc2c4e994f0 100644 --- a/consensus/types/src/signed_execution_payload_envelope.rs +++ b/consensus/types/src/signed_execution_payload_envelope.rs @@ -54,37 +54,6 @@ impl SignedExecutionPayloadEnvelope { Self::NextFork(ref signed) => ExecutionPayloadEnvelopeRef::NextFork(&signed.message), } } - - /// Verify `self.signature`. - /// - /// The `parent_state` is the post-state of the beacon block with - /// block_root = self.message.beacon_block_root - pub fn verify_signature( - &self, - parent_state: &BeaconState, - genesis_validators_root: Hash256, - spec: &ChainSpec, - ) -> Result { - let domain = spec.get_domain( - parent_state.current_epoch(), - Domain::BeaconBuilder, - &parent_state.fork(), - genesis_validators_root, - ); - let pubkey = parent_state - .validators() - .get(self.message().builder_index() as usize) - .and_then(|v| { - let pk: Option = v.pubkey.decompress().ok(); - pk - }) - .ok_or_else(|| { - BeaconStateError::UnknownValidator(self.message().builder_index() as usize) - })?; - let message = self.message().signing_root(domain); - - Ok(self.signature().verify(&pubkey, message)) - } } impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for SignedExecutionPayloadEnvelope { diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index 63b46945c2f..a53bce927cb 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -419,15 +419,8 @@ impl Operation for WithdrawalsPayload { spec: &ChainSpec, _: &Operations, ) -> Result<(), BlockProcessingError> { - if state.fork_name_unchecked().gloas_enabled() { - process_withdrawals::gloas::process_withdrawals(state, spec) - } else { - process_withdrawals::capella::process_withdrawals::<_, FullPayload<_>>( - state, - self.payload.to_ref(), - spec, - ) - } + // TODO(EIP-7732): implement separate gloas and non-gloas variants of process_withdrawals + process_withdrawals::<_, FullPayload<_>>(state, self.payload.to_ref(), spec) } } From a3563289c1c1516ba2aa7dc0a410f6834543e2f6 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Mon, 18 Aug 2025 12:21:21 -0700 Subject: [PATCH 09/16] additional updates per pr review --- .../src/test_utils/mock_builder.rs | 21 ++++++++++--------- beacon_node/store/src/partial_beacon_state.rs | 4 ---- .../state_processing/src/upgrade/gloas.rs | 12 ++++++----- consensus/types/src/beacon_state.rs | 5 ----- 4 files changed, 18 insertions(+), 24 deletions(-) diff --git a/beacon_node/execution_layer/src/test_utils/mock_builder.rs b/beacon_node/execution_layer/src/test_utils/mock_builder.rs index eef5811f901..5121551545a 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -439,7 +439,7 @@ impl MockBuilder { block: SignedBlindedBeaconBlock, ) -> Result, String> { let root = match &block { - SignedBlindedBeaconBlock::Base(_) | SignedBlindedBeaconBlock::Altair(_) => { + SignedBlindedBeaconBlock::Base(_) | types::SignedBeaconBlock::Altair(_) => { return Err("invalid fork".to_string()); } SignedBlindedBeaconBlock::Bellatrix(block) => { @@ -846,10 +846,6 @@ impl MockBuilder { // first to avoid polluting the execution block generator with invalid payload attributes // NOTE: this was part of an effort to add payload attribute uniqueness checks, // which was abandoned because it broke too many tests in subtle ways. - ForkName::Gloas => { - // TODO(EIP7732) Check if this is how we want to do error handling for gloas - return Err("invalid fork".to_string()); - } ForkName::Bellatrix | ForkName::Capella => PayloadAttributes::new( timestamp, *prev_randao, @@ -857,11 +853,16 @@ impl MockBuilder { expected_withdrawals, None, ), - ForkName::Deneb - | ForkName::Electra - | ForkName::Fulu - | ForkName::Base - | ForkName::Altair => { + ForkName::Deneb | ForkName::Electra | ForkName::Fulu | ForkName::Gloas => { + PayloadAttributes::new( + timestamp, + *prev_randao, + fee_recipient, + expected_withdrawals, + Some(head_block_root), + ) + } + ForkName::Base | ForkName::Altair => { return Err("invalid fork".to_string()); } }; diff --git a/beacon_node/store/src/partial_beacon_state.rs b/beacon_node/store/src/partial_beacon_state.rs index 2d8226bf4c4..298291eb4a8 100644 --- a/beacon_node/store/src/partial_beacon_state.rs +++ b/beacon_node/store/src/partial_beacon_state.rs @@ -167,9 +167,6 @@ where #[superstruct(only(Gloas))] pub latest_block_hash: ExecutionBlockHash, - #[superstruct(only(Gloas))] - pub latest_full_slot: Slot, - #[superstruct(only(Gloas))] pub latest_withdrawals_root: Hash256, } @@ -500,7 +497,6 @@ impl TryInto> for PartialBeaconState { builder_pending_payments, builder_pending_withdrawals, latest_block_hash, - latest_full_slot, latest_withdrawals_root ], [historical_summaries] diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index c2ec8b76006..9fede0a90a0 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -1,8 +1,8 @@ use bls::Hash256; use std::mem; use types::{ - BeaconState, BeaconStateError as Error, BeaconStateGloas, BitVector, ChainSpec, EthSpec, - ExecutionBid, Fork, List, Vector, + BeaconState, BeaconStateError as Error, BeaconStateGloas, BitVector, BuilderPendingPayment, + ChainSpec, EthSpec, ExecutionBid, Fork, List, Vector, }; /// Transform a `Fulu` state into a `Gloas` state. @@ -85,10 +85,12 @@ pub fn upgrade_state_to_gloas( pending_consolidations: pre.pending_consolidations.clone(), // Gloas execution_payload_availability: BitVector::default(), // All bits set to false initially - builder_pending_payments: Vector::default(), // Empty vector initially, - builder_pending_withdrawals: List::default(), // Empty list initially, + builder_pending_payments: Vector::new(vec![ + BuilderPendingPayment::default(); + 2 * E::slots_per_epoch() as usize + ])?, + builder_pending_withdrawals: List::default(), // Empty list initially, latest_block_hash: pre.latest_execution_payload_header.block_hash, - latest_full_slot: pre.slot, latest_withdrawals_root: Hash256::default(), // Caches total_active_balance: pre.total_active_balance, diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 279f2eff181..7a9ba9ba0ac 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -597,11 +597,6 @@ where #[metastruct(exclude_from(tree_lists))] pub latest_block_hash: ExecutionBlockHash, - #[test_random(default)] - #[superstruct(only(Gloas))] - #[metastruct(exclude_from(tree_lists))] - pub latest_full_slot: Slot, - #[test_random(default)] #[superstruct(only(Gloas))] #[metastruct(exclude_from(tree_lists))] From 5e562555b0187221ca8396ae139c41e8cbfb4cd1 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Mon, 18 Aug 2025 13:46:01 -0700 Subject: [PATCH 10/16] updates per pr review second wave --- beacon_node/beacon_chain/src/errors.rs | 1 - consensus/state_processing/src/upgrade/gloas.rs | 2 +- consensus/types/src/beacon_block.rs | 1 + consensus/types/src/beacon_block_body.rs | 4 ++-- consensus/types/src/beacon_state.rs | 5 ++--- consensus/types/src/execution_payload_header.rs | 1 - consensus/types/src/signed_beacon_block.rs | 1 + 7 files changed, 7 insertions(+), 8 deletions(-) diff --git a/beacon_node/beacon_chain/src/errors.rs b/beacon_node/beacon_chain/src/errors.rs index 16d0faa303d..a1a0ec74f66 100644 --- a/beacon_node/beacon_chain/src/errors.rs +++ b/beacon_node/beacon_chain/src/errors.rs @@ -292,7 +292,6 @@ pub enum BlockProductionError { MissingBlobs, MissingSyncAggregate, MissingExecutionPayload, - MissingExecutionBid, MissingKzgCommitment(String), TokioJoin(JoinError), BeaconChain(Box), diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index 9fede0a90a0..e1ca954e117 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -87,7 +87,7 @@ pub fn upgrade_state_to_gloas( execution_payload_availability: BitVector::default(), // All bits set to false initially builder_pending_payments: Vector::new(vec![ BuilderPendingPayment::default(); - 2 * E::slots_per_epoch() as usize + E::builder_pending_withdrawals_limit() ])?, builder_pending_withdrawals: List::default(), // Empty list initially, latest_block_hash: pre.latest_execution_payload_header.block_hash, diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 1483c493516..01816500ac8 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -684,6 +684,7 @@ impl> EmptyBlock for BeaconBlockGloa /// TODO(EIP-7732) Mark's branch had the following implementation but not sure if it's needed so will just add header below for reference // impl> BeaconBlockEIP7732 { +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas impl From>> for BeaconBlockGloas> { diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index ecbbf63b145..0671598f742 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -170,7 +170,6 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, 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)), - // TODO(eip-7732): idk if this is right there's no more execution payload Self::Gloas(_) => Err(Error::IncorrectStateVariant), } } @@ -229,6 +228,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, /// Produces the proof of inclusion for a `KzgCommitment` in `self.blob_kzg_commitments` /// at `index` using an existing proof for the `blob_kzg_commitments` field. + /// TODO(EIP7732) Investigate calling functions since this will no longer work for glas since no block_kzg_commitments in the body anymore pub fn complete_kzg_commitment_merkle_proof( &self, index: usize, @@ -240,7 +240,6 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> BeaconBlockBodyRef<'a, E, | Self::Bellatrix(_) | Self::Capella(_) | Self::Gloas(_) => Err(Error::IncorrectStateVariant), - // TODO(eip-7732): Mark's impl had the Self::EIP-7732 variant below, but I think it should produce error instead since no self.blob_kzg_commitments in BeaconState for gloas 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 @@ -521,6 +520,7 @@ impl From>> } // Post-Fulu block bodies without payloads can be converted into block bodies with payloads +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas impl From>> for BeaconBlockBodyGloas> { diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 7a9ba9ba0ac..cb851fb5920 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -1091,7 +1091,7 @@ impl BeaconState { BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRef::Fulu( &state.latest_execution_payload_header, )), - // FIXME(EIP-7732): this is only to make the code compile, needs to be written later + // TODO(EIP-7732): investigate calling functions BeaconState::Gloas(_) => Err(Error::IncorrectStateVariant), } } @@ -1116,7 +1116,7 @@ impl BeaconState { BeaconState::Fulu(state) => Ok(ExecutionPayloadHeaderRefMut::Fulu( &mut state.latest_execution_payload_header, )), - // FIXME(EIP-7732): this is only to make the code compile, needs to be written later + // TODO(EIP-7732): investigate calling functions BeaconState::Gloas(_) => Err(Error::IncorrectStateVariant), } } @@ -1854,7 +1854,6 @@ impl BeaconState { | BeaconState::Altair(_) | BeaconState::Bellatrix(_) | BeaconState::Capella(_) => self.get_validator_churn_limit(spec)?, - // FIXME(EIP-7732): check this BeaconState::Deneb(_) | BeaconState::Electra(_) | BeaconState::Fulu(_) diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index 6a257638b51..c72183ef57d 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -512,7 +512,6 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHead Self::Fulu(Deserialize::deserialize(deserializer).map_err(convert_err)?) } - // FIXME(EIP7732): Check this ForkName::Base | ForkName::Altair | ForkName::Gloas => { return Err(serde::de::Error::custom(format!( "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", diff --git a/consensus/types/src/signed_beacon_block.rs b/consensus/types/src/signed_beacon_block.rs index 98311a73e95..f197839ad30 100644 --- a/consensus/types/src/signed_beacon_block.rs +++ b/consensus/types/src/signed_beacon_block.rs @@ -649,6 +649,7 @@ impl SignedBeaconBlockFulu> { } // We can convert gloas blocks without payloads into blocks "with" payloads. +// TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas impl From>> for SignedBeaconBlockGloas> { From a695fa53b0240062028826db318e80187017de83 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Thu, 21 Aug 2025 15:12:41 -0400 Subject: [PATCH 11/16] updates per pr review --- consensus/types/src/beacon_block_body.rs | 3 --- consensus/types/src/beacon_state.rs | 1 + consensus/types/src/dumb_macros.rs | 1 + consensus/types/src/execution_bid.rs | 3 ++- consensus/types/src/signed_execution_bid.rs | 1 + 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/consensus/types/src/beacon_block_body.rs b/consensus/types/src/beacon_block_body.rs index 0671598f742..d6c6149b4d9 100644 --- a/consensus/types/src/beacon_block_body.rs +++ b/consensus/types/src/beacon_block_body.rs @@ -127,14 +127,11 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Fulu), partial_getter(rename = "execution_payload_fulu"))] #[serde(flatten)] pub execution_payload: Payload::Fulu, - // execution_payload removed from Gloas, replaced with signed_execution_bid below #[superstruct(only(Capella, Deneb, Electra, Fulu, Gloas))] pub bls_to_execution_changes: VariableList, - // blob_kzg_commitments removed from Gloas, moved to `ExecutionPayloadEnvelope` #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, - // execution_requests removed from Gloas, moved to `ExecutionPayloadEnvelope` #[superstruct(only(Electra, Fulu))] pub execution_requests: ExecutionRequests, #[superstruct(only(Gloas))] diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index cb851fb5920..17d56e46447 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -2137,6 +2137,7 @@ impl BeaconState { pub fn is_parent_block_full(&self) -> bool { match self { BeaconState::Base(_) | BeaconState::Altair(_) => false, + // TODO(EIP-7732): check the implications of this when we get to forkchoice modifications BeaconState::Bellatrix(_) | BeaconState::Capella(_) | BeaconState::Deneb(_) diff --git a/consensus/types/src/dumb_macros.rs b/consensus/types/src/dumb_macros.rs index 29bf3fb506c..4eae416bb56 100644 --- a/consensus/types/src/dumb_macros.rs +++ b/consensus/types/src/dumb_macros.rs @@ -3,6 +3,7 @@ // - ExecutionPayloadHeader // - FullPayload // - BlindedPayload +// TODO(EIP-7732): get rid of this whole file and panics once the engine_api is refactored for ePBS #[macro_export] macro_rules! map_execution_payload_into_full_payload { diff --git a/consensus/types/src/execution_bid.rs b/consensus/types/src/execution_bid.rs index 80f9d7684ae..a3672daeacc 100644 --- a/consensus/types/src/execution_bid.rs +++ b/consensus/types/src/execution_bid.rs @@ -11,7 +11,8 @@ use tree_hash_derive::TreeHash; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derivative(PartialEq, Hash)] #[context_deserialize(ForkName)] -// This is what Potuz' spec calls an `ExecutionPayload` even though it's clearly a bid. +// This is what Potuz' spec calls an `ExecutionPayloadHeader` even though it's clearly a bid. +// https://github.com/ethereum/consensus-specs/blob/bba2c7be148d6d921d2ca5e1cc528f5daaf456d9/specs/gloas/beacon-chain.md#executionpayloadheader pub struct ExecutionBid { pub parent_block_hash: ExecutionBlockHash, pub parent_block_root: Hash256, diff --git a/consensus/types/src/signed_execution_bid.rs b/consensus/types/src/signed_execution_bid.rs index 4538b9c2560..4be520565a6 100644 --- a/consensus/types/src/signed_execution_bid.rs +++ b/consensus/types/src/signed_execution_bid.rs @@ -12,6 +12,7 @@ use tree_hash_derive::TreeHash; #[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] #[derivative(PartialEq, Hash)] #[context_deserialize(ForkName)] +// https://github.com/ethereum/consensus-specs/blob/bba2c7be148d6d921d2ca5e1cc528f5daaf456d9/specs/gloas/beacon-chain.md#signedexecutionpayloadheader pub struct SignedExecutionBid { pub message: ExecutionBid, pub signature: Signature, From 7b9f25c341964621affcf4d37360c817eafd8823 Mon Sep 17 00:00:00 2001 From: Mark Mackey Date: Fri, 22 Aug 2025 10:19:55 -0500 Subject: [PATCH 12/16] Fix Compilation/Formatting/Lint Issues --- consensus/types/src/beacon_block.rs | 2 +- consensus/types/src/builder_bid.rs | 5 +-- .../types/src/execution_payload_header.rs | 4 +- consensus/types/src/lib.rs | 2 +- consensus/types/src/light_client_bootstrap.rs | 8 ++-- .../types/src/light_client_finality_update.rs | 6 +-- consensus/types/src/light_client_header.rs | 8 ++-- .../src/light_client_optimistic_update.rs | 6 +-- consensus/types/src/light_client_update.rs | 6 +-- consensus/types/src/payload.rs | 40 +++++++------------ .../src/signed_execution_payload_envelope.rs | 4 +- 11 files changed, 39 insertions(+), 52 deletions(-) diff --git a/consensus/types/src/beacon_block.rs b/consensus/types/src/beacon_block.rs index 01816500ac8..bd47418ad64 100644 --- a/consensus/types/src/beacon_block.rs +++ b/consensus/types/src/beacon_block.rs @@ -681,7 +681,7 @@ impl> EmptyBlock for BeaconBlockGloa } } -/// TODO(EIP-7732) Mark's branch had the following implementation but not sure if it's needed so will just add header below for reference +// TODO(EIP-7732) Mark's branch had the following implementation but not sure if it's needed so will just add header below for reference // impl> BeaconBlockEIP7732 { // TODO(EIP-7732) Look into whether we can remove this in the future since no blinded blocks post-gloas diff --git a/consensus/types/src/builder_bid.rs b/consensus/types/src/builder_bid.rs index 55ad6af6901..854dcc47e33 100644 --- a/consensus/types/src/builder_bid.rs +++ b/consensus/types/src/builder_bid.rs @@ -2,9 +2,8 @@ use crate::beacon_block_body::KzgCommitments; use crate::{ ChainSpec, ContextDeserialize, EthSpec, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, - ExecutionPayloadHeaderFulu, ExecutionPayloadHeaderRef, - ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, ForkVersionDecode, SignedRoot, - Uint256, test_utils::TestRandom, + ExecutionPayloadHeaderFulu, ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, + ExecutionRequests, ForkName, ForkVersionDecode, SignedRoot, Uint256, test_utils::TestRandom, }; use bls::PublicKeyBytes; use bls::Signature; diff --git a/consensus/types/src/execution_payload_header.rs b/consensus/types/src/execution_payload_header.rs index c72183ef57d..62583ff3eb9 100644 --- a/consensus/types/src/execution_payload_header.rs +++ b/consensus/types/src/execution_payload_header.rs @@ -126,7 +126,7 @@ impl ExecutionPayloadHeader { // TODO(newfork): Add a new case here if there are new variable fields if fork_name.gloas_enabled() { // TODO(EIP7732): check this - return 0; + 0 } else if fork_name.bellatrix_enabled() { // Max size of variable length `extra_data` field E::max_extra_data_bytes() * ::ssz_fixed_len() @@ -516,7 +516,7 @@ impl<'de, E: EthSpec> ContextDeserialize<'de, ForkName> for ExecutionPayloadHead return Err(serde::de::Error::custom(format!( "ExecutionPayloadHeader failed to deserialize: unsupported fork '{}'", context - ))) + ))); } }) } diff --git a/consensus/types/src/lib.rs b/consensus/types/src/lib.rs index e4cb84e2919..1e8adf50024 100644 --- a/consensus/types/src/lib.rs +++ b/consensus/types/src/lib.rs @@ -176,8 +176,8 @@ pub use crate::deposit_request::DepositRequest; pub use crate::deposit_tree_snapshot::{DepositTreeSnapshot, FinalizedExecutionBlock}; pub use crate::enr_fork_id::EnrForkId; pub use crate::epoch_cache::{EpochCache, EpochCacheError, EpochCacheKey}; -pub use crate::eth1_data::Eth1Data; pub use crate::eth_spec::EthSpecId; +pub use crate::eth1_data::Eth1Data; pub use crate::execution_bid::ExecutionBid; pub use crate::execution_block_hash::ExecutionBlockHash; pub use crate::execution_block_header::{EncodableExecutionBlockHeader, ExecutionBlockHeader}; diff --git a/consensus/types/src/light_client_bootstrap.rs b/consensus/types/src/light_client_bootstrap.rs index bd6616492e3..fac4e960cbd 100644 --- a/consensus/types/src/light_client_bootstrap.rs +++ b/consensus/types/src/light_client_bootstrap.rs @@ -1,9 +1,9 @@ use crate::context_deserialize; use crate::{ - light_client_update::*, test_utils::TestRandom, BeaconState, ChainSpec, ContextDeserialize, - EthSpec, FixedVector, ForkName, Hash256, LightClientHeader, LightClientHeaderAltair, - LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderElectra, - LightClientHeaderFulu, SignedBlindedBeaconBlock, Slot, SyncCommittee, + BeaconState, ChainSpec, ContextDeserialize, EthSpec, FixedVector, ForkName, Hash256, + LightClientHeader, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, + LightClientHeaderElectra, LightClientHeaderFulu, SignedBlindedBeaconBlock, Slot, SyncCommittee, + light_client_update::*, test_utils::TestRandom, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; diff --git a/consensus/types/src/light_client_finality_update.rs b/consensus/types/src/light_client_finality_update.rs index f61cfb4cb55..db268274962 100644 --- a/consensus/types/src/light_client_finality_update.rs +++ b/consensus/types/src/light_client_finality_update.rs @@ -2,9 +2,9 @@ use super::{EthSpec, FixedVector, Hash256, LightClientHeader, Slot, SyncAggregat use crate::ChainSpec; use crate::context_deserialize; use crate::{ - light_client_update::*, test_utils::TestRandom, ContextDeserialize, ForkName, - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderElectra, LightClientHeaderFulu, SignedBlindedBeaconBlock, + ContextDeserialize, ForkName, LightClientHeaderAltair, LightClientHeaderCapella, + LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, + SignedBlindedBeaconBlock, light_client_update::*, test_utils::TestRandom, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; diff --git a/consensus/types/src/light_client_header.rs b/consensus/types/src/light_client_header.rs index dbd03cf5013..5be73948f3f 100644 --- a/consensus/types/src/light_client_header.rs +++ b/consensus/types/src/light_client_header.rs @@ -1,12 +1,12 @@ use crate::ChainSpec; use crate::context_deserialize; -use crate::{light_client_update::*, BeaconBlockBody}; +use crate::{BeaconBlockBody, light_client_update::*}; use crate::{BeaconBlockHeader, ExecutionPayloadHeader}; use crate::{ContextDeserialize, ForkName}; use crate::{ EthSpec, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, - ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - FixedVector, Hash256, SignedBlindedBeaconBlock, test_utils::TestRandom, + ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, FixedVector, Hash256, + SignedBlindedBeaconBlock, test_utils::TestRandom, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; @@ -148,7 +148,7 @@ impl LightClientHeader { pub fn ssz_max_var_len_for_fork(fork_name: ForkName) -> usize { if fork_name.gloas_enabled() { // TODO(EIP7732): check this - return 0; + 0 } else if fork_name.capella_enabled() { ExecutionPayloadHeader::::ssz_max_var_len_for_fork(fork_name) } else { diff --git a/consensus/types/src/light_client_optimistic_update.rs b/consensus/types/src/light_client_optimistic_update.rs index ff1732c6994..b1c2dbe4917 100644 --- a/consensus/types/src/light_client_optimistic_update.rs +++ b/consensus/types/src/light_client_optimistic_update.rs @@ -2,9 +2,9 @@ use super::{ContextDeserialize, EthSpec, ForkName, LightClientHeader, Slot, Sync use crate::context_deserialize; use crate::test_utils::TestRandom; use crate::{ - light_client_update::*, ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella, - LightClientHeaderDeneb, LightClientHeaderElectra, LightClientHeaderFulu, - SignedBlindedBeaconBlock, + ChainSpec, LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, + LightClientHeaderElectra, LightClientHeaderFulu, SignedBlindedBeaconBlock, + light_client_update::*, }; use derivative::Derivative; use serde::{Deserialize, Deserializer, Serialize}; diff --git a/consensus/types/src/light_client_update.rs b/consensus/types/src/light_client_update.rs index e13f4f13c5a..2562e8dd483 100644 --- a/consensus/types/src/light_client_update.rs +++ b/consensus/types/src/light_client_update.rs @@ -3,9 +3,9 @@ use crate::LightClientHeader; use crate::context_deserialize; use crate::light_client_header::LightClientHeaderElectra; use crate::{ - beacon_state, test_utils::TestRandom, ChainSpec, ContextDeserialize, Epoch, ForkName, - LightClientHeaderAltair, LightClientHeaderCapella, LightClientHeaderDeneb, - LightClientHeaderFulu, SignedBlindedBeaconBlock, + ChainSpec, ContextDeserialize, Epoch, ForkName, LightClientHeaderAltair, + LightClientHeaderCapella, LightClientHeaderDeneb, LightClientHeaderFulu, + SignedBlindedBeaconBlock, beacon_state, test_utils::TestRandom, }; use derivative::Derivative; use safe_arith::ArithError; diff --git a/consensus/types/src/payload.rs b/consensus/types/src/payload.rs index 2bafeccfe64..4d326db5d85 100644 --- a/consensus/types/src/payload.rs +++ b/consensus/types/src/payload.rs @@ -305,18 +305,10 @@ impl ExecPayload for FullPayload { fn withdrawals_root(&self) -> Result { match self { FullPayload::Bellatrix(_) => Err(Error::IncorrectStateVariant), - FullPayload::Capella(ref inner) => { - Ok(inner.execution_payload.withdrawals.tree_hash_root()) - } - FullPayload::Deneb(ref inner) => { - Ok(inner.execution_payload.withdrawals.tree_hash_root()) - } - FullPayload::Electra(ref inner) => { - Ok(inner.execution_payload.withdrawals.tree_hash_root()) - } - FullPayload::Fulu(ref inner) => { - Ok(inner.execution_payload.withdrawals.tree_hash_root()) - } + FullPayload::Capella(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), + FullPayload::Deneb(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), + FullPayload::Electra(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), + FullPayload::Fulu(inner) => Ok(inner.execution_payload.withdrawals.tree_hash_root()), } } @@ -325,9 +317,9 @@ impl ExecPayload for FullPayload { FullPayload::Bellatrix(_) | FullPayload::Capella(_) => { Err(Error::IncorrectStateVariant) } - 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), + FullPayload::Deneb(inner) => Ok(inner.execution_payload.blob_gas_used), + FullPayload::Electra(inner) => Ok(inner.execution_payload.blob_gas_used), + FullPayload::Fulu(inner) => Ok(inner.execution_payload.blob_gas_used), } } @@ -651,14 +643,10 @@ impl ExecPayload for BlindedPayload { fn withdrawals_root(&self) -> Result { match self { BlindedPayload::Bellatrix(_) => Err(Error::IncorrectStateVariant), - BlindedPayload::Capella(ref inner) => { - Ok(inner.execution_payload_header.withdrawals_root) - } - BlindedPayload::Deneb(ref inner) => Ok(inner.execution_payload_header.withdrawals_root), - BlindedPayload::Electra(ref inner) => { - Ok(inner.execution_payload_header.withdrawals_root) - } - BlindedPayload::Fulu(ref inner) => Ok(inner.execution_payload_header.withdrawals_root), + BlindedPayload::Capella(inner) => Ok(inner.execution_payload_header.withdrawals_root), + BlindedPayload::Deneb(inner) => Ok(inner.execution_payload_header.withdrawals_root), + BlindedPayload::Electra(inner) => Ok(inner.execution_payload_header.withdrawals_root), + BlindedPayload::Fulu(inner) => Ok(inner.execution_payload_header.withdrawals_root), } } @@ -667,9 +655,9 @@ impl ExecPayload for BlindedPayload { BlindedPayload::Bellatrix(_) | BlindedPayload::Capella(_) => { Err(Error::IncorrectStateVariant) } - 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), + BlindedPayload::Deneb(inner) => Ok(inner.execution_payload_header.blob_gas_used), + BlindedPayload::Electra(inner) => Ok(inner.execution_payload_header.blob_gas_used), + BlindedPayload::Fulu(inner) => Ok(inner.execution_payload_header.blob_gas_used), } } diff --git a/consensus/types/src/signed_execution_payload_envelope.rs b/consensus/types/src/signed_execution_payload_envelope.rs index bc2c4e994f0..d8a5ca0b232 100644 --- a/consensus/types/src/signed_execution_payload_envelope.rs +++ b/consensus/types/src/signed_execution_payload_envelope.rs @@ -50,8 +50,8 @@ pub struct SignedExecutionPayloadEnvelope { impl SignedExecutionPayloadEnvelope { pub fn message(&self) -> ExecutionPayloadEnvelopeRef { match self { - Self::Gloas(ref signed) => ExecutionPayloadEnvelopeRef::Gloas(&signed.message), - Self::NextFork(ref signed) => ExecutionPayloadEnvelopeRef::NextFork(&signed.message), + Self::Gloas(signed) => ExecutionPayloadEnvelopeRef::Gloas(&signed.message), + Self::NextFork(signed) => ExecutionPayloadEnvelopeRef::NextFork(&signed.message), } } } From 2f1aa10d4df62e2fa85926c80345fb1f8d8c8422 Mon Sep 17 00:00:00 2001 From: Shane K Moore <41407272+shane-moore@users.noreply.github.com> Date: Fri, 29 Aug 2025 09:45:25 -0700 Subject: [PATCH 13/16] Gloas test fixes (#7932) * use builder_pending_payments_limit in upgrade gloas * check_all_blocks_from_altair_to_fulu test to not cover gloas for now * store_tests fixes * remove gloas fork from CI network testing for now * remove gloas fork from CI network testing for now --- Makefile | 11 +++++++++-- beacon_node/beacon_chain/src/beacon_block_streamer.rs | 5 ++--- beacon_node/beacon_chain/tests/store_tests.rs | 2 +- consensus/state_processing/src/upgrade/gloas.rs | 2 +- consensus/types/src/eth_spec.rs | 5 +++++ .../types/src/signed_execution_payload_envelope.rs | 2 +- 6 files changed, 19 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 66d0b682689..3b9d999e9ac 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,11 @@ PROFILE ?= release # they run for different forks. FORKS=phase0 altair bellatrix capella deneb electra fulu gloas +# List of all hard forks up to gloas. This list is used to set env variables for several tests so that +# they run for different forks. +# TODO(EIP-7732) Remove this once we extend network tests to support gloas +FORKS_BEFORE_GLOAS=phase0 altair bellatrix capella deneb electra fulu + # List of all recent hard forks. This list is used to set env variables for http_api tests RECENT_FORKS=electra fulu @@ -188,7 +193,8 @@ nextest-run-ef-tests: ./$(EF_TESTS)/check_all_files_accessed.py $(EF_TESTS)/.accessed_file_log.txt $(EF_TESTS)/consensus-spec-tests # Run the tests in the `beacon_chain` crate for all known forks. -test-beacon-chain: $(patsubst %,test-beacon-chain-%,$(FORKS)) +# TODO(EIP-7732) Extend to support gloas +test-beacon-chain: $(patsubst %,test-beacon-chain-%,$(FORKS_BEFORE_GLOAS)) test-beacon-chain-%: env FORK_NAME=$* cargo nextest run --release --features "fork_from_env,slasher/lmdb,$(TEST_FEATURES)" -p beacon_chain @@ -209,7 +215,8 @@ test-op-pool-%: -p operation_pool # Run the tests in the `network` crate for all known forks. -test-network: $(patsubst %,test-network-%,$(FORKS)) +# TODO(EIP-7732) Extend to support gloas +test-network: $(patsubst %,test-network-%,$(FORKS_BEFORE_GLOAS)) test-network-%: env FORK_NAME=$* cargo nextest run --release \ diff --git a/beacon_node/beacon_chain/src/beacon_block_streamer.rs b/beacon_node/beacon_chain/src/beacon_block_streamer.rs index d4ce38927b2..0ad08c58118 100644 --- a/beacon_node/beacon_chain/src/beacon_block_streamer.rs +++ b/beacon_node/beacon_chain/src/beacon_block_streamer.rs @@ -715,8 +715,9 @@ mod tests { harness } + // TODO(EIP-7732) Extend this test for gloas #[tokio::test] - async fn check_all_blocks_from_altair_to_gloas() { + async fn check_all_blocks_from_altair_to_fulu() { let slots_per_epoch = MinimalEthSpec::slots_per_epoch() as usize; let num_epochs = 12; let bellatrix_fork_epoch = 2usize; @@ -724,7 +725,6 @@ mod tests { let deneb_fork_epoch = 6usize; let electra_fork_epoch = 8usize; let fulu_fork_epoch = 10usize; - let gloas_fork_epoch = 12usize; let num_blocks_produced = num_epochs * slots_per_epoch; let mut spec = test_spec::(); @@ -734,7 +734,6 @@ mod tests { spec.deneb_fork_epoch = Some(Epoch::new(deneb_fork_epoch as u64)); spec.electra_fork_epoch = Some(Epoch::new(electra_fork_epoch as u64)); spec.fulu_fork_epoch = Some(Epoch::new(fulu_fork_epoch as u64)); - spec.gloas_fork_epoch = Some(Epoch::new(gloas_fork_epoch as u64)); let spec = Arc::new(spec); let harness = get_harness(VALIDATOR_COUNT, spec.clone()); diff --git a/beacon_node/beacon_chain/tests/store_tests.rs b/beacon_node/beacon_chain/tests/store_tests.rs index fbb592b510f..62c08adc212 100644 --- a/beacon_node/beacon_chain/tests/store_tests.rs +++ b/beacon_node/beacon_chain/tests/store_tests.rs @@ -137,6 +137,7 @@ fn get_states_descendant_of_block( .collect() } +// TODO(EIP-7732) Extend to support gloas #[tokio::test] async fn light_client_bootstrap_test() { let spec = test_spec::(); @@ -184,7 +185,6 @@ async fn light_client_bootstrap_test() { LightClientBootstrap::Deneb(lc_bootstrap) => lc_bootstrap.header.beacon.slot, LightClientBootstrap::Electra(lc_bootstrap) => lc_bootstrap.header.beacon.slot, LightClientBootstrap::Fulu(lc_bootstrap) => lc_bootstrap.header.beacon.slot, - LightClientBootstrap::Gloas(lc_bootstrap) => lc_bootstrap.header.beacon.slot, }; assert_eq!( diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index e1ca954e117..628af7c5c91 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -87,7 +87,7 @@ pub fn upgrade_state_to_gloas( execution_payload_availability: BitVector::default(), // All bits set to false initially builder_pending_payments: Vector::new(vec![ BuilderPendingPayment::default(); - E::builder_pending_withdrawals_limit() + E::builder_pending_payments_limit() ])?, builder_pending_withdrawals: List::default(), // Empty list initially, latest_block_hash: pre.latest_execution_payload_header.block_hash, diff --git a/consensus/types/src/eth_spec.rs b/consensus/types/src/eth_spec.rs index f4c7abefe71..b00c07865fe 100644 --- a/consensus/types/src/eth_spec.rs +++ b/consensus/types/src/eth_spec.rs @@ -354,6 +354,11 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::PendingConsolidationsLimit::to_usize() } + /// Returns the `BUILDER_PENDING_PAYMENTS_LIMIT` constant for this specification. + fn builder_pending_payments_limit() -> usize { + Self::BuilderPendingPaymentsLimit::to_usize() + } + /// Returns the `BUILDER_PENDING_WITHDRAWALS_LIMIT` constant for this specification. fn builder_pending_withdrawals_limit() -> usize { Self::BuilderPendingWithdrawalsLimit::to_usize() diff --git a/consensus/types/src/signed_execution_payload_envelope.rs b/consensus/types/src/signed_execution_payload_envelope.rs index d8a5ca0b232..bba7b149fd3 100644 --- a/consensus/types/src/signed_execution_payload_envelope.rs +++ b/consensus/types/src/signed_execution_payload_envelope.rs @@ -48,7 +48,7 @@ pub struct SignedExecutionPayloadEnvelope { } impl SignedExecutionPayloadEnvelope { - pub fn message(&self) -> ExecutionPayloadEnvelopeRef { + pub fn message(&self) -> ExecutionPayloadEnvelopeRef<'_, E> { match self { Self::Gloas(signed) => ExecutionPayloadEnvelopeRef::Gloas(&signed.message), Self::NextFork(signed) => ExecutionPayloadEnvelopeRef::NextFork(&signed.message), From 2e9816b960cfee9c9cb6483167a8352360595f93 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Wed, 17 Sep 2025 11:13:48 -0700 Subject: [PATCH 14/16] add ptc_duties to duties_service --- beacon_node/http_api/tests/tests.rs | 2 + common/eth2/src/lib.rs | 28 ++ common/eth2/src/types.rs | 8 + testing/simulator/src/checks.rs | 2 + validator_client/validator_metrics/src/lib.rs | 12 + .../validator_services/src/duties_service.rs | 356 +++++++++++++++++- 6 files changed, 407 insertions(+), 1 deletion(-) diff --git a/beacon_node/http_api/tests/tests.rs b/beacon_node/http_api/tests/tests.rs index 92abbd84c76..cf46f99c92d 100644 --- a/beacon_node/http_api/tests/tests.rs +++ b/beacon_node/http_api/tests/tests.rs @@ -3278,6 +3278,8 @@ impl ApiTester { self } + // TODO(EIP-7732): Add test_get_validator_duties_ptc function to test PTC duties endpoint + pub async fn test_get_validator_duties_early(self) -> Self { let current_epoch = self.chain.epoch().unwrap(); let next_epoch = current_epoch + 1; diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 9709b0631f1..8424d45c337 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -60,6 +60,8 @@ const HTTP_PROPOSAL_TIMEOUT_QUOTIENT: u32 = 2; const HTTP_PROPOSER_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; +// TODO(EIP-7732): Determine what this quotient should be +const HTTP_PTC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4; const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4; @@ -155,6 +157,7 @@ pub struct Timeouts { pub proposer_duties: Duration, pub sync_committee_contribution: Duration, pub sync_duties: Duration, + pub ptc_duties: Duration, pub get_beacon_blocks_ssz: Duration, pub get_debug_beacon_states: Duration, pub get_deposit_snapshot: Duration, @@ -173,6 +176,7 @@ impl Timeouts { proposer_duties: timeout, sync_committee_contribution: timeout, sync_duties: timeout, + ptc_duties: timeout, get_beacon_blocks_ssz: timeout, get_debug_beacon_states: timeout, get_deposit_snapshot: timeout, @@ -193,6 +197,7 @@ impl Timeouts { sync_committee_contribution: base_timeout / HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT, sync_duties: base_timeout / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT, + ptc_duties: base_timeout / HTTP_PTC_DUTIES_TIMEOUT_QUOTIENT, get_beacon_blocks_ssz: base_timeout / HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT, get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT, get_deposit_snapshot: base_timeout / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT, @@ -2841,6 +2846,29 @@ impl BeaconNodeHttpClient { ) .await } + + /// `POST validator/duties/ptc/{epoch}` + pub async fn post_validator_duties_ptc( + &self, + epoch: Epoch, + indices: &[u64], + ) -> Result>, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("validator") + .push("duties") + .push("ptc") + .push(&epoch.to_string()); + + self.post_with_timeout_and_response( + path, + &ValidatorIndexDataRef(indices), + self.timeouts.ptc_duties, + ) + .await + } } /// Returns `Ok(response)` if the response is a `200 OK` response. Otherwise, creates an diff --git a/common/eth2/src/types.rs b/common/eth2/src/types.rs index 169551e35bc..8f3059decbf 100644 --- a/common/eth2/src/types.rs +++ b/common/eth2/src/types.rs @@ -773,6 +773,14 @@ pub struct ProposerData { pub slot: Slot, } +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct PtcDuty { + pub pubkey: PublicKeyBytes, + #[serde(with = "serde_utils::quoted_u64")] + pub validator_index: u64, + pub slot: Slot, +} + #[derive(Clone, Deserialize)] pub struct ValidatorBlocksQuery { pub randao_reveal: SignatureBytes, diff --git a/testing/simulator/src/checks.rs b/testing/simulator/src/checks.rs index 1368c495cd8..615d91875e3 100644 --- a/testing/simulator/src/checks.rs +++ b/testing/simulator/src/checks.rs @@ -219,6 +219,8 @@ pub async fn verify_full_sync_aggregates_up_to( Ok(()) } +// TODO(EIP-7732): Add verify_ptc_duties_executed function to verify that PTC duties are being fetched and executed correctly when Gloas fork is enabled + /// Verify that the first merged PoS block got finalized. pub async fn verify_transition_block_finalized( network: LocalNetwork, diff --git a/validator_client/validator_metrics/src/lib.rs b/validator_client/validator_metrics/src/lib.rs index 060d8a4edd2..46a86381f91 100644 --- a/validator_client/validator_metrics/src/lib.rs +++ b/validator_client/validator_metrics/src/lib.rs @@ -22,7 +22,12 @@ pub const UPDATE_ATTESTERS_CURRENT_EPOCH: &str = "update_attesters_current_epoch pub const UPDATE_ATTESTERS_NEXT_EPOCH: &str = "update_attesters_next_epoch"; pub const UPDATE_ATTESTERS_FETCH: &str = "update_attesters_fetch"; pub const UPDATE_ATTESTERS_STORE: &str = "update_attesters_store"; +pub const UPDATE_PTC_CURRENT_EPOCH: &str = "update_ptc_current_epoch"; +pub const UPDATE_PTC_NEXT_EPOCH: &str = "update_ptc_next_epoch"; +pub const UPDATE_PTC_FETCH: &str = "update_ptc_fetch"; +pub const UPDATE_PTC_STORE: &str = "update_ptc_store"; pub const ATTESTER_DUTIES_HTTP_POST: &str = "attester_duties_http_post"; +pub const PTC_DUTIES_HTTP_POST: &str = "ptc_duties_http_post"; pub const PROPOSER_DUTIES_HTTP_GET: &str = "proposer_duties_http_get"; pub const VALIDATOR_DUTIES_SYNC_HTTP_POST: &str = "validator_duties_sync_http_post"; pub const VALIDATOR_ID_HTTP_GET: &str = "validator_id_http_get"; @@ -162,6 +167,13 @@ pub static ATTESTER_COUNT: LazyLock> = LazyLock::new(|| { &["task"], ) }); +pub static PTC_COUNT: LazyLock> = LazyLock::new(|| { + try_create_int_gauge_vec( + "vc_beacon_ptc_count", + "Number of PTC (Payload Timeliness Committee) validators on this host", + &["task"], + ) +}); pub static PROPOSAL_CHANGED: LazyLock> = LazyLock::new(|| { try_create_int_counter( "vc_beacon_block_proposal_changed", diff --git a/validator_client/validator_services/src/duties_service.rs b/validator_client/validator_services/src/duties_service.rs index 009537bc439..0f31ca279c0 100644 --- a/validator_client/validator_services/src/duties_service.rs +++ b/validator_client/validator_services/src/duties_service.rs @@ -11,7 +11,8 @@ use crate::sync::SyncDutiesMap; use crate::sync::poll_sync_committee_duties; use beacon_node_fallback::{ApiTopic, BeaconNodeFallback}; use eth2::types::{ - AttesterData, BeaconCommitteeSubscription, DutiesResponse, ProposerData, StateId, ValidatorId, + AttesterData, BeaconCommitteeSubscription, DutiesResponse, ProposerData, PtcDuty, StateId, + ValidatorId, }; use futures::{StreamExt, stream}; use parking_lot::RwLock; @@ -52,6 +53,7 @@ const VALIDATOR_METRICS_MIN_COUNT: usize = 64; /// The initial request is used to determine if further requests are required, so that it /// reduces the amount of data that needs to be transferred. const INITIAL_DUTIES_QUERY_SIZE: usize = 1; +const INITIAL_PTC_DUTIES_QUERY_SIZE: usize = 1; /// Offsets from the attestation duty slot at which a subscription should be sent. const ATTESTATION_SUBSCRIPTION_OFFSETS: [u64; 8] = [3, 4, 5, 6, 7, 8, 16, 32]; @@ -89,6 +91,7 @@ const _: () = assert!(ATTESTATION_SUBSCRIPTION_OFFSETS[0] > MIN_ATTESTATION_SUBS pub enum Error { UnableToReadSlotClock, FailedToDownloadAttesters(#[allow(dead_code)] String), + FailedToDownloadPtc(#[allow(dead_code)] String), FailedToProduceSelectionProof(#[allow(dead_code)] ValidatorStoreError), InvalidModulo(#[allow(dead_code)] ArithError), Arith(#[allow(dead_code)] ArithError), @@ -203,6 +206,7 @@ type DependentRoot = Hash256; type AttesterMap = HashMap>; type ProposerMap = HashMap)>; +type PtcMap = HashMap)>; pub struct DutiesServiceBuilder { /// Provides the canonical list of locally-managed validators. @@ -290,6 +294,7 @@ impl DutiesServiceBuilder { attesters: Default::default(), proposers: Default::default(), sync_duties: SyncDutiesMap::new(self.distributed), + ptc_duties: Default::default(), validator_store: self .validator_store .ok_or("Cannot build DutiesService without validator_store")?, @@ -320,6 +325,8 @@ pub struct DutiesService { pub proposers: RwLock, /// Map from validator index to sync committee duties. pub sync_duties: SyncDutiesMap, + /// Maps an epoch to all *local* PTC duties for that epoch with their dependent root. + pub ptc_duties: RwLock, /// Provides the canonical list of locally-managed validators. pub validator_store: Arc, /// Maps unknown validator pubkeys to the next slot time when a poll should be conducted again. @@ -378,6 +385,24 @@ impl DutiesService { .count() } + /// Returns the total number of validators that have PTC duties in the given epoch. + pub fn ptc_count(&self, epoch: Epoch) -> usize { + // Only collect validators that are considered safe in terms of doppelganger protection. + let signing_pubkeys: HashSet<_> = self + .validator_store + .voting_pubkeys(DoppelgangerStatus::only_safe); + self.ptc_duties + .read() + .get(&epoch) + .map(|(_, duties)| { + duties + .keys() + .filter(|pubkey| signing_pubkeys.contains(pubkey)) + .count() + }) + .unwrap_or(0) + } + /// Returns the total number of validators that are in a doppelganger detection period. pub fn doppelganger_detecting_count(&self) -> usize { self.validator_store @@ -440,6 +465,25 @@ impl DutiesService { self.enable_high_validator_count_metrics || self.total_validator_count() <= VALIDATOR_METRICS_MIN_COUNT } + + /// Get PTC duties for a specific slot. + /// + /// Returns duties for local validators who have PTC assignments at the given slot. + pub fn get_ptc_duties_for_slot(&self, slot: Slot) -> Vec { + let epoch = slot.epoch(S::E::slots_per_epoch()); + + self.ptc_duties + .read() + .get(&epoch) + .map(|(_, duties)| { + duties + .values() + .filter(|duty| duty.slot == slot) + .cloned() + .collect() + }) + .unwrap_or_default() + } } /// Start the service that periodically polls the beacon node for validator duties. This will start @@ -568,6 +612,61 @@ pub fn start_update_service }, "duties_service_sync_committee", ); + + // Spawn the task which keeps track of local PTC duties. + // Only start PTC duties service if Gloas fork is scheduled. + if core_duties_service.spec.is_gloas_scheduled() { + let duties_service = core_duties_service.clone(); + core_duties_service.executor.spawn( + async move { + loop { + // Check if we've reached the Gloas fork epoch before polling + let Some(current_slot) = duties_service.slot_clock.now() else { + // Unable to read slot clock, sleep and try again + sleep(duties_service.slot_clock.slot_duration()).await; + continue; + }; + + let current_epoch = current_slot.epoch(S::E::slots_per_epoch()); + let Some(gloas_fork_epoch) = duties_service.spec.gloas_fork_epoch else { + // Gloas fork epoch not configured, should not reach here + break; + }; + + if current_epoch < gloas_fork_epoch { + // Wait until the next slot and check again + if let Some(duration) = duties_service.slot_clock.duration_to_next_slot() { + sleep(duration).await; + } else { + sleep(duties_service.slot_clock.slot_duration()).await; + } + continue; + } + + if let Err(e) = poll_beacon_ptc_attesters(&duties_service).await { + error!( + error = ?e, + "Failed to poll PTC duties" + ); + } + + // Wait until the next slot before polling again. + // This doesn't mean that the beacon node will get polled every slot + // as the PTC duties service will return early if it deems it already has + // enough information. + if let Some(duration) = duties_service.slot_clock.duration_to_next_slot() { + sleep(duration).await; + } else { + // Just sleep for one slot if we are unable to read the system clock, this gives + // us an opportunity for the clock to eventually come good. + sleep(duties_service.slot_clock.slot_duration()).await; + continue; + } + } + }, + "duties_service_ptc", + ); + } } /// Iterate through all the voting pubkeys in the `ValidatorStore` and attempt to learn any unknown @@ -1058,6 +1157,25 @@ fn get_uninitialized_validators( .collect::>() } +/// Get a filtered list of local validators for which we don't already know their PTC duties for that epoch +fn get_uninitialized_ptc_validators( + duties_service: &Arc>, + epoch: &Epoch, + local_pubkeys: &HashSet, +) -> Vec { + let ptc_duties = duties_service.ptc_duties.read(); + + local_pubkeys + .iter() + .filter(|pubkey| { + ptc_duties + .get(epoch) + .map_or(true, |(_, duties)| !duties.contains_key(pubkey)) + }) + .filter_map(|pubkey| duties_service.validator_store.validator_index(pubkey)) + .collect::>() +} + fn update_per_validator_duty_metrics( duties_service: &Arc>, epoch: Epoch, @@ -1119,6 +1237,26 @@ async fn post_validator_duties_attester( + duties_service: &Arc>, + epoch: Epoch, + validator_indices: &[u64], +) -> Result>, Error> { + duties_service + .beacon_nodes + .first_success(|beacon_node| async move { + let _timer = validator_metrics::start_timer_vec( + &validator_metrics::DUTIES_SERVICE_TIMES, + &[validator_metrics::PTC_DUTIES_HTTP_POST], + ); + beacon_node + .post_validator_duties_ptc(epoch, validator_indices) + .await + }) + .await + .map_err(|e| Error::FailedToDownloadPtc(e.to_string())) +} + /// Compute the attestation selection proofs for the `duties` and add them to the `attesters` map. /// /// Duties are computed in batches each slot. If a re-org is detected then the process will @@ -1419,6 +1557,222 @@ async fn poll_beacon_proposers( Ok(()) } +/// Query the beacon node for ptc duties for any known validators. +async fn poll_beacon_ptc_attesters( + duties_service: &Arc>, +) -> Result<(), Error> { + let current_epoch_timer = validator_metrics::start_timer_vec( + &validator_metrics::DUTIES_SERVICE_TIMES, + &[validator_metrics::UPDATE_PTC_CURRENT_EPOCH], + ); + + let current_slot = duties_service + .slot_clock + .now() + .ok_or(Error::UnableToReadSlotClock)?; + let current_epoch = current_slot.epoch(S::E::slots_per_epoch()); + + // Collect *all* pubkeys, even those undergoing doppelganger protection. + let local_pubkeys: HashSet<_> = duties_service + .validator_store + .voting_pubkeys(DoppelgangerStatus::ignored); + + let local_indices = { + let mut local_indices = Vec::with_capacity(local_pubkeys.len()); + + for &pubkey in &local_pubkeys { + if let Some(validator_index) = duties_service.validator_store.validator_index(&pubkey) { + local_indices.push(validator_index) + } + } + local_indices + }; + + // Poll for current epoch + if let Err(e) = poll_beacon_ptc_attesters_for_epoch( + duties_service, + current_epoch, + &local_indices, + &local_pubkeys, + ) + .await + { + error!( + %current_epoch, + request_epoch = %current_epoch, + err = ?e, + "Failed to download PTC duties" + ); + } + drop(current_epoch_timer); + let next_epoch_timer = validator_metrics::start_timer_vec( + &validator_metrics::DUTIES_SERVICE_TIMES, + &[validator_metrics::UPDATE_PTC_NEXT_EPOCH], + ); + + // Poll for next epoch + let next_epoch = current_epoch + 1; + if let Err(e) = poll_beacon_ptc_attesters_for_epoch( + duties_service, + next_epoch, + &local_indices, + &local_pubkeys, + ) + .await + { + error!( + %current_epoch, + request_epoch = %next_epoch, + err = ?e, + "Failed to download PTC duties" + ); + } + drop(next_epoch_timer); + + // Prune old duties. + duties_service + .ptc_duties + .write() + .retain(|&epoch, _| epoch + HISTORICAL_DUTIES_EPOCHS >= current_epoch); + + Ok(()) +} + +/// For the given `local_indices` and `local_pubkeys`, download the PTC duties for the given `epoch` and +/// store them in `duties_service.ptc_duties` using bandwidth optimization. +async fn poll_beacon_ptc_attesters_for_epoch< + S: ValidatorStore + 'static, + T: SlotClock + 'static, +>( + duties_service: &Arc>, + epoch: Epoch, + local_indices: &[u64], + local_pubkeys: &HashSet, +) -> Result<(), Error> { + // No need to bother the BN if we don't have any validators. + if local_indices.is_empty() { + debug!( + %epoch, + "No validators, not downloading PTC duties" + ); + return Ok(()); + } + + let fetch_timer = validator_metrics::start_timer_vec( + &validator_metrics::DUTIES_SERVICE_TIMES, + &[validator_metrics::UPDATE_PTC_FETCH], + ); + + // Request duties for all uninitialized validators. If there isn't any, we will just request for + // `INITIAL_PTC_DUTIES_QUERY_SIZE` validators. We use the `dependent_root` in the response to + // determine whether validator duties need to be updated. This is to ensure that we don't + // request for extra data unless necessary in order to save on network bandwidth. + let uninitialized_validators = + get_uninitialized_ptc_validators(duties_service, &epoch, local_pubkeys); + let initial_indices_to_request = if !uninitialized_validators.is_empty() { + uninitialized_validators.as_slice() + } else { + &local_indices[0..min(INITIAL_PTC_DUTIES_QUERY_SIZE, local_indices.len())] + }; + + let response = + post_validator_duties_ptc(duties_service, epoch, initial_indices_to_request).await?; + let dependent_root = response.dependent_root; + + // Check if we need to update duties for this epoch and collect validators to update. + // We update if we have no epoch data OR if the dependent_root changed. + let validators_to_update = { + // Avoid holding the read-lock for any longer than required. + let ptc_duties = duties_service.ptc_duties.read(); + let needs_update = ptc_duties + .get(&epoch) + .map_or(true, |(prior_root, _duties)| { + // Update if dependent_root changed + *prior_root != dependent_root + }); + + if needs_update { + local_pubkeys.iter().collect::>() + } else { + Vec::new() + } + }; + + if validators_to_update.is_empty() { + // No validators have conflicting (epoch, dependent_root) values for this epoch. + return Ok(()); + } + + // Make a request for all indices that require updating which we have not already made a request for. + let indices_to_request = validators_to_update + .iter() + .filter_map(|pubkey| duties_service.validator_store.validator_index(pubkey)) + .filter(|validator_index| !initial_indices_to_request.contains(validator_index)) + .collect::>(); + + // Filter the initial duties by their relevance so that we don't hit warnings about + // overwriting duties. + let new_initial_duties = response + .data + .into_iter() + .filter(|duty| validators_to_update.contains(&&duty.pubkey)); + + let mut new_duties = if !indices_to_request.is_empty() { + post_validator_duties_ptc(duties_service, epoch, indices_to_request.as_slice()) + .await? + .data + } else { + vec![] + }; + new_duties.extend(new_initial_duties); + + drop(fetch_timer); + + let _store_timer = validator_metrics::start_timer_vec( + &validator_metrics::DUTIES_SERVICE_TIMES, + &[validator_metrics::UPDATE_PTC_STORE], + ); + + debug!( + %dependent_root, + num_new_duties = new_duties.len(), + "Downloaded PTC duties" + ); + + // Update duties - we only reach here if dependent_root changed or epoch is missing + let mut ptc_duties = duties_service.ptc_duties.write(); + + let duties_by_pubkey: HashMap = new_duties + .into_iter() + .map(|duty| (duty.pubkey, duty)) + .collect(); + + match ptc_duties.entry(epoch) { + hash_map::Entry::Occupied(mut entry) => { + // Dependent root must have changed, so we do complete replacement. + // We cannot support partial updates for the same dependent_root. + // The beacon node may return incomplete duty lists and we cannot distinguish between "no duties" and + // "duties not included in this response". We could query all local validators in each + // `post_validator_duties_ptc` call regardless of dependent_root changes, but the bandwidth + // cost is likely not justified since PTC assignments are sparse. + let (existing_root, _existing_duties) = entry.get(); + debug!( + old_root = %existing_root, + new_root = %dependent_root, + "PTC dependent root changed, replacing all duties" + ); + + *entry.get_mut() = (dependent_root, duties_by_pubkey); + } + hash_map::Entry::Vacant(entry) => { + // No existing duties for this epoch + entry.insert((dependent_root, duties_by_pubkey)); + } + } + + Ok(()) +} + /// Notify the block service if it should produce a block. async fn notify_block_production_service( current_slot: Slot, From 6caf8dbc4368c8dec2387a5c2e6236b60c2deb43 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Wed, 17 Sep 2025 11:50:56 -0700 Subject: [PATCH 15/16] remove get_uninitialized_ptc_validators --- .../validator_services/src/duties_service.rs | 34 +++---------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/validator_client/validator_services/src/duties_service.rs b/validator_client/validator_services/src/duties_service.rs index 0f31ca279c0..62cb4b54018 100644 --- a/validator_client/validator_services/src/duties_service.rs +++ b/validator_client/validator_services/src/duties_service.rs @@ -1157,24 +1157,6 @@ fn get_uninitialized_validators( .collect::>() } -/// Get a filtered list of local validators for which we don't already know their PTC duties for that epoch -fn get_uninitialized_ptc_validators( - duties_service: &Arc>, - epoch: &Epoch, - local_pubkeys: &HashSet, -) -> Vec { - let ptc_duties = duties_service.ptc_duties.read(); - - local_pubkeys - .iter() - .filter(|pubkey| { - ptc_duties - .get(epoch) - .map_or(true, |(_, duties)| !duties.contains_key(pubkey)) - }) - .filter_map(|pubkey| duties_service.validator_store.validator_index(pubkey)) - .collect::>() -} fn update_per_validator_duty_metrics( duties_service: &Arc>, @@ -1663,17 +1645,11 @@ async fn poll_beacon_ptc_attesters_for_epoch< &[validator_metrics::UPDATE_PTC_FETCH], ); - // Request duties for all uninitialized validators. If there isn't any, we will just request for - // `INITIAL_PTC_DUTIES_QUERY_SIZE` validators. We use the `dependent_root` in the response to - // determine whether validator duties need to be updated. This is to ensure that we don't - // request for extra data unless necessary in order to save on network bandwidth. - let uninitialized_validators = - get_uninitialized_ptc_validators(duties_service, &epoch, local_pubkeys); - let initial_indices_to_request = if !uninitialized_validators.is_empty() { - uninitialized_validators.as_slice() - } else { - &local_indices[0..min(INITIAL_PTC_DUTIES_QUERY_SIZE, local_indices.len())] - }; + // Make a small initial request to check the dependent_root and determine if we need to fetch + // all duties. We use the `dependent_root` in the response to determine whether validator + // duties need to be updated. This is to ensure that we don't request for extra data unless + // necessary in order to save on network bandwidth. + let initial_indices_to_request = &local_indices[0..min(INITIAL_PTC_DUTIES_QUERY_SIZE, local_indices.len())]; let response = post_validator_duties_ptc(duties_service, epoch, initial_indices_to_request).await?; From 2bff6b7f6b3285998b65887183df0797fd3fb686 Mon Sep 17 00:00:00 2001 From: shane-moore Date: Fri, 19 Sep 2025 18:03:43 -0700 Subject: [PATCH 16/16] add payload attestation service --- common/eth2/src/lib.rs | 48 +++ .../types/src/payload_attestation_message.rs | 11 + .../lighthouse_validator_store/src/lib.rs | 42 +- validator_client/signing_method/src/lib.rs | 5 + .../signing_method/src/web3signer.rs | 3 + validator_client/src/cli.rs | 2 +- validator_client/src/lib.rs | 17 + validator_client/validator_metrics/src/lib.rs | 11 + .../validator_services/src/duties_service.rs | 4 +- .../validator_services/src/lib.rs | 1 + .../src/payload_attestation_service.rs | 371 ++++++++++++++++++ validator_client/validator_store/src/lib.rs | 13 +- 12 files changed, 517 insertions(+), 11 deletions(-) create mode 100644 validator_client/validator_services/src/payload_attestation_service.rs diff --git a/common/eth2/src/lib.rs b/common/eth2/src/lib.rs index 8424d45c337..fcff50afd02 100644 --- a/common/eth2/src/lib.rs +++ b/common/eth2/src/lib.rs @@ -62,6 +62,8 @@ const HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; // TODO(EIP-7732): Determine what this quotient should be const HTTP_PTC_DUTIES_TIMEOUT_QUOTIENT: u32 = 4; +// TODO(EIP-7732): Determine what this quotient should be +const HTTP_PAYLOAD_ATTESTATION_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT: u32 = 4; const HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT: u32 = 4; const HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT: u32 = 4; @@ -158,6 +160,7 @@ pub struct Timeouts { pub sync_committee_contribution: Duration, pub sync_duties: Duration, pub ptc_duties: Duration, + pub payload_attestation: Duration, pub get_beacon_blocks_ssz: Duration, pub get_debug_beacon_states: Duration, pub get_deposit_snapshot: Duration, @@ -177,6 +180,7 @@ impl Timeouts { sync_committee_contribution: timeout, sync_duties: timeout, ptc_duties: timeout, + payload_attestation: timeout, get_beacon_blocks_ssz: timeout, get_debug_beacon_states: timeout, get_deposit_snapshot: timeout, @@ -198,6 +202,7 @@ impl Timeouts { / HTTP_SYNC_COMMITTEE_CONTRIBUTION_TIMEOUT_QUOTIENT, sync_duties: base_timeout / HTTP_SYNC_DUTIES_TIMEOUT_QUOTIENT, ptc_duties: base_timeout / HTTP_PTC_DUTIES_TIMEOUT_QUOTIENT, + payload_attestation: base_timeout / HTTP_PAYLOAD_ATTESTATION_TIMEOUT_QUOTIENT, get_beacon_blocks_ssz: base_timeout / HTTP_GET_BEACON_BLOCK_SSZ_TIMEOUT_QUOTIENT, get_debug_beacon_states: base_timeout / HTTP_GET_DEBUG_BEACON_STATE_QUOTIENT, get_deposit_snapshot: base_timeout / HTTP_GET_DEPOSIT_SNAPSHOT_QUOTIENT, @@ -2606,6 +2611,49 @@ impl BeaconNodeHttpClient { .map(|opt| opt.map(BeaconResponse::ForkVersioned)) } + /// `GET validator/payload_attestation_data/{slot}` + pub async fn get_validator_payload_attestation_data( + &self, + slot: Slot, + ) -> Result, Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("validator") + .push("payload_attestation_data") + .push(&slot.to_string()); + + self.get_with_timeout(path, self.timeouts.payload_attestation) + .await + .map(BeaconResponse::ForkVersioned) + } + + /// `POST beacon/pool/payload_attestations` + pub async fn post_beacon_pool_payload_attestations( + &self, + payload_attestations: Vec, + fork_name: ForkName, + ) -> Result<(), Error> { + let mut path = self.eth_path(V1)?; + + path.path_segments_mut() + .map_err(|()| Error::InvalidUrl(self.server.clone()))? + .push("beacon") + .push("pool") + .push("payload_attestations"); + + self.post_with_timeout_and_consensus_header( + path, + &payload_attestations, + self.timeouts.payload_attestation, + fork_name, + ) + .await?; + + Ok(()) + } + /// `GET validator/sync_committee_contribution` pub async fn get_validator_sync_committee_contribution( &self, diff --git a/consensus/types/src/payload_attestation_message.rs b/consensus/types/src/payload_attestation_message.rs index 46179e1deac..406c17a372b 100644 --- a/consensus/types/src/payload_attestation_message.rs +++ b/consensus/types/src/payload_attestation_message.rs @@ -15,6 +15,17 @@ pub struct PayloadAttestationMessage { pub signature: AggregateSignature, } +impl PayloadAttestationMessage { + /// Create a `PayloadAttestationMessage` with an empty signature for signing purposes. + pub fn empty_for_signing(validator_index: u64, data: PayloadAttestationData) -> Self { + Self { + validator_index, + data, + signature: AggregateSignature::empty(), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/validator_client/lighthouse_validator_store/src/lib.rs b/validator_client/lighthouse_validator_store/src/lib.rs index ed1ffa6bf6f..8d22d967475 100644 --- a/validator_client/lighthouse_validator_store/src/lib.rs +++ b/validator_client/lighthouse_validator_store/src/lib.rs @@ -19,11 +19,11 @@ use tracing::{error, info, warn}; use types::{ AbstractExecPayload, Address, AggregateAndProof, Attestation, BeaconBlock, BlindedPayload, ChainSpec, ContributionAndProof, Domain, Epoch, EthSpec, Fork, Graffiti, Hash256, - PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, SignedBeaconBlock, - SignedContributionAndProof, SignedRoot, SignedValidatorRegistrationData, SignedVoluntaryExit, - Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, SyncCommitteeMessage, - SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, VoluntaryExit, - graffiti::GraffitiString, + PayloadAttestationMessage, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, + SignedBeaconBlock, SignedContributionAndProof, SignedRoot, SignedValidatorRegistrationData, + SignedVoluntaryExit, Slot, SyncAggregatorSelectionData, SyncCommitteeContribution, + SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, + VoluntaryExit, graffiti::GraffitiString, }; use validator_store::{ DoppelgangerStatus, Error as ValidatorStoreError, ProposalData, SignedBlock, UnsignedBlock, @@ -745,6 +745,38 @@ impl ValidatorStore for LighthouseValidatorS } } + async fn sign_payload_attestation_message( + &self, + validator_pubkey: PublicKeyBytes, + payload_attestation_message: &mut PayloadAttestationMessage, + ) -> Result<(), Error> { + // Get the signing method and check doppelganger protection. + let signing_method = self.doppelganger_checked_signing_method(validator_pubkey)?; + + // Calculate the signing epoch from the payload attestation data slot + let signing_epoch = payload_attestation_message + .data + .slot + .epoch(E::slots_per_epoch()); + let signing_context = self.signing_context(Domain::PTCAttester, signing_epoch); + + // Unlike regular attestations, payload attestations are NOT slashable offenses, + // so we don't need slashing protection checks + let signature = signing_method + .get_signature::>( + SignableMessage::PayloadAttestationData(&payload_attestation_message.data), + signing_context, + &self.spec, + &self.task_executor, + ) + .await + .map_err(Error::SpecificError)?; + + payload_attestation_message.signature = (&signature).into(); + + Ok(()) + } + async fn sign_attestation( &self, validator_pubkey: PublicKeyBytes, diff --git a/validator_client/signing_method/src/lib.rs b/validator_client/signing_method/src/lib.rs index c535415b1e9..76838102659 100644 --- a/validator_client/signing_method/src/lib.rs +++ b/validator_client/signing_method/src/lib.rs @@ -47,6 +47,7 @@ pub enum SignableMessage<'a, E: EthSpec, Payload: AbstractExecPayload = FullP SignedContributionAndProof(&'a ContributionAndProof), ValidatorRegistration(&'a ValidatorRegistrationData), VoluntaryExit(&'a VoluntaryExit), + PayloadAttestationData(&'a PayloadAttestationData), } impl> SignableMessage<'_, E, Payload> { @@ -68,6 +69,7 @@ impl> SignableMessage<'_, E, Payload SignableMessage::SignedContributionAndProof(c) => c.signing_root(domain), SignableMessage::ValidatorRegistration(v) => v.signing_root(domain), SignableMessage::VoluntaryExit(exit) => exit.signing_root(domain), + SignableMessage::PayloadAttestationData(data) => data.signing_root(domain), } } } @@ -228,6 +230,9 @@ impl SigningMethod { Web3SignerObject::ValidatorRegistration(v) } SignableMessage::VoluntaryExit(e) => Web3SignerObject::VoluntaryExit(e), + SignableMessage::PayloadAttestationData(data) => { + Web3SignerObject::PayloadAttestation(data) + } }; // Determine the Web3Signer message type. diff --git a/validator_client/signing_method/src/web3signer.rs b/validator_client/signing_method/src/web3signer.rs index 99fad103035..f3c0d6ff027 100644 --- a/validator_client/signing_method/src/web3signer.rs +++ b/validator_client/signing_method/src/web3signer.rs @@ -18,6 +18,7 @@ pub enum MessageType { SyncCommitteeSelectionProof, SyncCommitteeContributionAndProof, ValidatorRegistration, + PayloadAttestation, } #[derive(Debug, PartialEq, Copy, Clone, Serialize)] @@ -74,6 +75,7 @@ pub enum Web3SignerObject<'a, E: EthSpec, Payload: AbstractExecPayload> { SyncAggregatorSelectionData(&'a SyncAggregatorSelectionData), ContributionAndProof(&'a ContributionAndProof), ValidatorRegistration(&'a ValidatorRegistrationData), + PayloadAttestation(&'a PayloadAttestationData), } impl<'a, E: EthSpec, Payload: AbstractExecPayload> Web3SignerObject<'a, E, Payload> { @@ -139,6 +141,7 @@ impl<'a, E: EthSpec, Payload: AbstractExecPayload> Web3SignerObject<'a, E, Pa MessageType::SyncCommitteeContributionAndProof } Web3SignerObject::ValidatorRegistration(_) => MessageType::ValidatorRegistration, + Web3SignerObject::PayloadAttestation(_) => MessageType::PayloadAttestation, } } } diff --git a/validator_client/src/cli.rs b/validator_client/src/cli.rs index 85e40ae6d37..b30e1e7b04c 100644 --- a/validator_client/src/cli.rs +++ b/validator_client/src/cli.rs @@ -98,7 +98,7 @@ pub struct ValidatorClient { #[clap( long, - help = "Disable the performance of attestation duties (and sync committee duties). This \ + help = "Disable the performance of attestation duties (also sync committee duties and payload attestation duties). This \ flag should only be used in emergencies to prioritise block proposal duties.", display_order = 0, help_heading = FLAG_HEADER diff --git a/validator_client/src/lib.rs b/validator_client/src/lib.rs index 5b396ccaf53..f18eac18857 100644 --- a/validator_client/src/lib.rs +++ b/validator_client/src/lib.rs @@ -42,6 +42,7 @@ use validator_services::{ block_service::{BlockService, BlockServiceBuilder}, duties_service::{self, DutiesService, DutiesServiceBuilder}, latency_service, + payload_attestation_service::{PayloadAttestationService, PayloadAttestationServiceBuilder}, preparation_service::{PreparationService, PreparationServiceBuilder}, sync_committee_service::SyncCommitteeService, }; @@ -63,6 +64,7 @@ pub struct ProductionValidatorClient { duties_service: Arc, SystemTimeSlotClock>>, block_service: BlockService, SystemTimeSlotClock>, attestation_service: AttestationService, SystemTimeSlotClock>, + payload_attestation_service: PayloadAttestationService, SystemTimeSlotClock>, sync_committee_service: SyncCommitteeService, SystemTimeSlotClock>, doppelganger_service: Option>, preparation_service: PreparationService, SystemTimeSlotClock>, @@ -452,6 +454,15 @@ impl ProductionValidatorClient { .disable(config.disable_attesting) .build()?; + let payload_attestation_service = PayloadAttestationServiceBuilder::new() + .duties_service(duties_service.clone()) + .slot_clock(slot_clock.clone()) + .validator_store(validator_store.clone()) + .beacon_nodes(beacon_nodes.clone()) + .executor(context.executor.clone()) + .chain_spec(context.eth2_config.spec.clone()) + .build()?; + let preparation_service = PreparationServiceBuilder::new() .slot_clock(slot_clock.clone()) .validator_store(validator_store.clone()) @@ -474,6 +485,7 @@ impl ProductionValidatorClient { duties_service, block_service, attestation_service, + payload_attestation_service, sync_committee_service, doppelganger_service, preparation_service, @@ -541,6 +553,11 @@ impl ProductionValidatorClient { .start_update_service(&self.context.eth2_config.spec) .map_err(|e| format!("Unable to start attestation service: {}", e))?; + self.payload_attestation_service + .clone() + .start_update_service(&self.context.eth2_config.spec) + .map_err(|e| format!("Unable to start payload attestation service: {}", e))?; + self.sync_committee_service .clone() .start_update_service(&self.context.eth2_config.spec) diff --git a/validator_client/validator_metrics/src/lib.rs b/validator_client/validator_metrics/src/lib.rs index 46a86381f91..733568f5412 100644 --- a/validator_client/validator_metrics/src/lib.rs +++ b/validator_client/validator_metrics/src/lib.rs @@ -28,6 +28,9 @@ pub const UPDATE_PTC_FETCH: &str = "update_ptc_fetch"; pub const UPDATE_PTC_STORE: &str = "update_ptc_store"; pub const ATTESTER_DUTIES_HTTP_POST: &str = "attester_duties_http_post"; pub const PTC_DUTIES_HTTP_POST: &str = "ptc_duties_http_post"; +pub const PAYLOAD_ATTESTATIONS: &str = "payload_attestations"; +pub const PAYLOAD_ATTESTATION_HTTP_GET: &str = "payload_attestation_http_get"; +pub const PAYLOAD_ATTESTATION_HTTP_POST: &str = "payload_attestation_http_post"; pub const PROPOSER_DUTIES_HTTP_GET: &str = "proposer_duties_http_get"; pub const VALIDATOR_DUTIES_SYNC_HTTP_POST: &str = "validator_duties_sync_http_post"; pub const VALIDATOR_ID_HTTP_GET: &str = "validator_id_http_get"; @@ -140,6 +143,14 @@ pub static ATTESTATION_SERVICE_TIMES: LazyLock> = LazyLock: &["task"], ) }); +pub static PAYLOAD_ATTESTATION_SERVICE_TIMES: LazyLock> = + LazyLock::new(|| { + try_create_histogram_vec( + "vc_payload_attestation_service_task_times_seconds", + "Duration to perform payload attestation service tasks", + &["task"], + ) + }); pub static SLASHING_PROTECTION_PRUNE_TIMES: LazyLock> = LazyLock::new(|| { try_create_histogram( "vc_slashing_protection_prune_times_seconds", diff --git a/validator_client/validator_services/src/duties_service.rs b/validator_client/validator_services/src/duties_service.rs index 62cb4b54018..fcac252f263 100644 --- a/validator_client/validator_services/src/duties_service.rs +++ b/validator_client/validator_services/src/duties_service.rs @@ -1157,7 +1157,6 @@ fn get_uninitialized_validators( .collect::>() } - fn update_per_validator_duty_metrics( duties_service: &Arc>, epoch: Epoch, @@ -1649,7 +1648,8 @@ async fn poll_beacon_ptc_attesters_for_epoch< // all duties. We use the `dependent_root` in the response to determine whether validator // duties need to be updated. This is to ensure that we don't request for extra data unless // necessary in order to save on network bandwidth. - let initial_indices_to_request = &local_indices[0..min(INITIAL_PTC_DUTIES_QUERY_SIZE, local_indices.len())]; + let initial_indices_to_request = + &local_indices[0..min(INITIAL_PTC_DUTIES_QUERY_SIZE, local_indices.len())]; let response = post_validator_duties_ptc(duties_service, epoch, initial_indices_to_request).await?; diff --git a/validator_client/validator_services/src/lib.rs b/validator_client/validator_services/src/lib.rs index 3b8bd9ae14b..0169335a7f4 100644 --- a/validator_client/validator_services/src/lib.rs +++ b/validator_client/validator_services/src/lib.rs @@ -3,6 +3,7 @@ pub mod block_service; pub mod duties_service; pub mod latency_service; pub mod notifier_service; +pub mod payload_attestation_service; pub mod preparation_service; pub mod sync; pub mod sync_committee_service; diff --git a/validator_client/validator_services/src/payload_attestation_service.rs b/validator_client/validator_services/src/payload_attestation_service.rs new file mode 100644 index 00000000000..8a4835d7f86 --- /dev/null +++ b/validator_client/validator_services/src/payload_attestation_service.rs @@ -0,0 +1,371 @@ +use crate::duties_service::DutiesService; +use beacon_node_fallback::BeaconNodeFallback; +use eth2::types::PtcDuty; +use futures::future::join_all; +use logging::crit; +use slot_clock::SlotClock; +use std::ops::Deref; +use std::sync::Arc; +use task_executor::TaskExecutor; +use tokio::time::{Duration, sleep}; +use tracing::{error, info, trace, warn}; +use types::{ChainSpec, EthSpec, PayloadAttestationData, PayloadAttestationMessage, Slot}; +use validator_metrics; +use validator_store::ValidatorStore; + +/// Builds a `PayloadAttestationService`. +#[derive(Default)] +pub struct PayloadAttestationServiceBuilder { + duties_service: Option>>, + validator_store: Option>, + slot_clock: Option, + beacon_nodes: Option>>, + executor: Option, + chain_spec: Option>, +} + +impl PayloadAttestationServiceBuilder { + pub fn new() -> Self { + Self { + duties_service: None, + validator_store: None, + slot_clock: None, + beacon_nodes: None, + executor: None, + chain_spec: None, + } + } + + pub fn duties_service(mut self, service: Arc>) -> Self { + self.duties_service = Some(service); + self + } + + pub fn validator_store(mut self, store: Arc) -> Self { + self.validator_store = Some(store); + self + } + + pub fn slot_clock(mut self, slot_clock: T) -> Self { + self.slot_clock = Some(slot_clock); + self + } + + pub fn beacon_nodes(mut self, beacon_nodes: Arc>) -> Self { + self.beacon_nodes = Some(beacon_nodes); + self + } + + pub fn executor(mut self, executor: TaskExecutor) -> Self { + self.executor = Some(executor); + self + } + + pub fn chain_spec(mut self, chain_spec: Arc) -> Self { + self.chain_spec = Some(chain_spec); + self + } + + pub fn build(self) -> Result, String> { + Ok(PayloadAttestationService { + inner: Arc::new(Inner { + duties_service: self + .duties_service + .ok_or("Cannot build PayloadAttestationService without duties_service")?, + validator_store: self + .validator_store + .ok_or("Cannot build PayloadAttestationService without validator_store")?, + slot_clock: self + .slot_clock + .ok_or("Cannot build PayloadAttestationService without slot_clock")?, + beacon_nodes: self + .beacon_nodes + .ok_or("Cannot build PayloadAttestationService without beacon_nodes")?, + executor: self + .executor + .ok_or("Cannot build PayloadAttestationService without executor")?, + chain_spec: self + .chain_spec + .ok_or("Cannot build PayloadAttestationService without chain_spec")?, + }), + }) + } +} + +/// Helper to minimise `Arc` usage. +pub struct Inner { + duties_service: Arc>, + validator_store: Arc, + slot_clock: T, + beacon_nodes: Arc>, + executor: TaskExecutor, + chain_spec: Arc, +} + +pub struct PayloadAttestationService { + inner: Arc>, +} + +impl Clone for PayloadAttestationService { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } + } +} + +impl Deref for PayloadAttestationService { + type Target = Inner; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl PayloadAttestationService { + /// Starts the service which periodically produces payload attestations. + pub fn start_update_service(self, spec: &ChainSpec) -> Result<(), String> { + if self.duties_service.disable_attesting { + info!("Payload attestation service disabled"); + return Ok(()); + } + + let slot_duration = Duration::from_secs(spec.seconds_per_slot); + let duration_to_next_slot = self + .slot_clock + .duration_to_next_slot() + .ok_or("Unable to determine duration to next slot")?; + + info!( + next_update_millis = duration_to_next_slot.as_millis(), + "Payload attestation service started" + ); + + let executor = self.executor.clone(); + + let interval_fut = async move { + loop { + if let Some(duration_to_next_slot) = self.slot_clock.duration_to_next_slot() { + let payload_attestation_delay = slot_duration * 3 / 4; + + sleep(duration_to_next_slot + payload_attestation_delay).await; + + // Check if we've reached the Gloas fork epoch before proceeding + if self.chain_spec.is_gloas_scheduled() { + let Some(current_slot) = self.slot_clock.now() else { + error!("Unable to read slot clock"); + sleep(slot_duration).await; + continue; + }; + + let current_epoch = current_slot.epoch(S::E::slots_per_epoch()); + + let Some(gloas_fork_epoch) = self.chain_spec.gloas_fork_epoch else { + // Gloas fork epoch not configured, should not reach here + warn!("Gloas fork is scheduled but no fork epoch configured"); + break; + }; + + if current_epoch < gloas_fork_epoch { + // Wait until the next slot and check again + continue; + } + } else { + // Gloas fork not scheduled, skip payload attestation duties + continue; + } + + if let Err(e) = self.spawn_payload_attestation_tasks(slot_duration) { + crit!(error = e, "Failed to spawn payload attestation tasks") + } else { + trace!("Spawned payload attestation tasks"); + } + } else { + error!("Failed to read slot clock"); + // If we can't read the slot clock, just wait another slot. + sleep(slot_duration).await; + continue; + } + } + }; + + executor.spawn(interval_fut, "payload_attestation_service"); + Ok(()) + } + + /// For each PTC duty at the current slot, spawn a new task that creates, signs and uploads + /// the payload attestation message to the beacon node. + fn spawn_payload_attestation_tasks(&self, _slot_duration: Duration) -> Result<(), String> { + let slot = self.slot_clock.now().ok_or("Failed to read slot clock")?; + + // Get PTC duties for this slot + let duties = self.duties_service.get_ptc_duties_for_slot(slot); + + // Spawn a single task for all PTC duties in this slot + self.executor.spawn_ignoring_error( + self.clone().publish_payload_attestations(slot, duties), + "payload_attestation_batch_publish", + ); + + Ok(()) + } + + /// Downloads `PayloadAttestationData`, signs them, and publishes them to the BN as `PayloadAttestationMessage`s + /// for all PTC duties in a slot. + async fn publish_payload_attestations( + self, + slot: Slot, + duties: Vec, + ) -> Result<(), ()> { + let _payload_attestations_timer = validator_metrics::start_timer_vec( + &validator_metrics::PAYLOAD_ATTESTATION_SERVICE_TIMES, + &[validator_metrics::PAYLOAD_ATTESTATIONS], + ); + + if duties.is_empty() { + return Ok(()); + } + + // Step 1: Single GET request for payload attestation data (shared by all validators) + let payload_attestation_data = self + .beacon_nodes + .first_success(|beacon_node| async move { + let _timer = validator_metrics::start_timer_vec( + &validator_metrics::PAYLOAD_ATTESTATION_SERVICE_TIMES, + &[validator_metrics::PAYLOAD_ATTESTATION_HTTP_GET], + ); + + beacon_node + .get_validator_payload_attestation_data(slot) + .await + .map_err(|e| format!("Failed to get payload attestation data: {:?}", e)) + .map(|result| result.data().clone()) + }) + .await + .map_err(move |e| { + crit!( + error = format!("{:?}", e.to_string()), + slot = slot.as_u64(), + "Error during payload attestation data retrieval" + ) + })?; + + // Step 2: Sign all attestations in parallel + let signing_futures = duties.iter().map(|duty| { + let service = self.clone(); + let payload_attestation_data = payload_attestation_data.clone(); + + async move { + // Ensure that the payload attestation data matches the duty slot. + if duty.slot != payload_attestation_data.slot { + crit!( + validator = ?duty.pubkey, + duty_slot = %duty.slot, + attestation_slot = %payload_attestation_data.slot, + "Inconsistent validator duty slot during payload attestation signing" + ); + return None; + } + + match service + .sign_payload_attestation_data(&duty.pubkey, &payload_attestation_data, slot) + .await + { + Ok(signature) => Some(PayloadAttestationMessage { + validator_index: duty.validator_index, + data: payload_attestation_data, + signature, + }), + Err(e) => { + crit!( + slot = slot.as_u64(), + validator_index = duty.validator_index, + error = e, + "Failed to sign payload attestation" + ); + None + } + } + } + }); + + // Execute all signing futures in parallel + let signed_attestations: Vec = join_all(signing_futures) + .await + .into_iter() + .flatten() + .collect(); + + if signed_attestations.is_empty() { + warn!( + slot = slot.as_u64(), + num_duties = duties.len(), + "No payload attestations were signed successfully" + ); + return Err(()); + } + + // Step 3: Single batched POST request to beacon node of `PayloadAttestationMessage` + let fork_name = self.chain_spec.fork_name_at_slot::(slot); + self.beacon_nodes + .first_success(|beacon_node| { + let signed_attestations = signed_attestations.clone(); + async move { + let _timer = validator_metrics::start_timer_vec( + &validator_metrics::PAYLOAD_ATTESTATION_SERVICE_TIMES, + &[validator_metrics::PAYLOAD_ATTESTATION_HTTP_POST], + ); + + beacon_node + .post_beacon_pool_payload_attestations(signed_attestations, fork_name) + .await + } + }) + .await + .map_err(move |e| { + crit!( + error = format!("{:?}", e.to_string()), + slot = slot.as_u64(), + "Error during payload attestation publishing" + ) + })?; + + info!( + count = signed_attestations.len(), + slot = slot.as_u64(), + payload_present = payload_attestation_data.payload_present, + blob_data_available = payload_attestation_data.blob_data_available, + beacon_block_root = ?payload_attestation_data.beacon_block_root, + "Successfully published payload attestations" + ); + + Ok(()) + } + + /// Sign payload attestation data according to the Gloas specification. + /// + /// This creates a `PayloadAttestationMessage` and uses the validator store to sign it + async fn sign_payload_attestation_data( + &self, + validator_pubkey: &types::PublicKeyBytes, + attestation_data: &PayloadAttestationData, + _slot: Slot, + ) -> Result { + // Create a PayloadAttestationMessage with an empty signature for signing + let validator_index = self + .validator_store + .validator_index(validator_pubkey) + .ok_or_else(|| format!("Unknown validator: {:?}", validator_pubkey))?; + + let mut payload_attestation_message = + PayloadAttestationMessage::empty_for_signing(validator_index, attestation_data.clone()); + + // Use the validator store to sign the message + self.validator_store + .sign_payload_attestation_message(*validator_pubkey, &mut payload_attestation_message) + .await + .map_err(|e| format!("Failed to sign payload attestation: {:?}", e))?; + + Ok(payload_attestation_message.signature) + } +} diff --git a/validator_client/validator_store/src/lib.rs b/validator_client/validator_store/src/lib.rs index c3b551c249b..0a50d43a08e 100644 --- a/validator_client/validator_store/src/lib.rs +++ b/validator_client/validator_store/src/lib.rs @@ -5,9 +5,10 @@ use std::future::Future; use std::sync::Arc; use types::{ Address, Attestation, AttestationError, BlindedBeaconBlock, Epoch, EthSpec, Graffiti, Hash256, - PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, SignedBlindedBeaconBlock, - SignedContributionAndProof, SignedValidatorRegistrationData, Slot, SyncCommitteeContribution, - SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, ValidatorRegistrationData, + PayloadAttestationMessage, PublicKeyBytes, SelectionProof, Signature, SignedAggregateAndProof, + SignedBlindedBeaconBlock, SignedContributionAndProof, SignedValidatorRegistrationData, Slot, + SyncCommitteeContribution, SyncCommitteeMessage, SyncSelectionProof, SyncSubnetId, + ValidatorRegistrationData, }; #[derive(Debug, PartialEq, Clone)] @@ -109,6 +110,12 @@ pub trait ValidatorStore: Send + Sync { current_epoch: Epoch, ) -> impl Future>> + Send; + fn sign_payload_attestation_message( + &self, + validator_pubkey: PublicKeyBytes, + payload_attestation_message: &mut PayloadAttestationMessage, + ) -> impl Future>> + Send; + fn sign_validator_registration_data( &self, validator_registration_data: ValidatorRegistrationData,