From b25b8c00ee1db8b507fb92762b22f67dc0410789 Mon Sep 17 00:00:00 2001 From: Georgios Konstantopoulos Date: Tue, 10 Feb 2026 10:10:05 -0500 Subject: [PATCH] feat(engine): add getPayloadBodiesV2 endpoints for EIP-7928 BAL support (#21774) Co-authored-by: Amp --- crates/rpc/rpc-api/src/engine.rs | 49 +++++--- crates/rpc/rpc-engine-api/src/capabilities.rs | 2 + crates/rpc/rpc-engine-api/src/engine_api.rs | 115 +++++++++++++----- crates/rpc/rpc-engine-api/src/metrics.rs | 4 + 4 files changed, 125 insertions(+), 45 deletions(-) diff --git a/crates/rpc/rpc-api/src/engine.rs b/crates/rpc/rpc-api/src/engine.rs index 520058f0bb..9cdbc7b3e9 100644 --- a/crates/rpc/rpc-api/src/engine.rs +++ b/crates/rpc/rpc-api/src/engine.rs @@ -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 { block_hashes: Vec, ) -> RpcResult; + /// Returns `ExecutionPayloadBodyV2` objects for the given block hashes. + /// + /// V2 includes the `block_access_list` field for EIP-7928 BAL support. + /// + /// See also + #[method(name = "getPayloadBodiesByHashV2")] + async fn get_payload_bodies_by_hash_v2( + &self, + block_hashes: Vec, + ) -> RpcResult; + /// See also /// /// Returns the execution payload bodies by the range starting at `start`, containing `count` @@ -230,6 +241,26 @@ pub trait EngineApi { count: U64, ) -> RpcResult; + /// 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 + #[method(name = "getPayloadBodiesByRangeV2")] + async fn get_payload_bodies_by_range_v2( + &self, + start: U64, + count: U64, + ) -> RpcResult; + /// This function will return the [`ClientVersionV1`] object. /// See also: /// @@ -278,18 +309,6 @@ pub trait EngineApi { &self, versioned_hashes: Vec, ) -> RpcResult>>>; - - /// Returns the Block Access Lists for the given block hashes. - /// - /// See also - #[method(name = "getBALsByHashV1")] - async fn get_bals_by_hash_v1(&self, block_hashes: Vec) -> RpcResult>; - - /// Returns the Block Access Lists for the given block range. - /// - /// See also - #[method(name = "getBALsByRangeV1")] - async fn get_bals_by_range_v1(&self, start: U64, count: U64) -> RpcResult>; } /// A subset of the ETH rpc interface: diff --git a/crates/rpc/rpc-engine-api/src/capabilities.rs b/crates/rpc/rpc-engine-api/src/capabilities.rs index e13d7d3611..093fc70a6f 100644 --- a/crates/rpc/rpc-engine-api/src/capabilities.rs +++ b/crates/rpc/rpc-engine-api/src/capabilities.rs @@ -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", diff --git a/crates/rpc/rpc-engine-api/src/engine_api.rs b/crates/rpc/rpc-engine-api/src/engine_api.rs index 4c0eeed026..1abf4be911 100644 --- a/crates/rpc/rpc-engine-api/src/engine_api.rs +++ b/crates/rpc/rpc-engine-api/src/engine_api.rs @@ -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 { + 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 { + 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( &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, + ) -> EngineApiResult { + 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, + ) -> EngineApiResult { + 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 + async fn get_payload_bodies_by_hash_v2( + &self, + block_hashes: Vec, + ) -> RpcResult { + 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 @@ -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 + async fn get_payload_bodies_by_range_v2( + &self, + start: U64, + count: U64, + ) -> RpcResult { + 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 @@ -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 - async fn get_bals_by_hash_v1( - &self, - _block_hashes: Vec, - ) -> RpcResult> { - trace!(target: "rpc::engine", "Serving engine_getBALsByHashV1"); - Err(EngineApiError::EngineObjectValidationError( - reth_payload_primitives::EngineObjectValidationError::UnsupportedFork, - ))? - } - - /// Handler for `engine_getBALsByRangeV1` - /// - /// See also - async fn get_bals_by_range_v1( - &self, - _start: U64, - _count: U64, - ) -> RpcResult> { - trace!(target: "rpc::engine", "Serving engine_getBALsByRangeV1"); - Err(EngineApiError::EngineObjectValidationError( - reth_payload_primitives::EngineObjectValidationError::UnsupportedFork, - ))? - } } impl IntoEngineApiRpcModule diff --git a/crates/rpc/rpc-engine-api/src/metrics.rs b/crates/rpc/rpc-engine-api/src/metrics.rs index d2ec2d3e08..eaf9e4b3e4 100644 --- a/crates/rpc/rpc-engine-api/src/metrics.rs +++ b/crates/rpc/rpc-engine-api/src/metrics.rs @@ -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`