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
1,045 changes: 418 additions & 627 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ A minimal implementation of generating zero-knowledge proofs of EVM block execut
Required

- [Rust](https://www.rust-lang.org/tools/install)
- [Ziren toolchain](https://docs.zkm.io/introduction/installation.html)
- [Ziren toolchain](https://docs.zkm.io/introduction/installation.html): the guest program is always compiled with the `zkm` rustup toolchain, whichever toolchain builds the host; set `ZKM_GUEST_TOOLCHAIN` if it is installed under another name.
- Register on [Alchemy](https://www.alchemy.com/) or [Chainstack](https://chainstack.com/)​​ to get ​​`rpc url`​ and `ws url`​. But [Chainstack](https://chainstack.com/) does not support archival/historical `eth_getProof` calls.

### RPC Node Requirement
Expand Down
16 changes: 16 additions & 0 deletions bin/eth-proofs/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
use std::env;

use zkm_build::build_program;

/// The guest is compiled by a nested `cargo` that inherits this script's environment, into
/// which cargo exports the compiler building the host (`RUSTC`, a toolchain without the zkVM
/// target) and rustup exports that toolchain's name. Both are replaced by the Ziren toolchain
/// here; `ZKM_GUEST_TOOLCHAIN` names one installed under a different name.
/// `RUSTC_WORKSPACE_WRAPPER` is kept: zkm-build reads it to skip the guest build under clippy.
fn select_guest_toolchain() {
println!("cargo:rerun-if-env-changed=ZKM_GUEST_TOOLCHAIN");
let toolchain = env::var("ZKM_GUEST_TOOLCHAIN").unwrap_or_else(|_| "zkm".to_string());
env::set_var("RUSTUP_TOOLCHAIN", toolchain);
env::remove_var("RUSTC");
env::remove_var("RUSTC_WRAPPER");
}

fn main() {
select_guest_toolchain();
build_program("../guest");
}
44 changes: 38 additions & 6 deletions bin/guest/Cargo.lock

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

16 changes: 16 additions & 0 deletions bin/host/build.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
use std::env;

use zkm_build::{build_program_with_args, BuildArgs};

/// The guest is compiled by a nested `cargo` that inherits this script's environment, into
/// which cargo exports the compiler building the host (`RUSTC`, a toolchain without the zkVM
/// target) and rustup exports that toolchain's name. Both are replaced by the Ziren toolchain
/// here; `ZKM_GUEST_TOOLCHAIN` names one installed under a different name.
/// `RUSTC_WORKSPACE_WRAPPER` is kept: zkm-build reads it to skip the guest build under clippy.
fn select_guest_toolchain() {
println!("cargo:rerun-if-env-changed=ZKM_GUEST_TOOLCHAIN");
let toolchain = env::var("ZKM_GUEST_TOOLCHAIN").unwrap_or_else(|_| "zkm".to_string());
env::set_var("RUSTUP_TOOLCHAIN", toolchain);
env::remove_var("RUSTC");
env::remove_var("RUSTC_WRAPPER");
}

fn main() {
select_guest_toolchain();
let mut args: BuildArgs = Default::default();
args.rustflags.push("llvm-args=--pre-RA-sched=list-ilp".to_string());
build_program_with_args("../guest", args);
Expand Down
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)
}
1 change: 0 additions & 1 deletion crates/executor/host/src/bins/persist_report_hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ impl PersistExecutionReport {
headers.push("recover_senders_cycles_count".to_string());
headers.push("block_execution_cycles_count".to_string());
headers.push("block_validation_cycles_count".to_string());
headers.push("accrue_logs_bloom_cycles_count".to_string());
headers.push("state_root_computation_cycles_count".to_string());
headers.push("syscalls_count".to_string());
headers.push("prover_gas".to_string());
Expand Down
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
Loading
Loading