From dd23c0466f0e2fcdc7b55a4c7c58a4dddd0150d2 Mon Sep 17 00:00:00 2001 From: clabby Date: Tue, 4 Jun 2024 01:15:20 -0400 Subject: [PATCH] feat(client): `BootInfo` Begins the client program, adding the `BootInfo` prologue phase implementation (sans the `RollupConfig` / `ChainConfig`, waiting on https://github.com/ethereum-optimism/kona/issues/193 for these) --- Cargo.lock | 8 ++ bin/host/Cargo.toml | 2 +- bin/host/src/kv/local.rs | 14 +-- bin/programs/client/Cargo.toml | 11 +++ bin/programs/client/justfile | 41 ++++++++ bin/programs/client/src/boot.rs | 95 +++++++++++++++++++ .../client/src/comms/caching_oracle.rs | 71 ++++++++++++++ bin/programs/client/src/comms/mod.rs | 21 ++++ bin/programs/client/src/executor/mod.rs | 3 + bin/programs/client/src/lib.rs | 18 ++++ bin/programs/client/src/main.rs | 14 ++- bin/programs/minimal/Cargo.toml | 1 + bin/programs/minimal/src/main.rs | 3 +- bin/programs/simple-revm/src/main.rs | 5 +- crates/common-proc/src/lib.rs | 6 +- crates/preimage/src/hint.rs | 2 +- crates/preimage/src/oracle.rs | 2 +- 17 files changed, 298 insertions(+), 19 deletions(-) create mode 100644 bin/programs/client/justfile create mode 100644 bin/programs/client/src/boot.rs create mode 100644 bin/programs/client/src/comms/caching_oracle.rs create mode 100644 bin/programs/client/src/comms/mod.rs create mode 100644 bin/programs/client/src/executor/mod.rs create mode 100644 bin/programs/client/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index a0dc76cca..fd6d73642 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1582,9 +1582,15 @@ dependencies = [ name = "kona-client" version = "0.1.0" dependencies = [ + "alloy-primitives", + "anyhow", + "async-trait", "cfg-if", "kona-common", "kona-common-proc", + "kona-preimage", + "lru", + "spin 0.9.8", ] [[package]] @@ -1657,6 +1663,7 @@ dependencies = [ "clap", "command-fds", "futures", + "kona-client", "kona-common", "kona-mpt", "kona-preimage", @@ -1813,6 +1820,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" name = "minimal" version = "0.1.0" dependencies = [ + "anyhow", "cfg-if", "kona-common", "kona-common-proc", diff --git a/bin/host/Cargo.toml b/bin/host/Cargo.toml index d9a8a54b4..769133558 100644 --- a/bin/host/Cargo.toml +++ b/bin/host/Cargo.toml @@ -6,7 +6,6 @@ license.workspace = true authors.workspace = true repository.workspace = true homepage.workspace = true -exclude.workspace = true [dependencies] # workspace @@ -16,6 +15,7 @@ alloy-primitives = { workspace = true, features = ["serde"] } revm = { workspace = true, features = ["std", "c-kzg", "secp256k1", "portable", "blst"] } # local +kona-client = { path = "../programs/client", version = "0.1.0" } kona-common = { path = "../../crates/common", version = "0.0.1" } kona-preimage = { path = "../../crates/preimage", version = "0.0.1" } kona-mpt = { path = "../../crates/mpt", version = "0.0.1" } diff --git a/bin/host/src/kv/local.rs b/bin/host/src/kv/local.rs index ec7eb8723..de64cfa74 100644 --- a/bin/host/src/kv/local.rs +++ b/bin/host/src/kv/local.rs @@ -2,17 +2,13 @@ use super::KeyValueStore; use crate::cli::HostCli; -use alloy_primitives::{B256, U256}; +use alloy_primitives::B256; +use kona_client::{ + L1_HEAD_KEY, L2_CHAIN_CONFIG_KEY, L2_CHAIN_ID_KEY, L2_CLAIM_BLOCK_NUMBER_KEY, L2_CLAIM_KEY, + L2_OUTPUT_ROOT_KEY, L2_ROLLUP_CONFIG_KEY, +}; use kona_preimage::PreimageKey; -pub(crate) const L1_HEAD_KEY: U256 = U256::from_be_slice(&[1]); -pub(crate) const L2_OUTPUT_ROOT_KEY: U256 = U256::from_be_slice(&[2]); -pub(crate) const L2_CLAIM_KEY: U256 = U256::from_be_slice(&[3]); -pub(crate) const L2_CLAIM_BLOCK_NUMBER_KEY: U256 = U256::from_be_slice(&[4]); -pub(crate) const L2_CHAIN_ID_KEY: U256 = U256::from_be_slice(&[5]); -pub(crate) const L2_CHAIN_CONFIG_KEY: U256 = U256::from_be_slice(&[6]); -pub(crate) const L2_ROLLUP_CONFIG_KEY: U256 = U256::from_be_slice(&[7]); - /// A simple, synchronous key-value store that returns data from a [HostCli] config. pub struct LocalKeyValueStore { cfg: HostCli, diff --git a/bin/programs/client/Cargo.toml b/bin/programs/client/Cargo.toml index f52d1606a..31a48b0ea 100644 --- a/bin/programs/client/Cargo.toml +++ b/bin/programs/client/Cargo.toml @@ -8,6 +8,17 @@ repository.workspace = true homepage.workspace = true [dependencies] +# workspace cfg-if.workspace = true +alloy-primitives.workspace = true +anyhow.workspace = true + +# local kona-common = { path = "../../../crates/common" } kona-common-proc = { path = "../../../crates/common-proc" } +kona-preimage = { path = "../../../crates/preimage" } + +# external +lru = "0.12.3" +async-trait = "0.1.80" +spin = "0.9.8" diff --git a/bin/programs/client/justfile b/bin/programs/client/justfile new file mode 100644 index 000000000..ed98dc2f6 --- /dev/null +++ b/bin/programs/client/justfile @@ -0,0 +1,41 @@ +set fallback := true + +# default recipe to display help information +default: + @just --list + +# Run the client program natively with the hos +run-client-native l1_rpc l1_beacon_rpc l2_rpc verbosity: + #!/usr/bin/env bash + + ROLLUP_CONFIG_PATH="x" + L2_GENESIS_PATH="x" + L1_HEAD=$(cast 2b 0) + L2_HEAD=$(cast 2b 0) + L2_OUTPUT_ROOT=$(cast 2b 0) + L2_CLAIM=$(cast 2b 0) + L2_BLOCK_NUMBER=0 + + L1_NODE_ADDRESS="{{l1_rpc}}" + L1_BEACON_ADDRESS="{{l1_beacon_rpc}}" + L2_NODE_ADDRESS="{{l2_rpc}}" + + CLIENT_BIN_PATH="./target/release-client-lto/kona-client" + + echo "Building client program..." + cargo build --bin kona-client --profile release-client-lto + echo "Running host program with native client program..." + (cd ../../.. && cargo r --bin kona-host --release -- \ + --rollup-config $ROLLUP_CONFIG_PATH \ + --network optimism \ + --l2-genesis-path $L2_GENESIS_PATH \ + --l1-head $L1_HEAD \ + --l2-head $L2_HEAD \ + --l2-claim $L2_CLAIM \ + --l2-output-root $L2_OUTPUT_ROOT \ + --l2-block-number $L2_BLOCK_NUMBER \ + --l1-node-address $L1_NODE_ADDRESS \ + --l1-beacon-address $L1_BEACON_ADDRESS \ + --l2-node-address $L2_NODE_ADDRESS \ + --exec $CLIENT_BIN_PATH \ + {{verbosity}}) diff --git a/bin/programs/client/src/boot.rs b/bin/programs/client/src/boot.rs new file mode 100644 index 000000000..d0ebdbc4c --- /dev/null +++ b/bin/programs/client/src/boot.rs @@ -0,0 +1,95 @@ +//! This module contains the prologue phase of the client program, pulling in the boot information +//! through the `PreimageOracle` ABI as local keys. + +use alloy_primitives::{B256, U256}; +use anyhow::{anyhow, Result}; +use kona_preimage::{PreimageKey, PreimageOracleClient}; + +/// The local key ident for the L1 head hash. +pub const L1_HEAD_KEY: U256 = U256::from_be_slice(&[1]); + +/// The local key ident for the L2 output root. +pub const L2_OUTPUT_ROOT_KEY: U256 = U256::from_be_slice(&[2]); + +/// The local key ident for the L2 output root claim. +pub const L2_CLAIM_KEY: U256 = U256::from_be_slice(&[3]); + +/// The local key ident for the L2 claim block number. +pub const L2_CLAIM_BLOCK_NUMBER_KEY: U256 = U256::from_be_slice(&[4]); + +/// The local key ident for the L2 chain ID. +pub const L2_CHAIN_ID_KEY: U256 = U256::from_be_slice(&[5]); + +/// The local key ident for the L2 chain config. +pub const L2_CHAIN_CONFIG_KEY: U256 = U256::from_be_slice(&[6]); + +/// The local key ident for the L2 rollup config. +pub const L2_ROLLUP_CONFIG_KEY: U256 = U256::from_be_slice(&[7]); + +/// The boot information for the client program. +/// +/// **Verified inputs:** +/// - `l1_head`: The L1 head hash containing the safe L2 chain data that may reproduce the L2 head +/// hash. +/// - `l2_output_root`: The latest finalized L2 output root. +/// - `chain_id`: The L2 chain ID. +/// +/// **User submitted inputs:** +/// - `l2_claim`: The L2 output root claim. +/// - `l2_claim_block`: The L2 claim block number. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct BootInfo { + /// The L1 head hash containing the safe L2 chain data that may reproduce the L2 head hash. + pub l1_head: B256, + /// The latest finalized L2 output root. + pub l2_output_root: B256, + /// The L2 output root claim. + pub l2_claim: B256, + /// The L2 claim block number. + pub l2_claim_block: u64, + /// The L2 chain ID. + pub chain_id: u64, +} + +impl BootInfo { + /// Load the boot information from the preimage oracle. + /// + /// ## Takes + /// - `ORACLE_READER`: The preimage oracle reader. + /// + /// ## Returns + /// - `Ok(BootInfo)`: The boot information. + /// - `Err(_)`: Failed to load the boot information. + pub async fn load(oracle: &O) -> Result + where + O: PreimageOracleClient + Send, + { + let mut l1_head: B256 = B256::ZERO; + oracle.get_exact(PreimageKey::new_local(L1_HEAD_KEY.to()), l1_head.as_mut()).await?; + + let mut l2_output_root: B256 = B256::ZERO; + oracle + .get_exact(PreimageKey::new_local(L2_OUTPUT_ROOT_KEY.to()), l2_output_root.as_mut()) + .await?; + + let mut l2_claim: B256 = B256::ZERO; + oracle.get_exact(PreimageKey::new_local(L2_CLAIM_KEY.to()), l2_claim.as_mut()).await?; + + let l2_claim_block = u64::from_be_bytes( + oracle + .get(PreimageKey::new_local(L2_CLAIM_BLOCK_NUMBER_KEY.to())) + .await? + .try_into() + .map_err(|_| anyhow!("Failed to convert L2 claim block number to u64"))?, + ); + let chain_id = u64::from_be_bytes( + oracle + .get(PreimageKey::new_local(L2_CHAIN_ID_KEY.to())) + .await? + .try_into() + .map_err(|_| anyhow!("Failed to convert L2 chain ID to u64"))?, + ); + + Ok(Self { l1_head, l2_output_root, l2_claim, l2_claim_block, chain_id }) + } +} diff --git a/bin/programs/client/src/comms/caching_oracle.rs b/bin/programs/client/src/comms/caching_oracle.rs new file mode 100644 index 000000000..8099eeb8e --- /dev/null +++ b/bin/programs/client/src/comms/caching_oracle.rs @@ -0,0 +1,71 @@ +//! Contains the [CachingOracle], which is a wrapper around an [OracleReader] that stores a +//! configurable number of responses in an [LruCache] for quick retrieval. +//! +//! [OracleReader]: kona_preimage::OracleReader + +use crate::ORACLE_READER; +use alloc::{boxed::Box, sync::Arc, vec::Vec}; +use anyhow::Result; +use async_trait::async_trait; +use core::num::NonZeroUsize; +use kona_preimage::{PreimageKey, PreimageOracleClient}; +use lru::LruCache; +use spin::Mutex; + +/// A wrapper around an [OracleReader] that stores a configurable number of responses in an +/// [LruCache] for quick retrieval. +/// +/// [OracleReader]: kona_preimage::OracleReader +#[derive(Debug, Clone)] +pub struct CachingOracle { + /// The spin-locked cache that stores the responses from the oracle. + cache: Arc>>>, +} + +impl CachingOracle { + /// Creates a new [CachingOracle] that wraps the given [OracleReader] and stores up to `N` + /// responses in the cache. + /// + /// [OracleReader]: kona_preimage::OracleReader + pub fn new() -> Self { + Self { + cache: Arc::new(Mutex::new(LruCache::new( + NonZeroUsize::new(N).expect("N must be greater than 0"), + ))), + } + } +} + +impl Default for CachingOracle { + fn default() -> Self { + Self::new() + } +} + +#[async_trait] +impl PreimageOracleClient for CachingOracle { + async fn get(&self, key: PreimageKey) -> Result> { + let mut cache_lock = self.cache.lock(); + if let Some(value) = cache_lock.get(&key) { + Ok(value.clone()) + } else { + let value = ORACLE_READER.get(key).await?; + cache_lock.put(key, value.clone()); + Ok(value) + } + } + + async fn get_exact(&self, key: PreimageKey, buf: &mut [u8]) -> Result<()> { + let mut cache_lock = self.cache.lock(); + if let Some(value) = cache_lock.get(&key) { + // SAFETY: The value never enters the cache unless the preimage length matches the + // buffer length, due to the checks in the OracleReader. + buf.copy_from_slice(value.as_slice()); + Ok(()) + } else { + ORACLE_READER.get_exact(key, buf).await?; + cache_lock.put(key, buf.to_vec()); + Ok(()) + } + } +} diff --git a/bin/programs/client/src/comms/mod.rs b/bin/programs/client/src/comms/mod.rs new file mode 100644 index 000000000..9627a174b --- /dev/null +++ b/bin/programs/client/src/comms/mod.rs @@ -0,0 +1,21 @@ +//! Contains the host <-> client communication utilities. + +use kona_common::FileDescriptor; +use kona_preimage::{HintWriter, OracleReader, PipeHandle}; + +mod caching_oracle; +pub use caching_oracle::CachingOracle; + +/// The global preimage oracle reader pipe. +static ORACLE_READER_PIPE: PipeHandle = + PipeHandle::new(FileDescriptor::PreimageRead, FileDescriptor::PreimageWrite); + +/// The global hint writer pipe. +static HINT_WRITER_PIPE: PipeHandle = + PipeHandle::new(FileDescriptor::HintRead, FileDescriptor::HintWrite); + +/// The global preimage oracle reader. +pub static ORACLE_READER: OracleReader = OracleReader::new(ORACLE_READER_PIPE); + +/// The global hint writer. +pub static HINT_WRITER: HintWriter = HintWriter::new(HINT_WRITER_PIPE); diff --git a/bin/programs/client/src/executor/mod.rs b/bin/programs/client/src/executor/mod.rs new file mode 100644 index 000000000..b32f646f1 --- /dev/null +++ b/bin/programs/client/src/executor/mod.rs @@ -0,0 +1,3 @@ +//! Contains the EVM executor and its associated types. + +// TODO diff --git a/bin/programs/client/src/lib.rs b/bin/programs/client/src/lib.rs new file mode 100644 index 000000000..df8075340 --- /dev/null +++ b/bin/programs/client/src/lib.rs @@ -0,0 +1,18 @@ +#![doc = include_str!("../README.md")] +#![warn(missing_debug_implementations, missing_docs, unreachable_pub, rustdoc::all)] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![no_std] + +extern crate alloc; + +mod executor; + +mod comms; +pub use comms::{CachingOracle, HINT_WRITER, ORACLE_READER}; + +mod boot; +pub use boot::{ + BootInfo, L1_HEAD_KEY, L2_CHAIN_CONFIG_KEY, L2_CHAIN_ID_KEY, L2_CLAIM_BLOCK_NUMBER_KEY, + L2_CLAIM_KEY, L2_OUTPUT_ROOT_KEY, L2_ROLLUP_CONFIG_KEY, +}; diff --git a/bin/programs/client/src/main.rs b/bin/programs/client/src/main.rs index 015eaa605..8d4ec6248 100644 --- a/bin/programs/client/src/main.rs +++ b/bin/programs/client/src/main.rs @@ -1,12 +1,22 @@ +#![doc = include_str!("../README.md")] +#![warn(missing_debug_implementations, missing_docs, unreachable_pub, rustdoc::all)] +#![deny(unused_must_use, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![no_std] #![cfg_attr(any(target_arch = "mips", target_arch = "riscv64"), no_main)] +use kona_client::{BootInfo, CachingOracle}; use kona_common::io; use kona_common_proc::client_entry; extern crate alloc; #[client_entry(0x77359400)] -fn main() { - io::print("Hello, world!\n"); +fn main() -> Result<()> { + kona_common::block_on(async move { + let caching_oracle = CachingOracle::<16>::new(); + let boot = BootInfo::load(&caching_oracle).await?; + io::print(&alloc::format!("{:?}\n", boot)); + Ok(()) + }) } diff --git a/bin/programs/minimal/Cargo.toml b/bin/programs/minimal/Cargo.toml index 030bf8e84..bc8ac4888 100644 --- a/bin/programs/minimal/Cargo.toml +++ b/bin/programs/minimal/Cargo.toml @@ -10,6 +10,7 @@ homepage.workspace = true [dependencies] cfg-if.workspace = true +anyhow.workspace = true kona-common = { path = "../../../crates/common" } kona-common-proc = { path = "../../../crates/common-proc" } diff --git a/bin/programs/minimal/src/main.rs b/bin/programs/minimal/src/main.rs index be97185be..20526a3b8 100644 --- a/bin/programs/minimal/src/main.rs +++ b/bin/programs/minimal/src/main.rs @@ -7,6 +7,7 @@ use kona_common_proc::client_entry; extern crate alloc; #[client_entry(0xFFFFFFF)] -fn main() { +fn main() -> Result<()> { io::print("Hello, world!\n"); + Ok(()) } diff --git a/bin/programs/simple-revm/src/main.rs b/bin/programs/simple-revm/src/main.rs index 8a2444f89..24e24a335 100644 --- a/bin/programs/simple-revm/src/main.rs +++ b/bin/programs/simple-revm/src/main.rs @@ -32,13 +32,13 @@ static CLIENT_HINT_PIPE: PipeHandle = PipeHandle::new(FileDescriptor::HintRead, FileDescriptor::HintWrite); #[client_entry(0xFFFFFFF)] -fn main() { +fn main() -> Result<()> { kona_common::block_on(async { let mut oracle = OracleReader::new(CLIENT_PREIMAGE_PIPE); let hint_writer = HintWriter::new(CLIENT_HINT_PIPE); io::print("Booting EVM and checking hash...\n"); - let (digest, code) = boot(&mut oracle).await.expect("Failed to boot"); + let (digest, code) = boot(&mut oracle).await?; match run_evm(&mut oracle, &hint_writer, digest, code).await { Ok(_) => io::print("Success, hashes matched!\n"), @@ -47,6 +47,7 @@ fn main() { io::exit(1); } } + Ok(()) }) } diff --git a/crates/common-proc/src/lib.rs b/crates/common-proc/src/lib.rs index 4fa684a62..7d23bca5b 100644 --- a/crates/common-proc/src/lib.rs +++ b/crates/common-proc/src/lib.rs @@ -30,7 +30,9 @@ pub fn client_entry(attr: TokenStream, input: TokenStream) -> TokenStream { let fn_name = &input_fn.sig.ident; let expanded = quote! { - fn #fn_name() { + use anyhow::Result as AnyhowResult; + + fn #fn_name() -> AnyhowResult<()> { #fn_body } @@ -41,7 +43,7 @@ pub fn client_entry(attr: TokenStream, input: TokenStream) -> TokenStream { #[no_mangle] pub extern "C" fn _start() { kona_common::alloc_heap!(HEAP_SIZE); - #fn_name(); + #fn_name().unwrap(); kona_common::io::exit(0); } diff --git a/crates/preimage/src/hint.rs b/crates/preimage/src/hint.rs index 18ebf2d57..60a2ee561 100644 --- a/crates/preimage/src/hint.rs +++ b/crates/preimage/src/hint.rs @@ -14,7 +14,7 @@ pub struct HintWriter { impl HintWriter { /// Create a new [HintWriter] from a [PipeHandle]. - pub fn new(pipe_handle: PipeHandle) -> Self { + pub const fn new(pipe_handle: PipeHandle) -> Self { Self { pipe_handle } } } diff --git a/crates/preimage/src/oracle.rs b/crates/preimage/src/oracle.rs index 4f6eea4fc..9e6833a7e 100644 --- a/crates/preimage/src/oracle.rs +++ b/crates/preimage/src/oracle.rs @@ -12,7 +12,7 @@ pub struct OracleReader { impl OracleReader { /// Create a new [OracleReader] from a [PipeHandle]. - pub fn new(pipe_handle: PipeHandle) -> Self { + pub const fn new(pipe_handle: PipeHandle) -> Self { Self { pipe_handle } }