Skip to content

Commit

Permalink
feat(host): Host program scaffold
Browse files Browse the repository at this point in the history
Introduces the scaffolding and initial implementation of the `kona-host`
program.
  • Loading branch information
clabby committed May 27, 2024
1 parent 8cf1dec commit 9cd657a
Show file tree
Hide file tree
Showing 22 changed files with 685 additions and 55 deletions.
38 changes: 38 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 Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[workspace]
members = ["crates/*", "bin/host", "bin/programs/*"]
exclude = ["fpvm-tests/cannon-rs-tests", "bin/programs/optimism"]
exclude = ["fpvm-tests/cannon-rs-tests"]
resolver = "2"

[workspace.package]
Expand Down
11 changes: 11 additions & 0 deletions bin/host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,18 @@ anyhow.workspace = true
tracing.workspace = true
alloy-primitives = { workspace = true, features = ["serde"] }

# local
kona-common = { path = "../../crates/common", version = "0.0.1" }
kona-preimage = { path = "../../crates/preimage", version = "0.0.1" }

# external
alloy-provider = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-transport-http = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-rpc-client = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
reqwest = "0.12"
tokio = { version = "1.37.0", features = ["full"] }
clap = { version = "4.5.4", features = ["derive", "env"] }
serde = { version = "1.0.198", features = ["derive"] }
tracing-subscriber = "0.3.18"
command-fds = "0.3.0"
tempfile = "3.10"
26 changes: 13 additions & 13 deletions bin/host/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ mod parser;
pub(crate) use parser::parse_b256;

mod types;
pub(crate) use types::{Network, RpcKind};
pub(crate) use types::Network;

mod tracing_util;
pub(crate) use tracing_util::init_tracing_subscriber;
Expand All @@ -31,7 +31,7 @@ pub struct HostCli {
pub data_dir: Option<PathBuf>,
/// Address of L2 JSON-RPC endpoint to use (eth and debug namespace required).
#[clap(long)]
pub l2_node_address: String,
pub l2_node_address: Option<String>,
/// Hash of the L1 head block. Derivation stops after this block is processed.
#[clap(long, value_parser = parse_b256)]
pub l1_head: B256,
Expand All @@ -52,23 +52,23 @@ pub struct HostCli {
pub l2_genesis_path: PathBuf,
/// Address of L1 JSON-RPC endpoint to use (eth namespace required)
#[clap(long)]
pub l1_node_address: String,
pub l1_node_address: Option<String>,
/// Address of the L1 Beacon API endpoint to use.
#[clap(long)]
pub l1_beacon_address: String,
/// Trust the L1 RPC, sync faster at risk of malicious/buggy RPC providing bad or inconsistent
/// L1 data
#[clap(long)]
pub l1_trust_rpc: bool,
/// The kind of RPC provider, used to inform optimal transactions receipts fetching, and thus
/// reduce costs.
#[clap(long)]
pub l1_rpc_provider_kind: RpcKind,
pub l1_beacon_address: Option<String>,
/// Run the specified client program as a separate process detached from the host. Default is
/// to run the client program in the host process.
#[clap(long)]
pub exec: String,
/// Run in pre-image server mode without executing any client program.
/// Run in pre-image server mode without executing any client program. Defaults to `false`.
#[clap(long)]
pub server: bool,
}

impl HostCli {
pub fn is_offline(&self) -> bool {
self.l1_node_address.is_none() ||
self.l2_node_address.is_none() ||
self.l1_beacon_address.is_none()
}
}
9 changes: 2 additions & 7 deletions bin/host/src/cli/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ use serde::Serialize;
pub enum Network {
/// Optimism Mainnet
Optimism,
}

/// Available RPC provider types.
#[derive(Debug, Clone, ValueEnum, Serialize)]
pub enum RpcKind {
/// debug alloy provider
DebugRpc,
/// Optimism Sepolia
OptimismSepolia,
}
72 changes: 72 additions & 0 deletions bin/host/src/fetcher/hint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//! This module contains the [HintType] enum.

use std::fmt::Display;

/// The [HintType] enum is used to specify the type of hint that was received.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum HintType {
/// A hint that specifies the block header of a layer 1 block.
L1BlockHeader,
/// A hint that specifies the transactions of a layer 1 block.
L1Transactions,
/// A hint that specifies the state node of a layer 1 block.
L1Receipts,
/// A hint that specifies a blob in the layer 1 beacon chain.
L1Blob,
/// A hint that specifies a precompile call on layer 1.
L1Precompile,
/// A hint that specifies the block header of a layer 2 block.
L2BlockHeader,
/// A hint that specifies the transactions of a layer 2 block.
L2Transactions,
/// A hint that specifies the state node in the L2 state trie.
L2StateNode,
/// A hint that specifies the code of a contract on layer 2.
L2Code,
/// A hint that specifies the output root of a block on layer 2.
L2Output,
}

impl TryFrom<&str> for HintType {
type Error = anyhow::Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
match value {
"l1-block-header" => Ok(HintType::L1BlockHeader),
"l1-transactions" => Ok(HintType::L1Transactions),
"l1-receipts" => Ok(HintType::L1Receipts),
"l1-blob" => Ok(HintType::L1Blob),
"l1-precompile" => Ok(HintType::L1Precompile),
"l2-block-header" => Ok(HintType::L2BlockHeader),
"l2-transactions" => Ok(HintType::L2Transactions),
"l2-state-node" => Ok(HintType::L2StateNode),
"l2-code" => Ok(HintType::L2Code),
"l2-output" => Ok(HintType::L2Output),
_ => anyhow::bail!("Invalid hint type: {value}"),
}
}
}

