diff --git a/crates/derive/src/sources/blobs.rs b/crates/derive/src/sources/blobs.rs index 28e27a1077..cc7f229558 100644 --- a/crates/derive/src/sources/blobs.rs +++ b/crates/derive/src/sources/blobs.rs @@ -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 { + 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 { + // 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(_)))); + } +} diff --git a/crates/derive/src/traits/test_utils.rs b/crates/derive/src/traits/test_utils.rs index 03ff1721de..145591d569 100644 --- a/crates/derive/src/traits/test_utils.rs +++ b/crates/derive/src/traits/test_utils.rs @@ -61,6 +61,8 @@ impl DataAvailabilityProvider for TestDAP { pub struct TestBlobProvider { /// Maps block hashes to blob data. pub blobs: HashMap, + /// whether the blob provider should return an error. + pub should_error: bool, } impl TestBlobProvider { @@ -84,6 +86,9 @@ impl BlobProvider for TestBlobProvider { _block_ref: &BlockInfo, blob_hashes: &[IndexedBlobHash], ) -> Result, 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) {