diff --git a/guests/eth-block/src/main.rs b/guests/eth-block/src/main.rs index eccfe9d1..8df2b7a7 100644 --- a/guests/eth-block/src/main.rs +++ b/guests/eth-block/src/main.rs @@ -19,6 +19,7 @@ use zeth_lib::{ builder::{BlockBuilderStrategy, EthereumStrategy}, consts::ETH_MAINNET_CHAIN_SPEC, }; +use zeth_lib::output::BlockBuildOutput; risc0_zkvm::guest::entry!(main); @@ -26,10 +27,16 @@ pub fn main() { // Read the input previous block and transaction data let input = env::read(); // Build the resulting block - let (header, state) = EthereumStrategy::build_from(Ð_MAINNET_CHAIN_SPEC, input) + let mut output = EthereumStrategy::build_from(Ð_MAINNET_CHAIN_SPEC, input) .expect("Failed to build the resulting block"); - // Output the resulting block's hash to the journal - env::commit(&header.hash()); + // Abridge successful construction results + if let BlockBuildOutput::SUCCESS { new_block_hash, new_block_head, new_block_state } = &mut output { + let trie_root = core::mem::replace(new_block_state, new_block_head.state_root.into()); + // Leak memory, save cycles + core::mem::forget(trie_root); + } + // Output the construction result + env::commit(&output); // Leak memory, save cycles - core::mem::forget((header, state)); + core::mem::forget(output); } diff --git a/guests/op-block/src/main.rs b/guests/op-block/src/main.rs index 623bd611..623b2d22 100644 --- a/guests/op-block/src/main.rs +++ b/guests/op-block/src/main.rs @@ -19,6 +19,7 @@ use zeth_lib::{ builder::{BlockBuilderStrategy, OptimismStrategy}, consts::OP_MAINNET_CHAIN_SPEC, }; +use zeth_lib::output::BlockBuildOutput; risc0_zkvm::guest::entry!(main); @@ -26,10 +27,16 @@ pub fn main() { // Read the input previous block and transaction data let input = env::read(); // Build the resulting block - let (header, state) = OptimismStrategy::build_from(&OP_MAINNET_CHAIN_SPEC, input) + let output = OptimismStrategy::build_from(&OP_MAINNET_CHAIN_SPEC, input) .expect("Failed to build the resulting block"); - // Output the resulting block's hash to the journal - env::commit(&header.hash()); + // Abridge successful construction results + if let BlockBuildOutput::SUCCESS { new_block_hash, new_block_head, new_block_state } = &mut output { + let trie_root = core::mem::replace(new_block_state, new_block_head.state_root.into()); + // Leak memory, save cycles + core::mem::forget(trie_root); + } + // Output the construction result + env::commit(&output); // Leak memory, save cycles - core::mem::forget((header, state)); + core::mem::forget(output); } diff --git a/host/src/operations/chains.rs b/host/src/operations/chains.rs index 584971a9..239d3467 100644 --- a/host/src/operations/chains.rs +++ b/host/src/operations/chains.rs @@ -16,13 +16,14 @@ use std::fmt::Debug; use anyhow::Context; use ethers_core::types::Transaction as EthersTransaction; -use log::info; +use log::{info, warn}; use serde::{Deserialize, Serialize}; use zeth_lib::{ builder::BlockBuilderStrategy, consts::ChainSpec, host::{preflight::Preflight, verify::Verifier}, - input::Input, + input::BlockBuildInput, + output::BlockBuildOutput, }; use crate::{ @@ -61,20 +62,30 @@ where let preflight_data = preflight_result.context("preflight failed")?; // Create the guest input from [Init] - let input: Input = preflight_data + let input: BlockBuildInput = preflight_data .clone() .try_into() .context("invalid preflight data")?; // Verify that the transactions run correctly info!("Running from memory ..."); - let (header, state_trie) = - N::build_from(&chain_spec, input.clone()).context("Error while building block")?; + let output = N::build_from(&chain_spec, input.clone()).context("Error while building block")?; - info!("Verifying final state using provider data ..."); - preflight_data.verify_block(&header, &state_trie)?; + match &output { + BlockBuildOutput::SUCCESS { + new_block_hash, + new_block_head, + new_block_state, + } => { + info!("Verifying final state using provider data ..."); + preflight_data.verify_block(new_block_head, new_block_state)?; - info!("Final block hash derived successfully. {}", header.hash()); + info!("Final block hash derived successfully. {}", new_block_hash); + } + BlockBuildOutput::FAILURE { .. } => { + warn!("Proving bad block construction!") + } + } match &cli { Cli::Build(..) => {} @@ -84,7 +95,7 @@ where run_args.exec_args.local_exec, run_args.exec_args.profile, guest_elf, - &preflight_data.header.hash(), + &output, file_reference, ); } @@ -93,7 +104,7 @@ where &cli, &input, guest_elf, - &preflight_data.header.hash(), + &output, vec![], file_reference, None, diff --git a/host/src/operations/rollups.rs b/host/src/operations/rollups.rs index a142f6b4..95fc37f3 100644 --- a/host/src/operations/rollups.rs +++ b/host/src/operations/rollups.rs @@ -21,7 +21,7 @@ use zeth_lib::{ builder::OptimismStrategy, consts::{Network, OP_MAINNET_CHAIN_SPEC}, host::{preflight::Preflight, rpc_db::RpcDb}, - input::Input, + input::BlockBuildInput, optimism::{ batcher_db::BatcherDb, composition::{ComposeInput, ComposeInputOperation, ComposeOutputOperation}, @@ -45,7 +45,7 @@ async fn fetch_op_blocks( core_args: &CoreArgs, block_number: u64, block_count: u64, -) -> anyhow::Result>> { +) -> anyhow::Result>> { let mut op_blocks = vec![]; for i in 0..block_count { let block_number = block_number + i; diff --git a/lib/src/builder/mod.rs b/lib/src/builder/mod.rs index 7763b803..c65b1be1 100644 --- a/lib/src/builder/mod.rs +++ b/lib/src/builder/mod.rs @@ -28,8 +28,9 @@ use crate::{ prepare::{EthHeaderPrepStrategy, HeaderPrepStrategy}, }, consts::ChainSpec, - input::Input, + input::BlockBuildInput, mem_db::MemDb, + output::BlockBuildOutput, }; mod execute; @@ -41,7 +42,7 @@ mod prepare; #[derive(Clone, Debug)] pub struct BlockBuilder<'a, D, E: TxEssence> { pub(crate) chain_spec: &'a ChainSpec, - pub(crate) input: Input, + pub(crate) input: BlockBuildInput, pub(crate) db: Option, pub(crate) header: Option
, } @@ -53,7 +54,7 @@ where E: TxEssence, { /// Creates a new block builder. - pub fn new(chain_spec: &ChainSpec, input: Input) -> BlockBuilder<'_, D, E> { + pub fn new(chain_spec: &ChainSpec, input: BlockBuildInput) -> BlockBuilder<'_, D, E> { BlockBuilder { chain_spec, db: None, @@ -111,13 +112,41 @@ pub trait BlockBuilderStrategy { /// Builds a block from the given input. fn build_from( chain_spec: &ChainSpec, - input: Input, - ) -> Result<(Header, MptNode)> { - BlockBuilder::::new(chain_spec, input) - .initialize_database::()? - .prepare_header::()? - .execute_transactions::()? - .finalize::() + input: BlockBuildInput, + ) -> Result { + // Database initialization failure does not mean the block is faulty + let input_hash = input.partial_hash(); + let initialized = BlockBuilder::::new(chain_spec, input) + .initialize_database::()?; + + // Header validation errors mean a faulty block + let prepared = match initialized.prepare_header::() { + Ok(builder) => builder, + Err(_) => { + return Ok(BlockBuildOutput::FAILURE { + bad_input_hash: input_hash.into(), + }) + } + }; + + // Transaction execution errors mean a faulty block + let executed = match prepared.execute_transactions::() { + Ok(builder) => builder, + Err(_) => { + return Ok(BlockBuildOutput::FAILURE { + bad_input_hash: input_hash.into(), + }) + } + }; + + // Finalization does not indicate a faulty block + let (header, state) = executed.finalize::()?; + + Ok(BlockBuildOutput::SUCCESS { + new_block_hash: header.hash(), + new_block_head: header, + new_block_state: state, + }) } } diff --git a/lib/src/host/preflight.rs b/lib/src/host/preflight.rs index 91c1aff1..0446f1d6 100644 --- a/lib/src/host/preflight.rs +++ b/lib/src/host/preflight.rs @@ -38,7 +38,7 @@ use crate::{ provider::{new_provider, BlockQuery}, provider_db::ProviderDb, }, - input::{Input, StorageEntry}, + input::{BlockBuildInput, StorageEntry}, mem_db::MemDb, }; @@ -149,7 +149,7 @@ where fn new_preflight_input( block: EthersBlock, parent_header: Header, -) -> Result> +) -> Result> where E: TxEssence + TryFrom, >::Error: Debug, @@ -176,7 +176,7 @@ where }) .collect::, _>>()?; - let input = Input { + let input = BlockBuildInput { beneficiary: from_ethers_h160(block.author.context("author missing")?), gas_limit: from_ethers_u256(block.gas_limit), timestamp: from_ethers_u256(block.timestamp), @@ -193,12 +193,12 @@ where Ok(input) } -/// Converts the [Data] returned by the [Preflight] into [Input] required by the +/// Converts the [Data] returned by the [Preflight] into [BlockBuildInput] required by the /// [BlockBuilder]. -impl TryFrom> for Input { +impl TryFrom> for BlockBuildInput { type Error = anyhow::Error; - fn try_from(data: Data) -> Result> { + fn try_from(data: Data) -> Result> { // collect the code from each account let mut contracts = HashSet::new(); for account in data.db.accounts.values() { @@ -225,7 +225,7 @@ impl TryFrom> for Input { ); // Create the block builder input - let input = Input { + let input = BlockBuildInput { parent_header: data.parent_header, beneficiary: data.header.beneficiary, gas_limit: data.header.gas_limit, diff --git a/lib/src/input.rs b/lib/src/input.rs index 7984e85b..d2ce769c 100644 --- a/lib/src/input.rs +++ b/lib/src/input.rs @@ -12,14 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. +use ethers_core::k256::sha2::{Digest, Sha256}; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; use zeth_primitives::{ block::Header, transactions::{Transaction, TxEssence}, + tree::Hash, trie::MptNode, withdrawal::Withdrawal, - Address, Bytes, B256, U256, + Address, Bytes, RlpBytes, B256, U256, }; /// Represents the state of an account's storage. @@ -29,7 +31,7 @@ pub type StorageEntry = (MptNode, Vec); /// External block input. #[derive(Debug, Clone, Default, Deserialize, Serialize)] -pub struct Input { +pub struct BlockBuildInput { /// Previous block header pub parent_header: Header, /// Address to which all priority fees in this block are transferred. @@ -56,6 +58,24 @@ pub struct Input { pub ancestor_headers: Vec
, } +impl BlockBuildInput { + pub fn partial_hash(&self) -> Hash { + let mut hasher = Sha256::new(); + + hasher.update(self.parent_header.to_rlp()); + hasher.update(self.beneficiary.0); + hasher.update(self.gas_limit.as_le_slice()); + hasher.update(self.timestamp.as_le_slice()); + hasher.update(self.extra_data.as_ref()); + hasher.update(self.mix_hash.0); + // todo: use precalculated trie root hashes if available + hasher.update(self.transactions.to_rlp()); + hasher.update(self.withdrawals.to_rlp()); + + hasher.finalize().into() + } +} + #[cfg(test)] mod tests { use zeth_primitives::transactions::ethereum::EthereumTxEssence; @@ -64,7 +84,7 @@ mod tests { #[test] fn input_serde_roundtrip() { - let input = Input:: { + let input = BlockBuildInput:: { parent_header: Default::default(), beneficiary: Default::default(), gas_limit: Default::default(), @@ -78,7 +98,7 @@ mod tests { contracts: vec![], ancestor_headers: vec![], }; - let _: Input = + let _: BlockBuildInput = bincode::deserialize(&bincode::serialize(&input).unwrap()).unwrap(); } } diff --git a/lib/src/lib.rs b/lib/src/lib.rs index 633ebec8..96c57268 100644 --- a/lib/src/lib.rs +++ b/lib/src/lib.rs @@ -22,6 +22,7 @@ pub mod consts; pub mod input; pub mod mem_db; pub mod optimism; +pub mod output; mod utils; diff --git a/lib/src/optimism/mod.rs b/lib/src/optimism/mod.rs index aa773261..21d741f8 100644 --- a/lib/src/optimism/mod.rs +++ b/lib/src/optimism/mod.rs @@ -13,7 +13,6 @@ // limitations under the License. use core::iter::once; -use std::mem; use alloy_sol_types::{sol, SolInterface}; use anyhow::{bail, ensure, Context, Result}; @@ -35,9 +34,8 @@ use zeth_primitives::{ }; use crate::{ - builder::{BlockBuilderStrategy, OptimismStrategy}, - consts::{ONE, OP_MAINNET_CHAIN_SPEC}, - input::Input, + consts::ONE, + input::BlockBuildInput, optimism::{ batcher::{Batcher, BlockId, L2BlockInfo}, batcher_db::BatcherDb, @@ -85,7 +83,7 @@ pub struct DeriveInput { /// Block count for the operation. pub op_derive_block_count: u64, /// Block building data for execution - pub op_blocks: Vec>, + pub op_blocks: Vec>, } /// Represents the output of the derivation process. @@ -288,7 +286,7 @@ impl DeriveMachine { tx_trie.insert(&trie_key, tx)?; } - let _new_op_head_input = Input { + let _new_op_head_input = BlockBuildInput { parent_header: Default::default(), beneficiary: self.op_batcher.config.sequencer_fee_vault, gas_limit: self.op_batcher.config.system_config.gas_limit, @@ -304,6 +302,10 @@ impl DeriveMachine { ancestor_headers: vec![], }; + // in guest: ask for receipt about this (without RLP decoding) + // on host: go run the preflight and queue up the input data (using RLP decoded + // transactions) + // obtain verified op block header let new_op_head = { // load the next op block header @@ -382,25 +384,25 @@ impl DeriveMachine { } } - // Execute transactions to verify valid state transitions - let op_blocks = mem::take(&mut self.derive_input.op_blocks); - if op_blocks.len() != derived_op_blocks.len() { - bail!( - "Mismatch between number of input op blocks {} and derived block count {}", - op_blocks.len(), - derived_op_blocks.len() - ); - } - for (i, input) in op_blocks.into_iter().enumerate() { - let (header, _) = OptimismStrategy::build_from(&OP_MAINNET_CHAIN_SPEC, input)?; - if header.hash() != derived_op_blocks[i].1 { - bail!( - "Mismatch between built block {} and derived block {}.", - header.number, - &derived_op_blocks[i].0 - ) - } - } + // // Execute transactions to verify valid state transitions + // let op_blocks = mem::take(&mut self.derive_input.op_blocks); + // if op_blocks.len() != derived_op_blocks.len() { + // bail!( + // "Mismatch between number of input op blocks {} and derived block count {}", + // op_blocks.len(), + // derived_op_blocks.len() + // ); + // } + // for (i, input) in op_blocks.into_iter().enumerate() { + // let (header, _) = OptimismStrategy::build_from(&OP_MAINNET_CHAIN_SPEC, input)?; + // if header.hash() != derived_op_blocks[i].1 { + // bail!( + // "Mismatch between built block {} and derived block {}.", + // header.number, + // &derived_op_blocks[i].0 + // ) + // } + // } Ok(DeriveOutput { eth_tail: ( diff --git a/lib/src/output.rs b/lib/src/output.rs new file mode 100644 index 00000000..fe54068e --- /dev/null +++ b/lib/src/output.rs @@ -0,0 +1,38 @@ +// Copyright 2024 RISC Zero, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use serde::{Deserialize, Serialize}; +use zeth_primitives::{block::Header, trie::MptNode, B256}; + +/// Output of block execution +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub enum BlockBuildOutput { + SUCCESS { + new_block_hash: B256, + new_block_head: Header, + new_block_state: MptNode, + }, + FAILURE { + bad_input_hash: B256, + }, +} + +impl BlockBuildOutput { + pub fn success(&self) -> bool { + match self { + BlockBuildOutput::SUCCESS { .. } => true, + BlockBuildOutput::FAILURE { .. } => false, + } + } +} diff --git a/testing/ef-tests/src/lib.rs b/testing/ef-tests/src/lib.rs index 4772b3a8..3cb47a8e 100644 --- a/testing/ef-tests/src/lib.rs +++ b/testing/ef-tests/src/lib.rs @@ -26,7 +26,7 @@ use zeth_lib::{ provider::{AccountQuery, BlockQuery, ProofQuery, Provider, StorageQuery}, provider_db::ProviderDb, }, - input::Input, + input::BlockBuildInput, mem_db::{AccountState, DbAccount, MemDb}, }; use zeth_primitives::{ @@ -315,7 +315,7 @@ pub fn create_input( transactions: Vec, withdrawals: Vec, state: TestState, -) -> Input { +) -> BlockBuildInput { // create the provider DB let provider_db = ProviderDb::new( Box::new(TestProvider { @@ -330,7 +330,7 @@ pub fn create_input( .into_iter() .map(EthereumTransaction::from) .collect(); - let input = Input { + let input = BlockBuildInput { beneficiary: header.beneficiary, gas_limit: header.gas_limit, timestamp: header.timestamp, diff --git a/testing/ef-tests/testguest/src/main.rs b/testing/ef-tests/testguest/src/main.rs index 6abd32f6..ac2552c4 100644 --- a/testing/ef-tests/testguest/src/main.rs +++ b/testing/ef-tests/testguest/src/main.rs @@ -19,6 +19,7 @@ use zeth_lib::{ builder::{BlockBuilderStrategy, EthereumStrategy}, consts::ChainSpec, }; +use zeth_lib::output::BlockBuildOutput; risc0_zkvm::guest::entry!(main); @@ -28,10 +29,16 @@ pub fn main() { // Read the input previous block and transaction data let input = env::read(); // Build the resulting block - let (header, state) = EthereumStrategy::build_from(&chain_spec, input) + let output = EthereumStrategy::build_from(&chain_spec, input) .expect("Failed to build the resulting block"); - // Output the resulting block's hash to the journal - env::commit(&header.hash()); + // Abridge successful construction results + if let BlockBuildOutput::SUCCESS { new_block_hash, new_block_head, new_block_state } = &mut output { + let trie_root = core::mem::replace(new_block_state, new_block_head.state_root.into()); + // Leak memory, save cycles + core::mem::forget(trie_root); + } + // Output the construction result + env::commit(&output); // Leak memory, save cycles - core::mem::forget((header, state)); + core::mem::forget(output); } diff --git a/testing/ef-tests/tests/evm.rs b/testing/ef-tests/tests/evm.rs index 4bd686bd..d068aec7 100644 --- a/testing/ef-tests/tests/evm.rs +++ b/testing/ef-tests/tests/evm.rs @@ -17,7 +17,10 @@ use std::path::PathBuf; use rstest::rstest; -use zeth_lib::builder::{BlockBuilderStrategy, EthereumStrategy}; +use zeth_lib::{ + builder::{BlockBuilderStrategy, EthereumStrategy}, + output::BlockBuildOutput, +}; use zeth_primitives::{block::Header, trie::StateAccount}; use zeth_testeth::{ create_input, ethers, @@ -70,14 +73,23 @@ fn evm( post_state, ); - let (header, state) = EthereumStrategy::build_from(&chain_spec, input).unwrap(); + let output = EthereumStrategy::build_from(&chain_spec, input).unwrap(); + + let BlockBuildOutput::SUCCESS { + new_block_hash, + new_block_head, + new_block_state, + } = output + else { + panic!("Invalid block") + }; if let Some(post) = json.post { let (exp_state, _) = ethers::build_tries(&post); println!("diffing state trie:"); for diff in diff::slice( - &state.debug_rlp::(), + &new_block_state.debug_rlp::(), &exp_state.debug_rlp::(), ) { match diff { @@ -86,11 +98,11 @@ fn evm( diff::Result::Both(l, _) => println!(" {}", l), } } - assert_eq!(state.hash(), exp_state.hash()); + assert_eq!(new_block_state, exp_state.hash()); } // the headers should match - assert_eq!(header, expected_header); - assert_eq!(header.hash(), expected_header.hash()); + assert_eq!(new_block_head, expected_header); + assert_eq!(new_block_hash, expected_header.hash()); } }