Skip to content

Commit 2b4bd7e

Browse files
committed
feat(bin): kona consensus node
1 parent 3e89357 commit 2b4bd7e

File tree

17 files changed

+3118
-455
lines changed

17 files changed

+3118
-455
lines changed

Cargo.lock

Lines changed: 1435 additions & 426 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ codegen-units = 1
5353

5454
[workspace.dependencies]
5555
# Shared
56-
base-access-lists = { path = "crates/shared/access-lists" }
5756
base-bundles = { path = "crates/shared/bundles" }
5857
base-cli-utils = { path = "crates/shared/cli-utils" }
5958
base-flashtypes = { path = "crates/shared/flashtypes" }
6059
base-primitives = { path = "crates/shared/primitives" }
60+
base-access-lists = { path = "crates/shared/access-lists" }
6161
base-reth-rpc-types = { path = "crates/shared/reth-rpc-types" }
6262
base-jwt = { path = "crates/shared/jwt" }
6363
# Client
@@ -67,6 +67,20 @@ base-metering = { path = "crates/client/metering" }
6767
base-txpool = { path = "crates/client/txpool" }
6868
base-flashblocks = { path = "crates/client/flashblocks" }
6969

70+
# Kona
71+
kona-rpc = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
72+
kona-disc = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
73+
kona-engine = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
74+
kona-derive = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
75+
kona-gossip = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
76+
kona-genesis = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
77+
kona-registry = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
78+
kona-node-service = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
79+
kona-providers-alloy = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
80+
kona-cli = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
81+
kona-peers = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
82+
kona-sources = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
83+
7084
# reth
7185
reth-ipc = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" }
7286
reth-evm = { git = "https://github.com/paradigmxyz/reth", tag = "v1.9.3" }
@@ -188,21 +202,23 @@ alloy-rpc-types-beacon = { version = "1.0.41", features = ["ssz"] }
188202
# op-alloy
189203
op-alloy-flz = { version = "0.13.1", default-features = false }
190204
op-alloy-network = { version = "0.22.0", default-features = false }
205+
op-alloy-provider = { version = "0.22.0", default-features = false }
191206
op-alloy-rpc-types = { version = "0.22.0", default-features = false }
192207
op-alloy-consensus = { version = "0.22.0", default-features = false }
193208
op-alloy-rpc-jsonrpsee = { version = "0.22.0", default-features = false }
194209
op-alloy-rpc-types-engine = { version = "0.22.0", default-features = false }
195-
op-alloy-provider = { version = "0.22.0", default-features = false }
196210
alloy-op-evm = { version = "0.23.3", default-features = false }
197211
alloy-op-hardforks = "0.4.4"
198212

213+
# rollup-boost
214+
rollup-boost = { git = "https://github.com/flashbots/rollup-boost", tag = "v0.7.11" }
215+
# rollup-boost version that matches kona's dependency (used for consensus binary)
216+
rollup-boost-kona = { package = "rollup-boost", git = "https://github.com/flashbots/rollup-boost.git", rev = "7fda98f" }
217+
218+
199219
# op-revm
200220
op-revm = { version = "12.0.2", default-features = false }
201221

202-
# kona
203-
kona-registry = "0.4.5"
204-
kona-engine = { git = "https://github.com/op-rs/kona", rev = "24e7e2658e09ac00c8e6cbb48bebe6d10f8fb69d" }
205-
206222
# tokio
207223
tokio = "1.48.0"
208224
tokio-stream = "0.1.17"
@@ -212,7 +228,6 @@ tokio-tungstenite = { version = "0.28.0", features = ["native-tls"] }
212228
futures = "0.3.31"
213229
reqwest = "0.12.25"
214230
futures-util = "0.3.31"
215-
backon = "1.5"
216231

217232
# rpc
218233
jsonrpsee = "0.26.0"
@@ -223,6 +238,11 @@ vergen = "9.0.6"
223238
vergen-git2 = "1.0.7"
224239
clap = { version = "4.5.53", features = ["derive", "env", "string"] }
225240

