diff --git a/consensus/state_processing/src/execution_processing.rs b/consensus/state_processing/src/execution_processing.rs index ee79b68242..a8ceff0fff 100644 --- a/consensus/state_processing/src/execution_processing.rs +++ b/consensus/state_processing/src/execution_processing.rs @@ -1,10 +1,136 @@ -use crate::BlockProcessingError; -use types::{BeaconState, ChainSpec, EthSpec, SignedExecutionEnvelope}; +use super::signature_sets::{execution_envelope_signature_set, get_pubkey_from_state}; +use crate::per_block_processing::compute_timestamp_at_slot; +use crate::per_block_processing::errors::{BlockProcessingError, ExecutionEnvelopeError}; +use crate::VerifySignatures; +use tree_hash::TreeHash; +use types::{BeaconState, ChainSpec, EthSpec, Hash256, SignedExecutionEnvelope}; -pub fn process_execution_payload_envelope( +pub fn process_execution_envelope( state: &mut BeaconState, signed_envelope: SignedExecutionEnvelope, spec: &ChainSpec, + verify_signatures: VerifySignatures, ) -> Result<(), BlockProcessingError> { + if verify_signatures.is_true() { + block_verify!( + execution_envelope_signature_set( + state, + |i| get_pubkey_from_state(state, i), + &signed_envelope, + spec + )? + .verify(), + ExecutionEnvelopeError::BadSignature.into() + ) + } + + let envelope = signed_envelope.message(); + let payload = &envelope.payload; + let previous_state_root = state.canonical_root()?; + if state.latest_block_header().state_root == Hash256::default() { + *state.latest_block_header_mut().state_root = *previous_state_root; + } + + // Verify consistency with the beacon block + block_verify!( + envelope.tree_hash_root() == state.latest_block_header().tree_hash_root(), + ExecutionEnvelopeError::LatestBlockHeaderMismatch { + envelope_root: envelope.tree_hash_root(), + block_header_root: state.latest_block_header().tree_hash_root(), + } + .into() + ); + + // Verify consistency with the committed bid + let committed_bid = state.latest_execution_bid()?; + block_verify!( + envelope.builder_index == committed_bid.builder_index, + ExecutionEnvelopeError::BuilderIndexMismatch { + committed_bid: committed_bid.builder_index, + envelope: envelope.builder_index, + } + .into() + ); + block_verify!( + committed_bid.blob_kzg_commitments_root == envelope.blob_kzg_commitments.tree_hash_root(), + ExecutionEnvelopeError::BlobKzgCommitmentsRootMismatch { + committed_bid: committed_bid.blob_kzg_commitments_root, + envelope: envelope.blob_kzg_commitments.tree_hash_root(), + } + .into() + ); + + if !envelope.payment_withheld { + // Verify the withdrawals root + block_verify!( + payload.withdrawals.tree_hash_root() == state.latest_withdrawals_root()?, + ExecutionEnvelopeError::WithdrawalsRootMismatch { + state: state.latest_withdrawals_root()?, + envelope: payload.withdrawals.tree_hash_root(), + } + .into() + ); + + // Verify the gas limit + block_verify!( + payload.gas_limit == committed_bid.gas_limit, + ExecutionEnvelopeError::GasLimitMismatch { + committed_bid: committed_bid.gas_limit, + envelope: payload.gas_limit, + } + .into() + ); + + block_verify!( + committed_bid.block_hash == payload.block_hash, + ExecutionEnvelopeError::BlockHashMismatch { + committed_bid: committed_bid.block_hash, + envelope: payload.block_hash, + } + .into() + ); + + // Verify consistency of the parent hash with respect to the previous execution payload + block_verify!( + payload.parent_hash == state.latest_block_hash()?, + ExecutionEnvelopeError::ParentHashMismatch { + state: state.latest_block_hash()?, + envelope: payload.parent_hash, + } + .into() + ); + + // Verify prev_randao + block_verify!( + payload.prev_randao == *state.get_randao_mix(state.current_epoch())?, + ExecutionEnvelopeError::PrevRandaoMismatch { + state: *state.get_randao_mix(state.current_epoch())?, + envelope: payload.prev_randao, + } + .into() + ); + + // Verify the timestamp + let state_timestamp = compute_timestamp_at_slot(state, state.slot(), spec)?; + block_verify!( + payload.timestamp == state_timestamp, + ExecutionEnvelopeError::TimestampMismatch { + state: state_timestamp, + envelope: payload.timestamp, + } + .into() + ); + + // Verify the commitments are under limit + block_verify!( + envelope.blob_kzg_commitments.len() <= E::max_blob_commitments_per_block(), + ExecutionEnvelopeError::BlobLimitExceeded { + max: E::max_blob_commitments_per_block(), + envelope: envelope.blob_kzg_commitments.len(), + } + .into() + ); + } + Ok(()) } diff --git a/consensus/state_processing/src/lib.rs b/consensus/state_processing/src/lib.rs index 1dcd7372b7..075d8410be 100644 --- a/consensus/state_processing/src/lib.rs +++ b/consensus/state_processing/src/lib.rs @@ -33,7 +33,7 @@ pub mod verify_operation; pub use all_caches::AllCaches; pub use block_replayer::{BlockReplayError, BlockReplayer}; pub use consensus_context::{ConsensusContext, ContextError}; -pub use execution_processing::process_execution_payload_envelope; +pub use execution_processing::process_execution_envelope; pub use genesis::{ eth2_genesis_time, initialize_beacon_state_from_eth1, is_valid_genesis_state, process_activations, diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index a48187d778..bcaa628484 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -699,7 +699,7 @@ pub fn process_execution_bid>( decrease_balance(state, builder_index as usize, bid.value)?; increase_balance(state, block.proposer_index() as usize, bid.value)?; - *state.latest_execution_bid_eip7732_mut()? = bid.clone(); + *state.latest_execution_bid_mut()? = bid.clone(); Ok(()) } diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index e98181b920..5ceff37763 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -63,6 +63,7 @@ pub enum BlockProcessingError { ExecutionBidInvalid { reason: ExecutionBidInvalid, }, + ExecutionEnvelopeError(ExecutionEnvelopeError), BeaconStateError(BeaconStateError), SignatureSetError(SignatureSetError), SszTypesError(ssz_types::Error), @@ -159,6 +160,12 @@ impl From for BlockProcessingError { } } +impl From for BlockProcessingError { + fn from(e: ExecutionEnvelopeError) -> Self { + BlockProcessingError::ExecutionEnvelopeError(e) + } +} + impl From> for BlockProcessingError { fn from(e: BlockOperationError) -> BlockProcessingError { match e { @@ -541,3 +548,59 @@ pub enum ExecutionBidInvalid { bid_parent_root: Hash256, }, } + +#[derive(Debug, PartialEq, Clone)] +pub enum ExecutionEnvelopeError { + /// The signature is invalid. + BadSignature, + /// Envelope doesn't match latest beacon block header + LatestBlockHeaderMismatch { + envelope_root: Hash256, + block_header_root: Hash256, + }, + /// The builder index doesn't match the committed bid + BuilderIndexMismatch { + committed_bid: u64, + envelope: u64, + }, + /// The blob KZG commitments root doesn't match the committed bid + BlobKzgCommitmentsRootMismatch { + committed_bid: Hash256, + envelope: Hash256, + }, + /// The withdrawals root doesn't match the state's latest withdrawals root + WithdrawalsRootMismatch { + state: Hash256, + envelope: Hash256, + }, + // 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 { + state: Hash256, + envelope: Hash256, + }, + // The timestamp didn't match the payload + TimestampMismatch { + state: u64, + envelope: u64, + }, + // Blob committments exceeded the maximum + BlobLimitExceeded { + max: usize, + envelope: usize, + }, +} diff --git a/consensus/state_processing/src/per_block_processing/process_withdrawals.rs b/consensus/state_processing/src/per_block_processing/process_withdrawals.rs index 3f014e9b49..c372a0d21e 100644 --- a/consensus/state_processing/src/per_block_processing/process_withdrawals.rs +++ b/consensus/state_processing/src/per_block_processing/process_withdrawals.rs @@ -97,6 +97,10 @@ pub mod eip7732 { state: &mut BeaconState, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { + if !state.is_parent_block_full() { + return Ok(()); + } + let (expected_withdrawals, partial_withdrawals_count) = get_expected_withdrawals(state, spec)?; process_withdrawals_common(state, expected_withdrawals, partial_withdrawals_count, spec) diff --git a/consensus/state_processing/src/per_block_processing/signature_sets.rs b/consensus/state_processing/src/per_block_processing/signature_sets.rs index d4d2069a9b..5eefb7d0f4 100644 --- a/consensus/state_processing/src/per_block_processing/signature_sets.rs +++ b/consensus/state_processing/src/per_block_processing/signature_sets.rs @@ -12,8 +12,8 @@ use types::{ InconsistentFork, IndexedAttestation, IndexedAttestationRef, IndexedPayloadAttestation, ProposerSlashing, PublicKey, PublicKeyBytes, Signature, SignedAggregateAndProof, SignedBeaconBlock, SignedBeaconBlockHeader, SignedBlsToExecutionChange, - SignedContributionAndProof, SignedExecutionBid, SignedRoot, SignedVoluntaryExit, SigningData, - Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned, + SignedContributionAndProof, SignedExecutionBid, SignedExecutionEnvelope, SignedRoot, + SignedVoluntaryExit, SigningData, Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned, }; pub type Result = std::result::Result; @@ -389,6 +389,34 @@ where )) } +pub fn execution_envelope_signature_set<'a, E, F>( + state: &'a BeaconState, + get_pubkey: F, + signed_envelope: &'a SignedExecutionEnvelope, + spec: &'a ChainSpec, +) -> Result> +where + E: EthSpec, + F: Fn(usize) -> Option>, +{ + let domain = spec.get_domain( + state.current_epoch(), + Domain::BeaconBuilder, + &state.fork(), + state.genesis_validators_root(), + ); + let message = signed_envelope.message().signing_root(domain); + let pubkey = get_pubkey(signed_envelope.message().builder_index as usize).ok_or( + Error::ValidatorUnknown(signed_envelope.message().builder_index), + )?; + + Ok(SignatureSet::single_pubkey( + signed_envelope.signature(), + pubkey, + message, + )) +} + /// Returns the signature set for the given `attester_slashing` and corresponding `pubkeys`. pub fn attester_slashing_signature_sets<'a, E, F>( state: &'a BeaconState, diff --git a/consensus/types/src/beacon_state.rs b/consensus/types/src/beacon_state.rs index 52fe695271..cbfffdb96f 100644 --- a/consensus/types/src/beacon_state.rs +++ b/consensus/types/src/beacon_state.rs @@ -482,7 +482,7 @@ where )] #[metastruct(exclude_from(tree_lists))] pub latest_execution_payload_header: ExecutionPayloadHeaderElectra, - #[superstruct(only(EIP7732), partial_getter(rename = "latest_execution_bid_eip7732"))] + #[superstruct(only(EIP7732))] #[metastruct(exclude_from(tree_lists))] pub latest_execution_bid: ExecutionBid, @@ -1982,6 +1982,19 @@ impl BeaconState { } } + pub fn is_parent_block_full(&self) -> bool { + match self { + BeaconState::Base(_) | BeaconState::Altair(_) => false, + BeaconState::Bellatrix(_) + | BeaconState::Capella(_) + | BeaconState::Deneb(_) + | BeaconState::Electra(_) => true, + BeaconState::EIP7732(state) => { + state.latest_execution_bid.block_hash == state.latest_block_hash + } + } + } + /// Get the committee cache for some `slot`. /// /// Return an error if the cache for the slot's epoch is not initialized. diff --git a/consensus/types/src/execution_envelope.rs b/consensus/types/src/execution_envelope.rs index b0401bc47a..58e74f7d1a 100644 --- a/consensus/types/src/execution_envelope.rs +++ b/consensus/types/src/execution_envelope.rs @@ -48,3 +48,5 @@ pub struct ExecutionEnvelope { pub payment_withheld: bool, pub state_root: Hash256, } + +impl SignedRoot for ExecutionEnvelopeEIP7732 {}