diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 5a66f99e0a..ecd69d25f0 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -1,8 +1,6 @@ name: Integration Tests on: - pull_request: - types: [synchronize, opened, reopened, ready_for_review] push: branches: - main @@ -30,11 +28,16 @@ jobs: wget -q https://github.com/ethereum/solidity/releases/download/v0.8.10/solc-static-linux -O $HOME/bin/solc chmod u+x "$HOME/bin/solc" solc --version - # Run an initial build in a sepparate step to split the build time from execution time - - name: Build gendata bin + # Run an initial build in a separate step to split the build time from execution time + - name: Build bins run: cargo build --bin gen_blockchain_data + - name: Build tests + run: for testname in rpc circuit_input_builder circuits; do cargo test --profile release --test $testname --features $testname --no-run; done - run: ./run.sh --steps "setup" - run: ./run.sh --steps "gendata" - run: ./run.sh --steps "tests" --tests "rpc" - run: ./run.sh --steps "tests" --tests "circuit_input_builder" + # Uncomment once the evm and state circuits tests pass for the block + # where Greeter.sol is deployed. + # - run: ./run.sh --steps "tests" --tests "circuits" - run: ./run.sh --steps "cleanup" diff --git a/Cargo.lock b/Cargo.lock index 364a22140d..f875410767 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1952,13 +1952,16 @@ dependencies = [ "bus-mapping", "env_logger", "ethers", + "halo2", "lazy_static", "log", + "pairing_bn256", "pretty_assertions", "serde", "serde_json", "tokio", "url", + "zkevm-circuits", ] [[package]] diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index d9fcae53a1..45e9ae5867 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -12,12 +12,15 @@ use crate::exec_trace::OperationRef; use crate::geth_errors::*; use crate::operation::container::OperationContainer; use crate::operation::{MemoryOp, Op, Operation, StackOp, RW}; -use crate::state_db::{CodeDB, StateDB}; +use crate::state_db::{self, CodeDB, StateDB}; use crate::Error; use core::fmt::Debug; use ethers_core::utils::{get_contract_address, get_create2_address}; use std::collections::{hash_map::Entry, HashMap, HashSet}; +use crate::rpc::GethClient; +use ethers_providers::JsonRpcClient; + /// Out of Gas errors by opcode #[derive(Debug, PartialEq)] pub enum OogError { @@ -248,19 +251,19 @@ impl TryFrom for CallKind { #[derive(Debug)] pub struct Call { /// Unique call identifier within the Block. - call_id: usize, + pub call_id: usize, /// Type of call - kind: CallKind, + pub kind: CallKind, /// This call is being executed without write access (STATIC) - is_static: bool, + pub is_static: bool, /// This call generated implicity by a Transaction. - is_root: bool, + pub is_root: bool, /// Address where this call is being executed pub address: Address, /// Code Source - code_source: CodeSource, + pub code_source: CodeSource, /// Code Hash - code_hash: Hash, + pub code_hash: Hash, } impl Call { @@ -1193,6 +1196,164 @@ pub fn gen_state_access_trace( Ok(accs) } +type EthBlock = eth_types::Block; + +/// Struct that wraps a GethClient and contains methods to perform all the steps +/// necessary to generate the circuit inputs for a block by querying geth for +/// the necessary information and using the CircuitInputBuilder. +pub struct BuilderClient { + cli: GethClient

, + constants: ChainConstants, +} + +impl BuilderClient