241+
# Tracing
242+
tracing = "0.1.43"
243+
tracing-appender = "0.2.4"
244+
tracing-subscriber = "0.3.22"
245+
226246
# Metrics
227247
metrics = { version = "0.24.3", default-features = false }
228248
prometheus = { version = "0.14.0", default-features = false }
@@ -233,6 +253,8 @@ metrics-exporter-prometheus = { version = "0.18.1", default-features = false }
233253
url = "2.5.7"
234254
lru = "0.16.3"
235255
rand = "0.9.2"
256+
strum = "0.27.2"
257+
backon = "1.6.0"
236258
uuid = "1.19.0"
237259
time = { version = "0.3.44", features = ["macros", "formatting", "parsing"] }
238260
rayon = "1.11"
@@ -245,21 +267,21 @@ rstest = "0.26.1"
245267
serde = "1.0.228"
246268
rustls = "0.23.35"
247269
httpmock = "0.8.2"
248-
tracing = "0.1.43"
249270
arc-swap = "1.7.1"
250271
once_cell = "1.21.3"
251272
itertools = "0.14.0"
252273
derive_more = "2.1.0"
253274
serde_json = "1.0.145"
254275
metrics-derive = "0.1.0"
255-
tracing-subscriber = "0.3.22"
256-
tracing-appender = "0.2"
257276
thiserror = "2.0"
258277
async-trait = "0.1.83"
259278
parking_lot = "0.12.3"
260279
auto_impl = "1.2.0"
261280
serde_with = "3.8.1"
262281
secp256k1 = "0.30"
282+
libp2p = "0.56.0"
283+
libp2p-identity = "0.2.12"
284+
discv5 = "0.10"
263285
either = { version = "1.15.0", default-features = false }
264286
tokio-util = { version = "0.7.4", features = ["codec"] }
265287
warp = "0.3.7"

bin/consensus/Cargo.toml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,34 @@ workspace = true
1515
[dependencies]
1616
# Workspace
1717
base-cli-utils.workspace = true
18+
base-client-cli.workspace = true
19+
base-jwt = { workspace = true, features = ["engine-validation"] }
20+
21+
# Kona
22+
kona-genesis.workspace = true
23+
kona-registry.workspace = true
24+
kona-disc = { workspace = true, features = ["metrics"] }
25+
kona-derive = { workspace = true, features = ["metrics"] }
26+
kona-engine = { workspace = true, features = ["metrics"] }
27+
kona-gossip = { workspace = true, features = ["metrics"] }
28+
kona-node-service = { workspace = true, features = ["metrics"] }
29+
kona-providers-alloy = { workspace = true, features = ["metrics"] }
30+
31+
# Reth (for serde feature on transitive dependencies)
32+
reth-optimism-node.workspace = true
1833

1934
# CLI
2035
clap.workspace = true
2136
eyre.workspace = true
37+
strum.workspace = true
38+
serde_json.workspace = true
39+
40+
# Tracing
41+
tracing.workspace = true
42+
tracing-subscriber = { workspace = true, features = ["fmt", "env-filter"] }
43+
44+
# Metrics
45+
metrics.workspace = true
2246

2347

2448
[build-dependencies]

bin/consensus/src/cli.rs

Lines changed: 214 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,21 @@
11
//! Contains the CLI entry point for the Base consensus binary.
22
3-
use base_cli_utils::GlobalArgs;
3+
use std::{fs::File, path::PathBuf, sync::Arc, time::Duration};
4+
5+
use base_cli_utils::{CliStyles, GlobalArgs, LogConfig, RuntimeManager};
6+
use base_client_cli::{
7+
BuilderClientArgs, JwtSecret, L1ClientArgs, L2ClientArgs, P2PArgs, RollupBoostFlags, RpcArgs,
8+
SequencerArgs,
9+
};
10+
use base_jwt::JwtValidator;
411
use clap::Parser;
12+
use eyre::bail;
13+
use kona_genesis::{L1ChainConfig, RollupConfig};
14+
use kona_node_service::{EngineConfig, L1ConfigBuilder, NodeMode, RollupNodeBuilder};
15+
use kona_registry::{L1Config, scr_rollup_config_by_alloy_ident};
16+
use serde_json::from_reader;
17+
use strum::IntoEnumIterator;
18+
use tracing::{debug, error, info};
519

620
use crate::version;
721

