Skip to content

Commit

Permalink
feat(host): Add L1 txs, L1 receipts, & L1 precompile hint routes
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed May 29, 2024
1 parent ea78dd2 commit fadd69b
Show file tree
Hide file tree
Showing 9 changed files with 446 additions and 218 deletions.
383 changes: 220 additions & 163 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions bin/host/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,20 @@ exclude.workspace = true
anyhow.workspace = true
tracing.workspace = true
alloy-primitives = { workspace = true, features = ["serde"] }
revm = { workspace = true, features = ["std", "c-kzg", "secp256k1", "portable", "blst"] }

# local
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" }

# 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" }
alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "e3f2f07" }
alloy-eips = { 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"] }
Expand Down
125 changes: 121 additions & 4 deletions bin/host/src/fetcher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
//! remote source.

use crate::{kv::KeyValueStore, util};
use alloy_primitives::{Bytes, B256};
use alloy_consensus::TxEnvelope;
use alloy_eips::eip2718::Encodable2718;
use alloy_primitives::{keccak256, Address, Bytes, B256};
use alloy_provider::{Provider, ReqwestProvider};
use alloy_rpc_types::{Block, BlockTransactions};
use anyhow::{anyhow, Result};
use kona_preimage::{PreimageKey, PreimageKeyType};
use std::sync::Arc;
Expand All @@ -13,6 +16,8 @@ use tracing::debug;
mod hint;
pub use hint::HintType;

mod precompiles;

/// The [Fetcher] struct is responsible for fetching preimages from a remote source.
pub struct Fetcher<KV>
where
Expand Down Expand Up @@ -103,10 +108,80 @@ where
raw_header.into(),
);
}
HintType::L1Transactions => todo!(),
HintType::L1Receipts => todo!(),
HintType::L1Transactions => {
// Validate the hint data length.
if hint_data.len() != 32 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the block from the L1 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
.l1_provider
.get_block_by_hash(hash, true)
.await
.map_err(|e| anyhow!("Failed to fetch block: {e}"))?
.ok_or(anyhow!("Block not found."))?;
self.store_transactions(transactions).await?;
}
HintType::L1Receipts => {
// Validate the hint data length.
if hint_data.len() != 32 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the receipts from the L1 chain provider and store the receipts within 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 raw_receipts: Vec<Bytes> = self
.l1_provider
.client()
.request("debug_getRawReceipts", [hash])
.await
.map_err(|e| anyhow!(e))?;
self.store_trie_nodes(raw_receipts.as_slice()).await?;
}
HintType::L1Blob => todo!(),
HintType::L1Precompile => todo!(),
HintType::L1Precompile => {
// Validate the hint data length.
if hint_data.len() < 20 {
anyhow::bail!("Invalid hint data length: {}", hint_data.len());
}

// Fetch the precompile address from the hint data.
let precompile_address = Address::from_slice(&hint_data.as_ref()[..20]);
let precompile_input = hint_data[20..].to_vec();
let input_hash = keccak256(hint_data.as_ref());

let result = match precompiles::execute(precompile_address, precompile_input) {
Ok(raw_res) => {
let mut res = Vec::with_capacity(1 + raw_res.len());
res.push(0x01); // success type byte
res.extend_from_slice(&raw_res);
res
}
Err(_) => {
// failure type byte
vec![0u8; 1]
}
};

// Acquire a lock on the key-value store and set the preimages.
let mut kv_lock = self.kv_store.write().await;
kv_lock.set(
PreimageKey::new(*input_hash, PreimageKeyType::Keccak256).into(),
hint_data.into(),
);
kv_lock
.set(PreimageKey::new(*input_hash, PreimageKeyType::Precompile).into(), result);
}
HintType::L2BlockHeader => todo!(),
HintType::L2Transactions => todo!(),
HintType::L2StateNode => todo!(),
Expand All @@ -116,4 +191,46 @@ where

Ok(())
}

