Skip to content

Commit 892061f

Browse files
committed
feat: add builder for blob extractor
1 parent d394330 commit 892061f

File tree

9 files changed

+224
-37
lines changed

9 files changed

+224
-37
lines changed

crates/blobber/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ signet-zenith.workspace = true
1818

1919
reth.workspace = true
2020
reth-chainspec.workspace = true
21+
reth-transaction-pool = { workspace = true, optional = true }
2122

2223
smallvec.workspace = true
2324
tokio.workspace = true
2425
tracing.workspace = true
25-
eyre.workspace = true
2626
reqwest.workspace = true
2727
url.workspace = true
2828
foundry-blob-explorers.workspace = true
@@ -33,5 +33,9 @@ signet-constants = { workspace = true, features = ["test-utils"] }
3333

3434
reth-transaction-pool = { workspace = true, features = ["test-utils"] }
3535

36+
eyre.workspace = true
3637
serde_json.workspace = true
37-
tempfile.workspace = true
38+
tempfile.workspace = true
39+
40+
[features]
41+
test-utils = ["signet-constants/test-utils", "dep:reth-transaction-pool", "reth-transaction-pool?/test-utils"]

crates/blobber/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Block Extractor
2+
3+
The [`BlockExtractor`] retrieves blobs from host chain blocks and parses them
4+
into [`ZenithBlock`]s. It is used by the node during notification processing
5+
when a [`Zenith::BlockSubmitted`] event is extracted from a host chain block.
6+
7+
## Data Sources
8+
9+
The following sources can be configured:
10+
11+
- The local EL node transaction pool.
12+
- The local CL node via RPC.
13+
- A blob explorer.
14+
- Signet's Pylon blob storage system.
15+
16+
[`ZenithBlock`]: signet_zenith::ZenithBlock
17+
[`Zenith::BlockSubmitted`]: signet_zenith::Zenith::BlockSubmitted

