diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index f7abe73543b..d5fef4c5aaa 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -121,8 +121,7 @@ impl TryFrom> for ProvenancedPayload BlockProposalContents::PayloadAndBlobs { payload: ExecutionPayloadHeader::Fulu(builder_bid.header).into(), @@ -330,7 +329,7 @@ impl> BlockProposalContents { pub parent_hash: ExecutionBlockHash, pub parent_gas_limit: u64, 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 65181dcf4f2..3540909fe46 100644 --- a/beacon_node/execution_layer/src/test_utils/mock_builder.rs +++ b/beacon_node/execution_layer/src/test_utils/mock_builder.rs @@ -1,10 +1,15 @@ use crate::test_utils::{DEFAULT_BUILDER_PAYLOAD_VALUE_WEI, DEFAULT_JWT_SECRET}; use crate::{Config, ExecutionLayer, PayloadAttributes, PayloadParameters}; -use eth2::types::{BlobsBundle, BlockId, StateId, ValidatorId}; +use eth2::types::PublishBlockRequest; +use eth2::types::{ + BlobsBundle, BlockId, BroadcastValidation, EventKind, EventTopic, FullPayloadContents, + ProposerData, StateId, ValidatorId, +}; use eth2::{BeaconNodeHttpClient, Timeouts, CONSENSUS_VERSION_HEADER}; use fork_choice::ForkchoiceUpdateParameters; use parking_lot::RwLock; use sensitive_url::SensitiveUrl; +use slog::{debug, error, info, warn, Logger}; use std::collections::HashMap; use std::fmt::Debug; use std::future::Future; @@ -13,20 +18,26 @@ use std::sync::Arc; use std::time::Duration; use task_executor::TaskExecutor; use tempfile::NamedTempFile; +use tokio_stream::StreamExt; use tree_hash::TreeHash; use types::builder_bid::{ BuilderBid, BuilderBidBellatrix, BuilderBidCapella, BuilderBidDeneb, BuilderBidElectra, BuilderBidFulu, SignedBuilderBid, }; use types::{ - Address, BeaconState, ChainSpec, EthSpec, ExecPayload, ExecutionPayload, - ExecutionPayloadHeaderRefMut, ExecutionRequests, FixedBytesExtended, ForkName, - ForkVersionedResponse, Hash256, PublicKeyBytes, Signature, SignedBlindedBeaconBlock, - SignedRoot, SignedValidatorRegistrationData, Slot, Uint256, + Address, BeaconState, ChainSpec, Epoch, EthSpec, ExecPayload, ExecutionPayload, + ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, ForkVersionedResponse, Hash256, + PublicKeyBytes, Signature, SignedBlindedBeaconBlock, SignedRoot, + SignedValidatorRegistrationData, Slot, Uint256, }; use types::{ExecutionBlockHash, SecretKey}; use warp::{Filter, Rejection}; +pub const DEFAULT_FEE_RECIPIENT: Address = Address::repeat_byte(42); +pub const DEFAULT_GAS_LIMIT: u64 = 30_000_000; +pub const DEFAULT_BUILDER_PRIVATE_KEY: &str = + "607a11b45a7219cc61a3d9c5fd08c7eebd602a6a19a977f8d3771d5711a550f2"; + #[derive(Clone)] pub enum Operation { FeeRecipient(Address), @@ -259,6 +270,17 @@ impl BidStuff for BuilderBid { } } +// Non referenced version of `PayloadParameters` +#[derive(Clone)] +pub struct PayloadParametersCloned { + pub parent_hash: ExecutionBlockHash, + pub parent_gas_limit: u64, + pub proposer_gas_limit: Option, + pub payload_attributes: PayloadAttributes, + pub forkchoice_update_params: ForkchoiceUpdateParameters, + pub current_fork: ForkName, +} + #[derive(Clone)] pub struct MockBuilder { el: ExecutionLayer, @@ -268,6 +290,20 @@ pub struct MockBuilder { builder_sk: SecretKey, operations: Arc>>, invalidate_signatures: Arc>, + genesis_time: Option, + /// Only returns bids for registered validators if set to true. `true` by default. + validate_pubkey: bool, + /// Do not apply any operations if set to `false`. + /// Applying operations might modify the cached header in the execution layer. + /// Use this if you want get_header to return a valid bid that can be eventually submitted as + /// a valid block. + apply_operations: bool, + payload_id_cache: Arc>>, + /// If set to `true`, sets the bid returned by `get_header` to Uint256::MAX + max_bid: bool, + /// A cache that stores the proposers index for a given epoch + proposers_cache: Arc>>>, + log: Logger, } impl MockBuilder { @@ -295,7 +331,12 @@ impl MockBuilder { let builder = MockBuilder::new( el, BeaconNodeHttpClient::new(beacon_url, Timeouts::set_all(Duration::from_secs(1))), + true, + true, + false, spec, + None, + executor.log().clone(), ); let host: Ipv4Addr = Ipv4Addr::LOCALHOST; let port = 0; @@ -303,21 +344,47 @@ impl MockBuilder { (builder, server) } + #[allow(clippy::too_many_arguments)] pub fn new( el: ExecutionLayer, beacon_client: BeaconNodeHttpClient, + validate_pubkey: bool, + apply_operations: bool, + max_bid: bool, spec: Arc, + sk: Option<&[u8]>, + log: Logger, ) -> Self { - let sk = SecretKey::random(); + let builder_sk = if let Some(sk_bytes) = sk { + match SecretKey::deserialize(sk_bytes) { + Ok(sk) => sk, + Err(_) => { + error!( + log, + "Invalid sk_bytes provided, generating random secret key" + ); + SecretKey::random() + } + } + } else { + SecretKey::deserialize(&hex::decode(DEFAULT_BUILDER_PRIVATE_KEY).unwrap()).unwrap() + }; Self { el, beacon_client, // Should keep spec and context consistent somehow spec, val_registration_cache: Arc::new(RwLock::new(HashMap::new())), - builder_sk: sk, + builder_sk, + validate_pubkey, operations: Arc::new(RwLock::new(vec![])), invalidate_signatures: Arc::new(RwLock::new(false)), + payload_id_cache: Arc::new(RwLock::new(HashMap::new())), + proposers_cache: Arc::new(RwLock::new(HashMap::new())), + apply_operations, + max_bid, + genesis_time: None, + log, } } @@ -342,8 +409,523 @@ impl MockBuilder { } bid.stamp_payload(); } + + /// Return the public key of the builder + pub fn public_key(&self) -> PublicKeyBytes { + self.builder_sk.public_key().compress() + } + + pub async fn register_validators( + &self, + registrations: Vec, + ) -> Result<(), String> { + info!( + self.log, + "Registering validators"; + "count" => registrations.len(), + ); + for registration in registrations { + if !registration.verify_signature(&self.spec) { + error!( + self.log, + "Failed to register validator"; + "error" => "invalid signature", + "validator" => %registration.message.pubkey + ); + return Err("invalid signature".to_string()); + } + self.val_registration_cache + .write() + .insert(registration.message.pubkey, registration); + } + Ok(()) + } + + pub async fn submit_blinded_block( + &self, + block: SignedBlindedBeaconBlock, + ) -> Result, String> { + let root = match &block { + SignedBlindedBeaconBlock::Base(_) | types::SignedBeaconBlock::Altair(_) => { + return Err("invalid fork".to_string()); + } + SignedBlindedBeaconBlock::Bellatrix(block) => { + block.message.body.execution_payload.tree_hash_root() + } + SignedBlindedBeaconBlock::Capella(block) => { + block.message.body.execution_payload.tree_hash_root() + } + SignedBlindedBeaconBlock::Deneb(block) => { + block.message.body.execution_payload.tree_hash_root() + } + SignedBlindedBeaconBlock::Electra(block) => { + block.message.body.execution_payload.tree_hash_root() + } + SignedBlindedBeaconBlock::Fulu(block) => { + block.message.body.execution_payload.tree_hash_root() + } + }; + info!( + self.log, + "Submitting blinded beacon block to builder"; + "block_hash" => %root + ); + let payload = self + .el + .get_payload_by_root(&root) + .ok_or_else(|| "missing payload for tx root".to_string())?; + + let (payload, blobs) = payload.deconstruct(); + let full_block = block + .try_into_full_block(Some(payload.clone())) + .ok_or("Internal error, just provided a payload")?; + debug!( + self.log, + "Got full payload, sending to local beacon node for propagation"; + "txs_count" => payload.transactions().len(), + "blob_count" => blobs.as_ref().map(|b| b.commitments.len()) + ); + let publish_block_request = PublishBlockRequest::new( + Arc::new(full_block), + blobs.clone().map(|b| (b.proofs, b.blobs)), + ); + self.beacon_client + .post_beacon_blocks_v2(&publish_block_request, Some(BroadcastValidation::Gossip)) + .await + .map_err(|e| format!("Failed to post blinded block {:?}", e))?; + Ok(FullPayloadContents::new(payload, blobs)) + } + + pub async fn get_header( + &self, + slot: Slot, + parent_hash: ExecutionBlockHash, + pubkey: PublicKeyBytes, + ) -> Result, String> { + info!(self.log, "In get_header"); + // Check if the pubkey has registered with the builder if required + if self.validate_pubkey && !self.val_registration_cache.read().contains_key(&pubkey) { + return Err("validator not registered with builder".to_string()); + } + let payload_parameters = { + let mut guard = self.payload_id_cache.write(); + guard.remove(&parent_hash) + }; + + let payload_parameters = match payload_parameters { + Some(params) => params, + None => { + warn!( + self.log, + "Payload params not cached for parent_hash {}", parent_hash + ); + self.get_payload_params(slot, None, pubkey, None).await? + } + }; + + info!(self.log, "Got payload params"); + + let fork = self.fork_name_at_slot(slot); + let payload_response_type = self + .el + .get_full_payload_caching(PayloadParameters { + parent_hash: payload_parameters.parent_hash, + parent_gas_limit: payload_parameters.parent_gas_limit, + proposer_gas_limit: payload_parameters.proposer_gas_limit, + payload_attributes: &payload_parameters.payload_attributes, + forkchoice_update_params: &payload_parameters.forkchoice_update_params, + current_fork: payload_parameters.current_fork, + }) + .await + .map_err(|e| format!("couldn't get payload {:?}", e))?; + + info!(self.log, "Got payload message, fork {}", fork); + + let mut message = match payload_response_type { + crate::GetPayloadResponseType::Full(payload_response) => { + #[allow(clippy::type_complexity)] + let (payload, value, maybe_blobs_bundle, maybe_requests): ( + ExecutionPayload, + Uint256, + Option>, + Option>, + ) = payload_response.into(); + + match fork { + ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { + header: payload + .as_fulu() + .map_err(|_| "incorrect payload variant".to_string())? + .into(), + blob_kzg_commitments: maybe_blobs_bundle + .map(|b| b.commitments) + .unwrap_or_default(), + value: self.get_bid_value(value), + pubkey: self.builder_sk.public_key().compress(), + execution_requests: maybe_requests.unwrap_or_default(), + }), + ForkName::Electra => BuilderBid::Electra(BuilderBidElectra { + header: payload + .as_electra() + .map_err(|_| "incorrect payload variant".to_string())? + .into(), + blob_kzg_commitments: maybe_blobs_bundle + .map(|b| b.commitments) + .unwrap_or_default(), + value: self.get_bid_value(value), + pubkey: self.builder_sk.public_key().compress(), + execution_requests: maybe_requests.unwrap_or_default(), + }), + ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { + header: payload + .as_deneb() + .map_err(|_| "incorrect payload variant".to_string())? + .into(), + blob_kzg_commitments: maybe_blobs_bundle + .map(|b| b.commitments) + .unwrap_or_default(), + value: self.get_bid_value(value), + pubkey: self.builder_sk.public_key().compress(), + }), + ForkName::Capella => BuilderBid::Capella(BuilderBidCapella { + header: payload + .as_capella() + .map_err(|_| "incorrect payload variant".to_string())? + .into(), + value: self.get_bid_value(value), + pubkey: self.builder_sk.public_key().compress(), + }), + ForkName::Bellatrix => BuilderBid::Bellatrix(BuilderBidBellatrix { + header: payload + .as_bellatrix() + .map_err(|_| "incorrect payload variant".to_string())? + .into(), + value: self.get_bid_value(value), + pubkey: self.builder_sk.public_key().compress(), + }), + ForkName::Base | ForkName::Altair => return Err("invalid fork".to_string()), + } + } + _ => panic!("just requested full payload, cannot get blinded"), + }; + + if self.apply_operations { + info!(self.log, "Applying operations"); + self.apply_operations(&mut message); + } + info!(self.log, "Signing builder message"); + + let mut signature = message.sign_builder_message(&self.builder_sk, &self.spec); + + if *self.invalidate_signatures.read() { + signature = Signature::empty(); + }; + let signed_bid = SignedBuilderBid { message, signature }; + info!(self.log, "Builder bid {:?}", &signed_bid.message.value()); + Ok(signed_bid) + } + + fn fork_name_at_slot(&self, slot: Slot) -> ForkName { + self.spec.fork_name_at_slot::(slot) + } + + fn get_bid_value(&self, value: Uint256) -> Uint256 { + if self.max_bid { + Uint256::MAX + } else if !self.apply_operations { + value + } else { + Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI) + } + } + + /// Prepare the execution layer for payload creation every slot for the correct + /// proposer index + pub async fn prepare_execution_layer(&self) -> Result<(), String> { + info!( + self.log, + "Starting a task to prepare the execution layer"; + ); + let mut head_event_stream = self + .beacon_client + .get_events::(&[EventTopic::Head]) + .await + .map_err(|e| format!("Failed to get head event {:?}", e))?; + + while let Some(Ok(event)) = head_event_stream.next().await { + match event { + EventKind::Head(head) => { + debug!( + self.log, + "Got a new head event"; + "block_hash" => %head.block + ); + let next_slot = head.slot + 1; + // Find the next proposer index from the cached data or through a beacon api call + let epoch = next_slot.epoch(E::slots_per_epoch()); + let position_in_slot = next_slot.as_u64() % E::slots_per_epoch(); + let proposer_data = { + let proposers_opt = { + let proposers_cache = self.proposers_cache.read(); + proposers_cache.get(&epoch).cloned() + }; + match proposers_opt { + Some(proposers) => proposers + .get(position_in_slot as usize) + .expect("position in slot is max epoch size") + .clone(), + None => { + // make a call to the beacon api and populate the cache + let duties: Vec<_> = self + .beacon_client + .get_validator_duties_proposer(epoch) + .await + .map_err(|e| { + format!( + "Failed to get proposer duties for epoch: {}, {:?}", + epoch, e + ) + })? + .data; + let proposer_data = duties + .get(position_in_slot as usize) + .expect("position in slot is max epoch size") + .clone(); + self.proposers_cache.write().insert(epoch, duties); + proposer_data + } + } + }; + self.prepare_execution_layer_internal( + head.slot, + head.block, + proposer_data.validator_index, + proposer_data.pubkey, + ) + .await?; + } + e => { + warn!( + self.log, + "Got an unexpected event"; + "event" => %e.topic_name() + ); + } + } + } + Ok(()) + } + + async fn prepare_execution_layer_internal( + &self, + current_slot: Slot, + head_block_root: Hash256, + validator_index: u64, + pubkey: PublicKeyBytes, + ) -> Result<(), String> { + let next_slot = current_slot + 1; + let payload_parameters = self + .get_payload_params( + next_slot, + Some(head_block_root), + pubkey, + Some(validator_index), + ) + .await?; + + self.payload_id_cache + .write() + .insert(payload_parameters.parent_hash, payload_parameters); + Ok(()) + } + + /// Get the `PayloadParameters` for requesting an ExecutionPayload for `slot` + /// for the given `validator_index` and `pubkey`. + async fn get_payload_params( + &self, + slot: Slot, + head_block_root: Option, + pubkey: PublicKeyBytes, + validator_index: Option, + ) -> Result { + let fork = self.fork_name_at_slot(slot); + + let block_id = match head_block_root { + Some(block_root) => BlockId::Root(block_root), + None => BlockId::Head, + }; + let head = self + .beacon_client + .get_beacon_blocks::(block_id) + .await + .map_err(|_| "couldn't get head".to_string())? + .ok_or_else(|| "missing head block".to_string())? + .data; + + let head_block_root = head_block_root.unwrap_or(head.canonical_root()); + + let head_execution_payload = head + .message() + .body() + .execution_payload() + .map_err(|_| "pre-merge block".to_string())?; + let head_execution_hash = head_execution_payload.block_hash(); + let head_gas_limit = head_execution_payload.gas_limit(); + + let finalized_execution_hash = self + .beacon_client + .get_beacon_blocks::(BlockId::Finalized) + .await + .map_err(|_| "couldn't get finalized block".to_string())? + .ok_or_else(|| "missing finalized block".to_string())? + .data + .message() + .body() + .execution_payload() + .map_err(|_| "pre-merge block".to_string())? + .block_hash(); + + let justified_execution_hash = self + .beacon_client + .get_beacon_blocks::(BlockId::Justified) + .await + .map_err(|_| "couldn't get justified block".to_string())? + .ok_or_else(|| "missing justified block".to_string())? + .data + .message() + .body() + .execution_payload() + .map_err(|_| "pre-merge block".to_string())? + .block_hash(); + + let (fee_recipient, proposer_gas_limit) = + match self.val_registration_cache.read().get(&pubkey) { + Some(cached_data) => ( + cached_data.message.fee_recipient, + cached_data.message.gas_limit, + ), + None => { + warn!( + self.log, + "Validator not registered {}, using default fee recipient and gas limits", + pubkey + ); + (DEFAULT_FEE_RECIPIENT, DEFAULT_GAS_LIMIT) + } + }; + let slots_since_genesis = slot.as_u64() - self.spec.genesis_slot.as_u64(); + + let genesis_time = if let Some(genesis_time) = self.genesis_time { + genesis_time + } else { + self.beacon_client + .get_beacon_genesis() + .await + .map_err(|_| "couldn't get beacon genesis".to_string())? + .data + .genesis_time + }; + let timestamp = (slots_since_genesis * self.spec.seconds_per_slot) + genesis_time; + + let head_state: BeaconState = self + .beacon_client + .get_debug_beacon_states(StateId::Head) + .await + .map_err(|_| "couldn't get state".to_string())? + .ok_or_else(|| "missing state".to_string())? + .data; + + let prev_randao = head_state + .get_randao_mix(head_state.current_epoch()) + .map_err(|_| "couldn't get prev randao".to_string())?; + + let expected_withdrawals = if fork.capella_enabled() { + Some( + self.beacon_client + .get_expected_withdrawals(&StateId::Head) + .await + .map_err(|e| format!("Failed to get expected withdrawals: {:?}", e))? + .data, + ) + } else { + None + }; + + let payload_attributes = match fork { + // the withdrawals root is filled in by operations, but we supply the valid withdrawals + // first to avoid polluting the execution block generator with invalid payload attributes + // NOTE: this was part of an effort to add payload attribute uniqueness checks, + // which was abandoned because it broke too many tests in subtle ways. + ForkName::Bellatrix | ForkName::Capella => PayloadAttributes::new( + timestamp, + *prev_randao, + fee_recipient, + expected_withdrawals, + None, + ), + ForkName::Deneb | ForkName::Electra | ForkName::Fulu => PayloadAttributes::new( + timestamp, + *prev_randao, + fee_recipient, + expected_withdrawals, + Some(head_block_root), + ), + ForkName::Base | ForkName::Altair => { + return Err("invalid fork".to_string()); + } + }; + + // Tells the execution layer that the `validator_index` is expected to propose + // a block on top of `head_block_root` for the given slot + let val_index = validator_index.unwrap_or( + self.beacon_client + .get_beacon_states_validator_id(StateId::Head, &ValidatorId::PublicKey(pubkey)) + .await + .map_err(|_| "couldn't get validator".to_string())? + .ok_or_else(|| "missing validator".to_string())? + .data + .index, + ); + + self.el + .insert_proposer(slot, head_block_root, val_index, payload_attributes.clone()) + .await; + + let forkchoice_update_params = ForkchoiceUpdateParameters { + head_hash: Some(head_execution_hash), + finalized_hash: Some(finalized_execution_hash), + justified_hash: Some(justified_execution_hash), + head_root: head_block_root, + }; + + let _status = self + .el + .notify_forkchoice_updated( + head_execution_hash, + justified_execution_hash, + finalized_execution_hash, + slot - 1, + head_block_root, + ) + .await + .map_err(|e| format!("fcu call failed : {:?}", e))?; + + let payload_parameters = PayloadParametersCloned { + parent_hash: head_execution_hash, + parent_gas_limit: head_gas_limit, + proposer_gas_limit: Some(proposer_gas_limit), + payload_attributes, + forkchoice_update_params, + current_fork: fork, + }; + Ok(payload_parameters) + } } +/// Serve the builder api using warp. Uses the functions defined in `MockBuilder` to serve +/// the requests. +/// +/// We should eventually move this to axum when we move everything else. pub fn serve( listen_addr: Ipv4Addr, listen_port: u16, @@ -362,19 +944,16 @@ pub fn serve( .and(warp::path::end()) .and(ctx_filter.clone()) .and_then( - |registrations: Vec, builder: MockBuilder| async move { - for registration in registrations { - if !registration.verify_signature(&builder.spec) { - return Err(reject("invalid signature")); - } - builder - .val_registration_cache - .write() - .insert(registration.message.pubkey, registration); - } - Ok(warp::reply()) + |registrations: Vec, + builder: MockBuilder| async move { + builder + .register_validators(registrations) + .await + .map_err(|e| warp::reject::custom(Custom(e)))?; + Ok::<_, Rejection>(warp::reply()) }, - ); + ) + .boxed(); let blinded_block = prefix @@ -387,30 +966,10 @@ pub fn serve( |block: SignedBlindedBeaconBlock, fork_name: ForkName, builder: MockBuilder| async move { - let root = match block { - SignedBlindedBeaconBlock::Base(_) | types::SignedBeaconBlock::Altair(_) => { - return Err(reject("invalid fork")); - } - SignedBlindedBeaconBlock::Bellatrix(block) => { - block.message.body.execution_payload.tree_hash_root() - } - SignedBlindedBeaconBlock::Capella(block) => { - block.message.body.execution_payload.tree_hash_root() - } - SignedBlindedBeaconBlock::Deneb(block) => { - block.message.body.execution_payload.tree_hash_root() - } - SignedBlindedBeaconBlock::Electra(block) => { - block.message.body.execution_payload.tree_hash_root() - } - SignedBlindedBeaconBlock::Fulu(block) => { - block.message.body.execution_payload.tree_hash_root() - } - }; let payload = builder - .el - .get_payload_by_root(&root) - .ok_or_else(|| reject("missing payload for tx root"))?; + .submit_blinded_block(block) + .await + .map_err(|e| warp::reject::custom(Custom(e)))?; let resp: ForkVersionedResponse<_> = ForkVersionedResponse { version: Some(fork_name), metadata: Default::default(), @@ -453,305 +1012,12 @@ pub fn serve( parent_hash: ExecutionBlockHash, pubkey: PublicKeyBytes, builder: MockBuilder| async move { - let fork = builder.spec.fork_name_at_slot::(slot); - let signed_cached_data = builder - .val_registration_cache - .read() - .get(&pubkey) - .ok_or_else(|| reject("missing registration"))? - .clone(); - let cached_data = signed_cached_data.message; - - let head = builder - .beacon_client - .get_beacon_blocks::(BlockId::Head) - .await - .map_err(|_| reject("couldn't get head"))? - .ok_or_else(|| reject("missing head block"))?; - - let block = head.data.message(); - let head_block_root = block.tree_hash_root(); - let head_execution_payload = block - .body() - .execution_payload() - .map_err(|_| reject("pre-merge block"))?; - let head_execution_hash = head_execution_payload.block_hash(); - let head_gas_limit = head_execution_payload.gas_limit(); - if head_execution_hash != parent_hash { - return Err(reject("head mismatch")); - } - - let finalized_execution_hash = builder - .beacon_client - .get_beacon_blocks::(BlockId::Finalized) - .await - .map_err(|_| reject("couldn't get finalized block"))? - .ok_or_else(|| reject("missing finalized block"))? - .data - .message() - .body() - .execution_payload() - .map_err(|_| reject("pre-merge block"))? - .block_hash(); - - let justified_execution_hash = builder - .beacon_client - .get_beacon_blocks::(BlockId::Justified) - .await - .map_err(|_| reject("couldn't get justified block"))? - .ok_or_else(|| reject("missing justified block"))? - .data - .message() - .body() - .execution_payload() - .map_err(|_| reject("pre-merge block"))? - .block_hash(); - - let val_index = builder - .beacon_client - .get_beacon_states_validator_id(StateId::Head, &ValidatorId::PublicKey(pubkey)) - .await - .map_err(|_| reject("couldn't get validator"))? - .ok_or_else(|| reject("missing validator"))? - .data - .index; - let fee_recipient = cached_data.fee_recipient; - let slots_since_genesis = slot.as_u64() - builder.spec.genesis_slot.as_u64(); - - let genesis_data = builder - .beacon_client - .get_beacon_genesis() - .await - .map_err(|_| reject("couldn't get beacon genesis"))? - .data; - let genesis_time = genesis_data.genesis_time; - let timestamp = - (slots_since_genesis * builder.spec.seconds_per_slot) + genesis_time; - - let head_state: BeaconState = builder - .beacon_client - .get_debug_beacon_states(StateId::Head) - .await - .map_err(|_| reject("couldn't get state"))? - .ok_or_else(|| reject("missing state"))? - .data; - let prev_randao = head_state - .get_randao_mix(head_state.current_epoch()) - .map_err(|_| reject("couldn't get prev randao"))?; - - let expected_withdrawals = if fork.capella_enabled() { - Some( - builder - .beacon_client - .get_expected_withdrawals(&StateId::Head) - .await - .unwrap() - .data, - ) - } else { - None - }; - - let payload_attributes = match fork { - // the withdrawals root is filled in by operations, but we supply the valid withdrawals - // first to avoid polluting the execution block generator with invalid payload attributes - // NOTE: this was part of an effort to add payload attribute uniqueness checks, - // which was abandoned because it broke too many tests in subtle ways. - ForkName::Bellatrix | ForkName::Capella => PayloadAttributes::new( - timestamp, - *prev_randao, - fee_recipient, - expected_withdrawals, - None, - ), - ForkName::Deneb | ForkName::Electra | ForkName::Fulu => PayloadAttributes::new( - timestamp, - *prev_randao, - fee_recipient, - expected_withdrawals, - Some(head_block_root), - ), - ForkName::Base | ForkName::Altair => { - return Err(reject("invalid fork")); - } - }; - - builder - .el - .insert_proposer(slot, head_block_root, val_index, payload_attributes.clone()) - .await; - - let forkchoice_update_params = ForkchoiceUpdateParameters { - head_root: Hash256::zero(), - head_hash: None, - justified_hash: Some(justified_execution_hash), - finalized_hash: Some(finalized_execution_hash), - }; - - let proposer_gas_limit = builder - .val_registration_cache - .read() - .get(&pubkey) - .map(|v| v.message.gas_limit); - - let payload_parameters = PayloadParameters { - parent_hash: head_execution_hash, - parent_gas_limit: head_gas_limit, - proposer_gas_limit, - payload_attributes: &payload_attributes, - forkchoice_update_params: &forkchoice_update_params, - current_fork: fork, - }; - - let payload_response_type = builder - .el - .get_full_payload_caching(payload_parameters) + let fork_name = builder.fork_name_at_slot(slot); + let signed_bid = builder + .get_header(slot, parent_hash, pubkey) .await - .map_err(|_| reject("couldn't get payload"))?; - - let mut message = match payload_response_type { - crate::GetPayloadResponseType::Full(payload_response) => { - #[allow(clippy::type_complexity)] - let (payload, _block_value, maybe_blobs_bundle, _maybe_requests): ( - ExecutionPayload, - Uint256, - Option>, - Option>, - ) = payload_response.into(); - - match fork { - ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { - header: payload - .as_fulu() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) - .unwrap_or_default(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Electra => BuilderBid::Electra(BuilderBidElectra { - header: payload - .as_electra() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) - .unwrap_or_default(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { - header: payload - .as_deneb() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) - .unwrap_or_default(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Capella => BuilderBid::Capella(BuilderBidCapella { - header: payload - .as_capella() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Bellatrix => BuilderBid::Bellatrix(BuilderBidBellatrix { - header: payload - .as_bellatrix() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Base | ForkName::Altair => { - return Err(reject("invalid fork")) - } - } - } - crate::GetPayloadResponseType::Blinded(payload_response) => { - #[allow(clippy::type_complexity)] - let (payload, _block_value, maybe_blobs_bundle, _maybe_requests): ( - ExecutionPayload, - Uint256, - Option>, - Option>, - ) = payload_response.into(); - match fork { - ForkName::Fulu => BuilderBid::Fulu(BuilderBidFulu { - header: payload - .as_fulu() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) - .unwrap_or_default(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Electra => BuilderBid::Electra(BuilderBidElectra { - header: payload - .as_electra() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) - .unwrap_or_default(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Deneb => BuilderBid::Deneb(BuilderBidDeneb { - header: payload - .as_deneb() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - blob_kzg_commitments: maybe_blobs_bundle - .map(|b| b.commitments) - .unwrap_or_default(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Capella => BuilderBid::Capella(BuilderBidCapella { - header: payload - .as_capella() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Bellatrix => BuilderBid::Bellatrix(BuilderBidBellatrix { - header: payload - .as_bellatrix() - .map_err(|_| reject("incorrect payload variant"))? - .into(), - value: Uint256::from(DEFAULT_BUILDER_PAYLOAD_VALUE_WEI), - pubkey: builder.builder_sk.public_key().compress(), - }), - ForkName::Base | ForkName::Altair => { - return Err(reject("invalid fork")) - } - } - } - }; - - builder.apply_operations(&mut message); - - let mut signature = - message.sign_builder_message(&builder.builder_sk, &builder.spec); - - if *builder.invalidate_signatures.read() { - signature = Signature::empty(); - } + .map_err(|e| warp::reject::custom(Custom(e)))?; - let fork_name = builder - .spec - .fork_name_at_epoch(slot.epoch(E::slots_per_epoch())); - let signed_bid = SignedBuilderBid { message, signature }; let resp: ForkVersionedResponse<_> = ForkVersionedResponse { version: Some(fork_name), metadata: Default::default(), diff --git a/consensus/types/src/builder_bid.rs b/consensus/types/src/builder_bid.rs index 2ce46ca704b..ac53c41216f 100644 --- a/consensus/types/src/builder_bid.rs +++ b/consensus/types/src/builder_bid.rs @@ -2,8 +2,8 @@ use crate::beacon_block_body::KzgCommitments; use crate::{ ChainSpec, EthSpec, ExecutionPayloadHeaderBellatrix, ExecutionPayloadHeaderCapella, ExecutionPayloadHeaderDeneb, ExecutionPayloadHeaderElectra, ExecutionPayloadHeaderFulu, - ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ForkName, ForkVersionDeserialize, - SignedRoot, Uint256, + ExecutionPayloadHeaderRef, ExecutionPayloadHeaderRefMut, ExecutionRequests, ForkName, + ForkVersionDeserialize, SignedRoot, Uint256, }; use bls::PublicKeyBytes; use bls::Signature; @@ -36,6 +36,8 @@ pub struct BuilderBid { pub header: ExecutionPayloadHeaderFulu, #[superstruct(only(Deneb, Electra, Fulu))] pub blob_kzg_commitments: KzgCommitments, + #[superstruct(only(Electra, Fulu))] + pub execution_requests: ExecutionRequests, #[serde(with = "serde_utils::quoted_u256")] pub value: Uint256, pub pubkey: PublicKeyBytes,