Files
lighthouse/beacon_node/http_api/src/produce_block.rs
Eitan Seri-Levi eec0700f94 Gloas local block building MVP (#8754)
The flow for local block building is
1. Create execution payload and bid
2. Construct beacon block
3. Sign beacon block and publish
4. Sign execution payload and publish

This PR adds the beacon block v4 flow , GET payload envelope and POST payload envelope (local block building only). The spec for these endpoints can be found here:  https://github.com/ethereum/beacon-APIs/pull/552  and is subject to change.

We needed a way to store the unsigned execution payload envelope associated to the execution payload bid that was included in the block. I introduced a new cache that stores these unsigned execution payload envelopes. the GET payload envelope queries this cache directly so that a proposer, after publishing a block, can fetch the payload envelope + sign and publish it.

I kept payload signing and publishing within the validators block service to keep things simple for now. The idea was to build out a block production MVP for devnet 0, try not to affect any non gloas code paths and build things out in such a way that it will be easy to deprecate pre-gloas code paths later on (for example block production v2 and v3).

We will eventually need to track which beacon node was queried for the block so that we can later query it for the payload. But thats not needed for the devnet.


  


Co-Authored-By: Eitan Seri- Levi <eserilev@gmail.com>

Co-Authored-By: Michael Sproul <michael@sigmaprime.io>

Co-Authored-By: Jimmy Chen <jchen.tc@gmail.com>

Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
2026-02-17 02:09:35 +00:00

315 lines
11 KiB
Rust

use crate::{
build_block_contents,
version::{
ResponseIncludesVersion, add_consensus_block_value_header, add_consensus_version_header,
add_execution_payload_blinded_header, add_execution_payload_value_header,
add_ssz_content_type_header, beacon_response, inconsistent_fork_rejection,
},
};
use beacon_chain::graffiti_calculator::GraffitiSettings;
use beacon_chain::{
BeaconBlockResponseWrapper, BeaconChain, BeaconChainTypes, ProduceBlockVerification,
};
use eth2::types::{self as api_types, ProduceBlockV3Metadata, SkipRandaoVerification};
use eth2::{beacon_response::ForkVersionedResponse, types::ProduceBlockV4Metadata};
use ssz::Encode;
use std::sync::Arc;
use tracing::instrument;
use types::{execution::BlockProductionVersion, *};
use warp::{
Reply,
hyper::{Body, Response},
};
/// If default boost factor is provided in validator/blocks v3 request, we will skip the calculation
/// to keep the precision.
const DEFAULT_BOOST_FACTOR: u64 = 100;
pub fn get_randao_verification(
query: &api_types::ValidatorBlocksQuery,
randao_reveal_infinity: bool,
) -> Result<ProduceBlockVerification, warp::Rejection> {
let randao_verification = if query.skip_randao_verification == SkipRandaoVerification::Yes {
if !randao_reveal_infinity {
return Err(warp_utils::reject::custom_bad_request(
"randao_reveal must be point-at-infinity if verification is skipped".into(),
));
}
ProduceBlockVerification::NoVerification
} else {
ProduceBlockVerification::VerifyRandao
};
Ok(randao_verification)
}
#[instrument(
name = "lh_produce_block_v4",
skip_all,
fields(%slot)
)]
pub async fn produce_block_v4<T: BeaconChainTypes>(
accept_header: Option<api_types::Accept>,
chain: Arc<BeaconChain<T>>,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
) -> Result<Response<Body>, warp::Rejection> {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?;
let builder_boost_factor = if query.builder_boost_factor == Some(DEFAULT_BOOST_FACTOR) {
None
} else {
query.builder_boost_factor
};
let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy);
let (block, consensus_block_value) = chain
.produce_block_with_verification_gloas(
randao_reveal,
slot,
graffiti_settings,
randao_verification,
builder_boost_factor,
)
.await
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("failed to fetch a block: {:?}", e))
})?;
build_response_v4::<T>(block, consensus_block_value, accept_header, &chain.spec)
}
#[instrument(
name = "lh_produce_block_v3",
skip_all,
fields(%slot)
)]
pub async fn produce_block_v3<T: BeaconChainTypes>(
accept_header: Option<api_types::Accept>,
chain: Arc<BeaconChain<T>>,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
) -> Result<Response<Body>, warp::Rejection> {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?;
let builder_boost_factor = if query.builder_boost_factor == Some(DEFAULT_BOOST_FACTOR) {
None
} else {
query.builder_boost_factor
};
let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy);
let block_response_type = chain
.produce_block_with_verification(
randao_reveal,
slot,
graffiti_settings,
randao_verification,
builder_boost_factor,
BlockProductionVersion::V3,
)
.await
.map_err(|e| {
warp_utils::reject::custom_bad_request(format!("failed to fetch a block: {:?}", e))
})?;
build_response_v3(chain, block_response_type, accept_header)
}
pub fn build_response_v4<T: BeaconChainTypes>(
block: BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
consensus_block_value: u64,
accept_header: Option<api_types::Accept>,
spec: &ChainSpec,
) -> Result<Response<Body>, warp::Rejection> {
let fork_name = block
.to_ref()
.fork_name(spec)
.map_err(inconsistent_fork_rejection)?;
let consensus_block_value_wei =
Uint256::from(consensus_block_value) * Uint256::from(1_000_000_000u64);
let metadata = ProduceBlockV4Metadata {
consensus_version: fork_name,
consensus_block_value: consensus_block_value_wei,
};
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.body(block.as_ssz_bytes().into())
.map(|res: Response<Body>| add_ssz_content_type_header(res))
.map(|res: Response<Body>| add_consensus_version_header(res, fork_name))
.map(|res| add_consensus_block_value_header(res, consensus_block_value_wei))
.map_err(|e| -> warp::Rejection {
warp_utils::reject::custom_server_error(format!("failed to create response: {}", e))
}),
_ => Ok(warp::reply::json(&ForkVersionedResponse {
version: fork_name,
metadata,
data: block,
})
.into_response())
.map(|res| add_consensus_version_header(res, fork_name))
.map(|res| add_consensus_block_value_header(res, consensus_block_value_wei)),
}
}
pub fn build_response_v3<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
block_response: BeaconBlockResponseWrapper<T::EthSpec>,
accept_header: Option<api_types::Accept>,
) -> Result<Response<Body>, warp::Rejection> {
let fork_name = block_response
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let execution_payload_value = block_response.execution_payload_value();
let consensus_block_value = block_response.consensus_block_value_wei();
let execution_payload_blinded = block_response.is_blinded();
let metadata = ProduceBlockV3Metadata {
consensus_version: fork_name,
execution_payload_blinded,
execution_payload_value,
consensus_block_value,
};
let block_contents = build_block_contents::build_block_contents(fork_name, block_response)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Body>| add_ssz_content_type_header(res))
.map(|res: Response<Body>| add_consensus_version_header(res, fork_name))
.map(|res| add_execution_payload_blinded_header(res, execution_payload_blinded))
.map(|res: Response<Body>| {
add_execution_payload_value_header(res, execution_payload_value)
})
.map(|res| add_consensus_block_value_header(res, consensus_block_value))
.map_err(|e| -> warp::Rejection {
warp_utils::reject::custom_server_error(format!("failed to create response: {}", e))
}),
_ => Ok(warp::reply::json(&ForkVersionedResponse {
version: fork_name,
metadata,
data: block_contents,
})
.into_response())
.map(|res| res.into_response())
.map(|res| add_consensus_version_header(res, fork_name))
.map(|res| add_execution_payload_blinded_header(res, execution_payload_blinded))
.map(|res| add_execution_payload_value_header(res, execution_payload_value))
.map(|res| add_consensus_block_value_header(res, consensus_block_value)),
}
}
pub async fn produce_blinded_block_v2<T: BeaconChainTypes>(
accept_header: Option<api_types::Accept>,
chain: Arc<BeaconChain<T>>,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
) -> Result<Response<Body>, warp::Rejection> {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?;
let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy);
let block_response_type = chain
.produce_block_with_verification(
randao_reveal,
slot,
graffiti_settings,
randao_verification,
None,
BlockProductionVersion::BlindedV2,
)
.await
.map_err(warp_utils::reject::unhandled_error)?;
build_response_v2(chain, block_response_type, accept_header)
}
#[instrument(
name = "lh_produce_block_v2",
skip_all,
fields(%slot)
)]
pub async fn produce_block_v2<T: BeaconChainTypes>(
accept_header: Option<api_types::Accept>,
chain: Arc<BeaconChain<T>>,
slot: Slot,
query: api_types::ValidatorBlocksQuery,
) -> Result<Response<Body>, warp::Rejection> {
let randao_reveal = query.randao_reveal.decompress().map_err(|e| {
warp_utils::reject::custom_bad_request(format!(
"randao reveal is not a valid BLS signature: {:?}",
e
))
})?;
let randao_verification = get_randao_verification(&query, randao_reveal.is_infinity())?;
let graffiti_settings = GraffitiSettings::new(query.graffiti, query.graffiti_policy);
let block_response_type = chain
.produce_block_with_verification(
randao_reveal,
slot,
graffiti_settings,
randao_verification,
None,
BlockProductionVersion::FullV2,
)
.await
.map_err(warp_utils::reject::unhandled_error)?;
build_response_v2(chain, block_response_type, accept_header)
}
pub fn build_response_v2<T: BeaconChainTypes>(
chain: Arc<BeaconChain<T>>,
block_response: BeaconBlockResponseWrapper<T::EthSpec>,
accept_header: Option<api_types::Accept>,
) -> Result<Response<Body>, warp::Rejection> {
let fork_name = block_response
.fork_name(&chain.spec)
.map_err(inconsistent_fork_rejection)?;
let block_contents = build_block_contents::build_block_contents(fork_name, block_response)?;
match accept_header {
Some(api_types::Accept::Ssz) => Response::builder()
.status(200)
.body(block_contents.as_ssz_bytes().into())
.map(|res: Response<Body>| add_ssz_content_type_header(res))
.map(|res: Response<Body>| add_consensus_version_header(res, fork_name))
.map_err(|e| {
warp_utils::reject::custom_server_error(format!("failed to create response: {}", e))
}),
_ => Ok(warp::reply::json(&beacon_response(
ResponseIncludesVersion::Yes(fork_name),
block_contents,
))
.into_response()),
}
}