{ + /// Create a new BuilderClient + pub async fn new(client: GethClient

) -> Result { + let constants = ChainConstants { + coinbase: client.get_coinbase().await?, + chain_id: client.get_chain_id().await?, + }; + Ok(Self { + cli: client, + constants, + }) + } + + /// Step 1. Query geth for Block, Txs and TxExecTraces + pub async fn get_block( + &self, + block_num: u64, + ) -> Result<(EthBlock, Vec), Error> { + let eth_block = self.cli.get_block_by_number(block_num.into()).await?; + let geth_traces = + self.cli.trace_block_by_number(block_num.into()).await?; + Ok((eth_block, geth_traces)) + } + + /// Step 2. Get State Accesses from TxExecTraces + pub fn get_state_accesses( + &self, + eth_block: &EthBlock, + geth_traces: &[eth_types::GethExecTrace], + ) -> Result { + let mut block_access_trace = Vec::new(); + for (tx_index, tx) in eth_block.transactions.iter().enumerate() { + let geth_trace = &geth_traces[tx_index]; + let tx_access_trace = + gen_state_access_trace(eth_block, tx, geth_trace)?; + block_access_trace.extend(tx_access_trace); + } + + Ok(AccessSet::from(block_access_trace)) + } + + /// Step 3. Query geth for all accounts, storage keys, and codes from + /// Accesses + pub async fn get_state( + &self, + block_num: u64, + access_set: AccessSet, + ) -> Result< + ( + Vec, + HashMap>, + ), + Error, + > { + let mut proofs = Vec::new(); + for (address, key_set) in access_set.state { + let mut keys: Vec = key_set.iter().cloned().collect(); + keys.sort(); + let proof = self + .cli + .get_proof(address, keys, (block_num - 1).into()) + .await + .unwrap(); + proofs.push(proof); + } + let mut codes: HashMap> = HashMap::new(); + for address in access_set.code { + let code = self + .cli + .get_code(address, (block_num - 1).into()) + .await + .unwrap(); + codes.insert(address, code); + } + Ok((proofs, codes)) + } + + /// Step 4. Build a partial StateDB from step 3 + pub fn build_state_code_db( + &self, + proofs: Vec, + codes: HashMap>, + ) -> (StateDB, CodeDB) { + let mut sdb = StateDB::new(); + for proof in proofs { + let mut storage = HashMap::new(); + for storage_proof in proof.storage_proof { + storage.insert(storage_proof.key, storage_proof.value); + } + sdb.set_account( + &proof.address, + state_db::Account { + nonce: proof.nonce, + balance: proof.balance, + storage, + code_hash: proof.code_hash, + }, + ) + } + + let mut code_db = CodeDB::new(); + for (_address, code) in codes { + code_db.insert(code.clone()); + } + (sdb, code_db) + } + + /// Step 5. For each step in TxExecTraces, gen the associated ops and state + /// circuit inputs + pub fn gen_inputs_from_state( + &self, + sdb: StateDB, + code_db: CodeDB, + eth_block: &EthBlock, + geth_traces: &[eth_types::GethExecTrace], + ) -> Result { + let mut builder = CircuitInputBuilder::new( + sdb, + code_db, + eth_block, + self.constants.clone(), + ); + for (tx_index, tx) in eth_block.transactions.iter().enumerate() { + let geth_trace = &geth_traces[tx_index]; + builder.handle_tx(tx, geth_trace)?; + } + Ok(builder) + } + + /// Perform all the steps to generate the circuit inputs + pub async fn gen_inputs( + &self, + block_num: u64, + ) -> Result { + let (eth_block, geth_traces) = self.get_block(block_num).await?; + let access_set = self.get_state_accesses(ð_block, &geth_traces)?; + let (proofs, codes) = self.get_state(block_num, access_set).await?; + let (state_db, code_db) = self.build_state_code_db(proofs, codes); + let builder = self.gen_inputs_from_state( + state_db, + code_db, + ð_block, + &geth_traces, + )?; + Ok(builder) + } +} + #[cfg(test)] mod tracer_tests { use super::*; diff --git a/bus-mapping/src/eth_types.rs b/bus-mapping/src/eth_types.rs index 72e27e459a..4424b59215 100644 --- a/bus-mapping/src/eth_types.rs +++ b/bus-mapping/src/eth_types.rs @@ -188,7 +188,7 @@ struct GethExecStepInternal { pc: ProgramCounter, op: OpcodeId, gas: Gas, - #[serde(alias = "gasCost")] + #[serde(rename = "gasCost")] gas_cost: GasCost, depth: u16, error: Option, @@ -310,7 +310,7 @@ pub struct GethExecTraceInternal { pub gas: Gas, pub failed: bool, // return_value is a hex encoded byte array - #[serde(alias = "structLogs")] + #[serde(rename = "structLogs")] pub struct_logs: Vec, } diff --git a/bus-mapping/src/external_tracer.rs b/bus-mapping/src/external_tracer.rs index 208ad8006e..4c9068e1af 100644 --- a/bus-mapping/src/external_tracer.rs +++ b/bus-mapping/src/external_tracer.rs @@ -150,6 +150,6 @@ mod trace_test { let block = mock::BlockData::new_single_tx_trace_code_at_start(&code).unwrap(); - assert_eq!(block.geth_trace.struct_logs[2].memory.0.len(), 0) + assert_eq!(block.geth_trace.struct_logs[2].memory.0.len(), 0); } } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index f7a44ebfdd..f55ff89665 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -166,7 +166,7 @@ impl fmt::Debug for StackOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("StackOp { ")?; f.write_fmt(format_args!( - "{:?}, addr: {:?}, val: {:?}", + "{:?}, addr: {:?}, val: 0x{:x}", self.rw, self.address, self.value ))?; f.write_str(" }") @@ -250,7 +250,7 @@ impl fmt::Debug for StorageOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("StorageOp { ")?; f.write_fmt(format_args!( - "{:?}, addr: {:?}, key: {:?}, val_prev: {:?}, val: {:?}", + "{:?}, addr: {:?}, key: {:?}, val_prev: 0x{:x}, val: 0x{:x}", self.rw, self.address, self.key, self.value_prev, self.value, ))?; f.write_str(" }") @@ -389,7 +389,7 @@ impl fmt::Debug for TxAccessListAccountStorageOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("TxAccessListAccountStorageOp { ")?; f.write_fmt(format_args!( - "tx_id: {:?}, addr: {:?}, key: {:?}, val_prev: {:?}, val: {:?}", + "tx_id: {:?}, addr: {:?}, key: 0x{:x}, val_prev: {:?}, val: {:?}", self.tx_id, self.address, self.key, self.value_prev, self.value ))?; f.write_str(" }") @@ -437,7 +437,7 @@ impl fmt::Debug for TxRefundOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("TxRefundOp { ")?; f.write_fmt(format_args!( - "{:?} tx_id: {:?}, val_prev: {:?}, val: {:?}", + "{:?} tx_id: {:?}, val_prev: 0x{:x}, val: 0x{:x}", self.rw, self.tx_id, self.value_prev, self.value ))?; f.write_str(" }") @@ -495,7 +495,7 @@ impl fmt::Debug for AccountOp { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("AccountOp { ")?; f.write_fmt(format_args!( - "{:?}, addr: {:?}, field: {:?}, val_prev: {:?}, val: {:?}", + "{:?}, addr: {:?}, field: {:?}, val_prev: 0x{:x}, val: 0x{:x}", self.rw, self.address, self.field, self.value_prev, self.value ))?; f.write_str(" }") diff --git a/bus-mapping/src/rpc.rs b/bus-mapping/src/rpc.rs index 53645e7aed..967bd744f4 100644 --- a/bus-mapping/src/rpc.rs +++ b/bus-mapping/src/rpc.rs @@ -8,6 +8,7 @@ use crate::eth_types::{ use crate::Error; pub use ethers_core::types::BlockNumber; use ethers_providers::JsonRpcClient; +use serde::Serialize; /// Serialize a type. /// @@ -18,6 +19,34 @@ pub fn serialize(t: &T) -> serde_json::Value { serde_json::to_value(t).expect("Types never fail to serialize.") } +#[derive(Serialize)] +#[doc(hidden)] +pub(crate) struct GethLoggerConfig { + /// enable memory capture + #[serde(rename = "EnableMemory")] + enable_memory: bool, + /// disable stack capture + #[serde(rename = "DisableStack")] + disable_stack: bool, + /// disable storage capture + #[serde(rename = "DisableStorage")] + disable_storage: bool, + /// enable return data capture + #[serde(rename = "EnableReturnData")] + enable_return_data: bool, +} + +impl Default for GethLoggerConfig { + fn default() -> Self { + Self { + enable_memory: true, + disable_stack: false, + disable_storage: false, + enable_return_data: true, + } + } +} + /// Placeholder structure designed to contain the methods that the BusMapping /// needs in order to enable Geth queries. pub struct GethClient(pub P); @@ -83,9 +112,10 @@ impl GethClient

