Skip to content

Commit cde20ff

Browse files
authored
feat: add builder for signet node to aid instantiation and genesis init (#49)
1 parent 1e8891e commit cde20ff

File tree

6 files changed

+354
-62
lines changed

6 files changed

+354
-62
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = ["crates/*"]
33
resolver = "2"
44

55
[workspace.package]
6-
version = "0.14.1"
6+
version = "0.14.2"
77
edition = "2024"
88
rust-version = "1.88"
99
authors = ["init4"]

crates/node-tests/src/context.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use reth_db::{PlainAccountState, transaction::DbTxMut};
2424
use reth_exex_test_utils::{Adapter, TestExExHandle, TmpDB as TmpDb};
2525
use reth_node_api::FullNodeComponents;
2626
use signet_db::DbProviderExt;
27-
use signet_node::SignetNode;
27+
use signet_node::SignetNodeBuilder;
2828
use signet_node_config::test_utils::test_config;
2929
use signet_node_types::{NodeStatus, SignetNodeTypes};
3030
use signet_test_utils::contracts::counter::COUNTER_DEPLOY_CODE;
@@ -104,15 +104,12 @@ impl SignetTestContext {
104104

105105
let alias_oracle: Arc<Mutex<HashSet<Address>>> = Arc::new(Mutex::new(HashSet::default()));
106106

107-
// instantiate Signet Node, booting rpc
108-
let (node, mut node_status) = SignetNode::new(
109-
ctx,
110-
cfg.clone(),
111-
factory.clone(),
112-
Arc::clone(&alias_oracle),
113-
Default::default(),
114-
)
115-
.unwrap();
107+
let (node, mut node_status) = SignetNodeBuilder::new(cfg.clone())
108+
.with_ctx(ctx)
109+
.with_factory(factory.clone())
110+
.with_alias_oracle(Arc::clone(&alias_oracle))
111+
.build()
112+
.unwrap();
116113

117114
// Spawn the node, and wait for it to indicate RPC readiness.
118115
let node = tokio::spawn(node.start());

crates/node-tests/tests/db.rs

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use alloy::primitives::{Address, hex, map::HashSet};
1+
use alloy::primitives::hex;
22
use reth::providers::BlockReader;
33
use serial_test::serial;
4-
use signet_node::SignetNode;
4+
use signet_node::SignetNodeBuilder;
55
use signet_node_config::test_utils::test_config;
66
use signet_node_tests::utils::create_test_provider_factory_with_chain_spec;
77
use std::sync::Arc;
@@ -17,14 +17,11 @@ async fn test_genesis() {
1717
assert_eq!(chain_spec.genesis().config.chain_id, consts.unwrap().ru_chain_id());
1818

1919
let factory = create_test_provider_factory_with_chain_spec(chain_spec.clone());
20-
let (_, _) = SignetNode::<_, _, HashSet<Address>>::new(
21-
ctx,
22-
cfg.clone(),
23-
factory.clone(),
24-
Default::default(),
25-
Default::default(),
26-
)
27-
.unwrap();
20+
let (_, _) = SignetNodeBuilder::new(cfg.clone())
21+
.with_ctx(ctx)
22+
.with_factory(factory.clone())
23+
.build()
24+
.unwrap();
2825

2926
let genesis_block = factory.provider().unwrap().block_by_number(0).unwrap().unwrap();
3027

crates/node/src/builder.rs

Lines changed: 323 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,323 @@
1+
#![allow(clippy::type_complexity)]
2+
3+
use crate::{GENESIS_JOURNAL_HASH, SignetNode};
4+
use eyre::OptionExt;
5+
use reth::{
6+
primitives::EthPrimitives,
7+
providers::{BlockHashReader, ProviderFactory, StateProviderFactory},
8+
};
9+
use reth_db::transaction::DbTxMut;
10+
use reth_db_common::init;
11+
use reth_exex::ExExContext;
12+
use reth_node_api::{FullNodeComponents, NodeTypes};
13+
use signet_block_processor::AliasOracleFactory;
14+
use signet_db::DbProviderExt;
15+
use signet_node_config::SignetNodeConfig;
16+
use signet_node_types::{NodeStatus, NodeTypesDbTrait, SignetNodeTypes};
17+
use std::sync::Arc;
18+
19+
/// A type that does not implement [`AliasOracleFactory`].
20+
#[derive(Debug, Clone, Copy)]
21+
pub struct NotAnAof;
22+
23+
/// A type that does not implement [`NodeTypesDbTrait`].
24+
#[derive(Debug, Clone, Copy)]
25+
pub struct NotADb;
26+
27+
/// Builder for [`SignetNode`]. This is the main way to create a signet node.
28+
///
29+
/// The builder requires the following components to be set before building:
30+
/// - An [`ExExContext`], via [`Self::with_ctx`].
31+
/// - A [`ProviderFactory`] for the signet node's database.
32+
/// - This can be provided directly via [`Self::with_factory`].
33+
/// - Or created from a database implementing [`NodeTypesDbTrait`] via
34+
/// [`Self::with_db`].
35+
/// - If not set directly, can be created from the config via
36+
/// [`Self::with_config_db`].
37+
/// - An [`AliasOracleFactory`], via [`Self::with_alias_oracle`].
38+
/// - If not set, a default one will be created from the [`ExExContext`]'s
39+
/// provider.
40+
/// - A `reqwest::Client`, via [`Self::with_client`].
41+
/// - If not set, a default client will be created.
42+
pub struct SignetNodeBuilder<Host = (), Db = NotADb, Aof = NotAnAof> {
43+
config: SignetNodeConfig,
44+
alias_oracle: Option<Aof>,
45+
ctx: Option<Host>,
46+
factory: Option<Db>,
47+
client: Option<reqwest::Client>,
48+
}
49+
50+
impl<Host, Db, Aof> core::fmt::Debug for SignetNodeBuilder<Host, Db, Aof> {
51+
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
52+
f.debug_struct("SignetNodeBuilder").finish_non_exhaustive()
53+
}
54+
}
55+
56+
impl SignetNodeBuilder {
57+
/// Create a new SignetNodeBuilder instance.
58+
pub const fn new(config: SignetNodeConfig) -> Self {
59+
Self { config, alias_oracle: None, ctx: None, factory: None, client: None }
60+
}
61+
}
62+
63+
impl<Host, Db, Aof> SignetNodeBuilder<Host, Db, Aof> {
64+
/// Set the DB for the signet node.
65+
pub fn with_db<NewDb: NodeTypesDbTrait>(
66+
self,
67+
db: NewDb,
68+
) -> eyre::Result<SignetNodeBuilder<Host, ProviderFactory<SignetNodeTypes<NewDb>>, Aof>> {
69+
let factory = ProviderFactory::new(
70+
db,
71+
self.config.chain_spec().clone(),
72+
self.config.static_file_rw()?,
73+
);
74+
75+
Ok(SignetNodeBuilder {
76+
config: self.config,
77+
alias_oracle: self.alias_oracle,
78+
ctx: self.ctx,
79+
factory: Some(factory),
80+
client: self.client,
81+
})
82+
}
83+
84+
/// Set the DB for the signet node from config, opening the mdbx database.
85+
pub fn with_config_db(
86+
self,
87+
) -> eyre::Result<
88+
SignetNodeBuilder<Host, ProviderFactory<SignetNodeTypes<Arc<reth_db::DatabaseEnv>>>, Aof>,
89+
> {
90+
let factory = ProviderFactory::new_with_database_path(
91+
self.config.database_path(),
92+
self.config.chain_spec().clone(),
93+
reth_db::mdbx::DatabaseArguments::default(),
94+
self.config.static_file_rw().unwrap(),
95+
)?;
96+
Ok(SignetNodeBuilder {
97+
config: self.config,
98+
alias_oracle: self.alias_oracle,
99+
ctx: self.ctx,
100+
factory: Some(factory),
101+
client: self.client,
102+
})
103+
}
104+
105+
/// Set the provider factory for the signet node.
106+
///
107+
/// This is an alternative to [`Self::with_db`] and
108+
/// [`Self::with_config_db`].
109+
pub fn with_factory<NewDb>(
110+
self,
111+
factory: ProviderFactory<SignetNodeTypes<NewDb>>,
112+
) -> SignetNodeBuilder<Host, ProviderFactory<SignetNodeTypes<NewDb>>, Aof>
113+
where
114+
NewDb: NodeTypesDbTrait,
115+
{
116+
SignetNodeBuilder {
117+
config: self.config,
118+
alias_oracle: self.alias_oracle,
119+
ctx: self.ctx,
120+
factory: Some(factory),
121+
client: self.client,
122+
}
123+
}
124+
125+
/// Set the [`ExExContext`] for the signet node.
126+
pub fn with_ctx<NewHost>(
127+
self,
128+
ctx: ExExContext<NewHost>,
129+
) -> SignetNodeBuilder<ExExContext<NewHost>, Db, Aof>
130+
where
131+
NewHost: FullNodeComponents,
132+
NewHost::Types: NodeTypes<Primitives = EthPrimitives>,
133+
{
134+
SignetNodeBuilder {
135+
config: self.config,
136+
alias_oracle: self.alias_oracle,
137+
ctx: Some(ctx),
138+
factory: self.factory,
139+
client: self.client,
140+
}
141+
}
142+
143+
/// Set the [`AliasOracleFactory`] for the signet node.
144+
pub fn with_alias_oracle<NewAof: AliasOracleFactory>(
145+
self,
146+
alias_oracle: NewAof,
147+
) -> SignetNodeBuilder<Host, Db, NewAof> {
148+
SignetNodeBuilder {
149+
config: self.config,
150+
alias_oracle: Some(alias_oracle),
151+
ctx: self.ctx,
152+
factory: self.factory,
153+
client: self.client,
154+
}
155+
}
156+
157+
/// Set the reqwest client for the signet node.
158+
pub fn with_client(mut self, client: reqwest::Client) -> SignetNodeBuilder<Host, Db, Aof> {
159+
self.client = Some(client);
160+
self
161+
}
162+
}
163+
164+
impl<Host, Db, Aof> SignetNodeBuilder<ExExContext<Host>, ProviderFactory<SignetNodeTypes<Db>>, Aof>
165+
where
166+
Host: FullNodeComponents,
167+
Host::Types: NodeTypes<Primitives = EthPrimitives>,
168+
Db: NodeTypesDbTrait,
169+
{
170+
/// Prebuild checks for the signet node builder. Shared by all build
171+
/// commands.
172+
fn prebuild(&mut self) -> eyre::Result<()> {
173+
self.client.get_or_insert_default();
174+
self.ctx.as_ref().ok_or_eyre("Launch context must be set")?;
175+
let factory = self.factory.as_ref().ok_or_eyre("Provider factory must be set")?;
176+
177+
// This check appears redundant with the same check made in
178+
// `init_genesis`, but is not. We init the genesis DB state but then we
179+
// drop some of it, and reuse those tables for our own nefarious
180+
// purposes. If we attempt to drop those tables AFTER we have reused
181+
// them, we will get a key deser error (as the tables will contain keys
182+
// the old schema does not permit). This check ensures we only attempt
183+
// to drop the tables once.
184+
if matches!(
185+
factory.block_hash(0),
186+
Ok(None)
187+
| Err(reth::providers::ProviderError::MissingStaticFileBlock(
188+
reth::primitives::StaticFileSegment::Headers,
189+
0
190+
))
191+
) {
192+
init::init_genesis(factory)?;
193+
194+
factory.provider_rw()?.update(
195+
|writer: &mut reth::providers::DatabaseProviderRW<Db, SignetNodeTypes<Db>>| {
196+
writer.tx_mut().clear::<reth_db::tables::HashedAccounts>()?;
197+
writer.tx_mut().clear::<reth_db::tables::HashedStorages>()?;
198+
writer.tx_mut().clear::<reth_db::tables::AccountsTrie>()?;
199+
200+
writer.tx_ref().put::<signet_db::JournalHashes>(0, GENESIS_JOURNAL_HASH)?;
201+
// we do not need to pre-populate the `ZenithHeaders` or
202+
// `SignetEvents` tables, as missing data is legal in those
203+
// tables
204+
205+
Ok(())
206+
},
207+
)?;
208+
}
209+
210+
Ok(())
211+
}
212+
}
213+
214+
impl<Host> SignetNodeBuilder<ExExContext<Host>, NotADb, NotAnAof>
215+
where
216+
Host: FullNodeComponents,
217+
Host::Types: NodeTypes<Primitives = EthPrimitives>,
218+
{
219+
/// Build the node. This performs the following steps:
220+
///
221+
/// - Runs prebuild checks.
222+
/// - Inits the rollup DB from genesis if needed.
223+
/// - Creates a default `AliasOracleFactory` from the host DB.
224+
///
225+
/// # Panics
226+
///
227+
/// If called outside a tokio runtime.
228+
pub fn build(
229+
self,
230+
) -> eyre::Result<(
231+
SignetNode<Host, Arc<reth_db::DatabaseEnv>, Box<dyn StateProviderFactory>>,
232+
tokio::sync::watch::Receiver<NodeStatus>,
233+
)> {
234+
self.with_config_db()?.build()
235+
}
236+
}
237+
238+
impl<Host, Aof> SignetNodeBuilder<ExExContext<Host>, NotADb, Aof>
239+
where
240+
Host: FullNodeComponents,
241+
Host::Types: NodeTypes<Primitives = EthPrimitives>,
242+
Aof: AliasOracleFactory,
243+
{
244+
/// Build the node. This performs the following steps:
245+
///
246+
/// - Runs prebuild checks.
247+
/// - Inits the rollup DB from genesis if needed.
248+
///
249+
/// # Panics
250+
///
251+
/// If called outside a tokio runtime.
252+
pub fn build(
253+
self,
254+
) -> eyre::Result<(
255+
SignetNode<Host, Arc<reth_db::DatabaseEnv>, Aof>,
256+
tokio::sync::watch::Receiver<NodeStatus>,
257+
)> {
258+
self.with_config_db()?.build()
259+
}
260+
}
261+
262+
impl<Host, Db> SignetNodeBuilder<ExExContext<Host>, ProviderFactory<SignetNodeTypes<Db>>, NotAnAof>
263+
where
264+
Host: FullNodeComponents<Provider: StateProviderFactory>,
265+
Host::Types: NodeTypes<Primitives = EthPrimitives>,
266+
Db: NodeTypesDbTrait,
267+
{
268+
/// Build the node. This performs the following steps:
269+
///
270+
/// - Runs prebuild checks.
271+
/// - Inits the rollup DB from genesis if needed.
272+
/// - Creates a default `AliasOracleFactory` from the host DB.
273+
///
274+
/// # Panics
275+
///
276+
/// If called outside a tokio runtime.
277+
pub fn build(
278+
mut self,
279+
) -> eyre::Result<(SignetNode<Host, Db>, tokio::sync::watch::Receiver<NodeStatus>)> {
280+
self.prebuild()?;
281+
// This allows the node to look up contract status.
282+
let ctx = self.ctx.unwrap();
283+
let provider = ctx.provider().clone();
284+
let alias_oracle: Box<dyn StateProviderFactory> = Box::new(provider);
285+
286+
SignetNode::new_unsafe(
287+
ctx,
288+
self.config,
289+
self.factory.unwrap(),
290+
alias_oracle,
291+
self.client.unwrap(),
292+
)
293+
}
294+
}
295+
296+
impl<Host, Db, Aof> SignetNodeBuilder<ExExContext<Host>, ProviderFactory<SignetNodeTypes<Db>>, Aof>
297+
where
298+
Host: FullNodeComponents,
299+
Host::Types: NodeTypes<Primitives = EthPrimitives>,
300+
Db: NodeTypesDbTrait,
301+
Aof: AliasOracleFactory,
302+
{
303+
/// Build the node. This performs the following steps:
304+
///
305+
/// - Runs prebuild checks.
306+
/// - Inits the rollup DB from genesis if needed.
307+
///
308+
/// # Panics
309+
///
310+
/// If called outside a tokio runtime.
311+
pub fn build(
312+
mut self,
313+
) -> eyre::Result<(SignetNode<Host, Db, Aof>, tokio::sync::watch::Receiver<NodeStatus>)> {
314+
self.prebuild()?;
315+
SignetNode::new_unsafe(
316+
self.ctx.unwrap(),
317+
self.config,
318+
self.factory.unwrap(),
319+
self.alias_oracle.unwrap(),
320+
self.client.unwrap(),
321+
)
322+
}
323+
}

0 commit comments

Comments
 (0)