Skip to content

Commit 72007d4

Browse files
authored
feat(l1): engine_getBlobsV1 request endpoint (#3636)
**Motivation** We need to implement the RPC endpoint as it will be needed for Fusaka. **Description** This pr incorporates a new module to handle RPC endpoint for the **engine_getBlobsV1** request according to the following spec's description from [here](https://ethereum.github.io/execution-apis/api-documentation/) and [here](https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#engine_getblobsv1). To check the messages are sent correctly you can set the `fixtures/network/network_params.yaml` like this: ```bash participants: # NOTE: Both erigon and geth work on this example, but they provide wrong nodes information on discovery protocol # - el_type: erigon # el_image: ethpandaops/erigon:main-764a2c50 # cl_type: lighthouse # cl_image: sigp/lighthouse:v7.0.0-beta.0 # validator_count: 32 # - el_type: reth # el_image: ghcr.io/paradigmxyz/reth:v1.2.2 # cl_type: lighthouse # cl_image: sigp/lighthouse:v7.0.0-beta.0 # validator_count: 32 - el_type: besu el_image: ethpandaops/besu:main-142a5e6 cl_type: lighthouse cl_image: sigp/lighthouse:v7.0.0-beta.0 validator_count: 32 - el_type: geth el_image: ethereum/client-go:v1.15.2 cl_type: lighthouse cl_image: sigp/lighthouse:v7.0.0-beta.0 validator_count: 32 count: 1 - el_type: ethrex cl_type: lighthouse cl_image: sigp/lighthouse:v7.0.0-beta.0 validator_count: 32 snooper_enabled: true network_params: electra_fork_epoch: 1 # The address of the staking contract address on the Eth1 chain deposit_contract_address: "0x4242424242424242424242424242424242424242" ethereum_metrics_exporter_enabled: true additional_services: - dora - forkmon - spamoor blockscout_params: image: "blockscout/blockscout:latest" verif_image: "ghcr.io/blockscout/smart-contract-verifier:latest" frontend_image: "ghcr.io/blockscout/frontend:latest" prometheus_params: # TODO: switch to latest when it points to v3.x image: "prom/prometheus:v3.2.1" ``` Then run in a console: ```bash make localnet ``` And run in another console: ```bash docker logs -f $(docker ps -q --filter name=snooper-engine-3-lighthouse-ethrex) ``` This last console will show all the rpc requests and the responses. It's overwhelming. Here I left some pictures of my logs: <img width="785" height="411" alt="Screenshot 2025-07-22 at 16 07 22" src="https://github.com/user-attachments/assets/474ee749-0721-4ae6-8934-fc8c00fae70d" /> <img width="855" height="195" alt="Screenshot 2025-07-22 at 16 07 42" src="https://github.com/user-attachments/assets/be879e7c-244c-4130-9ae9-c4e2976f513d" /> <img width="898" height="155" alt="Screenshot 2025-07-22 at 16 07 59" src="https://github.com/user-attachments/assets/7f8922a0-077f-4361-9eed-2b42dd89dd99" /> <img width="843" height="144" alt="Screenshot 2025-07-22 at 16 08 18" src="https://github.com/user-attachments/assets/eb7e1246-1aba-417f-8217-34cbfec6b95c" /> <img width="861" height="129" alt="Screenshot 2025-07-22 at 16 08 34" src="https://github.com/user-attachments/assets/eee3c6c5-17fd-44f8-8e14-6bff8aba4a63" /> <img width="831" height="117" alt="Screenshot 2025-07-22 at 16 08 52" src="https://github.com/user-attachments/assets/3f450e2a-cd8d-4374-b87c-9f83107c09c3" /> <img width="830" height="105" alt="Screenshot 2025-07-22 at 16 09 18" src="https://github.com/user-attachments/assets/b7a8c40a-02c2-44f3-8f96-129a682a5046" /> I haven't found a better way of testing it. I think you can check it with the dora service but I'm not sure how. Closes #3428
1 parent cc1346e commit 72007d4

File tree

7 files changed

+136
-4
lines changed

7 files changed

+136
-4
lines changed

cmd/ethrex_replay/src/constants.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub fn make_chainconfig(chain_id: u64) -> ChainConfig {
3333
prague_time: Some(0),
3434
terminal_total_difficulty_passed: false,
3535
verkle_time: None,
36+
osaka_time: None,
3637
blob_schedule: BlobSchedule::default(),
3738
// Mainnet address
3839
deposit_contract_address: H160::from_str("0x00000000219ab540356cbb839cbe05303d7705fa")

crates/blockchain/mempool.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ impl Mempool {
232232
.collect())
233233
}
234234

235+
/// Returns all blobs bundles currently in the pool
236+
pub fn get_blobs_bundle_pool(&self) -> Result<Vec<BlobsBundle>, MempoolError> {
237+
let blobs_bundle_pool = self
238+
.blobs_bundle_pool
239+
.lock()
240+
.map_err(|error| StoreError::MempoolReadLock(error.to_string()))?;
241+
Ok(blobs_bundle_pool.values().cloned().collect())
242+
}
243+
235244
/// Returns the status of the mempool, which is the number of transactions currently in
236245
/// the pool. Until we add "queue" transactions.
237246
pub fn status(&self) -> Result<usize, MempoolError> {

crates/common/serde_utils.rs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,13 @@ pub mod bool {
388388
pub mod bytes48 {
389389
use super::*;
390390

391+
pub fn serialize<S>(value: &[u8; 48], serializer: S) -> Result<S::Ok, S::Error>
392+
where
393+
S: Serializer,
394+
{
395+
serializer.serialize_str(&format!("0x{}", hex::encode(value)))
396+
}
397+
391398
pub mod vec {
392399
use super::*;
393400

@@ -424,10 +431,16 @@ pub mod bytes48 {
424431

425432
pub mod blob {
426433
use super::*;
434+
use crate::types::BYTES_PER_BLOB;
427435

428-
pub mod vec {
429-
use crate::types::BYTES_PER_BLOB;
436+
pub fn serialize<S>(value: &[u8; BYTES_PER_BLOB], serializer: S) -> Result<S::Ok, S::Error>
437+
where
438+
S: Serializer,
439+
{
440+
serializer.serialize_str(&format!("0x{}", hex::encode(value)))
441+
}
430442

443+
pub mod vec {
431444
use super::*;
432445

433446
pub fn serialize<S>(

crates/common/types/genesis.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,7 @@ pub struct ChainConfig {
186186
pub cancun_time: Option<u64>,
187187
pub prague_time: Option<u64>,
188188
pub verkle_time: Option<u64>,
189+
pub osaka_time: Option<u64>,
189190

190191
/// Amount of total difficulty reached by the network that triggers the consensus upgrade.
191192
pub terminal_total_difficulty: Option<u128>,
@@ -253,6 +254,10 @@ impl From<Fork> for &str {
253254
}
254255

255256
impl ChainConfig {
257+
pub fn is_osaka_activated(&self, block_timestamp: u64) -> bool {
258+
self.osaka_time.is_some_and(|time| time <= block_timestamp)
259+
}
260+
256261
pub fn is_prague_activated(&self, block_timestamp: u64) -> bool {
257262
self.prague_time.is_some_and(|time| time <= block_timestamp)
258263
}
@@ -279,7 +284,9 @@ impl ChainConfig {
279284
}
280285

281286
pub fn get_fork(&self, block_timestamp: u64) -> Fork {
282-
if self.is_prague_activated(block_timestamp) {
287+
if self.is_osaka_activated(block_timestamp) {
288+
Fork::Osaka
289+
} else if self.is_prague_activated(block_timestamp) {
283290
Fork::Prague
284291
} else if self.is_cancun_activated(block_timestamp) {
285292
Fork::Cancun

crates/networking/rpc/engine/blobs.rs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
use ethrex_common::{
2+
H256,
3+
serde_utils::{self},
4+
types::{Blob, Proof, blobs_bundle::kzg_commitment_to_versioned_hash},
5+
};
6+
use serde::{Deserialize, Serialize};
7+
use serde_json::Value;
8+
use tracing::info;
9+
10+
use crate::{
11+
rpc::{RpcApiContext, RpcHandler},
12+
utils::RpcErr,
13+
};
14+
15+
// -> https://github.com/ethereum/execution-apis/blob/d41fdf10fabbb73c4d126fb41809785d830acace/src/engine/cancun.md?plain=1#L186
16+
const GET_BLOBS_V1_REQUEST_MAX_SIZE: usize = 128;
17+
18+
#[derive(Debug, Serialize, Deserialize)]
19+
pub struct BlobsV1Request {
20+
blob_versioned_hashes: Vec<H256>,
21+
}
22+
23+
#[derive(Clone, Debug, Serialize)]
24+
#[serde(rename_all = "camelCase")]
25+
pub struct BlobAndProofV1 {
26+
#[serde(with = "serde_utils::blob")]
27+
pub blob: Blob,
28+
#[serde(with = "serde_utils::bytes48")]
29+
pub proof: Proof,
30+
}
31+
32+
impl RpcHandler for BlobsV1Request {
33+
fn parse(params: &Option<Vec<Value>>) -> Result<Self, RpcErr> {
34+
let params = params
35+
.as_ref()
36+
.ok_or(RpcErr::BadParams("No params provided".to_owned()))?;
37+
if params.len() != 1 {
38+
return Err(RpcErr::BadParams("Expected 1 param".to_owned()));
39+
};
40+
Ok(BlobsV1Request {
41+
blob_versioned_hashes: serde_json::from_value(params[0].clone())?,
42+
})
43+
}
44+
45+
async fn handle(&self, context: RpcApiContext) -> Result<Value, RpcErr> {
46+
info!("Received new engine request: Requested Blobs");
47+
if self.blob_versioned_hashes.len() >= GET_BLOBS_V1_REQUEST_MAX_SIZE {
48+
return Err(RpcErr::TooLargeRequest);
49+
}
50+
51+
if let Some(current_block_header) = context
52+
.storage
53+
.get_block_header(context.storage.get_latest_block_number().await?)?
54+
{
55+
if context
56+
.storage
57+
.get_chain_config()?
58+
.is_osaka_activated(current_block_header.timestamp)
59+
{
60+
// validation requested in https://github.com/ethereum/execution-apis/blob/a1d95fb555cd91efb3e0d6555e4ab556d9f5dd06/src/engine/osaka.md?plain=1#L130
61+
return Err(RpcErr::UnsuportedFork(
62+
"getBlobsV1 engine request not supported for Osaka".to_string(),
63+
));
64+
}
65+
};
66+
67+
let mut res: Vec<Option<BlobAndProofV1>> = vec![None; self.blob_versioned_hashes.len()];
68+
69+
for blobs_bundle in context.blockchain.mempool.get_blobs_bundle_pool()? {
70+
// Go over all blobs bundles from the blobs bundle pool.
71+
let blobs_in_bundle = blobs_bundle.blobs;
72+
let commitments_in_bundle = blobs_bundle.commitments;
73+
let proofs_in_bundle = blobs_bundle.proofs;
74+
75+
// Go over all the commitments in each blobs bundle to calculate the blobs versioned hash.
76+
for (commitment, (blob, proof)) in commitments_in_bundle
77+
.iter()
78+
.zip(blobs_in_bundle.iter().zip(proofs_in_bundle.iter()))
79+
{
80+
let current_versioned_hash = kzg_commitment_to_versioned_hash(commitment);
81+
if let Some(index) = self
82+
.blob_versioned_hashes
83+
.iter()
84+
.position(|&hash| hash == current_versioned_hash)
85+
{
86+
// If the versioned hash is one of the requested we save its corresponding blob and proof in the returned vector. We store them in the same position as the versioned hash was received.
87+
res[index] = Some(BlobAndProofV1 {
88+
blob: *blob,
89+
proof: *proof,
90+
});
91+
}
92+
}
93+
}
94+
95+
serde_json::to_value(res).map_err(|error| RpcErr::Internal(error.to_string()))
96+
}
97+
}

crates/networking/rpc/engine/mod.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod blobs;
12
pub mod exchange_transition_config;
23
pub mod fork_choice;
34
pub mod payload;
@@ -13,7 +14,7 @@ pub type ExchangeCapabilitiesRequest = Vec<String>;
1314

1415
/// List of capabilities that the execution layer client supports. Add new capabilities here.
1516
/// More info: https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#engine_exchangecapabilities
16-
pub const CAPABILITIES: [&str; 14] = [
17+
pub const CAPABILITIES: [&str; 15] = [
1718
"engine_forkchoiceUpdatedV1",
1819
"engine_forkchoiceUpdatedV2",
1920
"engine_forkchoiceUpdatedV3",
@@ -28,6 +29,7 @@ pub const CAPABILITIES: [&str; 14] = [
2829
"engine_exchangeTransitionConfigurationV1",
2930
"engine_getPayloadBodiesByHashV1",
3031
"engine_getPayloadBodiesByRangeV1",
32+
"engine_getBlobsV1",
3133
];
3234

3335
impl From<ExchangeCapabilitiesRequest> for RpcRequest {

crates/networking/rpc/rpc.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::authentication::authenticate;
22
use crate::engine::{
33
ExchangeCapabilitiesRequest,
4+
blobs::BlobsV1Request,
45
exchange_transition_config::ExchangeTransitionConfigV1Req,
56
fork_choice::{ForkChoiceUpdatedV1, ForkChoiceUpdatedV2, ForkChoiceUpdatedV3},
67
payload::{
@@ -376,6 +377,7 @@ pub async fn map_engine_requests(
376377
"engine_getPayloadBodiesByRangeV1" => {
377378
GetPayloadBodiesByRangeV1Request::call(req, context).await
378379
}
380+
"engine_getBlobsV1" => BlobsV1Request::call(req, context).await,
379381
unknown_engine_method => Err(RpcErr::MethodNotFound(unknown_engine_method.to_owned())),
380382
}
381383
}
@@ -509,6 +511,7 @@ mod tests {
509511
"cancunTime": 0,
510512
"pragueTime": 1718232101,
511513
"verkleTime": null,
514+
"osakaTime": null,
512515
"terminalTotalDifficulty": 0,
513516
"terminalTotalDifficultyPassed": true,
514517
"blobSchedule": blob_schedule,

0 commit comments

Comments
 (0)