Add new block production endpoint

This commit is contained in:
Eitan Seri- Levi
2026-02-03 16:13:07 -08:00
parent 5bb7ebb8de
commit 7cf4eb0396
11 changed files with 844 additions and 231 deletions

View File

@@ -1,9 +1,9 @@
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::Arc;
use std::u64;
use bls::Signature;
use execution_layer::BuilderParams;
use operation_pool::CompactAttestationRef;
use ssz::Encode;
use state_processing::common::get_attesting_indices_from_state;
@@ -22,14 +22,12 @@ use types::{
SyncAggregate,
};
use crate::BeaconBlockResponse;
use crate::{
BeaconChain, BeaconChainError, BeaconChainTypes, BlockProductionError,
ProduceBlockVerification, graffiti_calculator::GraffitiSettings,
BeaconChain, BeaconChainTypes, BlockProductionError, ProduceBlockVerification,
graffiti_calculator::GraffitiSettings, metrics,
};
pub struct PartialBeaconBlock<E: EthSpec> {
state: BeaconState<E>,
slot: Slot,
proposer_index: u64,
parent_root: Hash256,
@@ -46,66 +44,136 @@ pub struct PartialBeaconBlock<E: EthSpec> {
bls_to_execution_changes: Vec<SignedBlsToExecutionChange>,
}
// We'll need to add that once we include trusted/trustless bids
impl<T: BeaconChainTypes> BeaconChain<T> {
pub async fn produce_block_on_bid(
pub async fn produce_block_with_verification_gloas(
self: &Arc<Self>,
randao_reveal: Signature,
slot: Slot,
graffiti_settings: GraffitiSettings,
verification: ProduceBlockVerification,
_builder_boost_factor: Option<u64>,
) -> Result<
(
BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
BeaconState<T::EthSpec>,
u64,
),
BlockProductionError,
> {
metrics::inc_counter(&metrics::BLOCK_PRODUCTION_REQUESTS);
let _complete_timer = metrics::start_timer(&metrics::BLOCK_PRODUCTION_TIMES);
// Part 1/2 (blocking)
//
// Load the parent state from disk.
let chain = self.clone();
let span = Span::current();
let (state, state_root_opt) = self
.task_executor
.spawn_blocking_handle(
move || {
let _guard =
debug_span!(parent: span, "load_state_for_block_production").entered();
chain.load_state_for_block_production(slot)
},
"load_state_for_block_production",
)
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)??;
// Part 2/2 (async, with some blocking components)
//
// Produce the block upon the state
self.produce_block_on_state_gloas(
state,
state_root_opt,
slot,
randao_reveal,
graffiti_settings,
verification,
)
.await
}
// TODO(gloas) need to implement builder boost factor logic
pub async fn produce_block_on_state_gloas(
self: &Arc<Self>,
state: BeaconState<T::EthSpec>,
execution_payload_bid: SignedExecutionPayloadBid<T::EthSpec>,
state_root_opt: Option<Hash256>,
produce_at_slot: Slot,
randao_reveal: Signature,
graffiti_settings: GraffitiSettings,
verification: ProduceBlockVerification,
builder_boost_factor: Option<u64>,
) -> Result<BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>, BlockProductionError> {
) -> Result<
(
BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
BeaconState<T::EthSpec>,
u64,
),
BlockProductionError,
> {
// Part 1/3 (blocking)
//
// Perform the state advance and block-packing functions.
let chain = self.clone();
let graffiti = self
.graffiti_calculator
.get_graffiti(graffiti_settings)
.await;
let span = Span::current();
let mut partial_beacon_block = self
let (partial_beacon_block, state) = self
.task_executor
.spawn_blocking_handle(
move || {
let _guard =
debug_span!(parent: span, "produce_partial_beacon_block").entered();
debug_span!(parent: span, "produce_partial_beacon_block_gloas").entered();
chain.produce_partial_beacon_block_gloas(
state,
state_root_opt,
produce_at_slot,
randao_reveal,
graffiti,
builder_boost_factor,
)
},
"produce_partial_beacon_block",
"produce_partial_beacon_block_gloas",
)
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)??;
// Part 2/3 (async)
//
// Produce the execution payload bid.
// TODO(gloas) this is strictly for building local bids
// We'll need to build out trustless/trusted bid paths.
let (execution_payload_bid, state) = self
.clone()
.produce_execution_payload_bid(state, state_root_opt, produce_at_slot, 0, u64::MAX)
.await?;
// Part 3/3 (blocking)
//
// Complete the block with the execution payload bid.
let chain = self.clone();
let span = Span::current();
let beacon_block_response = self
.task_executor
self.task_executor
.spawn_blocking_handle(
move || {
let _guard =
debug_span!(parent: span, "complete_partial_beacon_block").entered();
debug_span!(parent: span, "complete_partial_beacon_block_gloas").entered();
chain.complete_partial_beacon_block_gloas(
partial_beacon_block,
execution_payload_bid,
state,
verification,
)
},
"complete_partial_beacon_block",
"complete_partial_beacon_block_gloas",
)
.ok_or(BlockProductionError::ShuttingDown)?
.await
.map_err(BlockProductionError::TokioJoin)??;
todo!()
.map_err(BlockProductionError::TokioJoin)?
}
#[allow(clippy::too_many_arguments)]
@@ -116,8 +184,8 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
produce_at_slot: Slot,
randao_reveal: Signature,
graffiti: Graffiti,
builder_boost_factor: Option<u64>,
) -> Result<PartialBeaconBlock<T::EthSpec>, BlockProductionError> {
) -> Result<(PartialBeaconBlock<T::EthSpec>, BeaconState<T::EthSpec>), BlockProductionError>
{
// It is invalid to try to produce a block using a state from a future slot.
if state.slot() > produce_at_slot {
return Err(BlockProductionError::StateSlotTooHigh {
@@ -148,22 +216,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
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)))?,
};
let slashings_and_exits_span = debug_span!("get_slashings_and_exits").entered();
let (mut proposer_slashings, mut attester_slashings, mut voluntary_exits) =
self.op_pool.get_slashings_and_exits(&state, &self.spec);
@@ -339,30 +391,33 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
Some(sync_aggregate)
};
Ok(PartialBeaconBlock {
Ok((
PartialBeaconBlock {
slot,
proposer_index,
parent_root,
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
// TODO(gloas) need to implement payload attestations
payload_attestations: vec![],
bls_to_execution_changes,
},
state,
slot,
proposer_index,
parent_root,
randao_reveal,
eth1_data,
graffiti,
proposer_slashings,
attester_slashings,
attestations,
deposits,
voluntary_exits,
sync_aggregate,
// TODO(gloas) need to implement payload attestations
payload_attestations: vec![],
bls_to_execution_changes,
})
))
}
fn complete_partial_beacon_block_gloas(
&self,
partial_beacon_block: PartialBeaconBlock<T::EthSpec>,
signed_execution_payload_bid: SignedExecutionPayloadBid<T::EthSpec>,
mut state: BeaconState<T::EthSpec>,
verification: ProduceBlockVerification,
) -> Result<
(
@@ -373,7 +428,6 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
BlockProductionError,
> {
let PartialBeaconBlock {
mut state,
slot,
proposer_index,
parent_root,
@@ -392,36 +446,39 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
let beacon_block = match &state {
BeaconState::Base(_) => {
(
// TODO(gloas) this should be an error
todo!()
)
return Err(BlockProductionError::InvalidBlockVariant(
"Cannot construct a block pre-Gloas".to_owned(),
));
}
BeaconState::Altair(_) => {
(
// TODO(gloas) this should be an error
todo!()
)
return Err(BlockProductionError::InvalidBlockVariant(
"Cannot construct a block pre-Gloas".to_owned(),
));
}
BeaconState::Bellatrix(_) => {
// TODO(gloas) this should be an error
todo!()
return Err(BlockProductionError::InvalidBlockVariant(
"Cannot construct a block pre-Gloas".to_owned(),
));
}
BeaconState::Capella(_) => {
// TODO(gloas) this should be an error
todo!()
return Err(BlockProductionError::InvalidBlockVariant(
"Cannot construct a block pre-Gloas".to_owned(),
));
}
BeaconState::Deneb(_) => {
// TODO(gloas) this should be an error
todo!()
return Err(BlockProductionError::InvalidBlockVariant(
"Cannot construct a block pre-Gloas".to_owned(),
));
}
BeaconState::Electra(_) => {
// TODO(gloas) this should be an error
todo!()
return Err(BlockProductionError::InvalidBlockVariant(
"Cannot construct a block pre-Gloas".to_owned(),
));
}
BeaconState::Fulu(_) => {
// TODO(gloas) this should be an error
todo!()
return Err(BlockProductionError::InvalidBlockVariant(
"Cannot construct a block pre-Gloas".to_owned(),
));
}
BeaconState::Gloas(_) => BeaconBlock::Gloas(BeaconBlockGloas {
slot,

View File

@@ -4601,7 +4601,7 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
/// Load a beacon state from the database for block production. This is a long-running process
/// that should not be performed in an `async` context.
fn load_state_for_block_production(
pub fn load_state_for_block_production(
self: &Arc<Self>,
slot: Slot,
) -> Result<(BeaconState<T::EthSpec>, Option<Hash256>), BlockProductionError> {

View File

@@ -310,6 +310,7 @@ pub enum BlockProductionError {
MissingSyncAggregate,
MissingExecutionPayload,
MissingKzgCommitment(String),
MissingStateRoot,
TokioJoin(JoinError),
BeaconChain(Box<BeaconChainError>),
InvalidPayloadFork,

View File

@@ -1,12 +1,11 @@
use std::sync::Arc;
use std::{sync::Arc, u64};
use bls::Signature;
use execution_layer::{BlockProposalContentsType, BuilderParams};
use ssz_types::VariableList;
use state_processing::state_advance::complete_state_advance;
use tracing::instrument;
use types::{
Address, BeaconState, BlockProductionVersion, BuilderIndex, ExecutionPayload,
ExecutionPayloadBid, Hash256, ProposerPreferences, Slot,
Address, BeaconState, BlockProductionVersion, BuilderIndex, ExecutionPayloadBid, Hash256,
SignedExecutionPayloadBid, Slot,
};
use crate::{
@@ -15,34 +14,30 @@ use crate::{
};
impl<T: BeaconChainTypes> BeaconChain<T> {
// 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.
#[instrument(level = "debug", skip_all)]
pub async fn produce_execution_payload_bid(
self: &Arc<Self>,
mut state: BeaconState<T::EthSpec>,
state_root: Hash256,
execution_payload: ExecutionPayload<T::EthSpec>,
self: Arc<Self>,
state: BeaconState<T::EthSpec>,
state_root_opt: Option<Hash256>,
produce_at_slot: Slot,
proposer_preferences: Option<ProposerPreferences>,
bid_value: u64,
builder_index: BuilderIndex,
value: u64,
) -> Result<ExecutionPayloadBid<T::EthSpec>, BlockProductionError> {
// It is invalid to try to produce a block using a state from a future slot.
if state.slot() > produce_at_slot {
return Err(BlockProductionError::StateSlotTooHigh {
produce_at_slot,
state_slot: state.slot(),
});
}
// TODO(gloas) add sanity check on value
) -> Result<
(
SignedExecutionPayloadBid<T::EthSpec>,
BeaconState<T::EthSpec>,
),
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
// Ensure the state has performed a complete transition into the required slot.
complete_state_advance(&mut state, Some(state_root), produce_at_slot, &self.spec)?;
let parent_root = if state.slot() > 0 {
*state
.get_block_root(state.slot() - 1)
@@ -82,79 +77,63 @@ impl<T: BeaconChainTypes> BeaconChain<T> {
BlockProductionVersion::V3,
)?;
let block_contents_type_option = Some(
prepare_payload_handle
.await
.map_err(BlockProductionError::TokioJoin)?
.ok_or(BlockProductionError::ShuttingDown)??,
);
let block_contents_type = prepare_payload_handle
.await
.map_err(BlockProductionError::TokioJoin)?
.ok_or(BlockProductionError::ShuttingDown)??;
let blob_kzg_commitments = if let Some(block_contents_type) = block_contents_type_option {
match block_contents_type {
BlockProposalContentsType::Full(block_proposal_contents) => {
let blob_kzg_commitments =
block_proposal_contents.blob_kzg_commitments().cloned();
let (execution_payload, blob_kzg_commitments) = match block_contents_type {
BlockProposalContentsType::Full(block_proposal_contents) => {
let blob_kzg_commitments =
block_proposal_contents.blob_kzg_commitments().cloned();
if let Some(blob_kzg_commitments) = blob_kzg_commitments {
blob_kzg_commitments
} 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);
if let Some(blob_kzg_commitments) = blob_kzg_commitments {
(
block_proposal_contents.to_payload().execution_payload(),
blob_kzg_commitments,
)
} else {
return Err(BlockProductionError::MissingKzgCommitment(
"No KZG commitments from the payload".to_owned(),
));
}
}
} else {
todo!()
// 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 bid = if let Some(proposer_preferences) = proposer_preferences
&& proposer_preferences.proposal_slot == produce_at_slot
{
// Trustless bid
ExecutionPayloadBid::<T::EthSpec> {
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: proposer_preferences.fee_recipient,
// TODO(gloas) payload construction should factor in the proposers gas limit preferences
gas_limit: execution_payload.gas_limit(),
builder_index,
slot: produce_at_slot,
value,
execution_payment: 0,
blob_kzg_commitments,
}
} else if builder_index == u64::MAX {
// Local bid
ExecutionPayloadBid::<T::EthSpec> {
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,
execution_payment: 0,
blob_kzg_commitments,
}
} else {
// No proposer preferences and this isn't local building
// TODO(gloas) this should return a specific error type
// i.e if proposer prefs are missing and its a trustless bid
// return an error that communicates that.
return Err(BlockProductionError::GloasNotImplemented);
let state_root = state_root_opt.ok_or_else(|| {
BlockProductionError::MissingStateRoot
})?;
let bid = ExecutionPayloadBid::<T::EthSpec> {
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,
};
Ok(bid)
// 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,
))
}
}