Skip to content

Commit

Permalink
fix(client): Trace extension support (#778)
Browse files Browse the repository at this point in the history
* check

* check

* lint

* don't use `derive_more::Error`
  • Loading branch information
clabby authored Nov 4, 2024
1 parent 0a672bd commit 7ebfe14
Show file tree
Hide file tree
Showing 22 changed files with 404 additions and 182 deletions.
2 changes: 1 addition & 1 deletion .monorepo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
gk/holocene-derivation-action-testing-stacked
afe849ea0b0f5ee5889e0fe440ad7827d7ceef5f
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bin/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ anyhow.workspace = true
tracing.workspace = true
serde_json.workspace = true
async-trait.workspace = true
derive_more = { workspace = true, features = ["full"] }

# `tracing-subscriber` feature dependencies
tracing-subscriber = { workspace = true, optional = true, features = ["fmt"] }
Expand Down
45 changes: 45 additions & 0 deletions bin/client/src/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//! Error types for the client program.
use alloc::string::ToString;
use derive_more::derive::Display;
use kona_derive::errors::{PipelineError, PipelineErrorKind};
use kona_mpt::OrderedListWalkerError;
use kona_preimage::errors::PreimageOracleError;
use op_alloy_protocol::{FromBlockError, OpBlockConversionError};

/// Error from an oracle-backed provider.
#[derive(Display, Debug)]
pub enum OracleProviderError {
/// Requested block number is past the chain head.
#[display("Block number ({_0}) past chain head ({_1})")]
BlockNumberPastHead(u64, u64),
/// Preimage oracle error.
#[display("Preimage oracle error: {_0}")]
Preimage(PreimageOracleError),
/// List walker error.
#[display("Trie walker error: {_0}")]
TrieWalker(OrderedListWalkerError),
/// BlockInfo error.
#[display("From block error: {_0}")]
BlockInfo(FromBlockError),
/// Op Block conversion error.
#[display("Op block conversion error: {_0}")]
OpBlockConversion(OpBlockConversionError),
/// Error decoding or encoding RLP.
#[display("RLP error: {_0}")]
Rlp(alloy_rlp::Error),
/// Slice conversion error.
#[display("Slice conversion error: {_0}")]
SliceConversion(core::array::TryFromSliceError),
}

impl core::error::Error for OracleProviderError {}

impl From<OracleProviderError> for PipelineErrorKind {
fn from(val: OracleProviderError) -> Self {
match val {
OracleProviderError::BlockNumberPastHead(_, _) => PipelineError::EndOfSource.crit(),
_ => PipelineError::Provider(val.to_string()).crit(),
}
}
}
34 changes: 25 additions & 9 deletions bin/client/src/kona.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,12 @@ use kona_client::{
l2::OracleL2ChainProvider,
BootInfo, CachingOracle,
};
use kona_common::io;
use kona_common_proc::client_entry;

pub(crate) mod fault;
use fault::{fpvm_handle_register, HINT_WRITER, ORACLE_READER};
use tracing::{error, info, warn};

/// The size of the LRU cache in the oracle.
const ORACLE_LRU_SIZE: usize = 1024;
Expand All @@ -43,6 +45,17 @@ fn main() -> Result<()> {
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 //
////////////////////////////////////////////////////////////////
Expand All @@ -55,37 +68,40 @@ fn main() -> Result<()> {
// Run the derivation pipeline until we are able to produce the output root of the claimed
// L2 block.
let (number, output_root) = driver
.produce_output(&boot.rollup_config, &l2_provider, &l2_provider, fpvm_handle_register)
.advance_to_target(
&boot.rollup_config,
&l2_provider,
&l2_provider,
fpvm_handle_register,
)
.await?;

////////////////////////////////////////////////////////////////
// EPILOGUE //
////////////////////////////////////////////////////////////////

if number != boot.claimed_l2_block_number || output_root != boot.claimed_l2_output_root {
tracing::error!(
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
);
kona_common::io::print(&alloc::format!(
io::print(&alloc::format!(
"Failed to validate L2 block #{} with output root {}\n",
number,
output_root
));

kona_common::io::exit(1);
io::exit(1);
}

tracing::info!(
info!(
target: "client",
"Successfully validated L2 block #{number} with output root {output_root}",
number = number,
output_root = output_root
);

kona_common::io::print(&alloc::format!(
io::print(&alloc::format!(
"Successfully validated L2 block #{} with output root {}\n",
number,
output_root
Expand Down
71 changes: 43 additions & 28 deletions bin/client/src/l1/chain_provider.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
//! Contains the concrete implementation of the [ChainProvider] trait for the client program.
use crate::{BootInfo, HintType};
use crate::{errors::OracleProviderError, BootInfo, HintType};
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 anyhow::Result;
use async_trait::async_trait;
use kona_derive::traits::ChainProvider;
use kona_mpt::{OrderedListWalker, TrieProvider};
Expand All @@ -31,28 +31,33 @@ impl<T: CommsClient> OracleL1ChainProvider<T> {

#[async_trait]
impl<T: CommsClient + Sync + Send> ChainProvider for OracleL1ChainProvider<T> {
type Error = anyhow::Error;
type Error = OracleProviderError;

async fn header_by_hash(&mut self, hash: B256) -> Result<Header> {
async fn header_by_hash(&mut self, hash: B256) -> Result<Header, Self::Error> {
// Send a hint for the block header.
self.oracle.write(&HintType::L1BlockHeader.encode_with(&[hash.as_ref()])).await?;
self.oracle
.write(&HintType::L1BlockHeader.encode_with(&[hash.as_ref()]))
.await
.map_err(OracleProviderError::Preimage)?;

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

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

async fn block_info_by_number(&mut self, block_number: u64) -> Result<BlockInfo> {
async fn block_info_by_number(&mut self, block_number: u64) -> Result<BlockInfo, Self::Error> {
// 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.");
return Err(OracleProviderError::BlockNumberPastHead(block_number, header.number));
}

// Walk back the block headers to the desired block number.
Expand All @@ -68,32 +73,36 @@ impl<T: CommsClient + Sync + Send> ChainProvider for OracleL1ChainProvider<T> {
})
}

async fn receipts_by_hash(&mut self, hash: B256) -> Result<Vec<Receipt>> {
async fn receipts_by_hash(&mut self, hash: B256) -> Result<Vec<Receipt>, Self::Error> {
// 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.
self.oracle.write(&HintType::L1Receipts.encode_with(&[hash.as_ref()])).await?;
let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self)?;
self.oracle
.write(&HintType::L1Receipts.encode_with(&[hash.as_ref()]))
.await
.map_err(OracleProviderError::Preimage)?;
let trie_walker = OrderedListWalker::try_new_hydrated(header.receipts_root, self)
.map_err(OracleProviderError::TrieWalker)?;

// 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}"))?;
let envelope = ReceiptEnvelope::decode_2718(&mut rlp.as_ref())?;
Ok(envelope.as_receipt().expect("Infallible").clone())
})
.collect::<Result<Vec<_>>>()?;
.collect::<Result<Vec<_>, _>>()
.map_err(OracleProviderError::Rlp)?;

Ok(receipts)
}

async fn block_info_and_transactions_by_hash(
&mut self,
hash: B256,
) -> Result<(BlockInfo, Vec<TxEnvelope>)> {
) -> Result<(BlockInfo, Vec<TxEnvelope>), Self::Error> {
// Fetch the block header to construct the block info.
let header = self.header_by_hash(hash).await?;
let block_info = BlockInfo {
Expand All @@ -105,42 +114,48 @@ impl<T: CommsClient + Sync + Send> ChainProvider for OracleL1ChainProvider<T> {

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

// 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}"))
// note: not short-handed for error type coersion w/ `?`.
let rlp = TxEnvelope::decode_2718(&mut rlp.as_ref())?;
Ok(rlp)
})
.collect::<Result<Vec<_>>>()?;
.collect::<Result<Vec<_>, _>>()
.map_err(OracleProviderError::Rlp)?;

Ok((block_info, transactions))
}
}

impl<T: CommsClient> TrieProvider for OracleL1ChainProvider<T> {
type Error = anyhow::Error;
type Error = OracleProviderError;

fn trie_node_preimage(&self, key: B256) -> Result<Bytes> {
fn trie_node_preimage(&self, key: B256) -> Result<Bytes, Self::Error> {
// 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)
.map_err(Into::into)
.map_err(OracleProviderError::Preimage)
})
}

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

fn header_by_hash(&self, _: B256) -> Result<Header> {
fn header_by_hash(&self, _: B256) -> Result<Header, Self::Error> {
unimplemented!("TrieProvider::header_by_hash unimplemented for OracleL1ChainProvider")
}
}
Loading

0 comments on commit 7ebfe14

Please sign in to comment.