/// Stores a list of [BlockTransactions] in the key-value store.
async fn store_transactions(&self, transactions: BlockTransactions) -> Result<()> {
match transactions {
BlockTransactions::Full(transactions) => {
let encoded_transactions = transactions
.into_iter()
.map(|tx| {
let envelope: TxEnvelope = tx.try_into().map_err(|e| {
anyhow!(
"Failed to convert RPC transaction into consensus envelope: {e}"
)
})?;

Ok::<_, anyhow::Error>(envelope.encoded_2718())
})
.collect::<Result<Vec<_>>>()?;

self.store_trie_nodes(encoded_transactions.as_slice()).await
}
_ => anyhow::bail!("Only BlockTransactions::Full are supported."),
}
}

/// Stores intermediate trie nodes in the key-value store. Assumes that all nodes passed are
/// raw, RLP encoded trie nodes.
async fn store_trie_nodes<T: AsRef<[u8]>>(&self, nodes: &[T]) -> Result<()> {
let mut hb = kona_mpt::ordered_trie_with_encoder(nodes, |node, buf| {
buf.put_slice(node.as_ref());
});
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);

kv_write_lock.set(key.into(), value.into());
}

Ok(())
}
}
42 changes: 42 additions & 0 deletions bin/host/src/fetcher/precompiles.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//! Accelerated precompile runner for the host program.

use alloy_primitives::{Address, Bytes};
use anyhow::{anyhow, Result};
use revm::{
precompile::{self, PrecompileWithAddress},
primitives::{Env, Precompile},
};

/// List of precompiles that are accelerated by the host program.
pub(crate) const ACCELERATED_PRECOMPILES: &[PrecompileWithAddress] = &[
precompile::secp256k1::ECRECOVER, // ecRecover
precompile::bn128::pair::ISTANBUL, // ecPairing
precompile::kzg_point_evaluation::POINT_EVALUATION, // KZG point evaluation
];

/// Executes an accelerated precompile on [revm].
pub(crate) fn execute<T: Into<Bytes>>(address: Address, input: T) -> Result<Vec<u8>> {
if let Some(precompile) =
ACCELERATED_PRECOMPILES.iter().find(|precompile| precompile.0 == address)
{
match precompile.1 {
Precompile::Standard(std_precompile) => {
// Standard precompile execution - no access to environment required.
let (_, result) = std_precompile(&input.into(), u64::MAX)
.map_err(|e| anyhow!("Failed precompile execution: {e}"))?;

Ok(result.to_vec())
}
Precompile::Env(env_precompile) => {
// Use default environment for KZG point evaluation.
let (_, result) = env_precompile(&input.into(), u64::MAX, &Env::default())
.map_err(|e| anyhow!("Failed precompile execution: {e}"))?;

Ok(result.to_vec())
}
_ => anyhow::bail!("Precompile not accelerated"),
}
} else {
anyhow::bail!("Precompile not accelerated");
}
}
3 changes: 3 additions & 0 deletions crates/mpt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@ pub use node::TrieNode;
mod list_walker;
pub use list_walker::OrderedListWalker;

mod util;
pub use util::ordered_trie_with_encoder;

