diff --git a/beacon_node/beacon_chain/src/beacon_chain.rs b/beacon_node/beacon_chain/src/beacon_chain.rs index d175c54be7..c0526a6c1d 100644 --- a/beacon_node/beacon_chain/src/beacon_chain.rs +++ b/beacon_node/beacon_chain/src/beacon_chain.rs @@ -6028,6 +6028,11 @@ impl BeaconChain { blob_kzg_commitments: kzg_commitments .ok_or(BlockProductionError::InvalidPayloadFork)?, execution_requests: maybe_requests + .map(|r| ExecutionRequestsElectra { + deposits: r.deposits().clone(), + withdrawals: r.withdrawals().clone(), + consolidations: r.consolidations().clone(), + }) .ok_or(BlockProductionError::MissingExecutionRequests)?, }, }), @@ -6082,6 +6087,11 @@ impl BeaconChain { blob_kzg_commitments: kzg_commitments .ok_or(BlockProductionError::InvalidPayloadFork)?, execution_requests: maybe_requests + .map(|r| ExecutionRequestsElectra { + deposits: r.deposits().clone(), + withdrawals: r.withdrawals().clone(), + consolidations: r.consolidations().clone(), + }) .ok_or(BlockProductionError::MissingExecutionRequests)?, }, }), diff --git a/beacon_node/beacon_chain/src/block_production/gloas.rs b/beacon_node/beacon_chain/src/block_production/gloas.rs index 90fc60524a..34571df7a9 100644 --- a/beacon_node/beacon_chain/src/block_production/gloas.rs +++ b/beacon_node/beacon_chain/src/block_production/gloas.rs @@ -31,10 +31,11 @@ use types::{ Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra, BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BeaconStateError, BuilderIndex, ChainSpec, Deposit, Eth1Data, EthSpec, ExecutionBlockHash, ExecutionPayloadBid, - ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, FullPayload, Graffiti, - Hash256, PayloadAttestation, ProposerSlashing, RelativeEpoch, SignedBeaconBlock, - SignedBlsToExecutionChange, SignedExecutionPayloadBid, SignedExecutionPayloadEnvelope, - SignedVoluntaryExit, Slot, SyncAggregate, Withdrawal, Withdrawals, + ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, ExecutionRequestsGloas, + FullPayload, Graffiti, Hash256, PayloadAttestation, ProposerSlashing, RelativeEpoch, + SignedBeaconBlock, SignedBlsToExecutionChange, SignedExecutionPayloadBid, + SignedExecutionPayloadEnvelope, SignedVoluntaryExit, Slot, SyncAggregate, Withdrawal, + Withdrawals, }; use crate::pending_payload_envelopes::PendingEnvelopeData; @@ -74,7 +75,7 @@ pub struct PartialBeaconBlock { /// The envelope requires the beacon_block_root which can only be computed after the block exists. pub struct ExecutionPayloadData { pub payload: ExecutionPayloadGloas, - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsGloas, pub builder_index: BuilderIndex, pub slot: Slot, pub blobs_and_proofs: (types::BlobsList, types::KzgProofs), @@ -175,7 +176,7 @@ impl BeaconChain { .map(|env| env.message.execution_requests.clone()) .ok_or(BlockProductionError::MissingParentExecutionPayload)? } else { - ExecutionRequests::default() + ExecutionRequestsGloas::default() }; // Part 1/3 (blocking) @@ -259,7 +260,7 @@ impl BeaconChain { produce_at_slot: Slot, randao_reveal: Signature, graffiti: Graffiti, - parent_execution_requests: &ExecutionRequests, + parent_execution_requests: &ExecutionRequestsGloas, ) -> Result<(PartialBeaconBlock, BeaconState), BlockProductionError> { // It is invalid to try to produce a block using a state from a future slot. @@ -530,7 +531,7 @@ impl BeaconChain { &self, partial_beacon_block: PartialBeaconBlock, signed_execution_payload_bid: SignedExecutionPayloadBid, - parent_execution_requests: ExecutionRequests, + parent_execution_requests: ExecutionRequestsGloas, payload_data: Option>, mut state: BeaconState, verification: ProduceBlockVerification, @@ -809,6 +810,11 @@ impl BeaconChain { should_override_builder, } = block_proposal_contents; + // The EL get_payload response carries the standard (Electra-shaped) requests; lift them + // into the Gloas variant carried by the envelope and committed to by the bid. + // TODO(gloas): plumb builder deposit/exit requests from the EL. + let execution_requests = to_gloas_execution_requests(execution_requests); + // TODO(gloas) since we are defaulting to local building, execution payment is 0 // execution payment should only be set to > 0 for trusted building. let bid = ExecutionPayloadBid:: { @@ -1107,9 +1113,29 @@ where /// processing. EL-triggered withdrawal-full-exit requests (EIP-7002) and cross-pubkey /// consolidation requests (EIP-7251) call `initiate_validator_exit`, setting the target's /// `exit_epoch`. A voluntary exit for the same validator would then fail with `AlreadyExited`. +/// Lift a fork-agnostic `ExecutionRequests` (as received from the EL) into the Gloas variant. +/// +/// The standard request types are carried over; the Gloas-only builder deposit/exit lists are +/// left empty. +/// TODO(gloas): plumb builder deposit/exit requests from the EL. +fn to_gloas_execution_requests( + requests: ExecutionRequests, +) -> ExecutionRequestsGloas { + match requests { + ExecutionRequests::Gloas(requests) => requests, + other => ExecutionRequestsGloas { + deposits: other.deposits().clone(), + withdrawals: other.withdrawals().clone(), + consolidations: other.consolidations().clone(), + builder_deposits: <_>::default(), + builder_exits: <_>::default(), + }, + } +} + fn filter_voluntary_exits_for_parent_execution_requests( voluntary_exits: &mut Vec, - parent_execution_requests: &ExecutionRequests, + parent_execution_requests: &ExecutionRequestsGloas, pubkey_at_index: impl Fn(u64) -> Option, spec: &ChainSpec, ) { @@ -1161,17 +1187,19 @@ mod tests { fn requests( withdrawals: Vec, consolidations: Vec, - ) -> ExecutionRequests { - ExecutionRequests { + ) -> ExecutionRequestsGloas { + ExecutionRequestsGloas { deposits: VariableList::empty(), withdrawals: VariableList::new(withdrawals).unwrap(), consolidations: VariableList::new(consolidations).unwrap(), + builder_deposits: VariableList::empty(), + builder_exits: VariableList::empty(), } } fn run_filter( exits: &mut Vec, - requests: &ExecutionRequests, + requests: &ExecutionRequestsGloas, validator_pubkeys: &[PublicKeyBytes], spec: &ChainSpec, ) { @@ -1307,7 +1335,7 @@ mod tests { LocalBuildResult { payload_data: ExecutionPayloadData { payload: types::ExecutionPayloadGloas::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: BUILDER_INDEX_SELF_BUILD, slot: Slot::new(0), blobs_and_proofs: (VariableList::empty(), VariableList::empty()), diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index a20963302b..341c7c00f0 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -315,9 +315,9 @@ mod tests { use ssz_types::VariableList; use types::{ BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, Eth1Data, ExecutionBlockHash, - ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, - Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock, SignedExecutionPayloadBid, Slot, - SyncAggregate, + ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, + ExecutionRequestsGloas, Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock, + SignedExecutionPayloadBid, Slot, SyncAggregate, }; use super::verify_envelope_consistency; @@ -336,7 +336,7 @@ mod tests { slot_number: slot, ..ExecutionPayloadGloas::default() }, - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index, beacon_block_root: Hash256::ZERO, parent_beacon_block_root: Hash256::ZERO, @@ -364,7 +364,7 @@ mod tests { voluntary_exits: VariableList::empty(), sync_aggregate: SyncAggregate::empty(), bls_to_execution_changes: VariableList::empty(), - parent_execution_requests: ExecutionRequests::default(), + parent_execution_requests: ExecutionRequestsGloas::default(), signed_execution_payload_bid: SignedExecutionPayloadBid::empty(), payload_attestations: VariableList::empty(), _phantom: PhantomData, diff --git a/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs b/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs index c5c97418c7..81af4133f6 100644 --- a/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs +++ b/beacon_node/beacon_chain/src/pending_payload_cache/mod.rs @@ -518,7 +518,7 @@ mod data_availability_checker_tests { use logging::create_test_tracing_subscriber; use types::test_utils::test_unstructured; use types::{ - ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequests, ForkName, + ExecutionPayloadEnvelope, ExecutionPayloadGloas, ExecutionRequestsGloas, ForkName, MinimalEthSpec, SignedExecutionPayloadEnvelope, }; @@ -619,7 +619,7 @@ mod data_availability_checker_tests { envelope: Arc::new(SignedExecutionPayloadEnvelope { message: ExecutionPayloadEnvelope { payload: ExecutionPayloadGloas::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: 0, beacon_block_root: block_root, parent_beacon_block_root: Hash256::random(), diff --git a/beacon_node/beacon_chain/src/pending_payload_envelopes.rs b/beacon_node/beacon_chain/src/pending_payload_envelopes.rs index 8f7568d017..5d4e461f26 100644 --- a/beacon_node/beacon_chain/src/pending_payload_envelopes.rs +++ b/beacon_node/beacon_chain/src/pending_payload_envelopes.rs @@ -91,7 +91,7 @@ impl PendingPayloadEnvelopes { #[cfg(test)] mod tests { use super::*; - use types::{ExecutionPayloadGloas, ExecutionRequests, Hash256, MainnetEthSpec}; + use types::{ExecutionPayloadGloas, ExecutionRequestsGloas, Hash256, MainnetEthSpec}; type E = MainnetEthSpec; @@ -102,7 +102,7 @@ mod tests { slot_number: slot, ..ExecutionPayloadGloas::default() }, - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: 0, beacon_block_root: Hash256::ZERO, parent_beacon_block_root: Hash256::ZERO, diff --git a/beacon_node/execution_layer/src/block_hash.rs b/beacon_node/execution_layer/src/block_hash.rs index e45bf477a2..69a6324b23 100644 --- a/beacon_node/execution_layer/src/block_hash.rs +++ b/beacon_node/execution_layer/src/block_hash.rs @@ -7,7 +7,7 @@ use keccak_hash::KECCAK_EMPTY_LIST_RLP; use triehash::ordered_trie_root; use types::{ EncodableExecutionBlockHeader, EthSpec, ExecutionBlockHash, ExecutionBlockHeader, - ExecutionPayloadRef, ExecutionRequests, Hash256, + ExecutionPayloadRef, ExecutionRequestsRef, Hash256, }; /// Calculate the block hash of an execution block. @@ -17,7 +17,7 @@ use types::{ pub fn calculate_execution_block_hash( payload: ExecutionPayloadRef, parent_beacon_block_root: Option, - execution_requests: Option<&ExecutionRequests>, + execution_requests: Option>, ) -> (ExecutionBlockHash, Hash256) { // Calculate the transactions root. // We're currently using a deprecated Parity library for this. We should move to a diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 8df7d2a54b..cb5168b6f8 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -844,8 +844,7 @@ impl HttpJsonRpc { ), new_payload_request_electra.versioned_hashes, new_payload_request_electra.parent_beacon_block_root, - new_payload_request_electra - .execution_requests + types::ExecutionRequestsRef::Electra(new_payload_request_electra.execution_requests) .get_execution_requests_list(), ]); @@ -873,8 +872,7 @@ impl HttpJsonRpc { ), new_payload_request_fulu.versioned_hashes, new_payload_request_fulu.parent_beacon_block_root, - new_payload_request_fulu - .execution_requests + types::ExecutionRequestsRef::Electra(new_payload_request_fulu.execution_requests) .get_execution_requests_list(), ]); @@ -902,8 +900,7 @@ impl HttpJsonRpc { ), new_payload_request_gloas.versioned_hashes, new_payload_request_gloas.parent_beacon_block_root, - new_payload_request_gloas - .execution_requests + types::ExecutionRequestsRef::Gloas(new_payload_request_gloas.execution_requests) .get_execution_requests_list(), ]); diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index fb516e3e16..453e4d11be 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -1,12 +1,15 @@ use super::*; use alloy_rlp::RlpEncodable; use serde::{Deserialize, Serialize}; -use ssz::{Decode, Encode, TryFromIter}; +use ssz::{Decode, TryFromIter}; use ssz_types::{FixedVector, VariableList, typenum::Unsigned}; use strum::EnumString; use superstruct::superstruct; use types::data::BlobsList; -use types::execution::{ConsolidationRequests, DepositRequests, RequestType, WithdrawalRequests}; +use types::execution::{ + BuilderDepositRequests, BuilderExitRequests, ConsolidationRequests, DepositRequests, + ExecutionRequestsElectra, ExecutionRequestsGloas, RequestType, WithdrawalRequests, +}; use types::kzg_ext::KzgCommitments; use types::{Blob, KzgProof}; @@ -483,28 +486,13 @@ pub struct JsonExecutionRequests(pub Vec); impl From> for JsonExecutionRequests { fn from(requests: ExecutionRequests) -> Self { - let mut result = Vec::new(); - if !requests.deposits.is_empty() { - result.push(format!( - "0x{:02x}{}", - RequestType::Deposit.to_u8(), - hex::encode(requests.deposits.as_ssz_bytes()) - )); - } - if !requests.withdrawals.is_empty() { - result.push(format!( - "0x{:02x}{}", - RequestType::Withdrawal.to_u8(), - hex::encode(requests.withdrawals.as_ssz_bytes()) - )); - } - if !requests.consolidations.is_empty() { - result.push(format!( - "0x{:02x}{}", - RequestType::Consolidation.to_u8(), - hex::encode(requests.consolidations.as_ssz_bytes()) - )); - } + // Each element is a `RequestType`-prefixed, SSZ-encoded request list (EIP-7685). + // The Gloas variant additionally emits builder deposit/exit requests. + let result = requests + .get_execution_requests_list() + .into_iter() + .map(|bytes| format!("0x{}", hex::encode(bytes))) + .collect(); JsonExecutionRequests(result) } } @@ -513,7 +501,15 @@ impl TryFrom for ExecutionRequests { type Error = RequestsError; fn try_from(value: JsonExecutionRequests) -> Result { - let mut requests = ExecutionRequests::default(); + let mut deposits = DepositRequests::::default(); + let mut withdrawals = WithdrawalRequests::::default(); + let mut consolidations = ConsolidationRequests::::default(); + let mut builder_deposits = BuilderDepositRequests::::default(); + let mut builder_exits = BuilderExitRequests::::default(); + // [New in Gloas:EIP8282] The presence of builder requests determines the variant: the + // EIP-7685 list is fork-agnostic, so we only know it is Gloas-shaped once a builder + // request type appears. + let mut has_builder_requests = false; let mut prev_prefix: Option = None; for (i, request) in value.0.into_iter().enumerate() { // hex string @@ -540,8 +536,8 @@ impl TryFrom for ExecutionRequests { match current_prefix { RequestType::Deposit => { - requests.deposits = DepositRequests::::from_ssz_bytes(request_bytes) - .map_err(|e| { + deposits = + DepositRequests::::from_ssz_bytes(request_bytes).map_err(|e| { RequestsError::DecodeError(format!( "Failed to decode DepositRequest from EL: {:?}", e @@ -549,26 +545,64 @@ impl TryFrom for ExecutionRequests { })?; } RequestType::Withdrawal => { - requests.withdrawals = WithdrawalRequests::::from_ssz_bytes(request_bytes) - .map_err(|e| { - RequestsError::DecodeError(format!( - "Failed to decode WithdrawalRequest from EL: {:?}", - e - )) - })?; + withdrawals = + WithdrawalRequests::::from_ssz_bytes(request_bytes).map_err(|e| { + RequestsError::DecodeError(format!( + "Failed to decode WithdrawalRequest from EL: {:?}", + e + )) + })?; } RequestType::Consolidation => { - requests.consolidations = - ConsolidationRequests::::from_ssz_bytes(request_bytes).map_err(|e| { + consolidations = ConsolidationRequests::::from_ssz_bytes(request_bytes) + .map_err(|e| { RequestsError::DecodeError(format!( "Failed to decode ConsolidationRequest from EL: {:?}", e )) })?; } + RequestType::BuilderDeposit => { + builder_deposits = BuilderDepositRequests::::from_ssz_bytes(request_bytes) + .map_err(|e| { + RequestsError::DecodeError(format!( + "Failed to decode BuilderDepositRequest from EL: {:?}", + e + )) + })?; + has_builder_requests = true; + } + RequestType::BuilderExit => { + builder_exits = BuilderExitRequests::::from_ssz_bytes(request_bytes) + .map_err(|e| { + RequestsError::DecodeError(format!( + "Failed to decode BuilderExitRequest from EL: {:?}", + e + )) + })?; + has_builder_requests = true; + } } } - Ok(requests) + + // Without any builder requests the list is indistinguishable from a pre-Gloas one, so we + // produce the Electra-shaped variant. Consumers that require the Gloas variant lift it + // (carrying empty builder lists) at their boundary. + if has_builder_requests { + Ok(ExecutionRequests::Gloas(ExecutionRequestsGloas { + deposits, + withdrawals, + consolidations, + builder_deposits, + builder_exits, + })) + } else { + Ok(ExecutionRequests::Electra(ExecutionRequestsElectra { + deposits, + withdrawals, + consolidations, + })) + } } } 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 ba94296b85..219761c80a 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 @@ -9,7 +9,8 @@ use types::{ }; use types::{ ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequestsElectra, + ExecutionRequestsGloas, ExecutionRequestsRef, }; #[superstruct( @@ -47,8 +48,13 @@ pub struct NewPayloadRequest<'block, E: EthSpec> { pub versioned_hashes: Vec, #[superstruct(only(Deneb, Electra, Fulu, Gloas))] pub parent_beacon_block_root: Hash256, - #[superstruct(only(Electra, Fulu, Gloas))] - pub execution_requests: &'block ExecutionRequests, + #[superstruct( + only(Electra, Fulu), + partial_getter(rename = "execution_requests_electra") + )] + pub execution_requests: &'block ExecutionRequestsElectra, + #[superstruct(only(Gloas), partial_getter(rename = "execution_requests_gloas"))] + pub execution_requests: &'block ExecutionRequestsGloas, } impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { @@ -123,6 +129,16 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { Ok(()) } + /// Returns the execution requests as a fork-tagged reference, if present. + pub fn execution_requests_ref(&self) -> Option> { + match self { + Self::Bellatrix(_) | Self::Capella(_) | Self::Deneb(_) => None, + Self::Electra(r) => Some(ExecutionRequestsRef::Electra(r.execution_requests)), + Self::Fulu(r) => Some(ExecutionRequestsRef::Electra(r.execution_requests)), + Self::Gloas(r) => Some(ExecutionRequestsRef::Gloas(r.execution_requests)), + } + } + /// Verify the block hash is consistent locally within Lighthouse. /// /// ## Specification @@ -143,7 +159,7 @@ impl<'block, E: EthSpec> NewPayloadRequest<'block, E> { let (header_hash, rlp_transactions_root) = calculate_execution_block_hash( payload, parent_beacon_block_root, - self.execution_requests().ok().copied(), + self.execution_requests_ref(), ); if header_hash != self.block_hash() { diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 78076bee6c..a392d21e2e 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -118,14 +118,14 @@ impl TryFrom> for ProvenancedPayload BlockProposalContents::PayloadAndBlobs { payload: ExecutionPayloadHeader::Fulu(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), + requests: Some(builder_bid.execution_requests.into()), }, }; Ok(ProvenancedPayload::Builder( diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index 9924fbe474..10eaefaaba 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -438,7 +438,9 @@ pub async fn handle_rpc( should_override_builder: false, execution_requests: maybe_execution_requests .clone() - .unwrap_or_default() + .unwrap_or_else(|| { + types::ExecutionRequests::Electra(Default::default()) + }) .into(), }) .unwrap() @@ -461,7 +463,9 @@ pub async fn handle_rpc( should_override_builder: false, execution_requests: maybe_execution_requests .clone() - .unwrap_or_default() + .unwrap_or_else(|| { + types::ExecutionRequests::Electra(Default::default()) + }) .into(), }) .unwrap() @@ -483,7 +487,9 @@ pub async fn handle_rpc( .into(), should_override_builder: false, execution_requests: maybe_execution_requests - .unwrap_or_default() + .unwrap_or_else(|| { + types::ExecutionRequests::Electra(Default::default()) + }) .into(), }) .unwrap() 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 d456c9adc1..5bcc7613b5 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -35,8 +35,9 @@ use types::builder::{ }; use types::{ Address, BeaconState, ChainSpec, Epoch, EthSpec, ExecPayload, ExecutionPayload, - ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, ForkVersionDecode, Hash256, - SignedBlindedBeaconBlock, SignedRoot, SignedValidatorRegistrationData, Slot, Uint256, + ExecutionPayloadHeaderRefMut, ExecutionRequests, ExecutionRequestsElectra, ForkName, + ForkVersionDecode, Hash256, SignedBlindedBeaconBlock, SignedRoot, + SignedValidatorRegistrationData, Slot, Uint256, }; use warp::reply::{self, Reply}; use warp::{Filter, Rejection}; @@ -603,7 +604,13 @@ impl MockBuilder { .unwrap_or_default(), value: self.get_bid_value(value), pubkey: self.builder_sk.public_key().compress(), - execution_requests: maybe_requests.unwrap_or_default(), + execution_requests: maybe_requests + .map(|r| ExecutionRequestsElectra { + deposits: r.deposits().clone(), + withdrawals: r.withdrawals().clone(), + consolidations: r.consolidations().clone(), + }) + .unwrap_or_default(), }), ForkName::Electra => BuilderBid::Electra(BuilderBidElectra { header: payload @@ -615,7 +622,13 @@ impl MockBuilder { .unwrap_or_default(), value: self.get_bid_value(value), pubkey: self.builder_sk.public_key().compress(), - execution_requests: maybe_requests.unwrap_or_default(), + execution_requests: maybe_requests + .map(|r| ExecutionRequestsElectra { + deposits: r.deposits().clone(), + withdrawals: r.withdrawals().clone(), + consolidations: r.consolidations().clone(), + }) + .unwrap_or_default(), }), ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { header: payload diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 7a40ff28af..5d293890ee 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -564,7 +564,7 @@ pub fn process_parent_execution_payload( state: &mut BeaconState, - requests: &ExecutionRequests, + requests: &ExecutionRequestsGloas, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { let parent_bid = state.latest_execution_payload_bid()?.clone(); @@ -599,9 +599,11 @@ pub fn apply_parent_execution_payload( let parent_epoch = parent_slot.epoch(E::slots_per_epoch()); // Process execution requests from the parent's payload - process_operations::process_deposit_requests_post_gloas(state, &requests.deposits, spec)?; + process_operations::process_deposit_requests(state, &requests.deposits, spec)?; process_operations::process_withdrawal_requests(state, &requests.withdrawals, spec)?; process_operations::process_consolidation_requests(state, &requests.consolidations, spec)?; + process_operations::process_builder_deposit_requests(state, &requests.builder_deposits, spec)?; + process_operations::process_builder_exit_requests(state, &requests.builder_exits, spec)?; // Queue the builder payment if parent_epoch == state.current_epoch() { diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index d081a27df3..354001a75a 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -54,11 +54,7 @@ pub fn process_operations>( )?; } else if state.fork_name_unchecked().electra_enabled() { state.update_pubkey_cache()?; - process_deposit_requests_pre_gloas( - state, - &block_body.execution_requests()?.deposits, - spec, - )?; + process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?; process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?; process_consolidation_requests( state, @@ -528,15 +524,6 @@ pub fn process_exits( .into_with_index(i)); } - // [New in Gloas:EIP7732] - if state.fork_name_unchecked().gloas_enabled() - && is_builder_index(exit.message.validator_index) - { - process_builder_voluntary_exit(state, exit, verify_signatures, spec) - .map_err(|e| e.into_with_index(i))?; - continue; - } - verify_exit(state, Some(current_epoch), exit, verify_signatures, spec) .map_err(|e| e.into_with_index(i))?; @@ -545,59 +532,6 @@ pub fn process_exits( Ok(()) } -/// Process a builder voluntary exit. [New in Gloas:EIP7732] -fn process_builder_voluntary_exit( - state: &mut BeaconState, - signed_exit: &SignedVoluntaryExit, - verify_signatures: VerifySignatures, - spec: &ChainSpec, -) -> Result<(), BlockOperationError> { - let builder_index = - convert_validator_index_to_builder_index(signed_exit.message.validator_index); - - // Verify builder is known - state - .builders()? - .get(builder_index as usize) - .cloned() - .ok_or(BlockOperationError::invalid(ExitInvalid::ValidatorUnknown( - signed_exit.message.validator_index, - )))?; - - // Verify the builder is active - if !state.is_active_builder(builder_index, spec)? { - return Err(BlockOperationError::invalid(ExitInvalid::NotActive( - signed_exit.message.validator_index, - ))); - } - - // Only exit builder if it has no pending withdrawals in the queue - let pending_balance = state.get_pending_balance_to_withdraw_for_builder(builder_index)?; - if pending_balance != 0 { - return Err(BlockOperationError::invalid( - ExitInvalid::PendingWithdrawalInQueue(signed_exit.message.validator_index), - )); - } - - if verify_signatures.is_true() { - verify!( - exit_signature_set( - state, - |i| get_pubkey_from_state(state, i), - signed_exit, - spec - )? - .verify(), - ExitInvalid::BadSignature - ); - } - - // Initiate builder exit - initiate_builder_exit(state, builder_index, spec)?; - - Ok(()) -} - /// Initiate the exit of a builder. [New in Gloas:EIP7732] fn initiate_builder_exit( state: &mut BeaconState, @@ -881,7 +815,7 @@ pub fn process_withdrawal_requests( Ok(()) } -pub fn process_deposit_requests_pre_gloas( +pub fn process_deposit_requests( state: &mut BeaconState, deposit_requests: &[DepositRequest], spec: &ChainSpec, @@ -911,18 +845,109 @@ pub fn process_deposit_requests_pre_gloas( Ok(()) } -pub fn process_deposit_requests_post_gloas( +pub fn process_builder_deposit_requests( state: &mut BeaconState, - deposit_requests: &[DepositRequest], + builder_deposit_requests: &[BuilderDepositRequest], spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { - for request in deposit_requests { - process_deposit_request_post_gloas(state, request, spec)?; + for builder_deposit_request in builder_deposit_requests { + process_builder_deposit_request(state, builder_deposit_request, spec)?; } Ok(()) } +fn process_builder_deposit_request( + state: &mut BeaconState, + builder_deposit_request: &BuilderDepositRequest, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + let builder_index = state + .builders()? + .iter() + .position(|builder| builder.pubkey == builder_deposit_request.pubkey); + + match builder_index { + None => { + if builder_deposit_request.is_valid_builder_deposit_signature(spec) { + let version = builder_deposit_request + .version() + .ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?; + let slot = state.slot(); + state.add_builder_to_registry( + builder_deposit_request.pubkey, + version, + builder_deposit_request.withdrawal_credentials, + builder_deposit_request.amount, + slot, + spec, + )?; + } + } + Some(builder_index) => { + let current_epoch = state.current_epoch(); + let builder = state + .builders_mut()? + .get_mut(builder_index) + .ok_or(BeaconStateError::UnknownBuilder(builder_index as u64))?; + + builder + .balance + .safe_add_assign(builder_deposit_request.amount)?; + + if builder.withdrawable_epoch != spec.far_future_epoch { + builder.withdrawable_epoch = + current_epoch.safe_add(spec.min_builder_withdrawability_delay)?; + } + } + } + + Ok(()) +} + +pub fn process_builder_exit_requests( + state: &mut BeaconState, + builder_exit_requests: &[BuilderExitRequest], + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + for builder_exit_request in builder_exit_requests { + process_builder_exit_request(state, builder_exit_request, spec)?; + } + + Ok(()) +} + +fn process_builder_exit_request( + state: &mut BeaconState, + builder_exit_request: &BuilderExitRequest, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + let Some(builder_index) = state + .builders()? + .iter() + .position(|builder| builder.pubkey == builder_exit_request.pubkey) + .map(|i| i as u64) + else { + return Ok(()); + }; + + if !state.is_active_builder(builder_index, spec)? { + return Ok(()); + } + + if state.get_builder(builder_index)?.execution_address != builder_exit_request.source_address { + return Ok(()); + } + + if state.get_pending_balance_to_withdraw_for_builder(builder_index)? != 0 { + return Ok(()); + } + + initiate_builder_exit(state, builder_index, spec)?; + + Ok(()) +} + /// Check if there is a pending deposit for a new validator with the given pubkey. // TODO(gloas): cache the deposit signature validation or remove this loop entirely if possible, // it is `O(n * m)` where `n` is max 8192 and `m` is max 128M. @@ -946,69 +971,6 @@ pub fn is_pending_validator<'a>( }) } -pub fn process_deposit_request_post_gloas( - state: &mut BeaconState, - deposit_request: &DepositRequest, - spec: &ChainSpec, -) -> Result<(), BlockProcessingError> { - // [New in Gloas:EIP7732] - // Regardless of the withdrawal credentials prefix, if a builder/validator - // already exists with this pubkey, apply the deposit to their balance - // TODO(gloas): this could be more efficient in the builder case, see: - // https://github.com/sigp/lighthouse/issues/8783 - let builder_index = state - .builders()? - .iter() - .enumerate() - .find(|(_, builder)| builder.pubkey == deposit_request.pubkey) - .map(|(i, _)| i as u64); - let is_builder = builder_index.is_some(); - - let validator_index = state.get_validator_index(&deposit_request.pubkey)?; - let is_validator = validator_index.is_some(); - - let has_builder_prefix = - is_builder_withdrawal_credential(deposit_request.withdrawal_credentials, spec); - - if is_builder - || (has_builder_prefix - && !is_validator - && !is_pending_validator(state.pending_deposits()?, &deposit_request.pubkey, spec)) - { - // Apply builder deposits immediately. The builder version is taken from the - // withdrawal credentials prefix byte (spec: `process_builder_deposit_request`). - let version = *deposit_request - .withdrawal_credentials - .as_slice() - .first() - .ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?; - apply_deposit_for_builder( - state, - builder_index, - deposit_request.pubkey, - version, - deposit_request.withdrawal_credentials, - deposit_request.amount, - deposit_request.signature.clone(), - state.slot(), - spec, - )?; - return Ok(()); - } - - // Add validator deposits to the queue - let slot = state.slot(); - state.pending_deposits_mut()?.push(PendingDeposit { - pubkey: deposit_request.pubkey, - withdrawal_credentials: deposit_request.withdrawal_credentials, - amount: deposit_request.amount, - signature: deposit_request.signature.clone(), - slot, - })?; - - Ok(()) -} - #[allow(clippy::too_many_arguments)] pub fn apply_deposit_for_builder( state: &mut BeaconState, diff --git a/consensus/state_processing/src/upgrade/gloas.rs b/consensus/state_processing/src/upgrade/gloas.rs index e23eb94a64..a649360331 100644 --- a/consensus/state_processing/src/upgrade/gloas.rs +++ b/consensus/state_processing/src/upgrade/gloas.rs @@ -10,8 +10,8 @@ use tree_hash::TreeHash; use typenum::Unsigned; use types::{ BeaconState, BeaconStateError as Error, BeaconStateGloas, BuilderPendingPayment, ChainSpec, - EthSpec, ExecutionPayloadBid, ExecutionRequests, Fork, consts::gloas::PAYLOAD_BUILDER_VERSION, - is_builder_withdrawal_credential, + EthSpec, ExecutionPayloadBid, ExecutionRequestsGloas, Fork, + consts::gloas::PAYLOAD_BUILDER_VERSION, is_builder_withdrawal_credential, }; /// Transform a `Fulu` state into a `Gloas` state. @@ -80,7 +80,7 @@ pub fn upgrade_state_to_gloas( latest_execution_payload_bid: ExecutionPayloadBid { block_hash: pre.latest_execution_payload_header.block_hash, gas_limit: pre.latest_execution_payload_header.gas_limit, - execution_requests_root: ExecutionRequests::::default().tree_hash_root(), + execution_requests_root: ExecutionRequestsGloas::::default().tree_hash_root(), ..Default::default() }, // Capella diff --git a/consensus/types/presets/gnosis/gloas.yaml b/consensus/types/presets/gnosis/gloas.yaml index d1a48adca1..1f290d7010 100644 --- a/consensus/types/presets/gnosis/gloas.yaml +++ b/consensus/types/presets/gnosis/gloas.yaml @@ -21,3 +21,10 @@ BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 # --------------------------------------------------------------- # 2**14 (= 16,384) builders MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384 + +# Execution +# --------------------------------------------------------------- +# 2**8 (= 256) builder deposit requests +MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256 +# 2**4 (= 16) builder exit requests +MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 diff --git a/consensus/types/presets/mainnet/gloas.yaml b/consensus/types/presets/mainnet/gloas.yaml index 2da198dcae..4bd7888c6c 100644 --- a/consensus/types/presets/mainnet/gloas.yaml +++ b/consensus/types/presets/mainnet/gloas.yaml @@ -20,4 +20,11 @@ BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 # Withdrawals processing # --------------------------------------------------------------- # 2**14 (= 16,384) builders -MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384 \ No newline at end of file +MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384 + +# Execution +# --------------------------------------------------------------- +# 2**8 (= 256) builder deposit requests +MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256 +# 2**4 (= 16) builder exit requests +MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 \ No newline at end of file diff --git a/consensus/types/presets/minimal/gloas.yaml b/consensus/types/presets/minimal/gloas.yaml index 559c2d46df..096afd53f4 100644 --- a/consensus/types/presets/minimal/gloas.yaml +++ b/consensus/types/presets/minimal/gloas.yaml @@ -21,3 +21,10 @@ BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 # --------------------------------------------------------------- # [customized] 2**4 (= 16) builders MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16 + +# Execution +# --------------------------------------------------------------- +# 2**8 (= 256) builder deposit requests +MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256 +# 2**4 (= 16) builder exit requests +MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 diff --git a/consensus/types/src/block/beacon_block.rs b/consensus/types/src/block/beacon_block.rs index 639a89d7e6..a307c9bd95 100644 --- a/consensus/types/src/block/beacon_block.rs +++ b/consensus/types/src/block/beacon_block.rs @@ -25,8 +25,8 @@ use crate::{ core::{ChainSpec, Domain, Epoch, EthSpec, Graffiti, Hash256, SignedRoot, Slot}, deposit::{Deposit, DepositData}, execution::{ - AbstractExecPayload, BlindedPayload, Eth1Data, ExecutionPayload, ExecutionRequests, - FullPayload, + AbstractExecPayload, BlindedPayload, Eth1Data, ExecutionPayload, ExecutionRequestsElectra, + ExecutionRequestsGloas, FullPayload, }, exit::{SignedVoluntaryExit, VoluntaryExit}, fork::{Fork, ForkName, InconsistentFork, map_fork_name}, @@ -650,7 +650,7 @@ impl> EmptyBlock for BeaconBlockElec execution_payload: Payload::Electra::default(), bls_to_execution_changes: VariableList::empty(), blob_kzg_commitments: VariableList::empty(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsElectra::default(), }, } } @@ -684,7 +684,7 @@ impl> EmptyBlock for BeaconBlockFulu execution_payload: Payload::Fulu::default(), bls_to_execution_changes: VariableList::empty(), blob_kzg_commitments: VariableList::empty(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsElectra::default(), }, } } @@ -713,7 +713,7 @@ impl> EmptyBlock for BeaconBlockGloa voluntary_exits: VariableList::empty(), sync_aggregate: SyncAggregate::empty(), bls_to_execution_changes: VariableList::empty(), - parent_execution_requests: ExecutionRequests::default(), + parent_execution_requests: ExecutionRequestsGloas::default(), signed_execution_payload_bid: SignedExecutionPayloadBid::empty(), payload_attestations: VariableList::empty(), _phantom: PhantomData, diff --git a/consensus/types/src/block/beacon_block_body.rs b/consensus/types/src/block/beacon_block_body.rs index 071c9e76d4..f8e6ad6ae7 100644 --- a/consensus/types/src/block/beacon_block_body.rs +++ b/consensus/types/src/block/beacon_block_body.rs @@ -24,9 +24,10 @@ use crate::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, BlindedPayloadDeneb, BlindedPayloadElectra, BlindedPayloadFulu, Eth1Data, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, - ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionRequests, - FullPayload, FullPayloadBellatrix, FullPayloadCapella, FullPayloadDeneb, - FullPayloadElectra, FullPayloadFulu, SignedBlsToExecutionChange, + ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, + ExecutionRequestsElectra, ExecutionRequestsGloas, FullPayload, FullPayloadBellatrix, + FullPayloadCapella, FullPayloadDeneb, FullPayloadElectra, FullPayloadFulu, + SignedBlsToExecutionChange, }, exit::SignedVoluntaryExit, fork::{ForkName, map_fork_name}, @@ -163,13 +164,13 @@ pub struct BeaconBlockBody = FullPay #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, #[superstruct(only(Electra, Fulu))] - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsElectra, #[superstruct(only(Gloas))] pub signed_execution_payload_bid: SignedExecutionPayloadBid, #[superstruct(only(Gloas))] pub payload_attestations: VariableList, E::MaxPayloadAttestations>, #[superstruct(only(Gloas))] - pub parent_execution_requests: ExecutionRequests, + pub parent_execution_requests: ExecutionRequestsGloas, #[superstruct(only(Base, Altair, Gloas))] #[metastruct(exclude_from(fields))] #[ssz(skip_serializing, skip_deserializing)] diff --git a/consensus/types/src/builder/builder_bid.rs b/consensus/types/src/builder/builder_bid.rs index df7893b909..ddd877c46f 100644 --- a/consensus/types/src/builder/builder_bid.rs +++ b/consensus/types/src/builder/builder_bid.rs @@ -12,7 +12,7 @@ use crate::{ execution::{ ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ExecutionRequests, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ExecutionRequestsElectra, }, fork::{ForkName, ForkVersionDecode}, kzg_ext::KzgCommitments, @@ -59,7 +59,7 @@ pub struct BuilderBid { #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, #[superstruct(only(Electra, Fulu))] - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsElectra, #[serde(with = "serde_utils::quoted_u256")] pub value: Uint256, pub pubkey: PublicKeyBytes, diff --git a/consensus/types/src/builder/builder_deposit_request.rs b/consensus/types/src/builder/builder_deposit_request.rs new file mode 100644 index 0000000000..5b2ab35f02 --- /dev/null +++ b/consensus/types/src/builder/builder_deposit_request.rs @@ -0,0 +1,74 @@ +use bls::{PublicKeyBytes, SignatureBytes}; +use context_deserialize::context_deserialize; +use serde::{Deserialize, Serialize}; +use ssz::Encode; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +use crate::{ + Address, BeaconStateError, ChainSpec, DepositMessage, Domain, SignedRoot, core::Hash256, + fork::ForkName, +}; + +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Debug, PartialEq, Hash, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] +#[context_deserialize(ForkName)] +pub struct BuilderDepositRequest { + pub pubkey: PublicKeyBytes, + pub withdrawal_credentials: Hash256, + #[serde(with = "serde_utils::quoted_u64")] + pub amount: u64, + pub signature: SignatureBytes, +} + +impl BuilderDepositRequest { + fn as_deposit_message(&self) -> DepositMessage { + DepositMessage { + pubkey: self.pubkey, + withdrawal_credentials: self.withdrawal_credentials, + amount: self.amount, + } + } + + pub fn version(&self) -> Option { + self.withdrawal_credentials.as_slice().first().cloned() + } + + pub fn is_valid_builder_deposit_signature(&self, spec: &ChainSpec) -> bool { + let Ok(pubkey) = self.pubkey.decompress() else { + return false; + }; + let Ok(signature) = self.signature.decompress() else { + return false; + }; + + let domain = spec.compute_domain( + Domain::BuilderDeposit, + spec.genesis_fork_version, + Hash256::ZERO, + ); + let signing_root = self.as_deposit_message().signing_root(domain); + + signature.verify(&pubkey, signing_root) + } +} + +impl BuilderDepositRequest { + pub fn max_size() -> usize { + Self { + pubkey: PublicKeyBytes::empty(), + withdrawal_credentials: Hash256::ZERO, + amount: 0, + signature: SignatureBytes::empty(), + } + .as_ssz_bytes() + .len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(BuilderDepositRequest); +} diff --git a/consensus/types/src/builder/builder_exit_request.rs b/consensus/types/src/builder/builder_exit_request.rs new file mode 100644 index 0000000000..667ff8a618 --- /dev/null +++ b/consensus/types/src/builder/builder_exit_request.rs @@ -0,0 +1,35 @@ +use bls::PublicKeyBytes; +use context_deserialize::context_deserialize; +use serde::{Deserialize, Serialize}; +use ssz::Encode; +use ssz_derive::{Decode, Encode}; +use tree_hash_derive::TreeHash; + +use crate::{core::Address, fork::ForkName}; + +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] +#[context_deserialize(ForkName)] +pub struct BuilderExitRequest { + #[serde(with = "serde_utils::address_hex")] + pub source_address: Address, + pub pubkey: PublicKeyBytes, +} + +impl BuilderExitRequest { + pub fn max_size() -> usize { + Self { + source_address: Address::repeat_byte(0), + pubkey: PublicKeyBytes::empty(), + } + .as_ssz_bytes() + .len() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + ssz_and_tree_hash_tests!(BuilderExitRequest); +} diff --git a/consensus/types/src/builder/mod.rs b/consensus/types/src/builder/mod.rs index bc6140f9d9..e9325939bb 100644 --- a/consensus/types/src/builder/mod.rs +++ b/consensus/types/src/builder/mod.rs @@ -1,5 +1,7 @@ mod builder; mod builder_bid; +mod builder_deposit_request; +mod builder_exit_request; mod builder_pending_payment; mod builder_pending_withdrawal; mod proposer_preferences; @@ -9,6 +11,8 @@ pub use builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, BuilderBidFulu, SignedBuilderBid, }; +pub use builder_deposit_request::BuilderDepositRequest; +pub use builder_exit_request::BuilderExitRequest; pub use builder_pending_payment::BuilderPendingPayment; pub use builder_pending_withdrawal::BuilderPendingWithdrawal; pub use proposer_preferences::{ProposerPreferences, SignedProposerPreferences}; diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index 9ccaa86579..73163060e3 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -37,6 +37,7 @@ pub enum Domain { BeaconBuilder, PTCAttester, ProposerPreferences, + BuilderDeposit, ApplicationMask(ApplicationDomain), } @@ -147,6 +148,7 @@ pub struct ChainSpec { pub(crate) domain_beacon_builder: u32, pub(crate) domain_ptc_attester: u32, pub(crate) domain_proposer_preferences: u32, + pub(crate) domain_builder_deposit: u32, /* * Fork choice @@ -525,6 +527,7 @@ impl ChainSpec { Domain::BeaconBuilder => self.domain_beacon_builder, Domain::PTCAttester => self.domain_ptc_attester, Domain::ProposerPreferences => self.domain_proposer_preferences, + Domain::BuilderDeposit => self.domain_builder_deposit, Domain::SyncCommittee => self.domain_sync_committee, Domain::ContributionAndProof => self.domain_contribution_and_proof, Domain::SyncCommitteeSelectionProof => self.domain_sync_committee_selection_proof, @@ -1158,6 +1161,7 @@ impl ChainSpec { domain_beacon_builder: 0x0B, domain_ptc_attester: 0x0C, domain_proposer_preferences: 0x0D, + domain_builder_deposit: 0x0E, /* * Fork choice @@ -1583,6 +1587,7 @@ impl ChainSpec { domain_beacon_builder: 0x0B, domain_ptc_attester: 0x0C, domain_proposer_preferences: 0x0D, + domain_builder_deposit: 0x0E, /* * Fork choice @@ -2982,6 +2987,12 @@ mod tests { 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::ProposerPreferences, + spec.domain_proposer_preferences, + &spec, + ); + test_domain(Domain::BuilderDeposit, spec.domain_builder_deposit, &spec); // The builder domain index is zero let builder_domain_pre_mask = [0; 4]; diff --git a/consensus/types/src/core/eth_spec.rs b/consensus/types/src/core/eth_spec.rs index 5f296afb44..9d79470647 100644 --- a/consensus/types/src/core/eth_spec.rs +++ b/consensus/types/src/core/eth_spec.rs @@ -181,6 +181,8 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type BuilderPendingPaymentsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxBuildersPerWithdrawalsSweep: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxBuilderDepositRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; + type MaxBuilderExitRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; fn default_spec() -> ChainSpec; @@ -444,6 +446,16 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::MaxBuildersPerWithdrawalsSweep::to_usize() } + /// Returns the `MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD` constant for this specification. + fn max_builder_deposit_requests_per_payload() -> usize { + Self::MaxBuilderDepositRequestsPerPayload::to_usize() + } + + /// Returns the `MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD` constant for this specification. + fn max_builder_exit_requests_per_payload() -> usize { + Self::MaxBuilderExitRequestsPerPayload::to_usize() + } + /// Returns the `PAYLOAD_TIMELY_THRESHOLD` constant (PTC_SIZE / 2). fn payload_timely_threshold() -> usize { Self::PTCSize::to_usize() / 2 @@ -529,6 +541,8 @@ impl EthSpec for MainnetEthSpec { type PtcWindowLength = U96; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH type MaxPayloadAttestations = U4; type MaxBuildersPerWithdrawalsSweep = U16384; + type MaxBuilderDepositRequestsPerPayload = U256; + type MaxBuilderExitRequestsPerPayload = U16; fn default_spec() -> ChainSpec { ChainSpec::mainnet() @@ -606,6 +620,8 @@ impl EthSpec for MinimalEthSpec { MaxDepositRequestsPerPayload, MaxWithdrawalRequestsPerPayload, MaxPayloadAttestations, + MaxBuilderDepositRequestsPerPayload, + MaxBuilderExitRequestsPerPayload, BuilderRegistryLimit }); @@ -684,6 +700,8 @@ impl EthSpec for GnosisEthSpec { type PtcWindowLength = U48; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH type MaxPayloadAttestations = U2; type MaxBuildersPerWithdrawalsSweep = U16384; + type MaxBuilderDepositRequestsPerPayload = U256; + type MaxBuilderExitRequestsPerPayload = U16; fn default_spec() -> ChainSpec { ChainSpec::gnosis() diff --git a/consensus/types/src/core/preset.rs b/consensus/types/src/core/preset.rs index 978fc6f4a1..d9043a5d77 100644 --- a/consensus/types/src/core/preset.rs +++ b/consensus/types/src/core/preset.rs @@ -342,6 +342,10 @@ pub struct GloasPreset { pub builder_pending_withdrawals_limit: u64, #[serde(with = "serde_utils::quoted_u64")] pub max_builders_per_withdrawals_sweep: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub max_builder_deposit_requests_per_payload: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub max_builder_exit_requests_per_payload: u64, } impl GloasPreset { @@ -352,6 +356,10 @@ impl GloasPreset { builder_registry_limit: E::BuilderRegistryLimit::to_u64(), builder_pending_withdrawals_limit: E::builder_pending_withdrawals_limit() as u64, max_builders_per_withdrawals_sweep: E::max_builders_per_withdrawals_sweep() as u64, + max_builder_deposit_requests_per_payload: E::max_builder_deposit_requests_per_payload() + as u64, + max_builder_exit_requests_per_payload: E::max_builder_exit_requests_per_payload() + as u64, } } } diff --git a/consensus/types/src/execution/execution_payload_envelope.rs b/consensus/types/src/execution/execution_payload_envelope.rs index 87a0ea7a63..c24665f41a 100644 --- a/consensus/types/src/execution/execution_payload_envelope.rs +++ b/consensus/types/src/execution/execution_payload_envelope.rs @@ -1,4 +1,4 @@ -use crate::execution::{ExecutionPayloadGloas, ExecutionRequests}; +use crate::execution::{ExecutionPayloadGloas, ExecutionRequestsGloas}; use crate::{EthSpec, ForkName, Hash256, SignedRoot, Slot}; use context_deserialize::context_deserialize; use educe::Educe; @@ -19,7 +19,7 @@ use tree_hash_derive::TreeHash; #[serde(bound = "E: EthSpec")] pub struct ExecutionPayloadEnvelope { pub payload: ExecutionPayloadGloas, - pub execution_requests: ExecutionRequests, + pub execution_requests: ExecutionRequestsGloas, #[serde(with = "serde_utils::quoted_u64")] pub builder_index: u64, pub beacon_block_root: Hash256, @@ -31,7 +31,7 @@ impl ExecutionPayloadEnvelope { pub fn empty() -> Self { Self { payload: ExecutionPayloadGloas::default(), - execution_requests: ExecutionRequests::default(), + execution_requests: ExecutionRequestsGloas::default(), builder_index: 0, beacon_block_root: Hash256::zero(), parent_beacon_block_root: Hash256::zero(), diff --git a/consensus/types/src/execution/execution_requests.rs b/consensus/types/src/execution/execution_requests.rs index 218b7edc17..dc14b71cd5 100644 --- a/consensus/types/src/execution/execution_requests.rs +++ b/consensus/types/src/execution/execution_requests.rs @@ -6,15 +6,19 @@ use serde::{Deserialize, Serialize}; use ssz::Encode; use ssz_derive::{Decode, Encode}; use ssz_types::VariableList; +use superstruct::superstruct; use tree_hash_derive::TreeHash; use crate::{ + builder::{BuilderDepositRequest, BuilderExitRequest}, consolidation::ConsolidationRequest, core::{EthSpec, Hash256}, deposit::DepositRequest, - fork::ForkName, + fork::{ForkName, ForkVersionDecode}, + state::BeaconStateError, withdrawal::WithdrawalRequest, }; +use ssz::Decode; pub type DepositRequests = VariableList::MaxDepositRequestsPerPayload>; @@ -22,46 +26,106 @@ pub type WithdrawalRequests = VariableList::MaxWithdrawalRequestsPerPayload>; pub type ConsolidationRequests = VariableList::MaxConsolidationRequestsPerPayload>; +pub type BuilderDepositRequests = + VariableList::MaxBuilderDepositRequestsPerPayload>; +pub type BuilderExitRequests = + VariableList::MaxBuilderExitRequestsPerPayload>; +#[superstruct( + variants(Electra, Gloas), + variant_attributes( + derive( + Default, + Debug, + Clone, + Serialize, + Deserialize, + Encode, + Decode, + TreeHash, + Educe, + ), + context_deserialize(ForkName), + educe(PartialEq, Eq, Hash(bound(E: EthSpec))), + serde(bound = "E: EthSpec"), + cfg_attr( + feature = "arbitrary", + derive(arbitrary::Arbitrary), + arbitrary(bound = "E: EthSpec"), + ), + ), + cast_error( + ty = "BeaconStateError", + expr = "BeaconStateError::IncorrectStateVariant" + ), + partial_getter_error( + ty = "BeaconStateError", + expr = "BeaconStateError::IncorrectStateVariant" + ) +)] #[cfg_attr( feature = "arbitrary", derive(arbitrary::Arbitrary), arbitrary(bound = "E: EthSpec") )] -#[derive(Debug, Educe, Default, Clone, Serialize, Deserialize, Encode, Decode, TreeHash)] -#[serde(bound = "E: EthSpec")] +#[derive(Debug, Educe, Clone, Serialize, Deserialize, Encode, TreeHash)] +#[serde(bound = "E: EthSpec", untagged)] #[educe(PartialEq, Eq, Hash(bound(E: EthSpec)))] -#[context_deserialize(ForkName)] +#[tree_hash(enum_behaviour = "transparent")] +#[ssz(enum_behaviour = "transparent")] pub struct ExecutionRequests { pub deposits: DepositRequests, pub withdrawals: WithdrawalRequests, pub consolidations: ConsolidationRequests, + #[superstruct(only(Gloas))] + pub builder_deposits: BuilderDepositRequests, + #[superstruct(only(Gloas))] + pub builder_exits: BuilderExitRequests, } -impl ExecutionRequests { +impl<'a, E: EthSpec> ExecutionRequestsRef<'a, E> { /// Returns the encoding according to EIP-7685 to send /// to the execution layer over the engine api. pub fn get_execution_requests_list(&self) -> Vec { let mut requests_list = Vec::new(); - if !self.deposits.is_empty() { + if !self.deposits().is_empty() { requests_list.push(Bytes::from_iter( [RequestType::Deposit.to_u8()] .into_iter() - .chain(self.deposits.as_ssz_bytes()), + .chain(self.deposits().as_ssz_bytes()), )); } - if !self.withdrawals.is_empty() { + if !self.withdrawals().is_empty() { requests_list.push(Bytes::from_iter( [RequestType::Withdrawal.to_u8()] .into_iter() - .chain(self.withdrawals.as_ssz_bytes()), + .chain(self.withdrawals().as_ssz_bytes()), )); } - if !self.consolidations.is_empty() { + if !self.consolidations().is_empty() { requests_list.push(Bytes::from_iter( [RequestType::Consolidation.to_u8()] .into_iter() - .chain(self.consolidations.as_ssz_bytes()), + .chain(self.consolidations().as_ssz_bytes()), + )); + } + // [New in Gloas:EIP8282] The builder request lists are only present on the Gloas variant. + if let Ok(builder_deposits) = self.builder_deposits() + && !builder_deposits.is_empty() + { + requests_list.push(Bytes::from_iter( + [RequestType::BuilderDeposit.to_u8()] + .into_iter() + .chain(builder_deposits.as_ssz_bytes()), + )); + } + if let Ok(builder_exits) = self.builder_exits() + && !builder_exits.is_empty() + { + requests_list.push(Bytes::from_iter( + [RequestType::BuilderExit.to_u8()] + .into_iter() + .chain(builder_exits.as_ssz_bytes()), )); } requests_list @@ -85,12 +149,46 @@ impl ExecutionRequests { } } +impl ExecutionRequests { + /// Returns the encoding according to EIP-7685 to send + /// to the execution layer over the engine api. + pub fn get_execution_requests_list(&self) -> Vec { + self.to_ref().get_execution_requests_list() + } + + /// Generate the execution layer `requests_hash` based on EIP-7685. + pub fn requests_hash(&self) -> Hash256 { + self.to_ref().requests_hash() + } +} + +impl ForkVersionDecode for ExecutionRequests { + /// SSZ decode with explicit fork variant. + fn from_ssz_bytes_by_fork(bytes: &[u8], fork_name: ForkName) -> Result { + match fork_name { + ForkName::Base + | ForkName::Altair + | ForkName::Bellatrix + | ForkName::Capella + | ForkName::Deneb => Err(ssz::DecodeError::BytesInvalid(format!( + "unsupported fork for ExecutionRequests: {fork_name}", + ))), + ForkName::Electra | ForkName::Fulu => { + ExecutionRequestsElectra::from_ssz_bytes(bytes).map(Self::Electra) + } + ForkName::Gloas => ExecutionRequestsGloas::from_ssz_bytes(bytes).map(Self::Gloas), + } + } +} + /// The prefix types for `ExecutionRequest` objects. #[derive(Debug, Copy, Clone)] pub enum RequestType { Deposit, Withdrawal, Consolidation, + BuilderDeposit, + BuilderExit, } impl RequestType { @@ -99,6 +197,8 @@ impl RequestType { 0 => Some(Self::Deposit), 1 => Some(Self::Withdrawal), 2 => Some(Self::Consolidation), + 3 => Some(Self::BuilderDeposit), + 4 => Some(Self::BuilderExit), _ => None, } } @@ -107,15 +207,24 @@ impl RequestType { Self::Deposit => 0, Self::Withdrawal => 1, Self::Consolidation => 2, + Self::BuilderDeposit => 3, + Self::BuilderExit => 4, } } } #[cfg(test)] -mod tests { +mod electra_tests { + use super::*; use crate::MainnetEthSpec; - use super::*; - - ssz_and_tree_hash_tests!(ExecutionRequests); + ssz_and_tree_hash_tests!(ExecutionRequestsElectra); +} + +#[cfg(test)] +mod gloas_tests { + use super::*; + use crate::MainnetEthSpec; + + ssz_and_tree_hash_tests!(ExecutionRequestsGloas); } diff --git a/consensus/types/src/execution/mod.rs b/consensus/types/src/execution/mod.rs index a3d4ed8730..befc27ceab 100644 --- a/consensus/types/src/execution/mod.rs +++ b/consensus/types/src/execution/mod.rs @@ -29,7 +29,9 @@ pub use execution_payload_header::{ ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, }; pub use execution_requests::{ - ConsolidationRequests, DepositRequests, ExecutionRequests, RequestType, WithdrawalRequests, + BuilderDepositRequests, BuilderExitRequests, ConsolidationRequests, DepositRequests, + ExecutionRequests, ExecutionRequestsElectra, ExecutionRequestsGloas, ExecutionRequestsRef, + RequestType, WithdrawalRequests, }; pub use payload::{ AbstractExecPayload, BlindedPayload, BlindedPayloadBellatrix, BlindedPayloadCapella, diff --git a/testing/ef_tests/Makefile b/testing/ef_tests/Makefile index a497fdaeff..1af7ca6433 100644 --- a/testing/ef_tests/Makefile +++ b/testing/ef_tests/Makefile @@ -1,6 +1,6 @@ # To download/extract nightly tests, run: # CONSENSUS_SPECS_TEST_VERSION=nightly make -CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.10 +CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.11 REPO_NAME := consensus-spec-tests OUTPUT_DIR := ./$(REPO_NAME) diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index d851427a95..0894b4bfdf 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -8,8 +8,8 @@ use state_processing::common::update_progressive_balances_cache::initialize_prog use state_processing::envelope_processing::verify_execution_payload_envelope; use state_processing::epoch_cache::initialize_epoch_cache; use state_processing::per_block_processing::process_operations::{ - process_consolidation_requests, process_deposit_requests_post_gloas, - process_deposit_requests_pre_gloas, process_withdrawal_requests, + process_builder_deposit_requests, process_builder_exit_requests, + process_consolidation_requests, process_deposit_requests, process_withdrawal_requests, }; use state_processing::{ ConsensusContext, @@ -30,10 +30,10 @@ use std::fmt::Debug; use types::{ Attestation, AttesterSlashing, BeaconBlock, BeaconBlockBody, BeaconBlockBodyBellatrix, BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu, - BeaconState, BlindedPayload, ConsolidationRequest, Deposit, DepositRequest, ExecutionPayload, - ForkVersionDecode, FullPayload, PayloadAttestation, ProposerSlashing, - SignedBlsToExecutionChange, SignedExecutionPayloadEnvelope, SignedVoluntaryExit, SyncAggregate, - WithdrawalRequest, + BeaconState, BlindedPayload, BuilderDepositRequest, BuilderExitRequest, ConsolidationRequest, + Deposit, DepositRequest, ExecutionPayload, ForkVersionDecode, FullPayload, PayloadAttestation, + ProposerSlashing, SignedBlsToExecutionChange, SignedExecutionPayloadEnvelope, + SignedVoluntaryExit, SyncAggregate, WithdrawalRequest, }; #[derive(Debug, Clone, Default, Deserialize)] @@ -720,11 +720,7 @@ impl Operation for DepositRequest { spec: &ChainSpec, _extra: &Operations, ) -> Result<(), BlockProcessingError> { - if state.fork_name_unchecked().gloas_enabled() { - process_deposit_requests_post_gloas(state, std::slice::from_ref(self), spec) - } else { - process_deposit_requests_pre_gloas(state, std::slice::from_ref(self), spec) - } + process_deposit_requests(state, std::slice::from_ref(self), spec) } } @@ -754,6 +750,56 @@ impl Operation for ConsolidationRequest { } } +impl Operation for BuilderDepositRequest { + type Error = BlockProcessingError; + + fn handler_name() -> String { + "builder_deposit_request".into() + } + + fn is_enabled_for_fork(fork_name: ForkName) -> bool { + fork_name.gloas_enabled() + } + + fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result { + ssz_decode_file(path) + } + + fn apply_to( + &self, + state: &mut BeaconState, + spec: &ChainSpec, + _extra: &Operations, + ) -> Result<(), BlockProcessingError> { + process_builder_deposit_requests(state, std::slice::from_ref(self), spec) + } +} + +impl Operation for BuilderExitRequest { + type Error = BlockProcessingError; + + fn handler_name() -> String { + "builder_exit_request".into() + } + + fn is_enabled_for_fork(fork_name: ForkName) -> bool { + fork_name.gloas_enabled() + } + + fn decode(path: &Path, _fork_name: ForkName, _spec: &ChainSpec) -> Result { + ssz_decode_file(path) + } + + fn apply_to( + &self, + state: &mut BeaconState, + spec: &ChainSpec, + _extra: &Operations, + ) -> Result<(), BlockProcessingError> { + process_builder_exit_requests(state, std::slice::from_ref(self), spec) + } +} + impl Operation for PayloadAttestation { type Error = BlockProcessingError; diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 9af88e0201..0aa4f08c5a 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -136,6 +136,19 @@ fn operations_consolidations() { OperationsHandler::::default().run(); } +#[test] +#[cfg(not(feature = "fake_crypto"))] +fn operations_builder_deposit_requests() { + OperationsHandler::::default().run(); + OperationsHandler::::default().run(); +} + +#[test] +fn operations_builder_exit_requests() { + OperationsHandler::::default().run(); + OperationsHandler::::default().run(); +} + #[test] fn operations_bls_to_execution_change() { OperationsHandler::::default().run();