From 7e275f8dc2b728ee44bd4c81352c8bf64d4c4ee7 Mon Sep 17 00:00:00 2001 From: Michael Sproul Date: Tue, 10 Feb 2026 14:59:25 +1100 Subject: [PATCH] Gloas envelope consensus and more operations tests (#8781) - Implement new `process_execution_payload` (as `process_execution_payload_envelope`). - Implement new processing for deposit requests, including logic for adding new builders to the registry with index reuse. - Enable a bunch more operations EF tests (most of them except bid processing/payload attestations/etc which we don't have code for yet). Co-Authored-By: Michael Sproul --- .../src/envelope_processing.rs | 278 ++++++++++++++++++ consensus/state_processing/src/lib.rs | 1 + .../process_operations.rs | 143 ++++++++- .../verify_attestation.rs | 4 - .../signed_execution_payload_envelope.rs | 41 ++- consensus/types/src/state/beacon_state.rs | 75 ++++- consensus/types/src/validator/mod.rs | 4 +- consensus/types/src/validator/validator.rs | 8 + testing/ef_tests/check_all_files_accessed.py | 10 - testing/ef_tests/src/cases/operations.rs | 92 +++++- testing/ef_tests/src/handler.rs | 13 +- testing/ef_tests/tests/tests.rs | 8 +- 12 files changed, 643 insertions(+), 34 deletions(-) create mode 100644 consensus/state_processing/src/envelope_processing.rs diff --git a/consensus/state_processing/src/envelope_processing.rs b/consensus/state_processing/src/envelope_processing.rs new file mode 100644 index 0000000000..d46728dbbc --- /dev/null +++ b/consensus/state_processing/src/envelope_processing.rs @@ -0,0 +1,278 @@ +use crate::BlockProcessingError; +use crate::VerifySignatures; +use crate::per_block_processing::compute_timestamp_at_slot; +use crate::per_block_processing::process_operations::{ + process_consolidation_requests, process_deposit_requests_post_gloas, + process_withdrawal_requests, +}; +use safe_arith::{ArithError, SafeArith}; +use tree_hash::TreeHash; +use types::{ + BeaconState, BeaconStateError, BuilderIndex, BuilderPendingPayment, ChainSpec, EthSpec, + ExecutionBlockHash, Hash256, SignedExecutionPayloadEnvelope, Slot, +}; + +macro_rules! envelope_verify { + ($condition: expr, $result: expr) => { + if !$condition { + return Err($result); + } + }; +} + +#[derive(Debug, Clone)] +pub enum EnvelopeProcessingError { + /// Bad Signature + BadSignature, + BeaconStateError(BeaconStateError), + BlockProcessingError(BlockProcessingError), + ArithError(ArithError), + /// Envelope doesn't match latest beacon block header + LatestBlockHeaderMismatch { + envelope_root: Hash256, + block_header_root: Hash256, + }, + /// Envelope doesn't match latest beacon block slot + SlotMismatch { + envelope_slot: Slot, + parent_state_slot: Slot, + }, + /// The payload withdrawals don't match the state's payload withdrawals. + WithdrawalsRootMismatch { + state: Hash256, + payload: Hash256, + }, + // The builder index doesn't match the committed bid. + BuilderIndexMismatch { + committed_bid: BuilderIndex, + envelope: BuilderIndex, + }, + // The gas limit doesn't match the committed bid + GasLimitMismatch { + committed_bid: u64, + envelope: u64, + }, + // The block hash doesn't match the committed bid + BlockHashMismatch { + committed_bid: ExecutionBlockHash, + envelope: ExecutionBlockHash, + }, + // The parent hash doesn't match the previous execution payload + ParentHashMismatch { + state: ExecutionBlockHash, + envelope: ExecutionBlockHash, + }, + // The previous randao didn't match the payload + PrevRandaoMismatch { + committed_bid: Hash256, + envelope: Hash256, + }, + // The timestamp didn't match the payload + TimestampMismatch { + state: u64, + envelope: u64, + }, + // Invalid state root + InvalidStateRoot { + state: Hash256, + envelope: Hash256, + }, + // BitFieldError + BitFieldError(ssz::BitfieldError), + // Some kind of error calculating the builder payment index + BuilderPaymentIndexOutOfBounds(usize), + /// The envelope was deemed invalid by the execution engine. + ExecutionInvalid, +} + +impl From for EnvelopeProcessingError { + fn from(e: BeaconStateError) -> Self { + EnvelopeProcessingError::BeaconStateError(e) + } +} + +impl From for EnvelopeProcessingError { + fn from(e: BlockProcessingError) -> Self { + EnvelopeProcessingError::BlockProcessingError(e) + } +} + +impl From for EnvelopeProcessingError { + fn from(e: ArithError) -> Self { + EnvelopeProcessingError::ArithError(e) + } +} + +/// Processes a `SignedExecutionPayloadEnvelope` +/// +/// This function does all the state modifications inside `process_execution_payload()` +pub fn process_execution_payload_envelope( + state: &mut BeaconState, + parent_state_root: Option, + signed_envelope: &SignedExecutionPayloadEnvelope, + verify_signatures: VerifySignatures, + spec: &ChainSpec, +) -> Result<(), EnvelopeProcessingError> { + if verify_signatures.is_true() { + // Verify Signed Envelope Signature + if !signed_envelope.verify_signature_with_state(state, spec)? { + return Err(EnvelopeProcessingError::BadSignature); + } + } + + let envelope = &signed_envelope.message; + let payload = &envelope.payload; + let execution_requests = &envelope.execution_requests; + + // Cache latest block header state root + if state.latest_block_header().state_root == Hash256::default() { + let previous_state_root = parent_state_root + .map(Ok) + .unwrap_or_else(|| state.canonical_root())?; + state.latest_block_header_mut().state_root = previous_state_root; + } + + // Verify consistency with the beacon block + let latest_block_header_root = state.latest_block_header().tree_hash_root(); + envelope_verify!( + envelope.beacon_block_root == latest_block_header_root, + EnvelopeProcessingError::LatestBlockHeaderMismatch { + envelope_root: envelope.beacon_block_root, + block_header_root: latest_block_header_root, + } + ); + envelope_verify!( + envelope.slot == state.slot(), + EnvelopeProcessingError::SlotMismatch { + envelope_slot: envelope.slot, + parent_state_slot: state.slot(), + } + ); + + // Verify consistency with the committed bid + let committed_bid = state.latest_execution_payload_bid()?; + envelope_verify!( + envelope.builder_index == committed_bid.builder_index, + EnvelopeProcessingError::BuilderIndexMismatch { + committed_bid: committed_bid.builder_index, + envelope: envelope.builder_index, + } + ); + envelope_verify!( + committed_bid.prev_randao == payload.prev_randao, + EnvelopeProcessingError::PrevRandaoMismatch { + committed_bid: committed_bid.prev_randao, + envelope: payload.prev_randao, + } + ); + + // Verify consistency with expected withdrawals + // NOTE: we don't bother hashing here except in case of error, because we can just compare for + // equality directly. This equality check could be more straight-forward if the types were + // changed to match (currently we are comparing VariableList to List). This could happen + // coincidentally when we adopt ProgressiveList. + envelope_verify!( + payload.withdrawals.len() == state.payload_expected_withdrawals()?.len() + && payload + .withdrawals + .iter() + .eq(state.payload_expected_withdrawals()?.iter()), + EnvelopeProcessingError::WithdrawalsRootMismatch { + state: state.payload_expected_withdrawals()?.tree_hash_root(), + payload: payload.withdrawals.tree_hash_root(), + } + ); + + // Verify the gas limit + envelope_verify!( + committed_bid.gas_limit == payload.gas_limit, + EnvelopeProcessingError::GasLimitMismatch { + committed_bid: committed_bid.gas_limit, + envelope: payload.gas_limit, + } + ); + + // Verify the block hash + envelope_verify!( + committed_bid.block_hash == payload.block_hash, + EnvelopeProcessingError::BlockHashMismatch { + committed_bid: committed_bid.block_hash, + envelope: payload.block_hash, + } + ); + + // Verify consistency of the parent hash with respect to the previous execution payload + envelope_verify!( + payload.parent_hash == *state.latest_block_hash()?, + EnvelopeProcessingError::ParentHashMismatch { + state: *state.latest_block_hash()?, + envelope: payload.parent_hash, + } + ); + + // Verify timestamp + let state_timestamp = compute_timestamp_at_slot(state, state.slot(), spec)?; + envelope_verify!( + payload.timestamp == state_timestamp, + EnvelopeProcessingError::TimestampMismatch { + state: state_timestamp, + envelope: payload.timestamp, + } + ); + + // TODO(gloas): newPayload happens here in the spec, ensure we wire that up correctly + + process_deposit_requests_post_gloas(state, &execution_requests.deposits, spec)?; + + // TODO(gloas): gotta update these + process_withdrawal_requests(state, &execution_requests.withdrawals, spec)?; + process_consolidation_requests(state, &execution_requests.consolidations, spec)?; + + // Queue the builder payment + let payment_index = E::slots_per_epoch() + .safe_add(state.slot().as_u64().safe_rem(E::slots_per_epoch())?)? + as usize; + let payment_mut = state + .builder_pending_payments_mut()? + .get_mut(payment_index) + .ok_or(EnvelopeProcessingError::BuilderPaymentIndexOutOfBounds( + payment_index, + ))?; + + // We have re-ordered the blanking out of the pending payment to avoid a double-lookup. + // This is semantically equivalent to the ordering used by the spec because we have taken a + // clone of the payment prior to doing the write. + let payment_withdrawal = payment_mut.withdrawal.clone(); + *payment_mut = BuilderPendingPayment::default(); + + let amount = payment_withdrawal.amount; + if amount > 0 { + state + .builder_pending_withdrawals_mut()? + .push(payment_withdrawal) + .map_err(|e| EnvelopeProcessingError::BeaconStateError(e.into()))?; + } + + // Cache the execution payload hash + let availability_index = state + .slot() + .as_usize() + .safe_rem(E::slots_per_historical_root())?; + state + .execution_payload_availability_mut()? + .set(availability_index, true) + .map_err(EnvelopeProcessingError::BitFieldError)?; + *state.latest_block_hash_mut()? = payload.block_hash; + + // Verify the state root + let state_root = state.canonical_root()?; + envelope_verify!( + envelope.state_root == state_root, + EnvelopeProcessingError::InvalidStateRoot { + state: state_root, + envelope: envelope.state_root, + } + ); + + Ok(()) +} diff --git a/consensus/state_processing/src/lib.rs b/consensus/state_processing/src/lib.rs index 9b2696c6d5..e37c526579 100644 --- a/consensus/state_processing/src/lib.rs +++ b/consensus/state_processing/src/lib.rs @@ -20,6 +20,7 @@ pub mod all_caches; pub mod block_replayer; pub mod common; pub mod consensus_context; +pub mod envelope_processing; pub mod epoch_cache; pub mod genesis; pub mod per_block_processing; 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 9db439b543..19109f1508 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -5,6 +5,7 @@ use crate::common::{ slash_validator, }; use crate::per_block_processing::errors::{BlockProcessingError, IntoWithIndex}; +use bls::{PublicKeyBytes, SignatureBytes}; use ssz_types::FixedVector; use typenum::U33; use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR}; @@ -38,9 +39,14 @@ pub fn process_operations>( process_bls_to_execution_changes(state, bls_to_execution_changes, verify_signatures, spec)?; } - if state.fork_name_unchecked().electra_enabled() { + if state.fork_name_unchecked().electra_enabled() && !state.fork_name_unchecked().gloas_enabled() + { state.update_pubkey_cache()?; - process_deposit_requests(state, &block_body.execution_requests()?.deposits, spec)?; + process_deposit_requests_pre_gloas( + state, + &block_body.execution_requests()?.deposits, + spec, + )?; process_withdrawal_requests(state, &block_body.execution_requests()?.withdrawals, spec)?; process_consolidation_requests( state, @@ -377,6 +383,31 @@ pub fn process_proposer_slashings( verify_proposer_slashing(proposer_slashing, state, verify_signatures, spec) .map_err(|e| e.into_with_index(i))?; + // [New in Gloas:EIP7732] + // Remove the BuilderPendingPayment corresponding to this proposal + // if it is still in the 2-epoch window. + if state.fork_name_unchecked().gloas_enabled() { + let slot = proposer_slashing.signed_header_1.message.slot; + let proposal_epoch = slot.epoch(E::slots_per_epoch()); + let slot_in_epoch = slot.as_usize().safe_rem(E::SlotsPerEpoch::to_usize())?; + + let payment_index = if proposal_epoch == state.current_epoch() { + Some(E::SlotsPerEpoch::to_usize().safe_add(slot_in_epoch)?) + } else if proposal_epoch == state.previous_epoch() { + Some(slot_in_epoch) + } else { + None + }; + + if let Some(index) = payment_index { + let payment = state + .builder_pending_payments_mut()? + .get_mut(index) + .ok_or(BlockProcessingError::BuilderPaymentIndexOutOfBounds(index))?; + *payment = BuilderPendingPayment::default(); + } + } + slash_validator( state, proposer_slashing.signed_header_1.message.proposer_index as usize, @@ -736,7 +767,7 @@ pub fn process_withdrawal_requests( Ok(()) } -pub fn process_deposit_requests( +pub fn process_deposit_requests_pre_gloas( state: &mut BeaconState, deposit_requests: &[DepositRequest], spec: &ChainSpec, @@ -763,6 +794,112 @@ pub fn process_deposit_requests( Ok(()) } +pub fn process_deposit_requests_post_gloas( + state: &mut BeaconState, + deposit_requests: &[DepositRequest], + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + for request in deposit_requests { + process_deposit_request_post_gloas(state, request, spec)?; + } + + Ok(()) +} + +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 is_builder_prefix = + is_builder_withdrawal_credential(deposit_request.withdrawal_credentials, spec); + + if is_builder || (is_builder_prefix && !is_validator) { + // Apply builder deposits immediately + apply_deposit_for_builder( + state, + builder_index, + deposit_request.pubkey, + 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, + builder_index_opt: Option, + pubkey: PublicKeyBytes, + withdrawal_credentials: Hash256, + amount: u64, + signature: SignatureBytes, + slot: Slot, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + match builder_index_opt { + None => { + // Verify the deposit signature (proof of possession) which is not checked by the deposit contract + let deposit_data = DepositData { + pubkey, + withdrawal_credentials, + amount, + signature, + }; + if is_valid_deposit_signature(&deposit_data, spec).is_ok() { + state.add_builder_to_registry( + pubkey, + withdrawal_credentials, + amount, + slot, + spec, + )?; + } + } + Some(builder_index) => { + state + .builders_mut()? + .get_mut(builder_index as usize) + .ok_or(BeaconStateError::UnknownBuilder(builder_index))? + .balance + .safe_add_assign(amount)?; + } + } + Ok(()) +} + // Make sure to build the pubkey cache before calling this function pub fn process_consolidation_requests( state: &mut BeaconState, diff --git a/consensus/state_processing/src/per_block_processing/verify_attestation.rs b/consensus/state_processing/src/per_block_processing/verify_attestation.rs index 00105f323c..64b7a31afb 100644 --- a/consensus/state_processing/src/per_block_processing/verify_attestation.rs +++ b/consensus/state_processing/src/per_block_processing/verify_attestation.rs @@ -52,8 +52,6 @@ pub fn verify_attestation_for_block_inclusion<'ctxt, E: EthSpec>( /// /// Returns a descriptive `Err` if the attestation is malformed or does not accurately reflect the /// prior blocks in `state`. -/// -/// Spec v0.12.1 pub fn verify_attestation_for_state<'ctxt, E: EthSpec>( state: &BeaconState, attestation: AttestationRef<'ctxt, E>, @@ -94,8 +92,6 @@ pub fn verify_attestation_for_state<'ctxt, E: EthSpec>( } /// Check target epoch and source checkpoint. -/// -/// Spec v0.12.1 fn verify_casper_ffg_vote( attestation: AttestationRef, state: &BeaconState, diff --git a/consensus/types/src/execution/signed_execution_payload_envelope.rs b/consensus/types/src/execution/signed_execution_payload_envelope.rs index ca48a9ec9b..b1d949f863 100644 --- a/consensus/types/src/execution/signed_execution_payload_envelope.rs +++ b/consensus/types/src/execution/signed_execution_payload_envelope.rs @@ -1,7 +1,8 @@ use crate::test_utils::TestRandom; use crate::{ - ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash, ExecutionPayloadEnvelope, Fork, - ForkName, Hash256, SignedRoot, Slot, + BeaconState, BeaconStateError, ChainSpec, Domain, Epoch, EthSpec, ExecutionBlockHash, + ExecutionPayloadEnvelope, Fork, ForkName, Hash256, SignedRoot, Slot, + consts::gloas::BUILDER_INDEX_SELF_BUILD, }; use bls::{PublicKey, Signature}; use context_deserialize::context_deserialize; @@ -58,6 +59,42 @@ impl SignedExecutionPayloadEnvelope { self.signature.verify(pubkey, message) } + + /// Verify `self.signature` using keys drawn from the beacon state. + pub fn verify_signature_with_state( + &self, + state: &BeaconState, + spec: &ChainSpec, + ) -> Result { + let builder_index = self.message.builder_index; + + let pubkey_bytes = if builder_index == BUILDER_INDEX_SELF_BUILD { + let validator_index = state.latest_block_header().proposer_index; + state.get_validator(validator_index as usize)?.pubkey + } else { + state.get_builder(builder_index)?.pubkey + }; + + // TODO(gloas): Could use pubkey cache on state here, but it probably isn't worth + // it because this function is rarely used. Almost always the envelope should be signature + // verified prior to consensus code running. + let pubkey = pubkey_bytes.decompress()?; + + // Ensure the state's epoch matches the message's epoch before determining the Fork. + if self.epoch() != state.current_epoch() { + return Err(BeaconStateError::SignedEnvelopeIncorrectEpoch { + state_epoch: state.current_epoch(), + envelope_epoch: self.epoch(), + }); + } + + Ok(self.verify_signature( + &pubkey, + &state.fork(), + state.genesis_validators_root(), + spec, + )) + } } #[cfg(test)] diff --git a/consensus/types/src/state/beacon_state.rs b/consensus/types/src/state/beacon_state.rs index 2720745b01..1352ded79e 100644 --- a/consensus/types/src/state/beacon_state.rs +++ b/consensus/types/src/state/beacon_state.rs @@ -23,7 +23,7 @@ use tree_hash_derive::TreeHash; use typenum::Unsigned; use crate::{ - ExecutionBlockHash, ExecutionPayloadBid, Withdrawal, + Address, ExecutionBlockHash, ExecutionPayloadBid, Withdrawal, attestation::{ AttestationData, AttestationDuty, BeaconCommittee, Checkpoint, CommitteeIndex, PTC, ParticipationFlags, PendingAttestation, @@ -174,6 +174,8 @@ pub enum BeaconStateError { MerkleTreeError(merkle_proof::MerkleTreeError), PartialWithdrawalCountInvalid(usize), NonExecutionAddressWithdrawalCredential, + WithdrawalCredentialMissingVersion, + WithdrawalCredentialMissingAddress, NoCommitteeFound(CommitteeIndex), InvalidCommitteeIndex(CommitteeIndex), /// `Attestation.data.index` field is invalid in overloaded data index scenario. @@ -199,6 +201,10 @@ pub enum BeaconStateError { ProposerLookaheadOutOfBounds { i: usize, }, + SignedEnvelopeIncorrectEpoch { + state_epoch: Epoch, + envelope_epoch: Epoch, + }, InvalidIndicesCount, InvalidExecutionPayloadAvailabilityIndex(usize), } @@ -1920,6 +1926,15 @@ impl BeaconState { .ok_or(BeaconStateError::UnknownValidator(validator_index)) } + /// Safe indexer for the `builders` list. + /// + /// Will return an error pre-Gloas, or for out-of-bounds indices. + pub fn get_builder(&self, builder_index: BuilderIndex) -> Result<&Builder, BeaconStateError> { + self.builders()? + .get(builder_index as usize) + .ok_or(BeaconStateError::UnknownBuilder(builder_index)) + } + /// Add a validator to the registry and return the validator index that was allocated for it. pub fn add_validator_to_registry( &mut self, @@ -1966,6 +1981,64 @@ impl BeaconState { Ok(index) } + /// Add a builder to the registry and return the builder index that was allocated for it. + pub fn add_builder_to_registry( + &mut self, + pubkey: PublicKeyBytes, + withdrawal_credentials: Hash256, + amount: u64, + slot: Slot, + spec: &ChainSpec, + ) -> Result { + // We are not yet using the spec's `set_or_append_list`, but could consider it if it crops + // up elsewhere. It has been retconned into the spec to support index reuse but so far + // index reuse is only relevant for builders. + let builder_index = self.get_index_for_new_builder()?; + let builders = self.builders_mut()?; + + let version = *withdrawal_credentials + .as_slice() + .first() + .ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?; + let execution_address = withdrawal_credentials + .as_slice() + .get(12..) + .and_then(|bytes| Address::try_from(bytes).ok()) + .ok_or(BeaconStateError::WithdrawalCredentialMissingAddress)?; + + let builder = Builder { + pubkey, + version, + execution_address, + balance: amount, + deposit_epoch: slot.epoch(E::slots_per_epoch()), + withdrawable_epoch: spec.far_future_epoch, + }; + + if builder_index == builders.len() as u64 { + builders.push(builder)?; + } else { + *builders + .get_mut(builder_index as usize) + .ok_or(BeaconStateError::UnknownBuilder(builder_index))? = builder; + } + Ok(builder_index) + } + + // TODO(gloas): Optimize this function if we see a lot of registered builders on-chain. + // A cache here could be quite fiddly because this calculation depends on withdrawable epoch + // and balance - a cache for this would need to be updated whenever either of those fields + // changes. + pub fn get_index_for_new_builder(&self) -> Result { + let current_epoch = self.current_epoch(); + for (index, builder) in self.builders()?.iter().enumerate() { + if builder.withdrawable_epoch <= current_epoch && builder.balance == 0 { + return Ok(index as u64); + } + } + Ok(self.builders()?.len() as u64) + } + /// Safe copy-on-write accessor for the `validators` list. pub fn get_validator_cow( &mut self, diff --git a/consensus/types/src/validator/mod.rs b/consensus/types/src/validator/mod.rs index 8a67407821..23f7a2a0e1 100644 --- a/consensus/types/src/validator/mod.rs +++ b/consensus/types/src/validator/mod.rs @@ -4,6 +4,8 @@ mod validator_registration_data; mod validator_subscription; pub use proposer_preparation_data::ProposerPreparationData; -pub use validator::{Validator, is_compounding_withdrawal_credential}; +pub use validator::{ + Validator, is_builder_withdrawal_credential, is_compounding_withdrawal_credential, +}; pub use validator_registration_data::{SignedValidatorRegistrationData, ValidatorRegistrationData}; pub use validator_subscription::ValidatorSubscription; diff --git a/consensus/types/src/validator/validator.rs b/consensus/types/src/validator/validator.rs index 7898ab9073..5c5bfc761f 100644 --- a/consensus/types/src/validator/validator.rs +++ b/consensus/types/src/validator/validator.rs @@ -319,6 +319,14 @@ pub fn is_compounding_withdrawal_credential( .unwrap_or(false) } +pub fn is_builder_withdrawal_credential(withdrawal_credentials: Hash256, spec: &ChainSpec) -> bool { + withdrawal_credentials + .as_slice() + .first() + .map(|prefix_byte| *prefix_byte == spec.builder_withdrawal_prefix_byte) + .unwrap_or(false) +} + #[cfg(test)] mod tests { use super::*; diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index ed220fbe8c..8e5bd24d24 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -48,19 +48,9 @@ excluded_paths = [ "tests/.*/eip7732", "tests/.*/eip7805", # TODO(gloas): remove these ignores as more Gloas operations are implemented - "tests/.*/gloas/operations/attester_slashing/.*", "tests/.*/gloas/operations/block_header/.*", - "tests/.*/gloas/operations/bls_to_execution_change/.*", - "tests/.*/gloas/operations/consolidation_request/.*", - "tests/.*/gloas/operations/deposit/.*", - "tests/.*/gloas/operations/deposit_request/.*", - "tests/.*/gloas/operations/execution_payload/.*", "tests/.*/gloas/operations/execution_payload_bid/.*", "tests/.*/gloas/operations/payload_attestation/.*", - "tests/.*/gloas/operations/proposer_slashing/.*", - "tests/.*/gloas/operations/sync_aggregate/.*", - "tests/.*/gloas/operations/voluntary_exit/.*", - "tests/.*/gloas/operations/withdrawal_request/.*", # TODO(EIP-7732): remove these ignores as Gloas consensus is implemented "tests/.*/gloas/epoch_processing/.*", "tests/.*/gloas/finality/.*", diff --git a/testing/ef_tests/src/cases/operations.rs b/testing/ef_tests/src/cases/operations.rs index 9133378ac5..ef998a94ba 100644 --- a/testing/ef_tests/src/cases/operations.rs +++ b/testing/ef_tests/src/cases/operations.rs @@ -7,10 +7,12 @@ use ssz::Decode; use state_processing::common::update_progressive_balances_cache::initialize_progressive_balances_cache; use state_processing::epoch_cache::initialize_epoch_cache; use state_processing::per_block_processing::process_operations::{ - process_consolidation_requests, process_deposit_requests, process_withdrawal_requests, + process_consolidation_requests, process_deposit_requests_post_gloas, + process_deposit_requests_pre_gloas, process_withdrawal_requests, }; use state_processing::{ ConsensusContext, + envelope_processing::{EnvelopeProcessingError, process_execution_payload_envelope}, per_block_processing::{ VerifyBlockRoot, VerifySignatures, errors::BlockProcessingError, @@ -29,7 +31,7 @@ use types::{ BeaconBlockBodyCapella, BeaconBlockBodyDeneb, BeaconBlockBodyElectra, BeaconBlockBodyFulu, BeaconState, BlindedPayload, ConsolidationRequest, Deposit, DepositRequest, ExecutionPayload, ForkVersionDecode, FullPayload, ProposerSlashing, SignedBlsToExecutionChange, - SignedVoluntaryExit, SyncAggregate, WithdrawalRequest, + SignedExecutionPayloadEnvelope, SignedVoluntaryExit, SyncAggregate, WithdrawalRequest, }; #[derive(Debug, Clone, Default, Deserialize)] @@ -59,6 +61,8 @@ pub struct Operations> { } pub trait Operation: Debug + Sync + Sized { + type Error: Debug; + fn handler_name() -> String; fn filename() -> String { @@ -76,10 +80,12 @@ pub trait Operation: Debug + Sync + Sized { state: &mut BeaconState, spec: &ChainSpec, _: &Operations, - ) -> Result<(), BlockProcessingError>; + ) -> Result<(), Self::Error>; } impl Operation for Attestation { + type Error = BlockProcessingError; + fn handler_name() -> String { "attestation".into() } @@ -132,6 +138,8 @@ impl Operation for Attestation { } impl Operation for AttesterSlashing { + type Error = BlockProcessingError; + fn handler_name() -> String { "attester_slashing".into() } @@ -163,6 +171,8 @@ impl Operation for AttesterSlashing { } impl Operation for Deposit { + type Error = BlockProcessingError; + fn handler_name() -> String { "deposit".into() } @@ -187,6 +197,8 @@ impl Operation for Deposit { } impl Operation for ProposerSlashing { + type Error = BlockProcessingError; + fn handler_name() -> String { "proposer_slashing".into() } @@ -214,6 +226,8 @@ impl Operation for ProposerSlashing { } impl Operation for SignedVoluntaryExit { + type Error = BlockProcessingError; + fn handler_name() -> String { "voluntary_exit".into() } @@ -238,6 +252,8 @@ impl Operation for SignedVoluntaryExit { } impl Operation for BeaconBlock { + type Error = BlockProcessingError; + fn handler_name() -> String { "block_header".into() } @@ -269,6 +285,8 @@ impl Operation for BeaconBlock { } impl Operation for SyncAggregate { + type Error = BlockProcessingError; + fn handler_name() -> String { "sync_aggregate".into() } @@ -297,6 +315,8 @@ impl Operation for SyncAggregate { } impl Operation for BeaconBlockBody> { + type Error = BlockProcessingError; + fn handler_name() -> String { "execution_payload".into() } @@ -306,7 +326,7 @@ impl Operation for BeaconBlockBody> { } fn is_enabled_for_fork(fork_name: ForkName) -> bool { - fork_name.bellatrix_enabled() + fork_name.bellatrix_enabled() && !fork_name.gloas_enabled() } fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result { @@ -317,8 +337,7 @@ impl Operation for BeaconBlockBody> { ForkName::Deneb => BeaconBlockBody::Deneb(<_>::from_ssz_bytes(bytes)?), ForkName::Electra => BeaconBlockBody::Electra(<_>::from_ssz_bytes(bytes)?), ForkName::Fulu => BeaconBlockBody::Fulu(<_>::from_ssz_bytes(bytes)?), - // TODO(EIP-7732): See if we need to handle Gloas here - _ => panic!(), + _ => panic!("Not supported after Gloas"), }) }) } @@ -340,7 +359,10 @@ impl Operation for BeaconBlockBody> { } } } + impl Operation for BeaconBlockBody> { + type Error = BlockProcessingError; + fn handler_name() -> String { "execution_payload".into() } @@ -350,7 +372,7 @@ impl Operation for BeaconBlockBody> { } fn is_enabled_for_fork(fork_name: ForkName) -> bool { - fork_name.bellatrix_enabled() + fork_name.bellatrix_enabled() && !fork_name.gloas_enabled() } fn decode(path: &Path, fork_name: ForkName, _spec: &ChainSpec) -> Result { @@ -377,8 +399,7 @@ impl Operation for BeaconBlockBody> { let inner = >>::from_ssz_bytes(bytes)?; BeaconBlockBody::Fulu(inner.clone_as_blinded()) } - // TODO(EIP-7732): See if we need to handle Gloas here - _ => panic!(), + _ => panic!("Not supported after Gloas"), }) }) } @@ -401,7 +422,46 @@ impl Operation for BeaconBlockBody> { } } +impl Operation for SignedExecutionPayloadEnvelope { + type Error = EnvelopeProcessingError; + + fn handler_name() -> String { + "execution_payload".into() + } + + fn filename() -> String { + "signed_envelope.ssz_snappy".into() + } + + fn is_enabled_for_fork(fork_name: ForkName) -> bool { + fork_name.gloas_enabled() + } + + fn decode(path: &Path, _: ForkName, _spec: &ChainSpec) -> Result { + ssz_decode_file(path) + } + + fn apply_to( + &self, + state: &mut BeaconState, + spec: &ChainSpec, + extra: &Operations, + ) -> Result<(), Self::Error> { + let valid = extra + .execution_metadata + .as_ref() + .is_some_and(|e| e.execution_valid); + if valid { + process_execution_payload_envelope(state, None, self, VerifySignatures::True, spec) + } else { + Err(EnvelopeProcessingError::ExecutionInvalid) + } + } +} + impl Operation for WithdrawalsPayload { + type Error = BlockProcessingError; + fn handler_name() -> String { "withdrawals".into() } @@ -448,6 +508,8 @@ impl Operation for WithdrawalsPayload { } impl Operation for SignedBlsToExecutionChange { + type Error = BlockProcessingError; + fn handler_name() -> String { "bls_to_execution_change".into() } @@ -480,6 +542,8 @@ impl Operation for SignedBlsToExecutionChange { } impl Operation for WithdrawalRequest { + type Error = BlockProcessingError; + fn handler_name() -> String { "withdrawal_request".into() } @@ -504,6 +568,8 @@ impl Operation for WithdrawalRequest { } impl Operation for DepositRequest { + type Error = BlockProcessingError; + fn handler_name() -> String { "deposit_request".into() } @@ -522,11 +588,17 @@ impl Operation for DepositRequest { spec: &ChainSpec, _extra: &Operations, ) -> Result<(), BlockProcessingError> { - process_deposit_requests(state, std::slice::from_ref(self), spec) + 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) + } } } impl Operation for ConsolidationRequest { + type Error = BlockProcessingError; + fn handler_name() -> String { "consolidation_request".into() } diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index 39ddff46e7..9d11252edb 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -1135,11 +1135,20 @@ impl> Handler for OperationsHandler } fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - // TODO(gloas): So far only withdrawals tests are enabled for Gloas. Self::Case::is_enabled_for_fork(fork_name) && (!fork_name.gloas_enabled() + || self.handler_name() == "attestation" + || self.handler_name() == "attester_slashing" + || self.handler_name() == "bls_to_execution_change" + || self.handler_name() == "consolidation_request" + || self.handler_name() == "deposit_request" + || self.handler_name() == "deposit" + || self.handler_name() == "execution_payload" + || self.handler_name() == "proposer_slashing" + || self.handler_name() == "sync_aggregate" + || self.handler_name() == "withdrawal_request" || self.handler_name() == "withdrawals" - || self.handler_name() == "attestation") + || self.handler_name() == "voluntary_exit") } } diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index b47d39a6fa..8a53a61929 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -87,6 +87,12 @@ fn operations_execution_payload_blinded() { OperationsHandler::>>::default().run(); } +#[test] +fn operations_execution_payload_envelope() { + OperationsHandler::>::default().run(); + OperationsHandler::>::default().run(); +} + #[test] fn operations_withdrawals() { OperationsHandler::>::default().run(); @@ -94,7 +100,7 @@ fn operations_withdrawals() { } #[test] -fn operations_withdrawal_reqeusts() { +fn operations_withdrawal_requests() { OperationsHandler::::default().run(); OperationsHandler::::default().run(); }