Skip to content

Commit 43f4e53

Browse files
committed
feat: hazmat router
1 parent 59dd6fe commit 43f4e53

File tree

8 files changed

+269
-21
lines changed

8 files changed

+269
-21
lines changed

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ signet-tx-cache = { git = "https://github.com/init4tech/signet-sdk", tag = "v0.8
4848
signet-types = { git = "https://github.com/init4tech/signet-sdk", tag = "v0.8.3" }
4949
signet-zenith = { git = "https://github.com/init4tech/signet-sdk", tag = "v0.8.3" }
5050

51-
5251
# ajj
5352
ajj = { version = "0.3.4" }
5453

crates/rpc/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ trevm.workspace = true
2020
alloy.workspace = true
2121
reth.workspace = true
2222
reth-chainspec.workspace = true
23+
reth-db.workspace = true
24+
reth-db-common.workspace = true
2325
reth-evm-ethereum.workspace = true
2426
reth-node-api.workspace = true
2527
reth-rpc-eth-api.workspace = true
@@ -35,6 +37,7 @@ tokio = { workspace = true, features = ["macros"] }
3537
tokio-util = "0.7.13"
3638
tower-http = { version = "0.6.2", features = ["cors"] }
3739
tracing.workspace = true
40+
serde_json.workspace = true
3841

3942
[dev-dependencies]
4043
signet-zenith.workspace = true

crates/rpc/src/ctx.rs

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,11 @@ use alloy::{
1414
};
1515
use reth::{
1616
core::primitives::SignerRecoverable,
17-
primitives::{Block, EthPrimitives, Receipt, Recovered, RecoveredBlock, TransactionSigned},
17+
primitives::{Block, Receipt, Recovered, RecoveredBlock, TransactionSigned},
1818
providers::{
1919
BlockHashReader, BlockIdReader, BlockNumReader, CanonStateSubscriptions, HeaderProvider,
20-
ProviderBlock, ProviderError, ProviderReceipt, ReceiptProvider, StateProviderFactory,
21-
TransactionsProvider,
22-
providers::{BlockchainProvider, ProviderNodeTypes},
20+
ProviderBlock, ProviderError, ProviderFactory, ProviderReceipt, ProviderResult,
21+
ReceiptProvider, StateProviderFactory, TransactionsProvider, providers::BlockchainProvider,
2322
},
2423
revm::{database::StateProviderDatabase, primitives::hardfork::SpecId},
2524
rpc::{
@@ -80,17 +79,16 @@ where
8079
pub fn new<Tasks>(
8180
host: Host,
8281
constants: SignetSystemConstants,
83-
provider: BlockchainProvider<Signet>,
82+
factory: ProviderFactory<Signet>,
8483
eth_config: EthConfig,
8584
tx_cache: Option<TxCache>,
8685
spawner: Tasks,
87-
) -> Self
86+
) -> ProviderResult<Self>
8887
where
8988
Tasks: TaskSpawner + Clone + 'static,
9089
{
91-
let inner = RpcCtxInner::new(host, constants, provider, eth_config, tx_cache, spawner);
92-
93-
Self { inner: Arc::new(inner) }
90+
RpcCtxInner::new(host, constants, factory, eth_config, tx_cache, spawner)
91+
.map(|inner| Self { inner: Arc::new(inner) })
9492
}
9593
}
9694

@@ -136,16 +134,16 @@ where
136134
pub fn new<Tasks>(
137135
host: Host,
138136
constants: SignetSystemConstants,
139-
provider: BlockchainProvider<Signet>,
137+
factory: ProviderFactory<Signet>,
140138
eth_config: EthConfig,
141139
tx_cache: Option<TxCache>,
142140
spawner: Tasks,
143-
) -> Self
141+
) -> ProviderResult<Self>
144142
where
145143
Tasks: TaskSpawner + Clone + 'static,
146144
{
147-
let signet = SignetCtx::new(constants, provider, eth_config, tx_cache, spawner);
148-
Self { host, signet }
145+
SignetCtx::new(constants, factory, eth_config, tx_cache, spawner)
146+
.map(|signet| Self { host, signet })
149147
}
150148