@@ -11,24 +25,217 @@ use crate::version;
1125
author,
1226
version = version::SHORT_VERSION,
1327
long_version = version::LONG_VERSION,
28+
styles = CliStyles::init(),
1429
about,
1530
long_about = None
1631
)]
1732
pub struct Cli {
1833
/// Global arguments for the Base Consensus CLI.
1934
#[command(flatten)]
2035
pub global: GlobalArgs,
36+
/// The mode to run the node in.
37+
#[arg(
38+
long = "mode",
39+
default_value_t = NodeMode::Validator,
40+
env = "KONA_NODE_MODE",
41+
help = format!(
42+
"The mode to run the node in. Supported modes are: {}",
43+
NodeMode::iter()
44+
.map(|mode| format!("\"{}\"", mode.to_string()))
45+
.collect::<Vec<_>>()
46+
.join(", ")
47+
)
48+
)]
49+
pub node_mode: NodeMode,
50+
51+
/// L1 RPC CLI arguments.
52+
#[clap(flatten)]
53+
pub l1_rpc_args: L1ClientArgs,
54+
55+
/// L2 engine CLI arguments.
56+
#[clap(flatten)]
57+
pub l2_client_args: L2ClientArgs,
58+
59+
/// Optional block builder client.
60+
#[clap(flatten)]
61+
pub builder_client_args: BuilderClientArgs,
62+
63+
/// Path to a custom L2 rollup configuration file
64+
/// (overrides the default rollup configuration from the registry)
65+
#[arg(long, visible_alias = "rollup-cfg", env = "KONA_NODE_ROLLUP_CONFIG")]
66+
pub l2_config_file: Option<PathBuf>,
67+
/// Path to a custom L1 rollup configuration file
68+
/// (overrides the default rollup configuration from the registry)
69+
#[arg(long, visible_alias = "rollup-l1-cfg", env = "KONA_NODE_L1_CHAIN_CONFIG")]
70+
pub l1_config_file: Option<PathBuf>,
71+
/// P2P CLI arguments.
72+
#[command(flatten)]
73+
pub p2p_flags: P2PArgs,
74+
/// RPC CLI arguments.
75+
#[command(flatten)]
76+
pub rpc_flags: RpcArgs,
77+
/// SEQUENCER CLI arguments.
78+
#[command(flatten)]
79+
pub sequencer_flags: SequencerArgs,
80+
/// Rollup Boost CLI arguments.
81+
#[command(flatten)]
82+
pub rollup_boost_flags: RollupBoostFlags,
2183
}
2284

