Skip to content

Commit

Permalink
feat(l1): implement RPC engine_getPayloadBodies methods (#1737)
Browse files Browse the repository at this point in the history
**Motivation**

In order to pass more Hive tests, we have to implement these two missing
RPC methods.

**Description**

This PR implements the engine RPC methods
`engine_getPayloadBodiesByRangeV1` and
`engine_getPayloadBodiesByHashV1`.

The following hive tests from the suite `engine-withdrawals` are now
passing:

* GetPayloadBodiesByRange
* GetPayloadBodies After Sync
* GetPayloadBodiesByRange (Sidechain)
* GetPayloadBodiesByRange (Empty Transactions/Withdrawals)
* GetPayloadBodiesByHash
* GetPayloadBodiesByHash (Empty Transactions/Withdrawals)
* GetPayloadBodies Parallel

You can run those tests only by executing this command:

```bash
make run-hive SIMULATION=ethereum/engine TEST_PATTERN="engine-withdrawals/GetPayloadBodies"
```

Closes #1548 #1549

---------

Co-authored-by: Julian Ventura <[email protected]>
  • Loading branch information
JulianVentura and Julian Ventura authored Jan 17, 2025
1 parent e8a402a commit 1926d8b
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci_l1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ jobs:
test_pattern: "engine-api/RPC|Re-Org Back to Canonical Chain From Syncing Chain|Re-org to Previously Validated Sidechain Payload|Re-Org Back into Canonical Chain, Depth=5|Safe Re-Org|Transaction Re-Org|Inconsistent|Suggested Fee|PrevRandao|Fork ID|Unknown|Invalid PayloadAttributes|Bad Hash|Unique Payload ID|Re-Execute Payload|In-Order|Multiple New Payloads|Valid NewPayload|NewPayload with|Invalid NewPayload|Payload Build|Invalid NewPayload, Transaction|ParentHash equals|Build Payload|Invalid Missing Ancestor ReOrg"
- name: "Engine withdrawal tests"
simulation: ethereum/engine
test_pattern: "engine-withdrawals/engine-withdrawals test loader|GetPayloadV2 Block Value|Sync after 2 blocks - Withdrawals on Genesis|Max Initcode Size|Pre-Merge Fork Number > 0|Empty Withdrawals|Corrupted Block Hash Payload|Withdrawals Fork on Block 2|Withdrawals Fork on Block 3"
test_pattern: "engine-withdrawals/engine-withdrawals test loader|GetPayloadV2 Block Value|Sync after 2 blocks - Withdrawals on Genesis|Max Initcode Size|Pre-Merge Fork Number > 0|Empty Withdrawals|Corrupted Block Hash Payload|Withdrawals Fork on Block 2|Withdrawals Fork on Block 3|GetPayloadBodies"
- name: "Sync"
simulation: ethereum/sync
test_pattern: ""
Expand Down
89 changes: 86 additions & 3 deletions crates/networking/rpc/engine/payload.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
use ethrex_blockchain::add_block;
use ethrex_blockchain::error::ChainError;
use ethrex_blockchain::payload::build_payload;
use ethrex_core::types::{BlobsBundle, Block, Fork};
use ethrex_core::types::{BlobsBundle, Block, BlockBody, BlockHash, BlockNumber, Fork};
use ethrex_core::{H256, U256};
use serde_json::Value;
use tracing::{error, info, warn};

use crate::types::payload::{ExecutionPayload, ExecutionPayloadResponse, PayloadStatus};
use crate::utils::RpcRequest;
use crate::types::payload::{
ExecutionPayload, ExecutionPayloadBody, ExecutionPayloadResponse, PayloadStatus,
};
use crate::utils::{parse_json_hex, RpcRequest};
use crate::{RpcApiContext, RpcErr, RpcHandler};

// Must support rquest sizes of at least 32 blocks
// Chosen an arbitrary x4 value
// -> https://github.com/ethereum/execution-apis/blob/main/src/engine/shanghai.md#specification-3
const GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE: usize = 128;

// NewPayload V1-V2-V3 implementations
pub struct NewPayloadV1Request {
pub payload: ExecutionPayload,
Expand Down Expand Up @@ -190,6 +197,82 @@ impl RpcHandler for GetPayloadV3Request {
}
}

pub struct GetPayloadBodiesByHashV1Request {
pub hashes: Vec<BlockHash>,
}

impl RpcHandler for GetPayloadBodiesByHashV1Request {
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 1 {
return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
};

Ok(GetPayloadBodiesByHashV1Request {
hashes: serde_json::from_value(params[0].clone())?,
})
}

fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
if self.hashes.len() >= GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE {
return Err(RpcErr::TooLargeRequest);
}
let bodies = self
.hashes
.iter()
.map(|hash| context.storage.get_block_body_by_hash(*hash))
.collect::<Result<Vec<Option<BlockBody>>, _>>()?;
build_payload_body_response(bodies)
}
}