{ hash: Hash, ) -> Result, Error> { let hash = serialize(&hash); + let cfg = serialize(&GethLoggerConfig::default()); let resp: ResultGethExecTraces = self .0 - .request("debug_traceBlockByHash", [hash]) + .request("debug_traceBlockByHash", [hash, cfg]) .await .map_err(|e| Error::JSONRpcError(e.into()))?; Ok(resp.0.into_iter().map(|step| step.result).collect()) @@ -99,9 +129,10 @@ impl GethClient

{ block_num: BlockNumber, ) -> Result, Error> { let num = serialize(&block_num); + let cfg = serialize(&GethLoggerConfig::default()); let resp: ResultGethExecTraces = self .0 - .request("debug_traceBlockByNumber", [num]) + .request("debug_traceBlockByNumber", [num, cfg]) .await .map_err(|e| Error::JSONRpcError(e.into()))?; Ok(resp.0.into_iter().map(|step| step.result).collect()) diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index 0ed4dfa505..0c49646a8c 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -11,11 +11,14 @@ ethers = "0.6" serde_json = "1.0.66" serde = {version = "1.0.130", features = ["derive"] } bus-mapping = { path = "../bus-mapping"} +zkevm-circuits = { path = "../zkevm-circuits"} tokio = { version = "1.13", features = ["macros", "rt-multi-thread"] } url = "2.2.2" pretty_assertions = "1.0.0" log = "0.4.14" env_logger = "0.9" +halo2 = { git = "https://github.com/appliedzkp/halo2.git", rev = "b78c39cacc1c79d287032f1b5f94beb661b3fb42" } +pairing = { git = 'https://github.com/appliedzkp/pairing', package = "pairing_bn256" } [dev-dependencies] pretty_assertions = "1.0.0" @@ -24,3 +27,4 @@ pretty_assertions = "1.0.0" default = [] rpc = [] circuit_input_builder = [] +circuits = [] diff --git a/integration-tests/run.sh b/integration-tests/run.sh index 996379dc92..592c790ac9 100755 --- a/integration-tests/run.sh +++ b/integration-tests/run.sh @@ -3,7 +3,7 @@ set -e ARG_DEFAULT_SUDO= ARG_DEFAULT_STEPS="setup gendata tests cleanup" -ARG_DEFAULT_TESTS="rpc circuit_input_builder" +ARG_DEFAULT_TESTS="rpc circuit_input_builder circuits" usage() { cat >&2 << EOF @@ -97,7 +97,7 @@ fi if [ -n "$STEP_TESTS" ]; then for testname in $ARG_TESTS; do echo "+ Running test group $testname" - cargo test --test $testname --features $testname -- --nocapture + cargo test --profile release --test $testname --features $testname -- --nocapture done fi diff --git a/integration-tests/tests/circuit_input_builder.rs b/integration-tests/tests/circuit_input_builder.rs index 21ae189f3f..1feaceb3b2 100644 --- a/integration-tests/tests/circuit_input_builder.rs +++ b/integration-tests/tests/circuit_input_builder.rs @@ -1,16 +1,9 @@ #![cfg(feature = "circuit_input_builder")] -use bus_mapping::circuit_input_builder::{ - gen_state_access_trace, AccessSet, CircuitInputBuilder, -}; -use bus_mapping::eth_types::{Address, Word}; -use bus_mapping::state_db::{self, CodeDB, StateDB}; -use integration_tests::{ - get_chain_constants, get_client, log_init, GenDataOutput, -}; +use bus_mapping::circuit_input_builder::BuilderClient; +use integration_tests::{get_client, log_init, GenDataOutput}; use lazy_static::lazy_static; use log::trace; -use std::collections::HashMap; lazy_static! { pub static ref GEN_DATA: GenDataOutput = GenDataOutput::load(); @@ -24,86 +17,27 @@ async fn test_circuit_input_builder_block_a() { let (block_num, _address) = GEN_DATA.deployments.get("Greeter").unwrap(); let cli = get_client(); + let cli = BuilderClient::new(cli).await.unwrap(); + // 1. Query geth for Block, Txs and TxExecTraces - let eth_block = cli.get_block_by_number((*block_num).into()).await.unwrap(); - let geth_trace = cli - .trace_block_by_number((*block_num).into()) - .await - .unwrap(); - let tx_index = 0; + let (eth_block, geth_trace) = cli.get_block(*block_num).await.unwrap(); // 2. Get State Accesses from TxExecTraces - let access_trace = gen_state_access_trace( - ð_block, - ð_block.transactions[tx_index], - &geth_trace[tx_index], - ) - .unwrap(); - trace!("AccessTrace:"); - for access in &access_trace { - trace!("{:#?}", access); - } - - let access_set = AccessSet::from(access_trace); + let access_set = cli.get_state_accesses(ð_block, &geth_trace).unwrap(); trace!("AccessSet: {:#?}", access_set); // 3. Query geth for all accounts, storage keys, and codes from Accesses - let mut proofs = Vec::new(); - for (address, key_set) in access_set.state { - let mut keys: Vec = key_set.iter().cloned().collect(); - keys.sort(); - let proof = cli - .get_proof(address, keys, (*block_num - 1).into()) - .await - .unwrap(); - proofs.push(proof); - } - let mut codes: HashMap> = HashMap::new(); - for address in access_set.code { - let code = cli - .get_code(address, (*block_num - 1).into()) - .await - .unwrap(); - codes.insert(address, code); - } + let (proofs, codes) = cli.get_state(*block_num, access_set).await.unwrap(); // 4. Build a partial StateDB from step 3 - let mut sdb = StateDB::new(); - for proof in proofs { - let mut storage = HashMap::new(); - for storage_proof in proof.storage_proof { - storage.insert(storage_proof.key, storage_proof.value); - } - sdb.set_account( - &proof.address, - state_db::Account { - nonce: proof.nonce, - balance: proof.balance, - storage, - code_hash: proof.code_hash, - }, - ) - } - trace!("StateDB: {:#?}", sdb); - - let mut code_db = CodeDB::new(); - for (_address, code) in codes { - code_db.insert(code.clone()); - } - let constants = get_chain_constants().await; - let mut builder = - CircuitInputBuilder::new(sdb, code_db, ð_block, constants); + let (state_db, code_db) = cli.build_state_code_db(proofs, codes); + trace!("StateDB: {:#?}", state_db); // 5. For each step in TxExecTraces, gen the associated ops and state // circuit inputs - let block_geth_trace = cli - .trace_block_by_number((*block_num).into()) - .await + let builder = cli + .gen_inputs_from_state(state_db, code_db, ð_block, &geth_trace) .unwrap(); - for (tx_index, tx) in eth_block.transactions.iter().enumerate() { - let geth_trace = &block_geth_trace[tx_index]; - builder.handle_tx(tx, geth_trace).unwrap(); - } trace!("CircuitInputBuilder: {:#?}", builder); } diff --git a/integration-tests/tests/circuits.rs b/integration-tests/tests/circuits.rs new file mode 100644 index 0000000000..bb75e7298a --- /dev/null +++ b/integration-tests/tests/circuits.rs @@ -0,0 +1,342 @@ +#![cfg(feature = "circuits")] + +use bus_mapping::circuit_input_builder::BuilderClient; +use halo2::dev::MockProver; +use integration_tests::{get_client, log_init, GenDataOutput}; +use lazy_static::lazy_static; +use log::trace; +use zkevm_circuits::evm_circuit::witness::block_convert; +use zkevm_circuits::state_circuit::StateCircuit; + +lazy_static! { + pub static ref GEN_DATA: GenDataOutput = GenDataOutput::load(); +} + +/// This module contains a definition of a Circuit for the EVM Circuit that can +/// be used for testing. This is required because there's no public directly +/// usable EVM Circuit yet. The code in this module is copied from +/// `zkevm_circuits::evm_circuit::test` at `zkevm-circuits/src/evm_circuit.rs`. +mod test_evm_circuit { + use halo2::{ + arithmetic::FieldExt, + circuit::{Layouter, SimpleFloorPlanner}, + dev::{MockProver, VerifyFailure}, + plonk::*, + }; + use zkevm_circuits::evm_circuit::{ + param::STEP_HEIGHT, + table::FixedTableTag, + witness::{Block, Bytecode, Rw, Transaction}, + EvmCircuit, + }; + + #[derive(Clone)] + struct TestCircuitConfig { + tx_table: [Column; 4], + rw_table: [Column; 8], + bytecode_table: [Column; 4], + evm_circuit: EvmCircuit, + } + + impl TestCircuitConfig { + fn load_txs( + &self, + layouter: &mut impl Layouter, + txs: &[Transaction], + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "tx table", + |mut region| { + let mut offset = 0; + for column in self.rw_table { + region.assign_advice( + || "tx table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + for tx in txs.iter() { + for row in tx.table_assignments(randomness) { + for (column, value) in self.tx_table.iter().zip(row) + { + region.assign_advice( + || format!("tx table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } + + fn load_rws( + &self, + layouter: &mut impl Layouter, + rws: &[Rw], + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "rw table", + |mut region| { + let mut offset = 0; + for column in self.rw_table { + region.assign_advice( + || "rw table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + for rw in rws.iter() { + for (column, value) in self + .rw_table + .iter() + .zip(rw.table_assignment(randomness)) + { + region.assign_advice( + || format!("rw table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + Ok(()) + }, + ) + } + + fn load_bytecodes( + &self, + layouter: &mut impl Layouter, + bytecodes: &[Bytecode], + randomness: F, + ) -> Result<(), Error> { + layouter.assign_region( + || "bytecode table", + |mut region| { + let mut offset = 0; + for column in self.bytecode_table { + region.assign_advice( + || "bytecode table all-zero row", + column, + offset, + || Ok(F::zero()), + )?; + } + offset += 1; + + for bytecode in bytecodes.iter() { + for row in bytecode.table_assignments(randomness) { + for (column, value) in + self.bytecode_table.iter().zip(row) + { + region.assign_advice( + || format!("bytecode table row {}", offset), + *column, + offset, + || Ok(value), + )?; + } + offset += 1; + } + } + Ok(()) + }, + ) + } + } + + #[derive(Default)] + struct TestCircuit { + block: Block, + fixed_table_tags: Vec, + } + + impl TestCircuit { + fn new(block: Block, fixed_table_tags: Vec) -> Self { + Self { + block, + fixed_table_tags, + } + } + } + + impl Circuit for TestCircuit { + type Config = TestCircuitConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let tx_table = [(); 4].map(|_| meta.advice_column()); + let rw_table = [(); 8].map(|_| meta.advice_column()); + let bytecode_table = [(); 4].map(|_| meta.advice_column()); + let randomness = meta.instance_column(); + + Self::Config { + tx_table, + rw_table, + bytecode_table, + evm_circuit: EvmCircuit::configure( + meta, + randomness, + tx_table, + rw_table, + bytecode_table, + ), + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + config.evm_circuit.load_fixed_table( + &mut layouter, + self.fixed_table_tags.clone(), + )?; + config.load_txs( + &mut layouter, + &self.block.txs, + self.block.randomness, + )?; + config.load_rws( + &mut layouter, + &self.block.rws, + self.block.randomness, + )?; + config.load_bytecodes( + &mut layouter, + &self.block.bytecodes, + self.block.randomness, + )?; + config.evm_circuit.assign_block(&mut layouter, &self.block) + } + } + + pub fn run_test_circuit_complete_fixed_table( + block: Block, + ) -> Result<(), Vec> { + run_test_circuit(block, FixedTableTag::iterator().collect()) + } + + fn run_test_circuit( + block: Block, + fixed_table_tags: Vec, + ) -> Result<(), Vec> { + let log2_ceil = |n| { + u32::BITS - (n as u32).leading_zeros() - (n & (n - 1) == 0) as u32 + }; + + let k = log2_ceil( + 64 + fixed_table_tags + .iter() + .map(|tag| tag.build::().count()) + .sum::(), + ); + let k = k.max(log2_ceil( + 64 + block + .bytecodes + .iter() + .map(|bytecode| bytecode.bytes.len()) + .sum::(), + )); + + let randomness = + vec![ + block.randomness; + block.txs.iter().map(|tx| tx.steps.len()).sum::() + * STEP_HEIGHT + ]; + let circuit = TestCircuit::::new(block, fixed_table_tags); + + let prover = + MockProver::::run(k, &circuit, vec![randomness]).unwrap(); + prover.verify() + } +} + +#[tokio::test] +async fn test_evm_circuit_block_a() { + use test_evm_circuit::*; + + log_init(); + let (block_num, _address) = GEN_DATA.deployments.get("Greeter").unwrap(); + let cli = get_client(); + + let cli = BuilderClient::new(cli).await.unwrap(); + let builder = cli.gen_inputs(*block_num).await.unwrap(); + + // Generate evm_circuit proof + let code_hash = builder.block.txs()[0].calls()[0].code_hash; + let bytecode = builder + .code_db + .0 + .get(&code_hash) + .expect("code_hash not found"); + let block = block_convert(bytecode, &builder.block); + run_test_circuit_complete_fixed_table(block) + .expect("evm_circuit verification failed"); +} + +#[tokio::test] +async fn test_state_circuit_block_a() { + log_init(); + let (block_num, _address) = GEN_DATA.deployments.get("Greeter").unwrap(); + let cli = get_client(); + + let cli = BuilderClient::new(cli).await.unwrap(); + let builder = cli.gen_inputs(*block_num).await.unwrap(); + + // Generate state proof + let stack_ops = builder.block.container.sorted_stack(); + trace!("stack_ops: {:#?}", stack_ops); + let memory_ops = builder.block.container.sorted_memory(); + trace!("memory_ops: {:#?}", memory_ops); + let storage_ops = builder.block.container.sorted_storage(); + trace!("storage_ops: {:#?}", storage_ops); + + const DEGREE: usize = 16; + const MEMORY_ADDRESS_MAX: usize = 2000; + const STACK_ADDRESS_MAX: usize = 1300; + + const MEMORY_ROWS_MAX: usize = 1 << (DEGREE - 2); + const STACK_ROWS_MAX: usize = 1 << (DEGREE - 2); + const STORAGE_ROWS_MAX: usize = 1 << (DEGREE - 2); + const GLOBAL_COUNTER_MAX: usize = + MEMORY_ROWS_MAX + STACK_ROWS_MAX + STORAGE_ROWS_MAX; + + let circuit = StateCircuit::< + GLOBAL_COUNTER_MAX, + MEMORY_ROWS_MAX, + MEMORY_ADDRESS_MAX, + STACK_ROWS_MAX, + STACK_ADDRESS_MAX, + STORAGE_ROWS_MAX, + > { + memory_ops, + stack_ops, + storage_ops, + }; + + use pairing::bn256::Fr as Fp; + let prover = + MockProver::::run(DEGREE as u32, &circuit, vec![]).unwrap(); + prover.verify().expect("state_circuit verification failed"); +} diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 2d8f06a376..19a039e318 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -4,7 +4,7 @@ use halo2::{arithmetic::FieldExt, circuit::Layouter, plonk::*}; mod execution; -mod param; +pub mod param; mod step; mod util; diff --git a/zkevm-circuits/src/evm_circuit/execution/memory.rs b/zkevm-circuits/src/evm_circuit/execution/memory.rs index ba8ec74dc9..5ca654ab40 100644 --- a/zkevm-circuits/src/evm_circuit/execution/memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/memory.rs @@ -241,7 +241,7 @@ mod test { builder .handle_tx(&block_trace.eth_tx, &block_trace.geth_trace) .unwrap(); - let block = witness::block_convert(&bytecode, &builder.block); + let block = witness::block_convert(bytecode.code(), &builder.block); assert_eq!(run_test_circuit_incomplete_fixed_table(block), Ok(())); } diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 47b84d5ee2..5e59d06014 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -1,6 +1,7 @@ // Step dimension pub(crate) const STEP_WIDTH: usize = 32; -pub(crate) const STEP_HEIGHT: usize = 10; +/// Step height +pub const STEP_HEIGHT: usize = 10; pub(crate) const NUM_CELLS_STEP_STATE: usize = 10; // The number of bytes an u64 has. diff --git a/zkevm-circuits/src/evm_circuit/witness.rs b/zkevm-circuits/src/evm_circuit/witness.rs index 7c8a68e877..bde96d9385 100644 --- a/zkevm-circuits/src/evm_circuit/witness.rs +++ b/zkevm-circuits/src/evm_circuit/witness.rs @@ -425,11 +425,11 @@ fn tx_convert( } pub fn block_convert( - bytecode: &bus_mapping::bytecode::Bytecode, + bytecode: &[u8], b: &bus_mapping::circuit_input_builder::Block, ) -> Block { let randomness = Fp::rand(); - let bytecode = bytecode.into(); + let bytecode = Bytecode::new(bytecode.to_vec()); // here stack_ops/memory_ops/etc are merged into a single array // in EVM circuit, we need rwc-sorted ops @@ -490,5 +490,5 @@ pub fn build_block_from_trace_code_at_start( let mut builder = block.new_circuit_input_builder(); builder.handle_tx(&block.eth_tx, &block.geth_trace).unwrap(); - block_convert(bytecode, &builder.block) + block_convert(bytecode.code(), &builder.block) } diff --git a/zkevm-circuits/src/state_circuit/state.rs b/zkevm-circuits/src/state_circuit/state.rs index 7dd9f18847..2411656636 100644 --- a/zkevm-circuits/src/state_circuit/state.rs +++ b/zkevm-circuits/src/state_circuit/state.rs @@ -1162,9 +1162,12 @@ pub struct StateCircuit< const STACK_ADDRESS_MAX: usize, const STORAGE_ROWS_MAX: usize, > { - memory_ops: Vec>, - stack_ops: Vec>, - storage_ops: Vec>, + /// Memory Operations + pub memory_ops: Vec>, + /// Stack Operations + pub stack_ops: Vec>, + /// Storage Operations + pub storage_ops: Vec>, } impl<