diff --git a/.gitignore b/.gitignore index b143b9c..5d1f033 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ target/ -data/ \ No newline at end of file +data/ diff --git a/Cargo.lock b/Cargo.lock index 016edbe..171dfe4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -1733,26 +1733,18 @@ dependencies = [ name = "hokulea-host" version = "0.1.0" dependencies = [ - "alloy-consensus", - "alloy-eips", "alloy-primitives", "alloy-provider", "alloy-rlp", - "alloy-rpc-types", "anyhow", "async-trait", "clap", "hokulea-client", "hokulea-proof", "kona-host", - "kona-mpt", "kona-preimage", - "op-alloy-protocol", - "op-alloy-rpc-types-engine", "proptest", "reqwest", - "revm", - "serde_json", "tokio", "tracing", ] diff --git a/README.md b/README.md index 631afab..32b65b7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ # Hokulea ![](./hokulea.jpeg) + +### Running against devnet + +First start the devnet: +```bash +git clone https://github.com/ethereum-optimism/optimism.git +cd optimism +DEVNET_ALTDA=true GENERIC_ALTDA=true make devnet-up +``` +Then run hokulea: +```bash +cd bin/client +just run-client-native-against-devnet +``` \ No newline at end of file diff --git a/bin/client/justfile b/bin/client/justfile index 7bb20cd..9ac832d 100644 --- a/bin/client/justfile +++ b/bin/client/justfile @@ -73,10 +73,16 @@ run-client-native-against-devnet verbosity='' block_number='' rollup_config_path if [ -z "{{block_number}}" ]; then BLOCK_NUMBER=$(cast block finalized --json --rpc-url $L2_RPC | jq -r .number | cast 2d) + if [ $BLOCK_NUMBER -eq 0 ]; then + echo "No finalized blocks found on L2 chain. If devnet was just started, wait a bit and try again..." + echo "You can run the following command to check the latest finalized block." + echo "cast block finalized --json --rpc-url $L2_RPC | jq -r .number | cast 2d" + exit 1 + fi else BLOCK_NUMBER={{block_number}} fi - + set -x just run-client-native $BLOCK_NUMBER \ $L1_RPC $L1_BEACON_RPC $L2_RPC $ROLLUP_NODE_RPC \ $ROLLUP_CONFIG_PATH {{verbosity}} diff --git a/bin/host/Cargo.toml b/bin/host/Cargo.toml index 5e6b8b7..e286988 100644 --- a/bin/host/Cargo.toml +++ b/bin/host/Cargo.toml @@ -5,39 +5,22 @@ edition = "2021" [dependencies] # Workspace -kona-mpt.workspace = true -kona-preimage = { workspace = true, features = ["std"] } -kona-host.workspace = true - hokulea-proof.workspace = true hokulea-client.workspace = true +# Kona +kona-preimage = { workspace = true, features = ["std"] } +kona-host.workspace = true + # Alloy alloy-rlp.workspace = true -alloy-eips = { workspace = true, features = ["kzg"] } alloy-provider = { workspace = true, features = ["reqwest"] } -alloy-consensus.workspace = true -alloy-rpc-types = { workspace = true, features = ["eth", "debug"] } alloy-primitives = { workspace = true, features = ["serde"] } -# Op Alloy -op-alloy-protocol = { workspace = true, features = ["std", "serde"] } -op-alloy-rpc-types-engine = { workspace = true, features = ["serde"] } - -# Revm -revm = { workspace = true, features = [ - "std", - "c-kzg", - "secp256k1", - "portable", - "blst", -] } - # General anyhow.workspace = true tracing.workspace = true reqwest.workspace = true -serde_json.workspace = true async-trait.workspace = true tokio = { workspace = true, features = ["full"] } clap = { workspace = true, features = ["derive", "env"] } diff --git a/bin/host/src/eigenda_fetcher/mod.rs b/bin/host/src/eigenda_fetcher/mod.rs new file mode 100644 index 0000000..643785d --- /dev/null +++ b/bin/host/src/eigenda_fetcher/mod.rs @@ -0,0 +1,169 @@ +//! This module contains the [Fetcher] struct, which is responsible for fetching preimages from a +//! remote source. + +use crate::eigenda_blobs::OnlineEigenDABlobProvider; +use alloy_primitives::{keccak256, B256}; +use alloy_provider::ReqwestProvider; +use anyhow::{anyhow, Result}; +use core::panic; +use hokulea_proof::hint::{ExtendedHint, ExtendedHintType}; +use kona_host::{blobs::OnlineBlobProvider, fetcher::Fetcher, kv::KeyValueStore}; +use kona_preimage::{PreimageKey, PreimageKeyType}; +use std::sync::Arc; +use tokio::sync::RwLock; +use tracing::{error, info, trace, warn}; + +/// The [FetcherWithEigenDASupport] struct wraps and extends kona's [Fetcher] struct with the ability +/// to fetch preimages from EigenDA. +/// TODO: Kona is planning to change the fetcher interface to allow registering extra hints +/// without needing a new type. We will probably want to switch when possible. +/// See +#[derive(Debug)] +pub struct FetcherWithEigenDASupport +where + KV: KeyValueStore + ?Sized, +{ + /// Kona's Fetcher + fetcher: Fetcher, + /// Key-value store for eigenda preimages. + kv_store: Arc>, + /// The eigenda provider + eigenda_blob_provider: OnlineEigenDABlobProvider, + /// The last hint that was received. [None] if no hint has been received yet. + last_eigenda_hint: Option, +} + +impl FetcherWithEigenDASupport +where + KV: KeyValueStore + ?Sized, +{ + /// Create a new [Fetcher] with the given [KeyValueStore]. + pub const fn new( + fetcher: Fetcher, + kv_store: Arc>, + eigenda_blob_provider: OnlineEigenDABlobProvider, + ) -> Self { + Self { + fetcher, + kv_store, + eigenda_blob_provider, + last_eigenda_hint: None, + } + } + + pub fn new_from_parts( + kv_store: Arc>, + l1_provider: ReqwestProvider, + blob_provider: OnlineBlobProvider, + eigenda_blob_provider: OnlineEigenDABlobProvider, + l2_provider: ReqwestProvider, + l2_head: B256, + ) -> Self { + let fetcher = Fetcher::new( + Arc::clone(&kv_store), + l1_provider, + blob_provider, + l2_provider, + l2_head, + ); + Self { + fetcher, + kv_store, + eigenda_blob_provider, + last_eigenda_hint: None, + } + } + + /// Set the last hint to be received. + pub fn hint(&mut self, hint: &str) -> Result<()> { + trace!(target: "fetcher_with_eigenda_support", "Received hint: {hint}"); + let (hint_type, _) = ExtendedHint::parse(hint)?.split(); + // We route the hint to the right fetcher based on the hint type. + match hint_type { + ExtendedHintType::EigenDACommitment => { + self.last_eigenda_hint = Some(hint.to_string()); + } + _ => { + self.fetcher.hint(hint); + // get_preimage will fetch from the underlying fetcher when last_eigenda_hint = None + self.last_eigenda_hint = None; + } + } + Ok(()) + } + + /// Fetch the preimage for the given key. The requested is routed to the appropriate fetcher + /// based on the last hint that was received (see hint() above). + /// FetcherWithEigenDASupport -> get_preimage_altda -> prefetch that only understands altda hints + /// \-> Fetcher -> get_preimage -> prefetch that understands all other hints + pub async fn get_preimage(&self, key: B256) -> Result> { + match self.last_eigenda_hint.as_ref() { + Some(hint) => self.get_preimage_eigenda(key, hint).await, + None => self.fetcher.get_preimage(key).await, + } + } + + async fn get_preimage_eigenda(&self, key: B256, hint: &str) -> Result> { + trace!(target: "fetcher_with_eigenda_support", "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); + + // Drop the read lock before beginning the retry loop. + drop(kv_lock); + + // Use a loop to keep retrying the prefetch as long as the key is not found + while preimage.is_none() { + if let Err(e) = self.prefetch(hint).await { + error!(target: "fetcher_with_eigenda_support", "Failed to prefetch hint: {e}"); + warn!(target: "fetcher_with_eigenda_support", "Retrying hint fetch: {hint}"); + continue; + } + + let kv_lock = self.kv_store.read().await; + preimage = kv_lock.get(key); + } + + 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<()> { + trace!(target: "fetcher_with_eigenda_support", "prefetch: {hint}"); + let hint = ExtendedHint::parse(hint)?; + let (hint_type, hint_data) = hint.split(); + trace!(target: "fetcher_with_eigenda_support", "Fetching hint: {hint_type} {hint_data}"); + + if hint_type == ExtendedHintType::EigenDACommitment { + let cert = hint_data; + info!(target: "fetcher_with_eigenda_support", "Fetching AltDACommitment cert: {:?}", cert); + // Fetch the blob sidecar from the blob provider. + let eigenda_blob = self + .eigenda_blob_provider + .fetch_eigenda_blob(&cert) + .await + .map_err(|e| anyhow!("Failed to fetch eigenda blob: {e}"))?; + + info!(target: "fetcher_with_eigenda_support", "eigenda_blob len {}", eigenda_blob.len()); + // Acquire a lock on the key-value store and set the preimages. + let mut kv_write_lock = self.kv_store.write().await; + + // Set the preimage for the blob commitment. + kv_write_lock.set( + PreimageKey::new(*keccak256(cert), PreimageKeyType::GlobalGeneric).into(), + eigenda_blob.to_vec(), + )?; + } else { + panic!("Invalid hint type: {hint_type}. FetcherWithEigenDASupport.prefetch only supports EigenDACommitment hints."); + } + // We don't match against the other enum case because fetcher.prefetch is private, + // so we can't make the below code compile. + // TODO: do we want to change the Fetcher api to make this possible? + // ExtendedHintType::Original(hint_type) => { + // self.fetcher.prefetch(hint_type, hint_data).await?; + // } + + Ok(()) + } +} diff --git a/bin/host/src/fetcher/mod.rs b/bin/host/src/fetcher/mod.rs deleted file mode 100644 index 24a908d..0000000 --- a/bin/host/src/fetcher/mod.rs +++ /dev/null @@ -1,644 +0,0 @@ -//! This module contains the [Fetcher] struct, which is responsible for fetching preimages from a -//! remote source. - -use crate::eigenda_blobs::OnlineEigenDABlobProvider; -use alloy_consensus::{Header, TxEnvelope, EMPTY_ROOT_HASH}; -use alloy_eips::{ - eip2718::Encodable2718, - eip4844::{IndexedBlobHash, FIELD_ELEMENTS_PER_BLOB}, - BlockId, -}; -use alloy_primitives::{address, keccak256, map::HashMap, Address, Bytes, B256}; -use alloy_provider::{Provider, ReqwestProvider}; -use alloy_rlp::{Decodable, EMPTY_STRING_CODE}; -use alloy_rpc_types::{ - debug::ExecutionWitness, Block, BlockNumberOrTag, BlockTransactions, BlockTransactionsKind, - Transaction, -}; -use anyhow::{anyhow, Result}; -use hokulea_proof::hint::{Hint, HintType}; -use kona_host::{blobs::OnlineBlobProvider, kv::KeyValueStore}; -use kona_preimage::{PreimageKey, PreimageKeyType}; -use op_alloy_protocol::BlockInfo; -use op_alloy_rpc_types_engine::OpPayloadAttributes; -use std::sync::Arc; -use tokio::sync::RwLock; -use tracing::{error, info, trace, warn}; - -mod precompiles; - -/// The [Fetcher] struct is responsible for fetching preimages from a remote source. -#[derive(Debug)] -pub struct Fetcher -where - KV: KeyValueStore + ?Sized, -{ - /// Key-value store for preimages. - kv_store: Arc>, - /// L1 chain provider. - l1_provider: ReqwestProvider, - /// The blob provider - blob_provider: OnlineBlobProvider, - /// The eigenda provider - eigenda_blob_provider: OnlineEigenDABlobProvider, - /// L2 chain provider. - l2_provider: ReqwestProvider, - /// L2 head - l2_head: B256, - /// The last hint that was received. [None] if no hint has been received yet. - last_hint: Option, -} - -impl Fetcher -where - KV: KeyValueStore + ?Sized, -{ - /// Create a new [Fetcher] with the given [KeyValueStore]. - pub const fn new( - kv_store: Arc>, - l1_provider: ReqwestProvider, - blob_provider: OnlineBlobProvider, - eigenda_blob_provider: OnlineEigenDABlobProvider, - l2_provider: ReqwestProvider, - l2_head: B256, - ) -> Self { - Self { - kv_store, - l1_provider, - blob_provider, - eigenda_blob_provider, - l2_provider, - l2_head, - last_hint: None, - } - } - - /// Set the last hint to be received. - pub fn hint(&mut self, hint: &str) { - trace!(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> { - trace!(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); - - // Drop the read lock before beginning the retry 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"); - - if let Err(e) = self.prefetch(hint).await { - error!(target: "fetcher", "Failed to prefetch hint: {e}"); - warn!(target: "fetcher", "Retrying hint fetch: {hint}"); - continue; - } - - let kv_lock = self.kv_store.read().await; - preimage = kv_lock.get(key); - } - - 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<()> { - trace!(target: "fetcher", "prefetch: {hint}"); - let hint = Hint::parse(hint)?; - let (hint_type, hint_data) = hint.split(); - trace!(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 => { - // 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, BlockTransactionsKind::Full) - .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 = 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 => { - if hint_data.len() != 48 { - anyhow::bail!("Invalid hint data length: {}", hint_data.len()); - } - - let hash_data_bytes: [u8; 32] = hint_data[0..32] - .try_into() - .map_err(|e| anyhow!("Failed to convert bytes to B256: {e}"))?; - let index_data_bytes: [u8; 8] = hint_data[32..40] - .try_into() - .map_err(|e| anyhow!("Failed to convert bytes to u64: {e}"))?; - let timestamp_data_bytes: [u8; 8] = hint_data[40..48] - .try_into() - .map_err(|e| anyhow!("Failed to convert bytes to u64: {e}"))?; - - let hash: B256 = hash_data_bytes.into(); - let index = u64::from_be_bytes(index_data_bytes); - let timestamp = u64::from_be_bytes(timestamp_data_bytes); - - let partial_block_ref = BlockInfo { - timestamp, - ..Default::default() - }; - let indexed_hash = IndexedBlobHash { index, hash }; - - // Fetch the blob sidecar from the blob provider. - let mut sidecars = self - .blob_provider - .fetch_filtered_sidecars(&partial_block_ref, &[indexed_hash]) - .await - .map_err(|e| anyhow!("Failed to fetch blob sidecars: {e}"))?; - if sidecars.len() != 1 { - anyhow::bail!("Expected 1 sidecar, got {}", sidecars.len()); - } - let sidecar = sidecars.remove(0); - - // Acquire a lock on the key-value store and set the preimages. - let mut kv_write_lock = self.kv_store.write().await; - - // Set the preimage for the blob commitment. - kv_write_lock.set( - PreimageKey::new(*hash, PreimageKeyType::Sha256).into(), - sidecar.kzg_commitment.to_vec(), - )?; - - // Write all the field elements to the key-value store. There should be 4096. - // The preimage oracle key for each field element is the keccak256 hash of - // `abi.encodePacked(sidecar.KZGCommitment, uint256(i))` - let mut blob_key = [0u8; 80]; - blob_key[..48].copy_from_slice(sidecar.kzg_commitment.as_ref()); - for i in 0..FIELD_ELEMENTS_PER_BLOB { - blob_key[72..].copy_from_slice(i.to_be_bytes().as_ref()); - let blob_key_hash = keccak256(blob_key.as_ref()); - - kv_write_lock.set( - PreimageKey::new(*blob_key_hash, PreimageKeyType::Keccak256).into(), - blob_key.into(), - )?; - kv_write_lock.set( - PreimageKey::new(*blob_key_hash, PreimageKeyType::Blob).into(), - sidecar.blob[(i as usize) << 5..(i as usize + 1) << 5].to_vec(), - )?; - } - - // Write the KZG Proof as the 4096th element. - blob_key[72..].copy_from_slice((FIELD_ELEMENTS_PER_BLOB).to_be_bytes().as_ref()); - let blob_key_hash = keccak256(blob_key.as_ref()); - - kv_write_lock.set( - PreimageKey::new(*blob_key_hash, PreimageKeyType::Keccak256).into(), - blob_key.into(), - )?; - kv_write_lock.set( - PreimageKey::new(*blob_key_hash, PreimageKeyType::Blob).into(), - sidecar.kzg_proof.to_vec(), - )?; - } - 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 = precompiles::execute(precompile_address, precompile_input) - .map_or_else( - |_| vec![0u8; 1], - |raw_res| { - let mut res = Vec::with_capacity(1 + raw_res.len()); - res.push(0x01); - res.extend_from_slice(&raw_res); - res - }, - ); - - // 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 => { - // 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 L2 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 - .l2_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::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, BlockTransactionsKind::Hashes) - .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'; - - if hint_data.len() != 32 { - anyhow::bail!("Invalid hint data length: {}", hint_data.len()); - } - - let hash: B256 = hint_data - .as_ref() - .try_into() - .map_err(|e| anyhow!("Failed to convert bytes to B256: {e}"))?; - - // Attempt to fetch the code from the L2 chain provider. - let code_hash = [&[CODE_PREFIX], hash.as_slice()].concat(); - let code = self - .l2_provider - .client() - .request::<&[Bytes; 1], Bytes>("debug_dbGet", &[code_hash.into()]) - .await; - - // Check if the first attempt to fetch the code failed. If it did, try fetching the - // code hash preimage without the geth hashdb scheme prefix. - let code = match code { - Ok(code) => code, - Err(_) => self - .l2_provider - .client() - .request::<&[B256; 1], Bytes>("debug_dbGet", &[hash]) - .await - .map_err(|e| anyhow!("Error fetching code hash preimage: {e}"))?, - }; - - let mut kv_write_lock = self.kv_store.write().await; - kv_write_lock.set( - PreimageKey::new(*hash, PreimageKeyType::Keccak256).into(), - code.into(), - )?; - } - HintType::StartingL2Output => { - const OUTPUT_ROOT_VERSION: u8 = 0; - const L2_TO_L1_MESSAGE_PASSER_ADDRESS: Address = - address!("4200000000000000000000000000000000000016"); - - if hint_data.len() != 32 { - anyhow::bail!("Invalid hint data length: {}", hint_data.len()); - } - - // Fetch the header for the L2 head block. - let raw_header: Bytes = self - .l2_provider - .client() - .request("debug_getRawHeader", &[self.l2_head]) - .await - .map_err(|e| anyhow!("Failed to fetch header RLP: {e}"))?; - let header = Header::decode(&mut raw_header.as_ref()) - .map_err(|e| anyhow!("Failed to decode header: {e}"))?; - - // Fetch the storage root for the L2 head block. - let l2_to_l1_message_passer = self - .l2_provider - .get_proof(L2_TO_L1_MESSAGE_PASSER_ADDRESS, Default::default()) - .block_id(BlockId::Hash(self.l2_head.into())) - .await - .map_err(|e| anyhow!("Failed to fetch account proof: {e}"))?; - - 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); - - if output_root.as_slice() != hint_data.as_ref() { - anyhow::bail!("Output root does not match L2 head."); - } - - let mut kv_write_lock = self.kv_store.write().await; - kv_write_lock.set( - PreimageKey::new(*output_root, PreimageKeyType::Keccak256).into(), - raw_output.into(), - )?; - } - HintType::L2StateNode => { - if hint_data.len() != 32 { - anyhow::bail!("Invalid hint data length: {}", hint_data.len()); - } - - let hash: B256 = hint_data - .as_ref() - .try_into() - .map_err(|e| anyhow!("Failed to convert bytes to B256: {e}"))?; - - // Fetch the preimage from the L2 chain provider. - let preimage: Bytes = self - .l2_provider - .client() - .request("debug_dbGet", &[hash]) - .await - .map_err(|e| anyhow!("Failed to fetch preimage: {e}"))?; - - let mut kv_write_lock = self.kv_store.write().await; - kv_write_lock.set( - PreimageKey::new(*hash, PreimageKeyType::Keccak256).into(), - preimage.into(), - )?; - } - HintType::L2AccountProof => { - if hint_data.len() != 8 + 20 { - anyhow::bail!("Invalid hint data length: {}", hint_data.len()); - } - - let block_number = u64::from_be_bytes( - hint_data.as_ref()[..8] - .try_into() - .map_err(|e| anyhow!("Error converting hint data to u64: {e}"))?, - ); - let address = Address::from_slice(&hint_data.as_ref()[8..28]); - - let proof_response = self - .l2_provider - .get_proof(address, Default::default()) - .block_id(BlockId::Number(BlockNumberOrTag::Number(block_number))) - .await - .map_err(|e| anyhow!("Failed to fetch account proof: {e}"))?; - - let mut kv_write_lock = self.kv_store.write().await; - - // Write the account proof nodes to the key-value store. - proof_response - .account_proof - .into_iter() - .try_for_each(|node| { - let node_hash = keccak256(node.as_ref()); - let key = PreimageKey::new(*node_hash, PreimageKeyType::Keccak256); - kv_write_lock.set(key.into(), node.into())?; - Ok::<(), anyhow::Error>(()) - })?; - } - HintType::L2AccountStorageProof => { - if hint_data.len() != 8 + 20 + 32 { - anyhow::bail!("Invalid hint data length: {}", hint_data.len()); - } - - let block_number = u64::from_be_bytes( - hint_data.as_ref()[..8] - .try_into() - .map_err(|e| anyhow!("Error converting hint data to u64: {e}"))?, - ); - let address = Address::from_slice(&hint_data.as_ref()[8..28]); - let slot = B256::from_slice(&hint_data.as_ref()[28..]); - - let mut proof_response = self - .l2_provider - .get_proof(address, vec![slot]) - .block_id(BlockId::Number(BlockNumberOrTag::Number(block_number))) - .await - .map_err(|e| anyhow!("Failed to fetch account proof: {e}"))?; - - let mut kv_write_lock = self.kv_store.write().await; - - // Write the account proof nodes to the key-value store. - proof_response - .account_proof - .into_iter() - .try_for_each(|node| { - let node_hash = keccak256(node.as_ref()); - let key = PreimageKey::new(*node_hash, PreimageKeyType::Keccak256); - kv_write_lock.set(key.into(), node.into())?; - Ok::<(), anyhow::Error>(()) - })?; - - // Write the storage proof nodes to the key-value store. - let storage_proof = proof_response.storage_proof.remove(0); - storage_proof.proof.into_iter().try_for_each(|node| { - let node_hash = keccak256(node.as_ref()); - let key = PreimageKey::new(*node_hash, PreimageKeyType::Keccak256); - kv_write_lock.set(key.into(), node.into())?; - Ok::<(), anyhow::Error>(()) - })?; - } - HintType::L2PayloadWitness => { - if hint_data.len() < 32 { - anyhow::bail!("Invalid hint data length: {}", hint_data.len()); - } - let parent_block_hash = B256::from_slice(&hint_data.as_ref()[..32]); - let payload_attributes: OpPayloadAttributes = - serde_json::from_slice(&hint_data[32..])?; - - let execute_payload_response: ExecutionWitness = self - .l2_provider - .client() - .request::<(B256, OpPayloadAttributes), ExecutionWitness>( - "debug_executePayload", - (parent_block_hash, payload_attributes), - ) - .await - .map_err(|e| anyhow!("Failed to fetch preimage: {e}"))?; - - let mut merged = HashMap::::default(); - merged.extend(execute_payload_response.state); - merged.extend(execute_payload_response.codes); - merged.extend(execute_payload_response.keys); - - let mut kv_write_lock = self.kv_store.write().await; - for (hash, preimage) in merged.into_iter() { - let computed_hash = keccak256(preimage.as_ref()); - assert_eq!( - computed_hash, hash, - "Preimage hash does not match expected hash" - ); - - let key = PreimageKey::new(*hash, PreimageKeyType::Keccak256); - kv_write_lock.set(key.into(), preimage.into())?; - } - } - HintType::EigenDACommitment => { - let cert = hint_data; - info!(target: "fetcher", "Fetching AltDACommitment cert: {:?}", cert); - // Fetch the blob sidecar from the blob provider. - let eigenda_blob = self - .eigenda_blob_provider - .fetch_eigenda_blob(&cert) - .await - .map_err(|e| anyhow!("Failed to fetch eigenda blob: {e}"))?; - - info!(target: "fetcher", "eigenda_blob len {}", eigenda_blob.len()); - // Acquire a lock on the key-value store and set the preimages. - let mut kv_write_lock = self.kv_store.write().await; - - // Set the preimage for the blob commitment. - kv_write_lock.set( - PreimageKey::new(*keccak256(cert), PreimageKeyType::GlobalGeneric).into(), - eigenda_blob.to_vec(), - )?; - } - } - - 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.into(); - - Ok::<_, anyhow::Error>(envelope.encoded_2718()) - }) - .collect::>>()?; - - 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>(&self, nodes: &[T]) -> Result<()> { - let mut kv_write_lock = self.kv_store.write().await; - - // If the list of nodes is empty, store the empty root hash and exit early. - // The `HashBuilder` will not push the preimage of the empty root hash to the - // `ProofRetainer` in the event that there are no leaves inserted. - if nodes.is_empty() { - let empty_key = PreimageKey::new(*EMPTY_ROOT_HASH, PreimageKeyType::Keccak256); - return kv_write_lock.set(empty_key.into(), [EMPTY_STRING_CODE].into()); - } - - let mut hb = kona_mpt::ordered_trie_with_encoder(nodes, |node, buf| { - buf.put_slice(node.as_ref()); - }); - hb.root(); - let intermediates = hb.take_proof_nodes().into_inner(); - - 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(()) - } -} diff --git a/bin/host/src/fetcher/precompiles.rs b/bin/host/src/fetcher/precompiles.rs deleted file mode 100644 index 3ebd6a8..0000000 --- a/bin/host/src/fetcher/precompiles.rs +++ /dev/null @@ -1,45 +0,0 @@ -// this is pretty ugly - -//! 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>(address: Address, input: T) -> Result> { - 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 output = std_precompile(&input.into(), u64::MAX) - .map_err(|e| anyhow!("Failed precompile execution: {e}"))?; - - Ok(output.bytes.into()) - } - Precompile::Env(env_precompile) => { - // Use default environment for KZG point evaluation. - let output = env_precompile(&input.into(), u64::MAX, &Env::default()) - .map_err(|e| anyhow!("Failed precompile execution: {e}"))?; - - Ok(output.bytes.into()) - } - _ => anyhow::bail!("Precompile not accelerated"), - } - } else { - anyhow::bail!("Precompile not accelerated"); - } -} diff --git a/bin/host/src/lib.rs b/bin/host/src/lib.rs index 6c6a6b1..c10aaab 100644 --- a/bin/host/src/lib.rs +++ b/bin/host/src/lib.rs @@ -1,4 +1,4 @@ -pub mod fetcher; +pub mod eigenda_fetcher; pub mod eigenda_blobs; @@ -14,7 +14,7 @@ use kona_host::kv; use crate::eigenda_blobs::OnlineEigenDABlobProvider; use anyhow::{anyhow, Result}; -use fetcher::Fetcher; +use eigenda_fetcher::FetcherWithEigenDASupport; use kona_preimage::{ BidirectionalChannel, HintReader, HintWriter, NativeChannel, OracleReader, OracleServer, }; @@ -45,15 +45,17 @@ pub async fn start_server_and_native_client(cfg: HostCli) -> Result { ) .await .map_err(|e| anyhow!("Failed to load eigenda blob provider configuration: {e}"))?; - info!(target: "host", "create fetch with eigenda_provider"); - Some(Arc::new(RwLock::new(Fetcher::new( - kv_store.clone(), - l1_provider, - blob_provider, - eigenda_blob_provider, - l2_provider, - cfg.agreed_l2_head_hash, - )))) + + Some(Arc::new(RwLock::new( + FetcherWithEigenDASupport::new_from_parts( + kv_store.clone(), + l1_provider, + blob_provider, + eigenda_blob_provider, + l2_provider, + cfg.agreed_l2_head_hash, + ), + ))) } else { None }; @@ -84,7 +86,7 @@ pub async fn start_server_and_native_client(cfg: HostCli) -> Result { pub async fn start_native_preimage_server( kv_store: Arc>, - fetcher: Option>>>, + fetcher: Option>>>, hint_chan: NativeChannel, preimage_chan: NativeChannel, ) -> Result<()> diff --git a/bin/host/src/preimage.rs b/bin/host/src/preimage.rs index 899af8b..7196a35 100644 --- a/bin/host/src/preimage.rs +++ b/bin/host/src/preimage.rs @@ -1,6 +1,6 @@ //! Contains the implementations of the [HintRouter] and [PreimageFetcher] traits.] -use crate::{fetcher::Fetcher, kv::KeyValueStore}; +use crate::{eigenda_fetcher::FetcherWithEigenDASupport, kv::KeyValueStore}; use async_trait::async_trait; use kona_preimage::{ errors::{PreimageOracleError, PreimageOracleResult}, @@ -9,13 +9,13 @@ use kona_preimage::{ use std::sync::Arc; use tokio::sync::RwLock; -/// A [Fetcher]-backed implementation of the [PreimageFetcher] trait. +/// A [FetcherWithEigenDASupport]-backed implementation of the [PreimageFetcher] trait. #[derive(Debug)] pub struct OnlinePreimageFetcher where KV: KeyValueStore + ?Sized, { - inner: Arc>>, + inner: Arc>>, } #[async_trait] @@ -36,8 +36,8 @@ impl OnlinePreimageFetcher where KV: KeyValueStore + ?Sized, { - /// Create a new [OnlinePreimageFetcher] from the given [Fetcher]. - pub const fn new(fetcher: Arc>>) -> Self { + /// Create a new [OnlinePreimageFetcher] from the given [FetcherWithEigenDASupport]. + pub const fn new(fetcher: Arc>>) -> Self { Self { inner: fetcher } } } @@ -74,13 +74,13 @@ where } } -/// A [Fetcher]-backed implementation of the [HintRouter] trait. +/// A [FetcherWithEigenDASupport]-backed implementation of the [HintRouter] trait. #[derive(Debug)] pub struct OnlineHintRouter where KV: KeyValueStore + ?Sized, { - inner: Arc>>, + inner: Arc>>, } #[async_trait] @@ -90,7 +90,9 @@ where { async fn route_hint(&self, hint: String) -> PreimageOracleResult<()> { let mut fetcher = self.inner.write().await; - fetcher.hint(&hint); + fetcher + .hint(&hint) + .map_err(|e| PreimageOracleError::Other(e.to_string()))?; Ok(()) } } @@ -99,19 +101,8 @@ impl OnlineHintRouter where KV: KeyValueStore + ?Sized, { - /// Create a new [OnlineHintRouter] from the given [Fetcher]. - pub const fn new(fetcher: Arc>>) -> Self { + /// Create a new [OnlineHintRouter] from the given [FetcherWithEigenDASupport]. + pub const fn new(fetcher: Arc>>) -> Self { Self { inner: fetcher } } } - -/// An [OfflineHintRouter] is a [HintRouter] that does nothing. -#[derive(Debug)] -pub struct OfflineHintRouter; - -#[async_trait] -impl HintRouter for OfflineHintRouter { - async fn route_hint(&self, _hint: String) -> PreimageOracleResult<()> { - Ok(()) - } -} diff --git a/bin/host/src/server.rs b/bin/host/src/server.rs index f1eb2d9..514072a 100644 --- a/bin/host/src/server.rs +++ b/bin/host/src/server.rs @@ -1,13 +1,12 @@ //! This module contains the [PreimageServer] struct and its implementation. use crate::{ - fetcher::Fetcher, + eigenda_fetcher::FetcherWithEigenDASupport, kv::KeyValueStore, - preimage::{ - OfflineHintRouter, OfflinePreimageFetcher, OnlineHintRouter, OnlinePreimageFetcher, - }, + preimage::{OfflinePreimageFetcher, OnlineHintRouter, OnlinePreimageFetcher}, }; use anyhow::{anyhow, Result}; +use kona_host::preimage::OfflineHintRouter; use kona_preimage::{ errors::PreimageOracleError, HintReaderServer, HintRouter, PreimageFetcher, PreimageOracleServer, @@ -33,7 +32,7 @@ where kv_store: Arc>, /// The fetcher for fetching preimages from a remote source. If [None], the server will only /// serve preimages that are already in the key-value store. - fetcher: Option>>>, + fetcher: Option>>>, } impl PreimageServer @@ -49,7 +48,7 @@ where oracle_server: P, hint_reader: H, kv_store: Arc>, - fetcher: Option>>>, + fetcher: Option>>>, ) -> Self { Self { oracle_server, @@ -80,7 +79,7 @@ where /// client. async fn start_oracle_server( kv_store: Arc>, - fetcher: Option>>>, + fetcher: Option>>>, oracle_server: P, ) -> Result<()> { #[inline(always)] @@ -121,7 +120,7 @@ where /// handler. async fn start_hint_router( hint_reader: H, - fetcher: Option>>>, + fetcher: Option>>>, ) -> Result<()> { #[inline(always)] async fn do_loop(router: &R, server: &H) -> Result<()> diff --git a/crates/proof/src/eigenda_provider.rs b/crates/proof/src/eigenda_provider.rs index 63a81f4..10e01e3 100644 --- a/crates/proof/src/eigenda_provider.rs +++ b/crates/proof/src/eigenda_provider.rs @@ -7,7 +7,7 @@ use kona_preimage::{CommsClient, PreimageKey, PreimageKeyType}; use kona_proof::errors::OracleProviderError; -use crate::hint::HintType; +use crate::hint::ExtendedHintType; /// The oracle-backed EigenDA provider for the client program. #[derive(Debug, Clone)] @@ -29,7 +29,7 @@ impl EigenDABlobProvider for OracleEigenDAProvider async fn get_blob(&mut self, cert: &Bytes) -> Result { self.oracle - .write(&HintType::EigenDACommitment.encode_with(&[cert])) + .write(&ExtendedHintType::EigenDACommitment.encode_with(&[cert])) .await .map_err(OracleProviderError::Preimage)?; let data = self @@ -45,14 +45,14 @@ impl EigenDABlobProvider for OracleEigenDAProvider async fn get_element(&mut self, cert: &Bytes, element: &Bytes) -> Result { self.oracle - .write(&HintType::EigenDACommitment.encode_with(&[cert])) + .write(&ExtendedHintType::EigenDACommitment.encode_with(&[cert])) .await .map_err(OracleProviderError::Preimage)?; let cert_point_key = Bytes::copy_from_slice(&[cert.to_vec(), element.to_vec()].concat()); self.oracle - .write(&HintType::EigenDACommitment.encode_with(&[&cert_point_key])) + .write(&ExtendedHintType::EigenDACommitment.encode_with(&[&cert_point_key])) .await .map_err(OracleProviderError::Preimage)?; let data = self diff --git a/crates/proof/src/hint.rs b/crates/proof/src/hint.rs index c099534..0e388ad 100644 --- a/crates/proof/src/hint.rs +++ b/crates/proof/src/hint.rs @@ -1,4 +1,4 @@ -//! This module contains the [HintType] enum. +//! This module contains the [ExtendedHintType], which adds an AltDACommitment case to kona's [HintType] enum. use alloc::{ string::{String, ToString}, @@ -6,20 +6,20 @@ use alloc::{ }; use alloy_primitives::{hex, Bytes}; use core::fmt::Display; -use kona_proof::errors::HintParsingError; +use kona_proof::{errors::HintParsingError, HintType}; -/// A [Hint] is parsed in the format ` `, where `` is a string that +/// A [ExtendedHint] is parsed in the format ` `, where `` is a string that /// represents the type of hint, and `` is the data associated with the hint (bytes /// encoded as hex UTF-8). #[derive(Debug, Clone, PartialEq, Eq)] -pub struct Hint { +pub struct ExtendedHint { /// The type of hint. - pub hint_type: HintType, + pub hint_type: ExtendedHintType, /// The data associated with the hint. pub hint_data: Bytes, } -impl Hint { +impl ExtendedHint { /// Parses a hint from a string. pub fn parse(s: &str) -> Result { let mut parts = s.split(' ').collect::>(); @@ -31,7 +31,7 @@ impl Hint { ))); } - let hint_type = HintType::try_from(parts.remove(0))?; + let hint_type = ExtendedHintType::try_from(parts.remove(0))?; let hint_data = hex::decode(parts.remove(0)) .map_err(|e| HintParsingError(e.to_string()))? .into(); @@ -42,48 +42,20 @@ impl Hint { }) } - /// Splits the [Hint] into its components. - pub fn split(self) -> (HintType, Bytes) { + /// Splits the [ExtendedHint] into its components. + pub fn split(self) -> (ExtendedHintType, Bytes) { (self.hint_type, self.hint_data) } } -/// The [HintType] enum is used to specify the type of hint that was received. +/// The [ExtendedHintType] extends the [HintType] enum and 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 code of a contract on layer 2. - L2Code, - /// A hint that specifies the preimage of the starting L2 output root on layer 2. - StartingL2Output, - /// A hint that specifies the state node in the L2 state trie. - L2StateNode, - /// A hint that specifies the proof on the path to an account in the L2 state trie. - L2AccountProof, - /// A hint that specifies the proof on the path to a storage slot in an account within in the - /// L2 state trie. - L2AccountStorageProof, - /// A hint that specifies bulk storage of all the code, state and keys generated by an - /// execution witness. - L2PayloadWitness, - /// A hint that specifies the EigenDA commitment. +pub enum ExtendedHintType { + Original(HintType), EigenDACommitment, } -impl HintType { +impl ExtendedHintType { /// Encodes the hint type as a string. pub fn encode_with(&self, data: &[&[u8]]) -> String { let concatenated = hex::encode(data.iter().copied().flatten().copied().collect::>()); @@ -91,52 +63,27 @@ impl HintType { } } -impl TryFrom<&str> for HintType { +impl TryFrom<&str> for ExtendedHintType { type Error = HintParsingError; fn try_from(value: &str) -> Result { match value { - "l1-block-header" => Ok(Self::L1BlockHeader), - "l1-transactions" => Ok(Self::L1Transactions), - "l1-receipts" => Ok(Self::L1Receipts), - "l1-blob" => Ok(Self::L1Blob), - "l1-precompile" => Ok(Self::L1Precompile), - "l2-block-header" => Ok(Self::L2BlockHeader), - "l2-transactions" => Ok(Self::L2Transactions), - "l2-code" => Ok(Self::L2Code), - "starting-l2-output" => Ok(Self::StartingL2Output), - "l2-state-node" => Ok(Self::L2StateNode), - "l2-account-proof" => Ok(Self::L2AccountProof), - "l2-account-storage-proof" => Ok(Self::L2AccountStorageProof), - "l2-payload-witness" => Ok(Self::L2PayloadWitness), "eigenda-commitment" => Ok(Self::EigenDACommitment), - _ => Err(HintParsingError(value.to_string())), + _ => Ok(Self::Original(HintType::try_from(value)?)), } } } -impl From for &str { - fn from(value: HintType) -> Self { +impl From for &str { + fn from(value: ExtendedHintType) -> 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::L2Code => "l2-code", - HintType::StartingL2Output => "starting-l2-output", - HintType::L2StateNode => "l2-state-node", - HintType::L2AccountProof => "l2-account-proof", - HintType::L2AccountStorageProof => "l2-account-storage-proof", - HintType::L2PayloadWitness => "l2-payload-witness", - HintType::EigenDACommitment => "eigenda-commitment", + ExtendedHintType::EigenDACommitment => "eigenda-commitment", + ExtendedHintType::Original(hint_type) => hint_type.into(), } } } -impl Display for HintType { +impl Display for ExtendedHintType { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let s: &str = (*self).into(); write!(f, "{}", s)