Skip to content

Commit

Permalink
feat(client): BootInfo (#205)
Browse files Browse the repository at this point in the history
Begins the client program, adding the `BootInfo` prologue phase
implementation (sans the `RollupConfig` / `ChainConfig`, waiting on
#193 for these)
  • Loading branch information
clabby authored Jun 5, 2024
1 parent 40e984e commit 6932f76
Show file tree
Hide file tree
Showing 17 changed files with 300 additions and 19 deletions.
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bin/host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ license.workspace = true
authors.workspace = true
repository.workspace = true
homepage.workspace = true
exclude.workspace = true

[dependencies]
# workspace
Expand All @@ -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" }
Expand Down
14 changes: 5 additions & 9 deletions bin/host/src/kv/local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions bin/programs/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
43 changes: 43 additions & 0 deletions bin/programs/client/justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
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
# TODO: These configuration values are currently mocked. Once they're consumed,
# update this to offer default run or optionally alter by pre-setting them.
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}})
95 changes: 95 additions & 0 deletions bin/programs/client/src/boot.rs
Original file line number Diff line number Diff line change
@@ -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`: The preimage oracle reader.
///
/// ## Returns
/// - `Ok(BootInfo)`: The boot information.
/// - `Err(_)`: Failed to load the boot information.
pub async fn load<O>(oracle: &O) -> Result<Self>
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 })
}
}
71 changes: 71 additions & 0 deletions bin/programs/client/src/comms/caching_oracle.rs
Original file line number Diff line number Diff line change
@@ -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<const N: usize> {
/// The spin-locked cache that stores the responses from the oracle.
cache: Arc<Mutex<LruCache<PreimageKey, Vec<u8>>>>,
}

impl<const N: usize> CachingOracle<N> {
/// 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<const N: usize> Default for CachingOracle<N> {
fn default() -> Self {
Self::new()
}
}

#[async_trait]
impl<const N: usize> PreimageOracleClient for CachingOracle<N> {
async fn get(&self, key: PreimageKey) -> Result<Vec<u8>> {
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(())
}
}
}
21 changes: 21 additions & 0 deletions bin/programs/client/src/comms/mod.rs
Original file line number Diff line number Diff line change
@@ -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);
3 changes: 3 additions & 0 deletions bin/programs/client/src/executor/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//! Contains the EVM executor and its associated types.

// TODO
18 changes: 18 additions & 0 deletions bin/programs/client/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
};
14 changes: 12 additions & 2 deletions bin/programs/client/src/main.rs
Original file line number Diff line number Diff line change
@@ -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(())
})
}
1 change: 1 addition & 0 deletions bin/programs/minimal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }

Expand Down
3 changes: 2 additions & 1 deletion bin/programs/minimal/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}
Loading

0 comments on commit 6932f76

Please sign in to comment.