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

@@ -50,6 +50,7 @@ use std::time::Duration;
pub const V1: EndpointVersion = EndpointVersion(1);
pub const V2: EndpointVersion = EndpointVersion(2);
pub const V3: EndpointVersion = EndpointVersion(3);
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";
@@ -2399,6 +2400,175 @@ impl BeaconNodeHttpClient {
opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
}
/// returns `GET v4/validator/blocks/{slot}` URL path
pub async fn get_validator_blocks_v4_path(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<Url, Error> {
let mut path = self.eth_path(V4)?;
path.path_segments_mut()
.map_err(|()| Error::InvalidUrl(self.server.clone()))?
.push("validator")
.push("blocks")
.push(&slot.to_string());
path.query_pairs_mut()
.append_pair("randao_reveal", &randao_reveal.to_string());
if let Some(graffiti) = graffiti {
path.query_pairs_mut()
.append_pair("graffiti", &graffiti.to_string());
}
if skip_randao_verification == SkipRandaoVerification::Yes {
path.query_pairs_mut()
.append_pair("skip_randao_verification", "");
}
if let Some(builder_booster_factor) = builder_booster_factor {
path.query_pairs_mut()
.append_pair("builder_boost_factor", &builder_booster_factor.to_string());
}
if let Some(GraffitiPolicy::AppendClientVersions) = graffiti_policy {
path.query_pairs_mut()
.append_pair("graffiti_policy", "AppendClientVersions");
}
Ok(path)
}
/// `GET v4/validator/blocks/{slot}`
pub async fn get_validator_blocks_v4<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(ForkVersionedResponse<BeaconBlock<E>, ProduceBlockV4Metadata>, ProduceBlockV4Metadata), Error> {
self.get_validator_blocks_v4_modular(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
graffiti_policy,
)
.await
}
/// `GET v4/validator/blocks/{slot}`
pub async fn get_validator_blocks_v4_modular<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(ForkVersionedResponse<BeaconBlock<E>, ProduceBlockV4Metadata>, ProduceBlockV4Metadata), Error> {
let path = self
.get_validator_blocks_v4_path(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
builder_booster_factor,
graffiti_policy,
)
.await?;
let opt_result = self
.get_response_with_response_headers(
path,
Accept::Json,
self.timeouts.get_validator_block,
|response, headers| async move {
let header_metadata = ProduceBlockV4Metadata::try_from(&headers)
.map_err(Error::InvalidHeaders)?;
let block_response = response
.json::<ForkVersionedResponse<BeaconBlock<E>, ProduceBlockV4Metadata>>()
.await?;
Ok((block_response, header_metadata))
},
)
.await?;
opt_result.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
}
/// `GET v4/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_v4_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(BeaconBlock<E>, ProduceBlockV4Metadata), Error> {
self.get_validator_blocks_v4_modular_ssz::<E>(
slot,
randao_reveal,
graffiti,
SkipRandaoVerification::No,
builder_booster_factor,
graffiti_policy,
)
.await
}
/// `GET v4/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_v4_modular_ssz<E: EthSpec>(
&self,
slot: Slot,
randao_reveal: &SignatureBytes,
graffiti: Option<&Graffiti>,
skip_randao_verification: SkipRandaoVerification,
builder_booster_factor: Option<u64>,
graffiti_policy: Option<GraffitiPolicy>,
) -> Result<(BeaconBlock<E>, ProduceBlockV4Metadata), Error> {
let path = self
.get_validator_blocks_v4_path(
slot,
randao_reveal,
graffiti,
skip_randao_verification,
builder_booster_factor,
graffiti_policy,
)
.await?;
let opt_response = self
.get_response_with_response_headers(
path,
Accept::Ssz,
self.timeouts.get_validator_block,
|response, headers| async move {
let metadata = ProduceBlockV4Metadata::try_from(&headers)
.map_err(Error::InvalidHeaders)?;
let response_bytes = response.bytes().await?;
let block = BeaconBlock::from_ssz_bytes_for_fork(
&response_bytes,
metadata.consensus_version,
)
.map_err(Error::InvalidSsz)?;
Ok((block, metadata))
},
)
.await?;
opt_response.ok_or(Error::StatusCode(StatusCode::NOT_FOUND))
}
/// `GET v2/validator/blocks/{slot}` in ssz format
pub async fn get_validator_blocks_ssz<E: EthSpec>(
&self,

View File

@@ -1742,6 +1742,21 @@ pub struct ProduceBlockV3Metadata {
pub consensus_block_value: Uint256,
}
/// Metadata about a `ProduceBlockV3Response` which is returned in the body & headers.
#[derive(Debug, Deserialize, Serialize)]
pub struct ProduceBlockV4Metadata {
// The consensus version is serialized & deserialized by `ForkVersionedResponse`.
#[serde(
skip_serializing,
skip_deserializing,
default = "dummy_consensus_version"
)]
pub consensus_version: ForkName,
#[serde(with = "serde_utils::u256_dec")]
pub consensus_block_value: Uint256,
}
impl<E: EthSpec> FullBlockContents<E> {
pub fn new(block: BeaconBlock<E>, blob_data: Option<(KzgProofs<E>, BlobsList<E>)>) -> Self {
match blob_data {
@@ -1898,6 +1913,27 @@ impl TryFrom<&HeaderMap> for ProduceBlockV3Metadata {
}
}
impl TryFrom<&HeaderMap> for ProduceBlockV4Metadata {
type Error = String;
fn try_from(headers: &HeaderMap) -> Result<Self, Self::Error> {
let consensus_version = parse_required_header(headers, CONSENSUS_VERSION_HEADER, |s| {
s.parse::<ForkName>()
.map_err(|e| format!("invalid {CONSENSUS_VERSION_HEADER}: {e:?}"))
})?;
let consensus_block_value =
parse_required_header(headers, CONSENSUS_BLOCK_VALUE_HEADER, |s| {
Uint256::from_str_radix(s, 10)
.map_err(|e| format!("invalid {CONSENSUS_BLOCK_VALUE_HEADER}: {e:?}"))
})?;
Ok(ProduceBlockV4Metadata {
consensus_version,
consensus_block_value,
})
}
}
/// A wrapper over a [`SignedBeaconBlock`] or a [`SignedBlockContents`].
#[derive(Clone, Debug, PartialEq, Encode, Serialize)]
#[serde(untagged)]