From 9152c2f3a2286bdb161daa731ae6630542038cab Mon Sep 17 00:00:00 2001 From: evalir Date: Tue, 30 Dec 2025 14:58:11 -0400 Subject: [PATCH] feat(blobber): Use new `/blobs` lighthouse endpoint to fetch blobs from CL This massively simplifies CL blob fetching code. Instead of having to match blobs with versioned hashes with potential problems, we can just ask for exactly the blobs we want using the slot and versioned hashes. It reduces the entire CL fetching code to an API call. Closes ENG-1667 --- crates/blobber/src/blobs/fetch.rs | 23 +++++--- crates/blobber/src/coder/trait.rs | 2 +- crates/blobber/src/lib.rs | 7 +-- crates/blobber/src/utils.rs | 87 ------------------------------- 4 files changed, 21 insertions(+), 98 deletions(-) delete mode 100644 crates/blobber/src/utils.rs diff --git a/crates/blobber/src/blobs/fetch.rs b/crates/blobber/src/blobs/fetch.rs index d6e20c9..d2540ff 100644 --- a/crates/blobber/src/blobs/fetch.rs +++ b/crates/blobber/src/blobs/fetch.rs @@ -1,10 +1,10 @@ -use crate::{BlobFetcherBuilder, FetchError, FetchResult, utils::extract_blobs_from_bundle}; +use crate::{BlobFetcherBuilder, FetchError, FetchResult}; use alloy::{ consensus::{Blob, BlobTransactionSidecar}, eips::eip7594::{BlobTransactionSidecarEip7594, BlobTransactionSidecarVariant}, primitives::{B256, TxHash}, }; -use reth::{rpc::types::beacon::sidecar::BeaconBlobBundle, transaction_pool::TransactionPool}; +use reth::{rpc::types::beacon::sidecar::GetBlobsResponse, transaction_pool::TransactionPool}; use std::{ops::Deref, sync::Arc}; use tokio::select; use tracing::instrument; @@ -218,15 +218,24 @@ where return Err(FetchError::ConsensusClientUrlNotSet); }; - let url = url - .join(&format!("/eth/v1/beacon/blob_sidecars/{slot}")) - .map_err(FetchError::UrlParse)?; + let mut url = + url.join(&format!("/eth/v1/beacon/blobs/{slot}")).map_err(FetchError::UrlParse)?; + + url.query_pairs_mut() + .extend_pairs(versioned_hashes.iter().map(|hash| ("versioned_hash", hash.to_string()))); let response = self.client.get(url).header("accept", "application/json").send().await?; - let response: BeaconBlobBundle = response.json().await?; + let response: GetBlobsResponse = response.json().await?; + + debug_assert!( + response.data.len() == versioned_hashes.len(), + "Expected {} blobs, got {}", + versioned_hashes.len(), + response.data.len() + ); - extract_blobs_from_bundle(response, versioned_hashes) + Ok(Arc::new(response.data).into()) } } diff --git a/crates/blobber/src/coder/trait.rs b/crates/blobber/src/coder/trait.rs index 3a303f2..51652bc 100644 --- a/crates/blobber/src/coder/trait.rs +++ b/crates/blobber/src/coder/trait.rs @@ -51,7 +51,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::{Blobs, utils::tests::PYLON_BLOB_RESPONSE}; + use crate::{Blobs, test::PYLON_BLOB_RESPONSE}; use alloy::{ consensus::{Blob, BlobTransactionSidecar, Bytes48, SimpleCoder}, primitives::{Address, B256, U256, b256}, diff --git a/crates/blobber/src/lib.rs b/crates/blobber/src/lib.rs index 4d4b7bd..ef6dcca 100644 --- a/crates/blobber/src/lib.rs +++ b/crates/blobber/src/lib.rs @@ -26,11 +26,12 @@ pub use error::{BlobberError, BlobberResult}; mod shim; pub use shim::ExtractableChainShim; -pub(crate) mod utils; - #[cfg(test)] mod test { - use crate::utils::tests::BLOBSCAN_BLOB_RESPONSE; + pub(crate) const BLOBSCAN_BLOB_RESPONSE: &str = + include_str!("../../../tests/artifacts/blob.json"); + pub(crate) const PYLON_BLOB_RESPONSE: &str = + include_str!("../../../tests/artifacts/pylon_blob.json"); use foundry_blob_explorers::TransactionDetails; // Sanity check on dependency compatibility. diff --git a/crates/blobber/src/utils.rs b/crates/blobber/src/utils.rs deleted file mode 100644 index 71d27be..0000000 --- a/crates/blobber/src/utils.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{Blobs, FetchResult}; -use alloy::{ - eips::{eip4844::kzg_to_versioned_hash, eip7691::MAX_BLOBS_PER_BLOCK_ELECTRA}, - primitives::B256, -}; -use reth::rpc::types::beacon::sidecar::BeaconBlobBundle; -use smallvec::SmallVec; - -/// Extracts the blobs from the [`BeaconBlobBundle`], and returns the blobs that match the versioned hashes in the transaction. -/// This also dedups any duplicate blobs if a builder lands the same blob multiple times in a block. -pub(crate) fn extract_blobs_from_bundle( - bundle: BeaconBlobBundle, - versioned_hashes: &[B256], -) -> FetchResult { - let mut blobs = vec![]; - // NB: There can be, at most, 9 blobs per block from Pectra forwards. We'll never need more space than this, unless blob capacity is increased again or made dynamic. - let mut seen_versioned_hashes: SmallVec<[B256; MAX_BLOBS_PER_BLOCK_ELECTRA as usize]> = - SmallVec::new(); - - for item in bundle.data.iter() { - let versioned_hash = kzg_to_versioned_hash(item.kzg_commitment.as_ref()); - - if versioned_hashes.contains(&versioned_hash) - && !seen_versioned_hashes.contains(&versioned_hash) - { - blobs.push(*item.blob); - seen_versioned_hashes.push(versioned_hash); - } - } - - Ok(blobs.into()) -} - -#[cfg(test)] -pub(crate) mod tests { - - use super::*; - use alloy::{ - consensus::{BlobTransactionSidecar, SidecarCoder, SimpleCoder, Transaction, TxEnvelope}, - eips::eip7594::BlobTransactionSidecarVariant, - }; - use signet_types::primitives::TransactionSigned; - use std::sync::Arc; - - pub(crate) const BLOBSCAN_BLOB_RESPONSE: &str = - include_str!("../../../tests/artifacts/blob.json"); - /// Blob from Slot 2277733, corresponding to block 277722 on Pecorino host. - pub(crate) const CL_BLOB_RESPONSE: &str = include_str!("../../../tests/artifacts/cl_blob.json"); - /// EIP4844 blob tx with hash 0x73d1c682fae85c761528a0a7ec22fac613b25ede87b80f0ac052107f3444324f, - /// corresponding to blob sent to block 277722 on Pecorino host. - pub(crate) const CL_BLOB_TX: &str = include_str!("../../../tests/artifacts/cl_blob_tx.json"); - /// Blob sidecar from Pylon, corresponding to block 277722 on Pecorino host. - pub(crate) const PYLON_BLOB_RESPONSE: &str = - include_str!("../../../tests/artifacts/pylon_blob.json"); - - #[test] - fn test_process_blob_extraction() { - let bundle: BeaconBlobBundle = serde_json::from_str(CL_BLOB_RESPONSE).unwrap(); - let tx: TxEnvelope = serde_json::from_str::(CL_BLOB_TX).unwrap(); - let tx: TransactionSigned = tx.into(); - - let versioned_hashes = tx.blob_versioned_hashes().unwrap().to_owned(); - - // Extract the blobs from the CL beacon blob bundle. - let cl_blobs = extract_blobs_from_bundle(bundle, &versioned_hashes).unwrap(); - assert_eq!(cl_blobs.len(), 1); - - // Now, process the pylon blobs which come in a [`BlobTransactionSidecar`]. - // NB: this should be changes to `BlobTransactionSidecarVariant` in the - // future. After https://github.com/alloy-rs/alloy/pull/2713 - // The json is definitely a `BlobTransactionSidecar`, so we can - // deserialize it directly and it doesn't really matter much. - let sidecar: BlobTransactionSidecar = - serde_json::from_str::(PYLON_BLOB_RESPONSE).unwrap(); - let pylon_blobs: Blobs = Arc::::new(sidecar.into()).into(); - - // Make sure that both blob sources have the same blobs after being processed. - assert_eq!(cl_blobs.len(), pylon_blobs.len()); - assert_eq!(cl_blobs.as_slice(), pylon_blobs.as_slice()); - - // Make sure both can be decoded - let cl_decoded = SimpleCoder::default().decode_all(cl_blobs.as_ref()).unwrap(); - let pylon_decoded = SimpleCoder::default().decode_all(pylon_blobs.as_ref()).unwrap(); - assert_eq!(cl_decoded.len(), pylon_decoded.len()); - assert_eq!(cl_decoded, pylon_decoded); - } -}