Skip to content

Commit 47e2ec8

Browse files
committed
refactor: improve mod and test layout
1 parent c77ae5b commit 47e2ec8

File tree

4 files changed

+193
-79
lines changed

4 files changed

+193
-79
lines changed

crates/blobber/src/cache.rs

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use crate::{BlobFetcherError, Blobs, FetchResult};
22
use alloy::consensus::{SidecarCoder, SimpleCoder, Transaction as _};
33
use alloy::eips::eip7691::MAX_BLOBS_PER_BLOCK_ELECTRA;
44
use alloy::eips::merge::EPOCH_SLOTS;
5-
use alloy::primitives::{keccak256, Bytes, B256};
5+
use alloy::primitives::{B256, Bytes, keccak256};
66
use reth::transaction_pool::TransactionPool;
77
use reth::{network::cache::LruMap, primitives::Receipt};
88
use signet_extract::ExtractedEvent;
@@ -206,3 +206,92 @@ impl<Pool: TransactionPool + 'static> BlobCacher<Pool> {
206206
CacheHandle { sender }
207207
}
208208
}
209+
210+
#[cfg(test)]
211+
mod tests {
212+
use crate::BlobFetcher;
213+
214+
use super::*;
215+
use alloy::{
216+
consensus::{SidecarBuilder, SignableTransaction as _, TxEip2930},
217+
eips::Encodable2718,
218+
primitives::{TxKind, U256, bytes},
219+
rlp::encode,
220+
signers::{SignerSync, local::PrivateKeySigner},
221+
};
222+
use init4_bin_base::utils::calc::SlotCalculator;
223+
use reth::primitives::Transaction;
224+
use reth_transaction_pool::{
225+
PoolTransaction, TransactionOrigin,
226+
test_utils::{MockTransaction, testing_pool},
227+
};
228+
use signet_types::{constants::SignetSystemConstants, primitives::TransactionSigned};
229+
230+
#[tokio::test]
231+
async fn test_fetch_from_pool() -> eyre::Result<()> {
232+
let wallet = PrivateKeySigner::random();
233+
let pool = testing_pool();
234+
235+
let test = signet_constants::KnownChains::Test;
236+
237+
let constants: SignetSystemConstants = test.try_into().unwrap();
238+
let calc = SlotCalculator::new(0, 0, 12);
239+
240+
let explorer_url = "https://api.holesky.blobscan.com/";
241+
let client = reqwest::Client::builder().use_rustls_tls();
242+
243+
let tx = Transaction::Eip2930(TxEip2930 {
244+
chain_id: 17001,
245+
nonce: 2,
246+
gas_limit: 50000,
247+
gas_price: 1_500_000_000,
248+
to: TxKind::Call(constants.host_zenith()),
249+
value: U256::from(1_f64),
250+
input: bytes!(""),
251+
..Default::default()
252+
});
253+
254+
let encoded_transactions =
255+
encode(vec![sign_tx_with_key_pair(wallet.clone(), tx).encoded_2718()]);
256+
257+
let result = SidecarBuilder::<SimpleCoder>::from_slice(&encoded_transactions).build();
258+
assert!(result.is_ok());
259+
260+
let mut mock_transaction = MockTransaction::eip4844_with_sidecar(result.unwrap().into());
261+
let transaction =
262+
sign_tx_with_key_pair(wallet, Transaction::from(mock_transaction.clone()));
263+
264+
mock_transaction.set_hash(*transaction.hash());
265+
266+
pool.add_transaction(TransactionOrigin::Local, mock_transaction.clone()).await?;
267+
268+
// Spawn the cache
269+
let cache = BlobFetcher::builder()
270+
.with_pool(pool.clone())
271+
.with_explorer_url(explorer_url)
272+
.with_client_builder(client)
273+
.unwrap()
274+
.with_slot_calculator(calc)
275+
.build_cache()?;
276+
let handle = cache.spawn();
277+
278+
let got = handle
279+
.fetch_blobs(
280+
0, // this is ignored by the pool
281+
*mock_transaction.hash(),
282+
mock_transaction.blob_versioned_hashes().unwrap().to_owned(),
283+
)
284+
.await;
285+
assert!(got.is_ok());
286+
287+
let got_blobs = got.unwrap();
288+
assert!(got_blobs.len() == 1);
289+
290+
Ok(())
291+
}
292+
293+
fn sign_tx_with_key_pair(wallet: PrivateKeySigner, tx: Transaction) -> TransactionSigned {
294+
let signature = wallet.sign_hash_sync(&tx.signature_hash()).unwrap();
295+
TransactionSigned::new_unhashed(tx, signature)
296+
}
297+
}