#[cfg(test)]
mod test_util;
4 changes: 2 additions & 2 deletions crates/mpt/src/list_walker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ impl<PreimageFetcher> Iterator for OrderedListWalker<PreimageFetcher> {
#[cfg(test)]
mod test {
use super::*;
use crate::test_util::{
get_live_derivable_receipts_list, get_live_derivable_transactions_list,
use crate::{
ordered_trie_with_encoder,
test_util::{get_live_derivable_receipts_list, get_live_derivable_transactions_list},
};
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use alloy_consensus::{ReceiptEnvelope, TxEnvelope};
Expand Down
2 changes: 1 addition & 1 deletion crates/mpt/src/node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ fn rlp_list_element_length(buf: &mut &[u8]) -> alloy_rlp::Result<usize> {
#[cfg(test)]
mod test {
use super::*;
use crate::{test_util::ordered_trie_with_encoder, TrieNode};
use crate::{ordered_trie_with_encoder, TrieNode};
use alloc::{collections::BTreeMap, vec, vec::Vec};
use alloy_primitives::{b256, bytes, hex, keccak256, Bytes, B256};
use alloy_rlp::{Decodable, Encodable, EMPTY_STRING_CODE};
Expand Down
49 changes: 1 addition & 48 deletions crates/mpt/src/test_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,17 @@

extern crate std;

use crate::ordered_trie_with_encoder;
use alloc::{collections::BTreeMap, vec::Vec};
use alloy_consensus::{Receipt, ReceiptEnvelope, ReceiptWithBloom, TxEnvelope, TxType};
use alloy_primitives::{keccak256, Bytes, Log, B256};
use alloy_provider::{network::eip2718::Encodable2718, Provider, ProviderBuilder};
use alloy_rlp::{BufMut, Encodable};
use alloy_rpc_types::BlockTransactions;
use alloy_trie::{HashBuilder, Nibbles};
use anyhow::{anyhow, Result};
use reqwest::Url;

const RPC_URL: &str = "https://docs-demo.quiknode.pro/";

/// Compute a trie root of the collection of items with a custom encoder.
pub(crate) fn ordered_trie_with_encoder<T, F>(items: &[T], mut encode: F) -> HashBuilder
where
F: FnMut(&T, &mut dyn BufMut),
{
let mut index_buffer = Vec::new();
let mut value_buffer = Vec::new();
let items_len = items.len();

// Store preimages for all intermediates
let path_nibbles = (0..items_len)
.map(|i| {
let i = adjust_index_for_rlp(i, items_len);
index_buffer.clear();
i.encode(&mut index_buffer);
Nibbles::unpack(&index_buffer)
})
.collect::<Vec<_>>();

let mut hb = HashBuilder::default().with_proof_retainer(path_nibbles);
for i in 0..items_len {
let index = adjust_index_for_rlp(i, items_len);

index_buffer.clear();
index.encode(&mut index_buffer);

value_buffer.clear();
encode(&items[index], &mut value_buffer);

hb.add_leaf(Nibbles::unpack(&index_buffer), &value_buffer);
}

hb
}

/// Adjust the index of an item for rlp encoding.
pub(crate) const fn adjust_index_for_rlp(i: usize, len: usize) -> usize {
if i > 0x7f {
i
} else if i == 0x7f || i + 1 == len {
0
} else {
i + 1
}
}

/// Grabs a live merkleized receipts list within a block header.
pub(crate) async fn get_live_derivable_receipts_list(
) -> Result<(B256, BTreeMap<B256, Bytes>, Vec<ReceiptEnvelope>)> {
Expand Down
51 changes: 51 additions & 0 deletions crates/mpt/src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Utilities for `kona-mpt`

use alloc::vec::Vec;
use alloy_rlp::{BufMut, Encodable};
use alloy_trie::{HashBuilder, Nibbles};

/// Compute a trie root of the collection of items with a custom encoder.
pub fn ordered_trie_with_encoder<T, F>(items: &[T], mut encode: F) -> HashBuilder
where
F: FnMut(&T, &mut dyn BufMut),
{
let mut index_buffer = Vec::new();
let mut value_buffer = Vec::new();
let items_len = items.len();

// Store preimages for all intermediates
let path_nibbles = (0..items_len)
.map(|i| {
let i = adjust_index_for_rlp(i, items_len);
index_buffer.clear();
i.encode(&mut index_buffer);
Nibbles::unpack(&index_buffer)
})
.collect::<Vec<_>>();

let mut hb = HashBuilder::default().with_proof_retainer(path_nibbles);
for i in 0..items_len {
let index = adjust_index_for_rlp(i, items_len);

index_buffer.clear();
index.encode(&mut index_buffer);

value_buffer.clear();
encode(&items[index], &mut value_buffer);

hb.add_leaf(Nibbles::unpack(&index_buffer), &value_buffer);
}

hb
}

/// Adjust the index of an item for rlp encoding.
pub(crate) const fn adjust_index_for_rlp(i: usize, len: usize) -> usize {
if i > 0x7f {
i
} else if i == 0x7f || i + 1 == len {
0
} else {
i + 1
}
}

0 comments on commit fadd69b

Please sign in to comment.