Skip to content
This repository was archived by the owner on Jan 16, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions crates/derive/src/sources/blobs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,137 @@ where
}
}
}

#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{errors::PipelineErrorKind, traits::test_utils::TestBlobProvider};
use alloy_rlp::Decodable;
use kona_providers::test_utils::TestChainProvider;

pub(crate) fn default_test_blob_source() -> BlobSource<TestChainProvider, TestBlobProvider> {
let chain_provider = TestChainProvider::default();
let blob_fetcher = TestBlobProvider::default();
let batcher_address = Address::default();
let block_ref = BlockInfo::default();
let signer = Address::default();
BlobSource::new(chain_provider, blob_fetcher, batcher_address, block_ref, signer)
}

pub(crate) fn valid_blob_txs() -> Vec<TxEnvelope> {
// https://sepolia.etherscan.io/getRawTx?tx=0x9a22ccb0029bc8b0ddd073be1a1d923b7ae2b2ea52100bae0db4424f9107e9c0
let raw_tx = alloy_primitives::hex::decode("0x03f9011d83aa36a7820fa28477359400852e90edd0008252089411e9ca82a3a762b4b5bd264d4173a242e7a770648080c08504a817c800f8a5a0012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921aa00152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4a0013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7a001148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1a0011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e654901a0c8de4cced43169f9aa3d36506363b2d2c44f6c49fc1fd91ea114c86f3757077ea01e11fdd0d1934eda0492606ee0bb80a7bf8f35cc5f86ec60fe5031ba48bfd544").unwrap();
let eip4844 = TxEnvelope::decode(&mut raw_tx.as_slice()).unwrap();
vec![eip4844]
}

#[tokio::test]
async fn test_load_blobs_open() {
let mut source = default_test_blob_source();
source.open = true;
assert!(source.load_blobs().await.is_ok());
}

#[tokio::test]
async fn test_load_blobs_chain_provider_err() {
let mut source = default_test_blob_source();
assert!(matches!(source.load_blobs().await, Err(BlobProviderError::Backend(_))));
}

#[tokio::test]
async fn test_load_blobs_chain_provider_empty_txs() {
let mut source = default_test_blob_source();
let block_info = BlockInfo::default();
source.chain_provider.insert_block_with_transactions(0, block_info, Vec::new());
assert!(!source.open); // Source is not open by default.
assert!(source.load_blobs().await.is_ok());
assert!(source.data.is_empty());
assert!(source.open);
}

#[tokio::test]
async fn test_load_blobs_chain_provider_4844_txs_blob_fetch_error() {
let mut source = default_test_blob_source();
let block_info = BlockInfo::default();
source.signer = alloy_primitives::address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2");
source.batcher_address =
alloy_primitives::address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064");
let txs = valid_blob_txs();
source.blob_fetcher.should_error = true;
source.chain_provider.insert_block_with_transactions(1, block_info, txs);
assert!(matches!(source.load_blobs().await, Err(BlobProviderError::Backend(_))));
}

#[tokio::test]
async fn test_load_blobs_chain_provider_4844_txs_succeeds() {
use alloy_consensus::Blob;

let mut source = default_test_blob_source();
let block_info = BlockInfo::default();
source.signer = alloy_primitives::address!("A83C816D4f9b2783761a22BA6FADB0eB0606D7B2");
source.batcher_address =
alloy_primitives::address!("11E9CA82A3a762b4B5bd264d4173a242e7a77064");
let txs = valid_blob_txs();
source.chain_provider.insert_block_with_transactions(1, block_info, txs);
let hashes = [
alloy_primitives::b256!(
"012ec3d6f66766bedb002a190126b3549fce0047de0d4c25cffce0dc1c57921a"
),
alloy_primitives::b256!(
"0152d8e24762ff22b1cfd9f8c0683786a7ca63ba49973818b3d1e9512cd2cec4"
),
alloy_primitives::b256!(
"013b98c6c83e066d5b14af2b85199e3d4fc7d1e778dd53130d180f5077e2d1c7"
),
alloy_primitives::b256!(
"01148b495d6e859114e670ca54fb6e2657f0cbae5b08063605093a4b3dc9f8f1"
),
alloy_primitives::b256!(
"011ac212f13c5dff2b2c6b600a79635103d6f580a4221079951181b25c7e6549"
),
];
for hash in hashes {
source.blob_fetcher.insert_blob(hash, Blob::with_last_byte(1u8));
}
source.load_blobs().await.unwrap();
assert!(source.open);
assert!(!source.data.is_empty());
}

#[tokio::test]
async fn test_open_empty_data_eof() {
let mut source = default_test_blob_source();
source.open = true;

let err = source.next().await.unwrap_err();
assert!(matches!(err, PipelineErrorKind::Temporary(PipelineError::Eof)));
}

#[tokio::test]
async fn test_open_calldata() {
let mut source = default_test_blob_source();
source.open = true;
source.data.push(BlobData { data: None, calldata: Some(Bytes::default()) });

let data = source.next().await.unwrap();
assert_eq!(data, Bytes::default());
}

#[tokio::test]
async fn test_open_blob_data_decode_missing_data() {
let mut source = default_test_blob_source();
source.open = true;
source.data.push(BlobData { data: Some(Bytes::from(&[1; 32])), calldata: None });

let err = source.next().await.unwrap_err();
assert!(matches!(err, PipelineErrorKind::Temporary(PipelineError::Eof)));
}

#[tokio::test]
async fn test_blob_source_pipeline_error() {
let mut source = default_test_blob_source();

let err = source.next().await.unwrap_err();
assert!(matches!(err, PipelineErrorKind::Temporary(PipelineError::Provider(_))));
}
}
5 changes: 5 additions & 0 deletions crates/derive/src/traits/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ impl DataAvailabilityProvider for TestDAP {
pub struct TestBlobProvider {
/// Maps block hashes to blob data.
pub blobs: HashMap<B256, Blob>,
/// whether the blob provider should return an error.
pub should_error: bool,
}

impl TestBlobProvider {
Expand All @@ -84,6 +86,9 @@ impl BlobProvider for TestBlobProvider {
_block_ref: &BlockInfo,
blob_hashes: &[IndexedBlobHash],
) -> Result<Vec<Blob>, Self::Error> {
if self.should_error {
return Err(BlobProviderError::SlotDerivation);
}
let mut blobs = Vec::new();
for blob_hash in blob_hashes {
if let Some(data) = self.blobs.get(&blob_hash.hash) {
Expand Down