diff --git a/Cargo.lock b/Cargo.lock index 02d323077e..3c9c11301d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3250,6 +3250,7 @@ dependencies = [ "http 1.2.0", "http-body 1.0.1", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -4203,6 +4204,20 @@ dependencies = [ "unsigned-varint 0.8.0", ] +[[package]] +name = "kona-nexus" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "kona-net", + "kona-registry", + "metrics-exporter-prometheus", + "tokio", + "tracing", + "tracing-subscriber", +] + [[package]] name = "kona-preimage" version = "0.2.1" @@ -5033,6 +5048,16 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "metrics" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a7deb012b3b2767169ff203fadb4c6b0b82b947512e5eb9e0b78c2e186ad9e3" +dependencies = [ + "ahash", + "portable-atomic", +] + [[package]] name = "metrics-derive" version = "0.1.0" @@ -5045,6 +5070,42 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "metrics-exporter-prometheus" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +dependencies = [ + "base64 0.22.1", + "http-body-util", + "hyper 1.6.0", + "hyper-util", + "indexmap 2.7.1", + "ipnet", + "metrics 0.24.1", + "metrics-util", + "quanta", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbd4884b1dd24f7d6628274a2f5ae22465c337c5ba065ec9b6edccddf8acc673" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.15.2", + "metrics 0.24.1", + "quanta", + "rand 0.8.5", + "rand_xoshiro", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -6160,6 +6221,21 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "quanta" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd1fe6824cea6538803de3ff1bc0cf3949024db3d43c9643024bfb33a807c0e" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.0+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -6345,6 +6421,24 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "raw-cpuid" +version = "11.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "529468c1335c1c03919960dfefdb1b3648858c20d7ec2d0663e728e4a717efbc" +dependencies = [ + "bitflags 2.8.0", +] + [[package]] name = "rayon" version = "1.10.0" @@ -6527,7 +6621,7 @@ dependencies = [ "alloy-primitives", "auto_impl", "derive_more 1.0.0", - "metrics", + "metrics 0.23.0", "parking_lot", "pin-project", "reth-chainspec", @@ -6608,7 +6702,7 @@ dependencies = [ "bytes", "derive_more 1.0.0", "eyre", - "metrics", + "metrics 0.23.0", "page_size", "paste", "reth-db-api", @@ -6639,7 +6733,7 @@ dependencies = [ "alloy-primitives", "bytes", "derive_more 1.0.0", - "metrics", + "metrics 0.23.0", "modular-bitfield", "parity-scale-codec", "reth-codecs", @@ -6735,7 +6829,7 @@ dependencies = [ "alloy-primitives", "auto_impl", "futures-util", - "metrics", + "metrics 0.23.0", "reth-chainspec", "reth-execution-errors", "reth-execution-types", @@ -6818,7 +6912,7 @@ name = "reth-metrics" version = "1.1.0" source = "git+https://github.com/paradigmxyz/reth?tag=v1.1.0#1ba631ba9581973e7c6cadeea92cfe1802aceb4a" dependencies = [ - "metrics", + "metrics 0.23.0", "metrics-derive", ] @@ -6982,7 +7076,7 @@ dependencies = [ "auto_impl", "dashmap", "itertools 0.13.0", - "metrics", + "metrics 0.23.0", "notify", "parking_lot", "rayon", @@ -7091,7 +7185,7 @@ dependencies = [ "auto_impl", "dyn-clone", "futures-util", - "metrics", + "metrics 0.23.0", "reth-metrics", "thiserror 1.0.69", "tokio", @@ -7126,7 +7220,7 @@ dependencies = [ "auto_impl", "bitflags 2.8.0", "futures-util", - "metrics", + "metrics 0.23.0", "parking_lot", "reth-chain-state", "reth-chainspec", @@ -7158,7 +7252,7 @@ dependencies = [ "auto_impl", "derive_more 1.0.0", "itertools 0.13.0", - "metrics", + "metrics 0.23.0", "rayon", "reth-execution-errors", "reth-metrics", @@ -7200,7 +7294,7 @@ dependencies = [ "auto_impl", "derive_more 1.0.0", "itertools 0.13.0", - "metrics", + "metrics 0.23.0", "rayon", "reth-db", "reth-db-api", @@ -8066,6 +8160,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "sketches-ddsketch" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" + [[package]] name = "slab" version = "0.4.9" diff --git a/Cargo.toml b/Cargo.toml index d6fee32ce7..7b719a282c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -173,6 +173,7 @@ libp2p-identity = "0.2.10" tracing-loki = "0.2.6" tracing-subscriber = "0.3.19" tracing = { version = "0.1.41", default-features = false } +metrics-exporter-prometheus = { version = "0.16.0", default-features = false } # Testing pprof = "0.14.0" diff --git a/Justfile b/Justfile index aa812eec24..0096b78fcd 100644 --- a/Justfile +++ b/Justfile @@ -102,7 +102,7 @@ build-cannon *args='': --rm \ -v `pwd`/:/workdir \ -w="/workdir" \ - ghcr.io/op-rs/kona/cannon-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host --exclude kona-net --exclude kona-providers-alloy --exclude kona-providers-local + ghcr.io/op-rs/kona/cannon-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host --exclude kona-nexus --exclude kona-net --exclude kona-providers-alloy --exclude kona-providers-local # Build for the `asterisc` target. Any crates that require the stdlib are excluded from the build for this target. build-asterisc *args='': @@ -110,7 +110,7 @@ build-asterisc *args='': --rm \ -v `pwd`/:/workdir \ -w="/workdir" \ - ghcr.io/op-rs/kona/asterisc-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host --exclude kona-net --exclude kona-providers-alloy --exclude kona-providers-local + ghcr.io/op-rs/kona/asterisc-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host --exclude kona-nexus --exclude kona-net --exclude kona-providers-alloy --exclude kona-providers-local # Clones and checks out the monorepo at the commit present in `.monorepo` monorepo: diff --git a/bin/nexus/Cargo.toml b/bin/nexus/Cargo.toml new file mode 100644 index 0000000000..66eee57dde --- /dev/null +++ b/bin/nexus/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "kona-nexus" +version = "0.1.0" +description = "Kona Networking Component Runner" + +edition.workspace = true +authors.workspace = true +license.workspace = true +keywords.workspace = true +repository.workspace = true +categories.workspace = true +rust-version.workspace = true + +[dependencies] +# Workspace +kona-net.workspace = true +kona-registry.workspace = true + +# Workspace +anyhow.workspace = true +tracing.workspace = true +clap = { workspace = true, features = ["derive", "env"] } +tokio = { workspace = true, features = ["rt-multi-thread", "macros"] } +tracing-subscriber = { workspace = true, features = ["env-filter", "fmt"] } +metrics-exporter-prometheus = { workspace = true, features = ["http-listener"] } diff --git a/bin/nexus/README.md b/bin/nexus/README.md new file mode 100644 index 0000000000..a6b18d70bd --- /dev/null +++ b/bin/nexus/README.md @@ -0,0 +1,7 @@ +# `kona-nexus` + +A binary that runs isolated components. + +These include: +- gossip using [`kona-net`](https://crates.io/crates/kona-net) +- discovery using [`kona-net`](https://crates.io/crates/kona-net) diff --git a/bin/nexus/src/disc.rs b/bin/nexus/src/disc.rs new file mode 100644 index 0000000000..810b439ce3 --- /dev/null +++ b/bin/nexus/src/disc.rs @@ -0,0 +1,44 @@ +//! Discovery subcommand for Hilo. + +use crate::globals::GlobalArgs; +use clap::Args; +use kona_net::discovery::builder::DiscoveryBuilder; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +/// The discovery subcommand. +#[derive(Debug, Clone, Args)] +#[non_exhaustive] +pub struct DiscCommand { + /// Port to listen for gossip on. + #[clap(long, short = 'l', default_value = "9099", help = "Port to listen for gossip on")] + pub gossip_port: u16, + /// Interval to send discovery packets. + #[clap(long, short = 'i', default_value = "1", help = "Interval to send discovery packets")] + pub interval: u64, +} + +impl DiscCommand { + /// Run the discovery subcommand. + pub async fn run(self, args: &GlobalArgs) -> anyhow::Result<()> { + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), self.gossip_port); + tracing::info!("Starting discovery service on {:?}", socket); + + let mut discovery_builder = + DiscoveryBuilder::new().with_address(socket).with_chain_id(args.l2_chain_id); + let mut discovery = discovery_builder.build()?; + discovery.interval = std::time::Duration::from_secs(self.interval); + let mut peer_recv = discovery.start(); + tracing::info!("Discovery service started, receiving peers."); + + loop { + match peer_recv.recv().await { + Some(peer) => { + tracing::info!("Received peer: {:?}", peer); + } + None => { + tracing::warn!("Failed to receive peer"); + } + } + } + } +} diff --git a/bin/nexus/src/globals.rs b/bin/nexus/src/globals.rs new file mode 100644 index 0000000000..d9ab9e17f7 --- /dev/null +++ b/bin/nexus/src/globals.rs @@ -0,0 +1,19 @@ +//! Global arguments for the CLI. + +use clap::Parser; + +/// Global arguments for the CLI. +#[derive(Parser, Clone, Debug)] +pub(crate) struct GlobalArgs { + /// The L2 chain ID to use. + #[clap(long, short = 'c', default_value = "10", help = "The L2 chain ID to use")] + pub l2_chain_id: u64, + /// A port to serve prometheus metrics on. + #[clap( + long, + short = 'm', + default_value = "9090", + help = "The port to serve prometheus metrics on" + )] + pub metrics_port: u16, +} diff --git a/bin/nexus/src/gossip.rs b/bin/nexus/src/gossip.rs new file mode 100644 index 0000000000..ef12c87dd6 --- /dev/null +++ b/bin/nexus/src/gossip.rs @@ -0,0 +1,58 @@ +//! Gossip subcommand. + +use crate::globals::GlobalArgs; +use clap::Args; +use kona_net::driver::NetworkDriver; +use kona_registry::ROLLUP_CONFIGS; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; + +/// The gossip subcommand. +#[derive(Debug, Clone, Args)] +#[non_exhaustive] +pub struct GossipCommand { + /// Port to listen for gossip on. + #[clap(long, short = 'l', default_value = "9099", help = "Port to listen for gossip on")] + pub gossip_port: u16, + /// Interval to send discovery packets. + #[clap(long, short = 'i', default_value = "1", help = "Interval to send discovery packets")] + pub interval: u64, +} + +impl GossipCommand { + /// Run the gossip subcommand. + pub async fn run(self, args: &GlobalArgs) -> anyhow::Result<()> { + let signer = ROLLUP_CONFIGS + .get(&args.l2_chain_id) + .ok_or(anyhow::anyhow!("No rollup config found for chain ID"))? + .genesis + .system_config + .as_ref() + .ok_or(anyhow::anyhow!("No system config found for chain ID"))? + .batcher_address; + tracing::info!("Gossip configured with signer: {:?}", signer); + + let socket = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), self.gossip_port); + tracing::info!("Starting gossip driver on {:?}", socket); + + let mut driver = NetworkDriver::builder() + .with_chain_id(args.l2_chain_id) + .with_unsafe_block_signer(signer) + .with_gossip_addr(socket) + .with_interval(std::time::Duration::from_secs(self.interval)) + .build()?; + let recv = + driver.take_unsafe_block_recv().ok_or(anyhow::anyhow!("No unsafe block receiver"))?; + driver.start()?; + tracing::info!("Gossip driver started, receiving blocks."); + loop { + match recv.recv() { + Ok(block) => { + tracing::info!("Received unsafe block: {:?}", block); + } + Err(e) => { + tracing::warn!("Failed to receive unsafe block: {:?}", e); + } + } + } + } +} diff --git a/bin/nexus/src/main.rs b/bin/nexus/src/main.rs new file mode 100644 index 0000000000..8bc9bf2358 --- /dev/null +++ b/bin/nexus/src/main.rs @@ -0,0 +1,49 @@ +#![doc = include_str!("../README.md")] +#![doc(issue_tracker_base_url = "https://github.com/op-rs/hilo/issues/")] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(not(test), warn(unused_crate_dependencies))] + +use anyhow::Result; +use clap::{Parser, Subcommand}; + +mod disc; +mod globals; +mod gossip; +mod telemetry; + +/// The CLI Arguments. +#[derive(Parser, Clone, Debug)] +#[command(author, version, about, long_about = None)] +pub(crate) struct NetArgs { + /// Global arguments for the CLI. + #[clap(flatten)] + pub global: globals::GlobalArgs, + /// The subcommand to run. + #[clap(subcommand)] + pub subcommand: NetSubcommand, +} + +/// Subcommands for the CLI. +#[derive(Debug, Clone, Subcommand)] +#[allow(clippy::large_enum_variant)] +pub(crate) enum NetSubcommand { + /// Discovery service command. + Disc(disc::DiscCommand), + /// Gossip service command. + Gossip(gossip::GossipCommand), +} + +#[tokio::main] +async fn main() -> Result<()> { + // Parse arguments. + let args = NetArgs::parse(); + + // Initialize the telemetry stack. + telemetry::init_stack(args.global.metrics_port)?; + + // Dispatch on subcommand. + match args.subcommand { + NetSubcommand::Disc(disc) => disc.run(&args.global).await, + NetSubcommand::Gossip(gossip) => gossip.run(&args.global).await, + } +} diff --git a/bin/nexus/src/telemetry.rs b/bin/nexus/src/telemetry.rs new file mode 100644 index 0000000000..cd123e6d3f --- /dev/null +++ b/bin/nexus/src/telemetry.rs @@ -0,0 +1,49 @@ +use std::{io::IsTerminal, net::SocketAddr}; + +use metrics_exporter_prometheus::PrometheusBuilder; +use tracing::{info, Level}; +use tracing_subscriber::{ + fmt::Layer as FmtLayer, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer, +}; + +/// Initialize the tracing stack and Prometheus metrics recorder. +/// +/// This function should be called at the beginning of the program. +pub fn init_stack(metrics_port: u16) -> anyhow::Result<()> { + let filter = EnvFilter::builder().with_default_directive("hilo=info".parse()?).from_env_lossy(); + + // Whether to use ANSI formatting and colors in the console output. + // If unset, always use colors if stdout is a tty. + // If set to "never", just disable all colors. + let should_use_colors = match std::env::var("RUST_LOG_STYLE") { + Ok(val) => val != "never", + Err(_) => std::io::stdout().is_terminal(), + }; + + // Whether to show the tracing target in the console output. + // If set, always show the target unless explicitly set to "0". + // If unset, show target only if the filter is more verbose than INFO. + let should_show_target = match std::env::var("RUST_LOG_TARGET") { + Ok(val) => val != "0", + Err(_) => filter.max_level_hint().map_or(true, |max_level| max_level > Level::INFO), + }; + + let std_layer = FmtLayer::new() + .with_ansi(should_use_colors) + .with_target(should_show_target) + .with_writer(std::io::stdout) + .with_filter(filter); + + tracing_subscriber::registry().with(std_layer).try_init()?; + + let prometheus_addr = SocketAddr::from(([0, 0, 0, 0], metrics_port)); + let builder = PrometheusBuilder::new().with_http_listener(prometheus_addr); + + if let Err(e) = builder.install() { + anyhow::bail!("failed to install Prometheus recorder: {:?}", e); + } else { + info!("Telemetry initialized. Serving Prometheus metrics at: http://{}", prometheus_addr); + } + + Ok(()) +} diff --git a/docker/fpvm-prestates/asterisc-repro.dockerfile b/docker/fpvm-prestates/asterisc-repro.dockerfile index 91f5ce7d82..e6b4f7d9f9 100644 --- a/docker/fpvm-prestates/asterisc-repro.dockerfile +++ b/docker/fpvm-prestates/asterisc-repro.dockerfile @@ -44,7 +44,7 @@ RUN git clone https://github.com/op-rs/kona # Build kona-client on the selected tag RUN cd kona && \ git checkout $CLIENT_TAG && \ - cargo build -Zbuild-std=core,alloc --workspace --bin kona --locked --profile release-client-lto --exclude kona-host --exclude kona-providers-alloy --exclude kona-providers-local --exclude kona-net && \ + cargo build -Zbuild-std=core,alloc --workspace --bin kona --locked --profile release-client-lto --exclude kona-host --exclude kona-nexus --exclude kona-providers-alloy --exclude kona-providers-local --exclude kona-net && \ mv ./target/riscv64imac-unknown-none-elf/release-client-lto/kona /kona-client-elf ################################################################