feat(engine): add getPayloadBodiesV2 endpoints for EIP-7928 BAL support (#21774)

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Georgios Konstantopoulos
2026-02-10 10:10:05 -05:00
committed by GitHub
parent b20a99e1c9
commit b25b8c00ee
4 changed files with 125 additions and 45 deletions

View File

@@ -11,9 +11,9 @@ use alloy_eips::{
use alloy_json_rpc::RpcObject;
use alloy_primitives::{Address, BlockHash, Bytes, B256, U256, U64};
use alloy_rpc_types_engine::{
ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadInputV2, ExecutionPayloadV1,
ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId,
PayloadStatus,
ClientVersionV1, ExecutionPayloadBodiesV1, ExecutionPayloadBodiesV2, ExecutionPayloadInputV2,
ExecutionPayloadV1, ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated,
PayloadId, PayloadStatus,
};
use alloy_rpc_types_eth::{
state::StateOverride, BlockOverrides, EIP1186AccountProofResponse, Filter, Log, SyncStatus,
@@ -211,6 +211,17 @@ pub trait EngineApi<Engine: EngineTypes> {
block_hashes: Vec<BlockHash>,
) -> RpcResult<ExecutionPayloadBodiesV1>;
/// Returns `ExecutionPayloadBodyV2` objects for the given block hashes.
///
/// V2 includes the `block_access_list` field for EIP-7928 BAL support.
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
#[method(name = "getPayloadBodiesByHashV2")]
async fn get_payload_bodies_by_hash_v2(
&self,
block_hashes: Vec<BlockHash>,
) -> RpcResult<ExecutionPayloadBodiesV2>;
/// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#engine_getpayloadbodiesbyrangev1>
///
/// Returns the execution payload bodies by the range starting at `start`, containing `count`
@@ -230,6 +241,26 @@ pub trait EngineApi<Engine: EngineTypes> {
count: U64,
) -> RpcResult<ExecutionPayloadBodiesV1>;
/// Returns `ExecutionPayloadBodyV2` objects for the given block range.
///
/// V2 includes the `block_access_list` field for EIP-7928 BAL support.
///
/// WARNING: This method is associated with the `BeaconBlocksByRange` message in the consensus
/// layer p2p specification, meaning the input should be treated as untrusted or potentially
/// adversarial.
///
/// Implementers should take care when acting on the input to this method, specifically
/// ensuring that the range is limited properly, and that the range boundaries are computed
/// correctly and without panics.
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
#[method(name = "getPayloadBodiesByRangeV2")]
async fn get_payload_bodies_by_range_v2(
&self,
start: U64,
count: U64,
) -> RpcResult<ExecutionPayloadBodiesV2>;
/// This function will return the [`ClientVersionV1`] object.
/// See also:
/// <https://github.com/ethereum/execution-apis/blob/03911ffc053b8b806123f1fc237184b0092a485a/src/engine/identification.md#engine_getclientversionv1>
@@ -278,18 +309,6 @@ pub trait EngineApi<Engine: EngineTypes> {
&self,
versioned_hashes: Vec<B256>,
) -> RpcResult<Option<Vec<Option<BlobAndProofV2>>>>;
/// Returns the Block Access Lists for the given block hashes.
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
#[method(name = "getBALsByHashV1")]
async fn get_bals_by_hash_v1(&self, block_hashes: Vec<BlockHash>) -> RpcResult<Vec<Bytes>>;
/// Returns the Block Access Lists for the given block range.
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
#[method(name = "getBALsByRangeV1")]
async fn get_bals_by_range_v1(&self, start: U64, count: U64) -> RpcResult<Vec<Bytes>>;
}
/// A subset of the ETH rpc interface: <https://ethereum.github.io/execution-apis/api-documentation>

View File

@@ -28,7 +28,9 @@ pub const CAPABILITIES: &[&str] = &[
"engine_newPayloadV3",
"engine_newPayloadV4",
"engine_getPayloadBodiesByHashV1",
"engine_getPayloadBodiesByHashV2",
"engine_getPayloadBodiesByRangeV1",
"engine_getPayloadBodiesByRangeV2",
"engine_getBlobsV1",
"engine_getBlobsV2",
"engine_getBlobsV3",

View File

@@ -10,9 +10,10 @@ use alloy_eips::{
use alloy_primitives::{BlockHash, BlockNumber, B256, U64};
use alloy_rpc_types_engine::{
CancunPayloadFields, ClientVersionV1, ExecutionData, ExecutionPayloadBodiesV1,
ExecutionPayloadBodyV1, ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1,
ExecutionPayloadV3, ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId,
PayloadStatus, PraguePayloadFields,
ExecutionPayloadBodiesV2, ExecutionPayloadBodyV1, ExecutionPayloadBodyV2,
ExecutionPayloadInputV2, ExecutionPayloadSidecar, ExecutionPayloadV1, ExecutionPayloadV3,
ExecutionPayloadV4, ForkchoiceState, ForkchoiceUpdated, PayloadId, PayloadStatus,
PraguePayloadFields,
};
use async_trait::async_trait;
use jsonrpsee_core::{server::RpcModule, RpcResult};
@@ -612,6 +613,34 @@ where
res
}
/// Returns the execution payload bodies by the range (V2).
///
/// V2 includes the `block_access_list` field for EIP-7928 BAL support.
pub async fn get_payload_bodies_by_range_v2(
&self,
start: BlockNumber,
count: u64,
) -> EngineApiResult<ExecutionPayloadBodiesV2> {
self.get_payload_bodies_by_range_with(start, count, |block| ExecutionPayloadBodyV2 {
transactions: block.body().encoded_2718_transactions(),
withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
block_access_list: None,
})
.await
}
/// Metrics version of `get_payload_bodies_by_range_v2`
pub async fn get_payload_bodies_by_range_v2_metered(
&self,
start: BlockNumber,
count: u64,
) -> EngineApiResult<ExecutionPayloadBodiesV2> {
let start_time = Instant::now();
let res = Self::get_payload_bodies_by_range_v2(self, start, count).await;
self.inner.metrics.latency.get_payload_bodies_by_range_v2.record(start_time.elapsed());
res
}
/// Called to retrieve execution payload bodies by hashes.
pub async fn get_payload_bodies_by_hash_with<F, R>(
&self,
@@ -673,6 +702,32 @@ where
res
}
/// Called to retrieve execution payload bodies by hashes (V2).
///
/// V2 includes the `block_access_list` field for EIP-7928 BAL support.
pub async fn get_payload_bodies_by_hash_v2(
&self,
hashes: Vec<BlockHash>,
) -> EngineApiResult<ExecutionPayloadBodiesV2> {
self.get_payload_bodies_by_hash_with(hashes, |block| ExecutionPayloadBodyV2 {
transactions: block.body().encoded_2718_transactions(),
withdrawals: block.body().withdrawals().cloned().map(Withdrawals::into_inner),
block_access_list: None,
})
.await
}
/// Metrics version of `get_payload_bodies_by_hash_v2`
pub async fn get_payload_bodies_by_hash_v2_metered(
&self,
hashes: Vec<BlockHash>,
) -> EngineApiResult<ExecutionPayloadBodiesV2> {
let start = Instant::now();
let res = Self::get_payload_bodies_by_hash_v2(self, hashes).await;
self.inner.metrics.latency.get_payload_bodies_by_hash_v2.record(start.elapsed());
res
}
/// Validates the `engine_forkchoiceUpdated` payload attributes and executes the forkchoice
/// update.
///
@@ -1129,6 +1184,19 @@ where
Ok(self.get_payload_bodies_by_hash_v1_metered(block_hashes).await?)
}
/// Handler for `engine_getPayloadBodiesByHashV2`
///
/// V2 includes the `block_access_list` field for EIP-7928 BAL support.
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
async fn get_payload_bodies_by_hash_v2(
&self,
block_hashes: Vec<BlockHash>,
) -> RpcResult<ExecutionPayloadBodiesV2> {
trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByHashV2");
Ok(self.get_payload_bodies_by_hash_v2_metered(block_hashes).await?)
}
/// Handler for `engine_getPayloadBodiesByRangeV1`
///
/// See also <https://github.com/ethereum/execution-apis/blob/6452a6b194d7db269bf1dbd087a267251d3cc7f8/src/engine/shanghai.md#engine_getpayloadbodiesbyrangev1>
@@ -1154,6 +1222,20 @@ where
Ok(self.get_payload_bodies_by_range_v1_metered(start.to(), count.to()).await?)
}
/// Handler for `engine_getPayloadBodiesByRangeV2`
///
/// V2 includes the `block_access_list` field for EIP-7928 BAL support.
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
async fn get_payload_bodies_by_range_v2(
&self,
start: U64,
count: U64,
) -> RpcResult<ExecutionPayloadBodiesV2> {
trace!(target: "rpc::engine", "Serving engine_getPayloadBodiesByRangeV2");
Ok(self.get_payload_bodies_by_range_v2_metered(start.to(), count.to()).await?)
}
/// Handler for `engine_getClientVersionV1`
///
/// See also <https://github.com/ethereum/execution-apis/blob/03911ffc053b8b806123f1fc237184b0092a485a/src/engine/identification.md>
@@ -1199,33 +1281,6 @@ where
trace!(target: "rpc::engine", "Serving engine_getBlobsV3");
Ok(self.get_blobs_v3_metered(versioned_hashes)?)
}
/// Handler for `engine_getBALsByHashV1`
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
async fn get_bals_by_hash_v1(
&self,
_block_hashes: Vec<BlockHash>,
) -> RpcResult<Vec<alloy_primitives::Bytes>> {
trace!(target: "rpc::engine", "Serving engine_getBALsByHashV1");
Err(EngineApiError::EngineObjectValidationError(
reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
))?
}
/// Handler for `engine_getBALsByRangeV1`
///
/// See also <https://eips.ethereum.org/EIPS/eip-7928>
async fn get_bals_by_range_v1(
&self,
_start: U64,
_count: U64,
) -> RpcResult<Vec<alloy_primitives::Bytes>> {
trace!(target: "rpc::engine", "Serving engine_getBALsByRangeV1");
Err(EngineApiError::EngineObjectValidationError(
reth_payload_primitives::EngineObjectValidationError::UnsupportedFork,
))?
}
}
impl<Provider, EngineT, Pool, Validator, ChainSpec> IntoEngineApiRpcModule

View File

@@ -40,8 +40,12 @@ pub(crate) struct EngineApiLatencyMetrics {
pub(crate) get_payload_v5: Histogram,
/// Latency for `engine_getPayloadBodiesByRangeV1`
pub(crate) get_payload_bodies_by_range_v1: Histogram,
/// Latency for `engine_getPayloadBodiesByRangeV2`
pub(crate) get_payload_bodies_by_range_v2: Histogram,
/// Latency for `engine_getPayloadBodiesByHashV1`
pub(crate) get_payload_bodies_by_hash_v1: Histogram,
/// Latency for `engine_getPayloadBodiesByHashV2`
pub(crate) get_payload_bodies_by_hash_v2: Histogram,
/// Latency for `engine_getBlobsV1`
pub(crate) get_blobs_v1: Histogram,
/// Latency for `engine_getBlobsV2`