impl From<HintType> for &str {
fn from(value: HintType) -> Self {
match value {
HintType::L1BlockHeader => "l1-block-header",
HintType::L1Transactions => "l1-transactions",
HintType::L1Receipts => "l1-receipts",
HintType::L1Blob => "l1-blob",
HintType::L1Precompile => "l1-precompile",
HintType::L2BlockHeader => "l2-block-header",
HintType::L2Transactions => "l2-transactions",
HintType::L2StateNode => "l2-state-node",
HintType::L2Code => "l2-code",
HintType::L2Output => "l2-output",
}
}
}

impl Display for HintType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s: &str = (*self).into();
write!(f, "{}", s)
}
}
119 changes: 119 additions & 0 deletions bin/host/src/fetcher/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
//! This module contains the [Fetcher] struct, which is responsible for fetching preimages from a
//! remote source.

use crate::{kv::KeyValueStore, util};
use alloy_primitives::{Bytes, B256};
use alloy_provider::{Provider, ReqwestProvider};
use anyhow::{anyhow, Result};
use kona_preimage::{PreimageKey, PreimageKeyType};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::debug;

mod hint;
pub use hint::HintType;

/// The [Fetcher] struct is responsible for fetching preimages from a remote source.
pub struct Fetcher<KV>
where
KV: KeyValueStore,
{
/// Key-value store for preimages.
kv_store: Arc<RwLock<KV>>,
/// L1 chain provider.
l1_provider: ReqwestProvider,
/// L2 chain provider.
/// TODO: OP provider, N = Optimism
#[allow(unused)]
l2_provider: ReqwestProvider,
/// The last hint that was received. [None] if no hint has been received yet.
last_hint: Option<String>,
}

impl<KV> Fetcher<KV>
where
KV: KeyValueStore,
{
/// Create a new [Fetcher] with the given [KeyValueStore].
pub fn new(
kv_store: Arc<RwLock<KV>>,
l1_provider: ReqwestProvider,
l2_provider: ReqwestProvider,
) -> Self {
Self { kv_store, l1_provider, l2_provider, last_hint: None }
}

/// Set the last hint to be received.
pub fn hint(&mut self, hint: &str) {
debug!(target: "fetcher", "Received hint: {hint}");
self.last_hint = Some(hint.to_string());
}

/// Get the preimage for the given key.
pub async fn get_preimage(&self, key: B256) -> Result<Vec<u8>> {
debug!(target: "fetcher", "Pre-image requested. Key: {key}");

// Acquire a read lock on the key-value store.
let kv_lock = self.kv_store.read().await;
let mut preimage = kv_lock.get(key).cloned();

// Drop the read lock before beginning the loop.
drop(kv_lock);

// Use a loop to keep retrying the prefetch as long as the key is not found
while preimage.is_none() && self.last_hint.is_some() {
let hint = self.last_hint.as_ref().expect("Cannot be None");
self.prefetch(hint).await?;

let kv_lock = self.kv_store.read().await;
preimage = kv_lock.get(key).cloned();
}

preimage.ok_or_else(|| anyhow!("Preimage not found."))
}

/// Fetch the preimage for the given hint and insert it into the key-value store.
async fn prefetch(&self, hint: &str) -> Result<()> {
let (hint_type, hint_data) = util::parse_hint(hint)?;
debug!(target: "fetcher", "Fetching hint: {hint_type} {hint_data}");

match hint_type {
HintType::L1BlockHeader => {
// Validate the hint data length.
if hint_data.len() != 32 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the raw header from the L1 chain provider.
let hash: B256 = hint_data
.as_ref()
.try_into()
.map_err(|e| anyhow!("Failed to convert bytes to B256: {e}"))?;
let raw_header: Bytes = self
.l1_provider
.client()
.request("debug_getRawHeader", [hash])
.await
.map_err(|e| anyhow!(e))?;

// Acquire a lock on the key-value store and set the preimage.
let mut kv_lock = self.kv_store.write().await;
kv_lock.set(
PreimageKey::new(*hash, PreimageKeyType::Keccak256).into(),
raw_header.into(),
);
}
HintType::L1Transactions => todo!(),
HintType::L1Receipts => todo!(),
HintType::L1Blob => todo!(),
HintType::L1Precompile => todo!(),
HintType::L2BlockHeader => todo!(),
HintType::L2Transactions => todo!(),
HintType::L2StateNode => todo!(),
HintType::L2Code => todo!(),
HintType::L2Output => todo!(),
}

Ok(())
}
}
30 changes: 30 additions & 0 deletions bin/host/src/kv/mem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Contains a concrete implementation of the [KeyValueStore] trait that stores data in memory.

use alloy_primitives::B256;

use super::KeyValueStore;
use std::collections::HashMap;

/// A simple, synchronous key-value store that stores data in memory. This is useful for testing and
/// development purposes.
#[derive(Default, Clone, Debug, Eq, PartialEq)]
pub struct MemoryKeyValueStore {
store: HashMap<B256, Vec<u8>>,
}

impl MemoryKeyValueStore {
/// Create a new [MemoryKeyValueStore] with an empty store.
pub fn new() -> Self {
Self { store: HashMap::new() }
}
}

impl KeyValueStore for MemoryKeyValueStore {
fn get(&self, key: B256) -> Option<&Vec<u8>> {
self.store.get(&key)
}

fn set(&mut self, key: B256, value: Vec<u8>) {
self.store.insert(key, value);
}
}
Loading

0 comments on commit 9cd657a

Please sign in to comment.