From f9bfaf9da69ff7643391ab0829bc6d88dff4874c Mon Sep 17 00:00:00 2001 From: Jimmy Chen Date: Thu, 5 Feb 2026 10:53:13 +1100 Subject: [PATCH] Move block production to gloas file (no logic change). --- .../gloas.rs} | 210 +++++++++++++++--- .../beacon_chain/src/block_production/mod.rs | 1 + .../beacon_chain/src/execution_payload_bid.rs | 177 --------------- beacon_node/beacon_chain/src/lib.rs | 3 +- .../src/pending_payload_envelopes.rs | 1 + beacon_node/http_api/src/produce_block.rs | 8 +- 6 files changed, 183 insertions(+), 217 deletions(-) rename beacon_node/beacon_chain/src/{beacon_block.rs => block_production/gloas.rs} (73%) create mode 100644 beacon_node/beacon_chain/src/block_production/mod.rs delete mode 100644 beacon_node/beacon_chain/src/execution_payload_bid.rs diff --git a/beacon_node/beacon_chain/src/beacon_block.rs b/beacon_node/beacon_chain/src/block_production/gloas.rs similarity index 73% rename from beacon_node/beacon_chain/src/beacon_block.rs rename to beacon_node/beacon_chain/src/block_production/gloas.rs index 98233cdf08..9b6285bbac 100644 --- a/beacon_node/beacon_chain/src/beacon_block.rs +++ b/beacon_node/beacon_chain/src/block_production/gloas.rs @@ -3,6 +3,7 @@ use std::marker::PhantomData; use std::sync::Arc; use bls::Signature; +use execution_layer::{BlockProposalContentsType, BuilderParams}; use operation_pool::CompactAttestationRef; use ssz::Encode; use state_processing::common::get_attesting_indices_from_state; @@ -12,19 +13,21 @@ use state_processing::{ BlockSignatureStrategy, ConsensusContext, VerifyBlockRoot, VerifySignatures, }; use state_processing::{VerifyOperation, state_advance::complete_state_advance}; -use tracing::{Span, debug, debug_span, error, trace, warn}; +use tracing::{Span, debug, debug_span, error, instrument, trace, warn}; use tree_hash::TreeHash; use types::{ - Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra, BeaconBlock, - BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, Deposit, Eth1Data, EthSpec, - ExecutionPayloadEnvelope, FullPayload, Graffiti, Hash256, PayloadAttestation, ProposerSlashing, - RelativeEpoch, SignedBeaconBlock, SignedBlsToExecutionChange, SignedExecutionPayloadBid, - SignedVoluntaryExit, Slot, SyncAggregate, + Address, Attestation, AttestationElectra, AttesterSlashing, AttesterSlashingElectra, + BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BeaconState, BlockProductionVersion, + BuilderIndex, Deposit, Eth1Data, EthSpec, ExecutionPayloadBid, ExecutionPayloadEnvelope, + ExecutionPayloadGloas, ExecutionRequests, FullPayload, Graffiti, Hash256, PayloadAttestation, + ProposerSlashing, RelativeEpoch, SignedBeaconBlock, SignedBlsToExecutionChange, + SignedExecutionPayloadBid, SignedVoluntaryExit, Slot, SyncAggregate, }; +use crate::execution_payload::get_execution_payload; use crate::{ - BeaconChain, BeaconChainTypes, BlockProductionError, ProduceBlockVerification, - execution_payload_bid::ExecutionPayloadData, graffiti_calculator::GraffitiSettings, metrics, + BeaconChain, BeaconChainError, BeaconChainTypes, BlockProductionError, + ProduceBlockVerification, graffiti_calculator::GraffitiSettings, metrics, }; pub struct PartialBeaconBlock { @@ -44,7 +47,16 @@ pub struct PartialBeaconBlock { bls_to_execution_changes: Vec, } -// We'll need to add that once we include trusted/trustless bids +/// Data needed to construct an ExecutionPayloadEnvelope. +/// 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 builder_index: BuilderIndex, + pub slot: Slot, + pub state_root: Hash256, +} + impl BeaconChain { pub async fn produce_block_with_verification_gloas( self: &Arc, @@ -53,14 +65,7 @@ impl BeaconChain { graffiti_settings: GraffitiSettings, verification: ProduceBlockVerification, _builder_boost_factor: Option, - ) -> Result< - ( - BeaconBlock>, - BeaconState, - u64, - ), - BlockProductionError, - > { + ) -> Result<(BeaconBlock>, u64), BlockProductionError> { metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS); let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES); // Part 1/2 (blocking) @@ -105,14 +110,7 @@ impl BeaconChain { randao_reveal: Signature, graffiti_settings: GraffitiSettings, verification: ProduceBlockVerification, - ) -> Result< - ( - BeaconBlock>, - BeaconState, - u64, - ), - BlockProductionError, - > { + ) -> Result<(BeaconBlock>, u64), BlockProductionError> { // Part 1/3 (blocking) // // Perform the state advance and block-packing functions. @@ -419,14 +417,7 @@ impl BeaconChain { payload_data: Option>, mut state: BeaconState, verification: ProduceBlockVerification, - ) -> Result< - ( - BeaconBlock>, - BeaconState, - u64, - ), - BlockProductionError, - > { + ) -> Result<(BeaconBlock>, u64), BlockProductionError> { let PartialBeaconBlock { slot, proposer_index, @@ -602,6 +593,157 @@ impl BeaconChain { "Produced beacon block" ); - Ok((block, state, consensus_block_value)) + Ok((block, consensus_block_value)) + } + + // TODO(gloas) introduce `ProposerPreferences` so we can build out trustless + // bid building. Right now this only works for local building. + /// Produce an `ExecutionPayloadBid` for some `slot` upon the given `state`. + /// This function assumes we've already done the state advance. + /// + /// Returns the signed bid, the state, and optionally the payload data needed to construct + /// the `ExecutionPayloadEnvelope` after the beacon block is created. + /// + /// For local building, payload data is always returned (`Some`). + /// For trustless building, the builder provides the envelope separately, so `None` is returned. + #[allow(clippy::type_complexity)] + #[instrument(level = "debug", skip_all)] + pub async fn produce_execution_payload_bid( + self: Arc, + state: BeaconState, + state_root_opt: Option, + produce_at_slot: Slot, + bid_value: u64, + builder_index: BuilderIndex, + ) -> Result< + ( + SignedExecutionPayloadBid, + BeaconState, + Option>, + ), + BlockProductionError, + > { + // TODO(gloas) For non local building, add sanity check on value + // The builder MUST have enough excess balance to fulfill this bid (i.e. `value`) and all pending payments. + + // TODO(gloas) add metrics for execution payload bid production + + let parent_root = if state.slot() > 0 { + *state + .get_block_root(state.slot() - 1) + .map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)? + } else { + state.latest_block_header().canonical_root() + }; + + let proposer_index = state.get_beacon_proposer_index(state.slot(), &self.spec)? as u64; + + let pubkey = state + .validators() + .get(proposer_index as usize) + .map(|v| v.pubkey) + .ok_or(BlockProductionError::BeaconChain(Box::new( + BeaconChainError::ValidatorIndexUnknown(proposer_index as usize), + )))?; + + let builder_params = BuilderParams { + pubkey, + slot: state.slot(), + chain_health: self + .is_healthy(&parent_root) + .map_err(|e| BlockProductionError::BeaconChain(Box::new(e)))?, + }; + + // TODO(gloas) this should be BlockProductionVersion::V4 + // V3 is okay for now as long as we're not connected to a builder + // TODO(gloas) add builder boost factor + let prepare_payload_handle = get_execution_payload( + self.clone(), + &state, + parent_root, + proposer_index, + builder_params, + None, + BlockProductionVersion::V3, + )?; + + let block_contents_type = prepare_payload_handle + .await + .map_err(BlockProductionError::TokioJoin)? + .ok_or(BlockProductionError::ShuttingDown)??; + + let (execution_payload, blob_kzg_commitments, execution_requests) = + match block_contents_type { + BlockProposalContentsType::Full(block_proposal_contents) => { + let (payload, blob_kzg_commitments, _, execution_requests, _) = + block_proposal_contents.deconstruct(); + + if let Some(blob_kzg_commitments) = blob_kzg_commitments + && let Some(execution_requests) = execution_requests + { + ( + payload.execution_payload(), + blob_kzg_commitments, + execution_requests, + ) + } else { + return Err(BlockProductionError::MissingKzgCommitment( + "No KZG commitments from the payload".to_owned(), + )); + } + } + // TODO(gloas) we should never receive a blinded response. + // Should return some type of `Unexpected` error variant as this should never happen + // in the V4 block production flow + BlockProposalContentsType::Blinded(_) => { + return Err(BlockProductionError::GloasNotImplemented); + } + }; + + let state_root = state_root_opt.ok_or_else(|| BlockProductionError::MissingStateRoot)?; + + // TODO(gloas) this is just a dummy error variant for now + let execution_payload_gloas = execution_payload + .as_gloas() + .map_err(|_| BlockProductionError::GloasNotImplemented)? + .to_owned(); + + let bid = ExecutionPayloadBid:: { + parent_block_hash: state.latest_block_hash()?.to_owned(), + parent_block_root: state.get_latest_block_root(state_root), + block_hash: execution_payload.block_hash(), + prev_randao: execution_payload.prev_randao(), + fee_recipient: Address::ZERO, + gas_limit: execution_payload.gas_limit(), + builder_index, + slot: produce_at_slot, + value: bid_value, + execution_payment: 0, + blob_kzg_commitments, + }; + + // Store payload data for envelope construction after block is created + let payload_data = ExecutionPayloadData { + payload: execution_payload_gloas, + execution_requests, + builder_index, + slot: produce_at_slot, + state_root, + }; + + // TODO(gloas) this is only local building + // we'll need to implement builder signature for the trustless path + Ok(( + SignedExecutionPayloadBid { + message: bid, + // TODO(gloas) return better error variant here + signature: Signature::infinity() + .map_err(|_| BlockProductionError::GloasNotImplemented)?, + }, + state, + // Local building always returns payload data. + // Trustless building would return None here. + Some(payload_data), + )) } } diff --git a/beacon_node/beacon_chain/src/block_production/mod.rs b/beacon_node/beacon_chain/src/block_production/mod.rs new file mode 100644 index 0000000000..37b62a181b --- /dev/null +++ b/beacon_node/beacon_chain/src/block_production/mod.rs @@ -0,0 +1 @@ +mod gloas; diff --git a/beacon_node/beacon_chain/src/execution_payload_bid.rs b/beacon_node/beacon_chain/src/execution_payload_bid.rs deleted file mode 100644 index fe9974d93c..0000000000 --- a/beacon_node/beacon_chain/src/execution_payload_bid.rs +++ /dev/null @@ -1,177 +0,0 @@ -use std::sync::Arc; - -use bls::Signature; -use execution_layer::{BlockProposalContentsType, BuilderParams}; -use tracing::instrument; -use types::{ - Address, BeaconState, BlockProductionVersion, BuilderIndex, ExecutionPayloadBid, - ExecutionPayloadGloas, ExecutionRequests, Hash256, SignedExecutionPayloadBid, Slot, -}; - -use crate::{ - BeaconChain, BeaconChainError, BeaconChainTypes, BlockProductionError, - execution_payload::get_execution_payload, -}; - -/// Data needed to construct an ExecutionPayloadEnvelope. -/// 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 builder_index: BuilderIndex, - pub slot: Slot, - pub state_root: Hash256, -} - -impl BeaconChain { - // TODO(gloas) introduce `ProposerPreferences` so we can build out trustless - // bid building. Right now this only works for local building. - /// Produce an `ExecutionPayloadBid` for some `slot` upon the given `state`. - /// This function assumes we've already done the state advance. - /// - /// Returns the signed bid, the state, and optionally the payload data needed to construct - /// the `ExecutionPayloadEnvelope` after the beacon block is created. - /// - /// For local building, payload data is always returned (`Some`). - /// For trustless building, the builder provides the envelope separately, so `None` is returned. - #[allow(clippy::type_complexity)] - #[instrument(level = "debug", skip_all)] - pub async fn produce_execution_payload_bid( - self: Arc, - state: BeaconState, - state_root_opt: Option, - produce_at_slot: Slot, - bid_value: u64, - builder_index: BuilderIndex, - ) -> Result< - ( - SignedExecutionPayloadBid, - BeaconState, - Option>, - ), - BlockProductionError, - > { - // TODO(gloas) For non local building, add sanity check on value - // The builder MUST have enough excess balance to fulfill this bid (i.e. `value`) and all pending payments. - - // TODO(gloas) add metrics for execution payload bid production - - let parent_root = if state.slot() > 0 { - *state - .get_block_root(state.slot() - 1) - .map_err(|_| BlockProductionError::UnableToGetBlockRootFromState)? - } else { - state.latest_block_header().canonical_root() - }; - - let proposer_index = state.get_beacon_proposer_index(state.slot(), &self.spec)? as u64; - - let pubkey = state - .validators() - .get(proposer_index as usize) - .map(|v| v.pubkey) - .ok_or(BlockProductionError::BeaconChain(Box::new( - BeaconChainError::ValidatorIndexUnknown(proposer_index as usize), - )))?; - - let builder_params = BuilderParams { - pubkey, - slot: state.slot(), - chain_health: self - .is_healthy(&parent_root) - .map_err(|e| BlockProductionError::BeaconChain(Box::new(e)))?, - }; - - // TODO(gloas) this should be BlockProductionVersion::V4 - // V3 is okay for now as long as we're not connected to a builder - // TODO(gloas) add builder boost factor - let prepare_payload_handle = get_execution_payload( - self.clone(), - &state, - parent_root, - proposer_index, - builder_params, - None, - BlockProductionVersion::V3, - )?; - - let block_contents_type = prepare_payload_handle - .await - .map_err(BlockProductionError::TokioJoin)? - .ok_or(BlockProductionError::ShuttingDown)??; - - let (execution_payload, blob_kzg_commitments, execution_requests) = - match block_contents_type { - BlockProposalContentsType::Full(block_proposal_contents) => { - let (payload, blob_kzg_commitments, _, execution_requests, _) = - block_proposal_contents.deconstruct(); - - if let Some(blob_kzg_commitments) = blob_kzg_commitments - && let Some(execution_requests) = execution_requests - { - ( - payload.execution_payload(), - blob_kzg_commitments, - execution_requests, - ) - } else { - return Err(BlockProductionError::MissingKzgCommitment( - "No KZG commitments from the payload".to_owned(), - )); - } - } - // TODO(gloas) we should never receive a blinded response. - // Should return some type of `Unexpected` error variant as this should never happen - // in the V4 block production flow - BlockProposalContentsType::Blinded(_) => { - return Err(BlockProductionError::GloasNotImplemented); - } - }; - - let state_root = state_root_opt.ok_or_else(|| BlockProductionError::MissingStateRoot)?; - - // TODO(gloas) this is just a dummy error variant for now - let execution_payload_gloas = execution_payload - .as_gloas() - .map_err(|_| BlockProductionError::GloasNotImplemented)? - .to_owned(); - - let bid = ExecutionPayloadBid:: { - parent_block_hash: state.latest_block_hash()?.to_owned(), - parent_block_root: state.get_latest_block_root(state_root), - block_hash: execution_payload.block_hash(), - prev_randao: execution_payload.prev_randao(), - fee_recipient: Address::ZERO, - gas_limit: execution_payload.gas_limit(), - builder_index, - slot: produce_at_slot, - value: bid_value, - execution_payment: 0, - blob_kzg_commitments, - }; - - // Store payload data for envelope construction after block is created - let payload_data = ExecutionPayloadData { - payload: execution_payload_gloas, - execution_requests, - builder_index, - slot: produce_at_slot, - state_root, - }; - - // TODO(gloas) this is only local building - // we'll need to implement builder signature for the trustless path - Ok(( - SignedExecutionPayloadBid { - message: bid, - // TODO(gloas) return better error variant here - signature: Signature::infinity() - .map_err(|_| BlockProductionError::GloasNotImplemented)?, - }, - state, - // Local building always returns payload data. - // Trustless building would return None here. - Some(payload_data), - )) - } -} diff --git a/beacon_node/beacon_chain/src/lib.rs b/beacon_node/beacon_chain/src/lib.rs index 78385cd226..3b03395a66 100644 --- a/beacon_node/beacon_chain/src/lib.rs +++ b/beacon_node/beacon_chain/src/lib.rs @@ -1,7 +1,6 @@ pub mod attestation_rewards; pub mod attestation_simulator; pub mod attestation_verification; -mod beacon_block; pub mod beacon_block_reward; mod beacon_block_streamer; mod beacon_chain; @@ -10,6 +9,7 @@ pub mod beacon_proposer_cache; mod beacon_snapshot; pub mod bellatrix_readiness; pub mod blob_verification; +mod block_production; pub mod block_reward; mod block_times_cache; mod block_verification; @@ -24,7 +24,6 @@ mod early_attester_cache; mod errors; pub mod events; pub mod execution_payload; -pub mod execution_payload_bid; pub mod fetch_blobs; pub mod fork_choice_signal; pub mod fork_revert; diff --git a/beacon_node/beacon_chain/src/pending_payload_envelopes.rs b/beacon_node/beacon_chain/src/pending_payload_envelopes.rs index 14979972b8..80e71b1178 100644 --- a/beacon_node/beacon_chain/src/pending_payload_envelopes.rs +++ b/beacon_node/beacon_chain/src/pending_payload_envelopes.rs @@ -39,6 +39,7 @@ impl PendingPayloadEnvelopes { /// Insert a pending envelope into the cache. pub fn insert(&mut self, slot: Slot, envelope: ExecutionPayloadEnvelope) { + // TODO(gloas): we may want to check for duplicates here, which shouldn't be allowed self.envelopes.insert(slot, envelope); } diff --git a/beacon_node/http_api/src/produce_block.rs b/beacon_node/http_api/src/produce_block.rs index c520a608a9..f97df399d7 100644 --- a/beacon_node/http_api/src/produce_block.rs +++ b/beacon_node/http_api/src/produce_block.rs @@ -70,7 +70,7 @@ pub async fn produce_block_v4( let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy); - let (block, _state, consensus_block_value) = chain + let (block, consensus_block_value) = chain .produce_block_with_verification_gloas( randao_reveal, slot, @@ -83,7 +83,7 @@ pub async fn produce_block_v4( warp_utils::reject::custom_bad_request(format!("failed to fetch a block: {:?}", e)) })?; - build_response_v4(chain, block, consensus_block_value, accept_header) + build_response_v4::(block, consensus_block_value, accept_header, &chain.spec) } #[instrument( @@ -131,14 +131,14 @@ pub async fn produce_block_v3( } pub fn build_response_v4( - chain: Arc>, block: BeaconBlock>, consensus_block_value: u64, accept_header: Option, + spec: &ChainSpec, ) -> Result, warp::Rejection> { let fork_name = block .to_ref() - .fork_name(&chain.spec) + .fork_name(&spec) .map_err(inconsistent_fork_rejection)?; let consensus_block_value_wei = Uint256::from(consensus_block_value) * Uint256::from(1_000_000_000u64);