pub struct GetPayloadBodiesByRangeV1Request {
start: BlockNumber,
count: u64,
}

impl RpcHandler for GetPayloadBodiesByRangeV1Request {
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
let params = params
.as_ref()
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
if params.len() != 2 {
return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
};
let start = parse_json_hex(&params[0]).map_err(|_| RpcErr::BadHexFormat(0))?;
let count = parse_json_hex(&params[1]).map_err(|_| RpcErr::BadHexFormat(1))?;
if start < 1 {
return Err(RpcErr::WrongParam("start".to_owned()));
}
if count < 1 {
return Err(RpcErr::WrongParam("count".to_owned()));
}
Ok(GetPayloadBodiesByRangeV1Request { start, count })
}

fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
if self.count as usize >= GET_PAYLOAD_BODIES_REQUEST_MAX_SIZE {
return Err(RpcErr::TooLargeRequest);
}
let latest_block_number = context.storage.get_latest_block_number()?;
let last = latest_block_number.min(self.start + self.count - 1);
let bodies = (self.start..=last)
.map(|block_num| context.storage.get_block_body(block_num))
.collect::<Result<Vec<Option<BlockBody>>, _>>()?;
build_payload_body_response(bodies)
}
}

fn build_payload_body_response(bodies: Vec<Option<BlockBody>>) -> Result<Value, RpcErr> {
let response: Vec<Option<ExecutionPayloadBody>> = bodies
.into_iter()
.map(|body| body.map(Into::into))
.collect();
serde_json::to_value(response).map_err(|error| RpcErr::Internal(error.to_string()))
}

fn parse_execution_payload(params: &Option<Vec<Value>>) -> Result<ExecutionPayload, RpcErr> {
let params = params
.as_ref()
Expand Down
7 changes: 5 additions & 2 deletions crates/networking/rpc/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ use engine::{
exchange_transition_config::ExchangeTransitionConfigV1Req,
fork_choice::{ForkChoiceUpdatedV1, ForkChoiceUpdatedV2, ForkChoiceUpdatedV3},
payload::{
GetPayloadV1Request, GetPayloadV2Request, GetPayloadV3Request, NewPayloadV1Request,
NewPayloadV2Request, NewPayloadV3Request,
GetPayloadBodiesByHashV1Request, GetPayloadBodiesByRangeV1Request, GetPayloadV1Request,
GetPayloadV2Request, GetPayloadV3Request, NewPayloadV1Request, NewPayloadV2Request,
NewPayloadV3Request,
},
ExchangeCapabilitiesRequest,
};
Expand Down Expand Up @@ -271,6 +272,8 @@ pub fn map_engine_requests(req: &RpcRequest, context: RpcApiContext) -> Result<V
"engine_getPayloadV3" => GetPayloadV3Request::call(req, context),
"engine_getPayloadV2" => GetPayloadV2Request::call(req, context),
"engine_getPayloadV1" => GetPayloadV1Request::call(req, context),
"engine_getPayloadBodiesByHashV1" => GetPayloadBodiesByHashV1Request::call(req, context),
"engine_getPayloadBodiesByRangeV1" => GetPayloadBodiesByRangeV1Request::call(req, context),
unknown_engine_method => Err(RpcErr::MethodNotFound(unknown_engine_method.to_owned())),
}
}
Expand Down
20 changes: 20 additions & 0 deletions crates/networking/rpc/types/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,26 @@ impl PayloadStatus {
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecutionPayloadBody {
pub transactions: Vec<EncodedTransaction>,
pub withdrawals: Option<Vec<Withdrawal>>,
}

impl From<BlockBody> for ExecutionPayloadBody {
fn from(body: BlockBody) -> Self {
Self {
transactions: body
.transactions
.iter()
.map(EncodedTransaction::encode)
.collect(),
withdrawals: body.withdrawals,
}
}
}

#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ExecutionPayloadResponse {
Expand Down
6 changes: 6 additions & 0 deletions crates/networking/rpc/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub enum RpcErr {
WrongParam(String),
BadParams(String),
MissingParam(String),
TooLargeRequest,
BadHexFormat(u64),
UnsuportedFork(String),
Internal(String),
Expand Down Expand Up @@ -47,6 +48,11 @@ impl From<RpcErr> for RpcErrorMetadata {
data: None,
message: format!("Expected parameter: {parameter_name} is missing"),
},
RpcErr::TooLargeRequest => RpcErrorMetadata {
code: -38004,
data: None,
message: "Too large request".to_string(),
},
RpcErr::UnsuportedFork(context) => RpcErrorMetadata {
code: -38005,
data: None,
Expand Down

0 comments on commit 1926d8b

Please sign in to comment.