diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 9d2e5c83dd..ab1c7dbe65 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -1,8 +1,12 @@ +use crate::common::{decrease_balance, increase_balance}; use crate::consensus_context::ConsensusContext; -use errors::{BlockOperationError, BlockProcessingError, HeaderInvalid}; +use errors::{BlockOperationError, BlockProcessingError, ExecutionBidInvalid, HeaderInvalid}; use rayon::prelude::*; use safe_arith::{ArithError, SafeArith}; -use signature_sets::{block_proposal_signature_set, get_pubkey_from_state, randao_signature_set}; +use signature_sets::{ + block_proposal_signature_set, execution_bid_signature_set, get_pubkey_from_state, + randao_signature_set, +}; use std::borrow::Cow; use tree_hash::TreeHash; use types::*; @@ -43,8 +47,6 @@ mod verify_exit; mod verify_payload_attestation; mod verify_proposer_slashing; -use crate::common::decrease_balance; - use crate::common::update_progressive_balances_cache::{ initialize_progressive_balances_cache, update_progressive_balances_metrics, }; @@ -178,6 +180,8 @@ pub fn per_block_processing>( process_execution_payload::(state, body, spec)?; } + process_execution_bid(state, block, verify_signatures, spec)?; + process_randao(state, block, verify_randao, ctxt, spec)?; process_eth1_data(state, block.body().eth1_data())?; process_operations(state, block.body(), verify_signatures, ctxt, spec)?; @@ -671,3 +675,94 @@ pub fn process_withdrawals>( BeaconState::EIP7732(_) => todo!("implement potuz' changes to process_withdrawals()"), } } + +pub fn process_execution_bid>( + state: &mut BeaconState, + block: BeaconBlockRef<'_, E, Payload>, + verify_signatures: VerifySignatures, + spec: &ChainSpec, +) -> Result<(), BlockProcessingError> { + if !state + .fork_name(&spec) + .map_err(BlockProcessingError::InconsistentStateFork)? + .eip7732_enabled() + { + return Ok(()); + } + + let signed_bid = block.body().signed_execution_bid()?; + if verify_signatures.is_true() { + // Verify the bid signature + block_verify!( + execution_bid_signature_set( + state, + |i| get_pubkey_from_state(state, i), + signed_bid, + spec + )? + .verify(), + ExecutionBidInvalid::BadSignature.into() + ); + } + + let bid = &signed_bid.message; + let builder_index = bid.builder_index; + + // Verify the bid is for the current slot + block_verify!( + bid.slot == state.slot(), + ExecutionBidInvalid::SlotMismatch { + state_slot: state.slot(), + bid_slot: bid.slot, + } + .into() + ); + // Verify the bid is for the correct parent block + let state_block_hash = state.latest_block_hash()?; + block_verify!( + bid.parent_block_hash == state_block_hash, + ExecutionBidInvalid::ParentBlockHashMismatch { + state_block_hash, + bid_parent_hash: bid.parent_block_hash, + } + .into() + ); + let block_parent_root = block.parent_root(); + block_verify!( + bid.parent_block_root == block_parent_root, + ExecutionBidInvalid::ParentBlockRootMismatch { + block_parent_root, + bid_parent_root: bid.parent_block_root, + } + .into() + ); + + // Check the builder is active, non-slashed, and has funds to cover the bid + let builder = state.get_validator(builder_index as usize)?; + block_verify!( + builder.is_active_at(state.current_epoch()), + ExecutionBidInvalid::BuilderNotActive(builder_index).into() + ); + block_verify!( + !builder.slashed, + ExecutionBidInvalid::BuilderSlashed(builder_index).into() + ); + let builder_balance = state.get_balance(builder_index as usize)?; + block_verify!( + builder_balance >= bid.value, + ExecutionBidInvalid::InsufficientBalance { + builder_index, + builder_balance, + bid_value: bid.value, + } + .into() + ); + + // Transfer the funds from the builder to the proposer + 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(); + + Ok(()) +} diff --git a/consensus/state_processing/src/per_block_processing/errors.rs b/consensus/state_processing/src/per_block_processing/errors.rs index 0ac5528f3c..e98181b920 100644 --- a/consensus/state_processing/src/per_block_processing/errors.rs +++ b/consensus/state_processing/src/per_block_processing/errors.rs @@ -60,6 +60,9 @@ pub enum BlockProcessingError { SyncAggregateInvalid { reason: SyncAggregateInvalid, }, + ExecutionBidInvalid { + reason: ExecutionBidInvalid, + }, BeaconStateError(BeaconStateError), SignatureSetError(SignatureSetError), SszTypesError(ssz_types::Error), @@ -150,6 +153,12 @@ impl From for BlockProcessingError { } } +impl From for BlockProcessingError { + fn from(reason: ExecutionBidInvalid) -> Self { + BlockProcessingError::ExecutionBidInvalid { reason } + } +} + impl From> for BlockProcessingError { fn from(e: BlockOperationError) -> BlockProcessingError { match e { @@ -504,3 +513,31 @@ pub enum SyncAggregateInvalid { /// The signature is invalid. SignatureInvalid, } + +#[derive(Debug, PartialEq, Clone)] +pub enum ExecutionBidInvalid { + /// The signature is invalid. + BadSignature, + /// The builder is not an active validator. + BuilderNotActive(u64), + /// The builder is slashed + BuilderSlashed(u64), + /// The builder has insufficient balance to cover the bid + InsufficientBalance { + builder_index: u64, + builder_balance: u64, + bid_value: u64, + }, + /// Bid slot doesn't match state slot + SlotMismatch { state_slot: Slot, bid_slot: Slot }, + /// The bid's parent block hash doesn't match the state's latest block hash + ParentBlockHashMismatch { + state_block_hash: ExecutionBlockHash, + bid_parent_hash: ExecutionBlockHash, + }, + /// The bid's parent block root doesn't match the block's parent root + ParentBlockRootMismatch { + block_parent_root: Hash256, + bid_parent_root: Hash256, + }, +} 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 944c16862a..d4d2069a9b 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, SignedRoot, SignedVoluntaryExit, SigningData, Slot, SyncAggregate, - SyncAggregatorSelectionData, Unsigned, + SignedContributionAndProof, SignedExecutionBid, SignedRoot, SignedVoluntaryExit, SigningData, + Slot, SyncAggregate, SyncAggregatorSelectionData, Unsigned, }; pub type Result = std::result::Result; @@ -361,6 +361,34 @@ where Ok(SignatureSet::multiple_pubkeys(signature, pubkeys, message)) } +pub fn execution_bid_signature_set<'a, E, F>( + state: &'a BeaconState, + get_pubkey: F, + signed_execution_bid: &'a SignedExecutionBid, + 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 execution_bid = &signed_execution_bid.message; + let pubkey = get_pubkey(execution_bid.builder_index as usize) + .ok_or(Error::ValidatorUnknown(execution_bid.builder_index))?; + let message = execution_bid.signing_root(domain); + + Ok(SignatureSet::single_pubkey( + &signed_execution_bid.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/execution_bid.rs b/consensus/types/src/execution_bid.rs index feceb4dbd1..4858e67d3c 100644 --- a/consensus/types/src/execution_bid.rs +++ b/consensus/types/src/execution_bid.rs @@ -21,19 +21,21 @@ use tree_hash_derive::TreeHash; #[derivative(PartialEq, Hash)] // This is what Potuz' spec calls an `ExecutionPayload` even though it's clearly a bid. pub struct ExecutionBid { - parent_block_hash: ExecutionBlockHash, - parent_block_root: Hash256, - block_hash: ExecutionBlockHash, + pub parent_block_hash: ExecutionBlockHash, + pub parent_block_root: Hash256, + pub block_hash: ExecutionBlockHash, #[serde(with = "serde_utils::quoted_u64")] - gas_limit: u64, + pub gas_limit: u64, #[serde(with = "serde_utils::quoted_u64")] - builder_index: u64, - slot: Slot, + pub builder_index: u64, + pub slot: Slot, #[serde(with = "serde_utils::quoted_u64")] - value: u64, - blob_kzg_commitments_root: Hash256, + pub value: u64, + pub blob_kzg_commitments_root: Hash256, } +impl SignedRoot for ExecutionBid {} + #[cfg(test)] mod tests { use super::*;