diff --git a/.github/workflows/client_host.yaml b/.github/workflows/client_host.yaml index 85231c120..faa2b5e13 100644 --- a/.github/workflows/client_host.yaml +++ b/.github/workflows/client_host.yaml @@ -45,7 +45,7 @@ jobs: - name: Build `asterisc` if: "!contains(matrix.target, 'native')" run: | - cd asterisc && git checkout v1.1.0 && make build-rvgo + cd asterisc && git checkout v1.1.2 && make build-rvgo mv ./rvgo/bin/asterisc /usr/local/bin/ - name: Set run environment run: | diff --git a/.monorepo b/.monorepo index 468fecb07..a17988b8f 100644 --- a/.monorepo +++ b/.monorepo @@ -1 +1 @@ -2fecdde0d8ee5ef549a80ff47c679961dc82e4f9 +e52030dba4d6130874f80e80900961c342c8c074 diff --git a/Cargo.lock b/Cargo.lock index a5d280249..ed6de6712 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -726,6 +726,18 @@ dependencies = [ "serde", ] +[[package]] +name = "async-channel" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b47800b0be77592da0afd425cc03468052844aff33b84e33cc696f64e77b6a" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -1138,14 +1150,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] -name = "command-fds" -version = "0.3.0" +name = "concurrent-queue" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bb11bd1378bf3731b182997b40cefe00aba6a6cc74042c8318c1b271d3badf7" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ - "nix 0.27.1", - "thiserror 1.0.69", - "tokio", + "crossbeam-utils", ] [[package]] @@ -1530,6 +1540,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "5.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.2.0" @@ -2322,14 +2353,14 @@ dependencies = [ "alloy-rpc-types-engine", "async-trait", "cfg-if", - "kona-common", - "kona-common-proc", "kona-derive", "kona-driver", "kona-executor", "kona-mpt", "kona-preimage", "kona-proof", + "kona-std-fpvm", + "kona-std-fpvm-proc", "lru", "op-alloy-consensus", "op-alloy-genesis", @@ -2341,28 +2372,6 @@ dependencies = [ "spin", "thiserror 2.0.3", "tracing", - "tracing-subscriber", -] - -[[package]] -name = "kona-common" -version = "0.1.0" -dependencies = [ - "cfg-if", - "linked_list_allocator", - "thiserror 2.0.3", -] - -[[package]] -name = "kona-common-proc" -version = "0.1.0" -dependencies = [ - "anyhow", - "cfg-if", - "kona-common", - "proc-macro2", - "quote", - "syn 2.0.87", ] [[package]] @@ -2448,18 +2457,15 @@ dependencies = [ "anyhow", "async-trait", "clap", - "command-fds", - "futures", "kona-client", - "kona-common", "kona-derive", "kona-mpt", "kona-preimage", "kona-proof", + "kona-std-fpvm", "op-alloy-genesis", "op-alloy-protocol", "op-alloy-rpc-types-engine", - "os_pipe", "proptest", "reqwest", "revm", @@ -2499,6 +2505,7 @@ name = "kona-preimage" version = "0.1.0" dependencies = [ "alloy-primitives", + "async-channel", "async-trait", "rkyv", "serde", @@ -2531,9 +2538,34 @@ dependencies = [ "serde_json", "spin", "thiserror 2.0.3", + "tokio", + "tracing", +] + +[[package]] +name = "kona-std-fpvm" +version = "0.1.0" +dependencies = [ + "async-trait", + "cfg-if", + "kona-preimage", + "linked_list_allocator", + "thiserror 2.0.3", "tracing", ] +[[package]] +name = "kona-std-fpvm-proc" +version = "0.1.0" +dependencies = [ + "anyhow", + "cfg-if", + "kona-std-fpvm", + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -2738,17 +2770,6 @@ dependencies = [ "libc", ] -[[package]] -name = "nix" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" -dependencies = [ - "bitflags 2.6.0", - "cfg-if", - "libc", -] - [[package]] name = "nom" version = "7.1.3" @@ -3049,16 +3070,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "os_pipe" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" -dependencies = [ - "libc", - "windows-sys 0.59.0", -] - [[package]] name = "overload" version = "0.1.1" @@ -3104,6 +3115,12 @@ dependencies = [ "syn 2.0.87", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + [[package]] name = "parking_lot" version = "0.12.3" @@ -3245,7 +3262,7 @@ dependencies = [ "inferno", "libc", "log", - "nix 0.26.4", + "nix", "once_cell", "parking_lot", "smallvec", diff --git a/Cargo.toml b/Cargo.toml index 4f13681ab..f5a297613 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,14 +63,14 @@ lto = "fat" [workspace.dependencies] # Workspace kona-mpt = { path = "crates/mpt", version = "0.1.0", default-features = false } -kona-client = { path = "bin/client", version = "0.1.0", default-features = false } kona-derive = { path = "crates/derive", version = "0.1.0", default-features = false } kona-driver = { path = "crates/driver", version = "0.1.0", default-features = false } kona-executor = { path = "crates/executor", version = "0.1.0", default-features = false } -kona-proof = { path = "crates/proof-sdk/proof", version = "0.1.0", default-features = false } -kona-common = { path = "crates/proof-sdk/common", version = "0.1.0", default-features = false } +kona-client = { path = "bin/client", version = "0.1.0", default-features = false } +kona-std-fpvm = { path = "crates/proof-sdk/std-fpvm", version = "0.1.0", default-features = false } +kona-std-fpvm-proc = { path = "crates/proof-sdk/std-fpvm-proc", version = "0.1.0", default-features = false } kona-preimage = { path = "crates/proof-sdk/preimage", version = "0.1.0", default-features = false } -kona-common-proc = { path = "crates/proof-sdk/common-proc", version = "0.1.0", default-features = false } +kona-proof = { path = "crates/proof-sdk/proof", version = "0.1.0", default-features = false } # Alloy alloy-rlp = { version = "0.3.9", default-features = false } @@ -101,10 +101,9 @@ spin = "0.9.8" rand = "0.8.5" clap = "4.5.19" tokio = "1.40.0" +async-channel = "2.3.1" cfg-if = "1.0.0" -os_pipe = "1.2.1" reqwest = "0.12.8" -command-fds = "0.3.0" async-trait = "0.1.83" linked_list_allocator = "0.10.5" @@ -112,7 +111,6 @@ linked_list_allocator = "0.10.5" sha2 = { version = "0.10.8", default-features = false } c-kzg = { version = "1.0.3", default-features = false } anyhow = { version = "1.0.89", default-features = false } -futures = { version = "0.3.30", default-features = false } thiserror = { version = "2.0", default-features = false } # Tracing diff --git a/README.md b/README.md index c0a35bd16..adf8a1d74 100644 --- a/README.md +++ b/README.md @@ -63,9 +63,10 @@ see the [SDK section of the book](https://anton-rs.github.io/kona/sdk/intro.html **Proof SDK** -- [`common`](./crates/proof-sdk/common): A suite of utilities for developing `client` programs to be run on top of Fault Proof VMs. -- [`common-proc`](./crates/proof-sdk/common-proc): Proc macro for the `client` program entrypoint. -- [`preimage`](./crates/proof-sdk/preimage): High level interfaces to the [`PreimageOracle`][fpp-specs] ABI +- [`kona-proof`](./crates/proof-sdk/proof): High level OP Stack state transition proof SDK. +- [`preimage`](./crates/proof-sdk/preimage): High level interfaces to the [`PreimageOracle`][fpp-specs] ABI. +- [`std-fpvm`](./crates/proof-sdk/std-fpvm): Platform specific [Fault Proof VM][g-fault-proof-vm] kernel APIs. +- [`std-fpvm-proc`](./crates/proof-sdk/std-fpvm-proc): Proc macro for [Fault Proof Program][fpp-specs] entrypoints. ## Book diff --git a/bin/client/Cargo.toml b/bin/client/Cargo.toml index b522e3d13..37c6a7347 100644 --- a/bin/client/Cargo.toml +++ b/bin/client/Cargo.toml @@ -11,13 +11,13 @@ repository.workspace = true [dependencies] # Workspace kona-mpt.workspace = true -kona-common.workspace = true kona-derive.workspace = true kona-driver.workspace = true kona-preimage.workspace = true kona-executor.workspace = true -kona-common-proc.workspace = true kona-proof.workspace = true +kona-std-fpvm.workspace = true +kona-std-fpvm-proc.workspace = true # Alloy alloy-rlp.workspace = true @@ -45,11 +45,9 @@ serde_json.workspace = true async-trait.workspace = true thiserror.workspace = true -# `tracing-subscriber` feature dependencies -tracing-subscriber = { workspace = true, optional = true, features = ["fmt"] } - [features] -tracing-subscriber = ["dep:tracing-subscriber"] +default = ["client-tracing"] +client-tracing = ["kona-std-fpvm/tracing"] [[bin]] name = "kona" diff --git a/bin/client/justfile b/bin/client/justfile index 529e3d9e9..7eb03331b 100644 --- a/bin/client/justfile +++ b/bin/client/justfile @@ -25,9 +25,9 @@ run-client-asterisc block_number l1_rpc l1_beacon_rpc l2_rpc rollup_node_rpc ver # Get the info for the previous block AGREED_L2_OUTPUT_ROOT=$(cast rpc --rpc-url $OP_NODE_ADDRESS "optimism_outputAtBlock" $(cast 2h $((CLAIMED_L2_BLOCK_NUMBER - 1))) | jq -r .outputRoot) - AGREED_L2_HEAD_HASH=$(cast block --rpc-url $L2_NODE_ADDRESS $((CLAIMED_L2_BLOCK_NUMBER - 1)) -j | jq -r .hash) + AGREED_L2_HEAD_HASH=$(cast block --rpc-url $L2_NODE_ADDRESS $((CLAIMED_L2_BLOCK_NUMBER - 1)) --json | jq -r .hash) L1_ORIGIN_NUM=$(cast rpc --rpc-url $OP_NODE_ADDRESS "optimism_outputAtBlock" $(cast 2h $((CLAIMED_L2_BLOCK_NUMBER - 1))) | jq -r .blockRef.l1origin.number) - L1_HEAD=$(cast block --rpc-url $L1_NODE_ADDRESS $((L1_ORIGIN_NUM + 30)) -j | jq -r .hash) + L1_HEAD=$(cast block --rpc-url $L1_NODE_ADDRESS $((L1_ORIGIN_NUM + 30)) --json | jq -r .hash) L2_CHAIN_ID=$(cast chain-id --rpc-url $L2_NODE_ADDRESS) # Move to the workspace root @@ -79,18 +79,14 @@ run-client-native block_number l1_rpc l1_beacon_rpc l2_rpc rollup_node_rpc verbo # Get the info for the previous block AGREED_L2_OUTPUT_ROOT=$(cast rpc --rpc-url $OP_NODE_ADDRESS "optimism_outputAtBlock" $(cast 2h $((CLAIMED_L2_BLOCK_NUMBER - 1))) | jq -r .outputRoot) - AGREED_L2_HEAD_HASH=$(cast block --rpc-url $L2_NODE_ADDRESS $((CLAIMED_L2_BLOCK_NUMBER - 1)) -j | jq -r .hash) + AGREED_L2_HEAD_HASH=$(cast block --rpc-url $L2_NODE_ADDRESS $((CLAIMED_L2_BLOCK_NUMBER - 1)) --json | jq -r .hash) L1_ORIGIN_NUM=$(cast rpc --rpc-url $OP_NODE_ADDRESS "optimism_outputAtBlock" $(cast 2h $((CLAIMED_L2_BLOCK_NUMBER - 1))) | jq -r .blockRef.l1origin.number) - L1_HEAD=$(cast block --rpc-url $L1_NODE_ADDRESS $((L1_ORIGIN_NUM + 30)) -j | jq -r .hash) + L1_HEAD=$(cast block --rpc-url $L1_NODE_ADDRESS $((L1_ORIGIN_NUM + 30)) --json | jq -r .hash) L2_CHAIN_ID=$(cast chain-id --rpc-url $L2_NODE_ADDRESS) - CLIENT_BIN_PATH="./target/release-client-lto/kona" - # Move to the workspace root cd $(git rev-parse --show-toplevel) - echo "Building client program..." - cargo build --bin kona --profile release-client-lto --features tracing-subscriber echo "Running host program with native client program..." cargo r --bin kona-host --release -- \ --l1-head $L1_HEAD \ @@ -102,7 +98,7 @@ run-client-native block_number l1_rpc l1_beacon_rpc l2_rpc rollup_node_rpc verbo --l1-node-address $L1_NODE_ADDRESS \ --l1-beacon-address $L1_BEACON_ADDRESS \ --l2-node-address $L2_NODE_ADDRESS \ - --exec $CLIENT_BIN_PATH \ + --native \ --data-dir ./data \ {{verbosity}} @@ -110,8 +106,6 @@ run-client-native block_number l1_rpc l1_beacon_rpc l2_rpc rollup_node_rpc verbo run-client-native-offline block_number l2_claim l2_output_root l2_head l1_head l2_chain_id verbosity='': #!/usr/bin/env bash - CLIENT_BIN_PATH="./target/release-client-lto/kona" - CLAIMED_L2_BLOCK_NUMBER={{block_number}} CLAIMED_L2_OUTPUT_ROOT={{l2_claim}} AGREED_L2_OUTPUT_ROOT={{l2_output_root}} @@ -122,8 +116,6 @@ run-client-native-offline block_number l2_claim l2_output_root l2_head l1_head l # Move to the workspace root cd $(git rev-parse --show-toplevel) - echo "Building client program..." - cargo build --bin kona --profile release-client-lto --features tracing-subscriber echo "Running host program with native client program..." cargo r --bin kona-host --release -- \ --l1-head $L1_HEAD \ @@ -132,7 +124,7 @@ run-client-native-offline block_number l2_claim l2_output_root l2_head l1_head l --agreed-l2-output-root $AGREED_L2_OUTPUT_ROOT \ --claimed-l2-block-number $CLAIMED_L2_BLOCK_NUMBER \ --l2-chain-id $L2_CHAIN_ID \ - --exec $CLIENT_BIN_PATH \ + --native \ --data-dir ./data \ {{verbosity}} diff --git a/bin/client/src/handler/mod.rs b/bin/client/src/handler/mod.rs deleted file mode 100644 index 910e343a1..000000000 --- a/bin/client/src/handler/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -//! Contains the [KonaHandleRegister] function for registering the FPVM-accelerated precompiles. -//! -//! [KonaHandleRegister]: kona_executor::KonaHandleRegister - -use alloc::sync::Arc; -use kona_executor::{TrieDB, TrieDBProvider}; -use kona_mpt::TrieHinter; -use revm::{ - handler::register::EvmHandler, - primitives::{spec_to_generic, SpecId}, - State, -}; - -mod bn128_pair; -mod ecrecover; -mod kzg_point_eval; - -/// The [KonaHandleRegister] function for registering the FPVM-accelerated precompiles. -/// -/// [KonaHandleRegister]: kona_executor::KonaHandleRegister -pub(crate) fn fpvm_handle_register( - handler: &mut EvmHandler<'_, (), &mut State<&mut TrieDB>>, -) where - F: TrieDBProvider, - H: TrieHinter, -{ - let spec_id = handler.cfg.spec_id; - - handler.pre_execution.load_precompiles = Arc::new(move || { - let mut ctx_precompiles = spec_to_generic!(spec_id, { - revm::optimism::load_precompiles::>>() - }); - - // Extend with FPVM-accelerated precompiles - let override_precompiles = [ - ecrecover::FPVM_ECRECOVER, - bn128_pair::FPVM_ECPAIRING, - kzg_point_eval::FPVM_KZG_POINT_EVAL, - ]; - ctx_precompiles.extend(override_precompiles); - - if spec_id.is_enabled_in(SpecId::GRANITE) { - ctx_precompiles.extend([bn128_pair::FPVM_ECPAIRING_GRANITE]); - } - - ctx_precompiles - }); -} diff --git a/bin/client/src/kona.rs b/bin/client/src/kona.rs index 3ba6e120b..a7788bc5a 100644 --- a/bin/client/src/kona.rs +++ b/bin/client/src/kona.rs @@ -7,152 +7,35 @@ extern crate alloc; -use alloc::{string::String, sync::Arc}; -use kona_client::PipeHandle; -use kona_common::{io, FileDescriptor}; -use kona_common_proc::client_entry; -use kona_driver::{Driver, DriverError}; +use alloc::string::String; use kona_preimage::{HintWriter, OracleReader}; -use kona_proof::{ - executor::KonaExecutorConstructor, - l1::{OracleBlobProvider, OracleL1ChainProvider, OraclePipeline}, - l2::OracleL2ChainProvider, - sync::new_pipeline_cursor, - BootInfo, CachingOracle, -}; -use tracing::{error, info, warn}; - -mod handler; -use handler::fpvm_handle_register; +use kona_std_fpvm::{FileChannel, FileDescriptor}; +use kona_std_fpvm_proc::client_entry; /// The global preimage oracle reader pipe. -static ORACLE_READER_PIPE: PipeHandle = - PipeHandle::new(FileDescriptor::PreimageRead, FileDescriptor::PreimageWrite); +static ORACLE_READER_PIPE: FileChannel = + FileChannel::new(FileDescriptor::PreimageRead, FileDescriptor::PreimageWrite); /// The global hint writer pipe. -static HINT_WRITER_PIPE: PipeHandle = - PipeHandle::new(FileDescriptor::HintRead, FileDescriptor::HintWrite); +static HINT_WRITER_PIPE: FileChannel = + FileChannel::new(FileDescriptor::HintRead, FileDescriptor::HintWrite); /// The global preimage oracle reader. -static ORACLE_READER: OracleReader = OracleReader::new(ORACLE_READER_PIPE); +static ORACLE_READER: OracleReader = OracleReader::new(ORACLE_READER_PIPE); /// The global hint writer. -static HINT_WRITER: HintWriter = HintWriter::new(HINT_WRITER_PIPE); - -/// The size of the LRU cache in the oracle. -const ORACLE_LRU_SIZE: usize = 1024; +static HINT_WRITER: HintWriter = HintWriter::new(HINT_WRITER_PIPE); #[client_entry(100_000_000)] fn main() -> Result<(), String> { - #[cfg(feature = "tracing-subscriber")] + #[cfg(feature = "client-tracing")] { - use tracing::Level; + use kona_std_fpvm::tracing::FpvmTracingSubscriber; - let subscriber = tracing_subscriber::fmt().with_max_level(Level::DEBUG).finish(); + let subscriber = FpvmTracingSubscriber::new(tracing::Level::INFO); tracing::subscriber::set_global_default(subscriber) - .expect("setting default subscriber failed"); + .expect("Failed to set tracing subscriber"); } - kona_common::block_on(async move { - //////////////////////////////////////////////////////////////// - // PROLOGUE // - //////////////////////////////////////////////////////////////// - - let oracle = Arc::new(CachingOracle::new(ORACLE_LRU_SIZE, ORACLE_READER, HINT_WRITER)); - let boot = match BootInfo::load(oracle.as_ref()).await { - Ok(boot) => Arc::new(boot), - Err(e) => { - error!(target: "client", "Failed to load boot info: {:?}", e); - io::print(&alloc::format!("Failed to load boot info: {:?}\n", e)); - io::exit(1); - } - }; - let l1_provider = OracleL1ChainProvider::new(boot.clone(), oracle.clone()); - let l2_provider = OracleL2ChainProvider::new(boot.clone(), oracle.clone()); - let beacon = OracleBlobProvider::new(oracle.clone()); - - // If the genesis block is claimed, we can exit early. - // The agreed upon prestate is consented to by all parties, and there is no state - // transition, so the claim is valid if the claimed output root matches the agreed - // upon output root. - if boot.claimed_l2_block_number == 0 { - warn!("Genesis block claimed. Exiting early."); - let exit_code = - if boot.agreed_l2_output_root == boot.claimed_l2_output_root { 0 } else { 1 }; - io::exit(exit_code); - } - - //////////////////////////////////////////////////////////////// - // DERIVATION & EXECUTION // - //////////////////////////////////////////////////////////////// - - // Create a new derivation driver with the given boot information and oracle. - - let Ok(cursor) = new_pipeline_cursor( - oracle.clone(), - &boot, - &mut l1_provider.clone(), - &mut l2_provider.clone(), - ) - .await - else { - error!(target: "client", "Failed to find sync start"); - io::print("Failed to find sync start\n"); - io::exit(1); - }; - let cfg = Arc::new(boot.rollup_config.clone()); - let pipeline = OraclePipeline::new( - cfg.clone(), - cursor.clone(), - oracle.clone(), - beacon, - l1_provider.clone(), - l2_provider.clone(), - ); - let executor = KonaExecutorConstructor::new( - &cfg, - l2_provider.clone(), - l2_provider, - fpvm_handle_register, - ); - let mut driver = Driver::new(cursor, executor, pipeline); - - // Run the derivation pipeline until we are able to produce the output root of the claimed - // L2 block. - let (number, output_root) = - driver.advance_to_target(&boot.rollup_config, boot.claimed_l2_block_number).await?; - - //////////////////////////////////////////////////////////////// - // EPILOGUE // - //////////////////////////////////////////////////////////////// - - if output_root != boot.claimed_l2_output_root { - error!( - target: "client", - "Failed to validate L2 block #{number} with output root {output_root}", - number = number, - output_root = output_root - ); - io::print(&alloc::format!( - "Failed to validate L2 block #{} with output root {}\n", - number, - output_root - )); - io::exit(1); - } - - info!( - target: "client", - "Successfully validated L2 block #{number} with output root {output_root}", - number = number, - output_root = output_root - ); - io::print(&alloc::format!( - "Successfully validated L2 block #{} with output root {}\n", - number, - output_root - )); - - Ok::<_, DriverError>(()) - }) + kona_proof::block_on(kona_client::run(ORACLE_READER, HINT_WRITER)) } diff --git a/bin/client/src/lib.rs b/bin/client/src/lib.rs index f32ecd6ea..964f11047 100644 --- a/bin/client/src/lib.rs +++ b/bin/client/src/lib.rs @@ -6,5 +6,144 @@ extern crate alloc; -mod pipe; -pub use pipe::PipeHandle; +use alloc::sync::Arc; +use alloy_primitives::B256; +use core::fmt::Debug; +use kona_driver::{Driver, DriverError}; +use kona_executor::ExecutorError; +use kona_preimage::{HintWriterClient, PreimageOracleClient}; +use kona_proof::{ + errors::OracleProviderError, + executor::KonaExecutorConstructor, + l1::{OracleBlobProvider, OracleL1ChainProvider, OraclePipeline}, + l2::OracleL2ChainProvider, + sync::new_pipeline_cursor, + BootInfo, CachingOracle, +}; +use thiserror::Error; +use tracing::{error, info, warn}; + +mod precompiles; +pub use precompiles::{ + EcPairingAccelerated, EcPairingAcceleratedGranite, EcRecoverAccelerated, + KZGPointEvalAccelerated, ECPAIRING_ADDRESS, ECRECOVER_ADDRESS, POINT_EVAL_ADDRESS, +}; + +/// An error that can occur when running the fault proof program. +#[derive(Error, Debug)] +pub enum FaultProofProgramError { + /// The claim is invalid. + #[error("Invalid claim. Expected {0}, actual {1}")] + InvalidClaim(B256, B256), + /// An error occurred in the Oracle provider. + #[error(transparent)] + OracleProviderError(#[from] OracleProviderError), + /// An error occurred in the driver. + #[error(transparent)] + Driver(#[from] DriverError), +} + +/// Executes the fault proof program with the given [PreimageOracleClient] and [HintWriterClient]. +#[inline] +pub async fn run(oracle_client: P, hint_client: H) -> Result<(), FaultProofProgramError> +where + P: PreimageOracleClient + Send + Sync + Debug + Clone, + H: HintWriterClient + Send + Sync + Debug + Clone, +{ + const ORACLE_LRU_SIZE: usize = 1024; + + //////////////////////////////////////////////////////////////// + // PROLOGUE // + //////////////////////////////////////////////////////////////// + + let oracle = Arc::new(CachingOracle::new(ORACLE_LRU_SIZE, oracle_client, hint_client)); + let boot = match BootInfo::load(oracle.as_ref()).await { + Ok(boot) => Arc::new(boot), + Err(e) => { + error!(target: "client", "Failed to load boot info: {:?}", e); + return Err(e.into()); + } + }; + let l1_provider = OracleL1ChainProvider::new(boot.clone(), oracle.clone()); + let l2_provider = OracleL2ChainProvider::new(boot.clone(), oracle.clone()); + let beacon = OracleBlobProvider::new(oracle.clone()); + + // If the genesis block is claimed, we can exit early. + // The agreed upon prestate is consented to by all parties, and there is no state + // transition, so the claim is valid if the claimed output root matches the agreed + // upon output root. + if boot.claimed_l2_block_number == 0 { + warn!("Genesis block claimed. Exiting early."); + if boot.agreed_l2_output_root == boot.claimed_l2_output_root { + info!( + target: "client", + "Successfully validated genesis block with output root {output_root}", + output_root = boot.agreed_l2_output_root + ); + return Ok(()); + } else { + error!( + target: "client", + "Failed to validate genesis block. Expected {genesis_root}, actual {claimed_root}", + genesis_root = boot.agreed_l2_output_root, + claimed_root = boot.claimed_l2_output_root + ); + return Err(FaultProofProgramError::InvalidClaim( + boot.agreed_l2_output_root, + boot.claimed_l2_output_root, + )); + }; + } + + //////////////////////////////////////////////////////////////// + // DERIVATION & EXECUTION // + //////////////////////////////////////////////////////////////// + + // Create a new derivation driver with the given boot information and oracle. + let cursor = new_pipeline_cursor( + oracle.clone(), + &boot, + &mut l1_provider.clone(), + &mut l2_provider.clone(), + ) + .await?; + let cfg = Arc::new(boot.rollup_config.clone()); + let pipeline = OraclePipeline::new( + cfg.clone(), + cursor.clone(), + oracle.clone(), + beacon, + l1_provider.clone(), + l2_provider.clone(), + ); + let executor = KonaExecutorConstructor::new(&cfg, l2_provider.clone(), l2_provider, None); + let mut driver = Driver::new(cursor, executor, pipeline); + + // Run the derivation pipeline until we are able to produce the output root of the claimed + // L2 block. + let (number, output_root) = + driver.advance_to_target(&boot.rollup_config, boot.claimed_l2_block_number).await?; + + //////////////////////////////////////////////////////////////// + // EPILOGUE // + //////////////////////////////////////////////////////////////// + + if output_root != boot.claimed_l2_output_root { + error!( + target: "client", + "Failed to validate L2 block #{number} with output root {output_root}", + number = number, + output_root = output_root + ); + return Err(FaultProofProgramError::InvalidClaim(output_root, boot.claimed_l2_output_root)); + } + + info!( + target: "client", + "Successfully validated L2 block #{number} with output root {output_root}", + number = number, + output_root = output_root + ); + + Ok(()) +} diff --git a/bin/client/src/handler/bn128_pair.rs b/bin/client/src/precompiles/bn128_pair.rs similarity index 51% rename from bin/client/src/handler/bn128_pair.rs rename to bin/client/src/precompiles/bn128_pair.rs index fb9e62cc1..b33880597 100644 --- a/bin/client/src/handler/bn128_pair.rs +++ b/bin/client/src/precompiles/bn128_pair.rs @@ -1,32 +1,82 @@ //! Contains the accelerated version of the `ecPairing` precompile. -use crate::{HINT_WRITER, ORACLE_READER}; -use alloc::{string::ToString, vec::Vec}; +use alloc::{string::ToString, sync::Arc, vec::Vec}; use alloy_primitives::{keccak256, Address, Bytes}; -use kona_preimage::{ - errors::PreimageOracleError, HintWriterClient, PreimageKey, PreimageKeyType, - PreimageOracleClient, -}; +use kona_preimage::{errors::PreimageOracleError, CommsClient, PreimageKey, PreimageKeyType}; use kona_proof::{errors::OracleProviderError, HintType}; use revm::{ precompile::{ bn128::pair::{ISTANBUL_PAIR_BASE, ISTANBUL_PAIR_PER_POINT}, - u64_to_address, Error as PrecompileError, PrecompileWithAddress, + u64_to_address, Error as PrecompileError, }, - primitives::{Precompile, PrecompileOutput, PrecompileResult}, + primitives::{PrecompileOutput, PrecompileResult, StatefulPrecompile}, }; -const ECPAIRING_ADDRESS: Address = u64_to_address(8); +/// The address of the `ecPairing` precompile. +pub const ECPAIRING_ADDRESS: Address = u64_to_address(8); + +/// The length of a single pair element. const PAIR_ELEMENT_LEN: usize = 64 + 128; -pub(crate) const FPVM_ECPAIRING: PrecompileWithAddress = - PrecompileWithAddress(ECPAIRING_ADDRESS, Precompile::Standard(fpvm_ecpairing)); +/// An accelerated version of the `ecPairing` precompile that calls out to the host for the +/// result of the precompile execution. +#[derive(Debug)] +pub struct EcPairingAccelerated +where + C: CommsClient, +{ + /// The comms client. + comms_client: Arc, +} + +impl EcPairingAccelerated +where + C: CommsClient, +{ + /// Creates a new [EcPairingAccelerated] instance. + pub fn new(comms_client: Arc) -> Self { + Self { comms_client } + } +} + +impl StatefulPrecompile for EcPairingAccelerated +where + C: CommsClient + Send + Sync, +{ + fn call(&self, input: &Bytes, gas_limit: u64, _: &revm::primitives::Env) -> PrecompileResult { + fpvm_ecpairing(self.comms_client.as_ref(), input, gas_limit) + } +} + +/// An accelerated version of the `ecPairing` precompile that calls out to the host for the +/// result of the precompile execution after the Granite hardfork. +#[derive(Debug)] +pub struct EcPairingAcceleratedGranite { + /// The comms client. + comms_client: Arc, +} + +impl EcPairingAcceleratedGranite { + /// Creates a new [EcPairingAcceleratedGranite] instance. + pub fn new(comms_client: Arc) -> Self { + Self { comms_client } + } +} -pub(crate) const FPVM_ECPAIRING_GRANITE: PrecompileWithAddress = - PrecompileWithAddress(ECPAIRING_ADDRESS, Precompile::Standard(fpvm_ecpairing_granite)); +impl StatefulPrecompile for EcPairingAcceleratedGranite +where + C: CommsClient + Send + Sync, +{ + fn call(&self, input: &Bytes, gas_limit: u64, _: &revm::primitives::Env) -> PrecompileResult { + fpvm_ecpairing_granite(self.comms_client.as_ref(), input, gas_limit) + } +} /// Performs an FPVM-accelerated `ecpairing` precompile call. -fn fpvm_ecpairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { +fn fpvm_ecpairing(comms_client: &C, input: &Bytes, gas_limit: u64) -> PrecompileResult +where + C: CommsClient, +{ let gas_used = (input.len() / PAIR_ELEMENT_LEN) as u64 * ISTANBUL_PAIR_PER_POINT + ISTANBUL_PAIR_BASE; @@ -38,10 +88,10 @@ fn fpvm_ecpairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { return Err(PrecompileError::Bn128PairLength.into()); } - let result_data = kona_common::block_on(async move { + let result_data = kona_proof::block_on(async move { // Write the hint for the ecrecover precompile run. let hint_data = &[ECPAIRING_ADDRESS.as_ref(), input.as_ref()]; - HINT_WRITER + comms_client .write(&HintType::L1Precompile.encode_with(hint_data)) .await .map_err(OracleProviderError::Preimage)?; @@ -51,7 +101,7 @@ fn fpvm_ecpairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { let key_hash = keccak256(&raw_key_data); // Fetch the result of the ecrecover precompile run from the host. - let result_data = ORACLE_READER + let result_data = comms_client .get(PreimageKey::new(*key_hash, PreimageKeyType::Precompile)) .await .map_err(OracleProviderError::Preimage)?; @@ -79,11 +129,14 @@ fn fpvm_ecpairing(input: &Bytes, gas_limit: u64) -> PrecompileResult { } /// Performs an FPVM-accelerated `ecpairing` precompile call after the Granite hardfork. -fn fpvm_ecpairing_granite(input: &Bytes, gas_limit: u64) -> PrecompileResult { +fn fpvm_ecpairing_granite(comms_client: &C, input: &Bytes, gas_limit: u64) -> PrecompileResult +where + C: CommsClient, +{ const BN256_MAX_PAIRING_SIZE_GRANITE: usize = 112_687; if input.len() > BN256_MAX_PAIRING_SIZE_GRANITE { return Err(PrecompileError::Bn128PairLength.into()); } - fpvm_ecpairing(input, gas_limit) + fpvm_ecpairing(comms_client, input, gas_limit) } diff --git a/bin/client/src/handler/ecrecover.rs b/bin/client/src/precompiles/ecrecover.rs similarity index 57% rename from bin/client/src/handler/ecrecover.rs rename to bin/client/src/precompiles/ecrecover.rs index 0e3a1cf5c..e772ab5f5 100644 --- a/bin/client/src/handler/ecrecover.rs +++ b/bin/client/src/precompiles/ecrecover.rs @@ -1,35 +1,62 @@ //! Contains the accelerated version of the `ecrecover` precompile. -use crate::{HINT_WRITER, ORACLE_READER}; -use alloc::{string::ToString, vec::Vec}; +use alloc::{string::ToString, sync::Arc, vec::Vec}; use alloy_primitives::{keccak256, Address, Bytes}; -use kona_preimage::{ - errors::PreimageOracleError, HintWriterClient, PreimageKey, PreimageKeyType, - PreimageOracleClient, -}; +use kona_preimage::{errors::PreimageOracleError, CommsClient, PreimageKey, PreimageKeyType}; use kona_proof::{errors::OracleProviderError, HintType}; use revm::{ - precompile::{u64_to_address, Error as PrecompileError, PrecompileWithAddress}, - primitives::{Precompile, PrecompileOutput, PrecompileResult}, + precompile::{u64_to_address, Error as PrecompileError}, + primitives::{PrecompileOutput, PrecompileResult, StatefulPrecompile}, }; -const ECRECOVER_ADDRESS: Address = u64_to_address(1); +/// The address of the `ecrecover` precompile. +pub const ECRECOVER_ADDRESS: Address = u64_to_address(1); + +/// An accelerated version of the `ecrecover` precompile that calls out to the host for the +/// result of the precompile execution +#[derive(Debug)] +pub struct EcRecoverAccelerated +where + C: CommsClient, +{ + /// The comms client. + comms_client: Arc, +} + +impl EcRecoverAccelerated +where + C: CommsClient, +{ + /// Creates a new [EcRecoverAccelerated] instance. + pub fn new(comms_client: Arc) -> Self { + Self { comms_client } + } +} -pub(crate) const FPVM_ECRECOVER: PrecompileWithAddress = - PrecompileWithAddress(ECRECOVER_ADDRESS, Precompile::Standard(fpvm_ecrecover)); +impl StatefulPrecompile for EcRecoverAccelerated +where + C: CommsClient + Send + Sync, +{ + fn call(&self, input: &Bytes, gas_limit: u64, _: &revm::primitives::Env) -> PrecompileResult { + fpvm_ecrecover(self.comms_client.as_ref(), input, gas_limit) + } +} /// Performs an FPVM-accelerated `ecrecover` precompile call. -fn fpvm_ecrecover(input: &Bytes, gas_limit: u64) -> PrecompileResult { +fn fpvm_ecrecover(comms_client: &C, input: &Bytes, gas_limit: u64) -> PrecompileResult +where + C: CommsClient, +{ const ECRECOVER_BASE: u64 = 3_000; if ECRECOVER_BASE > gas_limit { return Err(PrecompileError::OutOfGas.into()); } - let result_data = kona_common::block_on(async move { + let result_data = kona_proof::block_on(async move { // Write the hint for the ecrecover precompile run. let hint_data = &[ECRECOVER_ADDRESS.as_ref(), input.as_ref()]; - HINT_WRITER + comms_client .write(&HintType::L1Precompile.encode_with(hint_data)) .await .map_err(OracleProviderError::Preimage)?; @@ -39,7 +66,7 @@ fn fpvm_ecrecover(input: &Bytes, gas_limit: u64) -> PrecompileResult { let key_hash = keccak256(&raw_key_data); // Fetch the result of the ecrecover precompile run from the host. - let result_data = ORACLE_READER + let result_data = comms_client .get(PreimageKey::new(*key_hash, PreimageKeyType::Precompile)) .await .map_err(OracleProviderError::Preimage)?; diff --git a/bin/client/src/handler/kzg_point_eval.rs b/bin/client/src/precompiles/kzg_point_eval.rs similarity index 58% rename from bin/client/src/handler/kzg_point_eval.rs rename to bin/client/src/precompiles/kzg_point_eval.rs index 5447af1cf..12f094505 100644 --- a/bin/client/src/handler/kzg_point_eval.rs +++ b/bin/client/src/precompiles/kzg_point_eval.rs @@ -1,25 +1,50 @@ //! Contains the accelerated version of the KZG point evaluation precompile. -use crate::{HINT_WRITER, ORACLE_READER}; -use alloc::{string::ToString, vec::Vec}; +use alloc::{string::ToString, sync::Arc, vec::Vec}; use alloy_primitives::{keccak256, Address, Bytes}; -use kona_preimage::{ - errors::PreimageOracleError, HintWriterClient, PreimageKey, PreimageKeyType, - PreimageOracleClient, -}; +use kona_preimage::{errors::PreimageOracleError, CommsClient, PreimageKey, PreimageKeyType}; use kona_proof::{errors::OracleProviderError, HintType}; use revm::{ - precompile::{u64_to_address, Error as PrecompileError, PrecompileWithAddress}, - primitives::{Precompile, PrecompileOutput, PrecompileResult}, + precompile::{u64_to_address, Error as PrecompileError}, + primitives::{PrecompileOutput, PrecompileResult, StatefulPrecompile}, }; -const POINT_EVAL_ADDRESS: Address = u64_to_address(0x0A); +/// The address of the KZG point evaluation precompile. +pub const POINT_EVAL_ADDRESS: Address = u64_to_address(0x0A); + +/// An accelerated version of the KZG point evaluation precompile that calls out to the host for the +/// result of the precompile execution. +#[derive(Debug)] +pub struct KZGPointEvalAccelerated { + /// The comms client. + comms_client: Arc, +} + +impl KZGPointEvalAccelerated +where + C: CommsClient, +{ + /// Creates a new [KZGPointEvalAccelerated] instance. + #[allow(unused)] + fn new(comms_client: Arc) -> Self { + Self { comms_client } + } +} -pub(crate) const FPVM_KZG_POINT_EVAL: PrecompileWithAddress = - PrecompileWithAddress(POINT_EVAL_ADDRESS, Precompile::Standard(fpvm_kzg_point_eval)); +impl StatefulPrecompile for KZGPointEvalAccelerated +where + C: CommsClient + Send + Sync, +{ + fn call(&self, input: &Bytes, gas_limit: u64, _: &revm::primitives::Env) -> PrecompileResult { + fpvm_kzg_point_eval(self.comms_client.as_ref(), input, gas_limit) + } +} /// Performs an FPVM-accelerated KZG point evaluation precompile call. -fn fpvm_kzg_point_eval(input: &Bytes, gas_limit: u64) -> PrecompileResult { +fn fpvm_kzg_point_eval(comms_client: &C, input: &Bytes, gas_limit: u64) -> PrecompileResult +where + C: CommsClient, +{ const GAS_COST: u64 = 50_000; if gas_limit < GAS_COST { @@ -30,10 +55,10 @@ fn fpvm_kzg_point_eval(input: &Bytes, gas_limit: u64) -> PrecompileResult { return Err(PrecompileError::BlobInvalidInputLength.into()); } - let result_data = kona_common::block_on(async move { + let result_data = kona_proof::block_on(async move { // Write the hint for the ecrecover precompile run. let hint_data = &[POINT_EVAL_ADDRESS.as_ref(), input.as_ref()]; - HINT_WRITER + comms_client .write(&HintType::L1Precompile.encode_with(hint_data)) .await .map_err(OracleProviderError::Preimage)?; @@ -43,7 +68,7 @@ fn fpvm_kzg_point_eval(input: &Bytes, gas_limit: u64) -> PrecompileResult { let key_hash = keccak256(&raw_key_data); // Fetch the result of the ecrecover precompile run from the host. - let result_data = ORACLE_READER + let result_data = comms_client .get(PreimageKey::new(*key_hash, PreimageKeyType::Precompile)) .await .map_err(OracleProviderError::Preimage)?; diff --git a/bin/client/src/precompiles/mod.rs b/bin/client/src/precompiles/mod.rs new file mode 100644 index 000000000..ace879adf --- /dev/null +++ b/bin/client/src/precompiles/mod.rs @@ -0,0 +1,10 @@ +//! Contains accelerated precompiles for the FPVM. + +mod bn128_pair; +pub use bn128_pair::{EcPairingAccelerated, EcPairingAcceleratedGranite, ECPAIRING_ADDRESS}; + +mod ecrecover; +pub use ecrecover::{EcRecoverAccelerated, ECRECOVER_ADDRESS}; + +mod kzg_point_eval; +pub use kzg_point_eval::{KZGPointEvalAccelerated, POINT_EVAL_ADDRESS}; diff --git a/bin/host/Cargo.toml b/bin/host/Cargo.toml index 43ee726db..8637727a3 100644 --- a/bin/host/Cargo.toml +++ b/bin/host/Cargo.toml @@ -14,11 +14,11 @@ workspace = true [dependencies] # Workspace kona-mpt.workspace = true -kona-client.workspace = true kona-derive.workspace = true -kona-common.workspace = true -kona-preimage.workspace = true -kona-proof.workspace = true +kona-std-fpvm.workspace = true +kona-preimage = { workspace = true, features = ["std"] } +kona-proof = { workspace = true, features = ["std"] } +kona-client.workspace = true # Alloy alloy-rlp.workspace = true @@ -44,14 +44,11 @@ revm = { workspace = true, features = ["std", "c-kzg", "secp256k1", "portable", anyhow.workspace = true tracing.workspace = true reqwest.workspace = true -futures.workspace = true -os_pipe.workspace = true serde_json.workspace = true async-trait.workspace = true tokio = { workspace = true, features = ["full"] } serde = { workspace = true, features = ["derive"] } rocksdb = { workspace = true, features = ["snappy"] } -command-fds = { workspace = true, features = ["tokio"] } clap = { workspace = true, features = ["derive", "env"] } tracing-subscriber = { workspace = true, features = ["fmt"] } diff --git a/bin/host/README.md b/bin/host/README.md index 4d866aa21..29bf3c828 100644 --- a/bin/host/README.md +++ b/bin/host/README.md @@ -12,37 +12,44 @@ kona-host is a CLI application that runs the [pre-image server][p-server] and [c ## Usage ```txt -Usage: kona-host [OPTIONS] --l1-head --l2-head --l2-output-root --l2-claim --l2-block-number +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 separate thread with the pre-image server in the +primary thread. + + +Usage: kona-host [OPTIONS] --l1-head --agreed-l2-head-hash --agreed-l2-output-root --claimed-l2-output-root --claimed-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 + Hash of the L1 head block. Derivation stops after this block is processed [env: L1_HEAD=] + --agreed-l2-head-hash + Hash of the agreed upon safe L2 block committed to by `--agreed-l2-output-root` [env: AGREED_L2_HEAD_HASH=] [aliases: l2-head] + --agreed-l2-output-root + Agreed safe L2 Output Root to start derivation from [env: AGREED_L2_OUTPUT_ROOT=] [aliases: l2-output-root] + --claimed-l2-output-root + Claimed L2 output root at block # `--claimed-l2-block-number` to validate [env: CLAIMED_L2_OUTPUT_ROOT=] [aliases: l2-claim] + --claimed-l2-block-number + Number of the L2 block that the claimed output root commits to [env: CLAIMED_L2_BLOCK_NUMBER=] [aliases: l2-block-number] --l2-node-address - Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) [aliases: l2] + Address of L2 JSON-RPC endpoint to use (eth and debug namespace required) [env: L2_NODE_ADDRESS=] [aliases: l2] --l1-node-address - Address of L1 JSON-RPC endpoint to use (eth and debug namespace required) [aliases: l1] + Address of L1 JSON-RPC endpoint to use (eth and debug namespace required) [env: L1_NODE_ADDRESS=] [aliases: l1] --l1-beacon-address - Address of the L1 Beacon API endpoint to use [aliases: beacon] + Address of the L1 Beacon API endpoint to use [env: L1_BEACON_ADDRESS=] [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 + The Data Directory for preimage data storage. Optional if running in online mode, required if running in offline mode [env: DATA_DIR=] [aliases: db] + --native + Run the specified client program natively --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 + The L2 chain ID of a supported chain. If provided, the host will look for the corresponding rollup config in the superchain registry [env: L2_CHAIN_ID=] --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 + Path to rollup config. If provided, the host will use this config instead of attempting to look up the config in the superchain registry [env: ROLLUP_CONFIG_PATH=] -h, --help Print help -V, --version diff --git a/bin/host/src/cli/mod.rs b/bin/host/src/cli/mod.rs index 295d11f9a..324bd8661 100644 --- a/bin/host/src/cli/mod.rs +++ b/bin/host/src/cli/mod.rs @@ -30,8 +30,8 @@ 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. +mode, the host runs the client program in a separate thread with the pre-image server in the +primary thread. "; /// The host binary CLI application arguments. @@ -92,12 +92,12 @@ pub struct HostCli { env )] pub data_dir: Option, - /// Run the specified client program natively as a separate process detached from the host. + /// Run the client program natively. #[clap(long, conflicts_with = "server", required_unless_present = "server")] - pub exec: Option, + pub native: bool, /// 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")] + #[clap(long, conflicts_with = "native", required_unless_present = "native")] pub server: bool, /// The L2 chain ID of a supported chain. If provided, the host will look for the corresponding /// rollup config in the superchain registry. @@ -230,12 +230,8 @@ mod test { // 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, - ), + (["--native", "--l2-chain-id", "0", "--data-dir", "dummy"].as_slice(), true), + (["--native", "--rollup-config-path", "dummy", "--data-dir", "dummy"].as_slice(), true), ( [ "--l1-node-address", @@ -252,10 +248,10 @@ mod test { true, ), // invalid - (["--server", "--exec", "dummy", "--l2-chain-id", "0"].as_slice(), false), + (["--server", "--native", "--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), + (["--native"].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), diff --git a/bin/host/src/lib.rs b/bin/host/src/lib.rs index fc7b882aa..b523c350c 100644 --- a/bin/host/src/lib.rs +++ b/bin/host/src/lib.rs @@ -3,6 +3,8 @@ #![cfg_attr(not(test), warn(unused_crate_dependencies))] pub mod cli; +pub use cli::{init_tracing_subscriber, HostCli}; + pub mod fetcher; pub mod kv; pub mod preimage; @@ -10,39 +12,28 @@ pub mod providers; pub mod server; pub mod util; -pub use cli::{init_tracing_subscriber, HostCli}; +use anyhow::Result; use fetcher::Fetcher; -use server::PreimageServer; - -use anyhow::{anyhow, bail, Result}; -use command_fds::{CommandFdExt, FdMapping}; -use futures::FutureExt; -use kona_client::PipeHandle; -use kona_common::FileDescriptor; -use kona_preimage::{HintReader, OracleServer}; -use kv::KeyValueStore; -use std::{ - io::{stderr, stdin, stdout}, - os::fd::{AsFd, AsRawFd}, - panic::AssertUnwindSafe, - sync::Arc, +use kona_preimage::{ + BidirectionalChannel, HintReader, HintWriter, NativeChannel, OracleReader, OracleServer, }; -use tokio::{process::Command, sync::RwLock, task}; -use tracing::{debug, error, info}; -use util::Pipe; +use kona_std_fpvm::{FileChannel, FileDescriptor}; +use kv::KeyValueStore; +use server::PreimageServer; +use std::sync::Arc; +use tokio::{sync::RwLock, task}; +use tracing::info; /// Starts the [PreimageServer] in the primary thread. In this mode, the host program has been /// invoked by the Fault Proof VM and the client program is running in the parent process. pub async fn start_server(cfg: HostCli) -> Result<()> { - let (preimage_pipe, hint_pipe) = ( - PipeHandle::new(FileDescriptor::PreimageRead, FileDescriptor::PreimageWrite), - PipeHandle::new(FileDescriptor::HintRead, FileDescriptor::HintWrite), + let (preimage_chan, hint_chan) = ( + FileChannel::new(FileDescriptor::PreimageRead, FileDescriptor::PreimageWrite), + FileChannel::new(FileDescriptor::HintRead, FileDescriptor::HintWrite), ); - let oracle_server = OracleServer::new(preimage_pipe); - let hint_reader = HintReader::new(hint_pipe); - + let oracle_server = OracleServer::new(preimage_chan); + let hint_reader = HintReader::new(hint_chan); let kv_store = cfg.construct_kv_store(); - let fetcher = if !cfg.is_offline() { let (l1_provider, blob_provider, l2_provider) = cfg.create_providers().await?; Some(Arc::new(RwLock::new(Fetcher::new( @@ -58,8 +49,7 @@ pub async fn start_server(cfg: HostCli) -> Result<()> { // Start the server and wait for it to complete. info!("Starting preimage server."); - let server = PreimageServer::new(oracle_server, hint_reader, kv_store, fetcher); - server.start().await?; + PreimageServer::new(oracle_server, hint_reader, kv_store, fetcher).start().await?; info!("Preimage server has exited."); Ok(()) @@ -76,11 +66,9 @@ pub async fn start_server(cfg: HostCli) -> Result<()> { /// - `Err(_)` if the client program failed to execute, was killed by a signal, or the host program /// exited first. pub async fn start_server_and_native_client(cfg: HostCli) -> Result { - let hint_pipe = util::bidirectional_pipe()?; - let preimage_pipe = util::bidirectional_pipe()?; - + let hint_chan = BidirectionalChannel::new()?; + let preimage_chan = BidirectionalChannel::new()?; let kv_store = cfg.construct_kv_store(); - let fetcher = if !cfg.is_offline() { let (l1_provider, blob_provider, l2_provider) = cfg.create_providers().await?; Some(Arc::new(RwLock::new(Fetcher::new( @@ -98,31 +86,22 @@ pub async fn start_server_and_native_client(cfg: HostCli) -> Result { let server_task = task::spawn(start_native_preimage_server( kv_store, fetcher, - hint_pipe.host, - preimage_pipe.host, + hint_chan.host, + preimage_chan.host, )); // Start the client program in a separate child process. - let program_task = - task::spawn(start_native_client_program(cfg, hint_pipe.client, preimage_pipe.client)); + let program_task = task::spawn(kona_client::run( + OracleReader::new(preimage_chan.client), + HintWriter::new(hint_chan.client), + )); // Execute both tasks and wait for them to complete. info!("Starting preimage server and client program."); - let exit_status; - tokio::select!( - r = util::flatten_join_result(server_task) => { - r?; - error!(target: "kona_host", "Preimage server exited before client program."); - bail!("Host program exited before client program."); - }, - r = util::flatten_join_result(program_task) => { - exit_status = r?; - debug!(target: "kona_host", "Client program has exited with status {exit_status}."); - } - ); + let (_, client_result) = tokio::try_join!(server_task, program_task,)?; info!(target: "kona_host", "Preimage server and client program have joined."); - Ok(exit_status) + Ok(client_result.is_err() as i32) } /// Starts the preimage server in a separate thread. The client program is ran natively in this @@ -130,95 +109,14 @@ pub async fn start_server_and_native_client(cfg: HostCli) -> Result { pub async fn start_native_preimage_server( kv_store: Arc>, fetcher: Option>>>, - hint_pipe: Pipe, - preimage_pipe: Pipe, + hint_chan: NativeChannel, + preimage_chan: NativeChannel, ) -> Result<()> where KV: KeyValueStore + Send + Sync + ?Sized + 'static, { - let hint_reader = HintReader::new(PipeHandle::new( - FileDescriptor::Wildcard(hint_pipe.read.as_raw_fd() as usize), - FileDescriptor::Wildcard(hint_pipe.write.as_raw_fd() as usize), - )); - let oracle_server = OracleServer::new(PipeHandle::new( - FileDescriptor::Wildcard(preimage_pipe.read.as_raw_fd() as usize), - FileDescriptor::Wildcard(preimage_pipe.write.as_raw_fd() as usize), - )); - - let server = PreimageServer::new(oracle_server, hint_reader, kv_store, fetcher); - AssertUnwindSafe(server.start()) - .catch_unwind() - .await - .map_err(|_| { - error!(target: "preimage_server", "Preimage server panicked"); - anyhow!("Preimage server panicked") - })? - .map_err(|e| { - error!(target: "preimage_server", "Preimage server exited with an error"); - anyhow!("Preimage server exited with an error: {:?}", e) - })?; - - Ok(()) -} - -/// Starts the client program in a separate child process. The client program is ran natively in -/// this mode. -/// -/// ## Takes -/// - `cfg`: The host configuration. -/// - `files`: The files that are used to communicate with the native client. -/// - `tx`: The sender to signal the preimage server to exit. -/// - `rx`: The receiver to wait for the preimage server to exit. -/// -/// ## Returns -/// - `Ok(exit_code)` if the client program exits successfully. -/// - `Err(_)` if the client program failed to execute or was killed by a signal. -pub async fn start_native_client_program( - cfg: HostCli, - hint_pipe: Pipe, - preimage_pipe: Pipe, -) -> Result { - // Map the file descriptors to the standard streams and the preimage oracle and hint - // reader's special file descriptors. - let mut command = - Command::new(cfg.exec.ok_or_else(|| anyhow!("No client program binary path specified."))?); - command - .fd_mappings(vec![ - FdMapping { - parent_fd: stdin().as_fd().try_clone_to_owned().unwrap(), - child_fd: FileDescriptor::StdIn.into(), - }, - FdMapping { - parent_fd: stdout().as_fd().try_clone_to_owned().unwrap(), - child_fd: FileDescriptor::StdOut.into(), - }, - FdMapping { - parent_fd: stderr().as_fd().try_clone_to_owned().unwrap(), - child_fd: FileDescriptor::StdErr.into(), - }, - FdMapping { - parent_fd: hint_pipe.read.into(), - child_fd: FileDescriptor::HintRead.into(), - }, - FdMapping { - parent_fd: hint_pipe.write.into(), - child_fd: FileDescriptor::HintWrite.into(), - }, - FdMapping { - parent_fd: preimage_pipe.read.into(), - child_fd: FileDescriptor::PreimageRead.into(), - }, - FdMapping { - parent_fd: preimage_pipe.write.into(), - child_fd: FileDescriptor::PreimageWrite.into(), - }, - ]) - .expect("No errors may occur when mapping file descriptors."); - - let status = command.status().await.map_err(|e| { - error!(target: "client_program", "Failed to execute client program: {:?}", e); - anyhow!("Failed to execute client program: {:?}", e) - })?; + let hint_reader = HintReader::new(hint_chan); + let oracle_server = OracleServer::new(preimage_chan); - status.code().ok_or_else(|| anyhow!("Client program was killed by a signal.")) + PreimageServer::new(oracle_server, hint_reader, kv_store, fetcher).start().await } diff --git a/bin/host/src/server.rs b/bin/host/src/server.rs index 2a1e3e0d9..5d857676b 100644 --- a/bin/host/src/server.rs +++ b/bin/host/src/server.rs @@ -13,8 +13,8 @@ use kona_preimage::{ PreimageOracleServer, }; use std::sync::Arc; -use tokio::sync::RwLock; -use tracing::error; +use tokio::{spawn, sync::RwLock}; +use tracing::{error, info}; /// The [PreimageServer] is responsible for waiting for incoming preimage requests and /// serving them to the client. @@ -57,16 +57,14 @@ where /// Starts the [PreimageServer] and waits for incoming requests. pub async fn start(self) -> Result<()> { // Create the futures for the oracle server and hint router. - let server_fut = Self::start_oracle_server( + let server = spawn(Self::start_oracle_server( self.kv_store.clone(), self.fetcher.clone(), self.oracle_server, - ); - let hinter_fut = Self::start_hint_router(self.hint_reader, self.fetcher); + )); + let hint_router = spawn(Self::start_hint_router(self.hint_reader, self.fetcher)); // Spawn tasks for the futures and wait for them to complete. - let server = tokio::task::spawn(server_fut); - let hint_router = tokio::task::spawn(hinter_fut); tokio::select! { s = server => s.map_err(|e| anyhow!(e))?, h = hint_router => h.map_err(|e| anyhow!(e))?, @@ -88,7 +86,7 @@ where { loop { match server.next_preimage_request(fetcher).await { - Ok(_) => (), + Ok(_) => continue, Err(PreimageOracleError::IOError(_)) => return Ok(()), Err(e) => { error!("Failed to serve preimage request: {e}"); @@ -98,6 +96,7 @@ where } } + info!("Starting oracle server"); if let Some(fetcher) = fetcher.as_ref() { do_loop(&OnlinePreimageFetcher::new(Arc::clone(fetcher)), &oracle_server).await } else { @@ -119,7 +118,7 @@ where { loop { match server.next_hint(router).await { - Ok(_) => (), + Ok(_) => continue, Err(PreimageOracleError::IOError(_)) => return Ok(()), Err(e) => { error!("Failed to serve route hint: {e}"); @@ -129,8 +128,9 @@ where } } - if let Some(fetcher) = fetcher { - do_loop(&OnlineHintRouter::new(Arc::clone(&fetcher)), &hint_reader).await + info!("Starting hint router"); + if let Some(fetcher) = fetcher.as_ref() { + do_loop(&OnlineHintRouter::new(Arc::clone(fetcher)), &hint_reader).await } else { do_loop(&OfflineHintRouter, &hint_reader).await } diff --git a/bin/host/src/util.rs b/bin/host/src/util.rs index 5053e1bc0..d4e92adf2 100644 --- a/bin/host/src/util.rs +++ b/bin/host/src/util.rs @@ -6,34 +6,7 @@ use alloy_rpc_client::RpcClient; use alloy_transport_http::Http; use anyhow::{anyhow, Result}; use kona_proof::HintType; -use os_pipe::{PipeReader, PipeWriter}; use reqwest::Client; -use tokio::task::JoinHandle; - -/// A bidirectional pipe, with a client and host end. -#[derive(Debug)] -pub struct BidirectionalPipe { - pub(crate) client: Pipe, - pub(crate) host: Pipe, -} - -/// A single-direction pipe, with a read and write end. -#[derive(Debug)] -pub struct Pipe { - pub(crate) read: PipeReader, - pub(crate) write: PipeWriter, -} - -/// Creates a [BidirectionalPipe] instance. -pub fn bidirectional_pipe() -> Result { - let (ar, bw) = os_pipe::pipe().map_err(|e| anyhow!("Failed to create pipe: {e}"))?; - let (br, aw) = os_pipe::pipe().map_err(|e| anyhow!("Failed to create pipe: {e}"))?; - - Ok(BidirectionalPipe { - client: Pipe { read: ar, write: aw }, - host: Pipe { read: br, write: bw }, - }) -} /// Parses a hint from a string. /// @@ -59,17 +32,3 @@ pub(crate) fn http_provider(url: &str) -> ReqwestProvider { let http = Http::::new(url); ReqwestProvider::new(RpcClient::new(http, true)) } - -/// Flattens the result of a [JoinHandle] into a single result. -pub(crate) async fn flatten_join_result( - handle: JoinHandle>, -) -> Result -where - E: std::fmt::Display, -{ - match handle.await { - Ok(Ok(result)) => Ok(result), - Ok(Err(err)) => Err(anyhow!("{}", err)), - Err(err) => anyhow::bail!(err), - } -} diff --git a/crates/derive/src/attributes/stateful.rs b/crates/derive/src/attributes/stateful.rs index e64367906..f898c6eb1 100644 --- a/crates/derive/src/attributes/stateful.rs +++ b/crates/derive/src/attributes/stateful.rs @@ -212,8 +212,7 @@ async fn derive_deposits( for l in r.logs.iter() { let curr_index = global_index; global_index += 1; - #[allow(clippy::all)] - if !l.data.topics().first().map_or(false, |i| *i == DEPOSIT_EVENT_ABI_HASH) { + if l.data.topics().first().is_none_or(|i| *i != DEPOSIT_EVENT_ABI_HASH) { continue; } if l.address != deposit_contract { diff --git a/crates/proof-sdk/common-proc/CHANGELOG.md b/crates/proof-sdk/common-proc/CHANGELOG.md deleted file mode 100644 index a943e2ccb..000000000 --- a/crates/proof-sdk/common-proc/CHANGELOG.md +++ /dev/null @@ -1,43 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.0.6](https://github.com/anton-rs/kona/compare/kona-common-proc-v0.0.5...kona-common-proc-v0.0.6) - 2024-11-20 - -### Other - -- updated the following local packages: kona-common - -## [0.0.5](https://github.com/anton-rs/kona/compare/kona-common-proc-v0.0.4...kona-common-proc-v0.0.5) - 2024-11-06 - -### Added - -- *(client)* Remove `anyhow` ([#779](https://github.com/anton-rs/kona/pull/779)) - -## [0.0.4](https://github.com/anton-rs/kona/compare/kona-common-proc-v0.0.3...kona-common-proc-v0.0.4) - 2024-10-25 - -### Other - -- re-org imports ([#711](https://github.com/anton-rs/kona/pull/711)) - -## [0.0.3](https://github.com/anton-rs/kona/compare/kona-common-proc-v0.0.2...kona-common-proc-v0.0.3) - 2024-09-04 - -### Added -- *(workspace)* Workspace Re-exports ([#468](https://github.com/anton-rs/kona/pull/468)) - -### Other -- *(workspace)* Update for `anton-rs` org transfer ([#474](https://github.com/anton-rs/kona/pull/474)) -- *(workspace)* Hoist Dependencies ([#466](https://github.com/anton-rs/kona/pull/466)) -- *(common-proc)* Suppress doc warning ([#436](https://github.com/anton-rs/kona/pull/436)) - -## [0.0.2](https://github.com/anton-rs/kona/compare/kona-common-proc-v0.0.1...kona-common-proc-v0.0.2) - 2024-06-22 - -### Added -- *(client)* `BootInfo` ([#205](https://github.com/anton-rs/kona/pull/205)) - -### Other -- *(host)* Simplify host program ([#206](https://github.com/anton-rs/kona/pull/206)) diff --git a/crates/proof-sdk/common/CHANGELOG.md b/crates/proof-sdk/common/CHANGELOG.md deleted file mode 100644 index 789a820ae..000000000 --- a/crates/proof-sdk/common/CHANGELOG.md +++ /dev/null @@ -1,61 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.0.5](https://github.com/anton-rs/kona/compare/kona-common-v0.0.4...kona-common-v0.0.5) - 2024-11-20 - -### Other - -- *(workspace)* Reorganize SDK ([#816](https://github.com/anton-rs/kona/pull/816)) - -## [0.0.4](https://github.com/anton-rs/kona/compare/kona-common-v0.0.3...kona-common-v0.0.4) - 2024-10-25 - -### Added - -- remove thiserror ([#735](https://github.com/anton-rs/kona/pull/735)) -- *(preimage/common)* Migrate to `thiserror` ([#543](https://github.com/anton-rs/kona/pull/543)) - -### Fixed - -- *(workspace)* hoist and fix lints ([#577](https://github.com/anton-rs/kona/pull/577)) - -### Other - -- re-org imports ([#711](https://github.com/anton-rs/kona/pull/711)) -- *(preimage)* Test Coverage ([#634](https://github.com/anton-rs/kona/pull/634)) -- test coverage for common ([#629](https://github.com/anton-rs/kona/pull/629)) -- doc logos ([#609](https://github.com/anton-rs/kona/pull/609)) -- *(workspace)* Allow stdlib in `cfg(test)` ([#548](https://github.com/anton-rs/kona/pull/548)) - -## [0.0.3](https://github.com/anton-rs/kona/compare/kona-common-v0.0.2...kona-common-v0.0.3) - 2024-09-04 - -### Added -- add zkvm target for io ([#394](https://github.com/anton-rs/kona/pull/394)) - -### Other -- *(workspace)* Update for `anton-rs` org transfer ([#474](https://github.com/anton-rs/kona/pull/474)) -- *(workspace)* Hoist Dependencies ([#466](https://github.com/anton-rs/kona/pull/466)) -- *(bin)* Remove `kt` ([#461](https://github.com/anton-rs/kona/pull/461)) -- *(common)* Remove need for cursors in `NativeIO` ([#416](https://github.com/anton-rs/kona/pull/416)) - -## [0.0.2](https://github.com/anton-rs/kona/compare/kona-common-v0.0.1...kona-common-v0.0.2) - 2024-06-22 - -### Added -- *(client)* Derivation integration ([#257](https://github.com/anton-rs/kona/pull/257)) -- *(client/host)* Oracle-backed Blob fetcher ([#255](https://github.com/anton-rs/kona/pull/255)) -- *(host)* Host program scaffold ([#184](https://github.com/anton-rs/kona/pull/184)) -- *(preimage)* `OracleServer` + `HintReader` ([#96](https://github.com/anton-rs/kona/pull/96)) -- *(common)* Move from `RegisterSize` to native ptr size type ([#95](https://github.com/anton-rs/kona/pull/95)) -- *(workspace)* Add `rustfmt.toml` - -### Fixed -- *(common)* Pipe IO support ([#282](https://github.com/anton-rs/kona/pull/282)) - -### Other -- *(common)* Use `Box::leak` rather than `mem::forget` ([#180](https://github.com/anton-rs/kona/pull/180)) -- Add simple blocking async executor ([#38](https://github.com/anton-rs/kona/pull/38)) -- Make versions of packages independent ([#36](https://github.com/anton-rs/kona/pull/36)) diff --git a/crates/proof-sdk/common/Cargo.toml b/crates/proof-sdk/common/Cargo.toml deleted file mode 100644 index f4a6894cb..000000000 --- a/crates/proof-sdk/common/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "kona-common" -description = "Common traits and system interfaces for developing client programs on top of Fault Proof VMs." -version = "0.1.0" -edition.workspace = true -authors.workspace = true -license.workspace = true -repository.workspace = true -homepage.workspace = true - -[lints] -workspace = true - -[dependencies] -cfg-if.workspace = true -thiserror.workspace = true -linked_list_allocator.workspace = true diff --git a/crates/proof-sdk/common/README.md b/crates/proof-sdk/common/README.md deleted file mode 100644 index 18506d321..000000000 --- a/crates/proof-sdk/common/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# `kona-common` - -This library offers utilities for developing verifiable `client` executables that may run on top of Fault Proof Virtual -Machine targets. - -- The `alloc_heap` macro allows for statically allocating a heap of a certain size, and all `client` programs will need - to run it if they require heap allocation. The `alloc` crate can be used for programs targeting any FPVM, but is - optional. -- The `io` module provides a high-level safe interface over the `read`, `write`, and `exit` syscalls on all available - FPVM targets. While the FPVMs support a larger set of Linux syscalls, this crate looks to support the bare-minimum - required for `client` programs to communicate back and forth with the host and exit properly. If a consumer of the - library would like to extend the functionality of the `ClientIO`, an extension trait can be made for the `ClientIO` - type in the `io` module. diff --git a/crates/proof-sdk/common/src/executor.rs b/crates/proof-sdk/common/src/executor.rs deleted file mode 100644 index b590a3715..000000000 --- a/crates/proof-sdk/common/src/executor.rs +++ /dev/null @@ -1,52 +0,0 @@ -//! This module contains utilities for handling async functions in the no_std environment. This -//! allows for usage of async/await syntax for futures in a single thread. - -use alloc::boxed::Box; -use core::{ - future::Future, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, -}; - -/// This function busy waits on a future until it is ready. It uses a no-op waker to poll the future -/// in a thread-blocking loop. -pub fn block_on(f: impl Future) -> T { - let mut f = Box::pin(f); - - // Construct a no-op waker. - fn noop_clone(_: *const ()) -> RawWaker { - noop_raw_waker() - } - const fn noop(_: *const ()) {} - fn noop_raw_waker() -> RawWaker { - let vtable = &RawWakerVTable::new(noop_clone, noop, noop, noop); - RawWaker::new(core::ptr::null(), vtable) - } - let waker = unsafe { Waker::from_raw(noop_raw_waker()) }; - let mut context = Context::from_waker(&waker); - - loop { - // Safety: This is safe because we only poll the future once per loop iteration, - // and we do not move the future after pinning it. - if let Poll::Ready(v) = f.as_mut().poll(&mut context) { - return v; - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use core::future::ready; - - #[test] - fn test_block_on() { - let f = async { 42 }; - assert_eq!(block_on(f), 42); - } - - #[test] - fn test_block_on_ready() { - let f = ready(42); - assert_eq!(block_on(f), 42); - } -} diff --git a/crates/proof-sdk/common/src/io.rs b/crates/proof-sdk/common/src/io.rs deleted file mode 100644 index 5c576106e..000000000 --- a/crates/proof-sdk/common/src/io.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! This module contains the `ClientIO` struct, which is a system call interface for the kernel. - -use crate::{errors::IOResult, BasicKernelInterface, FileDescriptor}; -use cfg_if::cfg_if; - -cfg_if! { - if #[cfg(target_arch = "mips")] { - #[doc = "Concrete implementation of the [BasicKernelInterface] trait for the `MIPS32rel1` target architecture."] - pub(crate) type ClientIO = crate::mips32::io::Mips32IO; - } else if #[cfg(target_arch = "riscv64")] { - #[doc = "Concrete implementation of the [BasicKernelInterface] trait for the `riscv64` target architecture."] - pub(crate) type ClientIO = crate::riscv64::io::RiscV64IO; - } else if #[cfg(target_os = "zkvm")] { - #[doc = "Concrete implementation of the [BasicKernelInterface] trait for the `SP1` target architecture."] - pub(crate) type ClientIO = crate::zkvm::io::ZkvmIO; - } else { - #[doc = "Concrete implementation of the [BasicKernelInterface] trait for the `native` target architecture."] - pub(crate) type ClientIO = native_io::NativeIO; - } -} - -/// Print the passed string to the standard output [FileDescriptor]. -/// -/// # Panics -/// Panics if the write operation fails. -#[inline] -pub fn print(s: &str) { - ClientIO::write(FileDescriptor::StdOut, s.as_bytes()).expect("Error writing to stdout."); -} - -/// Print the passed string to the standard error [FileDescriptor]. -/// -/// # Panics -/// Panics if the write operation fails. -#[inline] -pub fn print_err(s: &str) { - ClientIO::write(FileDescriptor::StdErr, s.as_bytes()).expect("Error writing to stderr."); -} - -/// Write the passed buffer to the given [FileDescriptor]. -#[inline] -pub fn write(fd: FileDescriptor, buf: &[u8]) -> IOResult { - ClientIO::write(fd, buf) -} - -/// Write the passed buffer to the given [FileDescriptor]. -#[inline] -pub fn read(fd: FileDescriptor, buf: &mut [u8]) -> IOResult { - ClientIO::read(fd, buf) -} - -/// Exit the process with the given exit code. -#[inline] -pub fn exit(code: usize) -> ! { - ClientIO::exit(code) -} - -/// Native IO Module -#[cfg(not(any(target_arch = "mips", target_arch = "riscv64", target_os = "zkvm")))] -pub(crate) mod native_io { - use crate::{ - errors::{IOError, IOResult}, - io::FileDescriptor, - traits::BasicKernelInterface, - }; - use std::{ - fs::File, - io::{Read, Write}, - os::fd::FromRawFd, - }; - - /// Mock IO implementation for native tests. - #[derive(Debug)] - pub(crate) struct NativeIO; - - impl BasicKernelInterface for NativeIO { - fn write(fd: FileDescriptor, buf: &[u8]) -> IOResult { - let raw_fd: usize = fd.into(); - let mut file = unsafe { File::from_raw_fd(raw_fd as i32) }; - - file.write_all(buf).map_err(|_| IOError(9))?; - - std::mem::forget(file); - - Ok(buf.len()) - } - - fn read(fd: FileDescriptor, buf: &mut [u8]) -> IOResult { - let raw_fd: usize = fd.into(); - let mut file = unsafe { File::from_raw_fd(raw_fd as i32) }; - - let n = file.read(buf).map_err(|_| IOError(9))?; - - std::mem::forget(file); - - Ok(n) - } - - fn exit(code: usize) -> ! { - std::process::exit(code as i32) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::io::FileDescriptor; - - #[test] - fn test_print() { - print("Hello, World!"); - } - - #[test] - fn test_print_err() { - print_err("Hello, World!"); - } - - #[test] - fn test_write() { - let buf = b"Hello, World!"; - write(FileDescriptor::StdOut, buf).unwrap(); - } - - #[test] - fn test_read() { - let mut buf = [0u8; 1024]; - read(FileDescriptor::StdIn, &mut buf).unwrap(); - } - - #[test] - fn test_exit() { - exit(0); - } -} diff --git a/crates/proof-sdk/common/src/zkvm/io.rs b/crates/proof-sdk/common/src/zkvm/io.rs deleted file mode 100644 index 977e710f0..000000000 --- a/crates/proof-sdk/common/src/zkvm/io.rs +++ /dev/null @@ -1,19 +0,0 @@ -use crate::{errors::IOResult, BasicKernelInterface, FileDescriptor}; - -/// Concrete implementation of the [`KernelIO`] trait for the `SP1` target architecture. -#[derive(Debug)] -pub(crate) struct ZkvmIO; - -impl BasicKernelInterface for ZkvmIO { - fn write(_fd: FileDescriptor, _buf: &[u8]) -> IOResult { - unimplemented!(); - } - - fn read(_fd: FileDescriptor, _buf: &mut [u8]) -> IOResult { - unimplemented!(); - } - - fn exit(_code: usize) -> ! { - unimplemented!(); - } -} diff --git a/crates/proof-sdk/common/src/zkvm/mod.rs b/crates/proof-sdk/common/src/zkvm/mod.rs deleted file mode 100644 index 9e71ea8cc..000000000 --- a/crates/proof-sdk/common/src/zkvm/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! This module contains raw syscall bindings for the `ZKVM` compilation context. - -pub(crate) mod io; diff --git a/crates/proof-sdk/preimage/Cargo.toml b/crates/proof-sdk/preimage/Cargo.toml index 532eacc5c..9c50afad2 100644 --- a/crates/proof-sdk/preimage/Cargo.toml +++ b/crates/proof-sdk/preimage/Cargo.toml @@ -19,7 +19,7 @@ async-trait.workspace = true alloy-primitives.workspace = true # `std` feature dependencies -tokio = { workspace = true, features = ["full"], optional = true } +async-channel = { workspace = true, optional = true } # `rkyv` feature dependencies rkyv = { workspace = true, optional = true } @@ -32,6 +32,6 @@ tokio = { workspace = true, features = ["full"] } [features] default = [] -std = ["dep:tokio"] +std = ["dep:async-channel"] rkyv = ["dep:rkyv"] serde = ["dep:serde"] diff --git a/crates/proof-sdk/preimage/src/hint.rs b/crates/proof-sdk/preimage/src/hint.rs index 9eba5e41e..90946367f 100644 --- a/crates/proof-sdk/preimage/src/hint.rs +++ b/crates/proof-sdk/preimage/src/hint.rs @@ -145,7 +145,7 @@ mod test { async fn test_unblock_on_bad_utf8() { let mock_data = [0xf0, 0x90, 0x28, 0xbc]; - let hint_channel = BidirectionalChannel::new::<2>().unwrap(); + let hint_channel = BidirectionalChannel::new().unwrap(); let client = tokio::task::spawn(async move { let hint_writer = HintWriter::new(hint_channel.client); @@ -174,7 +174,7 @@ mod test { async fn test_unblock_on_fetch_failure() { const MOCK_DATA: &str = "test-hint 0xfacade"; - let hint_channel = BidirectionalChannel::new::<2>().unwrap(); + let hint_channel = BidirectionalChannel::new().unwrap(); let client = tokio::task::spawn(async move { let hint_writer = HintWriter::new(hint_channel.client); @@ -196,7 +196,7 @@ mod test { const MOCK_DATA: &str = "test-hint 0xfacade"; let incoming_hints = Arc::new(Mutex::new(Vec::new())); - let hint_channel = BidirectionalChannel::new::<2>().unwrap(); + let hint_channel = BidirectionalChannel::new().unwrap(); let client = tokio::task::spawn(async move { let hint_writer = HintWriter::new(hint_channel.client); diff --git a/crates/proof-sdk/preimage/src/native_channel.rs b/crates/proof-sdk/preimage/src/native_channel.rs index 0140f54cd..ba8badf8a 100644 --- a/crates/proof-sdk/preimage/src/native_channel.rs +++ b/crates/proof-sdk/preimage/src/native_channel.rs @@ -1,20 +1,16 @@ -//! Native implementation of the [Channel] trait, backed by [tokio]'s [mpsc] channel primitives. -//! -//! [mpsc]: tokio::sync::mpsc +//! Native implementation of the [Channel] trait, backed by [async_channel]'s unbounded +//! channel primitives. use crate::{ errors::{ChannelError, ChannelResult}, Channel, }; +use async_channel::{unbounded, Receiver, Sender}; use async_trait::async_trait; -use std::{io::Result, sync::Arc}; -use tokio::sync::{ - mpsc::{channel, Receiver, Sender}, - Mutex, -}; +use std::io::Result; /// A bidirectional channel, allowing for synchronized communication between two parties. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct BidirectionalChannel { /// The client handle of the channel. pub client: NativeChannel, @@ -24,22 +20,22 @@ pub struct BidirectionalChannel { impl BidirectionalChannel { /// Creates a [BidirectionalChannel] instance. - pub fn new() -> Result { - let (bw, ar) = channel(BUF); - let (aw, br) = channel(BUF); + pub fn new() -> Result { + let (bw, ar) = unbounded(); + let (aw, br) = unbounded(); Ok(Self { - client: NativeChannel { read: Arc::new(Mutex::new(ar)), write: aw }, - host: NativeChannel { read: Arc::new(Mutex::new(br)), write: bw }, + client: NativeChannel { read: ar, write: aw }, + host: NativeChannel { read: br, write: bw }, }) } } /// A channel with a receiver and sender. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct NativeChannel { /// The receiver of the channel. - pub(crate) read: Arc>>>, + pub(crate) read: Receiver>, /// The sender of the channel. pub(crate) write: Sender>, } @@ -47,33 +43,20 @@ pub struct NativeChannel { #[async_trait] impl Channel for NativeChannel { async fn read(&self, buf: &mut [u8]) -> ChannelResult { - let data = self.read.lock().await.recv().await.ok_or(ChannelError::Closed)?; + let data = self.read.recv().await.map_err(|_| ChannelError::Closed)?; let len = data.len().min(buf.len()); buf[..len].copy_from_slice(&data[..len]); Ok(len) } async fn read_exact(&self, buf: &mut [u8]) -> ChannelResult { - let mut read_lock = self.read.lock().await; - - let mut read = 0; - while read < buf.len() { - let data = read_lock.recv().await.ok_or(ChannelError::Closed)?; - let len = data.len(); - - if len + read > buf.len() { - return Err(ChannelError::UnexpectedEOF); - } - - buf[read..read + len].copy_from_slice(&data[..]); - read += len; - } - - Ok(read) + let data = self.read.recv().await.map_err(|_| ChannelError::Closed)?; + buf[..].copy_from_slice(&data[..]); + Ok(buf.len()) } async fn write(&self, buf: &[u8]) -> ChannelResult { - self.write.send(buf.to_vec()).await.unwrap(); + self.write.send(buf.to_vec()).await.map_err(|_| ChannelError::Closed)?; Ok(buf.len()) } } diff --git a/crates/proof-sdk/preimage/src/oracle.rs b/crates/proof-sdk/preimage/src/oracle.rs index 500a21888..143876ccf 100644 --- a/crates/proof-sdk/preimage/src/oracle.rs +++ b/crates/proof-sdk/preimage/src/oracle.rs @@ -172,7 +172,7 @@ mod test { Arc::new(Mutex::new(preimages)) }; - let preimage_channel = BidirectionalChannel::new::<2>().unwrap(); + let preimage_channel = BidirectionalChannel::new().unwrap(); let client = tokio::task::spawn(async move { let oracle_reader = OracleReader::new(preimage_channel.client); @@ -218,7 +218,7 @@ mod test { Arc::new(Mutex::new(preimages)) }; - let preimage_channel = BidirectionalChannel::new::<2>().unwrap(); + let preimage_channel = BidirectionalChannel::new().unwrap(); let client = tokio::task::spawn(async move { let oracle_reader = OracleReader::new(preimage_channel.client); diff --git a/crates/proof-sdk/proof/Cargo.toml b/crates/proof-sdk/proof/Cargo.toml index b096ca7da..c4d25b7c9 100644 --- a/crates/proof-sdk/proof/Cargo.toml +++ b/crates/proof-sdk/proof/Cargo.toml @@ -40,3 +40,9 @@ tracing.workspace = true serde_json.workspace = true async-trait.workspace = true thiserror.workspace = true + +# `std` feature dependencies +tokio = { workspace = true, features = ["full"], optional = true } + +[features] +std = ["dep:tokio"] diff --git a/crates/proof-sdk/proof/src/blocking_runtime.rs b/crates/proof-sdk/proof/src/blocking_runtime.rs index f04ad4385..9df7e4c46 100644 --- a/crates/proof-sdk/proof/src/blocking_runtime.rs +++ b/crates/proof-sdk/proof/src/blocking_runtime.rs @@ -1,15 +1,27 @@ //! This module contains a blocking runtime for futures, allowing for synchronous execution of async //! code in an embedded environment. -use alloc::boxed::Box; -use core::{ - future::Future, - task::{Context, Poll, RawWaker, RawWakerVTable, Waker}, -}; +use core::future::Future; + +/// This function blocks on a future in place until it is ready. +#[cfg(feature = "std")] +pub fn block_on(f: impl Future) -> T { + // When running with Tokio, use the appropriate blocking mechanism + if let Ok(runtime) = tokio::runtime::Handle::try_current() { + tokio::task::block_in_place(|| runtime.block_on(f)) + } else { + // Fallback to tokio's block_on if we're not in a runtime + tokio::runtime::Runtime::new().unwrap().block_on(f) + } +} /// This function busy waits on a future until it is ready. It uses a no-op waker to poll the future /// in a thread-blocking loop. +#[cfg(not(feature = "std"))] pub fn block_on(f: impl Future) -> T { + use alloc::boxed::Box; + use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; + let mut f = Box::pin(f); // Construct a no-op waker. diff --git a/crates/proof-sdk/proof/src/errors.rs b/crates/proof-sdk/proof/src/errors.rs index 7de8302b2..27bdc4db9 100644 --- a/crates/proof-sdk/proof/src/errors.rs +++ b/crates/proof-sdk/proof/src/errors.rs @@ -1,4 +1,4 @@ -//! Error types for the client program. +//! Error types for the proof program. use alloc::string::{String, ToString}; use kona_derive::errors::{PipelineError, PipelineErrorKind}; diff --git a/crates/proof-sdk/proof/src/executor.rs b/crates/proof-sdk/proof/src/executor.rs index 1859ff254..95376881f 100644 --- a/crates/proof-sdk/proof/src/executor.rs +++ b/crates/proof-sdk/proof/src/executor.rs @@ -59,7 +59,7 @@ where /// The trie hinter for the executor. trie_hinter: H, /// The handle register for the executor. - handle_register: KonaHandleRegister, + handle_register: Option>, } impl<'a, P, H> KonaExecutorConstructor<'a, P, H> @@ -72,7 +72,7 @@ where rollup_config: &'a Arc, trie_provider: P, trie_hinter: H, - handle_register: KonaHandleRegister, + handle_register: Option>, ) -> Self { Self { rollup_config, trie_provider, trie_hinter, handle_register } } @@ -85,15 +85,17 @@ where { /// Constructs the executor. fn new_executor(&self, header: Sealed
) -> KonaExecutor<'a, P, H> { - KonaExecutor::new( - StatelessL2BlockExecutor::builder( - self.rollup_config, - self.trie_provider.clone(), - self.trie_hinter.clone(), - ) - .with_parent_header(header) - .with_handle_register(self.handle_register) - .build(), + let mut builder = StatelessL2BlockExecutor::builder( + self.rollup_config, + self.trie_provider.clone(), + self.trie_hinter.clone(), ) + .with_parent_header(header); + + if let Some(register) = self.handle_register { + builder = builder.with_handle_register(register); + } + + KonaExecutor::new(builder.build()) } } diff --git a/crates/proof-sdk/std-fpvm-proc/CHANGELOG.md b/crates/proof-sdk/std-fpvm-proc/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/crates/proof-sdk/common-proc/Cargo.toml b/crates/proof-sdk/std-fpvm-proc/Cargo.toml similarity index 72% rename from crates/proof-sdk/common-proc/Cargo.toml rename to crates/proof-sdk/std-fpvm-proc/Cargo.toml index b16ce7e2a..19ca24782 100644 --- a/crates/proof-sdk/common-proc/Cargo.toml +++ b/crates/proof-sdk/std-fpvm-proc/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "kona-common-proc" -description = "Proc macro extension for the `kona-common` crate." +name = "kona-std-fpvm-proc" +description = "Proc macro entry point for `kona-std-fpvm` targeted programs." version = "0.1.0" edition.workspace = true authors.workspace = true @@ -17,7 +17,7 @@ anyhow.workspace = true cfg-if.workspace = true # Workspace -kona-common.workspace = true +kona-std-fpvm.workspace = true # Proc Macros quote = "1.0" diff --git a/crates/proof-sdk/common-proc/src/lib.rs b/crates/proof-sdk/std-fpvm-proc/src/lib.rs similarity index 79% rename from crates/proof-sdk/common-proc/src/lib.rs rename to crates/proof-sdk/std-fpvm-proc/src/lib.rs index f3bff9c27..dd13ad8e7 100644 --- a/crates/proof-sdk/common-proc/src/lib.rs +++ b/crates/proof-sdk/std-fpvm-proc/src/lib.rs @@ -32,10 +32,10 @@ pub fn client_entry(attr: TokenStream, input: TokenStream) -> TokenStream { let expanded = quote! { fn #fn_name() -> Result<(), String> { match #fn_body { - Ok(_) => kona_common::io::exit(0), + Ok(_) => kona_std_fpvm::io::exit(0), Err(e) => { - kona_common::io::print_err(alloc::format!("Program encountered fatal error: {:?}\n", e).as_ref()); - kona_common::io::exit(1); + kona_std_fpvm::io::print_err(alloc::format!("Program encountered fatal error: {:?}\n", e).as_ref()); + kona_std_fpvm::io::exit(1); } } } @@ -47,15 +47,15 @@ pub fn client_entry(attr: TokenStream, input: TokenStream) -> TokenStream { #[doc = "Program entry point"] #[no_mangle] pub extern "C" fn _start() { - kona_common::alloc_heap!(HEAP_SIZE); + kona_std_fpvm::alloc_heap!(HEAP_SIZE); let _ = #fn_name(); } #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { let msg = alloc::format!("Panic: {}", info); - kona_common::io::print_err(msg.as_ref()); - kona_common::io::exit(2) + kona_std_fpvm::io::print_err(msg.as_ref()); + kona_std_fpvm::io::exit(2) } } } diff --git a/crates/proof-sdk/std-fpvm/CHANGELOG.md b/crates/proof-sdk/std-fpvm/CHANGELOG.md new file mode 100644 index 000000000..e69de29bb diff --git a/crates/proof-sdk/std-fpvm/Cargo.toml b/crates/proof-sdk/std-fpvm/Cargo.toml new file mode 100644 index 000000000..54246340c --- /dev/null +++ b/crates/proof-sdk/std-fpvm/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "kona-std-fpvm" +description = "Platform specific APIs for interacting with Fault Proof VM kernels." +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true + +[lints] +workspace = true + +[dependencies] +# Workspace +kona-preimage.workspace = true + +# External +cfg-if.workspace = true +thiserror.workspace = true +linked_list_allocator.workspace = true +async-trait.workspace = true + +# `tracing` feature dependencies +tracing = { workspace = true, optional = true } + +[features] +tracing = ["dep:tracing"] diff --git a/crates/proof-sdk/std-fpvm/README.md b/crates/proof-sdk/std-fpvm/README.md new file mode 100644 index 000000000..2637e592e --- /dev/null +++ b/crates/proof-sdk/std-fpvm/README.md @@ -0,0 +1,10 @@ +# `kona-std-fpvm` + +CI +Kona Proof SDK +License +Codecov + +Platform specific [Fault Proof VM][g-fault-proof-vm] kernel APIs. + +[g-fault-proof-vm]: https://specs.optimism.io/experimental/fault-proof/index.html#fault-proof-vm diff --git a/bin/client/src/pipe.rs b/crates/proof-sdk/std-fpvm/src/channel.rs similarity index 59% rename from bin/client/src/pipe.rs rename to crates/proof-sdk/std-fpvm/src/channel.rs index 6a50f6ee4..d143aa89a 100644 --- a/bin/client/src/pipe.rs +++ b/crates/proof-sdk/std-fpvm/src/channel.rs @@ -1,6 +1,7 @@ -//! This module contains a rudamentary pipe between two file descriptors, using [kona_common::io] +//! This module contains a rudamentary channel between two file descriptors, using [crate::io] //! for reading and writing from the file descriptors. +use crate::{io, FileDescriptor}; use alloc::boxed::Box; use async_trait::async_trait; use core::{ @@ -10,85 +11,77 @@ use core::{ pin::Pin, task::{Context, Poll}, }; -use kona_common::{errors::IOResult, io, FileDescriptor}; use kona_preimage::{ errors::{ChannelError, ChannelResult}, Channel, }; -/// [PipeHandle] is a handle for one end of a bidirectional pipe. +/// [FileChannel] is a handle for one end of a bidirectional channel. #[derive(Debug, Clone, Copy)] -pub struct PipeHandle { +pub struct FileChannel { /// File descriptor to read from read_handle: FileDescriptor, /// File descriptor to write to write_handle: FileDescriptor, } -impl PipeHandle { - /// Create a new [PipeHandle] from two file descriptors. +impl FileChannel { + /// Create a new [FileChannel] from two file descriptors. pub const fn new(read_handle: FileDescriptor, write_handle: FileDescriptor) -> Self { Self { read_handle, write_handle } } - /// Read from the pipe into the given buffer. - pub fn read(&self, buf: &mut [u8]) -> IOResult { - io::read(self.read_handle, buf) - } - - /// Reads exactly `buf.len()` bytes into `buf`. - pub fn read_exact<'a>(&self, buf: &'a mut [u8]) -> impl Future> + 'a { - ReadFuture { pipe_handle: *self, buf: RefCell::new(buf), read: 0 } - } - - /// Write the given buffer to the pipe. - pub fn write<'a>(&self, buf: &'a [u8]) -> impl Future> + 'a { - WriteFuture { pipe_handle: *self, buf, written: 0 } - } - - /// Returns the read handle for the pipe. + /// Returns the a copy of the [FileDescriptor] used for the read end of the channel. pub const fn read_handle(&self) -> FileDescriptor { self.read_handle } - /// Returns the write handle for the pipe. + /// Returns the a copy of the [FileDescriptor] used for the write end of the channel. pub const fn write_handle(&self) -> FileDescriptor { self.write_handle } } #[async_trait] -impl Channel for PipeHandle { +impl Channel for FileChannel { async fn read(&self, buf: &mut [u8]) -> ChannelResult { - self.read(buf).map_err(|_| ChannelError::Closed) + io::read(self.read_handle, buf).map_err(|_| ChannelError::Closed) } async fn read_exact(&self, buf: &mut [u8]) -> ChannelResult { - self.read_exact(buf).await.map_err(|_| ChannelError::Closed) + ReadFuture::new(*self, buf).await.map_err(|_| ChannelError::Closed) } async fn write(&self, buf: &[u8]) -> ChannelResult { - self.write(buf).await.map_err(|_| ChannelError::Closed) + WriteFuture::new(*self, buf).await.map_err(|_| ChannelError::Closed) } } -/// A future that reads from a pipe, returning [Poll::Ready] when the buffer is full. +/// A future that reads from a channel, returning [Poll::Ready] when the buffer is full. struct ReadFuture<'a> { - /// The pipe handle to read from - pipe_handle: PipeHandle, + /// The channel to read from + channel: FileChannel, /// The buffer to read into buf: RefCell<&'a mut [u8]>, /// The number of bytes read so far read: usize, } +impl<'a> ReadFuture<'a> { + /// Create a new [ReadFuture] from a channel and a buffer. + fn new(channel: FileChannel, buf: &'a mut [u8]) -> Self { + Self { channel, buf: RefCell::new(buf), read: 0 } + } +} + impl Future for ReadFuture<'_> { - type Output = IOResult; + type Output = ChannelResult; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { let mut buf = self.buf.borrow_mut(); let buf_len = buf.len(); - let chunk_read = self.pipe_handle.read(&mut buf[self.read..])?; + let chunk_read = io::read(self.channel.read_handle, &mut buf[self.read..]) + .map_err(|_| ChannelError::Closed)?; // Drop the borrow on self. drop(buf); @@ -106,21 +99,29 @@ impl Future for ReadFuture<'_> { } } -/// A future that writes to a pipe, returning [Poll::Ready] when the full buffer has been written. +/// A future that writes to a channel, returning [Poll::Ready] when the full buffer has been +/// written. struct WriteFuture<'a> { - /// The pipe handle to write to - pipe_handle: PipeHandle, + /// The channel to write to + channel: FileChannel, /// The buffer to write buf: &'a [u8], /// The number of bytes written so far written: usize, } +impl<'a> WriteFuture<'a> { + /// Create a new [WriteFuture] from a channel and a buffer. + const fn new(channel: FileChannel, buf: &'a [u8]) -> Self { + Self { channel, buf, written: 0 } + } +} + impl Future for WriteFuture<'_> { - type Output = IOResult; + type Output = ChannelResult; fn poll(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { - match io::write(self.pipe_handle.write_handle(), &self.buf[self.written..]) { + match io::write(self.channel.write_handle(), &self.buf[self.written..]) { Ok(0) => Poll::Ready(Ok(self.written)), // Finished writing Ok(n) => { self.written += n; @@ -133,7 +134,7 @@ impl Future for WriteFuture<'_> { ctx.waker().wake_by_ref(); Poll::Pending } - Err(e) => Poll::Ready(Err(e)), + Err(_) => Poll::Ready(Err(ChannelError::Closed)), } } } @@ -146,8 +147,8 @@ mod tests { fn test_get_read_handle() { let read_handle = FileDescriptor::StdIn; let write_handle = FileDescriptor::StdOut; - let pipe_handle = PipeHandle::new(read_handle, write_handle); - let ref_read_handle = pipe_handle.read_handle(); + let chan = FileChannel::new(read_handle, write_handle); + let ref_read_handle = chan.read_handle(); assert_eq!(read_handle, ref_read_handle); } } diff --git a/crates/proof-sdk/common/src/errors.rs b/crates/proof-sdk/std-fpvm/src/errors.rs similarity index 87% rename from crates/proof-sdk/common/src/errors.rs rename to crates/proof-sdk/std-fpvm/src/errors.rs index 27cc7b0c6..a8fbec07c 100644 --- a/crates/proof-sdk/common/src/errors.rs +++ b/crates/proof-sdk/std-fpvm/src/errors.rs @@ -1,4 +1,4 @@ -//! Errors for the `kona-common` crate. +//! Errors for the `kona-std-fpvm` crate. use thiserror::Error; diff --git a/crates/proof-sdk/std-fpvm/src/io.rs b/crates/proof-sdk/std-fpvm/src/io.rs new file mode 100644 index 000000000..d7301d9a9 --- /dev/null +++ b/crates/proof-sdk/std-fpvm/src/io.rs @@ -0,0 +1,83 @@ +//! This module contains the `ClientIO` struct, which is a system call interface for the kernel. + +use crate::{errors::IOResult, BasicKernelInterface, FileDescriptor}; +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(target_arch = "mips")] { + #[doc = "Concrete implementation of the [BasicKernelInterface] trait for the `MIPS32rel1` target architecture."] + pub(crate) type ClientIO = crate::mips32::io::Mips32IO; + } else if #[cfg(target_arch = "riscv64")] { + #[doc = "Concrete implementation of the [BasicKernelInterface] trait for the `riscv64` target architecture."] + pub(crate) type ClientIO = crate::riscv64::io::RiscV64IO; + } else { + use std::{fs::File, os::fd::FromRawFd, io::{Read, Write}}; + use crate::errors::IOError; + + #[doc = "Native implementation of the [BasicKernelInterface] trait."] + pub(crate) struct NativeClientIO; + + impl BasicKernelInterface for NativeClientIO { + fn write(fd: FileDescriptor, buf: &[u8]) -> IOResult { + unsafe { + let mut file = File::from_raw_fd(fd as i32); + file.write_all(buf).map_err(|_| IOError(-9))?; + std::mem::forget(file); + Ok(buf.len()) + } + } + + fn read(fd: FileDescriptor, buf: &mut [u8]) -> IOResult { + unsafe { + let mut file = File::from_raw_fd(fd as i32); + file.read(buf).map_err(|_| IOError(-9))?; + std::mem::forget(file); + Ok(buf.len()) + } + } + + fn exit(code: usize) -> ! { + std::process::exit(code as i32) + } + } + + #[doc = "Native implementation of the [BasicKernelInterface] trait."] + pub(crate) type ClientIO = NativeClientIO; + } +} + +/// Print the passed string to the standard output [FileDescriptor]. +/// +/// # Panics +/// Panics if the write operation fails. +#[inline] +pub fn print(s: &str) { + ClientIO::write(FileDescriptor::StdOut, s.as_bytes()).expect("Error writing to stdout."); +} + +/// Print the passed string to the standard error [FileDescriptor]. +/// +/// # Panics +/// Panics if the write operation fails. +#[inline] +pub fn print_err(s: &str) { + ClientIO::write(FileDescriptor::StdErr, s.as_bytes()).expect("Error writing to stderr."); +} + +/// Write the passed buffer to the given [FileDescriptor]. +#[inline] +pub fn write(fd: FileDescriptor, buf: &[u8]) -> IOResult { + ClientIO::write(fd, buf) +} + +/// Write the passed buffer to the given [FileDescriptor]. +#[inline] +pub fn read(fd: FileDescriptor, buf: &mut [u8]) -> IOResult { + ClientIO::read(fd, buf) +} + +/// Exit the process with the given exit code. +#[inline] +pub fn exit(code: usize) -> ! { + ClientIO::exit(code) +} diff --git a/crates/proof-sdk/common/src/lib.rs b/crates/proof-sdk/std-fpvm/src/lib.rs similarity index 78% rename from crates/proof-sdk/common/src/lib.rs rename to crates/proof-sdk/std-fpvm/src/lib.rs index 884f6137c..5c34de005 100644 --- a/crates/proof-sdk/common/src/lib.rs +++ b/crates/proof-sdk/std-fpvm/src/lib.rs @@ -5,7 +5,7 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(target_arch = "mips", feature(asm_experimental_arch))] -#![cfg_attr(any(target_arch = "mips", target_arch = "riscv64", target_os = "zkvm"), no_std)] +#![cfg_attr(any(target_arch = "mips", target_arch = "riscv64"), no_std)] extern crate alloc; @@ -13,6 +13,9 @@ pub mod errors; pub mod io; +#[cfg(feature = "tracing")] +pub mod tracing; + pub mod malloc; mod traits; @@ -21,8 +24,8 @@ pub use traits::BasicKernelInterface; mod types; pub use types::FileDescriptor; -mod executor; -pub use executor::block_on; +mod channel; +pub use channel::FileChannel; pub(crate) mod linux; @@ -31,6 +34,3 @@ pub(crate) mod mips32; #[cfg(target_arch = "riscv64")] pub(crate) mod riscv64; - -#[cfg(target_os = "zkvm")] -pub(crate) mod zkvm; diff --git a/crates/proof-sdk/common/src/linux.rs b/crates/proof-sdk/std-fpvm/src/linux.rs similarity index 100% rename from crates/proof-sdk/common/src/linux.rs rename to crates/proof-sdk/std-fpvm/src/linux.rs diff --git a/crates/proof-sdk/common/src/malloc.rs b/crates/proof-sdk/std-fpvm/src/malloc.rs similarity index 96% rename from crates/proof-sdk/common/src/malloc.rs rename to crates/proof-sdk/std-fpvm/src/malloc.rs index 109bc1d72..324d13d53 100644 --- a/crates/proof-sdk/common/src/malloc.rs +++ b/crates/proof-sdk/std-fpvm/src/malloc.rs @@ -44,7 +44,7 @@ macro_rules! alloc_heap { ($size:expr) => {{ #[cfg(any(target_arch = "mips", target_arch = "riscv64"))] { - use kona_common::malloc::global_allocator::init_allocator; + use $crate::malloc::global_allocator::init_allocator; static mut HEAP: [u8; $size] = [0u8; $size]; unsafe { init_allocator(HEAP.as_mut_ptr(), $size) } diff --git a/crates/proof-sdk/common/src/mips32/io.rs b/crates/proof-sdk/std-fpvm/src/mips32/io.rs similarity index 100% rename from crates/proof-sdk/common/src/mips32/io.rs rename to crates/proof-sdk/std-fpvm/src/mips32/io.rs diff --git a/crates/proof-sdk/common/src/mips32/mod.rs b/crates/proof-sdk/std-fpvm/src/mips32/mod.rs similarity index 100% rename from crates/proof-sdk/common/src/mips32/mod.rs rename to crates/proof-sdk/std-fpvm/src/mips32/mod.rs diff --git a/crates/proof-sdk/common/src/mips32/syscall.rs b/crates/proof-sdk/std-fpvm/src/mips32/syscall.rs similarity index 100% rename from crates/proof-sdk/common/src/mips32/syscall.rs rename to crates/proof-sdk/std-fpvm/src/mips32/syscall.rs diff --git a/crates/proof-sdk/common/src/riscv64/io.rs b/crates/proof-sdk/std-fpvm/src/riscv64/io.rs similarity index 100% rename from crates/proof-sdk/common/src/riscv64/io.rs rename to crates/proof-sdk/std-fpvm/src/riscv64/io.rs diff --git a/crates/proof-sdk/common/src/riscv64/mod.rs b/crates/proof-sdk/std-fpvm/src/riscv64/mod.rs similarity index 100% rename from crates/proof-sdk/common/src/riscv64/mod.rs rename to crates/proof-sdk/std-fpvm/src/riscv64/mod.rs diff --git a/crates/proof-sdk/common/src/riscv64/syscall.rs b/crates/proof-sdk/std-fpvm/src/riscv64/syscall.rs similarity index 100% rename from crates/proof-sdk/common/src/riscv64/syscall.rs rename to crates/proof-sdk/std-fpvm/src/riscv64/syscall.rs diff --git a/crates/proof-sdk/std-fpvm/src/tracing.rs b/crates/proof-sdk/std-fpvm/src/tracing.rs new file mode 100644 index 000000000..ca2458256 --- /dev/null +++ b/crates/proof-sdk/std-fpvm/src/tracing.rs @@ -0,0 +1,78 @@ +//! This module contains + +use crate::io; +use alloc::{ + format, + string::{String, ToString}, +}; +use tracing::{ + field::{Field, Visit}, + span::{Attributes, Id, Record}, + Event, Level, Metadata, Subscriber, +}; + +/// Custom [Subscriber] implementation that uses [crate::io] to write log entries to +/// [crate::FileDescriptor::StdOut]. +#[derive(Debug, Clone)] +pub struct FpvmTracingSubscriber { + min_level: Level, +} + +impl FpvmTracingSubscriber { + /// Create a new [FpvmTracingSubscriber] with the specified minimum log level. + pub const fn new(min_level: Level) -> Self { + Self { min_level } + } +} + +impl Subscriber for FpvmTracingSubscriber { + fn enabled(&self, _metadata: &Metadata<'_>) -> bool { + true + } + + fn new_span(&self, _span: &Attributes<'_>) -> Id { + Id::from_u64(1) + } + + fn record(&self, _span: &Id, _values: &Record<'_>) {} + + fn record_follows_from(&self, _span: &Id, _follows: &Id) {} + + fn event(&self, event: &Event<'_>) { + let metadata = event.metadata(); + // Comparisons for the [Level] type are inverted. See the [Level] documentation for more + // information. + if *metadata.level() > self.min_level { + return; + } + + let mut visitor = FieldVisitor::new(); + event.record(&mut visitor); + io::print(&format!("[{}] {}: {}", metadata.level(), metadata.target(), visitor.message)); + } + + fn enter(&self, _span: &Id) {} + + fn exit(&self, _span: &Id) {} +} + +/// Custom [Visit] implementation to extract log field values. +struct FieldVisitor { + message: String, +} + +impl FieldVisitor { + const fn new() -> Self { + Self { message: String::new() } + } +} + +impl Visit for FieldVisitor { + fn record_debug(&mut self, _field: &Field, value: &dyn core::fmt::Debug) { + self.message = format!("{:?}", value); + } + + fn record_str(&mut self, _field: &Field, value: &str) { + self.message = value.to_string(); + } +} diff --git a/crates/proof-sdk/common/src/traits/basic.rs b/crates/proof-sdk/std-fpvm/src/traits/basic.rs similarity index 100% rename from crates/proof-sdk/common/src/traits/basic.rs rename to crates/proof-sdk/std-fpvm/src/traits/basic.rs diff --git a/crates/proof-sdk/common/src/traits/mod.rs b/crates/proof-sdk/std-fpvm/src/traits/mod.rs similarity index 100% rename from crates/proof-sdk/common/src/traits/mod.rs rename to crates/proof-sdk/std-fpvm/src/traits/mod.rs diff --git a/crates/proof-sdk/common/src/types.rs b/crates/proof-sdk/std-fpvm/src/types.rs similarity index 87% rename from crates/proof-sdk/common/src/types.rs rename to crates/proof-sdk/std-fpvm/src/types.rs index eb982fdcf..6a7fd724d 100644 --- a/crates/proof-sdk/common/src/types.rs +++ b/crates/proof-sdk/std-fpvm/src/types.rs @@ -1,4 +1,4 @@ -//! This module contains the local types for the `kona-common` crate. +//! This module contains the local types for the `kona-std-fpvm` crate. /// File descriptors available to the `client` within the FPVM kernel. #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -17,8 +17,6 @@ pub enum FileDescriptor { PreimageRead, /// Write-only. Used to request pre-images. PreimageWrite, - /// Other file descriptor. - Wildcard(usize), } impl From for usize { @@ -31,7 +29,6 @@ impl From for usize { FileDescriptor::HintWrite => 4, FileDescriptor::PreimageRead => 5, FileDescriptor::PreimageWrite => 6, - FileDescriptor::Wildcard(value) => value, } } } @@ -55,7 +52,6 @@ mod tests { assert_eq!(usize::from(FileDescriptor::HintWrite), 4); assert_eq!(usize::from(FileDescriptor::PreimageRead), 5); assert_eq!(usize::from(FileDescriptor::PreimageWrite), 6); - assert_eq!(usize::from(FileDescriptor::Wildcard(7)), 7); } #[test] @@ -67,6 +63,5 @@ mod tests { assert_eq!(i32::from(FileDescriptor::HintWrite), 4); assert_eq!(i32::from(FileDescriptor::PreimageRead), 5); assert_eq!(i32::from(FileDescriptor::PreimageWrite), 6); - assert_eq!(i32::from(FileDescriptor::Wildcard(7)), 7); } } diff --git a/justfile b/justfile index 5419c54c7..2678f06ab 100644 --- a/justfile +++ b/justfile @@ -33,9 +33,8 @@ action-tests test_name='Test_ProgramAction' *args='': (cd monorepo && make devnet-allocs) fi - echo "Building client and host programs for the native target" - just build-native --bin kona --profile release-client-lto --features tracing-subscriber && \ - just build-native --bin kona-host --release + echo "Building host program for the native target" + just build-native --bin kona-host --release echo "Running action tests for the client program on the native target" export KONA_HOST_PATH="{{justfile_directory()}}/target/release/kona-host" @@ -67,23 +66,23 @@ fmt-native-check: lint-native: fmt-native-check lint-docs cargo +nightly clippy --workspace --all --all-features --all-targets -- -D warnings -# Lint the workspace (mips arch). Currently, only the `kona-common` crate is linted for the `cannon` target, as it is the only crate with architecture-specific code. +# Lint the workspace (mips arch). Currently, only the `kona-std-fpvm` crate is linted for the `cannon` target, as it is the only crate with architecture-specific code. lint-cannon: docker run \ --rm \ --platform linux/amd64 \ -v `pwd`/:/workdir \ -w="/workdir" \ - ghcr.io/anton-rs/kona/cannon-builder:main cargo +nightly clippy -p kona-common --all-features --target /mips-unknown-none.json -Zbuild-std=core,alloc -- -D warnings + ghcr.io/anton-rs/kona/cannon-builder:main cargo +nightly clippy -p kona-std-fpvm --all-features --target /mips-unknown-none.json -Zbuild-std=core,alloc -- -D warnings -# Lint the workspace (risc-v arch). Currently, only the `kona-common` crate is linted for the `asterisc` target, as it is the only crate with architecture-specific code. +# Lint the workspace (risc-v arch). Currently, only the `kona-std-fpvm` crate is linted for the `asterisc` target, as it is the only crate with architecture-specific code. lint-asterisc: docker run \ --rm \ --platform linux/amd64 \ -v `pwd`/:/workdir \ -w="/workdir" \ - ghcr.io/anton-rs/kona/asterisc-builder:main cargo +nightly clippy -p kona-common --all-features --target riscv64gc-unknown-linux-gnu -Zbuild-std=core,alloc -- -D warnings + ghcr.io/anton-rs/kona/asterisc-builder:main cargo +nightly clippy -p kona-std-fpvm --all-features --target riscv64gc-unknown-linux-gnu -Zbuild-std=core,alloc -- -D warnings # Lint the Rust documentation lint-docs: @@ -107,7 +106,7 @@ build-cannon *args='': --platform linux/amd64 \ -v `pwd`/:/workdir \ -w="/workdir" \ - ghcr.io/anton-rs/kona/cannon-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host --exclude kona-derive-alloy + ghcr.io/anton-rs/kona/cannon-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host # Build for the `asterisc` target. Any crates that require the stdlib are excluded from the build for this target. build-asterisc *args='': @@ -116,7 +115,7 @@ build-asterisc *args='': --platform linux/amd64 \ -v `pwd`/:/workdir \ -w="/workdir" \ - ghcr.io/anton-rs/kona/asterisc-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host --exclude kona-derive-alloy + ghcr.io/anton-rs/kona/asterisc-builder:main cargo build --workspace -Zbuild-std=core,alloc $@ --exclude kona-host # Build the `kona-client` prestate artifacts for the latest release. build-client-prestate-asterisc-artifacts kona_tag asterisc_tag out='./prestate-artifacts-asterisc':