151149
pub const fn host(&self) -> &Host {
@@ -194,6 +192,7 @@ where
194192
eth_config: EthConfig,
195193

196194
// State stuff
195+
factory: ProviderFactory<Inner>,
197196
provider: BlockchainProvider<Inner>,
198197
cache: EthStateCache<
199198
ProviderBlock<BlockchainProvider<Inner>>,
@@ -217,20 +216,22 @@ where
217216

218217
impl<Inner> SignetCtx<Inner>
219218
where
220-
Inner: ProviderNodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>,
219+
Inner: Pnt,
221220
{
222221
/// Instantiate a new `SignetCtx`, spawning necessary tasks to keep the
223222
/// relevant caches up to date.
224223
pub fn new<Tasks>(
225224
constants: SignetSystemConstants,
226-
provider: BlockchainProvider<Inner>,
225+
factory: ProviderFactory<Inner>,
227226
eth_config: EthConfig,
228227
tx_cache: Option<TxCache>,
229228
spawner: Tasks,
230-
) -> Self
229+
) -> ProviderResult<Self>
231230
where
232231
Tasks: TaskSpawner + Clone + 'static,
233232
{
233+
let provider = BlockchainProvider::new(factory.clone())?;
234+
234235
let cache = EthStateCache::spawn_with(provider.clone(), eth_config.cache, spawner.clone());
235236
let gas_oracle =
236237
GasPriceOracle::new(provider.clone(), eth_config.gas_oracle, cache.clone());
@@ -249,8 +250,9 @@ where
249250

250251
let subs = SubscriptionManager::new(provider.clone(), eth_config.stale_filter_ttl);
251252

252-
Self {
253+
Ok(Self {
253254
constants,
255+
factory,
254256
provider,
255257
eth_config,
256258
cache,
@@ -260,14 +262,19 @@ where
260262
filters,
261263
subs,
262264
_pd: PhantomData,
263-
}
265+
})
264266
}
265267

266268
/// Access the signet constants
267269
pub const fn constants(&self) -> &SignetSystemConstants {
268270
&self.constants
269271
}
270272

273+
/// Access the signet [`ProviderFactory`].
274+
pub const fn factory(&self) -> &ProviderFactory<Inner> {
275+
&self.factory
276+
}
277+
271278
/// Access the signet DB
272279
pub const fn provider(&self) -> &BlockchainProvider<Inner> {
273280
&self.provider

crates/rpc/src/inspect/db.rs

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
use crate::Pnt;
2+
use ajj::serde_json;
3+
use eyre::WrapErr;
4+
use reth::providers::ProviderFactory;
5+
use reth_db::{Database, TableViewer, table::Table};
6+
use reth_db_common::{DbTool, ListFilter};
7+
use std::sync::OnceLock;
8+
use tracing::instrument;
9+
10+
/// Modeled on the `Command` struct from `reth/crates/cli/commands/src/db/list.rs`
11+
#[derive(Debug, serde::Deserialize)]
12+
pub(crate) struct DbArgs(
13+
/// The table name
14+
String, // 0
15+
/// Skip first N entries
16+
#[serde(default)]
17+
usize, // 1
18+
/// How many items to take from the walker
19+
#[serde(default)]
20+
Option<usize>, // 2
21+
/// Search parameter for both keys and values. Prefix it with `0x` to search for binary data,
22+
/// and text otherwise.
23+
///
24+
/// ATTENTION! For compressed tables (`Transactions` and `Receipts`), there might be
25+
/// missing results since the search uses the raw uncompressed value from the database.
26+
#[serde(default)]
27+
Option<String>, // 3
28+
);
29+
30+
impl DbArgs {
31+
/// Get the table name.
32+
pub(crate) fn table_name(&self) -> &str {
33+
&self.0
34+
}
35+
36+
/// Parse the table name into a [`reth_db::Tables`] enum.
37+
pub(crate) fn table(&self) -> Result<reth_db::Tables, String> {
38+
self.table_name().parse()
39+
}
40+
41+
/// Get the skip value.
42+
pub(crate) fn skip(&self) -> usize {
43+
self.1
44+
}
45+
46+
/// Get the length value.
47+
pub(crate) fn len(&self) -> usize {
48+
self.2.unwrap_or(5)
49+
}
50+
51+
/// Get the search value.
52+
pub(crate) fn search(&self) -> Vec<u8> {
53+
self.3
54+
.as_ref()
55+
.map(|search| {
56+
if let Some(search) = search.strip_prefix("0x") {
57+
return alloy::primitives::hex::decode(search).unwrap();
58+
}
59+
search.as_bytes().to_vec()
60+
})
61+
.unwrap_or_default()
62+
}
63+
64+
/// Generate [`ListFilter`] from command.
65+
pub(crate) fn list_filter(&self) -> ListFilter {
66+
ListFilter {
67+
skip: self.skip(),
68+
len: self.len(),
69+
search: self.search(),
70+
min_row_size: 0,
71+
min_key_size: 0,
72+
min_value_size: 0,
73+
reverse: false,
74+
only_count: false,
75+
}
76+
}
77+
}
78+
79+
pub(crate) struct ListTableViewer<'a, 'b, N: Pnt> {
80+
pub(crate) factory: &'b ProviderFactory<N>,
81+
pub(crate) args: &'a DbArgs,
82+
83+
pub(crate) output: OnceLock<Box<serde_json::value::RawValue>>,
84+
}
85+
86+
impl<'a, 'b, N: Pnt> ListTableViewer<'a, 'b, N> {
87+
/// Create a new `ListTableViewer`.
88+
pub(crate) fn new(factory: &'b ProviderFactory<N>, args: &'a DbArgs) -> Self {
89+
Self { factory, args, output: Default::default() }
90+
}
91+
92+
/// Take the output if it has been initialized, otherwise return `None`.
93+
pub(crate) fn take_output(self) -> Option<Box<serde_json::value::RawValue>> {
94+
self.output.into_inner()
95+
}
96+
}
97+
98+
impl<N: Pnt> TableViewer<()> for ListTableViewer<'_, '_, N> {
99+
type Error = eyre::Report;
100+
101+
#[instrument(skip(self), err)]
102+
fn view<T: Table>(&self) -> eyre::Result<()> {
103+
let tool = DbTool { provider_factory: self.factory.clone() };
104+
105+
self.factory.db_ref().view(|tx| {
106+
let table_db =
107+
tx.inner.open_db(Some(&self.args.table_name())).wrap_err("Could not open db.")?;
108+
let stats = tx
109+
.inner
110+
.db_stat(&table_db)
111+
.wrap_err(format!("Could not find table: {}", stringify!($table)))?;
112+
let total_entries = stats.entries();
113+
let final_entry_idx = total_entries.saturating_sub(1);
114+
eyre::ensure!(
115+
self.args.skip() >= final_entry_idx,
116+
"Skip value {} is greater than total entries {}",
117+
self.args.skip(),
118+
total_entries
119+
);
120+
121+
let list_filter = self.args.list_filter();
122+
123+
let (list, _) = tool.list::<T>(&list_filter)?;
124+
125+
let json =
126+
serde_json::value::to_raw_value(&list).wrap_err("Failed to serialize list")?;
127+
128+
self.output.get_or_init(|| json);
129+
130+
Ok(())
131+
})??;
132+
133+
Ok(())
134+
}
135+
}
136+
137+
// Some code in this file is adapted from github.com/paradigmxyz/reth.
138+
//
139+
// Particularly the `reth/crates/cli/commands/src/db/list.rs` file. It is
140+
// reproduced here under the terms of the MIT license,
141+
//
142+
// The MIT License (MIT)
143+
//
144+
// Copyright (c) 2022-2025 Reth Contributors
145+
//
146+
// Permission is hereby granted, free of charge, to any person obtaining a copy
147+
// of this software and associated documentation files (the "Software"), to deal
148+
// in the Software without restriction, including without limitation the rights
149+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
150+
// copies of the Software, and to permit persons to whom the Software is
151+
// furnished to do so, subject to the following conditions:
152+
//
153+
// The above copyright notice and this permission notice shall be included in
154+
// all copies or substantial portions of the Software.
155+
//
156+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
157+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
158+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
159+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
160+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
161+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
162+
// THE SOFTWARE.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
use crate::{
2+
Pnt, RpcCtx,
3+
inspect::db::{DbArgs, ListTableViewer},
4+
util::{await_jh_option_response, response_tri},
5+
};
6+
use ajj::{HandlerCtx, ResponsePayload};
7+
use reth_node_api::FullNodeComponents;
8+
9+
/// Handler for the `db` endpoint in the `inspect` module.
10+
pub(super) async fn db<Host, Signet>(
11+
hctx: HandlerCtx,
12+
args: DbArgs,
13+
ctx: RpcCtx<Host, Signet>,
14+
) -> ResponsePayload<Box<serde_json::value::RawValue>, String>
15+
where
16+
Host: FullNodeComponents,
17+
Signet: Pnt,
18+
{
19+
let task = async move {
20+
let table: reth_db::Tables = response_tri!(args.table(), "invalid table name");
21+
22+
let viewer = ListTableViewer::new(ctx.signet().factory(), &args);
23+
24+
response_tri!(table.view(&viewer), "Failed to view table");
25+
26+
let Some(output) = viewer.take_output() else {
27+
return ResponsePayload::internal_error_message(
28+
"No output generated. The task may have panicked or been cancelled. This is a bug, please report it.".into(),
29+
);
30+
};
31+
32+
ResponsePayload::Success(output)
33+
};
34+
35+
await_jh_option_response!(hctx.spawn_blocking(task))
36+
}

crates/rpc/src/inspect/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
pub(crate) mod db;
2+
3+
mod endpoints;
4+
5+
use crate::{Pnt, RpcCtx};
6+
use reth_node_api::FullNodeComponents;
7+
8+
/// Instantiate the `inspect` API router.
9+
pub fn inspect<Host, Signet>() -> ajj::Router<RpcCtx<Host, Signet>>
10+
where
11+
Host: FullNodeComponents,
12+
Signet: Pnt,
13+
{
14+
ajj::Router::new().route("db", endpoints::db::<Host, Signet>)
15+
}

crates/rpc/src/lib.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ pub use eth::{CallErrorData, EthError, eth};
6060
mod signet;
6161
pub use signet::{error::SignetError, signet};
6262

63+
mod inspect;
64+
pub use inspect::inspect;
65+
6366
mod interest;
6467

6568
pub mod receipts;
@@ -82,3 +85,12 @@ where
8285
{
8386
ajj::Router::new().nest("eth", eth::<Host, Signet>()).nest("signet", signet::<Host, Signet>())
8487
}
88+
89+
/// Create a new hazmat router that exposes the `inspect` API.
90+
pub fn hazmat_router<Host, Signet>() -> Router<ctx::RpcCtx<Host, Signet>>
91+
where
92+
Host: FullNodeComponents,
93+
Signet: Pnt,
94+
{
95+
ajj::Router::new().nest("inspect", inspect::inspect::<Host, Signet>())
96+
}

0 commit comments

Comments
 (0)