crates/blobber/src/fetch.rs

Lines changed: 2 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
BlobFetcherBuilder, BlobFetcherError, FetchResult, error::UnrecoverableBlobError,
3-
shim::ExtractableChainShim,
3+
shim::ExtractableChainShim, utils::extract_blobs_from_bundle,
44
};
55
use alloy::{
66
consensus::{Blob, SidecarCoder, SimpleCoder, Transaction as _},
@@ -14,7 +14,6 @@ use reth::{
1414
};
1515
use signet_extract::{ExtractedEvent, Extracts};
1616
use signet_zenith::{Zenith::BlockSubmitted, ZenithBlock};
17-
use smallvec::SmallVec;
1817
use std::{ops::Deref, sync::Arc};
1918
use tokio::select;
2019
use tracing::{error, instrument, trace};
@@ -305,98 +304,23 @@ where
305304
}
306305
}
307306

308-
/// Extracts the blobs from the [`BeaconBlobBundle`], and returns the blobs that match the versioned hashes in the transaction.
309-
/// This also dedups any duplicate blobs if a builder lands the same blob multiple times in a block.
310-
fn extract_blobs_from_bundle(
311-
bundle: BeaconBlobBundle,
312-
versioned_hashes: &[B256],
313-
) -> FetchResult<Blobs> {
314-
let mut blobs = vec![];
315-
// 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.
316-
let mut seen_versioned_hashes: SmallVec<[B256; 9]> = SmallVec::new();
317-
318-
for item in bundle.data.iter() {
319-
let versioned_hash =
320-
alloy::eips::eip4844::kzg_to_versioned_hash(item.kzg_commitment.as_ref());
321-
322-
if versioned_hashes.contains(&versioned_hash)
323-
&& !seen_versioned_hashes.contains(&versioned_hash)
324-
{
325-
blobs.push(*item.blob);
326-
seen_versioned_hashes.push(versioned_hash);
327-
}
328-
}
329-
330-
Ok(blobs.into())
331-
}
332-
333307
#[cfg(test)]
334308
mod tests {
335309
use super::*;
336310
use alloy::{
337-
consensus::{
338-
BlobTransactionSidecar, SidecarBuilder, SignableTransaction as _, TxEip2930, TxEnvelope,
339-
},
311+
consensus::{SidecarBuilder, SignableTransaction as _, TxEip2930},
340312
eips::Encodable2718,
341313
primitives::{TxKind, U256, bytes},
342314
rlp::encode,
343315
signers::{SignerSync, local::PrivateKeySigner},
344316
};
345-
use foundry_blob_explorers::TransactionDetails;
346317
use reth::primitives::Transaction;
347318
use reth_transaction_pool::{
348319
PoolTransaction, TransactionOrigin,
349320
test_utils::{MockTransaction, testing_pool},
350321
};
351322
use signet_types::{constants::SignetSystemConstants, primitives::TransactionSigned};
352323

353-
const BLOBSCAN_BLOB_RESPONSE: &str = include_str!("../../../tests/artifacts/blob.json");
354-
/// Blob from Slot 2277733, corresponding to block 277722 on Pecorino host.
355-
const CL_BLOB_RESPONSE: &str = include_str!("../../../tests/artifacts/cl_blob.json");
356-
/// EIP4844 blob tx with hash 0x73d1c682fae85c761528a0a7ec22fac613b25ede87b80f0ac052107f3444324f,
357-
/// corresponding to blob sent to block 277722 on Pecorino host.
358-
const CL_BLOB_TX: &str = include_str!("../../../tests/artifacts/cl_blob_tx.json");
359-
/// Blob sidecar from Pylon, corresponding to block 277722 on Pecorino host.
360-
const PYLON_BLOB_RESPONSE: &str = include_str!("../../../tests/artifacts/pylon_blob.json");
361-
362-
#[test]
363-
fn test_process_blob_extraction() {
364-
let bundle: BeaconBlobBundle = serde_json::from_str(CL_BLOB_RESPONSE).unwrap();
365-
let tx: TxEnvelope = serde_json::from_str::<TxEnvelope>(CL_BLOB_TX).unwrap();
366-
let tx: TransactionSigned = tx.into();
367-
368-
let versioned_hashes =
369-
tx.blob_versioned_hashes().map(ToOwned::to_owned).unwrap_or_default();
370-
371-
// Extract the blobs from the CL beacon blob bundle.
372-
let cl_blobs = extract_blobs_from_bundle(bundle, &versioned_hashes).unwrap();
373-
assert_eq!(cl_blobs.len(), 1);
374-
375-
// Now, process the pylon blobs which come in a [`BlobTransactionSidecar`].
376-
// NB: this should be changes to `BlobTransactionSidecarVariant` in the
377-
// future. After https://github.com/alloy-rs/alloy/pull/2713
378-
// The json is definitely a `BlobTransactionSidecar`, so we can
379-
// deserialize it directly and it doesn't really matter much.
380-
let sidecar: BlobTransactionSidecar =
381-
serde_json::from_str::<BlobTransactionSidecar>(PYLON_BLOB_RESPONSE).unwrap();
382-
let pylon_blobs: Blobs = Arc::<BlobTransactionSidecarVariant>::new(sidecar.into()).into();
383-
384-
// Make sure that both blob sources have the same blobs after being processed.
385-
assert_eq!(cl_blobs.len(), pylon_blobs.len());
386-
assert_eq!(cl_blobs.as_slice(), pylon_blobs.as_slice());
387-
388-
// Make sure both can be decoded
389-
let cl_decoded = SimpleCoder::default().decode_all(cl_blobs.as_ref()).unwrap();
390-
let pylon_decoded = SimpleCoder::default().decode_all(pylon_blobs.as_ref()).unwrap();
391-
assert_eq!(cl_decoded.len(), pylon_decoded.len());
392-
assert_eq!(cl_decoded, pylon_decoded);
393-
}
394-
395-
#[test]
396-
fn test_deser_blob() {
397-
let _: TransactionDetails = serde_json::from_str(BLOBSCAN_BLOB_RESPONSE).unwrap();
398-
}
399-
400324
#[tokio::test]
401325
async fn test_fetch_from_pool() -> eyre::Result<()> {
402326
let wallet = PrivateKeySigner::random();

crates/blobber/src/lib.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,17 @@ pub use fetch::{BlobFetcher, Blobs};
2828

2929
mod shim;
3030
pub use shim::ExtractableChainShim;
31+
32+
pub(crate) mod utils;
33+
34+
#[cfg(test)]
35+
mod test {
36+
use crate::utils::tests::BLOBSCAN_BLOB_RESPONSE;
37+
use foundry_blob_explorers::TransactionDetails;
38+
39+
// Sanity check on dependency compatibility.
40+
#[test]
41+
fn test_deser_blob() {
42+
let _: TransactionDetails = serde_json::from_str(BLOBSCAN_BLOB_RESPONSE).unwrap();
43+
}
44+
}

crates/blobber/src/utils.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use crate::{Blobs, FetchResult};
2+
use alloy::{
3+
eips::{eip4844::kzg_to_versioned_hash, eip7691::MAX_BLOBS_PER_BLOCK_ELECTRA},
4+
primitives::B256,
5+
};
6+
use reth::rpc::types::beacon::sidecar::BeaconBlobBundle;
7+
use smallvec::SmallVec;
8+
9+
/// Extracts the blobs from the [`BeaconBlobBundle`], and returns the blobs that match the versioned hashes in the transaction.
10+
/// This also dedups any duplicate blobs if a builder lands the same blob multiple times in a block.
11+
pub(crate) fn extract_blobs_from_bundle(
12+
bundle: BeaconBlobBundle,
13+
versioned_hashes: &[B256],
14+
) -> FetchResult<Blobs> {
15+
let mut blobs = vec![];
16+
// 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.
17+
let mut seen_versioned_hashes: SmallVec<[B256; MAX_BLOBS_PER_BLOCK_ELECTRA as usize]> =
18+
SmallVec::new();
19+
20+
for item in bundle.data.iter() {
21+
let versioned_hash = kzg_to_versioned_hash(item.kzg_commitment.as_ref());
22+
23+
if versioned_hashes.contains(&versioned_hash)
24+
&& !seen_versioned_hashes.contains(&versioned_hash)
25+
{
26+
blobs.push(*item.blob);
27+
seen_versioned_hashes.push(versioned_hash);
28+
}
29+
}
30+
31+
Ok(blobs.into())
32+
}
33+
34+
#[cfg(test)]
35+
pub(crate) mod tests {
36+
37+
use super::*;
38+
use alloy::{
39+
consensus::{BlobTransactionSidecar, SidecarCoder, SimpleCoder, Transaction, TxEnvelope},
40+
eips::eip7594::BlobTransactionSidecarVariant,
41+
};
42+
use signet_types::primitives::TransactionSigned;
43+
use std::sync::Arc;
44+
45+
pub(crate) const BLOBSCAN_BLOB_RESPONSE: &str =
46+
include_str!("../../../tests/artifacts/blob.json");
47+
/// Blob from Slot 2277733, corresponding to block 277722 on Pecorino host.
48+
pub(crate) const CL_BLOB_RESPONSE: &str = include_str!("../../../tests/artifacts/cl_blob.json");
49+
/// EIP4844 blob tx with hash 0x73d1c682fae85c761528a0a7ec22fac613b25ede87b80f0ac052107f3444324f,
50+
/// corresponding to blob sent to block 277722 on Pecorino host.
51+
pub(crate) const CL_BLOB_TX: &str = include_str!("../../../tests/artifacts/cl_blob_tx.json");
52+
/// Blob sidecar from Pylon, corresponding to block 277722 on Pecorino host.
53+
pub(crate) const PYLON_BLOB_RESPONSE: &str =
54+
include_str!("../../../tests/artifacts/pylon_blob.json");
55+
56+
#[test]
57+
fn test_process_blob_extraction() {
58+
let bundle: BeaconBlobBundle = serde_json::from_str(CL_BLOB_RESPONSE).unwrap();
59+
let tx: TxEnvelope = serde_json::from_str::<TxEnvelope>(CL_BLOB_TX).unwrap();
60+
let tx: TransactionSigned = tx.into();
61+
62+
let versioned_hashes = tx.blob_versioned_hashes().unwrap().to_owned();
63+
64+
// Extract the blobs from the CL beacon blob bundle.
65+
let cl_blobs = extract_blobs_from_bundle(bundle, &versioned_hashes).unwrap();
66+
assert_eq!(cl_blobs.len(), 1);
67+
68+
// Now, process the pylon blobs which come in a [`BlobTransactionSidecar`].
69+
// NB: this should be changes to `BlobTransactionSidecarVariant` in the
70+
// future. After https://github.com/alloy-rs/alloy/pull/2713
71+
// The json is definitely a `BlobTransactionSidecar`, so we can
72+
// deserialize it directly and it doesn't really matter much.
73+
let sidecar: BlobTransactionSidecar =
74+
serde_json::from_str::<BlobTransactionSidecar>(PYLON_BLOB_RESPONSE).unwrap();
75+
let pylon_blobs: Blobs = Arc::<BlobTransactionSidecarVariant>::new(sidecar.into()).into();
76+
77+
// Make sure that both blob sources have the same blobs after being processed.
78+
assert_eq!(cl_blobs.len(), pylon_blobs.len());
79+
assert_eq!(cl_blobs.as_slice(), pylon_blobs.as_slice());
80+
81+
// Make sure both can be decoded
82+
let cl_decoded = SimpleCoder::default().decode_all(cl_blobs.as_ref()).unwrap();
83+
let pylon_decoded = SimpleCoder::default().decode_all(pylon_blobs.as_ref()).unwrap();
84+
assert_eq!(cl_decoded.len(), pylon_decoded.len());
85+
assert_eq!(cl_decoded, pylon_decoded);
86+
}
87+
}

0 commit comments

Comments
 (0)