From 755d5fee62029a3280fa4c978df4b2159e36218c Mon Sep 17 00:00:00 2001 From: hangleang Date: Fri, 19 Jun 2026 16:52:09 +0700 Subject: [PATCH 1/2] Update consensus-spec-tests to v1.7.0-alpha.11 --- fork_choice_store/src/error.rs | 9 ++ fork_choice_store/src/store.rs | 33 ++++--- helper_functions/src/gloas.rs | 27 ++++-- scripts/download_spec_tests.sh | 2 +- .../src/gloas/block_processing.rs | 86 ++++++++++++------- transition_functions/src/unphased/error.rs | 20 ++++- types/src/gloas/consts.rs | 3 + types/src/gloas/containers.rs | 2 + 8 files changed, 130 insertions(+), 52 deletions(-) diff --git a/fork_choice_store/src/error.rs b/fork_choice_store/src/error.rs index 136b83824..21cdaa51e 100644 --- a/fork_choice_store/src/error.rs +++ b/fork_choice_store/src/error.rs @@ -188,6 +188,15 @@ pub enum Error { ExecutionPayloadBidSignatureNotEmpty, #[error("execution payload bid's value for self-build is not zero, value: {value} gwei")] ExecutionPayloadBidValueNonZero { value: Gwei }, + #[error( + "execution payload bid's builder version mismatch \ + (payload_bid: {payload_bid:?}, builder_version: {builder_version}, expected: {expected})" + )] + ExecutionPayloadBidBuilderVersionMismatch { + payload_bid: Arc>, + builder_version: u8, + expected: u8, + }, #[error( "execution payload block hash mismatch (envelope: {envelope:?}, expected: {expected:?})" )] diff --git a/fork_choice_store/src/store.rs b/fork_choice_store/src/store.rs index 14af1d394..87aa49bf2 100644 --- a/fork_choice_store/src/store.rs +++ b/fork_choice_store/src/store.rs @@ -58,7 +58,10 @@ use types::{ electra::containers::IndexedAttestation as ElectraIndexedAttestation, fulu::{containers::DataColumnIdentifier, primitives::ColumnIndex}, gloas::{ - consts::{BUILDER_INDEX_SELF_BUILD, PAYLOAD_STATUS_EMPTY, PAYLOAD_STATUS_FULL}, + consts::{ + BUILDER_INDEX_SELF_BUILD, PAYLOAD_BUILDER_VERSION, PAYLOAD_STATUS_EMPTY, + PAYLOAD_STATUS_FULL, + }, containers::{ CombinedPayloadAttestation, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, SignedProposerPreferences, @@ -891,24 +894,24 @@ impl> Store { pub fn should_build_on_full(&self) -> bool { let (head, payload_status) = self.head_with_payload_status(); - // EMPTY was resolved by weight / tiebreaker: build on the empty variant. - if payload_status == PAYLOAD_STATUS_EMPTY { - return false; - } - // For a head from an earlier slot the empty/full node was already resolved // by weight in `get_head`; only the previous-slot head still consults the // (possibly stale) PTC data-availability view. // See . if head.slot().saturating_add(1) != self.slot() { - return true; + return payload_status == PAYLOAD_STATUS_FULL; + } + + // EMPTY was resolved by weight / tiebreaker: build on the empty variant. + if payload_status == PAYLOAD_STATUS_EMPTY { + return false; } // Force a reorg of the payload if the PTC voted the blob data unavailable // or late payload. // See . - !self.payload_data_availability(head.block_root, false) - && !self.payload_timeliness(head.block_root, false) + !self.payload_timeliness(head.block_root, false) + && !self.payload_data_availability(head.block_root, false) } pub fn should_extend_payload(&self, block_root: H256) -> bool { @@ -2096,12 +2099,22 @@ impl> Store { let current_epoch = accessors::get_current_epoch(&state); ensure!( predicates::is_active_builder(builder, state.finalized_checkpoint().epoch), - Error::

::ExecutionPayloadBidBuilderInactive { + Error::ExecutionPayloadBidBuilderInactive { payload_bid, epoch: current_epoch } ); + // > The builder version is `PAYLOAD_BUILDER_VERSION` + ensure!( + builder.version == PAYLOAD_BUILDER_VERSION, + Error::ExecutionPayloadBidBuilderVersionMismatch { + payload_bid, + builder_version: builder.version, + expected: PAYLOAD_BUILDER_VERSION, + } + ); + // > the `bid.value` is less or equal than the builder's excess balance if !predicates::can_builder_cover_bid(post_gloas_state, builder_index, bid.value)? { return Ok(ExecutionPayloadBidAction::Ignore( diff --git a/helper_functions/src/gloas.rs b/helper_functions/src/gloas.rs index f9b8d0a48..c3b4b16ee 100644 --- a/helper_functions/src/gloas.rs +++ b/helper_functions/src/gloas.rs @@ -4,7 +4,7 @@ use pubkey_cache::PubkeyCache; use ssz::H256; use types::{ config::Config, - gloas::{containers::Builder, primitives::BuilderIndex}, + gloas::{consts::PAYLOAD_BUILDER_VERSION, containers::Builder, primitives::BuilderIndex}, phase0::{ consts::FAR_FUTURE_EPOCH, containers::DepositMessage, @@ -15,9 +15,7 @@ use types::{ }; use crate::{ - accessors::get_current_epoch, - misc::compute_epoch_at_slot, - mutators::{builder_balance, increase_balance}, + accessors::get_current_epoch, error::Error, misc::compute_epoch_at_slot, signing::SignForAllForks as _, }; @@ -37,9 +35,25 @@ pub fn apply_deposit_for_builder( .into_iter() .position(|builder| builder.pubkey == pubkey) { + let current_epoch = get_current_epoch(state); let builder_index = builder_index.try_into()?; + let builder = state + .builders_mut() + .get_mut(builder_index) + .expect("builder index is valid since its pubkey found in builder registry"); - increase_balance(builder_balance(state, builder_index)?, amount)?; + builder.balance = builder.balance.checked_add(amount).ok_or_else(|| { + anyhow::anyhow!( + "balance overflow when applying deposit for builder at index {builder_index}", + ) + })?; + + // > If exited, reset the withdrawable epoch + if builder.withdrawable_epoch != FAR_FUTURE_EPOCH { + builder.withdrawable_epoch = current_epoch + .checked_add(config.min_builder_withdrawability_delay) + .ok_or(Error::EpochOverflow)?; + } } else { // > Verify the deposit signature (proof of possession) // > which is not checked by the deposit contract @@ -71,13 +85,12 @@ fn add_builder_to_registry( ) -> Result<()> { let builder_index = get_index_for_new_builder(state); - let version = withdrawal_credentials[0]; let mut address = ExecutionAddress::zero(); address.assign_from_slice(&withdrawal_credentials[12..]); let builder = Builder { pubkey, - version, + version: PAYLOAD_BUILDER_VERSION, execution_address: address, balance: amount, deposit_epoch: compute_epoch_at_slot::

(slot), diff --git a/scripts/download_spec_tests.sh b/scripts/download_spec_tests.sh index 1c685ec54..725b1a0b2 100755 --- a/scripts/download_spec_tests.sh +++ b/scripts/download_spec_tests.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash set -euo pipefail -SPEC_VERSION="${SPEC_VERSION:-v1.7.0-alpha.10}" +SPEC_VERSION="${SPEC_VERSION:-v1.7.0-alpha.11}" TESTS_DIR="consensus-spec-tests" VERSION_FILE="${TESTS_DIR}/.version" BASE_URL="https://github.com/ethereum/consensus-specs/releases/download/${SPEC_VERSION}" diff --git a/transition_functions/src/gloas/block_processing.rs b/transition_functions/src/gloas/block_processing.rs index 836f62336..d92ada192 100644 --- a/transition_functions/src/gloas/block_processing.rs +++ b/transition_functions/src/gloas/block_processing.rs @@ -46,7 +46,7 @@ use types::{ electra::containers::{Attestation, ExecutionRequests}, gloas::{ beacon_state::BeaconState as GloasBeaconState, - consts::BUILDER_INDEX_SELF_BUILD, + consts::{BUILDER_INDEX_SELF_BUILD, PAYLOAD_BUILDER_VERSION}, containers::{ BeaconBlock, BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionPayloadBid, PayloadAttestation, SignedBeaconBlock, SignedExecutionPayloadBid, @@ -55,7 +55,7 @@ use types::{ }, nonstandard::{AttestationEpoch, SlashingKind}, phase0::{ - consts::FAR_FUTURE_EPOCH, + consts::{FAR_FUTURE_EPOCH, GENESIS_SLOT}, containers::{AttestationData, ProposerSlashing, SignedVoluntaryExit}, primitives::{Epoch, ExecutionAddress, Gwei}, }, @@ -164,7 +164,12 @@ pub fn custom_process_block( // > [New in Gloas:EIP7732] // This function must be called after `process_withdrawals` - process_execution_payload_bid(config, pubkey_cache, state, block)?; + process_execution_payload_bid( + config, + pubkey_cache, + state, + &block.body.signed_execution_payload_bid, + )?; unphased::process_randao(config, pubkey_cache, state, &block.body, &mut verifier)?; unphased::process_eth1_data(state, &block.body)?; @@ -573,10 +578,9 @@ fn validate_execution_payload_bid( config: &Config, pubkey_cache: &PubkeyCache, state: &impl PostGloasBeaconState

, - block: &BeaconBlock

, + signed_bid: &SignedExecutionPayloadBid

, ) -> Result<()> { let current_epoch = get_current_epoch(state); - let signed_bid = &block.body.signed_execution_payload_bid; let ExecutionPayloadBid { builder_index, value: amount, @@ -604,6 +608,14 @@ fn validate_execution_payload_bid( current_epoch } ); + ensure!( + builder.version == PAYLOAD_BUILDER_VERSION, + Error::

::BuilderVersionMismatch { + index: builder_index, + builder_version: builder.version, + expected: PAYLOAD_BUILDER_VERSION, + } + ); ensure!( can_builder_cover_bid(state, builder_index, amount)?, Error::

::BuilderBalanceNotSufficient { @@ -637,12 +649,13 @@ fn validate_execution_payload_bid( // > Verify that the bid is for the current slot ensure!( - slot == block.slot, + slot == state.slot(), Error::

::BidSlotMismatch { in_bid: slot, - in_block: block.slot + in_state: state.slot(), } ); + ensure!(state.slot() > GENESIS_SLOT, Error::

::BidSlotAtGenesis); // > Verify that the bid is for the right parent block ensure!( @@ -652,11 +665,15 @@ fn validate_execution_payload_bid( in_state: state.latest_block_hash(), } ); + + let parent_beacon_block_root = + accessors::get_block_root_at_slot(state, state.slot().saturating_sub(1))?; + ensure!( - parent_block_root == block.parent_root, + parent_block_root == parent_beacon_block_root, Error::

::BidParentBlockRootMismatch { - in_bid: parent_block_hash, - in_block: block.parent_root, + in_bid: parent_block_root, + in_state: parent_beacon_block_root, } ); @@ -775,18 +792,17 @@ pub fn process_execution_payload_bid( config: &Config, pubkey_cache: &PubkeyCache, state: &mut impl PostGloasBeaconState

, - block: &BeaconBlock

, + signed_bid: &SignedExecutionPayloadBid

, ) -> Result<()> { - let payload_bid = block.body.signed_execution_payload_bid.message.clone(); let ExecutionPayloadBid { value: amount, builder_index, slot, fee_recipient, .. - } = payload_bid; + } = signed_bid.message; - validate_execution_payload_bid(config, pubkey_cache, state, block)?; + validate_execution_payload_bid(config, pubkey_cache, state, signed_bid)?; // > Record the pending payment if there is some payment if amount > 0 { @@ -797,6 +813,7 @@ pub fn process_execution_payload_bid( amount, builder_index, }, + proposer_index: get_beacon_proposer_index(config, state)?, }; *state .builder_pending_payments_mut() @@ -804,7 +821,7 @@ pub fn process_execution_payload_bid( } // > Cache the signed execution payload bid - *state.latest_execution_payload_bid_mut() = payload_bid; + *state.latest_execution_payload_bid_mut() = signed_bid.message.clone(); Ok(()) } @@ -1267,27 +1284,36 @@ pub fn process_proposer_slashing( verifier, )?; - // > Remove the BuilderPendingPayment corresponding to this proposal if it is still in the 2-epoch window. + // > Remove the BuilderPendingPayment corresponding to this proposal if it is + // > still in the 2-epoch window. Only clear it when the slashed validator is + // > the proposer associated with the payment; otherwise an unrelated same-slot + // > equivocation could grief an honest proposer's payment let slot = proposer_slashing.signed_header_1.message.slot; + let proposer_index = proposer_slashing.signed_header_1.message.proposer_index; let proposal_epoch = compute_epoch_at_slot::

(slot); - if proposal_epoch == get_current_epoch(state) { - *state - .builder_pending_payments_mut() - .mod_index_mut(builder_payment_index_for_current_epoch::

(slot)?) = - BuilderPendingPayment::default(); + + let payment_index = if proposal_epoch == get_current_epoch(state) { + Some(builder_payment_index_for_current_epoch::

(slot)?) } else if proposal_epoch == get_previous_epoch(state) { - *state + Some(builder_payment_index_for_previous_epoch::

(slot)) + } else { + None + }; + + if let Some(payment_index) = payment_index { + let payment = state .builder_pending_payments_mut() - .mod_index_mut(builder_payment_index_for_previous_epoch::

(slot)) = - BuilderPendingPayment::default(); - } + .mod_index_mut(payment_index); - let index = proposer_slashing.signed_header_1.message.proposer_index; + if payment.proposer_index == proposer_index { + *payment = BuilderPendingPayment::default(); + } + } slash_validator( config, state, - index, + proposer_index, None, SlashingKind::Proposer, slot_report, @@ -1491,15 +1517,15 @@ mod spec_tests { processing_tests! { process_execution_payload_bid, - |config, pubkey_cache, state, block, _| { + |config, pubkey_cache, state, execution_payload_bid, _| { process_execution_payload_bid( config, pubkey_cache, state, - &block, + &execution_payload_bid, ) }, - "block", + "execution_payload_bid", "consensus-spec-tests/tests/mainnet/gloas/operations/execution_payload_bid/*/*", "consensus-spec-tests/tests/minimal/gloas/operations/execution_payload_bid/*/*", } diff --git a/transition_functions/src/unphased/error.rs b/transition_functions/src/unphased/error.rs index 50f5cf5c1..ee5a23174 100644 --- a/transition_functions/src/unphased/error.rs +++ b/transition_functions/src/unphased/error.rs @@ -39,15 +39,19 @@ pub enum Error { AttestationWithInvalidPayloadStatus { attestation: Attestation

}, #[error("post-Electra attestation with invalid (non-zero) committee index: {attestation:?}")] AttestationWithNonZeroCommitteeIndex { attestation: Attestation

}, - #[error("bid slot ({in_bid}) does not match block slot ({in_block})")] - BidSlotMismatch { in_bid: Slot, in_block: Slot }, + #[error("bid at genesis slot")] + BidSlotAtGenesis, + #[error("bid slot ({in_bid}) does not match state slot ({in_state})")] + BidSlotMismatch { in_bid: Slot, in_state: Slot }, #[error("bid parent block hash ({in_bid}) does not match in state ({in_state})")] BidParentBlockHashMismatch { in_bid: ExecutionBlockHash, in_state: ExecutionBlockHash, }, - #[error("bid parent block root ({in_bid:?}) does not match in block ({in_block:?})")] - BidParentBlockRootMismatch { in_bid: H256, in_block: H256 }, + #[error( + "bid parent block root ({in_bid:?}) does not match parent block root in the state ({in_state:?})" + )] + BidParentBlockRootMismatch { in_bid: H256, in_state: H256 }, #[error("bid prev randao ({in_bid:?}) does not match in state ({in_state:?})")] BidPrevRandaoMismatch { in_bid: H256, in_state: H256 }, #[error("block is not newer than latest block header ({block_slot} <= {block_header_slot})")] @@ -64,6 +68,14 @@ pub enum Error { index: BuilderIndex, current_epoch: Epoch, }, + #[error( + "builder version mismatch for builder {index} (builder_version: {builder_version}, expected: {expected})" + )] + BuilderVersionMismatch { + index: BuilderIndex, + builder_version: u8, + expected: u8, + }, #[error("builder payment index ({index}) out of bounds (length: {length})")] BuilderPaymentIndexOutOfBounds { index: u64, length: u64 }, #[error("cannot exit builder because it has pending withdrawals in the queue")] diff --git a/types/src/gloas/consts.rs b/types/src/gloas/consts.rs index ff8f8542b..c3ef7b6dd 100644 --- a/types/src/gloas/consts.rs +++ b/types/src/gloas/consts.rs @@ -77,3 +77,6 @@ pub const BUILDER_PAYMENT_THRESHOLD_DENOMINATOR: u64 = 10; // Bitwise flag which indicates that a `ValidatorIndex` should be treated as a `BuilderIndex` pub const BUILDER_INDEX_FLAG: u64 = 0x0100_0000_0000; pub const BUILDER_WITHDRAWAL_PREFIX: &[u8] = &hex!("03"); + +// Versioning +pub const PAYLOAD_BUILDER_VERSION: u8 = 0; diff --git a/types/src/gloas/containers.rs b/types/src/gloas/containers.rs index 7b2867843..0f784d166 100644 --- a/types/src/gloas/containers.rs +++ b/types/src/gloas/containers.rs @@ -80,6 +80,8 @@ pub struct BuilderPendingPayment { #[serde(with = "serde_utils::string_or_native")] pub weight: Gwei, pub withdrawal: BuilderPendingWithdrawal, + #[serde(with = "serde_utils::string_or_native")] + pub proposer_index: ValidatorIndex, } #[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Deserialize, Serialize, Ssz)] From 706baa091c27a59b5569cb89d97f64a99b77f863 Mon Sep 17 00:00:00 2001 From: hangleang Date: Tue, 23 Jun 2026 16:02:33 +0700 Subject: [PATCH 2/2] Add builder execution requests (EIP-8282) --- block_producer/src/block_producer.rs | 27 +-- eth1_api/src/eth1_api/http_api.rs | 6 +- eth2_libp2p | 2 +- execution_engine/src/types.rs | 164 ++++++++++++++++-- fork_choice_control/src/mutator.rs | 2 +- fork_choice_control/src/spec_tests.rs | 4 + fork_choice_store/src/store.rs | 2 +- helper_functions/src/error.rs | 2 + helper_functions/src/fork.rs | 95 +++++----- helper_functions/src/gloas.rs | 95 +++------- helper_functions/src/mutators.rs | 16 -- helper_functions/src/predicates.rs | 29 +++- helper_functions/src/signing.rs | 15 +- .../src/gloas/block_processing.rs | 148 ++-------------- .../src/gloas/execution_payload_processing.rs | 150 ++++++++++------ .../src/gloas/state_transition.rs | 12 +- transition_functions/src/lib.rs | 8 +- transition_functions/src/unphased/error.rs | 2 - types/src/combined.rs | 51 +++++- types/src/gloas/consts.rs | 1 + types/src/gloas/container_impls.rs | 22 ++- types/src/gloas/containers.rs | 41 ++++- types/src/gloas/spec_tests.rs | 17 +- types/src/nonstandard.rs | 3 +- types/src/preset.rs | 27 ++- 25 files changed, 558 insertions(+), 383 deletions(-) diff --git a/block_producer/src/block_producer.rs b/block_producer/src/block_producer.rs index 0599b1717..1711665e1 100644 --- a/block_producer/src/block_producer.rs +++ b/block_producer/src/block_producer.rs @@ -62,7 +62,7 @@ use types::{ }, combined::{ AttesterSlashing, BeaconBlock, BeaconState, BlindedBeaconBlock, ExecutionPayload, - ExecutionPayloadHeader, SignedBlindedBeaconBlock, + ExecutionPayloadHeader, ExecutionRequests, SignedBlindedBeaconBlock, }, config::Config as ChainConfig, deneb::{ @@ -75,7 +75,7 @@ use types::{ electra::containers::{ Attestation as ElectraAttestation, AttesterSlashing as ElectraAttesterSlashing, BeaconBlock as ElectraBeaconBlock, BeaconBlockBody as ElectraBeaconBlockBody, - ExecutionRequests, + ExecutionRequests as ElectraExecutionRequests, }, fulu::containers::{BeaconBlock as FuluBeaconBlock, BeaconBlockBody as FuluBeaconBlockBody}, gloas::{ @@ -83,7 +83,8 @@ use types::{ containers::{ BeaconBlock as GloasBeaconBlock, BeaconBlockBody as GloasBeaconBlockBody, ExecutionPayload as GloasExecutionPayload, ExecutionPayloadBid, - ExecutionPayloadEnvelope, PayloadAttestation, SignedExecutionPayloadBid, + ExecutionPayloadEnvelope, ExecutionRequests as GloasExecutionRequests, + PayloadAttestation, SignedExecutionPayloadBid, }, }, nonstandard::{BlockRewards, Phase, WEI_IN_GWEI, WithBlobsAndMev}, @@ -488,7 +489,7 @@ impl BlockProducer { state, exit, ), - BeaconState::Gloas(state) => gloas::validate_voluntary_exit( + BeaconState::Gloas(state) => electra::validate_voluntary_exit( &self.producer_context.chain_config, &self.producer_context.pubkey_cache, state, @@ -975,7 +976,7 @@ impl BlockBuildContext { execution_payload: DenebExecutionPayload::default(), bls_to_execution_changes, blob_kzg_commitments: ContiguousList::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ElectraExecutionRequests::default(), }, })), Phase::Fulu => BeaconBlock::from(Hc::new(FuluBeaconBlock { @@ -996,7 +997,7 @@ impl BlockBuildContext { execution_payload: DenebExecutionPayload::default(), bls_to_execution_changes, blob_kzg_commitments: ContiguousList::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ElectraExecutionRequests::default(), }, })), Phase::Gloas => { @@ -1024,7 +1025,7 @@ impl BlockBuildContext { .map(|payload| payload.message.execution_requests.clone()) .unwrap_or_default() } else { - ExecutionRequests::default() + GloasExecutionRequests::default() }; BeaconBlock::from(Hc::new(GloasBeaconBlock { @@ -1105,7 +1106,7 @@ impl BlockBuildContext { beacon_block: BeaconBlock

, payload_header: ExecutionPayloadHeader

, blob_kzg_commitments: Option>, - execution_requests: Option>, + execution_requests: Option>, ) -> Option<(BlindedBeaconBlock

, Option)> { let without_state_root = match beacon_block.into_blinded( payload_header, @@ -1535,7 +1536,7 @@ impl BlockBuildContext { state, *voluntary_exit, ), - BeaconState::Gloas(state) => gloas::validate_voluntary_exit( + BeaconState::Gloas(state) => electra::validate_voluntary_exit( &self.producer_context.chain_config, &self.producer_context.pubkey_cache, state, @@ -2231,7 +2232,7 @@ impl BlockBuildContext { /// Returns only the fields needed to build `ExecutionPayloadEnvelope` async fn get_gloas_envelope_data( &self, - ) -> Option<(GloasExecutionPayload

, ExecutionRequests

)> { + ) -> Option<(GloasExecutionPayload

, GloasExecutionRequests

)> { let payload_root = *self .producer_context .cached_payload_roots @@ -2253,12 +2254,14 @@ impl BlockBuildContext { .. } = local_payload.result; - let ExecutionPayload::Gloas(payload) = execution_payload else { + let (ExecutionPayload::Gloas(payload), Some(ExecutionRequests::Gloas(requests))) = + (execution_payload, execution_requests) + else { warn_with_peers!("unexpected non-Gloas payload format in Gloas envelope data"); return None; }; - Some((payload, execution_requests.unwrap_or_default())) + Some((payload, requests)) } async fn fee_recipient(&self) -> Result { diff --git a/eth1_api/src/eth1_api/http_api.rs b/eth1_api/src/eth1_api/http_api.rs index a593a5ce3..e3435b9b9 100644 --- a/eth1_api/src/eth1_api/http_api.rs +++ b/eth1_api/src/eth1_api/http_api.rs @@ -335,7 +335,7 @@ impl Eth1Api { } ( ExecutionPayload::Gloas(payload), - Some(ExecutionPayloadParams::Electra { + Some(ExecutionPayloadParams::Gloas { versioned_hashes, parent_beacon_block_root, execution_requests, @@ -1046,7 +1046,7 @@ mod tests { assert_eq!(payload.value.phase(), Phase::Deneb); assert_eq!( payload.execution_requests, - Some(ExecutionRequests::default()) + Some(ExecutionRequests::default().into()) ); Ok(()) @@ -1148,7 +1148,7 @@ mod tests { } ])?, ..Default::default() - }) + }.into()) ); Ok(()) diff --git a/eth2_libp2p b/eth2_libp2p index 68e360d86..a316ae0af 160000 --- a/eth2_libp2p +++ b/eth2_libp2p @@ -1 +1 @@ -Subproject commit 68e360d8686f75f56e45c57420b58f519dd337a1 +Subproject commit a316ae0af1a281321c58d09ed1e42689db3a71a4 diff --git a/execution_engine/src/types.rs b/execution_engine/src/types.rs index 5ef1ea665..1dddc89f9 100644 --- a/execution_engine/src/types.rs +++ b/execution_engine/src/types.rs @@ -22,16 +22,23 @@ use types::{ containers::{ExecutionPayload as CapellaExecutionPayload, Withdrawal}, primitives::WithdrawalIndex, }, - combined::{ExecutionPayload, SignedBeaconBlock}, + combined::{ExecutionPayload, ExecutionRequests, SignedBeaconBlock}, deneb::{ containers::{BlobIdentifier, ExecutionPayload as DenebExecutionPayload}, primitives::{Blob, KzgCommitment, KzgProof}, }, electra::containers::{ - ConsolidationRequest, DepositRequest, ExecutionRequests, WithdrawalRequest, + ConsolidationRequest, DepositRequest, ExecutionRequests as ElectraExecutionRequests, + WithdrawalRequest, }, fulu::containers::DataColumnIdentifier, - gloas::{containers::ExecutionPayload as GloasExecutionPayload, primitives::BlockAccessList}, + gloas::{ + containers::{ + BuilderDepositRequest, BuilderExitRequest, ExecutionPayload as GloasExecutionPayload, + ExecutionRequests as GloasExecutionRequests, + }, + primitives::BlockAccessList, + }, nonstandard::{BlockOrDataColumnSidecar, KzgProofs, Phase, WithBlobsAndMev}, phase0::primitives::{ ExecutionAddress, ExecutionBlockHash, ExecutionBlockNumber, Gwei, H256, Slot, UnixSeconds, @@ -40,10 +47,12 @@ use types::{ preset::Preset, }; -const SUPPORTED_REQUEST_TYPES: &[&str; 3] = &[ +const SUPPORTED_REQUEST_TYPES: &[&str; 5] = &[ RequestType::Deposits.request_type(), RequestType::Withdrawals.request_type(), RequestType::Consolidations.request_type(), + RequestType::BuilderDeposits.request_type(), + RequestType::BuilderExits.request_type(), ]; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -51,6 +60,8 @@ pub enum RequestType { Deposits, Withdrawals, Consolidations, + BuilderDeposits, + BuilderExits, } impl RequestType { @@ -60,6 +71,8 @@ impl RequestType { Self::Deposits => "0x00", Self::Withdrawals => "0x01", Self::Consolidations => "0x02", + Self::BuilderDeposits => "0x03", + Self::BuilderExits => "0x04", } } @@ -69,6 +82,8 @@ impl RequestType { Self::Deposits => 0x00, Self::Withdrawals => 0x01, Self::Consolidations => 0x02, + Self::BuilderDeposits => 0x03, + Self::BuilderExits => 0x04, } } @@ -78,6 +93,8 @@ impl RequestType { Self::Deposits => "deposit", Self::Withdrawals => "withdrawal", Self::Consolidations => "consolidation", + Self::BuilderDeposits => "builder_deposit", + Self::BuilderExits => "builder_exit", } } } @@ -90,6 +107,8 @@ impl TryFrom for RequestType { 0x00 => Ok(Self::Deposits), 0x01 => Ok(Self::Withdrawals), 0x02 => Ok(Self::Consolidations), + 0x03 => Ok(Self::BuilderDeposits), + 0x04 => Ok(Self::BuilderExits), v => Err(v), } } @@ -748,7 +767,7 @@ impl From> for WithBlobsAndMev From> for WithBlobsAndMev From> for WithBlobsAndMev( ContiguousList, ContiguousList, ContiguousList, + ContiguousList, + ContiguousList, ); impl Serialize for RawExecutionRequests

{ @@ -1009,7 +1030,13 @@ impl Serialize for RawExecutionRequests

{ where S: serde::Serializer, { - let Self(deposit_requests, withdrawal_requests, consolidation_requests) = self; + let Self( + deposit_requests, + withdrawal_requests, + consolidation_requests, + builder_deposits, + builder_exits, + ) = self; let mut seq = serializer.serialize_seq(None)?; if !deposit_requests.is_empty() { @@ -1040,11 +1067,32 @@ impl Serialize for RawExecutionRequests

{ ))?; } + if !builder_deposits.is_empty() { + let bytes = builder_deposits.to_ssz().map_err(S::Error::custom)?; + + seq.serialize_element(&format_args!( + "{}{}", + RequestType::BuilderDeposits.request_type(), + const_hex::encode(bytes).as_str(), + ))?; + } + + if !builder_exits.is_empty() { + let bytes = builder_exits.to_ssz().map_err(S::Error::custom)?; + + seq.serialize_element(&format_args!( + "{}{}", + RequestType::BuilderExits.request_type(), + const_hex::encode(bytes).as_str(), + ))?; + } + seq.end() } } impl<'de, P: Preset> Deserialize<'de> for RawExecutionRequests

{ + #[expect(clippy::too_many_lines)] fn deserialize(deserializer: D) -> Result where D: Deserializer<'de>, @@ -1110,6 +1158,8 @@ impl<'de, P: Preset> Deserialize<'de> for RawExecutionRequests

{ let mut deposit_requests = ContiguousList::default(); let mut withdrawal_requests = ContiguousList::default(); let mut consolidation_requests = ContiguousList::default(); + let mut builder_deposits = ContiguousList::default(); + let mut builder_exits = ContiguousList::default(); while let Some(next_element) = seq.next_element::()? { if let Some(digits) = @@ -1145,6 +1195,30 @@ impl<'de, P: Preset> Deserialize<'de> for RawExecutionRequests

{ continue; } + if let Some(digits) = + next_element.strip_prefix(RequestType::BuilderDeposits.request_type()) + { + builder_deposits = decode_requests( + digits, + RequestType::BuilderDeposits, + &mut prev_request_type, + )?; + + continue; + } + + if let Some(digits) = + next_element.strip_prefix(RequestType::BuilderExits.request_type()) + { + builder_exits = decode_requests( + digits, + RequestType::BuilderExits, + &mut prev_request_type, + )?; + + continue; + } + return Err(serde::de::Error::unknown_variant( &next_element, SUPPORTED_REQUEST_TYPES, @@ -1155,6 +1229,8 @@ impl<'de, P: Preset> Deserialize<'de> for RawExecutionRequests

{ deposit_requests, withdrawal_requests, consolidation_requests, + builder_deposits, + builder_exits, )) } } @@ -1167,21 +1243,28 @@ impl<'de, P: Preset> Deserialize<'de> for RawExecutionRequests

{ } } -impl From> for RawExecutionRequests

{ - fn from(execution_requests: ExecutionRequests

) -> Self { - let ExecutionRequests { +impl From> for RawExecutionRequests

{ + fn from(execution_requests: ElectraExecutionRequests

) -> Self { + let ElectraExecutionRequests { deposits, withdrawals, consolidations, } = execution_requests; - Self(deposits, withdrawals, consolidations) + Self( + deposits, + withdrawals, + consolidations, + ContiguousList::default(), + ContiguousList::default(), + ) } } -impl From> for ExecutionRequests

{ +impl From> for ElectraExecutionRequests

{ fn from(raw_execution_requests: RawExecutionRequests

) -> Self { - let RawExecutionRequests(deposits, withdrawals, consolidations) = raw_execution_requests; + let RawExecutionRequests(deposits, withdrawals, consolidations, ..) = + raw_execution_requests; Self { deposits, @@ -1191,6 +1274,55 @@ impl From> for ExecutionRequests

{ } } +impl From> for RawExecutionRequests

{ + fn from(execution_requests: GloasExecutionRequests

) -> Self { + let GloasExecutionRequests { + deposits, + withdrawals, + consolidations, + builder_deposits, + builder_exits, + } = execution_requests; + + Self( + deposits, + withdrawals, + consolidations, + builder_deposits, + builder_exits, + ) + } +} + +impl From> for GloasExecutionRequests

{ + fn from(raw_execution_requests: RawExecutionRequests

) -> Self { + let RawExecutionRequests( + deposits, + withdrawals, + consolidations, + builder_deposits, + builder_exits, + ) = raw_execution_requests; + + Self { + deposits, + withdrawals, + consolidations, + builder_deposits, + builder_exits, + } + } +} + +impl From> for RawExecutionRequests

{ + fn from(execution_requests: ExecutionRequests

) -> Self { + match execution_requests { + ExecutionRequests::Electra(requests) => requests.into(), + ExecutionRequests::Gloas(requests) => requests.into(), + } + } +} + #[derive(Clone, Deserialize)] #[serde(bound = "")] pub struct BlobAndProofV1 { @@ -1330,7 +1462,7 @@ mod tests { #[test] fn test_non_default_raw_execution_requests_roundtrip() -> Result<()> { - let execution_requests = ExecutionRequests:: { + let execution_requests = ElectraExecutionRequests:: { deposits: ContiguousList::try_from(vec![ DepositRequest { pubkey: hex!("92f9fe7570a6650d030bb2227d699c744303d08a887cd2e1592e30906cd8cedf9646c1a1afd902235bb36620180eb688").into(), @@ -1384,7 +1516,7 @@ mod tests { #[test] fn test_invalid_prefix_raw_execution_requests_deserialization() { let payload = json!([ - "0x0392f9fe7570a6650d030bb2227d699c744303d08a887cd2e1592e30906cd8cedf9646c1a1afd902235bb36620180eb68802000000000000000000000065d08a056c17ae13370565b04cf77d2afa1cb9fa0010a5d4e8000000a13741d65b47825c147201cfce3360438d4011fe81b455e86226c95a2669bfde14712ba36d1c2f44371a98bf28ff38370ce7d28c65872bf65ff88d6014468676029e298903c89c51c27ab5f07e178b8b14d3ca191e2ce3b24703629e3994e05b000000000000000090a58546229c585cef35f3afab904411530303d95c371e246a2e9a1ef6beb5db7a98c2fd79a388709a30ec782576a5d602000000000000000000000065d08a056c17ae13370565b04cf77d2afa1cb9fa0010a5d4e8000000b23e205d2fcfc3e9d3ae58c0f78b55b19f97f59eaf43d85113a1960ee2c38f6b4ef705302e46e0593fc41ba5632b047a14d76dc82bb2619d7c73e0d89da2eda2ea11fff9036c2d08f9d457c07f23b1411ecd13ff0e9c00eeb85d851bae2494e00100000000000000", + "0x0592f9fe7570a6650d030bb2227d699c744303d08a887cd2e1592e30906cd8cedf9646c1a1afd902235bb36620180eb68802000000000000000000000065d08a056c17ae13370565b04cf77d2afa1cb9fa0010a5d4e8000000a13741d65b47825c147201cfce3360438d4011fe81b455e86226c95a2669bfde14712ba36d1c2f44371a98bf28ff38370ce7d28c65872bf65ff88d6014468676029e298903c89c51c27ab5f07e178b8b14d3ca191e2ce3b24703629e3994e05b000000000000000090a58546229c585cef35f3afab904411530303d95c371e246a2e9a1ef6beb5db7a98c2fd79a388709a30ec782576a5d602000000000000000000000065d08a056c17ae13370565b04cf77d2afa1cb9fa0010a5d4e8000000b23e205d2fcfc3e9d3ae58c0f78b55b19f97f59eaf43d85113a1960ee2c38f6b4ef705302e46e0593fc41ba5632b047a14d76dc82bb2619d7c73e0d89da2eda2ea11fff9036c2d08f9d457c07f23b1411ecd13ff0e9c00eeb85d851bae2494e00100000000000000", ]); let error = serde_json::from_value::>(payload) diff --git a/fork_choice_control/src/mutator.rs b/fork_choice_control/src/mutator.rs index d25af9a39..8ac02c0a4 100644 --- a/fork_choice_control/src/mutator.rs +++ b/fork_choice_control/src/mutator.rs @@ -528,7 +528,7 @@ where self.execution_payload_envelope_by_root(head.block_root)? .map(|envelope| { - params = Some(ExecutionPayloadParams::Electra { + params = Some(ExecutionPayloadParams::Gloas { versioned_hashes, parent_beacon_block_root: head.block.message().parent_root(), execution_requests: envelope.message.execution_requests.clone(), diff --git a/fork_choice_control/src/spec_tests.rs b/fork_choice_control/src/spec_tests.rs index 27f2f6333..0424cd8c4 100644 --- a/fork_choice_control/src/spec_tests.rs +++ b/fork_choice_control/src/spec_tests.rs @@ -184,6 +184,8 @@ struct PtcVotes { ["consensus-spec-tests/tests/mainnet/gloas/fork_choice/on_block/*/*"] [gloas_mainnet_on_block] [Mainnet] [Gloas]; ["consensus-spec-tests/tests/mainnet/gloas/fork_choice/on_execution_payload_envelope/*/*"] [gloas_mainnet_on_execution_payload_envelope] [Mainnet] [Gloas]; ["consensus-spec-tests/tests/mainnet/gloas/fork_choice/on_payload_attestation_message/*/*"] [gloas_mainnet_on_payload_attestation_message] [Mainnet] [Gloas]; + ["consensus-spec-tests/tests/mainnet/gloas/fork_choice/payload_data_availability/*/*"] [gloas_mainnet_payload_data_availability] [Mainnet] [Gloas]; + ["consensus-spec-tests/tests/mainnet/gloas/fork_choice/payload_timeliness/*/*"] [gloas_mainnet_payload_timeliness] [Mainnet] [Gloas]; ["consensus-spec-tests/tests/minimal/gloas/fork_choice/deposit_with_reorg/*/*"] [gloas_minimal_deposit_with_reorg] [Minimal] [Gloas]; ["consensus-spec-tests/tests/minimal/gloas/fork_choice/ex_ante/*/*"] [gloas_minimal_ex_ante] [Minimal] [Gloas]; ["consensus-spec-tests/tests/minimal/gloas/fork_choice/get_head/*/*"] [gloas_minimal_get_head] [Minimal] [Gloas]; @@ -192,6 +194,8 @@ struct PtcVotes { ["consensus-spec-tests/tests/minimal/gloas/fork_choice/on_block/*/*"] [gloas_minimal_on_block] [Minimal] [Gloas]; ["consensus-spec-tests/tests/minimal/gloas/fork_choice/on_execution_payload_envelope/*/*"] [gloas_minimal_on_execution_payload_envelope] [Minimal] [Gloas]; ["consensus-spec-tests/tests/minimal/gloas/fork_choice/on_payload_attestation_message/*/*"] [gloas_minimal_on_payload_attestation_message] [Minimal] [Gloas]; + ["consensus-spec-tests/tests/minimal/gloas/fork_choice/payload_data_availability/*/*"] [gloas_minimal_payload_data_availability] [Minimal] [Gloas]; + ["consensus-spec-tests/tests/minimal/gloas/fork_choice/payload_timeliness/*/*"] [gloas_minimal_payload_timeliness] [Minimal] [Gloas]; ["consensus-spec-tests/tests/minimal/gloas/fork_choice/reorg/*/*"] [gloas_minimal_reorg] [Minimal] [Gloas]; ["consensus-spec-tests/tests/minimal/gloas/fork_choice/withholding/*/*"] [gloas_minimal_withholding] [Minimal] [Gloas]; )] diff --git a/fork_choice_store/src/store.rs b/fork_choice_store/src/store.rs index 87aa49bf2..076dff3cf 100644 --- a/fork_choice_store/src/store.rs +++ b/fork_choice_store/src/store.rs @@ -3710,7 +3710,7 @@ impl> Store { .map(misc::kzg_commitment_to_versioned_hash) .collect(); - let params = Some(ExecutionPayloadParams::Electra { + let params = Some(ExecutionPayloadParams::Gloas { versioned_hashes, parent_beacon_block_root, execution_requests: envelope.message.execution_requests.clone(), diff --git a/helper_functions/src/error.rs b/helper_functions/src/error.rs index 6c16081f0..c6207fa5f 100644 --- a/helper_functions/src/error.rs +++ b/helper_functions/src/error.rs @@ -86,6 +86,8 @@ pub enum SignatureKind { BlsToExecutionChange, #[display("builder signature")] Builder, + #[display("builder deposit signature")] + BuilderDeposit, #[display("consolidation signature")] Consolidation, #[display("sync committee contribution and proof signature")] diff --git a/helper_functions/src/fork.rs b/helper_functions/src/fork.rs index b8e117c66..91e3446d1 100644 --- a/helper_functions/src/fork.rs +++ b/helper_functions/src/fork.rs @@ -1,9 +1,10 @@ use core::ops::BitOrAssign as _; +use std::collections::{HashMap, HashSet}; use std::sync::Arc; use anyhow::Result; use arithmetic::U64Ext as _; -use bls::{SignatureBytes, traits::SignatureBytes as _}; +use bls::{PublicKeyBytes, SignatureBytes, traits::SignatureBytes as _}; use itertools::Itertools as _; use pubkey_cache::PubkeyCache; use ssz::{BitVector, PersistentList, PersistentVector, SszHash}; @@ -28,22 +29,33 @@ use types::{ }, electra::{ beacon_state::BeaconState as ElectraBeaconState, - consts::UNSET_DEPOSIT_REQUESTS_START_INDEX, - containers::{ExecutionRequests, PendingDeposit}, + consts::UNSET_DEPOSIT_REQUESTS_START_INDEX, containers::PendingDeposit, }, fulu::beacon_state::BeaconState as FuluBeaconState, - gloas::{beacon_state::BeaconState as GloasBeaconState, containers::ExecutionPayloadBid}, + gloas::{ + beacon_state::BeaconState as GloasBeaconState, + consts::PAYLOAD_BUILDER_VERSION, + containers::{ExecutionPayloadBid, ExecutionRequests}, + primitives::BuilderIndex, + }, phase0::{ beacon_state::BeaconState as Phase0BeaconState, consts::{FAR_FUTURE_EPOCH, GENESIS_SLOT}, containers::{Fork, PendingAttestation}, - primitives::H256, + primitives::{ExecutionAddress, H256}, }, preset::Preset, traits::{BeaconState as _, PostElectraBeaconState as _}, }; -use crate::{accessors, gloas::apply_deposit_for_builder, misc, mutators, phase0, predicates}; +use crate::{ + accessors, + gloas::add_builder_to_registry, + misc, + mutators::{self, builder_balance, increase_balance}, + phase0, + predicates::{self, is_valid_deposit_signature}, +}; pub fn upgrade_to_altair( config: &Config, @@ -964,11 +976,14 @@ fn onboard_builders( state: &mut GloasBeaconState

, ) -> Result<()> { let mut signature_cache = DepositSignatureCache::new(); - let validator_pubkeys = accessors::get_or_init_validator_indices(state, true) + + let validator_pubkeys: HashSet<_> = accessors::get_or_init_validator_indices(state, true) .keys() .copied() - .collect_vec(); - let mut builder_pubkeys = vec![]; + .collect(); + + let mut builder_indices: HashMap = HashMap::new(); + let mut pending_deposits = vec![]; for deposit in &state.pending_deposits().clone() { @@ -976,45 +991,41 @@ fn onboard_builders( pubkey, withdrawal_credentials, amount, - signature, slot, + .. } = *deposit; - if validator_pubkeys.contains(&pubkey) { - pending_deposits.push(*deposit); - continue; - } - - if !builder_pubkeys.contains(&pubkey) { - if !predicates::is_builder_withdrawal_credential(withdrawal_credentials) { + if let Some(builder_index) = builder_indices.get(&pubkey) { + increase_balance(builder_balance(state, *builder_index)?, amount)?; + } else { + let is_not_builder = validator_pubkeys.contains(&pubkey) + || !predicates::is_builder_withdrawal_credential(withdrawal_credentials) + || predicates::is_pending_validator( + config, + &pending_deposits, + pubkey, + pubkey_cache, + &mut signature_cache, + ); + + if is_not_builder { pending_deposits.push(*deposit); - continue; - } - - if predicates::is_pending_validator( - config, - &pending_deposits, - pubkey, - pubkey_cache, - &mut signature_cache, - ) { - pending_deposits.push(*deposit); - continue; + } else if is_valid_deposit_signature(config, pubkey_cache, deposit) { + let mut address = ExecutionAddress::zero(); + address.assign_from_slice(&withdrawal_credentials[12..]); + + add_builder_to_registry( + state, + pubkey, + PAYLOAD_BUILDER_VERSION, + address, + amount, + slot, + )?; + + builder_indices.insert(pubkey, builder_indices.len().try_into()?); } } - - builder_pubkeys.push(pubkey); - - apply_deposit_for_builder( - config, - pubkey_cache, - state, - pubkey, - withdrawal_credentials, - amount, - signature, - slot, - )?; } *state.pending_deposits_mut() = PersistentList::try_from_iter(pending_deposits)?; diff --git a/helper_functions/src/gloas.rs b/helper_functions/src/gloas.rs index c3b4b16ee..84f3d8743 100644 --- a/helper_functions/src/gloas.rs +++ b/helper_functions/src/gloas.rs @@ -1,96 +1,31 @@ use anyhow::Result; -use bls::{PublicKeyBytes, SignatureBytes}; -use pubkey_cache::PubkeyCache; -use ssz::H256; +use bls::PublicKeyBytes; use types::{ config::Config, - gloas::{consts::PAYLOAD_BUILDER_VERSION, containers::Builder, primitives::BuilderIndex}, + gloas::{containers::Builder, primitives::BuilderIndex}, phase0::{ consts::FAR_FUTURE_EPOCH, - containers::DepositMessage, primitives::{ExecutionAddress, Gwei, Slot}, }, preset::Preset, traits::PostGloasBeaconState, }; -use crate::{ - accessors::get_current_epoch, error::Error, misc::compute_epoch_at_slot, - signing::SignForAllForks as _, -}; - -#[expect(clippy::too_many_arguments)] -pub fn apply_deposit_for_builder( - config: &Config, - pubkey_cache: &PubkeyCache, - state: &mut impl PostGloasBeaconState

, - pubkey: PublicKeyBytes, - withdrawal_credentials: H256, - amount: Gwei, - signature: SignatureBytes, - slot: Slot, -) -> Result<()> { - if let Some(builder_index) = state - .builders() - .into_iter() - .position(|builder| builder.pubkey == pubkey) - { - let current_epoch = get_current_epoch(state); - let builder_index = builder_index.try_into()?; - let builder = state - .builders_mut() - .get_mut(builder_index) - .expect("builder index is valid since its pubkey found in builder registry"); - - builder.balance = builder.balance.checked_add(amount).ok_or_else(|| { - anyhow::anyhow!( - "balance overflow when applying deposit for builder at index {builder_index}", - ) - })?; - - // > If exited, reset the withdrawable epoch - if builder.withdrawable_epoch != FAR_FUTURE_EPOCH { - builder.withdrawable_epoch = current_epoch - .checked_add(config.min_builder_withdrawability_delay) - .ok_or(Error::EpochOverflow)?; - } - } else { - // > Verify the deposit signature (proof of possession) - // > which is not checked by the deposit contract - let deposit_message = DepositMessage { - pubkey, - withdrawal_credentials, - amount, - }; - - // > Fork-agnostic domain since deposits are valid across forks - if let Ok(decompressed) = pubkey_cache.get_or_insert(pubkey) - && deposit_message - .verify(config, signature, decompressed) - .is_ok() - { - add_builder_to_registry(state, pubkey, withdrawal_credentials, amount, slot)?; - } - } - - Ok(()) -} +use crate::{accessors::get_current_epoch, error::Error, misc::compute_epoch_at_slot}; -fn add_builder_to_registry( +pub fn add_builder_to_registry( state: &mut impl PostGloasBeaconState

, pubkey: PublicKeyBytes, - withdrawal_credentials: H256, + version: u8, + address: ExecutionAddress, amount: Gwei, slot: Slot, ) -> Result<()> { let builder_index = get_index_for_new_builder(state); - let mut address = ExecutionAddress::zero(); - address.assign_from_slice(&withdrawal_credentials[12..]); - let builder = Builder { pubkey, - version: PAYLOAD_BUILDER_VERSION, + version, execution_address: address, balance: amount, deposit_epoch: compute_epoch_at_slot::

(slot), @@ -108,6 +43,22 @@ fn add_builder_to_registry( Ok(()) } +pub fn initiate_builder_exit( + config: &Config, + state: &mut impl PostGloasBeaconState

, + builder_index: BuilderIndex, +) -> Result<()> { + // > Set builder withdrawable epoch + let current_epoch = get_current_epoch(state); + let builder = state.builders_mut().get_mut(builder_index)?; + + builder.withdrawable_epoch = current_epoch + .checked_add(config.min_builder_withdrawability_delay) + .ok_or(Error::EpochOverflow)?; + + Ok(()) +} + fn get_index_for_new_builder(state: &impl PostGloasBeaconState

) -> BuilderIndex { let current_epoch = get_current_epoch(state); diff --git a/helper_functions/src/mutators.rs b/helper_functions/src/mutators.rs index 4b3b641db..246b1ba71 100644 --- a/helper_functions/src/mutators.rs +++ b/helper_functions/src/mutators.rs @@ -114,22 +114,6 @@ pub fn initiate_validator_exit( Ok(()) } -pub fn initiate_builder_exit( - config: &Config, - state: &mut impl PostGloasBeaconState

, - builder_index: BuilderIndex, -) -> Result<()> { - // > Set builder withdrawable epoch - let current_epoch = get_current_epoch(state); - let builder = state.builders_mut().get_mut(builder_index)?; - - builder.withdrawable_epoch = current_epoch - .checked_add(config.min_builder_withdrawability_delay) - .ok_or(Error::EpochOverflow)?; - - Ok(()) -} - pub fn switch_to_compounding_validator( state: &mut impl PostElectraBeaconState

, index: ValidatorIndex, diff --git a/helper_functions/src/predicates.rs b/helper_functions/src/predicates.rs index 22ab64d67..e3709f80d 100644 --- a/helper_functions/src/predicates.rs +++ b/helper_functions/src/predicates.rs @@ -23,7 +23,9 @@ use types::{ fulu::containers::DataColumnSidecar, gloas::{ consts::BUILDER_WITHDRAWAL_PREFIX, - containers::{Builder, IndexedPayloadAttestation}, + containers::{ + Builder, BuilderDepositMessage, BuilderDepositRequest, IndexedPayloadAttestation, + }, primitives::BuilderIndex, }, phase0::{ @@ -603,6 +605,31 @@ pub fn is_valid_deposit_signature( .is_ok() } +#[must_use] +pub fn is_valid_builder_deposit_signature( + config: &Config, + pubkey_cache: &PubkeyCache, + request: &BuilderDepositRequest, +) -> bool { + let BuilderDepositRequest { + pubkey, + withdrawal_credentials, + amount, + signature, + } = *request; + + let deposit_message = BuilderDepositMessage { + pubkey, + withdrawal_credentials, + amount, + }; + + pubkey_cache + .get_or_insert(pubkey) + .and_then(|decompressed| deposit_message.verify(config, signature, decompressed)) + .is_ok() +} + #[cfg(test)] mod spec_tests { use duplicate::duplicate_item; diff --git a/helper_functions/src/signing.rs b/helper_functions/src/signing.rs index 7cb1cd6d3..5dd8d21ff 100644 --- a/helper_functions/src/signing.rs +++ b/helper_functions/src/signing.rs @@ -30,10 +30,13 @@ use types::{ }, fulu::containers::BeaconBlock as FuluBeaconBlock, gloas::{ - consts::{DOMAIN_BEACON_BUILDER, DOMAIN_PROPOSER_PREFERENCES, DOMAIN_PTC_ATTESTER}, + consts::{ + DOMAIN_BEACON_BUILDER, DOMAIN_BUILDER_DEPOSIT, DOMAIN_PROPOSER_PREFERENCES, + DOMAIN_PTC_ATTESTER, + }, containers::{ - BeaconBlock as GloasBeaconBlock, ExecutionPayloadBid, ExecutionPayloadEnvelope, - PayloadAttestationData, ProposerPreferences, + BeaconBlock as GloasBeaconBlock, BuilderDepositMessage, ExecutionPayloadBid, + ExecutionPayloadEnvelope, PayloadAttestationData, ProposerPreferences, }, }, phase0::{ @@ -505,3 +508,9 @@ impl SignForSingleFork

for ProposerPreferences { misc::compute_signing_root(self, domain) } } + +// +impl SignForAllForks for BuilderDepositMessage { + const DOMAIN_TYPE: DomainType = DOMAIN_BUILDER_DEPOSIT; + const SIGNATURE_KIND: SignatureKind = SignatureKind::BuilderDeposit; +} diff --git a/transition_functions/src/gloas/block_processing.rs b/transition_functions/src/gloas/block_processing.rs index d92ada192..c8bb3800d 100644 --- a/transition_functions/src/gloas/block_processing.rs +++ b/transition_functions/src/gloas/block_processing.rs @@ -8,12 +8,12 @@ use helper_functions::{ accessors::{ self, attestation_epoch, get_attestation_participation_flags, get_base_reward, get_base_reward_per_increment, get_beacon_proposer_index, get_current_epoch, - get_indexed_payload_attestation, get_pending_balance_to_withdraw_for_builder, - get_previous_epoch, get_randao_mix, initialize_shuffled_indices, + get_indexed_payload_attestation, get_previous_epoch, get_randao_mix, + initialize_shuffled_indices, }, electra::{ - get_attesting_indices, get_indexed_attestation, initiate_validator_exit, - is_fully_withdrawable_validator, is_partially_withdrawable_validator, slash_validator, + get_attesting_indices, get_indexed_attestation, is_fully_withdrawable_validator, + is_partially_withdrawable_validator, slash_validator, }, error::SignatureKind, misc::{ @@ -21,9 +21,7 @@ use helper_functions::{ compute_epoch_at_slot, convert_builder_index_to_validator_index, get_max_effective_balance, maybe_builder_index, }, - mutators::{ - balance, builder_balance, decrease_balance, increase_balance, initiate_builder_exit, - }, + mutators::{balance, builder_balance, decrease_balance, increase_balance}, predicates::{ can_builder_cover_bid, is_active_builder, is_attestation_same_slot, validate_constructed_indexed_attestation, validate_constructed_indexed_payload_attestation, @@ -43,20 +41,19 @@ use types::{ altair::consts::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR}, capella::{containers::Withdrawal, primitives::WithdrawalIndex}, config::Config, - electra::containers::{Attestation, ExecutionRequests}, + electra::containers::Attestation, gloas::{ beacon_state::BeaconState as GloasBeaconState, consts::{BUILDER_INDEX_SELF_BUILD, PAYLOAD_BUILDER_VERSION}, containers::{ BeaconBlock, BuilderPendingPayment, BuilderPendingWithdrawal, ExecutionPayloadBid, - PayloadAttestation, SignedBeaconBlock, SignedExecutionPayloadBid, + ExecutionRequests, PayloadAttestation, SignedBeaconBlock, SignedExecutionPayloadBid, }, - primitives::BuilderIndex, }, nonstandard::{AttestationEpoch, SlashingKind}, phase0::{ consts::{FAR_FUTURE_EPOCH, GENESIS_SLOT}, - containers::{AttestationData, ProposerSlashing, SignedVoluntaryExit}, + containers::{AttestationData, ProposerSlashing}, primitives::{Epoch, ExecutionAddress, Gwei}, }, preset::{BuilderPendingPaymentsLength, Preset, SlotsPerHistoricalRoot}, @@ -914,7 +911,13 @@ where } for voluntary_exit in body.voluntary_exits().iter().copied() { - process_voluntary_exit(config, pubkey_cache, state, voluntary_exit, &mut verifier)?; + electra::process_voluntary_exit( + config, + pubkey_cache, + state, + voluntary_exit, + &mut verifier, + )?; } for bls_to_execution_change in body.bls_to_execution_changes().iter().copied() { @@ -1111,121 +1114,6 @@ pub fn validate_attestation_with_verifier( ) } -#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))] -pub fn process_voluntary_exit( - config: &Config, - pubkey_cache: &PubkeyCache, - state: &mut impl PostGloasBeaconState

, - signed_voluntary_exit: SignedVoluntaryExit, - verifier: impl Verifier, -) -> Result<()> { - validate_voluntary_exit_with_verifier( - config, - pubkey_cache, - state, - signed_voluntary_exit, - verifier, - )?; - - // > Initiate exit - let validator_index = signed_voluntary_exit.message.validator_index; - if let Some(builder_index) = maybe_builder_index(validator_index) { - initiate_builder_exit(config, state, builder_index) - } else { - initiate_validator_exit(config, state, validator_index) - } -} - -pub fn validate_voluntary_exit( - config: &Config, - pubkey_cache: &PubkeyCache, - state: &impl PostGloasBeaconState

, - signed_voluntary_exit: SignedVoluntaryExit, -) -> Result<()> { - validate_voluntary_exit_with_verifier( - config, - pubkey_cache, - state, - signed_voluntary_exit, - SingleVerifier, - ) -} - -pub fn validate_voluntary_exit_with_verifier( - config: &Config, - pubkey_cache: &PubkeyCache, - state: &impl PostGloasBeaconState

, - signed_voluntary_exit: SignedVoluntaryExit, - verifier: impl Verifier, -) -> Result<()> { - if let Some(builder_index) = maybe_builder_index(signed_voluntary_exit.message.validator_index) - { - validate_builder_voluntary_exit_with_verifier( - config, - pubkey_cache, - state, - signed_voluntary_exit, - builder_index, - verifier, - ) - } else { - electra::validate_voluntary_exit_with_verifier( - config, - pubkey_cache, - state, - signed_voluntary_exit, - verifier, - ) - } -} - -fn validate_builder_voluntary_exit_with_verifier( - config: &Config, - pubkey_cache: &PubkeyCache, - state: &impl PostGloasBeaconState

, - signed_voluntary_exit: SignedVoluntaryExit, - builder_index: BuilderIndex, - mut verifier: impl Verifier, -) -> Result<()> { - let voluntary_exit = signed_voluntary_exit.message; - let builder = state.builders().get(builder_index)?; - let current_epoch = get_current_epoch(state); - - // > Exits must specify an epoch when they become valid; they are not valid before then - ensure!( - current_epoch >= voluntary_exit.epoch, - Error::

::VoluntaryExitIsExpired { - current_epoch, - epoch: voluntary_exit.epoch, - }, - ); - - // > Verify the builder is active - ensure!( - is_active_builder(builder, state.finalized_checkpoint().epoch), - Error::

::BuilderNotActive { - index: builder_index, - current_epoch - } - ); - - // > Only exit builder if it has no pending withdrawals in the queue - ensure!( - get_pending_balance_to_withdraw_for_builder(state, builder_index)? == 0, - Error::

::BuilderVoluntaryExitWithPendingWithdrawals - ); - - // > Verify signature - verifier.verify_singular( - voluntary_exit.signing_root(config, state), - signed_voluntary_exit.signature, - pubkey_cache.get_or_insert(builder.pubkey)?, - SignatureKind::VoluntaryExit, - )?; - - Ok(()) -} - #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))] pub fn process_payload_attestation( config: &Config, @@ -1469,7 +1357,7 @@ mod spec_tests { processing_tests! { process_voluntary_exit, |config, pubkey_cache, state, voluntary_exit, _| { - process_voluntary_exit( + electra::process_voluntary_exit( config, pubkey_cache, state, @@ -1485,7 +1373,7 @@ mod spec_tests { processing_tests! { process_voluntary_exit_churn, |config, pubkey_cache, state, voluntary_exit, _| { - process_voluntary_exit( + electra::process_voluntary_exit( config, pubkey_cache, state, @@ -1577,7 +1465,7 @@ mod spec_tests { validation_tests! { validate_voluntary_exit, |config, pubkey_cache, state, voluntary_exit| { - validate_voluntary_exit_with_verifier(config, pubkey_cache, state, voluntary_exit, SingleVerifier) + electra::validate_voluntary_exit_with_verifier(config, pubkey_cache, state, voluntary_exit, SingleVerifier) }, "voluntary_exit", "consensus-spec-tests/tests/mainnet/gloas/operations/voluntary_exit/*/*", diff --git a/transition_functions/src/gloas/execution_payload_processing.rs b/transition_functions/src/gloas/execution_payload_processing.rs index b8955a691..666278f2a 100644 --- a/transition_functions/src/gloas/execution_payload_processing.rs +++ b/transition_functions/src/gloas/execution_payload_processing.rs @@ -1,18 +1,19 @@ use anyhow::Result; use helper_functions::{ - gloas::apply_deposit_for_builder, - predicates::{is_builder_withdrawal_credential, is_pending_validator}, + accessors::{get_current_epoch, get_pending_balance_to_withdraw_for_builder}, + gloas::{add_builder_to_registry, initiate_builder_exit}, + predicates::{is_active_builder, is_valid_builder_deposit_signature}, }; use pubkey_cache::PubkeyCache; use types::{ - DepositSignatureCache, config::Config, - electra::containers::{DepositRequest, ExecutionRequests, PendingDeposit}, + gloas::containers::{BuilderDepositRequest, BuilderExitRequest, ExecutionRequests}, + phase0::{consts::FAR_FUTURE_EPOCH, primitives::ExecutionAddress}, preset::Preset, traits::PostGloasBeaconState, }; -use crate::electra; +use crate::{electra, fulu}; pub fn process_execution_requests( config: &Config, @@ -20,16 +21,8 @@ pub fn process_execution_requests( state: &mut impl PostGloasBeaconState

, execution_requests: &ExecutionRequests

, ) -> Result<()> { - let mut signature_cache = DepositSignatureCache::new(); - for deposit_request in &execution_requests.deposits { - process_deposit_request( - config, - pubkey_cache, - state, - *deposit_request, - &mut signature_cache, - )?; + fulu::process_deposit_request(state, *deposit_request)?; } for withdrawal_request in &execution_requests.withdrawals { @@ -40,64 +33,103 @@ pub fn process_execution_requests( electra::process_consolidation_request(config, state, *consolidation_request)?; } + for deposit_request in &execution_requests.builder_deposits { + process_builder_deposit_request(config, pubkey_cache, state, *deposit_request)?; + } + + for exit_request in &execution_requests.builder_exits { + process_builder_exit_request(config, state, *exit_request)?; + } + Ok(()) } #[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))] -pub fn process_deposit_request( +pub fn process_builder_deposit_request( config: &Config, pubkey_cache: &PubkeyCache, state: &mut impl PostGloasBeaconState

, - deposit_request: DepositRequest, - signature_cache: &mut DepositSignatureCache, + deposit_request: BuilderDepositRequest, ) -> Result<()> { - let DepositRequest { + let BuilderDepositRequest { pubkey, withdrawal_credentials, amount, - signature, .. } = deposit_request; - // > Regardless of the withdrawal credentials prefix, if a builder/validator - // already exists with this pubkey, apply the deposit to their balance - if state + if let Some(builder_index) = state .builders() .into_iter() - .any(|builder| builder.pubkey == pubkey) - || (is_builder_withdrawal_credential(withdrawal_credentials) - && !state - .validators() - .into_iter() - .any(|validator| validator.pubkey == pubkey) - && !is_pending_validator( - config, - state.pending_deposits(), - pubkey, - pubkey_cache, - signature_cache, - )) + .position(|builder| builder.pubkey == pubkey) { - apply_deposit_for_builder( - config, - pubkey_cache, + let current_epoch = get_current_epoch(state); + let builder_index = builder_index.try_into()?; + let builder = state + .builders_mut() + .get_mut(builder_index) + .expect("builder index is valid since its pubkey found in builder registry"); + + builder.balance = builder.balance.checked_add(amount).ok_or_else(|| { + anyhow::anyhow!( + "balance overflow when applying deposit for builder at index {builder_index}", + ) + })?; + + // > If exited, reset the withdrawable epoch + if builder.withdrawable_epoch != FAR_FUTURE_EPOCH { + builder.withdrawable_epoch = + current_epoch.checked_add(config.min_builder_withdrawability_delay).ok_or_else(|| { + anyhow::anyhow!( + "epoch overflow when resetting withdrawable epoch for builder at index {builder_index}", + ) + })?; + } + } else if is_valid_builder_deposit_signature(config, pubkey_cache, &deposit_request) { + let mut address = ExecutionAddress::zero(); + address.assign_from_slice(&withdrawal_credentials[12..]); + + add_builder_to_registry( state, pubkey, - withdrawal_credentials, + withdrawal_credentials[0], + address, amount, - signature, state.slot(), )?; - } else { - let slot = state.slot(); + } - state.pending_deposits_mut().push(PendingDeposit { - pubkey, - withdrawal_credentials, - amount, - signature, - slot, - })?; + Ok(()) +} + +#[cfg_attr(feature = "tracing", tracing::instrument(level = "debug", skip_all))] +pub fn process_builder_exit_request( + config: &Config, + state: &mut impl PostGloasBeaconState

, + exit_request: BuilderExitRequest, +) -> Result<()> { + let BuilderExitRequest { + source_address, + pubkey, + } = exit_request; + + if let Some(builder_index) = state + .builders() + .into_iter() + .position(|builder| builder.pubkey == pubkey) + { + let builder_index = builder_index.try_into()?; + let builder = state + .builders() + .get(builder_index) + .expect("builder index is valid since its pubkey found in builder registry"); + + if is_active_builder(builder, state.finalized_checkpoint().epoch) + && builder.execution_address == source_address + && get_pending_balance_to_withdraw_for_builder(state, builder_index)? == 0 + { + initiate_builder_exit(config, state, builder_index)?; + } } Ok(()) @@ -105,8 +137,6 @@ pub fn process_deposit_request( #[cfg(test)] mod spec_tests { - use std::collections::HashMap; - use spec_test_utils::{BlsSetting, Case}; use ssz::SszReadDefault; use test_generator::test_resources; @@ -147,7 +177,7 @@ mod spec_tests { processing_tests! { process_deposit_request, - |config, pubkey_cache, state, deposit_request, _| process_deposit_request(config, pubkey_cache, state, deposit_request, &mut HashMap::new()), + |_, _, state, deposit_request, _| fulu::process_deposit_request(state, deposit_request), "deposit_request", "consensus-spec-tests/tests/mainnet/gloas/operations/deposit_request/*/*", "consensus-spec-tests/tests/minimal/gloas/operations/deposit_request/*/*", @@ -169,6 +199,22 @@ mod spec_tests { "consensus-spec-tests/tests/minimal/gloas/operations/consolidation_request/*/*", } + processing_tests! { + process_builder_deposit_request, + |config, pubkey_cache, state, builder_deposit_request, _| process_builder_deposit_request(config, pubkey_cache, state, builder_deposit_request), + "builder_deposit_request", + "consensus-spec-tests/tests/mainnet/gloas/operations/builder_deposit_request/*/*", + "consensus-spec-tests/tests/minimal/gloas/operations/builder_deposit_request/*/*", + } + + processing_tests! { + process_builder_exit_request, + |config, _, state, builder_exit_request, _| process_builder_exit_request(config, state, builder_exit_request), + "builder_exit_request", + "consensus-spec-tests/tests/mainnet/gloas/operations/builder_exit_request/*/*", + "consensus-spec-tests/tests/minimal/gloas/operations/builder_exit_request/*/*", + } + fn run_processing_case( case: Case, operation_name: &str, diff --git a/transition_functions/src/gloas/state_transition.rs b/transition_functions/src/gloas/state_transition.rs index 6699ef092..f7369cd02 100644 --- a/transition_functions/src/gloas/state_transition.rs +++ b/transition_functions/src/gloas/state_transition.rs @@ -216,17 +216,13 @@ pub fn verify_signatures( // Voluntary exits for voluntary_exit in &block.message.body.voluntary_exits { - let validator_index = voluntary_exit.message.validator_index; - let pubkey = if let Some(builder_index) = misc::maybe_builder_index(validator_index) { - state.builders.get(builder_index)?.pubkey - } else { - *accessors::public_key(state, validator_index)? - }; - verifier.verify_singular( voluntary_exit.message.signing_root(config, state), voluntary_exit.signature, - pubkey_cache.get_or_insert(pubkey)?, + pubkey_cache.get_or_insert(*accessors::public_key( + state, + voluntary_exit.message.validator_index, + )?)?, SignatureKind::VoluntaryExit, )?; } diff --git a/transition_functions/src/lib.rs b/transition_functions/src/lib.rs index 99960b3d9..7f4caeb66 100644 --- a/transition_functions/src/lib.rs +++ b/transition_functions/src/lib.rs @@ -223,7 +223,9 @@ pub mod fulu { pub use epoch_processing::process_proposer_lookahead; pub(crate) use blinded_block_processing::custom_process_blinded_block; - pub(crate) use block_processing::{process_block, process_block_for_gossip}; + pub(crate) use block_processing::{ + process_block, process_block_for_gossip, process_deposit_request, + }; pub(crate) use epoch_processing::process_epoch; pub(crate) use slot_processing::process_slots; pub(crate) use state_transition::{state_transition, verify_signatures}; @@ -237,9 +239,7 @@ pub mod fulu { } pub mod gloas { - pub use block_processing::{ - apply_parent_execution_payload, get_expected_withdrawals, validate_voluntary_exit, - }; + pub use block_processing::{apply_parent_execution_payload, get_expected_withdrawals}; pub(crate) use block_processing::{process_block, process_block_for_gossip}; pub(crate) use epoch_processing::process_epoch; diff --git a/transition_functions/src/unphased/error.rs b/transition_functions/src/unphased/error.rs index ee5a23174..2296faa85 100644 --- a/transition_functions/src/unphased/error.rs +++ b/transition_functions/src/unphased/error.rs @@ -78,8 +78,6 @@ pub enum Error { }, #[error("builder payment index ({index}) out of bounds (length: {length})")] BuilderPaymentIndexOutOfBounds { index: u64, length: u64 }, - #[error("cannot exit builder because it has pending withdrawals in the queue")] - BuilderVoluntaryExitWithPendingWithdrawals, #[error("deposit count is incorrect (computed: {computed}, in_block: {in_block})")] DepositCountMismatch { computed: u64, in_block: u64 }, #[error("deposit proof is invalid: {deposit:?}")] diff --git a/types/src/combined.rs b/types/src/combined.rs index 3b8611883..9f31cd091 100644 --- a/types/src/combined.rs +++ b/types/src/combined.rs @@ -70,7 +70,8 @@ use crate::{ AggregateAndProof as ElectraAggregateAndProof, Attestation as ElectraAttestation, AttesterSlashing as ElectraAttesterSlashing, BeaconBlock as ElectraBeaconBlock, BeaconBlockBody as ElectraBeaconBlockBody, - BlindedBeaconBlock as ElectraBlindedBeaconBlock, ExecutionRequests, + BlindedBeaconBlock as ElectraBlindedBeaconBlock, + ExecutionRequests as ElectraExecutionRequests, LightClientBootstrap as ElectraLightClientBootstrap, LightClientFinalityUpdate as ElectraLightClientFinalityUpdate, LightClientOptimisticUpdate as ElectraLightClientOptimisticUpdate, @@ -99,6 +100,7 @@ use crate::{ containers::{ BeaconBlock as GloasBeaconBlock, DataColumnSidecar as GloasDataColumnSidecar, ExecutionPayload as GloasExecutionPayload, ExecutionPayloadBid, + ExecutionRequests as GloasExecutionRequests, LightClientBootstrap as GloasLightClientBootstrap, LightClientFinalityUpdate as GloasLightClientFinalityUpdate, LightClientOptimisticUpdate as GloasLightClientOptimisticUpdate, @@ -968,7 +970,7 @@ impl BeaconBlock

{ mut self, execution_requests: Option>, ) -> Self { - let Some(execution_requests) = execution_requests else { + let Some(ExecutionRequests::Electra(execution_requests)) = execution_requests else { return self; }; @@ -1010,7 +1012,7 @@ impl BeaconBlock

{ self, execution_payload_header: ExecutionPayloadHeader

, kzg_commitments: Option>, - execution_requests: Option>, + execution_requests: Option>, ) -> Result, BlockPhaseError> { match (self, execution_payload_header) { (Self::Bellatrix(block), ExecutionPayloadHeader::Bellatrix(header)) => Ok(block @@ -1623,10 +1625,51 @@ pub enum ExecutionPayloadParams { Electra { versioned_hashes: Vec, parent_beacon_block_root: H256, - execution_requests: ExecutionRequests

, + execution_requests: ElectraExecutionRequests

, + }, + Gloas { + versioned_hashes: Vec, + parent_beacon_block_root: H256, + execution_requests: GloasExecutionRequests

, }, } +#[derive(Debug, Clone, From, PartialEq, Eq, Deserialize, Serialize)] +#[serde(bound = "", untagged)] +pub enum ExecutionRequests { + Electra(ElectraExecutionRequests

), + Gloas(GloasExecutionRequests

), +} + +impl SszSize for ExecutionRequests

{ + // The const parameter should be `Self::VARIANT_COUNT`, but `Self` refers to a generic type. + // Type parameters cannot be used in `const` contexts until `generic_const_exprs` is stable. + const SIZE: Size = Size::for_untagged_union::<{ Phase::CARDINALITY - 6 }>([ + ElectraExecutionRequests::

::SIZE, + GloasExecutionRequests::

::SIZE, + ]); +} + +impl SszWrite for ExecutionRequests

{ + fn write_variable(&self, bytes: &mut Vec) -> Result<(), WriteError> { + match self { + Self::Electra(execution_requests) => execution_requests.write_variable(bytes), + Self::Gloas(execution_requests) => execution_requests.write_variable(bytes), + } + } +} + +impl SszHash for ExecutionRequests

{ + type PackingFactor = U1; + + fn hash_tree_root(&self) -> H256 { + match self { + Self::Electra(execution_requests) => execution_requests.hash_tree_root(), + Self::Gloas(execution_requests) => execution_requests.hash_tree_root(), + } + } +} + #[derive(Clone, PartialEq, Eq, Debug)] pub enum LightClientBootstrap { Altair(Box>), diff --git a/types/src/gloas/consts.rs b/types/src/gloas/consts.rs index c3ef7b6dd..026121c26 100644 --- a/types/src/gloas/consts.rs +++ b/types/src/gloas/consts.rs @@ -61,6 +61,7 @@ pub const INTERVALS_PER_SLOT_GLOAS: NonZeroUsize = nonzero!(4_usize); pub const DOMAIN_BEACON_BUILDER: DomainType = H32(hex!("0B000000")); pub const DOMAIN_PTC_ATTESTER: DomainType = H32(hex!("0C000000")); pub const DOMAIN_PROPOSER_PREFERENCES: DomainType = H32(hex!("0D000000")); +pub const DOMAIN_BUILDER_DEPOSIT: DomainType = H32(hex!("0E000000")); // Payload status pub const PAYLOAD_STATUS_EMPTY: PayloadStatus = 0u8; diff --git a/types/src/gloas/container_impls.rs b/types/src/gloas/container_impls.rs index a0a753b3b..cfbc40e62 100644 --- a/types/src/gloas/container_impls.rs +++ b/types/src/gloas/container_impls.rs @@ -2,18 +2,18 @@ use core::fmt; use std::sync::Arc; use ssz::{ByteList, ContiguousList, H256}; +use typenum::Unsigned as _; use crate::{ capella::containers::Withdrawal, deneb::primitives::{KzgCommitment, KzgProof}, - electra::containers::{ - ConsolidationRequest, DepositRequest, ExecutionRequests, WithdrawalRequest, - }, + electra::containers::{ConsolidationRequest, DepositRequest, WithdrawalRequest}, gloas::{ containers::{ - CombinedPayloadAttestation, DataColumnSidecar, ExecutionPayload, - ExecutionPayloadEnvelope, PayloadAttestationData, PayloadAttestationMessage, - PayloadEnvelopeIdentifier, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, + BuilderDepositRequest, BuilderExitRequest, CombinedPayloadAttestation, + DataColumnSidecar, ExecutionPayload, ExecutionPayloadEnvelope, ExecutionRequests, + PayloadAttestationData, PayloadAttestationMessage, PayloadEnvelopeIdentifier, + SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, }, primitives::BuilderIndex, }, @@ -44,16 +44,22 @@ impl SignedExecutionPayloadEnvelope

{ payload: ExecutionPayload { extra_data: Arc::new(ByteList::from(ContiguousList::full(u8::MAX))), transactions: Arc::new(ContiguousList::full(ByteList::from( - ContiguousList::full(u8::MAX), + ContiguousList::try_from(vec![u8::MAX; P::MaxBytesPerTransaction::USIZE]) + .expect("should fit in MaxBytesPerTransaction"), ))), withdrawals: ContiguousList::full(Withdrawal::default()), - block_access_list: Arc::new(ByteList::from(ContiguousList::full(u8::MAX))), + block_access_list: Arc::new(ByteList::from( + ContiguousList::try_from(vec![u8::MAX; P::MaxBytesPerTransaction::USIZE]) + .expect("should fit in MaxBytesPerTransaction"), + )), ..Default::default() }, execution_requests: ExecutionRequests { deposits: ContiguousList::full(DepositRequest::default()), withdrawals: ContiguousList::full(WithdrawalRequest::default()), consolidations: ContiguousList::full(ConsolidationRequest::default()), + builder_deposits: ContiguousList::full(BuilderDepositRequest::default()), + builder_exits: ContiguousList::full(BuilderExitRequest::default()), }, ..Default::default() }, diff --git a/types/src/gloas/containers.rs b/types/src/gloas/containers.rs index 0f784d166..9035605dd 100644 --- a/types/src/gloas/containers.rs +++ b/types/src/gloas/containers.rs @@ -13,7 +13,9 @@ use crate::{ deneb::primitives::{KzgCommitment, KzgProof}, electra::{ consts::{CurrentSyncCommitteeIndex, FinalizedRootIndex, NextSyncCommitteeIndex}, - containers::{Attestation, AttesterSlashing, ExecutionRequests}, + containers::{ + Attestation, AttesterSlashing, ConsolidationRequest, DepositRequest, WithdrawalRequest, + }, }, fulu::primitives::{Cell, ColumnIndex}, gloas::consts::ExecutionBlockHashGindexGloas, @@ -74,6 +76,32 @@ pub struct Builder { pub withdrawable_epoch: Epoch, } +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Deserialize, Serialize, Ssz)] +#[serde(deny_unknown_fields)] +pub struct BuilderDepositMessage { + pub pubkey: PublicKeyBytes, + pub withdrawal_credentials: H256, + #[serde(with = "serde_utils::string_or_native")] + pub amount: Gwei, +} + +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Deserialize, Serialize, Ssz)] +#[serde(deny_unknown_fields)] +pub struct BuilderDepositRequest { + pub pubkey: PublicKeyBytes, + pub withdrawal_credentials: H256, + #[serde(with = "serde_utils::string_or_native")] + pub amount: Gwei, + pub signature: SignatureBytes, +} + +#[derive(Clone, Copy, PartialEq, Eq, Default, Debug, Deserialize, Serialize, Ssz)] +#[serde(deny_unknown_fields)] +pub struct BuilderExitRequest { + pub source_address: ExecutionAddress, + pub pubkey: PublicKeyBytes, +} + #[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Deserialize, Serialize, Ssz)] #[serde(bound = "", deny_unknown_fields)] pub struct BuilderPendingPayment { @@ -174,6 +202,17 @@ pub struct ExecutionPayloadEnvelope { pub parent_beacon_block_root: H256, } +#[derive(Clone, PartialEq, Eq, Default, Debug, Deserialize, Serialize, Ssz)] +#[serde(bound = "", deny_unknown_fields)] +pub struct ExecutionRequests { + pub deposits: ContiguousList, + pub withdrawals: ContiguousList, + pub consolidations: ContiguousList, + pub builder_deposits: + ContiguousList, + pub builder_exits: ContiguousList, +} + #[derive(Clone, Copy, PartialEq, Eq, Debug, Deserialize, Serialize, Ssz)] #[serde(bound = "", deny_unknown_fields)] pub struct ForkChoiceNode { diff --git a/types/src/gloas/spec_tests.rs b/types/src/gloas/spec_tests.rs index fcab4a33d..5f0fdb9a2 100644 --- a/types/src/gloas/spec_tests.rs +++ b/types/src/gloas/spec_tests.rs @@ -19,9 +19,8 @@ mod tested_types { }, electra::containers::{ AggregateAndProof, Attestation, AttesterSlashing, ConsolidationRequest, DepositRequest, - ExecutionRequests, IndexedAttestation, PendingConsolidation, PendingDeposit, - PendingPartialWithdrawal, SignedAggregateAndProof, SingleAttestation, - WithdrawalRequest, + IndexedAttestation, PendingConsolidation, PendingDeposit, PendingPartialWithdrawal, + SignedAggregateAndProof, SingleAttestation, WithdrawalRequest, }, fulu::containers::{DataColumnsByRootIdentifier, MatrixEntry}, gloas::{beacon_state::BeaconState, containers::*}, @@ -120,6 +119,18 @@ tests_for_type! { "consensus-spec-tests/tests/minimal/gloas/ssz_static/Builder/*/*", } +tests_for_type! { + BuilderDepositRequest, + "consensus-spec-tests/tests/mainnet/gloas/ssz_static/BuilderDepositRequest/*/*", + "consensus-spec-tests/tests/minimal/gloas/ssz_static/BuilderDepositRequest/*/*", +} + +tests_for_type! { + BuilderExitRequest, + "consensus-spec-tests/tests/mainnet/gloas/ssz_static/BuilderExitRequest/*/*", + "consensus-spec-tests/tests/minimal/gloas/ssz_static/BuilderExitRequest/*/*", +} + tests_for_type! { BuilderPendingPayment, "consensus-spec-tests/tests/mainnet/gloas/ssz_static/BuilderPendingPayment/*/*", diff --git a/types/src/nonstandard.rs b/types/src/nonstandard.rs index a121d4561..6837c3de4 100644 --- a/types/src/nonstandard.rs +++ b/types/src/nonstandard.rs @@ -19,13 +19,12 @@ use crate::{ primitives::ParticipationFlags, }, bellatrix::{containers::PowBlock, primitives::Wei}, - combined::{Attestation, BeaconState, DataColumnSidecar, SignedBeaconBlock}, + combined::{Attestation, BeaconState, DataColumnSidecar, ExecutionRequests, SignedBeaconBlock}, config::Config, deneb::{ containers::{BlobIdentifier, BlobSidecar}, primitives::{Blob, KzgCommitment, KzgProof}, }, - electra::containers::ExecutionRequests, fulu::containers::DataColumnIdentifier, phase0::{ containers::SignedBeaconBlockHeader, diff --git a/types/src/preset.rs b/types/src/preset.rs index 1b2e7c8fe..fc1cba0dd 100644 --- a/types/src/preset.rs +++ b/types/src/preset.rs @@ -41,7 +41,10 @@ use crate::{ PendingPartialWithdrawal, WithdrawalRequest, }, fulu::primitives::{Cell, ColumnIndex}, - gloas::containers::{BuilderPendingPayment, BuilderPendingWithdrawal, PayloadAttestation}, + gloas::containers::{ + BuilderDepositRequest, BuilderExitRequest, BuilderPendingPayment, BuilderPendingWithdrawal, + PayloadAttestation, + }, phase0::{ containers::{ Attestation, AttesterSlashing, Deposit, ProposerSlashing, SignedVoluntaryExit, @@ -244,6 +247,16 @@ pub trait Preset: Copy + Eq + Ord + Hash + Default + Debug + Send + Sync + 'stat + Debug + Send + Sync; + type MaxBuilderDepositRequestsPerPayload: MerkleElements + + Eq + + Debug + + Send + + Sync; + type MaxBuilderExitRequestsPerPayload: MerkleElements + + Eq + + Debug + + Send + + Sync; // Derived type-level variables type MaxAttestersPerSlot: MerkleElements @@ -388,6 +401,8 @@ impl Preset for Mainnet { type MaxPayloadAttestation = U4; type BuilderRegistryLimit = U1099511627776; type BuilderPendingWithdrawalsLimit = U1048576; + type MaxBuilderDepositRequestsPerPayload = U256; + type MaxBuilderExitRequestsPerPayload = U16; // Derived type-level variables type MaxAttestersPerSlot = Prod; @@ -470,6 +485,8 @@ impl Preset for Minimal { type MaxPayloadAttestation; type BuilderRegistryLimit; type BuilderPendingWithdrawalsLimit; + type MaxBuilderDepositRequestsPerPayload; + type MaxBuilderExitRequestsPerPayload; } // Phase 0 @@ -587,6 +604,8 @@ impl Preset for Medalla { type MaxPayloadAttestation; type BuilderRegistryLimit; type BuilderPendingWithdrawalsLimit; + type MaxBuilderDepositRequestsPerPayload; + type MaxBuilderExitRequestsPerPayload; // Derived type-level variables type MaxAttestersPerSlot; @@ -1109,6 +1128,10 @@ pub struct GloasPreset { builder_pending_withdrawals_limit: u64, #[serde(with = "serde_utils::string_or_native")] max_builders_per_withdrawals_sweep: u64, + #[serde(with = "serde_utils::string_or_native")] + max_builder_deposit_requests_per_payload: u64, + #[serde(with = "serde_utils::string_or_native")] + max_builder_exit_requests_per_payload: u64, } impl GloasPreset { @@ -1120,6 +1143,8 @@ impl GloasPreset { builder_registry_limit: P::BuilderRegistryLimit::non_zero(), builder_pending_withdrawals_limit: P::BuilderPendingWithdrawalsLimit::U64, max_builders_per_withdrawals_sweep: P::MAX_BUILDERS_PER_WITHDRAWALS_SWEEP, + max_builder_deposit_requests_per_payload: P::MaxBuilderDepositRequestsPerPayload::U64, + max_builder_exit_requests_per_payload: P::MaxBuilderExitRequestsPerPayload::U64, } } }