From c6fce4303a327275964b30316ac10f706421cff4 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Mon, 14 Jul 2025 18:59:46 -0300 Subject: [PATCH 01/16] endpoint implemented --- crates/networking/rpc/engine/blobs.rs | 67 +++++++++++++++++++++++++++ crates/networking/rpc/engine/mod.rs | 4 +- crates/networking/rpc/rpc.rs | 2 + 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 crates/networking/rpc/engine/blobs.rs diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs new file mode 100644 index 0000000000..3fe8543f25 --- /dev/null +++ b/crates/networking/rpc/engine/blobs.rs @@ -0,0 +1,67 @@ +use ethrex_common::{ + H256, serde_utils, + types::{Blob, Proof}, +}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; +use tracing::info; + +use crate::{ + rpc::{RpcApiContext, RpcHandler}, + utils::RpcErr, +}; + +// -> https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#specification-3 +const GET_BLOBS_V1_REQUEST_MAX_SIZE: usize = 128; + +#[derive(Debug, Serialize, Deserialize)] +pub struct BlobsV1Request { + blob_versioned_hashes: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BlobsV1Response { + #[serde(with = "serde_utils::blob::vec")] + pub blobs: Vec, + #[serde(with = "serde_utils::bytes48::vec")] + pub proofs: Vec, +} + +impl std::fmt::Display for BlobsV1Request { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Blob versioned hashes: {:#?}", + self.blob_versioned_hashes + ) + } +} + +impl RpcHandler for BlobsV1Request { + fn parse(params: &Option>) -> Result { + 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(BlobsV1Request { + blob_versioned_hashes: serde_json::from_value(params[0].clone())?, + }) + } + + async fn handle(&self, context: RpcApiContext) -> Result { + info!("Received new engine request: {self}"); + if self.blob_versioned_hashes.len() >= GET_BLOBS_V1_REQUEST_MAX_SIZE { + return Err(RpcErr::TooLargeRequest); + } + + let mut blobs_bundles = Vec::new(); + for hash in self.blob_versioned_hashes.iter() { + blobs_bundles.push(context.blockchain.mempool.get_blobs_bundle(*hash)?) + } + + serde_json::to_value(blobs_bundles).map_err(|error| RpcErr::Internal(error.to_string())) + } +} diff --git a/crates/networking/rpc/engine/mod.rs b/crates/networking/rpc/engine/mod.rs index 9686fc4ea5..97038d15f4 100644 --- a/crates/networking/rpc/engine/mod.rs +++ b/crates/networking/rpc/engine/mod.rs @@ -1,3 +1,4 @@ +pub mod blobs; pub mod exchange_transition_config; pub mod fork_choice; pub mod payload; @@ -13,7 +14,7 @@ pub type ExchangeCapabilitiesRequest = Vec; /// List of capabilities that the execution layer client supports. Add new capabilities here. /// More info: https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#engine_exchangecapabilities -pub const CAPABILITIES: [&str; 14] = [ +pub const CAPABILITIES: [&str; 15] = [ "engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV2", "engine_forkchoiceUpdatedV3", @@ -28,6 +29,7 @@ pub const CAPABILITIES: [&str; 14] = [ "engine_exchangeTransitionConfigurationV1", "engine_getPayloadBodiesByHashV1", "engine_getPayloadBodiesByRangeV1", + "engine_getBlobsV1", ]; impl From for RpcRequest { diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index aeebfe182e..fedca7730f 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -1,6 +1,7 @@ use crate::authentication::authenticate; use crate::engine::{ ExchangeCapabilitiesRequest, + blobs::BlobsV1Request, exchange_transition_config::ExchangeTransitionConfigV1Req, fork_choice::{ForkChoiceUpdatedV1, ForkChoiceUpdatedV2, ForkChoiceUpdatedV3}, payload::{ @@ -372,6 +373,7 @@ pub async fn map_engine_requests( "engine_getPayloadBodiesByRangeV1" => { GetPayloadBodiesByRangeV1Request::call(req, context).await } + "engine_getBlobsV1" => BlobsV1Request::call(req, context).await, unknown_engine_method => Err(RpcErr::MethodNotFound(unknown_engine_method.to_owned())), } } From e53c7c171846dc6094563e90ad0fd70cd6bff814 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Tue, 15 Jul 2025 18:16:52 -0300 Subject: [PATCH 02/16] drop extra field from request result --- crates/networking/rpc/engine/blobs.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 3fe8543f25..f7f57798cc 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -59,7 +59,20 @@ impl RpcHandler for BlobsV1Request { let mut blobs_bundles = Vec::new(); for hash in self.blob_versioned_hashes.iter() { - blobs_bundles.push(context.blockchain.mempool.get_blobs_bundle(*hash)?) + let blob_bundle = context.blockchain.mempool.get_blobs_bundle(*hash)?; + match blob_bundle { + None => blobs_bundles.push( + serde_json::to_value(blob_bundle) + .map_err(|error| RpcErr::Internal(error.to_string()))?, + ), + Some(blob_bundle) => blobs_bundles.push( + serde_json::to_value(BlobsV1Response { + blobs: blob_bundle.blobs, + proofs: blob_bundle.proofs, + }) + .map_err(|error| RpcErr::Internal(error.to_string()))?, + ), + } } serde_json::to_value(blobs_bundles).map_err(|error| RpcErr::Internal(error.to_string())) From 8a7690846aad0052c5d3ea5155dc97636625eb0d Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Fri, 18 Jul 2025 16:17:00 -0300 Subject: [PATCH 03/16] add serializers and complete blobs and proofs fetching --- crates/blockchain/mempool.rs | 9 +++++ crates/common/serde_utils.rs | 52 +++++++++++++++++++++++++ crates/networking/rpc/engine/blobs.rs | 55 ++++++++++++++++----------- 3 files changed, 93 insertions(+), 23 deletions(-) diff --git a/crates/blockchain/mempool.rs b/crates/blockchain/mempool.rs index a661acfdb0..3d4b378794 100644 --- a/crates/blockchain/mempool.rs +++ b/crates/blockchain/mempool.rs @@ -232,6 +232,15 @@ impl Mempool { .collect()) } + /// Returns all blobs bundles currently in the pool + pub fn get_blobs_bundle_pool(&self) -> Result, MempoolError> { + let blobs_bundle_pool = self + .blobs_bundle_pool + .lock() + .map_err(|error| StoreError::MempoolReadLock(error.to_string()))?; + Ok(blobs_bundle_pool.values().cloned().collect()) + } + /// Returns the status of the mempool, which is the number of transactions currently in /// the pool. Until we add "queue" transactions. pub fn status(&self) -> Result { diff --git a/crates/common/serde_utils.rs b/crates/common/serde_utils.rs index 99f4278b2d..bbf32df6e8 100644 --- a/crates/common/serde_utils.rs +++ b/crates/common/serde_utils.rs @@ -338,6 +338,22 @@ pub mod bytes48 { } } +pub mod bytes48_opt { + use super::*; + + pub mod vec { + + use super::*; + + pub fn serialize(value: &Vec>, serializer: S) -> Result + where + S: Serializer, + { + serialize_vec_of_opt_hex_encodables(value, serializer) + } + } +} + pub mod blob { use super::*; @@ -381,6 +397,26 @@ pub mod blob { } } +pub mod blob_opt { + use super::*; + + pub mod vec { + use crate::types::BYTES_PER_BLOB; + + use super::*; + + pub fn serialize( + value: &Vec>, + serializer: S, + ) -> Result + where + S: Serializer, + { + serialize_vec_of_opt_hex_encodables(value, serializer) + } + } +} + // Const generics are not supported on `Serialize` impls so we need separate impls for different array sizes fn serialize_vec_of_hex_encodables>( value: &Vec, @@ -393,6 +429,22 @@ fn serialize_vec_of_hex_encodables>( seq_serializer.end() } +fn serialize_vec_of_opt_hex_encodables>( + value: &Vec>, + serializer: S, +) -> Result { + let mut seq_serializer = serializer.serialize_seq(Some(value.len()))?; + for encoded in value { + match encoded { + Some(encoded) => { + seq_serializer.serialize_element(&format!("0x{}", hex::encode(encoded)))? + } + None => seq_serializer.serialize_element(&"null".to_string())?, + } + } + seq_serializer.end() +} + pub mod duration { use std::time::Duration; diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index f7f57798cc..786363e653 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -1,6 +1,7 @@ use ethrex_common::{ - H256, serde_utils, - types::{Blob, Proof}, + H256, + serde_utils::{self}, + types::{Blob, Proof, blobs_bundle::kzg_commitment_to_versioned_hash}, }; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -19,13 +20,13 @@ pub struct BlobsV1Request { blob_versioned_hashes: Vec, } -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BlobsV1Response { - #[serde(with = "serde_utils::blob::vec")] - pub blobs: Vec, - #[serde(with = "serde_utils::bytes48::vec")] - pub proofs: Vec, + #[serde(with = "serde_utils::blob_opt::vec")] + pub blobs: Vec>, + #[serde(with = "serde_utils::bytes48_opt::vec")] + pub proofs: Vec>, } impl std::fmt::Display for BlobsV1Request { @@ -56,25 +57,33 @@ impl RpcHandler for BlobsV1Request { if self.blob_versioned_hashes.len() >= GET_BLOBS_V1_REQUEST_MAX_SIZE { return Err(RpcErr::TooLargeRequest); } + //TODO: spec https://ethereum.github.io/execution-apis/docs/reference/engine_getblobsv1/ says error should be thrown for unsupported fork. + let mut blobs: Vec> = vec![None; self.blob_versioned_hashes.len()]; + let mut proofs: Vec> = vec![None; self.blob_versioned_hashes.len()]; - let mut blobs_bundles = Vec::new(); - for hash in self.blob_versioned_hashes.iter() { - let blob_bundle = context.blockchain.mempool.get_blobs_bundle(*hash)?; - match blob_bundle { - None => blobs_bundles.push( - serde_json::to_value(blob_bundle) - .map_err(|error| RpcErr::Internal(error.to_string()))?, - ), - Some(blob_bundle) => blobs_bundles.push( - serde_json::to_value(BlobsV1Response { - blobs: blob_bundle.blobs, - proofs: blob_bundle.proofs, - }) - .map_err(|error| RpcErr::Internal(error.to_string()))?, - ), + for blobs_bundle in context.blockchain.mempool.get_blobs_bundle_pool()? { + // Go over all blobs bundles from the blobs bundle pool. + let blobs_in_bundle = blobs_bundle.blobs; + let commitments_in_bundle = blobs_bundle.commitments; + let proofs_in_bundle = blobs_bundle.proofs; + + // Go over all the commitments in each blobs bundle to get the blobs versioned hash. + for i in 0..commitments_in_bundle.len() { + let current_versioned_hash = + kzg_commitment_to_versioned_hash(&commitments_in_bundle[i]); + if let Some(index) = self + .blob_versioned_hashes + .iter() + .position(|&hash| hash == current_versioned_hash) + { + // If the versioned hash is one of the requested we save its corresponding blob and proof in the returned vectors storing them in the same position as the versioned hash was received. + blobs[index] = Some(blobs_in_bundle[i]); + proofs[index] = Some(proofs_in_bundle[i]) + } } } - serde_json::to_value(blobs_bundles).map_err(|error| RpcErr::Internal(error.to_string())) + serde_json::to_value(BlobsV1Response { blobs, proofs }) + .map_err(|error| RpcErr::Internal(error.to_string())) } } From 4a476d60cb625d6bc0bd90529f5c404723611b65 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Fri, 18 Jul 2025 18:04:03 -0300 Subject: [PATCH 04/16] struct fix --- crates/common/serde_utils.rs | 43 ++++++++------------------- crates/networking/rpc/engine/blobs.rs | 40 ++++++++++++------------- 2 files changed, 32 insertions(+), 51 deletions(-) diff --git a/crates/common/serde_utils.rs b/crates/common/serde_utils.rs index bbf32df6e8..da8f3a71b2 100644 --- a/crates/common/serde_utils.rs +++ b/crates/common/serde_utils.rs @@ -336,20 +336,18 @@ pub mod bytes48 { Ok(output) } } -} - -pub mod bytes48_opt { - use super::*; - - pub mod vec { + pub mod opt { use super::*; - pub fn serialize(value: &Vec>, serializer: S) -> Result + pub fn serialize(value: &Option<[u8; 48]>, serializer: S) -> Result where S: Serializer, { - serialize_vec_of_opt_hex_encodables(value, serializer) + match value { + Some(value) => serializer.serialize_str(&format!("0x{}", hex::encode(value))), + None => serializer.serialize_none(), + } } } } @@ -395,24 +393,23 @@ pub mod blob { Ok(output) } } -} - -pub mod blob_opt { - use super::*; - pub mod vec { + pub mod opt { use crate::types::BYTES_PER_BLOB; use super::*; pub fn serialize( - value: &Vec>, + value: &Option<[u8; BYTES_PER_BLOB]>, serializer: S, ) -> Result where S: Serializer, { - serialize_vec_of_opt_hex_encodables(value, serializer) + match value { + Some(value) => serializer.serialize_str(&format!("0x{}", hex::encode(value))), + None => serializer.serialize_none(), + } } } } @@ -429,22 +426,6 @@ fn serialize_vec_of_hex_encodables>( seq_serializer.end() } -fn serialize_vec_of_opt_hex_encodables>( - value: &Vec>, - serializer: S, -) -> Result { - let mut seq_serializer = serializer.serialize_seq(Some(value.len()))?; - for encoded in value { - match encoded { - Some(encoded) => { - seq_serializer.serialize_element(&format!("0x{}", hex::encode(encoded)))? - } - None => seq_serializer.serialize_element(&"null".to_string())?, - } - } - seq_serializer.end() -} - pub mod duration { use std::time::Duration; diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 786363e653..9419a64c34 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -20,25 +20,21 @@ pub struct BlobsV1Request { blob_versioned_hashes: Vec, } -#[derive(Clone, Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct BlobsV1Response { - #[serde(with = "serde_utils::blob_opt::vec")] - pub blobs: Vec>, - #[serde(with = "serde_utils::bytes48_opt::vec")] - pub proofs: Vec>, -} - impl std::fmt::Display for BlobsV1Request { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Blob versioned hashes: {:#?}", - self.blob_versioned_hashes - ) + write!(f, "Requested Blobs") } } +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BlobsV1ResponseItem { + #[serde(with = "serde_utils::blob::opt")] + pub blob: Option, + #[serde(with = "serde_utils::bytes48::opt")] + pub proof: Option, +} + impl RpcHandler for BlobsV1Request { fn parse(params: &Option>) -> Result { let params = params @@ -58,8 +54,13 @@ impl RpcHandler for BlobsV1Request { return Err(RpcErr::TooLargeRequest); } //TODO: spec https://ethereum.github.io/execution-apis/docs/reference/engine_getblobsv1/ says error should be thrown for unsupported fork. - let mut blobs: Vec> = vec![None; self.blob_versioned_hashes.len()]; - let mut proofs: Vec> = vec![None; self.blob_versioned_hashes.len()]; + let mut res: Vec = vec![ + BlobsV1ResponseItem { + blob: None, + proof: None + }; + self.blob_versioned_hashes.len() + ]; for blobs_bundle in context.blockchain.mempool.get_blobs_bundle_pool()? { // Go over all blobs bundles from the blobs bundle pool. @@ -77,13 +78,12 @@ impl RpcHandler for BlobsV1Request { .position(|&hash| hash == current_versioned_hash) { // If the versioned hash is one of the requested we save its corresponding blob and proof in the returned vectors storing them in the same position as the versioned hash was received. - blobs[index] = Some(blobs_in_bundle[i]); - proofs[index] = Some(proofs_in_bundle[i]) + res[index].blob = Some(blobs_in_bundle[i]); + res[index].proof = Some(proofs_in_bundle[i]); } } } - serde_json::to_value(BlobsV1Response { blobs, proofs }) - .map_err(|error| RpcErr::Internal(error.to_string())) + serde_json::to_value(res).map_err(|error| RpcErr::Internal(error.to_string())) } } From 45d11f588dc28f51dc6c15d4a57cf77c82b0d101 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Mon, 21 Jul 2025 12:33:35 -0300 Subject: [PATCH 05/16] add permalink fix comments and names --- crates/networking/rpc/engine/blobs.rs | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 9419a64c34..44e7f3c207 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -12,7 +12,7 @@ use crate::{ utils::RpcErr, }; -// -> https://github.com/ethereum/execution-apis/blob/main/src/engine/cancun.md#specification-3 +// -> https://github.com/ethereum/execution-apis/blob/d41fdf10fabbb73c4d126fb41809785d830acace/src/engine/cancun.md?plain=1#L186 const GET_BLOBS_V1_REQUEST_MAX_SIZE: usize = 128; #[derive(Debug, Serialize, Deserialize)] @@ -20,15 +20,9 @@ pub struct BlobsV1Request { blob_versioned_hashes: Vec, } -impl std::fmt::Display for BlobsV1Request { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Requested Blobs") - } -} - #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct BlobsV1ResponseItem { +pub struct BlobAndProofV1 { #[serde(with = "serde_utils::blob::opt")] pub blob: Option, #[serde(with = "serde_utils::bytes48::opt")] @@ -49,13 +43,13 @@ impl RpcHandler for BlobsV1Request { } async fn handle(&self, context: RpcApiContext) -> Result { - info!("Received new engine request: {self}"); + info!("Received new engine request: Requested Blobs"); if self.blob_versioned_hashes.len() >= GET_BLOBS_V1_REQUEST_MAX_SIZE { return Err(RpcErr::TooLargeRequest); } //TODO: spec https://ethereum.github.io/execution-apis/docs/reference/engine_getblobsv1/ says error should be thrown for unsupported fork. - let mut res: Vec = vec![ - BlobsV1ResponseItem { + let mut res: Vec = vec![ + BlobAndProofV1 { blob: None, proof: None }; @@ -68,7 +62,7 @@ impl RpcHandler for BlobsV1Request { let commitments_in_bundle = blobs_bundle.commitments; let proofs_in_bundle = blobs_bundle.proofs; - // Go over all the commitments in each blobs bundle to get the blobs versioned hash. + // Go over all the commitments in each blobs bundle to calculate the blobs versioned hash. for i in 0..commitments_in_bundle.len() { let current_versioned_hash = kzg_commitment_to_versioned_hash(&commitments_in_bundle[i]); @@ -77,7 +71,7 @@ impl RpcHandler for BlobsV1Request { .iter() .position(|&hash| hash == current_versioned_hash) { - // If the versioned hash is one of the requested we save its corresponding blob and proof in the returned vectors storing them in the same position as the versioned hash was received. + // 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. res[index].blob = Some(blobs_in_bundle[i]); res[index].proof = Some(proofs_in_bundle[i]); } From cfc6a2356e0036d71d1d7b23b3d071a52aff3630 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Mon, 21 Jul 2025 12:55:05 -0300 Subject: [PATCH 06/16] fix on response struct --- crates/common/serde_utils.rs | 50 ++++++++------------------- crates/networking/rpc/engine/blobs.rs | 22 +++++------- 2 files changed, 24 insertions(+), 48 deletions(-) diff --git a/crates/common/serde_utils.rs b/crates/common/serde_utils.rs index da8f3a71b2..f1c4acb790 100644 --- a/crates/common/serde_utils.rs +++ b/crates/common/serde_utils.rs @@ -304,6 +304,13 @@ pub mod bool { pub mod bytes48 { use super::*; + pub fn serialize(value: &[u8; 48], serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("0x{}", hex::encode(value))) + } + pub mod vec { use super::*; @@ -336,28 +343,20 @@ pub mod bytes48 { Ok(output) } } - - pub mod opt { - use super::*; - - pub fn serialize(value: &Option<[u8; 48]>, serializer: S) -> Result - where - S: Serializer, - { - match value { - Some(value) => serializer.serialize_str(&format!("0x{}", hex::encode(value))), - None => serializer.serialize_none(), - } - } - } } pub mod blob { use super::*; + use crate::types::BYTES_PER_BLOB; - pub mod vec { - use crate::types::BYTES_PER_BLOB; + pub fn serialize(value: &[u8; BYTES_PER_BLOB], serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("0x{}", hex::encode(value))) + } + pub mod vec { use super::*; pub fn serialize( @@ -393,25 +392,6 @@ pub mod blob { Ok(output) } } - - pub mod opt { - use crate::types::BYTES_PER_BLOB; - - use super::*; - - pub fn serialize( - value: &Option<[u8; BYTES_PER_BLOB]>, - serializer: S, - ) -> Result - where - S: Serializer, - { - match value { - Some(value) => serializer.serialize_str(&format!("0x{}", hex::encode(value))), - None => serializer.serialize_none(), - } - } - } } // Const generics are not supported on `Serialize` impls so we need separate impls for different array sizes diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 44e7f3c207..cf912b6e34 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -23,10 +23,10 @@ pub struct BlobsV1Request { #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct BlobAndProofV1 { - #[serde(with = "serde_utils::blob::opt")] - pub blob: Option, - #[serde(with = "serde_utils::bytes48::opt")] - pub proof: Option, + #[serde(with = "serde_utils::blob")] + pub blob: Blob, + #[serde(with = "serde_utils::bytes48")] + pub proof: Proof, } impl RpcHandler for BlobsV1Request { @@ -48,13 +48,7 @@ impl RpcHandler for BlobsV1Request { return Err(RpcErr::TooLargeRequest); } //TODO: spec https://ethereum.github.io/execution-apis/docs/reference/engine_getblobsv1/ says error should be thrown for unsupported fork. - let mut res: Vec = vec![ - BlobAndProofV1 { - blob: None, - proof: None - }; - self.blob_versioned_hashes.len() - ]; + let mut res: Vec> = vec![None; self.blob_versioned_hashes.len()]; for blobs_bundle in context.blockchain.mempool.get_blobs_bundle_pool()? { // Go over all blobs bundles from the blobs bundle pool. @@ -72,8 +66,10 @@ impl RpcHandler for BlobsV1Request { .position(|&hash| hash == current_versioned_hash) { // 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. - res[index].blob = Some(blobs_in_bundle[i]); - res[index].proof = Some(proofs_in_bundle[i]); + res[index] = Some(BlobAndProofV1 { + blob: blobs_in_bundle[i], + proof: proofs_in_bundle[i], + }); } } } From 83b124326d2406faef52476909429057028aed9b Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Mon, 21 Jul 2025 15:05:29 -0300 Subject: [PATCH 07/16] throw unsupported fork error --- crates/networking/rpc/engine/blobs.rs | 30 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index cf912b6e34..996b5e3429 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -47,7 +47,23 @@ impl RpcHandler for BlobsV1Request { if self.blob_versioned_hashes.len() >= GET_BLOBS_V1_REQUEST_MAX_SIZE { return Err(RpcErr::TooLargeRequest); } - //TODO: spec https://ethereum.github.io/execution-apis/docs/reference/engine_getblobsv1/ says error should be thrown for unsupported fork. + + if let Some(current_block_header) = context + .storage + .get_block_header(context.storage.get_latest_block_number().await?)? + { + if !context + .storage + .get_chain_config()? + .is_cancun_activated(current_block_header.timestamp) + { + return Err(RpcErr::UnsuportedFork( + "getBlobsV1 engine request unsupported for forks previous to Cancun" + .to_string(), + )); + } + }; + let mut res: Vec> = vec![None; self.blob_versioned_hashes.len()]; for blobs_bundle in context.blockchain.mempool.get_blobs_bundle_pool()? { @@ -57,9 +73,11 @@ impl RpcHandler for BlobsV1Request { let proofs_in_bundle = blobs_bundle.proofs; // Go over all the commitments in each blobs bundle to calculate the blobs versioned hash. - for i in 0..commitments_in_bundle.len() { - let current_versioned_hash = - kzg_commitment_to_versioned_hash(&commitments_in_bundle[i]); + for (commitment, (blob, proof)) in commitments_in_bundle + .iter() + .zip(blobs_in_bundle.iter().zip(proofs_in_bundle.iter())) + { + let current_versioned_hash = kzg_commitment_to_versioned_hash(&commitment); if let Some(index) = self .blob_versioned_hashes .iter() @@ -67,8 +85,8 @@ impl RpcHandler for BlobsV1Request { { // 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. res[index] = Some(BlobAndProofV1 { - blob: blobs_in_bundle[i], - proof: proofs_in_bundle[i], + blob: *blob, + proof: *proof, }); } } From 740b844ea0a57171d8cc4c5e26ca9ee686b18e24 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Mon, 21 Jul 2025 15:18:44 -0300 Subject: [PATCH 08/16] drop reference --- crates/networking/rpc/engine/blobs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 996b5e3429..68c086411c 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -77,7 +77,7 @@ impl RpcHandler for BlobsV1Request { .iter() .zip(blobs_in_bundle.iter().zip(proofs_in_bundle.iter())) { - let current_versioned_hash = kzg_commitment_to_versioned_hash(&commitment); + let current_versioned_hash = kzg_commitment_to_versioned_hash(commitment); if let Some(index) = self .blob_versioned_hashes .iter() From 7490ab06d78a16caa4f6ecf649ce10913c7df652 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Mon, 21 Jul 2025 19:04:47 -0300 Subject: [PATCH 09/16] endpoint implemented --- crates/networking/rpc/engine/blobs.rs | 69 +++++++++++++++++++++++++++ crates/networking/rpc/engine/mod.rs | 3 +- crates/networking/rpc/rpc.rs | 3 +- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 68c086411c..1156fb0935 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -14,6 +14,7 @@ use crate::{ // -> https://github.com/ethereum/execution-apis/blob/d41fdf10fabbb73c4d126fb41809785d830acace/src/engine/cancun.md?plain=1#L186 const GET_BLOBS_V1_REQUEST_MAX_SIZE: usize = 128; +const CELLS_PER_EXT_BLOB: usize = 255; //?? #[derive(Debug, Serialize, Deserialize)] pub struct BlobsV1Request { @@ -95,3 +96,71 @@ impl RpcHandler for BlobsV1Request { serde_json::to_value(res).map_err(|error| RpcErr::Internal(error.to_string())) } } + +#[derive(Debug, Serialize, Deserialize)] +pub struct BlobsV2Request { + blob_versioned_hashes: Vec, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BlobAndProofV2 { + #[serde(with = "serde_utils::blob")] + pub blob: Blob, + #[serde(with = "serde_utils::bytes48::vec")] + pub proof: Vec, +} + +impl RpcHandler for BlobsV2Request { + fn parse(params: &Option>) -> Result { + 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(BlobsV2Request { + blob_versioned_hashes: serde_json::from_value(params[0].clone())?, + }) + } + + async fn handle(&self, context: RpcApiContext) -> Result { + info!("Received new engine request: Requested Blobs"); + if self.blob_versioned_hashes.len() >= GET_BLOBS_V1_REQUEST_MAX_SIZE { + return Err(RpcErr::TooLargeRequest); + } + + let mut res: Vec> = vec![None; self.blob_versioned_hashes.len()]; + + for blobs_bundle in context.blockchain.mempool.get_blobs_bundle_pool()? { + // Go over all blobs bundles from the blobs bundle pool. + let blobs_in_bundle = blobs_bundle.blobs; + let commitments_in_bundle = blobs_bundle.commitments; + let proofs_in_bundle = blobs_bundle.proofs; + + // Go over all the commitments in each blobs bundle to calculate the blobs versioned hash. + for (i, (commitment, blob)) in commitments_in_bundle + .iter() + .zip(blobs_in_bundle.iter()) + .enumerate() + { + let current_versioned_hash = kzg_commitment_to_versioned_hash(commitment); + if let Some(index) = self + .blob_versioned_hashes + .iter() + .position(|&hash| hash == current_versioned_hash) + { + // 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. + res[index] = Some(BlobAndProofV2 { + blob: *blob, + proof: proofs_in_bundle + [i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB] + .to_vec(), + }); + } + } + } + + serde_json::to_value(res).map_err(|error| RpcErr::Internal(error.to_string())) + } +} diff --git a/crates/networking/rpc/engine/mod.rs b/crates/networking/rpc/engine/mod.rs index 97038d15f4..5b2c39ed00 100644 --- a/crates/networking/rpc/engine/mod.rs +++ b/crates/networking/rpc/engine/mod.rs @@ -14,7 +14,7 @@ pub type ExchangeCapabilitiesRequest = Vec; /// List of capabilities that the execution layer client supports. Add new capabilities here. /// More info: https://github.com/ethereum/execution-apis/blob/main/src/engine/common.md#engine_exchangecapabilities -pub const CAPABILITIES: [&str; 15] = [ +pub const CAPABILITIES: [&str; 16] = [ "engine_forkchoiceUpdatedV1", "engine_forkchoiceUpdatedV2", "engine_forkchoiceUpdatedV3", @@ -30,6 +30,7 @@ pub const CAPABILITIES: [&str; 15] = [ "engine_getPayloadBodiesByHashV1", "engine_getPayloadBodiesByRangeV1", "engine_getBlobsV1", + "engine_getBlobsV2", ]; impl From for RpcRequest { diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index fedca7730f..4972c7822a 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -1,7 +1,7 @@ use crate::authentication::authenticate; use crate::engine::{ ExchangeCapabilitiesRequest, - blobs::BlobsV1Request, + blobs::{BlobsV1Request, BlobsV2Request}, exchange_transition_config::ExchangeTransitionConfigV1Req, fork_choice::{ForkChoiceUpdatedV1, ForkChoiceUpdatedV2, ForkChoiceUpdatedV3}, payload::{ @@ -374,6 +374,7 @@ pub async fn map_engine_requests( GetPayloadBodiesByRangeV1Request::call(req, context).await } "engine_getBlobsV1" => BlobsV1Request::call(req, context).await, + "engine_getBlobsV2" => BlobsV2Request::call(req, context).await, unknown_engine_method => Err(RpcErr::MethodNotFound(unknown_engine_method.to_owned())), } } From 7a42af2e40c4f0ebcea77eb101ef1417242200c5 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Tue, 22 Jul 2025 09:47:38 -0300 Subject: [PATCH 10/16] fix comment --- crates/networking/rpc/engine/blobs.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 1156fb0935..a6a0de20cd 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -14,7 +14,7 @@ use crate::{ // -> https://github.com/ethereum/execution-apis/blob/d41fdf10fabbb73c4d126fb41809785d830acace/src/engine/cancun.md?plain=1#L186 const GET_BLOBS_V1_REQUEST_MAX_SIZE: usize = 128; -const CELLS_PER_EXT_BLOB: usize = 255; //?? +const CELLS_PER_EXT_BLOB: usize = 1; //?? #[derive(Debug, Serialize, Deserialize)] pub struct BlobsV1Request { @@ -150,7 +150,7 @@ impl RpcHandler for BlobsV2Request { .iter() .position(|&hash| hash == current_versioned_hash) { - // 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. + // If the versioned hash is one of the requested we save its corresponding blob and its proofs in the returned vector. We store them in the same position as the versioned hash was received. res[index] = Some(BlobAndProofV2 { blob: *blob, proof: proofs_in_bundle From 7abe8c0114f04d5f37ca921216596800aa816483 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Tue, 22 Jul 2025 10:28:59 -0300 Subject: [PATCH 11/16] set correct unsuported fork --- cmd/ethrex_replay/src/constants.rs | 1 + crates/common/types/genesis.rs | 9 ++++++++- crates/networking/rpc/engine/blobs.rs | 6 +++--- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cmd/ethrex_replay/src/constants.rs b/cmd/ethrex_replay/src/constants.rs index 0e9500736b..7b6a254da5 100644 --- a/cmd/ethrex_replay/src/constants.rs +++ b/cmd/ethrex_replay/src/constants.rs @@ -33,6 +33,7 @@ pub fn make_chainconfig(chain_id: u64) -> ChainConfig { prague_time: Some(0), terminal_total_difficulty_passed: false, verkle_time: None, + osaka_time: None, blob_schedule: BlobSchedule::default(), // Mainnet address deposit_contract_address: H160::from_str("0x00000000219ab540356cbb839cbe05303d7705fa") diff --git a/crates/common/types/genesis.rs b/crates/common/types/genesis.rs index 3d5571cb92..d88198c1c9 100644 --- a/crates/common/types/genesis.rs +++ b/crates/common/types/genesis.rs @@ -167,6 +167,7 @@ pub struct ChainConfig { pub cancun_time: Option, pub prague_time: Option, pub verkle_time: Option, + pub osaka_time: Option, /// Amount of total difficulty reached by the network that triggers the consensus upgrade. pub terminal_total_difficulty: Option, @@ -233,6 +234,10 @@ impl From for &str { } impl ChainConfig { + pub fn is_osaka_activated(&self, block_timestamp: u64) -> bool { + self.osaka_time.is_some_and(|time| time <= block_timestamp) + } + pub fn is_prague_activated(&self, block_timestamp: u64) -> bool { self.prague_time.is_some_and(|time| time <= block_timestamp) } @@ -255,7 +260,9 @@ impl ChainConfig { } pub fn get_fork(&self, block_timestamp: u64) -> Fork { - if self.is_prague_activated(block_timestamp) { + if self.is_osaka_activated(block_timestamp) { + Fork::Osaka + } else if self.is_prague_activated(block_timestamp) { Fork::Prague } else if self.is_cancun_activated(block_timestamp) { Fork::Cancun diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 68c086411c..6ab147b59d 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -55,11 +55,11 @@ impl RpcHandler for BlobsV1Request { if !context .storage .get_chain_config()? - .is_cancun_activated(current_block_header.timestamp) + .is_osaka_activated(current_block_header.timestamp) { + // validation requested in https://github.com/ethereum/execution-apis/blob/a1d95fb555cd91efb3e0d6555e4ab556d9f5dd06/src/engine/osaka.md?plain=1#L130 return Err(RpcErr::UnsuportedFork( - "getBlobsV1 engine request unsupported for forks previous to Cancun" - .to_string(), + "getBlobsV1 engine request not supported for Osaka".to_string(), )); } }; From a1a5c1d80e353c1a0498c601f912a0d54f687da6 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Tue, 22 Jul 2025 11:34:26 -0300 Subject: [PATCH 12/16] add osaka_time field in test setup --- crates/networking/rpc/rpc.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/networking/rpc/rpc.rs b/crates/networking/rpc/rpc.rs index fedca7730f..86e2bfff13 100644 --- a/crates/networking/rpc/rpc.rs +++ b/crates/networking/rpc/rpc.rs @@ -507,6 +507,7 @@ mod tests { "cancunTime": 0, "pragueTime": 1718232101, "verkleTime": null, + "osakaTime": null, "terminalTotalDifficulty": 0, "terminalTotalDifficultyPassed": true, "blobSchedule": blob_schedule, From 29f434021fdbb8a338eeb4c9f46f965266b6dab7 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Tue, 22 Jul 2025 16:14:24 -0300 Subject: [PATCH 13/16] fix on unsuported fork error throwing --- crates/networking/rpc/engine/blobs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index 6ab147b59d..929aac2e8b 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -52,7 +52,7 @@ impl RpcHandler for BlobsV1Request { .storage .get_block_header(context.storage.get_latest_block_number().await?)? { - if !context + if context .storage .get_chain_config()? .is_osaka_activated(current_block_header.timestamp) From 9300745ce43fc9fedeaa237ccc72b55ccc51f482 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Tue, 22 Jul 2025 17:13:52 -0300 Subject: [PATCH 14/16] set cells_per_ext_blob as dynamic var --- crates/networking/rpc/engine/blobs.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/networking/rpc/engine/blobs.rs b/crates/networking/rpc/engine/blobs.rs index e1a0d5d8c6..81961a2a94 100644 --- a/crates/networking/rpc/engine/blobs.rs +++ b/crates/networking/rpc/engine/blobs.rs @@ -14,7 +14,6 @@ use crate::{ // -> https://github.com/ethereum/execution-apis/blob/d41fdf10fabbb73c4d126fb41809785d830acace/src/engine/cancun.md?plain=1#L186 const GET_BLOBS_V1_REQUEST_MAX_SIZE: usize = 128; -const CELLS_PER_EXT_BLOB: usize = 1; //?? #[derive(Debug, Serialize, Deserialize)] pub struct BlobsV1Request { @@ -138,6 +137,8 @@ impl RpcHandler for BlobsV2Request { let commitments_in_bundle = blobs_bundle.commitments; let proofs_in_bundle = blobs_bundle.proofs; + let cells_per_ext_blob = proofs_in_bundle.len() / blobs_in_bundle.len(); + // Go over all the commitments in each blobs bundle to calculate the blobs versioned hash. for (i, (commitment, blob)) in commitments_in_bundle .iter() @@ -154,7 +155,7 @@ impl RpcHandler for BlobsV2Request { res[index] = Some(BlobAndProofV2 { blob: *blob, proof: proofs_in_bundle - [i * CELLS_PER_EXT_BLOB..(i + 1) * CELLS_PER_EXT_BLOB] + [i * cells_per_ext_blob..(i + 1) * cells_per_ext_blob] .to_vec(), }); } From f303576721244664f65699c22e17652568f13f32 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Fri, 1 Aug 2025 14:54:25 -0300 Subject: [PATCH 15/16] ci: include hive tests for simulation eest/execute-blob --- .github/workflows/pr-main_l1.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/pr-main_l1.yaml b/.github/workflows/pr-main_l1.yaml index 5131c6f232..9f80244afd 100644 --- a/.github/workflows/pr-main_l1.yaml +++ b/.github/workflows/pr-main_l1.yaml @@ -220,6 +220,9 @@ jobs: - name: Run Hive Simulation run: chmod +x hive && ./hive --client-file fixtures/network/hive_clients/ethrex.yml --client ethrex --sim ${{ matrix.simulation }} --sim.limit "${{ matrix.test_pattern }}" --sim.parallelism 16 --sim.loglevel 1 + - name: Run Hive Simulation ethereum/eest/execute-blobs + run: chmod +x hive && ./hive --client-file fixtures/network/hive_clients/ethrex.yml --client ethrex --sim ethereum/eest/execute-blobs --sim.parallelism 16 --sim.loglevel 1 --sim.buildarg fork=Cancun + # The purpose of this job is to add it as a required check in GitHub so that we don't have to add every individual job as a required check all-tests: # "Integration Test" is a required check, don't change the name From c9318927b23512df5f1d0a48dec67ca2c6279c63 Mon Sep 17 00:00:00 2001 From: Camila Di Ielsi Date: Fri, 1 Aug 2025 15:07:21 -0300 Subject: [PATCH 16/16] show test docker output --- .github/workflows/pr-main_l1.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-main_l1.yaml b/.github/workflows/pr-main_l1.yaml index 9f80244afd..65eb3ee43a 100644 --- a/.github/workflows/pr-main_l1.yaml +++ b/.github/workflows/pr-main_l1.yaml @@ -221,7 +221,7 @@ jobs: run: chmod +x hive && ./hive --client-file fixtures/network/hive_clients/ethrex.yml --client ethrex --sim ${{ matrix.simulation }} --sim.limit "${{ matrix.test_pattern }}" --sim.parallelism 16 --sim.loglevel 1 - name: Run Hive Simulation ethereum/eest/execute-blobs - run: chmod +x hive && ./hive --client-file fixtures/network/hive_clients/ethrex.yml --client ethrex --sim ethereum/eest/execute-blobs --sim.parallelism 16 --sim.loglevel 1 --sim.buildarg fork=Cancun + run: chmod +x hive && ./hive --client-file fixtures/network/hive_clients/ethrex.yml --client ethrex --sim ethereum/eest/execute-blobs --sim.parallelism 16 --sim.loglevel 1 --sim.buildarg fork=Cancun --docker.output # The purpose of this job is to add it as a required check in GitHub so that we don't have to add every individual job as a required check all-tests: