Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 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/guest/Cargo.lock

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

5 changes: 5 additions & 0 deletions crates/executor/guest/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use alloy_primitives::{Address, FixedBytes};
use mpt::Error as MptError;
use reth_consensus::ConsensusError;
use reth_evm::execute::BlockExecutionError;
use revm_primitives::U256;

#[derive(Debug, thiserror::Error)]
pub enum ClientError {
Expand Down Expand Up @@ -31,4 +32,8 @@ pub enum ClientError {
FailedToReadGenesisFile(#[from] std::io::Error),
#[error("Failed to deserialize the genesis file: {}", .0)]
FailedToDeserializeGenesisFile(#[from] serde_json::Error),
#[error("Failed to check slot and value: {}", .0)]
FailedToCheckSlotAndValue(U256),
#[error("Failed to fetch slot and value: {}", .0)]
FailedToFetchSlotAndValue(U256),
}
71 changes: 68 additions & 3 deletions crates/executor/guest/src/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ use reth_evm::{
use reth_evm_ethereum::EthEvmConfig;
use reth_execution_types::ExecutionOutcome;
use reth_primitives_traits::Block;
use reth_trie::KeccakKeyHasher;
use revm::{database::WrapDatabaseRef, install_crypto};
use revm_primitives::Address;
use reth_trie::{KeccakKeyHasher, TrieAccount, EMPTY_ROOT_HASH};
use revm::{database::WrapDatabaseRef, install_crypto, DatabaseRef};
use revm_primitives::{Address, HashMap, U256};

use crate::{
custom::{CustomCrypto, CustomEvmFactory},
Expand All @@ -33,6 +33,7 @@ pub const BLOCK_EXECUTION: &str = "block execution";
pub const VALIDATE_HEADER: &str = "validate header";
pub const VALIDATE_EXECUTION: &str = "validate block post-execution";
pub const COMPUTE_STATE_ROOT: &str = "compute state root";
pub const CHECK_SLOT_AND_VALUE: &str = "check slot and value";

pub type EthClientExecutor = ClientExecutor<EthEvmConfig<ChainSpec, CustomEvmFactory>, ChainSpec>;

Expand All @@ -55,6 +56,7 @@ where
pub fn execute(
&self,
mut input: ClientExecutorInput<C::Primitives>,
storage_info: Vec<(Address, U256, U256)>,
) -> Result<(Header, B256), ClientError> {
let chain_id: u64 = (&input.genesis).try_into().expect("convert chain id err");

Expand Down Expand Up @@ -152,6 +154,69 @@ where
requests_hash: input.current_block.header().requests_hash(),
};

if !storage_info.is_empty() {
let check_result: Result<(), ClientError> = profile_report!(CHECK_SLOT_AND_VALUE, {
let state = input.state();
let db = {
for (hashed_address, storage_trie) in state.storage_tries.iter() {
let account = state
.state_trie
.get_rlp::<TrieAccount>(hashed_address.as_slice())
.unwrap();
let storage_root = account.map_or(EMPTY_ROOT_HASH, |a| a.storage_root);
if storage_root != storage_trie.hash() {
return Err(ClientError::MismatchedStorageRoot);
}
}

let bytecodes_by_hash = input
.bytecodes()
.map(|code| (code.hash_slow(), code))
.collect::<HashMap<_, _>>();

// Verify and build block hashes
let mut block_hashes: HashMap<u64, B256> =
HashMap::with_hasher(Default::default());
for (child_header, parent_header) in input.sealed_headers().tuple_windows() {
if parent_header.number() != child_header.number() - 1 {
return Err(ClientError::InvalidHeaderBlockNumber(
parent_header.number() + 1,
child_header.number(),
));
}

let parent_header_hash = parent_header.hash_slow();
if parent_header_hash != child_header.parent_hash() {
return Err(ClientError::InvalidHeaderParentHash(
parent_header_hash,
child_header.parent_hash(),
));
}

block_hashes.insert(parent_header.number(), child_header.parent_hash());
}

TrieDB::new(state, block_hashes, bytecodes_by_hash)
};

for (contract_address, slot_id, expected_value) in storage_info {
match db.storage_ref(contract_address, slot_id) {
Ok(actual_value) => {
if actual_value != expected_value {
return Err(ClientError::FailedToCheckSlotAndValue(slot_id));
}
}
_ => {
return Err(ClientError::FailedToFetchSlotAndValue(slot_id));
}
}
}
Ok(())
});
if check_result.is_err() {
return Err(check_result.err().unwrap());
}
}
Ok((header, parent_state_root))
}
}
Expand Down
29 changes: 16 additions & 13 deletions crates/executor/guest/src/io.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use std::iter::once;

use crate::error::ClientError;
use alloy_consensus::{Block, BlockHeader, Header};
use alloy_primitives::map::HashMap;
use itertools::Itertools;
use mpt::EthereumState;
use primitives::genesis::Genesis;
use primitives::{genesis::Genesis, is_precompile};
use reth_errors::ProviderError;
use reth_ethereum_primitives::EthPrimitives;
use reth_primitives_traits::{NodePrimitives, SealedHeader};
Expand All @@ -17,8 +18,6 @@ use revm_primitives::{keccak256, Address, B256, U256};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;

use crate::error::ClientError;

pub type EthClientExecutorInput = ClientExecutorInput<EthPrimitives>;

#[cfg(feature = "optimism")]
Expand Down Expand Up @@ -129,7 +128,11 @@ impl DatabaseRef for TrieDB<'_> {
let hashed_address = keccak256(address);
let hashed_address = hashed_address.as_slice();

let account_in_trie = self.inner.state_trie.get_rlp::<TrieAccount>(hashed_address).unwrap();
let account_in_trie = match self.inner.state_trie.get_rlp::<TrieAccount>(hashed_address) {
Ok(res) => res,
Err(mpt::Error::NodeNotResolved(_)) if is_precompile(address) => None,
Err(err) => return Err(ProviderError::TrieWitnessError(err.to_string())),
};

let account = account_in_trie.map(|account_in_trie| AccountInfo {
balance: account_in_trie.balance,
Expand All @@ -151,16 +154,16 @@ impl DatabaseRef for TrieDB<'_> {
let hashed_address = keccak256(address);
let hashed_address = hashed_address.as_slice();

let storage_trie = self
.inner
.storage_tries
.get(hashed_address)
.expect("A storage trie must be provided for each account");
let storage_trie = self.inner.storage_tries.get(hashed_address);

Ok(storage_trie
.get_rlp::<U256>(keccak256(index.to_be_bytes::<32>()).as_slice())
.expect("Can get from MPT")
.unwrap_or_default())
if let Some(storage_trie) = storage_trie {
Ok(storage_trie
.get_rlp::<U256>(keccak256(index.to_be_bytes::<32>()).as_slice())
.expect("Can get from MPT")
.unwrap_or_default())
} else {
Ok(U256::ZERO)
}
}

/// Get block hash by block number.
Expand Down
3 changes: 2 additions & 1 deletion crates/executor/guest/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ pub fn verify_block(input: &[u8]) -> (B256, B256, B256) {
Arc::new((&input.genesis).try_into().unwrap()),
input.custom_beneficiary,
);
let (header, prev_state_root) = executor.execute(input).expect("failed to execute client");
let (header, prev_state_root) =
executor.execute(input, vec![]).expect("failed to execute client");
let block_hash = header.hash_slow();
(block_hash, header.state_root, prev_state_root)
}
5 changes: 4 additions & 1 deletion crates/executor/host/src/host_executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ impl<C: ConfigureEvm, CS> HostExecutor<C, CS> {
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
Expand Down Expand Up @@ -105,6 +107,7 @@ impl<C: ConfigureEvm, CS> HostExecutor<C, CS> {
debug_provider,
block_number - 1,
previous_block.header().state_root(),
is_goat_testnet,
)
.await
.map_err(HostError::RpcDbError)?;
Expand Down Expand Up @@ -149,7 +152,7 @@ impl<C: ConfigureEvm, CS> HostExecutor<C, CS> {
&block,
self.chain_spec.clone(),
&execution_output,
is_goat_testnet(chain_id),
is_goat_testnet,
)?;

// Accumulate the logs bloom.
Expand Down
2 changes: 1 addition & 1 deletion crates/executor/host/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ async fn run_e2e<C, CS, N>(
.expect("failed to execute host");

// Execute the client.
client_executor.execute(client_input.clone()).expect("failed to execute client");
client_executor.execute(client_input.clone(), vec![]).expect("failed to execute client");

// Save the client input to a buffer.
let buffer = bincode::serialize(&client_input).unwrap();
Expand Down
7 changes: 3 additions & 4 deletions crates/mpt/src/execution_witness.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<Bytes>,
pre_state_root: B256,
) -> Result<(MptNode, HashMap<B256, MptNode>), String> {
// Step 1: Decode all RLP-encoded trie nodes and index by hash
Expand All @@ -19,7 +18,7 @@ pub(crate) fn build_validated_tries(
let mut node_by_hash: HashMap<B256, MptNode> = HashMap::default();
let mut root_node: Option<MptNode> = 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 {
Expand Down
24 changes: 15 additions & 9 deletions crates/mpt/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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<Bytes>, 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 }
}
Expand All @@ -93,7 +88,18 @@ impl EthereumState {
.get(hashed_address)
.cloned()
.unwrap_or_else(|| HashedStorage::new(false));
let storage_root = {

let storage_root = if state_storage.is_empty() {
// Preserve the existing storage root when witness lacks the storage trie.
self.state_trie
.get_rlp::<TrieAccount>(hashed_address.as_slice())
.ok()
.flatten()
.map(|existing| existing.storage_root)
.unwrap_or_else(|| {
self.storage_tries.entry(*hashed_address).or_default().hash()
})
} else {
let storage_trie = self.storage_tries.entry(*hashed_address).or_default();

if state_storage.wiped {
Expand Down
1 change: 1 addition & 0 deletions crates/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ reth-chainspec.workspace = true
reth-optimism-chainspec = { workspace = true, optional = true }
reth-optimism-forks = { workspace = true, optional = true }
reth-trie.workspace = true
alloy-primitives.workspace = true

# alloy
alloy-eips.workspace = true
Expand Down
15 changes: 15 additions & 0 deletions crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@ pub mod account_proof;
pub mod chain_spec;
pub mod genesis;

use alloy_primitives::Address;

#[inline]
pub fn is_goat_testnet(chain_id: u64) -> bool {
chain_id == 48816
}

#[inline]
pub fn is_precompile(address: Address) -> bool {
let bytes = address.as_slice();
// Check if the first 19 bytes are zero.
if !bytes[..19].iter().all(|&b| b == 0) {
return false;
}
// Check if the last byte is non-zero (so it is not the zero address).
let last = bytes[19];

last > 0
}
3 changes: 2 additions & 1 deletion crates/storage/rpc-db/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
Loading