Skip to content

Commit

Permalink
feat(client): Oracle-backed derive traits
Browse files Browse the repository at this point in the history
Implements the oracle-backed `kona-derive` traits in the client program.
  • Loading branch information
clabby committed Jun 15, 2024
1 parent b355654 commit 4bb663d
Show file tree
Hide file tree
Showing 15 changed files with 433 additions and 135 deletions.
56 changes: 46 additions & 10 deletions bin/host/src/fetcher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ use alloy_provider::{Provider, ReqwestProvider};
use alloy_rlp::Decodable;
use alloy_rpc_types::{Block, BlockTransactions};
use anyhow::{anyhow, Result};
use kona_client::HintType;
use kona_preimage::{PreimageKey, PreimageKeyType};
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::debug;

mod hint;
pub use hint::HintType;

mod precompiles;

/// The [Fetcher] struct is responsible for fetching preimages from a remote source.
Expand Down Expand Up @@ -191,7 +189,7 @@ where
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the raw header from the L1 chain provider.
// Fetch the raw header from the L2 chain provider.
let hash: B256 = hint_data
.as_ref()
.try_into()
Expand All @@ -210,7 +208,43 @@ where
raw_header.into(),
);
}
HintType::L2Transactions => todo!(),
HintType::L2Transactions => {
// Validate the hint data length.
if hint_data.len() != 32 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the block from the L2 chain provider and store the transactions within its
// body in the key-value store.
let hash: B256 = hint_data
.as_ref()
.try_into()
.map_err(|e| anyhow!("Failed to convert bytes to B256: {e}"))?;
let Block { transactions, .. } = self
.l2_provider
.get_block_by_hash(hash, false)
.await
.map_err(|e| anyhow!("Failed to fetch block: {e}"))?
.ok_or(anyhow!("Block not found."))?;

match transactions {
BlockTransactions::Hashes(transactions) => {
let mut encoded_transactions = Vec::with_capacity(transactions.len());
for tx_hash in transactions {
let tx = self
.l2_provider
.client()
.request::<&[B256; 1], Bytes>("debug_getRawTransaction", &[tx_hash])
.await
.map_err(|e| anyhow!("Error fetching transaction: {e}"))?;
encoded_transactions.push(tx);
}

self.store_trie_nodes(encoded_transactions.as_slice()).await?;
}
_ => anyhow::bail!("Only BlockTransactions::Hashes are supported."),
};
}
HintType::L2Code => {
// geth hashdb scheme code hash key prefix
const CODE_PREFIX: u8 = b'c';
Expand Down Expand Up @@ -277,11 +311,11 @@ where
.await
.map_err(|e| anyhow!("Failed to fetch account proof: {e}"))?;

let mut raw_output = [0u8; 97];
raw_output[0] = OUTPUT_ROOT_VERSION;
raw_output[1..33].copy_from_slice(header.state_root.as_ref());
raw_output[33..65].copy_from_slice(l2_to_l1_message_passer.storage_hash.as_ref());
raw_output[65..97].copy_from_slice(self.l2_head.as_ref());
let mut raw_output = [0u8; 128];
raw_output[31] = OUTPUT_ROOT_VERSION;
raw_output[32..64].copy_from_slice(header.state_root.as_ref());
raw_output[64..96].copy_from_slice(l2_to_l1_message_passer.storage_hash.as_ref());
raw_output[96..128].copy_from_slice(self.l2_head.as_ref());
let output_root = keccak256(raw_output);

let mut kv_write_lock = self.kv_store.write().await;
Expand Down Expand Up @@ -405,9 +439,11 @@ where
let mut hb = kona_mpt::ordered_trie_with_encoder(nodes, |node, buf| {
buf.put_slice(node.as_ref());
});
hb.root();
let intermediates = hb.take_proofs();

let mut kv_write_lock = self.kv_store.write().await;

for (_, value) in intermediates.into_iter() {
let value_hash = keccak256(value.as_ref());
let key = PreimageKey::new(*value_hash, PreimageKeyType::Keccak256);
Expand Down
3 changes: 2 additions & 1 deletion bin/host/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
//! Contains utility functions and helpers for the host program.
use crate::{fetcher::HintType, types::NativePipeFiles};
use crate::types::NativePipeFiles;
use alloy_primitives::{hex, Bytes};
use alloy_provider::ReqwestProvider;
use alloy_rpc_client::RpcClient;
use alloy_transport_http::Http;
use anyhow::{anyhow, Result};
use kona_client::HintType;
use kona_common::FileDescriptor;
use kona_preimage::PipeHandle;
use reqwest::Client;
Expand Down
16 changes: 5 additions & 11 deletions bin/programs/client/src/comms/caching_oracle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,27 @@ use spin::Mutex;
///
/// [OracleReader]: kona_preimage::OracleReader
#[derive(Debug, Clone)]
pub struct CachingOracle<const N: usize> {
pub struct CachingOracle {
/// The spin-locked cache that stores the responses from the oracle.
cache: Arc<Mutex<LruCache<PreimageKey, Vec<u8>>>>,
}

impl<const N: usize> CachingOracle<N> {
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 {
pub fn new(cache_size: usize) -> Self {
Self {
cache: Arc::new(Mutex::new(LruCache::new(
NonZeroUsize::new(N).expect("N must be greater than 0"),
NonZeroUsize::new(cache_size).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> {
impl PreimageOracleClient for CachingOracle {
async fn get(&self, key: PreimageKey) -> Result<Vec<u8>> {
let mut cache_lock = self.cache.lock();
if let Some(value) = cache_lock.get(&key) {
Expand Down
15 changes: 13 additions & 2 deletions bin/host/src/fetcher/hint.rs → bin/programs/client/src/hint.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! This module contains the [HintType] enum.
use std::fmt::Display;
use core::fmt::Display;

use alloc::{string::String, vec::Vec};
use alloy_primitives::hex;

/// The [HintType] enum is used to specify the type of hint that was received.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
Expand Down Expand Up @@ -32,6 +35,14 @@ pub enum HintType {
L2AccountStorageProof,
}

impl HintType {
/// Encodes the hint type as a string.
pub fn encode_with(&self, data: &[&[u8]]) -> String {
let concatenated = data.iter().map(hex::encode).collect::<Vec<_>>().join(" ");
alloc::format!("{} {}", self, concatenated)
}
}

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

Expand Down Expand Up @@ -74,7 +85,7 @@ impl From<HintType> for &str {
}

impl Display for HintType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let s: &str = (*self).into();
write!(f, "{}", s)
}
Expand Down
141 changes: 141 additions & 0 deletions bin/programs/client/src/l1/chain_provider.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
//! Contains the concrete implementation of the [ChainProvider] trait for the client program.
use crate::{BootInfo, CachingOracle, HintType, HINT_WRITER};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use alloy_consensus::{Header, Receipt, ReceiptEnvelope, TxEnvelope};
use alloy_eips::eip2718::Decodable2718;
use alloy_primitives::{Bytes, B256};
use alloy_rlp::Decodable;
use anyhow::{anyhow, Result};
use async_trait::async_trait;
use kona_derive::traits::ChainProvider;
use kona_mpt::{OrderedListWalker, TrieDBFetcher};
use kona_preimage::{HintWriterClient, PreimageKey, PreimageKeyType, PreimageOracleClient};
use kona_primitives::BlockInfo;

/// The oracle-backed L1 chain provider for the client program.
#[derive(Debug)]
pub struct OracleL1ChainProvider {
/// The boot information
boot_info: Arc<BootInfo>,
/// The preimage oracle client.
oracle: Arc<CachingOracle>,
}

impl OracleL1ChainProvider {
/// Creates a new [OracleL1ChainProvider] with the given boot information and oracle client.
pub fn new(boot_info: Arc<BootInfo>, oracle: Arc<CachingOracle>) -> Self {
Self { boot_info, oracle }
}
}

#[async_trait]
impl ChainProvider for OracleL1ChainProvider {
async fn header_by_hash(&mut self, hash: B256) -> Result<Header> {
// Send a hint for the block header.
HINT_WRITER.write(&HintType::L1BlockHeader.encode_with(&[hash.as_ref()])).await?;

// Fetch the header RLP from the oracle.
let header_rlp =
self.oracle.get(PreimageKey::new(*hash, PreimageKeyType::Keccak256)).await?;

// Decode the header RLP into a Header.
Header::decode(&mut header_rlp.as_slice())
.map_err(|e| anyhow!("Failed to decode header RLP: {e}"))
}

async fn block_info_by_number(&mut self, block_number: u64) -> Result<BlockInfo> {
// Fetch the starting block header.
let mut header = self.header_by_hash(self.boot_info.l1_head).await?;

// Check if the block number is in range. If not, we can fail early.
if block_number > header.number {
anyhow::bail!("Block number past L1 head.");
}

// Walk back the block headers to the desired block number.
while header.number > block_number {
header = self.header_by_hash(header.parent_hash).await?;
}

Ok(BlockInfo {
hash: header.hash_slow(),
number: header.number,
parent_hash: header.parent_hash,
timestamp: header.timestamp,
})
}

async fn receipts_by_hash(&mut self, hash: B256) -> Result<Vec<Receipt>> {
// Fetch the block header to find the receipts root.
let header = self.header_by_hash(hash).await?;

// Send a hint for the block's receipts, and walk through the receipts trie in the header to
// verify them.
HINT_WRITER.write(&HintType::L1Receipts.encode_with(&[hash.as_ref()])).await?;
let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self)?;

// Decode the receipts within the transactions trie.
let receipts = trie_walker
.into_iter()
.map(|(_, rlp)| {
let envelope = ReceiptEnvelope::decode_2718(&mut rlp.as_ref())
.map_err(|e| anyhow!("Failed to decode ReceiptEnvelope RLP: {e}"))?;
Ok(envelope.as_receipt().expect("Infalliable").clone())
})
.collect::<Result<Vec<_>>>()?;

Ok(receipts)
}

async fn block_info_and_transactions_by_hash(
&mut self,
hash: B256,
) -> Result<(BlockInfo, Vec<TxEnvelope>)> {
// Fetch the block header to construct the block info.
let header = self.header_by_hash(hash).await?;
let block_info = BlockInfo {
hash,
number: header.number,
parent_hash: header.parent_hash,
timestamp: header.timestamp,
};

// Send a hint for the block's transactions, and walk through the transactions trie in the
// header to verify them.
HINT_WRITER.write(&HintType::L1Transactions.encode_with(&[hash.as_ref()])).await?;
let trie_walker = OrderedListWalker::try_new_hydrated(header.transactions_root, self)?;

// Decode the transactions within the transactions trie.
let transactions = trie_walker
.into_iter()
.map(|(_, rlp)| {
TxEnvelope::decode_2718(&mut rlp.as_ref())
.map_err(|e| anyhow!("Failed to decode TxEnvelope RLP: {e}"))
})
.collect::<Result<Vec<_>>>()?;

Ok((block_info, transactions))
}
}

impl TrieDBFetcher for OracleL1ChainProvider {
fn trie_node_preimage(&self, key: B256) -> Result<Bytes> {
// On L1, trie node preimages are stored as keccak preimage types in the oracle. We assume
// that a hint for these preimages has already been sent, prior to this call.
kona_common::block_on(async move {
self.oracle
.get(PreimageKey::new(*key, PreimageKeyType::Keccak256))
.await
.map(Into::into)
})
}

fn bytecode_by_hash(&self, _: B256) -> Result<Bytes> {
unimplemented!("TrieDBFetcher::bytecode_by_hash unimplemented for OracleL1ChainProvider")
}

fn header_by_hash(&self, _: B256) -> Result<Header> {
unimplemented!("TrieDBFetcher::header_by_hash unimplemented for OracleL1ChainProvider")
}
}
4 changes: 4 additions & 0 deletions bin/programs/client/src/l1/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
//! Contains the L1 constructs of the client program.
mod chain_provider;
pub use chain_provider::OracleL1ChainProvider;
Loading

0 comments on commit 4bb663d

Please sign in to comment.