2385
impl Cli {
24-
/// Parse the CLI arguments.
25-
pub fn parse() -> Self {
26-
<Self as Parser>::parse()
86+
/// Runs the CLI.
87+
pub fn run(self) -> eyre::Result<()> {
88+
// Initialize telemetry - allow subcommands to customize the filter.
89+
Self::init_logs(&self.global)?;
90+
91+
// Initialize unified metrics
92+
self.global.metrics.init_with(|| {
93+
kona_gossip::Metrics::init();
94+
kona_disc::Metrics::init();
95+
kona_engine::Metrics::init();
96+
kona_node_service::Metrics::init();
97+
kona_derive::Metrics::init();
98+
kona_providers_alloy::Metrics::init();
99+
version::VersionInfo::from_build().register_version_metrics();
100+
})?;
101+
102+
// Run the subcommand.
103+
RuntimeManager::run_until_ctrl_c(self.exec(&self.global))
27104
}
28105

29-
/// Run the CLI.
30-
pub fn run(self) -> eyre::Result<()> {
31-
// TODO: Implement the CLI logic
106+
/// Run the Node subcommand.
107+
pub async fn exec(&self, args: &GlobalArgs) -> eyre::Result<()> {
108+
let cfg = self.get_l2_config(args)?;
109+
110+
info!(
111+
target: "rollup_node",
112+
chain_id = cfg.l2_chain_id.id(),
113+
"Starting rollup node services"
114+
);
115+
for hf in cfg.hardforks.to_string().lines() {
116+
info!(target: "rollup_node", "{hf}");
117+
}
118+
119+
let l1_config = L1ConfigBuilder {
120+
chain_config: self.get_l1_config(cfg.l1_chain_id)?,
121+
trust_rpc: self.l1_rpc_args.l1_trust_rpc,
122+
beacon: self.l1_rpc_args.l1_beacon.clone(),
123+
rpc_url: self.l1_rpc_args.l1_eth_rpc.clone(),
124+
slot_duration_override: self.l1_rpc_args.l1_slot_duration_override,
125+
};
126+
127+
// TODO: If metrics are enabled, initialize the global cli metrics.
128+
// args.metrics.enabled.then(|| init_rollup_config_metrics(&cfg));
129+
130+
let jwt_secret = self.validate_jwt().await?;
131+
132+
self.p2p_flags.check_ports()?;
133+
let genesis_signer = args.genesis_signer().ok();
134+
let p2p_config = self
135+
.p2p_flags
136+
.clone()
137+
.config(
138+
&cfg,
139+
args.l2_chain_id.into(),
140+
Some(self.l1_rpc_args.l1_eth_rpc.clone()),
141+
genesis_signer,
142+
)
143+
.await?;
144+
let rpc_config = self.rpc_flags.clone().into();
145+
146+
let engine_config = EngineConfig {
147+
config: Arc::new(cfg.clone()),
148+
builder_url: self.builder_client_args.l2_builder_rpc.clone(),
149+
builder_jwt_secret: self
150+
.builder_client_args
151+
.jwt_secret()
152+
.map_err(|e| eyre::eyre!(e))?,
153+
builder_timeout: Duration::from_millis(self.builder_client_args.builder_timeout),
154+
l2_url: self.l2_client_args.l2_engine_rpc.clone(),
155+
l2_jwt_secret: jwt_secret,
156+
l2_timeout: Duration::from_millis(self.l2_client_args.l2_engine_timeout),
157+
l1_url: self.l1_rpc_args.l1_eth_rpc.clone(),
158+
mode: self.node_mode,
159+
rollup_boost: self.rollup_boost_flags.clone().as_rollup_boost_args(),
160+
};
161+
162+
RollupNodeBuilder::new(
163+
cfg,
164+
l1_config,
165+
self.l2_client_args.l2_trust_rpc,
166+
engine_config,
167+
p2p_config,
168+
rpc_config,
169+
)
170+
.with_sequencer_config(self.sequencer_flags.config())
171+
.build()
172+
.start()
173+
.await
174+
.map_err(|e| {
175+
error!(target: "rollup_node", "Failed to start rollup node service: {e}");
176+
eyre::eyre!("{e}")
177+
})?;
178+
32179
Ok(())
33180
}
181+
182+
/// Validate the jwt secret if specified by exchanging capabilities with the engine.
183+
/// Since the engine client will fail if the jwt token is invalid, this allows to ensure
184+
/// that the jwt token passed as a cli arg is correct.
185+
pub async fn validate_jwt(&self) -> eyre::Result<JwtSecret> {
186+
let jwt_secret = self.l2_client_args.jwt_secret().map_err(|e| eyre::eyre!(e))?;
187+
let validator = JwtValidator::new(jwt_secret);
188+
validator
189+
.validate_with_engine(self.l2_client_args.l2_engine_rpc.clone())
190+
.await
191+
.map_err(|e| eyre::eyre!(e))
192+
}
193+
194+
/// Get the L1 config, either from a file or the known chains.
195+
pub fn get_l1_config(&self, l1_chain_id: u64) -> eyre::Result<L1ChainConfig> {
196+
match &self.l1_config_file {
197+
Some(path) => {
198+
debug!("Loading l1 config from file: {:?}", path);
199+
let file = File::open(path)
200+
.map_err(|e| eyre::eyre!("Failed to open l1 config file: {e}"))?;
201+
from_reader(file).map_err(|e| eyre::eyre!("Failed to parse l1 config: {e}"))
202+
}
203+
None => {
204+
debug!("Loading l1 config from known chains");
205+
let cfg = L1Config::get_l1_genesis(l1_chain_id).map_err(|e| {
206+
eyre::eyre!("Failed to find l1 config for chain ID {l1_chain_id}: {e}")
207+
})?;
208+
Ok(cfg.into())
209+
}
210+
}
211+
}
212+
213+
/// Get the L2 rollup config, either from a file or the superchain registry.
214+
pub fn get_l2_config(&self, args: &GlobalArgs) -> eyre::Result<RollupConfig> {
215+
match &self.l2_config_file {
216+
Some(path) => {
217+
debug!("Loading l2 config from file: {:?}", path);
218+
let file = File::open(path)
219+
.map_err(|e| eyre::eyre!("Failed to open l2 config file: {e}"))?;
220+
from_reader(file).map_err(|e| eyre::eyre!("Failed to parse l2 config: {e}"))
221+
}
222+
None => {
223+
debug!("Loading l2 config from superchain registry");
224+
let Some(cfg) = scr_rollup_config_by_alloy_ident(&args.l2_chain_id) else {
225+
bail!("Failed to find l2 config for chain ID {}", args.l2_chain_id);
226+
};
227+
Ok(cfg.clone())
228+
}
229+
}
230+
}
231+
232+
/// Initializes the logging system based on global arguments.
233+
pub fn init_logs(args: &GlobalArgs) -> eyre::Result<()> {
234+
// Filter out discovery warnings since they're very very noisy.
235+
let filter = tracing_subscriber::EnvFilter::from_default_env()
236+
.add_directive("discv5=error".parse()?);
237+
238+
let config: LogConfig = args.logging.clone().into();
239+
config.init_tracing_subscriber_with_filter(filter)
240+
}
34241
}

0 commit comments

Comments
 (0)