chore: remove builder_index from produce_block_v4 (#9267)

Part of #8828 for the stateful path and helps align gloas `produceBlockV4` with beacon-APIs [PR](https://github.com/ethereum/beacon-APIs/pull/580)


  - Plumb `include_payload` query through the handler. Ignored for now since stateless mode isn't wired up yet
- Add `execution_payload_included` metadata field + `Eth-Execution-Payload-Included` header per spec. Both `false` until stateless lands
- Drop the `{builder_index}` segment from the envelope GET URL since no longer included in spec


Co-Authored-By: shane-moore <skm1790@gmail.com>

Co-Authored-By: Eitan Seri-Levi <eserilev@ucsc.edu>
This commit is contained in:
Shane K Moore
2026-05-11 08:27:41 -07:00
committed by GitHub
parent 1b921a64e6
commit 2208e17937
7 changed files with 76 additions and 36 deletions

View File

@@ -2,8 +2,9 @@ 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,
add_execution_payload_blinded_header, add_execution_payload_included_header,
add_execution_payload_value_header, add_ssz_content_type_header, beacon_response,
inconsistent_fork_rejection,
},
};
use beacon_chain::graffiti_calculator::GraffitiSettings;
@@ -83,7 +84,16 @@ pub async fn produce_block_v4<T: BeaconChainTypes>(
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)
// TODO(gloas): wire up for stateless mode (#8828).
let execution_payload_included = false;
build_response_v4::<T>(
block,
consensus_block_value,
execution_payload_included,
accept_header,
&chain.spec,
)
}
#[instrument(
@@ -133,6 +143,7 @@ pub async fn produce_block_v3<T: BeaconChainTypes>(
pub fn build_response_v4<T: BeaconChainTypes>(
block: BeaconBlock<T::EthSpec, FullPayload<T::EthSpec>>,
consensus_block_value: u64,
execution_payload_included: bool,
accept_header: Option<api_types::Accept>,
spec: &ChainSpec,
) -> Result<Response<Body>, warp::Rejection> {
@@ -146,6 +157,7 @@ pub fn build_response_v4<T: BeaconChainTypes>(
let metadata = ProduceBlockV4Metadata {
consensus_version: fork_name,
consensus_block_value: consensus_block_value_wei,
execution_payload_included,
};
match accept_header {
@@ -155,6 +167,7 @@ pub fn build_response_v4<T: BeaconChainTypes>(
.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(|res| add_execution_payload_included_header(res, execution_payload_included))
.map_err(|e| -> warp::Rejection {
warp_utils::reject::custom_server_error(format!("failed to create response: {}", e))
}),
@@ -165,7 +178,8 @@ pub fn build_response_v4<T: BeaconChainTypes>(
})
.into_response())
.map(|res| add_consensus_version_header(res, fork_name))
.map(|res| add_consensus_block_value_header(res, consensus_block_value_wei)),
.map(|res| add_consensus_block_value_header(res, consensus_block_value_wei))
.map(|res| add_execution_payload_included_header(res, execution_payload_included)),
}
}

View File

@@ -12,7 +12,7 @@ use types::Slot;
use warp::http::Response;
use warp::{Filter, Rejection};
// GET validator/execution_payload_envelope/{slot}/{builder_index}
// GET validator/execution_payload_envelope/{slot}
pub fn get_validator_execution_payload_envelope<T: BeaconChainTypes>(
eth_v1: EthV1Filter,
chain_filter: ChainFilter<T>,
@@ -27,11 +27,6 @@ pub fn get_validator_execution_payload_envelope<T: BeaconChainTypes>(
"Invalid slot".to_string(),
))
}))
.and(warp::path::param::<u64>().or_else(|_| async {
Err(warp_utils::reject::custom_bad_request(
"Invalid builder_index".to_string(),
))
}))
.and(warp::path::end())
.and(warp::header::optional::<Accept>("accept"))
.and(not_while_syncing_filter)
@@ -39,10 +34,6 @@ pub fn get_validator_execution_payload_envelope<T: BeaconChainTypes>(
.and(chain_filter)
.then(
|slot: Slot,
// TODO(gloas) we're only doing local building
// we'll need to implement builder index logic
// eventually.
_builder_index: u64,
accept_header: Option<Accept>,
not_synced_filter: Result<(), Rejection>,
task_spawner: TaskSpawner<T::EthSpec>,

View File

@@ -5,7 +5,8 @@ use eth2::beacon_response::{
};
use eth2::{
CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, CONTENT_TYPE_HEADER,
EXECUTION_PAYLOAD_BLINDED_HEADER, EXECUTION_PAYLOAD_VALUE_HEADER, SSZ_CONTENT_TYPE_HEADER,
EXECUTION_PAYLOAD_BLINDED_HEADER, EXECUTION_PAYLOAD_INCLUDED_HEADER,
EXECUTION_PAYLOAD_VALUE_HEADER, SSZ_CONTENT_TYPE_HEADER,
};
use serde::Serialize;
use types::{ForkName, InconsistentFork, Uint256};
@@ -88,6 +89,19 @@ pub fn add_execution_payload_blinded_header<T: Reply>(
.into_response()
}
/// Add the `Eth-Execution-Payload-Included` header to a response.
pub fn add_execution_payload_included_header<T: Reply>(
reply: T,
execution_payload_included: bool,
) -> Response {
reply::with_header(
reply,
EXECUTION_PAYLOAD_INCLUDED_HEADER,
execution_payload_included.to_string(),
)
.into_response()
}
/// Add the `Eth-Execution-Payload-Value` header to a response.
pub fn add_execution_payload_value_header<T: Reply>(
reply: T,

View File

@@ -4288,6 +4288,7 @@ impl ApiTester {
);
// TODO(gloas): check why consensus block value is 0
// assert!(!metadata.consensus_block_value.is_zero());
assert!(!metadata.execution_payload_included);
let block_root = block.tree_hash_root();
let envelope = self
@@ -4360,7 +4361,7 @@ impl ApiTester {
let (response, metadata) = self
.client
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None)
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None, None)
.await
.unwrap();
let block = response.data;
@@ -4369,7 +4370,7 @@ impl ApiTester {
let envelope = self
.client
.get_validator_execution_payload_envelope::<E>(slot, BUILDER_INDEX_SELF_BUILD)
.get_validator_execution_payload_envelope::<E>(slot)
.await
.unwrap()
.data;
@@ -4423,7 +4424,7 @@ impl ApiTester {
let (block, metadata) = self
.client
.get_validator_blocks_v4_ssz::<E>(slot, &randao_reveal, None, None, None)
.get_validator_blocks_v4_ssz::<E>(slot, &randao_reveal, None, None, None, None)
.await
.unwrap();
@@ -4431,7 +4432,7 @@ impl ApiTester {
let envelope = self
.client
.get_validator_execution_payload_envelope_ssz::<E>(slot, BUILDER_INDEX_SELF_BUILD)
.get_validator_execution_payload_envelope_ssz::<E>(slot)
.await
.unwrap();
@@ -4861,7 +4862,7 @@ impl ApiTester {
// Produce and publish a block.
let (response, _metadata) = self
.client
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None)
.get_validator_blocks_v4::<E>(slot, &randao_reveal, None, None, None, None)
.await
.unwrap();
let block = response.data;
@@ -4878,7 +4879,7 @@ impl ApiTester {
// Retrieve and publish the envelope.
let envelope = self
.client
.get_validator_execution_payload_envelope::<E>(slot, BUILDER_INDEX_SELF_BUILD)
.get_validator_execution_payload_envelope::<E>(slot)
.await
.unwrap()
.data;

View File

@@ -56,6 +56,7 @@ pub const V4: EndpointVersion = EndpointVersion(4);
pub const CONSENSUS_VERSION_HEADER: &str = "Eth-Consensus-Version";
pub const EXECUTION_PAYLOAD_BLINDED_HEADER: &str = "Eth-Execution-Payload-Blinded";
pub const EXECUTION_PAYLOAD_VALUE_HEADER: &str = "Eth-Execution-Payload-Value";
pub const EXECUTION_PAYLOAD_INCLUDED_HEADER: &str = "Eth-Execution-Payload-Included";
pub const CONSENSUS_BLOCK_VALUE_HEADER: &str = "Eth-Consensus-Block-Value";
pub const CONTENT_TYPE_HEADER: &str = "Content-Type";
@@ -2554,12 +2555,14 @@ impl BeaconNodeHttpClient {
}
/// returns `GET v4/validator/blocks/{slot}` URL path
#[allow(clippy::too_many_arguments)]
pub async fn get_validator_blocks_v4_path(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
include_payload: Option<bool>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<Url, Error> {
@@ -2584,6 +2587,11 @@ impl BeaconNodeHttpClient {
.append_pair("skip_randao_verification", "");
}
if let Some(include_payload) = include_payload {
path.query_pairs_mut()
.append_pair("include_payload", &include_payload.to_string());
}
if let Some(builder_booster_factor) = builder_booster_factor {
path.query_pairs_mut()
.append_pair("builder_boost_factor", &builder_booster_factor.to_string());
@@ -2603,6 +2611,7 @@ impl BeaconNodeHttpClient {
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
include_payload: Option<bool>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<
@@ -2617,6 +2626,7 @@ impl BeaconNodeHttpClient {
randao_reveal,
graffiti,
SkipRandaoVerification::No,
include_payload,
builder_booster_factor,
graffiti_policy,
)
@@ -2624,12 +2634,14 @@ impl BeaconNodeHttpClient {
}
/// `GET v4/validator/blocks/{slot}`
#[allow(clippy::too_many_arguments)]
pub async fn get_validator_blocks_v4_modular<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
include_payload: Option<bool>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<
@@ -2645,6 +2657,7 @@ impl BeaconNodeHttpClient {
randao_reveal,
graffiti,
skip_randao_verification,
include_payload,
builder_booster_factor,
graffiti_policy,
)
@@ -2675,6 +2688,7 @@ impl BeaconNodeHttpClient {
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
include_payload: Option<bool>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(BeaconBlock<E>, ProduceBlockV4Metadata), Error> {
@@ -2683,6 +2697,7 @@ impl BeaconNodeHttpClient {
randao_reveal,
graffiti,
SkipRandaoVerification::No,
include_payload,
builder_booster_factor,
graffiti_policy,
)
@@ -2690,12 +2705,14 @@ impl BeaconNodeHttpClient {
}
/// `GET v4/validator/blocks/{slot}` in ssz format
#[allow(clippy::too_many_arguments)]
pub async fn get_validator_blocks_v4_modular_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
include_payload: Option<bool>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(BeaconBlock<E>, ProduceBlockV4Metadata), Error> {
@@ -2705,6 +2722,7 @@ impl BeaconNodeHttpClient {
randao_reveal,
graffiti,
skip_randao_verification,
include_payload,
builder_booster_factor,
graffiti_policy,
)
@@ -2734,11 +2752,10 @@ impl BeaconNodeHttpClient {
opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
}
/// `GET v1/validator/execution_payload_envelope/{slot}/{builder_index}`
/// `GET v1/validator/execution_payload_envelope/{slot}`
pub async fn get_validator_execution_payload_envelope<E: EthSpec>(
&self,
slot: Slot,
builder_index: u64,
) -> Result<ForkVersionedResponse<ExecutionPayloadEnvelope<E>>, Error> {
let mut path = self.eth_path(V1)?;
@@ -2746,17 +2763,15 @@ impl BeaconNodeHttpClient {
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("execution_payload_envelope")
.push(&slot.to_string())
.push(&builder_index.to_string());
.push(&slot.to_string());
self.get(path).await
}
/// `GET v1/validator/execution_payload_envelope/{slot}/{builder_index}` in SSZ format
/// `GET v1/validator/execution_payload_envelope/{slot}` in SSZ format
pub async fn get_validator_execution_payload_envelope_ssz<E: EthSpec>(
&self,
slot: Slot,
builder_index: u64,
) -> Result<ExecutionPayloadEnvelope<E>, Error> {
let mut path = self.eth_path(V1)?;
@@ -2764,8 +2779,7 @@ impl BeaconNodeHttpClient {
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("execution_payload_envelope")
.push(&slot.to_string())
.push(&builder_index.to_string());
.push(&slot.to_string());
let opt_response = self
.get_bytes_opt_accept_header(path, Accept::Ssz, self.timeouts.get_validator_block)

View File

@@ -5,7 +5,7 @@ pub use types::*;
use crate::{
CONSENSUS_BLOCK_VALUE_HEADER, CONSENSUS_VERSION_HEADER, EXECUTION_PAYLOAD_BLINDED_HEADER,
EXECUTION_PAYLOAD_VALUE_HEADER, Error as ServerError,
EXECUTION_PAYLOAD_INCLUDED_HEADER, EXECUTION_PAYLOAD_VALUE_HEADER, Error as ServerError,
};
use bls::{PublicKeyBytes, SecretKey, Signature, SignatureBytes};
use context_deserialize::ContextDeserialize;
@@ -778,6 +778,7 @@ pub struct ValidatorBlocksQuery {
pub randao_reveal: SignatureBytes,
pub graffiti: Option<Graffiti>,
pub skip_randao_verification: SkipRandaoVerification,
pub include_payload: Option<bool>,
pub builder_boost_factor: Option<u64>,
pub graffiti_policy: Option<GraffitiPolicy>,
}
@@ -1848,6 +1849,7 @@ pub struct ProduceBlockV4Metadata {
pub consensus_version: ForkName,
#[serde(with = "serde_utils::u256_dec")]
pub consensus_block_value: Uint256,
pub execution_payload_included: bool,
}
impl<E: EthSpec> FullBlockContents<E> {
@@ -2021,10 +2023,16 @@ impl TryFrom<&HeaderMap> for ProduceBlockV4Metadata {
Uint256::from_str_radix(s, 10)
.map_err(|e| format!("invalid {CONSENSUS_BLOCK_VALUE_HEADER}: {e:?}"))
})?;
let execution_payload_included =
parse_required_header(headers, EXECUTION_PAYLOAD_INCLUDED_HEADER, |s| {
s.parse::<bool>()
.map_err(|e| format!("invalid {EXECUTION_PAYLOAD_INCLUDED_HEADER}: {e:?}"))
})?;
Ok(ProduceBlockV4Metadata {
consensus_version,
consensus_block_value,
execution_payload_included,
})
}
}

View File

@@ -14,7 +14,6 @@ use std::time::Duration;
use task_executor::TaskExecutor;
use tokio::sync::mpsc;
use tracing::{Instrument, debug, error, info, info_span, instrument, trace, warn};
use types::consts::gloas::BUILDER_INDEX_SELF_BUILD;
use types::{BlockType, ChainSpec, EthSpec, Graffiti, Slot};
use validator_store::{Error as ValidatorStoreError, SignedBlock, UnsignedBlock, ValidatorStore};
@@ -479,6 +478,7 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
slot,
randao_reveal_ref,
graffiti.as_ref(),
None,
builder_boost_factor,
self_ref.graffiti_policy,
)
@@ -506,6 +506,7 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
slot,
randao_reveal_ref,
graffiti.as_ref(),
None,
builder_boost_factor,
self_ref.graffiti_policy,
)
@@ -652,16 +653,13 @@ impl<S: ValidatorStore + 'static, T: SlotClock + 'static> BlockService<S, T> {
) -> Result<(), BlockError> {
info!(slot = slot.as_u64(), "Fetching execution payload envelope");
// Fetch the envelope from the beacon node. Use builder_index=BUILDER_INDEX_SELF_BUILD for local building.
// Fetch the envelope from the beacon node.
// TODO(gloas): Use proposer_fallback once multi-BN is supported.
let envelope = self
.beacon_nodes
.first_success(|beacon_node| async move {
beacon_node
.get_validator_execution_payload_envelope_ssz::<S::E>(
slot,
BUILDER_INDEX_SELF_BUILD,
)
.get_validator_execution_payload_envelope_ssz::<S::E>(slot)
.await
.map_err(|e| {
BlockError::Recoverable(format!(