crates/blobber/src/block_data.rs

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
22
error::UnrecoverableBlobError, shim::ExtractableChainShim, BlockExtractionError,
3-
ExtractionResult,
3+
BlockExtractorBuilder, ExtractionResult,
44
};
55
use alloy::{
66
consensus::{Blob, SidecarCoder, SimpleCoder},
@@ -15,7 +15,7 @@ use reth::{
1515
use signet_extract::{ExtractedEvent, Extracts};
1616
use signet_zenith::{Zenith::BlockSubmitted, ZenithBlock};
1717
use smallvec::SmallVec;
18-
use std::{borrow::Cow, ops::Deref, sync::Arc};
18+
use std::{ops::Deref, sync::Arc};
1919
use tokio::select;
2020
use tracing::{error, instrument, trace};
2121

@@ -94,7 +94,7 @@ impl From<Vec<Blob>> for Blobs {
9494
/// queries an explorer if it can't find the blob. When Decoder does find a
9595
/// blob, it decodes it and returns the decoded transactions.
9696
#[derive(Debug)]
97-
pub struct BlockExtractor<Pool: TransactionPool> {
97+
pub struct BlockExtractor<Pool> {
9898
pool: Pool,
9999
explorer: foundry_blob_explorers::Client,
100100
client: reqwest::Client,
@@ -103,26 +103,27 @@ pub struct BlockExtractor<Pool: TransactionPool> {
103103
slot_calculator: SlotCalculator,
104104
}
105105

106+
impl BlockExtractor<()> {
107+
/// Returns a new [`BlockExtractorBuilder`].
108+
pub fn builder() -> BlockExtractorBuilder<()> {
109+
BlockExtractorBuilder::default()
110+
}
111+
}
112+
106113
impl<Pool> BlockExtractor<Pool>
107114
where
108115
Pool: TransactionPool,
109116
{
110117
/// new returns a new `Decoder` generic over a `Pool`
111-
pub fn new(
118+
pub const fn new(
112119
pool: Pool,
113120
explorer: foundry_blob_explorers::Client,
114121
cl_client: reqwest::Client,
115-
cl_url: Option<Cow<'static, str>>,
116-
pylon_url: Option<Cow<'static, str>>,
122+
cl_url: Option<url::Url>,
123+
pylon_url: Option<url::Url>,
117124
slot_calculator: SlotCalculator,
118-
) -> Result<Self, url::ParseError> {
119-
let cl_url =
120-
if let Some(url) = cl_url { Some(url::Url::parse(url.as_ref())?) } else { None };
121-
122-
let pylon_url =
123-
if let Some(url) = pylon_url { Some(url::Url::parse(url.as_ref())?) } else { None };
124-
125-
Ok(Self { pool, explorer, client: cl_client, cl_url, pylon_url, slot_calculator })
125+
) -> Self {
126+
Self { pool, explorer, client: cl_client, cl_url, pylon_url, slot_calculator }
126127
}
127128

128129
/// Get blobs from either the pool or the network and decode them,
@@ -412,13 +413,16 @@ mod tests {
412413
let constants: SignetSystemConstants = test.try_into().unwrap();
413414
let calc = SlotCalculator::new(0, 0, 12);
414415

415-
let explorer_url = Cow::Borrowed("https://api.holesky.blobscan.com/");
416-
let client = reqwest::Client::builder().use_rustls_tls().build().unwrap();
417-
let explorer =
418-
foundry_blob_explorers::Client::new_with_client(explorer_url.as_ref(), client.clone());
416+
let explorer_url = "https://api.holesky.blobscan.com/";
417+
let client = reqwest::Client::builder().use_rustls_tls();
419418

420-
let extractor =
421-
BlockExtractor::new(pool.clone(), explorer, client.clone(), None, None, calc)?;
419+
let extractor = BlockExtractor::builder()
420+
.with_pool(pool.clone())
421+
.with_explorer_url(explorer_url)
422+
.with_client_builder(client)
423+
.unwrap()
424+
.with_slot_calculator(calc)
425+
.build()?;
422426

423427
let tx = Transaction::Eip2930(TxEip2930 {
424428
chain_id: 17001,

crates/blobber/src/builder.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
use crate::block_data::BlockExtractor;
2+
use init4_bin_base::utils::calc::SlotCalculator;
3+
use reth::transaction_pool::TransactionPool;
4+
use url::Url;
5+
6+
/// Errors that can occur while building the [`BlockExtractor`] with a
7+
/// [`BlockExtractorBuilder`].
8+
#[derive(Debug, thiserror::Error)]
9+
pub enum BuilderError {
10+
/// The transaction pool was not provided.
11+
#[error("transaction pool is required")]
12+
MissingPool,
13+
/// The explorer URL was not provided or could not be parsed.
14+
#[error("explorer URL is required and must be valid")]
15+
MissingExplorerUrl,
16+
/// The URL provided was invalid.
17+
#[error("invalid URL provided")]
18+
Url(#[from] url::ParseError),
19+
/// The client was not provided.
20+
#[error("client is required")]
21+
MissingClient,
22+
/// The client failed to build.
23+
#[error("failed to build client: {0}")]
24+
Client(#[from] reqwest::Error),
25+
/// The slot calculator was not provided.
26+
#[error("slot calculator is required")]
27+
MissingSlotCalculator,
28+
}
29+
30+
/// Builder for the [`BlockExtractor`].
31+
#[derive(Debug, Default, Clone)]
32+
pub struct BlockExtractorBuilder<Pool> {
33+
pool: Option<Pool>,
34+
explorer_url: Option<String>,
35+
client: Option<reqwest::Client>,
36+
cl_url: Option<String>,
37+
pylon_url: Option<String>,
38+
slot_calculator: Option<SlotCalculator>,
39+
}
40+
41+
impl<Pool> BlockExtractorBuilder<Pool> {
42+
/// Set the transaction pool to use for the extractor.
43+
pub fn with_pool<P2>(self, pool: P2) -> BlockExtractorBuilder<P2> {
44+
BlockExtractorBuilder {
45+
pool: Some(pool),
46+
explorer_url: self.explorer_url,
47+
client: self.client,
48+
cl_url: self.cl_url,
49+
pylon_url: self.pylon_url,
50+
slot_calculator: self.slot_calculator,
51+
}
52+
}
53+
54+
/// Set the transaction pool to use a mock test pool.
55+
#[cfg(feature = "test-utils")]
56+
pub fn with_test_pool(
57+
self,
58+
) -> BlockExtractorBuilder<reth_transaction_pool::test_utils::TestPool> {
59+
self.with_pool(reth_transaction_pool::test_utils::testing_pool())
60+
}
61+
62+
/// Set the blob explorer URL to use for the extractor. This will be used
63+
/// to construct a [`foundry_blob_explorers::Client`].
64+
pub fn with_explorer_url(mut self, explorer_url: &str) -> Self {
65+
self.explorer_url = Some(explorer_url.to_string());
66+
self
67+
}
68+
69+
/// Set the [`reqwest::Client`] to use for the extractor. This client will
70+
/// be used to make requests to the blob explorer, and the CL and Pylon URLs
71+
/// if provided.
72+
pub fn with_client(mut self, client: reqwest::Client) -> Self {
73+
self.client = Some(client);
74+
self
75+
}
76+
77+
/// Set the [`reqwest::Client`] via a [reqwest::ClientBuilder]. This
78+
/// function will immediately build the client and return an error if it
79+
/// fails.
80+
///
81+
/// This client will be used to make requests to the blob explorer, and the
82+
/// CL and Pylon URLs if provided.
83+
pub fn with_client_builder(self, client: reqwest::ClientBuilder) -> Result<Self, BuilderError> {
84+
client.build().map(|client| self.with_client(client)).map_err(Into::into)
85+
}
86+
87+
/// Set the CL URL to use for the extractor.
88+
pub fn with_cl_url(mut self, cl_url: &str) -> Result<Self, BuilderError> {
89+
self.cl_url = Some(cl_url.to_string());
90+
Ok(self)
91+
}
92+
93+
/// Set the Pylon URL to use for the extractor.
94+
pub fn with_pylon_url(mut self, pylon_url: &str) -> Result<Self, BuilderError> {
95+
self.pylon_url = Some(pylon_url.to_string());
96+
Ok(self)
97+
}
98+
99+
/// Set the slot calculator to use for the extractor.
100+
pub const fn with_slot_calculator(
101+
mut self,
102+
slot_calculator: SlotCalculator,
103+
) -> BlockExtractorBuilder<Pool> {
104+
self.slot_calculator = Some(slot_calculator);
105+
self
106+
}
107+
108+
/// Set the slot calculator to use for the extractor, using the Pecornino
109+
/// host configuration.
110+
pub const fn with_pecornino_slots(mut self) -> BlockExtractorBuilder<Pool> {
111+
self.slot_calculator = Some(SlotCalculator::pecorino_host());
112+
self
113+
}
114+
}
115+
116+
impl<Pool: TransactionPool> BlockExtractorBuilder<Pool> {
117+
/// Build the [`BlockExtractor`] with the provided parameters.
118+
pub fn build(self) -> Result<BlockExtractor<Pool>, BuilderError> {
119+
let pool = self.pool.ok_or(BuilderError::MissingPool)?;
120+
121+
let explorer_url = self.explorer_url.ok_or(BuilderError::MissingExplorerUrl)?;
122+
123+
let cl_url = self.cl_url.map(parse_url).transpose()?;
124+
125+
let pylon_url = self.pylon_url.map(parse_url).transpose()?;
126+
127+
let client = self.client.ok_or(BuilderError::MissingClient)?;
128+
129+
let explorer =
130+
foundry_blob_explorers::Client::new_with_client(explorer_url, client.clone());
131+
132+
let slot_calculator = self.slot_calculator.ok_or(BuilderError::MissingSlotCalculator)?;
133+
134+
Ok(BlockExtractor::new(pool, explorer, client, cl_url, pylon_url, slot_calculator))
135+
}
136+
}
137+
138+
fn parse_url(url: String) -> Result<Url, BuilderError> {
139+
Url::parse(url.as_ref()).map_err(BuilderError::Url)
140+
}

crates/blobber/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,12 @@ pub enum BlockExtractionError {
6161

6262
impl BlockExtractionError {
6363
/// Returns true if the error is ignorable
64-
pub fn is_ignorable(&self) -> bool {
64+
pub const fn is_ignorable(&self) -> bool {
6565
matches!(self, Self::Ignorable(_))
6666
}
6767

6868
/// Returns true if the error is unrecoverable
69-
pub fn is_unrecoverable(&self) -> bool {
69+
pub const fn is_unrecoverable(&self) -> bool {
7070
matches!(self, Self::Unrecoverable(_))
7171
}
7272

crates/blobber/src/lib.rs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
//! Contains logic for extracting data from host chain blocks.
1+
#![doc = include_str!("../README.md")]
2+
#![warn(
3+
missing_copy_implementations,
4+
missing_debug_implementations,
5+
missing_docs,
6+
unreachable_pub,
7+
clippy::missing_const_for_fn,
8+
rustdoc::all
9+
)]
10+
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
11+
#![deny(unused_must_use, rust_2018_idioms)]
12+
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
213

314
mod block_data;
415
pub use block_data::{Blobs, BlockExtractor};
516

17+
mod builder;
18+
pub use builder::BlockExtractorBuilder;
19+
620
mod error;
721
pub use error::{BlockExtractionError, ExtractionResult};
822

crates/blobber/src/shim.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ pub struct ExtractableChainShim<'a> {
1818

1919
impl<'a> ExtractableChainShim<'a> {
2020
/// Create a new shim around the given Reth chain.
21-
pub fn new(chain: &'a Chain) -> Self {
21+
pub const fn new(chain: &'a Chain) -> Self {
2222
Self { chain }
2323
}
2424

2525
/// Get a reference to the underlying Reth chain.
26-
pub fn chain(&self) -> &'a Chain {
26+
pub const fn chain(&self) -> &'a Chain {
2727
self.chain
2828
}
2929
}

crates/db/src/provider.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,26 @@ where
138138
}
139139

140140
/// Inserts the zenith block into the database, always modifying the following tables:
141-
/// * [`JournalHashes`](crate::db::JournalHashes)
141+
/// * [`JournalHashes`]
142142
/// * [`CanonicalHeaders`](tables::CanonicalHeaders)
143143
/// * [`Headers`](tables::Headers)
144144
/// * [`HeaderTerminalDifficulties`](tables::HeaderTerminalDifficulties)
145145
/// * [`HeaderNumbers`](tables::HeaderNumbers)
146-
/// * [`BlockBodyIndices`](tables::BlockBodyIndices) (through [`RuWriter::append_signet_block_body`])
146+
/// * [`BlockBodyIndices`](tables::BlockBodyIndices) (through
147+
/// [`RuWriter::append_signet_block_body`])
147148
///
148-
/// If there are transactions in the block, the following tables will be modified:
149-
/// * [`Transactions`](tables::Transactions) (through [`RuWriter::append_signet_block_body`])
150-
/// * [`TransactionBlocks`](tables::TransactionBlocks) (through [`RuWriter::append_signet_block_body`])
149+
/// If there are transactions in the block, the following tables will be
150+
/// modified:
151+
/// * [`Transactions`](tables::Transactions) (through
152+
/// [`RuWriter::append_signet_block_body`])
153+
/// * [`TransactionBlocks`](tables::TransactionBlocks) (through
154+
/// [`RuWriter::append_signet_block_body`])
151155
///
152-
/// If the provider has __not__ configured full sender pruning, this will modify
153-
/// [`TransactionSenders`](tables::TransactionSenders).
156+
/// If the provider has __not__ configured full sender pruning, this will
157+
/// modify [`TransactionSenders`](tables::TransactionSenders).
154158
///
155-
/// If the provider has __not__ configured full transaction lookup pruning, this will modify
156-
/// [`TransactionHashNumbers`](tables::TransactionHashNumbers).
159+
/// If the provider has __not__ configured full transaction lookup pruning,
160+
/// this will modify [`TransactionHashNumbers`](tables::TransactionHashNumbers).
157161
///
158162
/// Ommers and withdrawals are not inserted, as Signet does not use them.
159163
fn insert_signet_block(
@@ -371,6 +375,8 @@ where
371375

372376
/// Get [`Passage::EnterToken`], [`Passage::Enter`] and
373377
/// [`Transactor::Transact`] events.
378+
///
379+
/// [`Transactor::Transact`]: signet_zenith::Transactor::Transact
374380
fn get_signet_events(
375381
&self,
376382
range: RangeInclusive<BlockNumber>,
@@ -394,6 +400,8 @@ where
394400

395401
/// Remove [`Passage::EnterToken`], [`Passage::Enter`] and
396402
/// [`Transactor::Transact`] events above the specified height from the DB.
403+
///
404+
/// [`Transactor::Transact`]: signet_zenith::Transactor::Transact
397405
fn remove_signet_events_above(
398406
&self,
399407
target: BlockNumber,

crates/rpc/src/ctx.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ where
758758
/// - underlying database error
759759
/// - amount of matches exceeds configured limit
760760
///
761-
/// https://github.com/paradigmxyz/reth/blob/d01658e516abbf2a1a76855a26d7123286865f22/crates/rpc/rpc/src/eth/filter.rs#L506
761+
// https://github.com/paradigmxyz/reth/blob/d01658e516abbf2a1a76855a26d7123286865f22/crates/rpc/rpc/src/eth/filter.rs#L506
762762
async fn get_logs_in_block_range(
763763
&self,
764764
filter: &Filter,

0 commit comments

Comments
 (0)