diff --git a/bin/host/Cargo.toml b/bin/host/Cargo.toml index 9f00a9f8..3bbf7fcd 100644 --- a/bin/host/Cargo.toml +++ b/bin/host/Cargo.toml @@ -16,8 +16,6 @@ kona-common.workspace = true kona-preimage.workspace = true kona-derive = { workspace = true, features = ["online"] } kona-primitives = { workspace = true, features = ["online"] } -op-alloy-genesis = { workspace = true, features = ["std", "serde"] } -op-alloy-protocol = { workspace = true, features = ["std", "serde"] } # Alloy & Revm alloy-eips.workspace = true @@ -28,6 +26,8 @@ alloy-transport-http.workspace = true alloy-rpc-client.workspace = true alloy-rpc-types = { workspace = true, features = ["eth"] } alloy-primitives = { workspace = true, features = ["serde"] } +op-alloy-genesis = { workspace = true, features = ["std", "serde"] } +op-alloy-protocol = { workspace = true, features = ["std", "serde"] } revm = { workspace = true, features = ["std", "c-kzg", "secp256k1", "portable", "blst"] } # General diff --git a/bin/host/README.md b/bin/host/README.md index 0912cabe..4d866aa2 100644 --- a/bin/host/README.md +++ b/bin/host/README.md @@ -1,10 +1,53 @@ # `kona-host` -The host binary's primary role is to serve the client program responses to requests over the [Preimage Oracle ABI][preimage-spec]. +kona-host is a CLI application that runs the [pre-image server][p-server] and [client program][client-program]. ## Modes -| Mode | Description | -| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `server` | Starts with the preimage server only, expecting the client program to have been invoked by the host process. This mode is particularly purposed to be activated by the FPVM running the client program | -| `native` | Starts both the preimage oracle and client program in a native process, bypassing the verifiable FPVM environment. This mode is useful for upfront witness generation as well as testing. | +| Mode | Description | +| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `server` | Starts with the preimage server only, expecting the client program to have been invoked by the host process. This mode is intended for use by the FPVM when running the client program. | +| `native` | Starts both the preimage oracle and client program in a native process. This mode is useful for witness generation as well as testing. | + +## Usage + +```txt +Usage: kona-host [OPTIONS] --l1-head --l2-head --l2-output-root --l2-claim --l2-block-number + +Options: + -v, --v... + Verbosity level (0-2) + --l1-head + Hash of the L1 head block. Derivation stops after this block is processed + --l2-head + Hash of the L2 block committed to by `--l2-output-root` + --l2-output-root + Agreed L2 Output Root to start derivation from + --l2-claim + Claimed L2 output root at block # `--l2-block-number` to validate + --l2-block-number + Number of the L2 block that the claim commits to + --l2-node-address + Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) [aliases: l2] + --l1-node-address + Address of L1 JSON-RPC endpoint to use (eth and debug namespace required) [aliases: l1] + --l1-beacon-address + Address of the L1 Beacon API endpoint to use [aliases: beacon] + --data-dir + The Data Directory for preimage data storage. Default uses in-memory storage [aliases: db] + --exec + Run the specified client program natively as a separate process detached from the host + --server + Run in pre-image server mode without executing any client program. If not provided, the host will run the client program in the host process + --l2-chain-id + The L2 chain ID of a supported chain. If provided, the host will look for the corresponding rollup config in the superchain registry + --rollup-config-path + Path to rollup config. If provided, the host will use this config instead of attempting to look up the config in the superchain registry + -h, --help + Print help + -V, --version + Print version +``` + +[p-server]: https://specs.optimism.io/fault-proof/index.html#pre-image-oracle +[client-program]: https://specs.optimism.io/fault-proof/index.html#fault-proof-program diff --git a/bin/host/src/cli/mod.rs b/bin/host/src/cli/mod.rs index 61e9db51..2ed6b378 100644 --- a/bin/host/src/cli/mod.rs +++ b/bin/host/src/cli/mod.rs @@ -1,12 +1,20 @@ //! This module contains all CLI-specific code for the host binary. -use crate::kv::{ - DiskKeyValueStore, LocalKeyValueStore, MemoryKeyValueStore, SharedKeyValueStore, - SplitKeyValueStore, +use crate::{ + kv::{ + DiskKeyValueStore, LocalKeyValueStore, MemoryKeyValueStore, SharedKeyValueStore, + SplitKeyValueStore, + }, + util, }; use alloy_primitives::B256; -use anyhow::{anyhow, ensure, Result}; -use clap::{ArgAction, Parser}; +use alloy_provider::ReqwestProvider; +use anyhow::{anyhow, Result}; +use clap::{ + builder::styling::{AnsiColor, Color, Style}, + ArgAction, Parser, +}; +use kona_derive::online::{OnlineBeaconClient, OnlineBlobProvider, SimpleSlotDerivation}; use op_alloy_genesis::RollupConfig; use serde::Serialize; use std::{path::PathBuf, sync::Arc}; @@ -18,76 +26,133 @@ pub(crate) use parser::parse_b256; mod tracing_util; pub use tracing_util::init_tracing_subscriber; +const ABOUT: &str = " +kona-host is a CLI application that runs the Kona pre-image server and client program. The host +can run in two modes: server mode and native mode. In server mode, the host runs the pre-image +server and waits for the client program in the parent process to request pre-images. In native +mode, the host runs the client program in a child process with the pre-image server in the primary +thread. +"; + /// The host binary CLI application arguments. #[derive(Default, Parser, Serialize, Clone, Debug)] +#[command(about = ABOUT, version, styles = cli_styles())] pub struct HostCli { - /// Verbosity level (0-4) - #[arg(long, short, help = "Verbosity level (0-4)", action = ArgAction::Count)] + /// Verbosity level (0-2) + #[arg(long, short, action = ArgAction::Count)] pub v: u8, /// Hash of the L1 head block. Derivation stops after this block is processed. #[clap(long, value_parser = parse_b256)] pub l1_head: B256, - /// Hash of the L2 block at the L2 Output Root. + /// Hash of the L2 block committed to by `--l2-output-root`. #[clap(long, value_parser = parse_b256)] pub l2_head: B256, /// Agreed L2 Output Root to start derivation from. #[clap(long, value_parser = parse_b256)] pub l2_output_root: B256, - /// Claimed L2 output root to validate + /// Claimed L2 output root at block # `--l2-block-number` to validate. #[clap(long, value_parser = parse_b256)] pub l2_claim: B256, - /// Number of the L2 block that the claim is from. + /// Number of the L2 block that the claim commits to. #[clap(long)] pub l2_block_number: u64, - /// The L2 chain ID. - #[clap(long)] - pub l2_chain_id: Option, /// Address of L2 JSON-RPC endpoint to use (eth and debug namespace required). - #[clap(long)] + #[clap( + long, + visible_alias = "l2", + requires = "l1_node_address", + requires = "l1_beacon_address" + )] pub l2_node_address: Option, - /// Address of L1 JSON-RPC endpoint to use (eth namespace required) - #[clap(long)] + /// Address of L1 JSON-RPC endpoint to use (eth and debug namespace required) + #[clap( + long, + visible_alias = "l1", + requires = "l2_node_address", + requires = "l1_beacon_address" + )] pub l1_node_address: Option, /// Address of the L1 Beacon API endpoint to use. - #[clap(long)] + #[clap( + long, + visible_alias = "beacon", + requires = "l1_node_address", + requires = "l2_node_address" + )] pub l1_beacon_address: Option, - /// The Data Directory for preimage data storage. Default uses in-memory storage. - #[clap(long)] + /// The Data Directory for preimage data storage. Optional if running in online mode, + /// required if running in offline mode. + #[clap( + long, + visible_alias = "db", + required_unless_present_all = ["l2_node_address", "l1_node_address", "l1_beacon_address"] + )] pub data_dir: Option, - /// Run the specified client program as a separate process detached from the host. Default is - /// to run the client program in the host process. - #[clap(long)] + /// Run the specified client program natively as a separate process detached from the host. + #[clap(long, conflicts_with = "server", required_unless_present = "server")] pub exec: Option, - /// Run in pre-image server mode without executing any client program. Defaults to `false`. - #[clap(long)] + /// Run in pre-image server mode without executing any client program. If not provided, the + /// host will run the client program in the host process. + #[clap(long, conflicts_with = "exec", required_unless_present = "exec")] pub server: bool, - /// Path to rollup config - #[clap(long)] + /// The L2 chain ID of a supported chain. If provided, the host will look for the corresponding + /// rollup config in the superchain registry. + #[clap( + long, + conflicts_with = "rollup_config_path", + required_unless_present = "rollup_config_path" + )] + pub l2_chain_id: Option, + /// Path to rollup config. If provided, the host will use this config instead of attempting to + /// look up the config in the superchain registry. + #[clap( + long, + alias = "rollup-cfg", + conflicts_with = "l2_chain_id", + required_unless_present = "l2_chain_id" + )] pub rollup_config_path: Option, } impl HostCli { - /// Validates the CLI arguments. - pub fn validate(&self) -> Result<()> { - ensure!( - self.exec.is_some() ^ self.server, - "One of `--exec` or `--server` must be supplied, but not both." - ); - ensure!( - self.rollup_config_path.is_some() ^ self.l2_chain_id.is_some(), - "One of `--rollup-config-path` or `--l2-chain-id` must be supplied, but not both." - ); - - Ok(()) - } - /// Returns `true` if the host is running in offline mode. pub fn is_offline(&self) -> bool { - self.l1_node_address.is_none() || - self.l2_node_address.is_none() || + self.l1_node_address.is_none() && + self.l2_node_address.is_none() && self.l1_beacon_address.is_none() } + /// Creates the providers associated with the [HostCli] configuration. + /// + /// ## Returns + /// - A [ReqwestProvider] for the L1 node. + /// - An [OnlineBlobProvider] for the L1 beacon node. + /// - A [ReqwestProvider] for the L2 node. + pub async fn create_providers( + &self, + ) -> Result<( + ReqwestProvider, + OnlineBlobProvider, + ReqwestProvider, + )> { + let beacon_client = OnlineBeaconClient::new_http( + self.l1_beacon_address.clone().ok_or(anyhow!("Beacon API URL must be set"))?, + ); + let mut blob_provider = OnlineBlobProvider::new(beacon_client, None, None); + blob_provider + .load_configs() + .await + .map_err(|e| anyhow!("Failed to load blob provider configuration: {e}"))?; + let l1_provider = util::http_provider( + self.l1_node_address.as_ref().ok_or(anyhow!("Provider must be set"))?, + ); + let l2_provider = util::http_provider( + self.l2_node_address.as_ref().ok_or(anyhow!("L2 node address must be set"))?, + ); + + Ok((l1_provider, blob_provider, l2_provider)) + } + /// Parses the CLI arguments and returns a new instance of a [SharedKeyValueStore], as it is /// configured to be created. pub fn construct_kv_store(&self) -> SharedKeyValueStore { @@ -124,24 +189,84 @@ impl HostCli { } } +/// Styles for the CLI application. +fn cli_styles() -> clap::builder::Styles { + clap::builder::Styles::styled() + .usage(Style::new().bold().underline().fg_color(Some(Color::Ansi(AnsiColor::Yellow)))) + .header(Style::new().bold().underline().fg_color(Some(Color::Ansi(AnsiColor::Yellow)))) + .literal(Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)))) + .invalid(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::Red)))) + .error(Style::new().bold().fg_color(Some(Color::Ansi(AnsiColor::Red)))) + .valid(Style::new().bold().underline().fg_color(Some(Color::Ansi(AnsiColor::Green)))) + .placeholder(Style::new().fg_color(Some(Color::Ansi(AnsiColor::White)))) +} + #[cfg(test)] mod test { use crate::HostCli; + use alloy_primitives::B256; + use clap::Parser; #[test] - fn test_invalid_cli_server_and_native() { - let cli = - HostCli { server: true, exec: Some("deaddead".to_string()), ..Default::default() }; - assert!(cli.validate().is_err()); - } + fn test_flags() { + let zero_hash_str = &B256::ZERO.to_string(); + let default_flags = [ + "host", + "--l1-head", + zero_hash_str, + "--l2-head", + zero_hash_str, + "--l2-output-root", + zero_hash_str, + "--l2-claim", + zero_hash_str, + "--l2-block-number", + "0", + ]; - #[test] - fn test_invalid_cli_chain_id_and_rollup_config() { - let cli = HostCli { - l2_chain_id: Some(1), - rollup_config_path: Some("deaddead".into()), - ..Default::default() - }; - assert!(cli.validate().is_err()); + let cases = [ + // valid + (["--server", "--l2-chain-id", "0", "--data-dir", "dummy"].as_slice(), true), + (["--server", "--rollup-config-path", "dummy", "--data-dir", "dummy"].as_slice(), true), + (["--exec", "dummy", "--l2-chain-id", "0", "--data-dir", "dummy"].as_slice(), true), + ( + ["--exec", "dummy", "--rollup-config-path", "dummy", "--data-dir", "dummy"] + .as_slice(), + true, + ), + ( + [ + "--l1-node-address", + "dummy", + "--l2-node-address", + "dummy", + "--l1-beacon-address", + "dummy", + "--server", + "--l2-chain-id", + "0", + ] + .as_slice(), + true, + ), + // invalid + (["--server", "--exec", "dummy", "--l2-chain-id", "0"].as_slice(), false), + (["--l2-chain-id", "0", "--rollup-config-path", "dummy", "--server"].as_slice(), false), + (["--server"].as_slice(), false), + (["--exec", "dummy"].as_slice(), false), + (["--rollup-config-path", "dummy"].as_slice(), false), + (["--l2-chain-id", "0"].as_slice(), false), + (["--l1-node-address", "dummy", "--server", "--l2-chain-id", "0"].as_slice(), false), + (["--l2-node-address", "dummy", "--server", "--l2-chain-id", "0"].as_slice(), false), + (["--l1-beacon-address", "dummy", "--server", "--l2-chain-id", "0"].as_slice(), false), + ([].as_slice(), false), + ]; + + for (args_ext, valid) in cases.into_iter() { + let args = default_flags.iter().chain(args_ext.iter()).cloned().collect::>(); + + let parsed = HostCli::try_parse_from(args); + assert_eq!(parsed.is_ok(), valid); + } } } diff --git a/bin/host/src/cli/tracing_util.rs b/bin/host/src/cli/tracing_util.rs index 1deadead..5422bfd2 100644 --- a/bin/host/src/cli/tracing_util.rs +++ b/bin/host/src/cli/tracing_util.rs @@ -6,17 +6,15 @@ use tracing::Level; /// Initializes the tracing subscriber /// /// # Arguments -/// * `verbosity_level` - The verbosity level (0-4) +/// * `verbosity_level` - The verbosity level (0-2) /// /// # Returns /// * `Result<()>` - Ok if successful, Err otherwise. pub fn init_tracing_subscriber(verbosity_level: u8) -> Result<()> { let subscriber = tracing_subscriber::fmt() .with_max_level(match verbosity_level { - 0 => Level::ERROR, - 1 => Level::WARN, - 2 => Level::INFO, - 3 => Level::DEBUG, + 0 => Level::INFO, + 1 => Level::DEBUG, _ => Level::TRACE, }) .finish(); diff --git a/bin/host/src/lib.rs b/bin/host/src/lib.rs index 4789e9a3..88ea04a5 100644 --- a/bin/host/src/lib.rs +++ b/bin/host/src/lib.rs @@ -19,7 +19,6 @@ use anyhow::{anyhow, bail, Result}; use command_fds::{CommandFdExt, FdMapping}; use futures::FutureExt; use kona_common::FileDescriptor; -use kona_derive::online::{OnlineBeaconClient, OnlineBlobProvider}; use kona_preimage::{HintReader, OracleServer, PipeHandle}; use kv::KeyValueStore; use std::{ @@ -45,16 +44,7 @@ pub async fn start_server(cfg: HostCli) -> Result<()> { let kv_store = cfg.construct_kv_store(); let fetcher = if !cfg.is_offline() { - let beacon_client = OnlineBeaconClient::new_http( - cfg.l1_beacon_address.clone().expect("Beacon API URL must be set"), - ); - let mut blob_provider = OnlineBlobProvider::new(beacon_client, None, None); - blob_provider - .load_configs() - .await - .map_err(|e| anyhow!("Failed to load blob provider configuration: {e}"))?; - let l1_provider = util::http_provider(&cfg.l1_node_address.expect("Provider must be set")); - let l2_provider = util::http_provider(&cfg.l2_node_address.expect("Provider must be set")); + let (l1_provider, blob_provider, l2_provider) = cfg.create_providers().await?; Some(Arc::new(RwLock::new(Fetcher::new( kv_store.clone(), l1_provider, @@ -92,18 +82,7 @@ pub async fn start_server_and_native_client(cfg: HostCli) -> Result { let kv_store = cfg.construct_kv_store(); let fetcher = if !cfg.is_offline() { - let beacon_client = OnlineBeaconClient::new_http( - cfg.l1_beacon_address.clone().expect("Beacon API URL must be set"), - ); - let mut blob_provider = OnlineBlobProvider::new(beacon_client, None, None); - blob_provider - .load_configs() - .await - .map_err(|e| anyhow!("Failed to load blob provider configuration: {e}"))?; - let l1_provider = - util::http_provider(cfg.l1_node_address.as_ref().expect("Provider must be set")); - let l2_provider = - util::http_provider(cfg.l2_node_address.as_ref().expect("Provider must be set")); + let (l1_provider, blob_provider, l2_provider) = cfg.create_providers().await?; Some(Arc::new(RwLock::new(Fetcher::new( kv_store.clone(), l1_provider, diff --git a/bin/host/src/main.rs b/bin/host/src/main.rs index b38c2257..648531f6 100644 --- a/bin/host/src/main.rs +++ b/bin/host/src/main.rs @@ -6,7 +6,6 @@ use tracing::{error, info}; #[tokio::main(flavor = "multi_thread")] async fn main() -> Result<()> { let cfg = HostCli::parse(); - cfg.validate()?; init_tracing_subscriber(cfg.v)?; if cfg.server {