From 5b8507502e067cef4d3c315d3a8e36c9faa0d2a1 Mon Sep 17 00:00:00 2001 From: Blake Date: Wed, 14 Jan 2026 14:23:56 +0800 Subject: [PATCH] feat(execution-witness): add support for goat testnet execution witness --- Cargo.lock | 1 + crates/executor/host/src/host_executor.rs | 5 +- crates/mpt/src/execution_witness.rs | 7 +-- crates/mpt/src/lib.rs | 11 +--- crates/storage/rpc-db/Cargo.toml | 3 +- .../storage/rpc-db/src/execution_witness.rs | 62 +++++++++++++------ 6 files changed, 57 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index feda82f..65cd62b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7269,6 +7269,7 @@ dependencies = [ "revm-database-interface", "revm-primitives", "revm-state", + "serde", "thiserror 1.0.69", "tokio", "tracing", diff --git a/crates/executor/host/src/host_executor.rs b/crates/executor/host/src/host_executor.rs index a8d7d23..a3db56e 100644 --- a/crates/executor/host/src/host_executor.rs +++ b/crates/executor/host/src/host_executor.rs @@ -76,6 +76,8 @@ impl HostExecutor { let chain_id: u64 = (&genesis).try_into().unwrap(); tracing::debug!("chain id: {}", chain_id); + let is_goat_testnet = is_goat_testnet(chain_id); + // Fetch the current block and the previous block from the provider. tracing::info!("[{}] fetching the current block and the previous block", block_number); let rpc_block = provider @@ -105,6 +107,7 @@ impl HostExecutor { debug_provider, block_number - 1, previous_block.header().state_root(), + is_goat_testnet, ) .await .map_err(HostError::RpcDbError)?; @@ -149,7 +152,7 @@ impl HostExecutor { &block, self.chain_spec.clone(), &execution_output, - is_goat_testnet(chain_id), + is_goat_testnet, )?; // Accumulate the logs bloom. diff --git a/crates/mpt/src/execution_witness.rs b/crates/mpt/src/execution_witness.rs index 48cb0d1..b740c74 100644 --- a/crates/mpt/src/execution_witness.rs +++ b/crates/mpt/src/execution_witness.rs @@ -1,6 +1,5 @@ -use alloy_primitives::{keccak256, map::HashMap, B256}; +use alloy_primitives::{keccak256, map::HashMap, Bytes, B256}; use alloy_rlp::Decodable; -use alloy_rpc_types_debug::ExecutionWitness; use reth_trie::TrieAccount; use crate::mpt::{resolve_nodes, MptNode, MptNodeData, MptNodeReference}; @@ -10,7 +9,7 @@ use crate::mpt::{resolve_nodes, MptNode, MptNodeData, MptNodeReference}; // NOTE: This method should be called outside zkVM! In general you construct tries, then // validate them inside zkVM. pub(crate) fn build_validated_tries( - witness: &ExecutionWitness, + state: &Vec, pre_state_root: B256, ) -> Result<(MptNode, HashMap), String> { // Step 1: Decode all RLP-encoded trie nodes and index by hash @@ -19,7 +18,7 @@ pub(crate) fn build_validated_tries( let mut node_by_hash: HashMap = HashMap::default(); let mut root_node: Option = None; - for encoded in &witness.state { + for encoded in state { let node = MptNode::decode(encoded).expect("Valid MPT node in witness"); let hash = keccak256(encoded); if hash == pre_state_root { diff --git a/crates/mpt/src/lib.rs b/crates/mpt/src/lib.rs index 97642df..44d0664 100644 --- a/crates/mpt/src/lib.rs +++ b/crates/mpt/src/lib.rs @@ -1,6 +1,4 @@ -#![cfg_attr(not(test), warn(unused_crate_dependencies))] - -use alloy_primitives::{keccak256, map::HashMap, Address, B256}; +use alloy_primitives::{keccak256, map::HashMap, Address, Bytes, B256}; use alloy_rpc_types::EIP1186AccountProofResponse; use reth_trie::{AccountProof, HashedPostState, HashedStorage, TrieAccount}; use serde::{Deserialize, Serialize}; @@ -73,12 +71,9 @@ impl EthereumState { } #[cfg(feature = "execution-witness")] - pub fn from_execution_witness( - witness: &alloy_rpc_types_debug::ExecutionWitness, - pre_state_root: B256, - ) -> Self { + pub fn from_execution_witness(state: &Vec, pre_state_root: B256) -> Self { let (state_trie, storage_tries) = - execution_witness::build_validated_tries(witness, pre_state_root).unwrap(); + execution_witness::build_validated_tries(state, pre_state_root).unwrap(); Self { state_trie, storage_tries } } diff --git a/crates/storage/rpc-db/Cargo.toml b/crates/storage/rpc-db/Cargo.toml index 4780e6c..cec1420 100644 --- a/crates/storage/rpc-db/Cargo.toml +++ b/crates/storage/rpc-db/Cargo.toml @@ -9,6 +9,7 @@ async-trait.workspace = true tokio.workspace = true thiserror.workspace = true tracing.workspace = true +serde.workspace = true mpt.workspace = true primitives.workspace = true @@ -33,7 +34,7 @@ alloy-trie = { workspace = true, optional = true, features = ["ethereum"] } [features] default = ["execution-witness"] execution-witness = [ - "dep:alloy-consensus", + "dep:alloy-consensus", "dep:alloy-rlp", "dep:alloy-trie", "alloy-provider/debug-api" diff --git a/crates/storage/rpc-db/src/execution_witness.rs b/crates/storage/rpc-db/src/execution_witness.rs index b7e5105..8b39982 100644 --- a/crates/storage/rpc-db/src/execution_witness.rs +++ b/crates/storage/rpc-db/src/execution_witness.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; -use alloy_consensus::Header; -use alloy_primitives::{map::HashMap, Address, B256}; +use alloy_consensus::{private::alloy_eips::BlockNumberOrTag, Header}; +use alloy_primitives::{map::HashMap, Address, Bytes, B256}; use alloy_provider::{ext::DebugApi, Network, Provider}; use alloy_rlp::Decodable; use alloy_trie::TrieAccount; @@ -11,9 +11,18 @@ use reth_storage_errors::ProviderError; use revm_database::{BundleState, DatabaseRef}; use revm_primitives::{keccak256, ruint::aliases::U256, StorageKey, StorageValue}; use revm_state::{AccountInfo, Bytecode}; +use serde::{Deserialize, Serialize}; use crate::{RpcDb, RpcDbError}; +#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExecutionWitnessGoat { + pub state: Vec, + pub codes: Vec, + pub keys: Option>, + pub headers: Vec
, +} + #[derive(Debug)] pub struct ExecutionWitnessRpcDb { /// The provider which fetches data. @@ -30,23 +39,40 @@ pub struct ExecutionWitnessRpcDb { impl + Clone, N: Network> ExecutionWitnessRpcDb { /// Create a new [`ExecutionWitnessRpcDb`]. - pub async fn new(provider: P, block_number: u64, state_root: B256) -> Result { - let execution_witness = provider.debug_execution_witness((block_number + 1).into()).await?; - - let state = EthereumState::from_execution_witness(&execution_witness, state_root); - - let codes = execution_witness - .codes - .iter() - .map(|encoded| (keccak256(encoded), Bytecode::new_raw(encoded.clone()))) - .collect(); - - let ancestor_headers = execution_witness - .headers - .iter() - .map(|encoded| Header::decode(&mut encoded.as_ref()).unwrap()) - .map(|h| (h.number, h)) + pub async fn new( + provider: P, + block_number: u64, + state_root: B256, + is_goat_testnet: bool, + ) -> Result { + let (state, codes, headers) = if is_goat_testnet { + let execution_witness: ExecutionWitnessGoat = provider + .raw_request( + "debug_executionWitness".into(), + (BlockNumberOrTag::Number(block_number + 1),), + ) + .await?; + + (execution_witness.state, execution_witness.codes, execution_witness.headers) + } else { + let execution_witness = + provider.debug_execution_witness((block_number + 1).into()).await?; + let headers = execution_witness + .headers + .iter() + .map(|encoded| Header::decode(&mut encoded.as_ref()).unwrap()) + .collect(); + + (execution_witness.state, execution_witness.codes, headers) + }; + tracing::info!("fetch execution witness for block {}", block_number + 1); + + let state = EthereumState::from_execution_witness(&state, state_root); + let codes = codes + .into_iter() + .map(|encoded| (keccak256(&encoded), Bytecode::new_raw(encoded))) .collect(); + let ancestor_headers = headers.into_iter().map(|h| (h.number, h)).collect(); let db = Self { provider, state, codes, ancestor_headers, phantom: PhantomData };