diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index f7e08c1131..6861a870ae 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -42,6 +42,43 @@ use std::{ pub use transaction::{Transaction, TransactionContext}; pub use withdrawal::{Withdrawal, WithdrawalContext}; +/// Runtime Config +/// +/// Default to mainnet block +#[derive(Debug, Clone, Copy)] +pub struct FeatureConfig { + /// Zero difficulty + pub zero_difficulty: bool, + /// Free first transaction + pub free_first_tx: bool, + /// Enable EIP1559 + pub enable_eip1559: bool, + /// Allow invalid transactions to be included in a block + /// + /// Transactions with mismatched nonce, insufficient gas limit, or insufficient balance + /// shouldn't be included in a mainnet block. However, rollup developers might want to + /// include invalid tx in the L2 block to support forced exit feature. + pub invalid_tx: bool, +} + +impl Default for FeatureConfig { + fn default() -> Self { + Self { + zero_difficulty: true, + free_first_tx: false, + enable_eip1559: true, + invalid_tx: false, + } + } +} + +impl FeatureConfig { + /// Check if we are mainnet config + pub fn is_mainnet(&self) -> bool { + self.zero_difficulty && !self.free_first_tx && self.enable_eip1559 && !self.invalid_tx + } +} + /// Circuit Setup Parameters #[derive(Debug, Clone, Copy)] pub struct FixedCParams { @@ -150,18 +187,27 @@ pub struct CircuitInputBuilder { pub circuits_params: C, /// Block Context pub block_ctx: BlockContext, + /// Feature config + pub feature_config: FeatureConfig, } impl<'a, C: CircuitsParams> CircuitInputBuilder { /// Create a new CircuitInputBuilder from the given `eth_block` and /// `constants`. - pub fn new(sdb: StateDB, code_db: CodeDB, block: Block, params: C) -> Self { + pub fn new( + sdb: StateDB, + code_db: CodeDB, + block: Block, + params: C, + feature_config: FeatureConfig, + ) -> Self { Self { sdb, code_db, block, circuits_params: params, block_ctx: BlockContext::new(), + feature_config, } } @@ -273,13 +319,15 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { let end_tx_step = gen_associated_steps(&mut self.state_ref(&mut tx, &mut tx_ctx), ExecState::EndTx)?; tx.steps_mut().push(end_tx_step); - } else { + } else if self.feature_config.invalid_tx { // Generate InvalidTx step let invalid_tx_step = gen_associated_steps( &mut self.state_ref(&mut tx, &mut tx_ctx), ExecState::InvalidTx, )?; tx.steps_mut().push(invalid_tx_step); + } else { + panic!("invalid tx support not enabled") } self.sdb.commit_tx(); @@ -457,6 +505,7 @@ impl CircuitInputBuilder { block: self.block, circuits_params: c_params, block_ctx: self.block_ctx, + feature_config: self.feature_config, }; cib.set_end_block(c_params.max_rws)?; @@ -567,6 +616,7 @@ pub struct BuilderClient { cli: GethClient

, chain_id: Word, circuits_params: FixedCParams, + feature_config: FeatureConfig, } /// Get State Accesses from TxExecTraces @@ -624,12 +674,22 @@ pub fn build_state_code_db( impl BuilderClient

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

, circuits_params: FixedCParams) -> Result { + Self::new_with_features(client, circuits_params, FeatureConfig::default()).await + } + + /// Create a new BuilderClient + pub async fn new_with_features( + client: GethClient

, + circuits_params: FixedCParams, + feature_config: FeatureConfig, + ) -> Result { let chain_id = client.get_chain_id().await?; Ok(Self { cli: client, chain_id: chain_id.into(), circuits_params, + feature_config, }) } @@ -741,7 +801,13 @@ impl BuilderClient

{ prev_state_root: Word, ) -> Result, Error> { let block = Block::new(self.chain_id, history_hashes, prev_state_root, eth_block)?; - let mut builder = CircuitInputBuilder::new(sdb, code_db, block, self.circuits_params); + let mut builder = CircuitInputBuilder::new( + sdb, + code_db, + block, + self.circuits_params, + self.feature_config, + ); builder.handle_block(eth_block, geth_traces)?; Ok(builder) } diff --git a/bus-mapping/src/mock.rs b/bus-mapping/src/mock.rs index 4e06367dbe..08a581e318 100644 --- a/bus-mapping/src/mock.rs +++ b/bus-mapping/src/mock.rs @@ -3,7 +3,7 @@ use crate::{ circuit_input_builder::{ get_state_accesses, Block, CircuitInputBuilder, CircuitsParams, DynamicCParams, - FixedCParams, + FeatureConfig, FixedCParams, }, state_db::{self, CodeDB, StateDB}, }; @@ -34,6 +34,14 @@ impl BlockData { /// Generate a new CircuitInputBuilder initialized with the context of the /// BlockData. pub fn new_circuit_input_builder(&self) -> CircuitInputBuilder { + self.new_circuit_input_builder_with_feature(FeatureConfig::default()) + } + /// Generate a new CircuitInputBuilder initialized with the context of the + /// BlockData. + pub fn new_circuit_input_builder_with_feature( + &self, + feature_config: FeatureConfig, + ) -> CircuitInputBuilder { CircuitInputBuilder::new( self.sdb.clone(), self.code_db.clone(), @@ -45,6 +53,7 @@ impl BlockData { ) .unwrap(), self.circuits_params, + feature_config, ) } diff --git a/circuit-benchmarks/src/mpt_circuit.rs b/circuit-benchmarks/src/mpt_circuit.rs index 3f850eb3b4..5160e66a76 100644 --- a/circuit-benchmarks/src/mpt_circuit.rs +++ b/circuit-benchmarks/src/mpt_circuit.rs @@ -48,10 +48,12 @@ mod tests { } } + let max_nodes = 720; let circuit = MPTCircuit:: { nodes, keccak_data, degree: degree as usize, + max_nodes, disable_preimage_check: false, _marker: PhantomData, }; diff --git a/light-client-poc/src/circuit/mod.rs b/light-client-poc/src/circuit/mod.rs index 69756e2dbc..cdd773caf1 100644 --- a/light-client-poc/src/circuit/mod.rs +++ b/light-client-poc/src/circuit/mod.rs @@ -5,5 +5,7 @@ mod witness; pub use prover::StateUpdateCircuitKeys; -pub use state_update::{StateUpdateCircuit, DEFAULT_CIRCUIT_DEGREE, DEFAULT_MAX_PROOF_COUNT}; +pub use state_update::{ + StateUpdateCircuit, DEFAULT_CIRCUIT_DEGREE, DEFAULT_MAX_NODES, DEFAULT_MAX_PROOF_COUNT, +}; pub use witness::{PublicInputs, StateUpdateWitness}; diff --git a/light-client-poc/src/circuit/state_update.rs b/light-client-poc/src/circuit/state_update.rs index 1c57f7e0d8..15464e311b 100644 --- a/light-client-poc/src/circuit/state_update.rs +++ b/light-client-poc/src/circuit/state_update.rs @@ -34,6 +34,7 @@ use zkevm_circuits::{ util::{SubCircuit, SubCircuitConfig}, }; +pub const DEFAULT_MAX_NODES: usize = 4000; pub const DEFAULT_MAX_PROOF_COUNT: usize = 20; pub const DEFAULT_CIRCUIT_DEGREE: usize = 14; @@ -90,6 +91,7 @@ impl Circuit for StateUpdateCircuit { MPTCircuitParams { degree: self.mpt_circuit.degree, disable_preimage_check: self.mpt_circuit.disable_preimage_check, + max_nodes: self.mpt_circuit.max_nodes, } } @@ -301,14 +303,15 @@ impl Circuit for StateUpdateCircuit { // assign MPT witness - let height = - config - .mpt_config - .assign(&mut layouter, &self.mpt_circuit.nodes, &challenges)?; - config.mpt_config.load_fixed_table(&mut layouter)?; config .mpt_config - .load_mult_table(&mut layouter, &challenges, height)?; + .assign(&mut layouter, &self.mpt_circuit.nodes, &challenges)?; + config.mpt_config.load_fixed_table(&mut layouter)?; + config.mpt_config.load_mult_table( + &mut layouter, + &challenges, + self.mpt_circuit.max_nodes, + )?; #[cfg(feature = "disable-keccak")] config.mpt_config.keccak_table.dev_load( @@ -466,6 +469,7 @@ impl StateUpdateCircuit { pub fn new( witness: StateUpdateWitness, degree: usize, + max_nodes: usize, max_proof_count: usize, ) -> Result> { let StateUpdateWitness { @@ -489,6 +493,7 @@ impl StateUpdateCircuit { nodes: mpt_witness, keccak_data: keccak_data.clone(), degree, + max_nodes, disable_preimage_check, _marker: std::marker::PhantomData, }; diff --git a/light-client-poc/src/server.rs b/light-client-poc/src/server.rs index c07c397bba..c9b68c616b 100644 --- a/light-client-poc/src/server.rs +++ b/light-client-poc/src/server.rs @@ -9,7 +9,7 @@ use std::{collections::HashMap, str::FromStr, time::SystemTime}; use crate::circuit::{ PublicInputs, StateUpdateCircuit, StateUpdateCircuitKeys, StateUpdateWitness, - DEFAULT_CIRCUIT_DEGREE, DEFAULT_MAX_PROOF_COUNT, + DEFAULT_CIRCUIT_DEGREE, DEFAULT_MAX_NODES, DEFAULT_MAX_PROOF_COUNT, }; pub async fn serve() -> Result<()> { @@ -62,8 +62,12 @@ pub async fn serve() -> Result<()> { }; let public_inputs: PublicInputs = (&witness.lc_witness).into(); - let circuit = - StateUpdateCircuit::new(witness, DEFAULT_CIRCUIT_DEGREE, DEFAULT_MAX_PROOF_COUNT)?; + let circuit = StateUpdateCircuit::new( + witness, + DEFAULT_CIRCUIT_DEGREE, + DEFAULT_MAX_NODES, + DEFAULT_MAX_PROOF_COUNT, + )?; println!("trns: {:#?}", circuit.transforms); diff --git a/light-client-poc/src/tests/mainnet.rs b/light-client-poc/src/tests/mainnet.rs index b2f1db597d..6910b7e480 100644 --- a/light-client-poc/src/tests/mainnet.rs +++ b/light-client-poc/src/tests/mainnet.rs @@ -10,7 +10,7 @@ mod test { use crate::circuit::{ PublicInputs, StateUpdateCircuit, StateUpdateCircuitKeys, StateUpdateWitness, - DEFAULT_CIRCUIT_DEGREE, DEFAULT_MAX_PROOF_COUNT, + DEFAULT_CIRCUIT_DEGREE, DEFAULT_MAX_NODES, DEFAULT_MAX_PROOF_COUNT, }; #[ctor::ctor] @@ -23,6 +23,7 @@ mod test { block_no: u64, access_list: &[(&str, Vec<&str>)], degree: usize, + max_nodes: usize, max_proof_count: usize, ) -> Result> { const PVK: &str = "7ccb34dc5fd31fd0aa7860de89a4adc37ccb34dc5fd31fd0aa7860de89a4adc3"; @@ -54,7 +55,7 @@ mod test { println!("trns: {:#?}", witness.transforms); - let circuit = StateUpdateCircuit::new(witness, degree, max_proof_count)?; + let circuit = StateUpdateCircuit::new(witness, degree, max_nodes, max_proof_count)?; circuit.assert_satisfied(); @@ -150,7 +151,14 @@ mod test { async fn test_block_436875() -> Result<()> { let block_no = 436875; let access_list = blocks().get(&block_no).unwrap().clone(); - let _ = mock_prove(block_no, &access_list, 16, DEFAULT_MAX_PROOF_COUNT).await?; + let _ = mock_prove( + block_no, + &access_list, + 16, + DEFAULT_MAX_NODES, + DEFAULT_MAX_PROOF_COUNT, + ) + .await?; Ok(()) } @@ -159,7 +167,14 @@ mod test { async fn test_block_107() -> Result<()> { let block_no = 107; let access_list = blocks().get(&block_no).unwrap().clone(); - let _ = mock_prove(block_no, &access_list, 15, DEFAULT_MAX_PROOF_COUNT).await?; + let _ = mock_prove( + block_no, + &access_list, + 15, + DEFAULT_MAX_NODES, + DEFAULT_MAX_PROOF_COUNT, + ) + .await?; Ok(()) } @@ -169,7 +184,14 @@ mod test { let block_no = 107; let access_list = blocks().get(&block_no).unwrap().clone(); - let circuit = mock_prove(block_no, &access_list, 15, DEFAULT_MAX_PROOF_COUNT).await?; + let circuit = mock_prove( + block_no, + &access_list, + 15, + DEFAULT_MAX_NODES, + DEFAULT_MAX_PROOF_COUNT, + ) + .await?; let public_inputs: PublicInputs = (&circuit.lc_witness).into(); let keys = StateUpdateCircuitKeys::new(&circuit); @@ -186,6 +208,7 @@ mod test { block_no, &access_list, DEFAULT_CIRCUIT_DEGREE, + DEFAULT_MAX_NODES, DEFAULT_MAX_PROOF_COUNT, ) .await?; @@ -214,7 +237,14 @@ mod test { async fn test_block_2000007() -> Result<()> { let block_no = 2000007; let access_list = blocks().get(&block_no).unwrap().clone(); - let _ = mock_prove(block_no, &access_list, 18, DEFAULT_MAX_PROOF_COUNT).await?; + let _ = mock_prove( + block_no, + &access_list, + 18, + DEFAULT_MAX_NODES, + DEFAULT_MAX_PROOF_COUNT, + ) + .await?; Ok(()) } @@ -223,7 +253,14 @@ mod test { async fn test_block_2000004() -> Result<()> { let block_no = 2000004; let access_list = blocks().get(&block_no).unwrap().clone(); - let _ = mock_prove(block_no, &access_list, 18, DEFAULT_MAX_PROOF_COUNT).await?; + let _ = mock_prove( + block_no, + &access_list, + 18, + DEFAULT_MAX_NODES, + DEFAULT_MAX_PROOF_COUNT, + ) + .await?; Ok(()) } @@ -232,7 +269,14 @@ mod test { async fn test_block_2000070() -> Result<()> { let block_no = 2000070; let access_list = blocks().get(&block_no).unwrap().clone(); - let _ = mock_prove(block_no, &access_list, 18, DEFAULT_MAX_PROOF_COUNT).await?; + let _ = mock_prove( + block_no, + &access_list, + 18, + DEFAULT_MAX_NODES, + DEFAULT_MAX_PROOF_COUNT, + ) + .await?; Ok(()) } } diff --git a/light-client-poc/src/utils.rs b/light-client-poc/src/utils.rs index a4706ac848..384e9dd221 100644 --- a/light-client-poc/src/utils.rs +++ b/light-client-poc/src/utils.rs @@ -146,10 +146,12 @@ pub fn verify_mpt_witness(nodes: Vec) -> Result<()> { // verify the circuit let disable_preimage_check = nodes[0].start.clone().unwrap().disable_preimage_check; let degree = 15; + let max_nodes = 520; let circuit = zkevm_circuits::mpt_circuit::MPTCircuit:: { nodes, keccak_data, degree, + max_nodes, disable_preimage_check, _marker: std::marker::PhantomData, }; diff --git a/zkevm-circuits/src/bin/stats/main.rs b/zkevm-circuits/src/bin/stats/main.rs index fdd2066f33..73514d35cc 100644 --- a/zkevm-circuits/src/bin/stats/main.rs +++ b/zkevm-circuits/src/bin/stats/main.rs @@ -1,3 +1,4 @@ +use bus_mapping::circuit_input_builder::FeatureConfig; use cli_table::{print_stdout, Cell, Style, Table}; use eth_types::{bytecode, evm_types::OpcodeId, ToWord}; use halo2_proofs::{ @@ -113,7 +114,7 @@ fn copy_states_stats() { /// cell consumers of each EVM Cell type. fn get_exec_steps_occupancy() { let mut meta = ConstraintSystem::::default(); - let circuit = EvmCircuit::configure(&mut meta); + let circuit = EvmCircuit::configure_with_params(&mut meta, FeatureConfig::default()); let report = circuit.0.execution.instrument().clone().analyze(); macro_rules! gen_report { diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index e330550eb7..7afff29d6d 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -26,7 +26,7 @@ use crate::{ }, util::{Challenges, SubCircuit, SubCircuitConfig}, }; -use bus_mapping::evm::OpcodeId; +use bus_mapping::{circuit_input_builder::FeatureConfig, evm::OpcodeId}; use eth_types::Field; use execution::ExecutionConfig; use itertools::Itertools; @@ -74,6 +74,8 @@ pub struct EvmCircuitConfigArgs { pub u8_table: UXTable<8>, /// U16Table pub u16_table: UXTable<16>, + /// Feature config + pub feature_config: FeatureConfig, } impl SubCircuitConfig for EvmCircuitConfig { @@ -93,6 +95,7 @@ impl SubCircuitConfig for EvmCircuitConfig { exp_table, u8_table, u16_table, + feature_config, }: Self::ConfigArgs, ) -> Self { let fixed_table = [(); 4].map(|_| meta.fixed_column()); @@ -109,6 +112,7 @@ impl SubCircuitConfig for EvmCircuitConfig { ©_table, &keccak_table, &exp_table, + feature_config, )); u8_table.annotate_columns(meta); @@ -310,7 +314,8 @@ pub(crate) mod cached { /// Circuit configuration. These values are calculated just once. static ref CACHE: Cache = { let mut meta = ConstraintSystem::::default(); - let config = EvmCircuit::::configure(&mut meta); + // Cached EVM circuit is configured with Mainnet FeatureConfig + let config = EvmCircuit::::configure_with_params(&mut meta, FeatureConfig::default()); Cache { cs: meta, config } }; } @@ -356,13 +361,21 @@ pub(crate) mod cached { impl Circuit for EvmCircuit { type Config = (EvmCircuitConfig, Challenges); type FloorPlanner = SimpleFloorPlanner; - type Params = (); + type Params = FeatureConfig; fn without_witnesses(&self) -> Self { Self::default() } - fn configure(meta: &mut ConstraintSystem) -> Self::Config { + /// Try to get the [`FeatureConfig`] from the block or fallback to default + fn params(&self) -> Self::Params { + self.block + .as_ref() + .map(|block| block.feature_config) + .unwrap_or_default() + } + + fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { let tx_table = TxTable::construct(meta); let rw_table = RwTable::construct(meta); let bytecode_table = BytecodeTable::construct(meta); @@ -390,12 +403,17 @@ impl Circuit for EvmCircuit { exp_table, u8_table, u16_table, + feature_config: params, }, ), challenges, ) } + fn configure(_meta: &mut ConstraintSystem) -> Self::Config { + unreachable!(); + } + fn synthesize( &self, config: Self::Config, @@ -443,7 +461,10 @@ mod evm_circuit_stats { util::{unusable_rows, SubCircuit}, witness::block_convert, }; - use bus_mapping::{circuit_input_builder::FixedCParams, mock::BlockData}; + use bus_mapping::{ + circuit_input_builder::{FeatureConfig, FixedCParams}, + mock::BlockData, + }; use eth_types::{bytecode, geth_types::GethData}; use halo2_proofs::{self, dev::MockProver, halo2curves::bn256::Fr}; @@ -455,9 +476,20 @@ mod evm_circuit_stats { #[test] fn evm_circuit_unusable_rows() { + let computed = EvmCircuit::::unusable_rows(); + let mainnet_config = FeatureConfig::default(); + let invalid_tx_config = FeatureConfig { + invalid_tx: true, + ..Default::default() + }; + + assert_eq!( + computed, + unusable_rows::>(mainnet_config), + ); assert_eq!( - EvmCircuit::::unusable_rows(), - unusable_rows::>(()), + computed, + unusable_rows::>(invalid_tx_config), ) } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index ffd1a9cf9e..87595ef6af 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -26,7 +26,7 @@ use crate::{ Challenges, Expr, }, }; -use bus_mapping::operation::Target; +use bus_mapping::{circuit_input_builder::FeatureConfig, operation::Target}; use eth_types::{evm_unimplemented, Field}; use gadgets::util::not; use halo2_proofs::{ @@ -338,7 +338,7 @@ pub struct ExecutionConfig { error_precompile_failed: Box>, error_return_data_out_of_bound: Box>, precompile_identity_gadget: Box>, - invalid_tx: Box>, + invalid_tx: Option>>, } impl ExecutionConfig { @@ -357,6 +357,7 @@ impl ExecutionConfig { copy_table: &dyn LookupTable, keccak_table: &dyn LookupTable, exp_table: &dyn LookupTable, + feature_config: FeatureConfig, ) -> Self { let mut instrument = Instrument::default(); let q_usable = meta.complex_selector(); @@ -397,11 +398,15 @@ impl ExecutionConfig { // NEW: Enabled, this will break hand crafted tests, maybe we can remove them? let first_step_check = { - let begin_tx_invalid_tx_end_block_selector = step_curr.execution_state_selector([ - ExecutionState::BeginTx, - ExecutionState::InvalidTx, - ExecutionState::EndBlock, - ]); + let begin_tx_invalid_tx_end_block_selector = step_curr.execution_state_selector( + [ExecutionState::BeginTx, ExecutionState::EndBlock] + .into_iter() + .chain( + feature_config + .invalid_tx + .then_some(ExecutionState::InvalidTx), + ), + ); iter::once(( "First step should be BeginTx, InvalidTx or EndBlock", q_step_first * (1.expr() - begin_tx_invalid_tx_end_block_selector), @@ -504,6 +509,7 @@ impl ExecutionConfig { &mut stored_expressions_map, &mut debug_expressions_map, &mut instrument, + feature_config.clone(), )) })() }; @@ -524,7 +530,7 @@ impl ExecutionConfig { begin_tx_gadget: configure_gadget!(), end_block_gadget: configure_gadget!(), end_tx_gadget: configure_gadget!(), - invalid_tx: configure_gadget!(), + invalid_tx: feature_config.invalid_tx.then(|| configure_gadget!()), // opcode gadgets add_sub_gadget: configure_gadget!(), addmod_gadget: configure_gadget!(), @@ -654,6 +660,7 @@ impl ExecutionConfig { stored_expressions_map: &mut HashMap>>, debug_expressions_map: &mut HashMap)>>, instrument: &mut Instrument, + feature_config: FeatureConfig, ) -> G { // Configure the gadget with the max height first so we can find out the actual // height @@ -665,6 +672,7 @@ impl ExecutionConfig { dummy_step_next, challenges, G::EXECUTION_STATE, + feature_config, ); G::configure(&mut cb); let (_, _, height, _) = cb.build(); @@ -679,6 +687,7 @@ impl ExecutionConfig { step_next.clone(), challenges, G::EXECUTION_STATE, + feature_config, ); let gadget = G::configure(&mut cb); @@ -734,6 +743,10 @@ impl ExecutionConfig { instrument.on_gadget_built(execution_state, &cb); let debug_expressions = cb.debug_expressions.clone(); + + // Extract feature config here before cb is built. + let enable_invalid_tx = cb.feature_config.invalid_tx; + let (constraints, stored_expressions, _, meta) = cb.build(); debug_assert!( !height_map.contains_key(&execution_state), @@ -785,36 +798,33 @@ impl ExecutionConfig { // ExecutionState transition should be correct. iter::empty() .chain( - IntoIterator::into_iter([ + [ ( "EndTx can only transit to BeginTx, InvalidTx or EndBlock", ExecutionState::EndTx, - vec![ - ExecutionState::BeginTx, - ExecutionState::InvalidTx, - ExecutionState::EndBlock, - ], + vec![ExecutionState::BeginTx, ExecutionState::EndBlock] + .into_iter() + .chain(enable_invalid_tx.then_some(ExecutionState::InvalidTx)) + .collect(), ), ( "EndBlock can only transit to EndBlock", ExecutionState::EndBlock, vec![ExecutionState::EndBlock], ), - ]) + ] + .into_iter() .filter(move |(_, from, _)| *from == execution_state) .map(|(_, _, to)| 1.expr() - step_next.execution_state_selector(to)), ) .chain( - IntoIterator::into_iter([ - ( - "Only EndTx and InvalidTx can transit to InvalidTx", - ExecutionState::InvalidTx, - vec![ExecutionState::EndTx, ExecutionState::InvalidTx], - ), + [ ( "Only EndTx and InvalidTx can transit to BeginTx", ExecutionState::BeginTx, - vec![ExecutionState::EndTx, ExecutionState::InvalidTx], + iter::once(ExecutionState::EndTx) + .chain(enable_invalid_tx.then_some(ExecutionState::InvalidTx)) + .collect(), ), ( "Only ExecutionState which halts or BeginTx can transit to EndTx", @@ -827,13 +837,20 @@ impl ExecutionConfig { ( "Only EndTx, InvalidTx or EndBlock can transit to EndBlock", ExecutionState::EndBlock, - vec![ - ExecutionState::EndTx, - ExecutionState::InvalidTx, - ExecutionState::EndBlock, - ], + vec![ExecutionState::EndTx, ExecutionState::EndBlock] + .into_iter() + .chain(enable_invalid_tx.then_some(ExecutionState::InvalidTx)) + .collect(), ), - ]) + ] + .into_iter() + .chain(enable_invalid_tx.then(|| { + ( + "Only EndTx and InvalidTx can transit to InvalidTx", + ExecutionState::InvalidTx, + vec![ExecutionState::EndTx, ExecutionState::InvalidTx], + ) + })) .filter(move |(_, _, from)| !from.contains(&execution_state)) .map(|(_, to, _)| step_next.execution_state_selector([to])), ) @@ -1259,7 +1276,12 @@ impl ExecutionConfig { ExecutionState::BeginTx => assign_exec_step!(self.begin_tx_gadget), ExecutionState::EndTx => assign_exec_step!(self.end_tx_gadget), ExecutionState::EndBlock => assign_exec_step!(self.end_block_gadget), - ExecutionState::InvalidTx => assign_exec_step!(self.invalid_tx), + ExecutionState::InvalidTx => { + assign_exec_step!(self + .invalid_tx + .as_deref() + .expect("invalid tx gadget must exist")) + } // opcode ExecutionState::ADD_SUB => assign_exec_step!(self.add_sub_gadget), ExecutionState::ADDMOD => assign_exec_step!(self.addmod_gadget), diff --git a/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs b/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs index 13e0bc860e..69a50a0de0 100644 --- a/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs @@ -69,7 +69,7 @@ impl ExecutionGadget for InvalidTxGadget { insufficient_gas_limit.expr(), insufficient_balance.expr(), ]); - cb.require_equal("Tx needs to be invalid", invalid_tx.expr(), 1.expr()); + cb.require_true("Tx needs to be invalid", invalid_tx.expr()); let end_tx = EndTxHelperGadget::construct( cb, @@ -138,8 +138,8 @@ impl ExecutionGadget for InvalidTxGadget { #[cfg(test)] mod test { use crate::test_util::CircuitTestBuilder; + use bus_mapping::circuit_input_builder::FeatureConfig; use eth_types::{self, bytecode, Word}; - use mock::{eth, gwei, TestContext, MOCK_ACCOUNTS}; #[test] @@ -164,7 +164,12 @@ mod test { |block, _| block, ) .unwrap(); - CircuitTestBuilder::new_from_test_ctx(ctx).run(); + CircuitTestBuilder::new_from_test_ctx(ctx) + .feature(FeatureConfig { + invalid_tx: true, + ..Default::default() + }) + .run(); } #[test] @@ -194,7 +199,12 @@ mod test { |block, _| block, ) .unwrap(); - CircuitTestBuilder::new_from_test_ctx(ctx).run(); + CircuitTestBuilder::new_from_test_ctx(ctx) + .feature(FeatureConfig { + invalid_tx: true, + ..Default::default() + }) + .run(); } #[test] @@ -220,7 +230,12 @@ mod test { |block, _| block, ) .unwrap(); - CircuitTestBuilder::new_from_test_ctx(ctx).run(); + CircuitTestBuilder::new_from_test_ctx(ctx) + .feature(FeatureConfig { + invalid_tx: true, + ..Default::default() + }) + .run(); } #[test] @@ -249,7 +264,12 @@ mod test { |block, _| block, ) .unwrap(); - CircuitTestBuilder::new_from_test_ctx(ctx).run(); + CircuitTestBuilder::new_from_test_ctx(ctx) + .feature(FeatureConfig { + invalid_tx: true, + ..Default::default() + }) + .run(); } // Check different permutations to make sure all transitions work correctly diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index 18f24190e2..4a06970258 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -1,6 +1,7 @@ //! Constants and parameters for the EVM circuit use super::table::Table; use crate::evm_circuit::{step::ExecutionState, EvmCircuit}; +use bus_mapping::circuit_input_builder::FeatureConfig; use halo2_proofs::{ halo2curves::bn256::Fr, plonk::{Circuit, ConstraintSystem}, @@ -168,12 +169,35 @@ pub(crate) const N_BYTES_WITHDRAWAL: usize = N_BYTES_U64 //id + N_BYTES_U64; // amount lazy_static::lazy_static! { + static ref INVALID_TX_CONFIG: FeatureConfig = FeatureConfig { + invalid_tx: true, + ..Default::default() + }; // Step slot height in evm circuit - pub(crate) static ref EXECUTION_STATE_HEIGHT_MAP : HashMap = get_step_height_map(); + // We enable the invalid_tx feature to get invalid tx's ExecutionState height + // We garentee the heights of other ExecutionStates remains unchanged in the following test + pub(crate) static ref EXECUTION_STATE_HEIGHT_MAP : HashMap = get_step_height_map(*INVALID_TX_CONFIG); } -fn get_step_height_map() -> HashMap { +fn get_step_height_map(feature_config: FeatureConfig) -> HashMap { let mut meta = ConstraintSystem::::default(); - let circuit = EvmCircuit::configure(&mut meta); - + let circuit = EvmCircuit::configure_with_params(&mut meta, feature_config); circuit.0.execution.height_map } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get_step_height_map() { + let mainnet_config = FeatureConfig::default(); + let map_mainnet = get_step_height_map(mainnet_config); + + let map_invalid_tx = { + let mut map = EXECUTION_STATE_HEIGHT_MAP.clone(); + map.remove(&ExecutionState::InvalidTx); + map + }; + // We show that the invalid tx feature affects none of the other execution state heights + assert_eq!(map_invalid_tx, map_mainnet); + } +} diff --git a/zkevm-circuits/src/evm_circuit/util.rs b/zkevm-circuits/src/evm_circuit/util.rs index a77ea32b68..f5202ad9f1 100644 --- a/zkevm-circuits/src/evm_circuit/util.rs +++ b/zkevm-circuits/src/evm_circuit/util.rs @@ -449,15 +449,6 @@ pub(crate) fn is_precompiled(address: &Address) -> bool { address.0[0..19] == [0u8; 19] && (1..=9).contains(&address.0[19]) } -const BASE_128_BYTES: [u8; 32] = [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -]; - -/// convert address (h160) to single expression. -pub fn address_word_to_expr(address: Word>) -> Expression { - address.lo() + address.hi() * Expression::Constant(F::from_repr(BASE_128_BYTES).unwrap()) -} - /// Helper struct to read rw operations from a step sequentially. pub(crate) struct StepRws<'a> { rws: &'a RwMap, diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 5a2aa4117f..ff50a6cbe1 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -15,7 +15,9 @@ use crate::{ Challenges, Expr, }, }; -use bus_mapping::{operation::Target, state_db::EMPTY_CODE_HASH_LE}; +use bus_mapping::{ + circuit_input_builder::FeatureConfig, operation::Target, state_db::EMPTY_CODE_HASH_LE, +}; use eth_types::Field; use gadgets::util::{not, sum}; use halo2_proofs::{ @@ -28,8 +30,7 @@ use halo2_proofs::{ }; use super::{ - address_word_to_expr, rlc, AccountAddress, CachedRegion, CellType, MemoryAddress, - StoredExpression, U64Cell, + rlc, AccountAddress, CachedRegion, CellType, MemoryAddress, StoredExpression, U64Cell, }; // Max degree allowed in all expressions passing through the ConstraintBuilder. @@ -313,6 +314,7 @@ pub(crate) struct EVMConstraintBuilder<'a, F: Field> { stored_expressions: Vec>, pub(crate) debug_expressions: Vec<(String, Expression)>, meta: &'a mut ConstraintSystem, + pub(crate) feature_config: FeatureConfig, } impl<'a, F: Field> ConstrainBuilderCommon for EVMConstraintBuilder<'a, F> { @@ -337,6 +339,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { next: Step, challenges: &'a Challenges>, execution_state: ExecutionState, + feature_config: FeatureConfig, ) -> Self { Self { curr, @@ -358,6 +361,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { stored_expressions: Vec::new(), meta, debug_expressions: Vec::new(), + feature_config, } } @@ -859,7 +863,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::TxAccessListAccount, RwValues::new( tx_id, - address_word_to_expr(account_address), + account_address.compress(), 0.expr(), Word::zero(), Word::from_lo_unchecked(value), @@ -882,7 +886,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::TxAccessListAccount, RwValues::new( tx_id, - address_word_to_expr(account_address), + account_address.compress(), 0.expr(), Word::zero(), Word::from_lo_unchecked(value.clone()), @@ -905,7 +909,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::TxAccessListAccountStorage, RwValues::new( tx_id, - address_word_to_expr(account_address), + account_address.compress(), 0.expr(), storage_key, value, @@ -929,7 +933,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::TxAccessListAccountStorage, RwValues::new( tx_id, - address_word_to_expr(account_address), + account_address.compress(), 0.expr(), storage_key, value.clone(), @@ -994,7 +998,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::Account, RwValues::new( 0.expr(), - address_word_to_expr(account_address), + account_address.compress(), field_tag.expr(), Word::zero(), value.clone(), @@ -1017,7 +1021,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::Account, RwValues::new( 0.expr(), - address_word_to_expr(account_address), + account_address.compress(), field_tag.expr(), Word::zero(), value, @@ -1043,7 +1047,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::Storage, RwValues::new( tx_id, - address_word_to_expr(account_address), + account_address.compress(), 0.expr(), key, value.clone(), @@ -1069,7 +1073,7 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Target::Storage, RwValues::new( tx_id, - address_word_to_expr(account_address), + account_address.compress(), 0.expr(), key, value, diff --git a/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs b/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs index c6941d4e70..e829f86773 100644 --- a/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs +++ b/zkevm-circuits/src/evm_circuit/util/math_gadget/test_util.rs @@ -1,3 +1,4 @@ +use bus_mapping::circuit_input_builder::FeatureConfig; use itertools::Itertools; use std::marker::PhantomData; use strum::IntoEnumIterator; @@ -122,6 +123,7 @@ impl> Circuit for UnitTestMathGadgetBaseC step_next, &challenges_exprs, ExecutionState::STOP, + FeatureConfig::default(), ); let math_gadget_container = G::configure_gadget_container(&mut cb); let (constraints, stored_expressions, _, _) = cb.build(); diff --git a/zkevm-circuits/src/evm_circuit/util/memory_gadget/test_util.rs b/zkevm-circuits/src/evm_circuit/util/memory_gadget/test_util.rs index 1d42a79cae..66a2365c9c 100644 --- a/zkevm-circuits/src/evm_circuit/util/memory_gadget/test_util.rs +++ b/zkevm-circuits/src/evm_circuit/util/memory_gadget/test_util.rs @@ -11,6 +11,7 @@ use crate::{ }, util::Challenges, }; +use bus_mapping::circuit_input_builder::FeatureConfig; use eth_types::{Field, Word}; pub(crate) use halo2_proofs::circuit::Layouter; use halo2_proofs::{ @@ -101,6 +102,7 @@ impl> Circuit for UnitTestMemoryGadgetB step_next, &challenges_exprs, ExecutionState::STOP, + FeatureConfig::default(), ); let memory_gadget_container = G::configure_gadget_container(&mut cb); let (constraints, stored_expressions, _, _) = cb.build(); diff --git a/zkevm-circuits/src/evm_circuit/util/tx.rs b/zkevm-circuits/src/evm_circuit/util/tx.rs index 2ddeca2615..d3987300ed 100644 --- a/zkevm-circuits/src/evm_circuit/util/tx.rs +++ b/zkevm-circuits/src/evm_circuit/util/tx.rs @@ -120,25 +120,28 @@ impl EndTxHelperGadget { // Transition let rw_counter_offset = num_rw.expr() - is_first_tx.expr(); - cb.condition( - cb.next - .execution_state_selector([ExecutionState::BeginTx, ExecutionState::InvalidTx]), - |cb| { - let next_step_rwc = cb.next.state.rw_counter.expr(); - // lookup use next step initial rwc, thus lead to same record on rw table - cb.call_context_lookup_write_with_counter( - next_step_rwc.clone(), - Some(next_step_rwc), - CallContextFieldTag::TxId, - // tx_id has been lookup and range_check above - Word::from_lo_unchecked(tx_id.expr() + 1.expr()), - ); - cb.require_step_state_transition(StepStateTransition { - rw_counter: Delta(rw_counter_offset.clone()), - ..StepStateTransition::any() - }); - }, - ); + let next_begin = if cb.feature_config.invalid_tx { + vec![ExecutionState::BeginTx, ExecutionState::InvalidTx] + } else { + vec![ExecutionState::BeginTx] + }; + cb.condition(cb.next.execution_state_selector(next_begin), |cb| { + let next_step_rwc = cb.next.state.rw_counter.expr(); + // lookup use next step initial rwc, thus lead to same record on rw table + cb.call_context_lookup_write_with_counter( + next_step_rwc.clone(), + Some(next_step_rwc), + CallContextFieldTag::TxId, + // tx_id has been lookup and range_check above + Word::from_lo_unchecked(tx_id.expr() + 1.expr()), + ); + // minus 1.expr() because `call_context_lookup_write_with_counter` do not bump + // rwc + cb.require_step_state_transition(StepStateTransition { + rw_counter: Delta(rw_counter_offset.clone()), + ..StepStateTransition::any() + }); + }); cb.condition( cb.next.execution_state_selector([ExecutionState::EndBlock]), |cb| { diff --git a/zkevm-circuits/src/mpt_circuit.rs b/zkevm-circuits/src/mpt_circuit.rs index db73d02588..bc9ca04398 100644 --- a/zkevm-circuits/src/mpt_circuit.rs +++ b/zkevm-circuits/src/mpt_circuit.rs @@ -174,7 +174,6 @@ impl MPTContext { pub struct MPTConfig { pub(crate) q_enable: Column, pub(crate) q_first: Column, - pub(crate) q_last: Column, pub(crate) memory: MptMemory, /// MPT table pub mpt_table: MptTable, @@ -219,7 +218,6 @@ impl MPTConfig { ) -> Self { let q_enable = meta.fixed_column(); let q_first = meta.fixed_column(); - let q_last = meta.fixed_column(); let mpt_table = MptTable::construct(meta); @@ -308,7 +306,7 @@ impl MPTConfig { // Main MPT circuit // State machine cb.base.set_cell_manager(state_cm.clone()); - ifx! {f!(q_first) + f!(q_last) => { + ifx! {f!(q_first) => { require!(a!(state_machine.is_start) => true); }}; // Main state machine @@ -370,7 +368,6 @@ impl MPTConfig { MPTConfig { q_enable, q_first, - q_last, memory, keccak_table, fixed_table, @@ -390,8 +387,7 @@ impl MPTConfig { layouter: &mut impl Layouter, nodes: &[Node], challenges: &Challenges>, - ) -> Result { - let mut height = 0; + ) -> Result<(), Error> { layouter.assign_region( || "MPT", |mut region| { @@ -497,24 +493,28 @@ impl MPTConfig { cached_region.assign_stored_expressions(&self.cb.base, challenges)?; } - height = offset; - // Make sure the circuit is high enough for the mult table - while height < (2 * HASH_WIDTH + 1) { - height += 1; - } + assert!( self.params.max_nodes >= (2 * HASH_WIDTH + 1), + "The parameter max_nodes is set too low for the mult table: {}, mult table height: {}", + self.params.max_nodes, + 2 * HASH_WIDTH + 1, + ); + assert!( offset <= self.params.max_nodes, + "The parameter max_nodes is set too low, max_nodes: {}, offset: {}", + self.params.max_nodes, + offset, + ); - for offset in 0..height { + for offset in 0..self.params.max_nodes { assignf!(region, (self.q_enable, offset) => true.scalar())?; assignf!(region, (self.q_first, offset) => (offset == 0).scalar())?; - assignf!(region, (self.q_last, offset) => (offset == height - 2).scalar())?; } Ok(()) }, )?; - Ok(height) + Ok(()) } /// Loads MPT fixed table @@ -653,6 +653,8 @@ pub struct MPTCircuit { pub keccak_data: Vec>, /// log2(height) pub degree: usize, + /// Maximal number of nodes MPT can prove (for example, one branch has 16 nodes) + pub max_nodes: usize, /// Can be used to test artificially created tests with keys without known their known /// preimage. ONLY ENABLE FOR TESTS! pub disable_preimage_check: bool, @@ -667,6 +669,8 @@ pub struct MPTCircuitParams { pub degree: usize, /// pub disable_preimage_check: bool, + /// Maximal number of nodes MPT can prove (for example, one branch has 16 nodes) + pub max_nodes: usize, } impl MPTCircuitParams { @@ -694,6 +698,7 @@ impl Circuit for MPTCircuit { MPTCircuitParams { degree: self.degree, disable_preimage_check: self.disable_preimage_check, + max_nodes: self.max_nodes, } } @@ -717,9 +722,9 @@ impl Circuit for MPTCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let challenges = _challenges.values(&mut layouter); - let height = config.assign(&mut layouter, &self.nodes, &challenges)?; + config.assign(&mut layouter, &self.nodes, &challenges)?; config.load_fixed_table(&mut layouter)?; - config.load_mult_table(&mut layouter, &challenges, height)?; + config.load_mult_table(&mut layouter, &challenges, self.max_nodes)?; config .keccak_table .dev_load(&mut layouter, &self.keccak_data, &challenges)?; @@ -789,10 +794,12 @@ mod tests { let disable_preimage_check = nodes[0].start.clone().unwrap().disable_preimage_check; let degree = 15; + let max_nodes = 520; let circuit = MPTCircuit:: { nodes, keccak_data, degree, + max_nodes, disable_preimage_check, _marker: PhantomData, }; diff --git a/zkevm-circuits/src/mpt_circuit/account_leaf.rs b/zkevm-circuits/src/mpt_circuit/account_leaf.rs index 43ff136b56..2b16ff34c7 100644 --- a/zkevm-circuits/src/mpt_circuit/account_leaf.rs +++ b/zkevm-circuits/src/mpt_circuit/account_leaf.rs @@ -149,10 +149,10 @@ impl AccountLeafConfig { require!(config.main_data.is_below_account => false); let mut key_rlc = vec![0.expr(); 2]; - let mut nonce = vec![Word::>::new([0.expr(), 0.expr()]); 2]; - let mut balance = vec![Word::>::new([0.expr(), 0.expr()]); 2]; - let mut storage = vec![Word::>::new([0.expr(), 0.expr()]); 2]; - let mut codehash = vec![Word::>::new([0.expr(), 0.expr()]); 2]; + let mut nonce = vec![Word::zero(); 2]; + let mut balance = vec![Word::zero(); 2]; + let mut storage = vec![Word::zero(); 2]; + let mut codehash = vec![Word::zero(); 2]; let mut leaf_no_key_rlc = vec![0.expr(); 2]; let mut leaf_no_key_rlc_mult = vec![0.expr(); 2]; let mut value_list_num_bytes = vec![0.expr(); 2]; @@ -385,8 +385,7 @@ impl AccountLeafConfig { [ config.main_data.proof_type.expr(), true.expr(), - address_item.word().lo() - + address_item.word().hi() * pow::value::(256.scalar(), 16), + address_item.word().compress(), config.main_data.new_root.lo().expr(), config.main_data.new_root.hi().expr(), config.main_data.old_root.lo().expr(), @@ -459,10 +458,7 @@ impl AccountLeafConfig { require!((1.expr(), address_item.bytes_le()[1..21].rlc(&cb.keccak_r), 20.expr(), key.lo(), key.hi()) =>> @KECCAK); } }}; - let to_hi = Expression::::Constant(pow::value::(256.scalar(), 16)); - let lo = address_item.word().lo(); - let hi = address_item.word().hi() * to_hi; - let address = lo + hi; + let address = address_item.word().compress(); ifx! {not!(config.parent_data[false.idx()].is_placeholder) => { ifx! {not!(config.is_non_existing_account_proof) => { @@ -471,7 +467,7 @@ impl AccountLeafConfig { &mut cb.base, address.clone(), proof_type.clone(), - Word::>::new([0.expr(), 0.expr()]), + Word::zero(), config.main_data.new_root.expr(), config.main_data.old_root.expr(), Word::>::new([new_value_lo, new_value_hi]), @@ -485,11 +481,11 @@ impl AccountLeafConfig { &mut cb.base, address.clone(), proof_type.clone(), - Word::>::new([0.expr(), 0.expr()]), + Word::zero(), config.main_data.new_root.expr(), config.main_data.old_root.expr(), - Word::>::new([0.expr(), 0.expr()]), - Word::>::new([0.expr(), 0.expr()]), + Word::zero(), + Word::zero(), ); }}; } elsex { @@ -503,10 +499,10 @@ impl AccountLeafConfig { &mut cb.base, address, proof_type, - Word::>::new([0.expr(), 0.expr()]), + Word::zero(), config.main_data.new_root.expr(), config.main_data.old_root.expr(), - Word::>::new([0.expr(), 0.expr()]), + Word::zero(), Word::>::new([old_value_lo, old_value_hi]), ); }}; @@ -558,10 +554,10 @@ impl AccountLeafConfig { // Key let mut key_rlc = vec![0.scalar(); 2]; - let mut nonce = vec![Word::::new([0.scalar(), 0.scalar()]); 2]; - let mut balance = vec![Word::::new([0.scalar(), 0.scalar()]); 2]; - let mut storage = vec![Word::::new([0.scalar(), 0.scalar()]); 2]; - let mut codehash = vec![Word::::new([0.scalar(), 0.scalar()]); 2]; + let mut nonce = vec![Word::zero_f(); 2]; + let mut balance = vec![Word::zero_f(); 2]; + let mut storage = vec![Word::zero_f(); 2]; + let mut codehash = vec![Word::zero_f(); 2]; let mut key_data = vec![KeyDataWitness::default(); 2]; let mut parent_data = vec![ParentDataWitness::default(); 2]; for is_s in [true, false] { @@ -710,16 +706,13 @@ impl AccountLeafConfig { )?; // Anything following this node is below the account - let lo = address_item.word::().lo(); - let hi: F = address_item.word::().hi() * pow::value::(256.scalar(), 16); - let address = lo + hi; MainData::witness_store( region, offset, &mut memory[main_memory()], main_data.proof_type, true, - address, + address_item.word().compress_f(), main_data.new_root, main_data.old_root, )?; @@ -734,20 +727,11 @@ impl AccountLeafConfig { } else if is_codehash_mod { (MPTProofType::CodeHashChanged, codehash) } else if is_account_delete_mod { - ( - MPTProofType::AccountDestructed, - vec![Word::::new([0.scalar(), 0.scalar()]); 2], - ) + (MPTProofType::AccountDestructed, vec![Word::zero_f(); 2]) } else if is_non_existing_proof { - ( - MPTProofType::AccountDoesNotExist, - vec![Word::::new([0.scalar(), 0.scalar()]); 2], - ) + (MPTProofType::AccountDoesNotExist, vec![Word::zero_f(); 2]) } else { - ( - MPTProofType::Disabled, - vec![Word::::new([0.scalar(), 0.scalar()]); 2], - ) + (MPTProofType::Disabled, vec![Word::zero_f(); 2]) }; if account.is_mod_extension[0] || account.is_mod_extension[1] { @@ -762,10 +746,10 @@ impl AccountLeafConfig { let mut new_value = value[false.idx()]; let mut old_value = value[true.idx()]; if parent_data[false.idx()].is_placeholder { - new_value = word::Word::::new([0.scalar(), 0.scalar()]); + new_value = word::Word::zero_f(); } else if is_non_existing_proof { - new_value = word::Word::::new([0.scalar(), 0.scalar()]); - old_value = word::Word::::new([0.scalar(), 0.scalar()]); + new_value = word::Word::zero_f(); + old_value = word::Word::zero_f(); } mpt_config.mpt_table.assign_cached( region, @@ -774,7 +758,7 @@ impl AccountLeafConfig { address: Value::known(from_bytes::value( &account.address.iter().cloned().rev().collect::>(), )), - storage_key: word::Word::::new([0.scalar(), 0.scalar()]).into_value(), + storage_key: word::Word::zero_f().into_value(), proof_type: Value::known(proof_type.scalar()), new_root: main_data.new_root.into_value(), old_root: main_data.old_root.into_value(), diff --git a/zkevm-circuits/src/mpt_circuit/branch.rs b/zkevm-circuits/src/mpt_circuit/branch.rs index ae3fda4705..5909eb0c8a 100644 --- a/zkevm-circuits/src/mpt_circuit/branch.rs +++ b/zkevm-circuits/src/mpt_circuit/branch.rs @@ -351,7 +351,7 @@ impl BranchGadget { let key_mult_post_branch = *key_mult * mult; // Set the branch we'll take - let mut mod_node_hash_word = [word::Word::::new([0.scalar(), 0.scalar()]); 2]; + let mut mod_node_hash_word = [word::Word::zero_f(); 2]; let mut mod_node_hash_rlc = [0.scalar(); 2]; for is_s in [true, false] { ( diff --git a/zkevm-circuits/src/mpt_circuit/extension.rs b/zkevm-circuits/src/mpt_circuit/extension.rs index 28d6957c44..4663bdea09 100644 --- a/zkevm-circuits/src/mpt_circuit/extension.rs +++ b/zkevm-circuits/src/mpt_circuit/extension.rs @@ -100,7 +100,7 @@ impl ExtensionGadget { require!((FixedTableTag::ExtOddKey.expr(), first_byte, config.is_key_part_odd.expr()) =>> @FIXED); let mut branch_rlp_rlc = vec![0.expr(); 2]; - let mut branch_rlp_word = vec![Word::>::new([0.expr(), 0.expr()]); 2]; + let mut branch_rlp_word = vec![Word::zero(); 2]; for is_s in [true, false] { // In C we have the key nibbles, we check below only for S. if is_s { diff --git a/zkevm-circuits/src/mpt_circuit/extension_branch.rs b/zkevm-circuits/src/mpt_circuit/extension_branch.rs index 9962b9c9b7..50301fd4f8 100644 --- a/zkevm-circuits/src/mpt_circuit/extension_branch.rs +++ b/zkevm-circuits/src/mpt_circuit/extension_branch.rs @@ -158,7 +158,7 @@ impl ExtensionBranchConfig { branch.mod_rlc[is_s.idx()].expr(), false.expr(), false.expr(), - Word::>::new([0.expr(), 0.expr()]) + Word::zero(), ); } elsex { // For the placeholder branch / extension node the values did not change, we reuse @@ -291,7 +291,7 @@ impl ExtensionBranchConfig { mod_node_hash_rlc[is_s.idx()], false, false, - Word::::new([0.scalar(), 0.scalar()]), + Word::zero_f(), )?; } else { KeyData::witness_store( diff --git a/zkevm-circuits/src/mpt_circuit/helpers.rs b/zkevm-circuits/src/mpt_circuit/helpers.rs index 8adfd3cbd2..ffdd697504 100644 --- a/zkevm-circuits/src/mpt_circuit/helpers.rs +++ b/zkevm-circuits/src/mpt_circuit/helpers.rs @@ -1081,11 +1081,8 @@ impl IsPlaceholderLeafGadget { Expression::Constant(empty_hash.hi()), ]), ); - let is_nil_in_branch_at_mod_index = IsEqualWordGadget::construct( - &mut cb.base, - &parent_word, - &Word::>::new([0.expr(), 0.expr()]), - ); + let is_nil_in_branch_at_mod_index = + IsEqualWordGadget::construct(&mut cb.base, &parent_word, &Word::zero()); Self { is_empty_trie, diff --git a/zkevm-circuits/src/mpt_circuit/start.rs b/zkevm-circuits/src/mpt_circuit/start.rs index 906928a8eb..ae9d1ab40b 100644 --- a/zkevm-circuits/src/mpt_circuit/start.rs +++ b/zkevm-circuits/src/mpt_circuit/start.rs @@ -40,7 +40,7 @@ impl StartConfig { config.proof_type = cb.query_cell(); - let mut root = vec![Word::new([0.expr(), 0.expr()]); 2]; + let mut root = vec![Word::zero(); 2]; for is_s in [true, false] { root[is_s.idx()] = root_items[is_s.idx()].word(); } @@ -96,7 +96,7 @@ impl StartConfig { self.proof_type .assign(region, offset, start.proof_type.scalar())?; - let mut root = vec![Word::new([0.scalar(), 0.scalar()]); 2]; + let mut root = vec![Word::zero_f(); 2]; for is_s in [true, false] { root[is_s.idx()] = rlp_values[is_s.idx()].word(); } diff --git a/zkevm-circuits/src/mpt_circuit/storage_leaf.rs b/zkevm-circuits/src/mpt_circuit/storage_leaf.rs index 17a82d96ab..9dcf7a514b 100644 --- a/zkevm-circuits/src/mpt_circuit/storage_leaf.rs +++ b/zkevm-circuits/src/mpt_circuit/storage_leaf.rs @@ -104,7 +104,7 @@ impl StorageLeafConfig { require!(config.main_data.is_below_account => true); let mut key_rlc = vec![0.expr(); 2]; - let mut value_word = vec![Word::>::new([0.expr(), 0.expr()]); 2]; + let mut value_word = vec![Word::zero(); 2]; let mut value_rlp_rlc = vec![0.expr(); 2]; let mut value_rlp_rlc_mult = vec![0.expr(); 2]; @@ -191,7 +191,7 @@ impl StorageLeafConfig { // Placeholder leaves default to value `0`. ifx! {is_placeholder_leaf => { - require!(value_word[is_s.idx()] => [0.expr(), 0.expr()]); + require!(value_word[is_s.idx()] => Word::zero()); }} // Make sure the RLP encoding is correct. @@ -246,11 +246,11 @@ impl StorageLeafConfig { ParentData::store( cb, &mut ctx.memory[parent_memory(is_s)], - word::Word::>::new([0.expr(), 0.expr()]), + word::Word::zero(), 0.expr(), true.expr(), false.expr(), - word::Word::>::new([0.expr(), 0.expr()]), + word::Word::zero(), ); } @@ -395,7 +395,7 @@ impl StorageLeafConfig { address_item.word(), config.main_data.new_root.expr(), config.main_data.old_root.expr(), - Word::>::new([0.expr(), 0.expr()]), + Word::zero(), value_word[true.idx()].clone(), ); }}; @@ -436,7 +436,7 @@ impl StorageLeafConfig { let mut key_data = vec![KeyDataWitness::default(); 2]; let mut parent_data = vec![ParentDataWitness::default(); 2]; let mut key_rlc = vec![0.scalar(); 2]; - let mut value_word = vec![Word::::new([0.scalar(), 0.scalar()]); 2]; + let mut value_word = vec![Word::zero_f(); 2]; for is_s in [true, false] { self.is_mod_extension[is_s.idx()].assign( region, @@ -596,10 +596,10 @@ impl StorageLeafConfig { let mut new_value = value_word[false.idx()]; let mut old_value = value_word[true.idx()]; if parent_data[false.idx()].is_placeholder { - new_value = word::Word::::new([0.scalar(), 0.scalar()]); + new_value = word::Word::zero_f(); } else if is_non_existing_proof { - new_value = word::Word::::new([0.scalar(), 0.scalar()]); - old_value = word::Word::::new([0.scalar(), 0.scalar()]); + new_value = word::Word::zero_f(); + old_value = word::Word::zero_f(); } mpt_config.mpt_table.assign_cached( region, diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index 99b2683328..786d235fed 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -70,7 +70,7 @@ use crate::{ witness::{block_convert, Block, MptUpdates}, }; use bus_mapping::{ - circuit_input_builder::{CircuitInputBuilder, FixedCParams}, + circuit_input_builder::{CircuitInputBuilder, FeatureConfig, FixedCParams}, mock::BlockData, }; use eth_types::{geth_types::GethData, Field}; @@ -99,20 +99,8 @@ pub struct SuperCircuitConfig { exp_circuit: ExpCircuitConfig, } -/// Circuit configuration arguments -pub struct SuperCircuitConfigArgs { - /// Max txs - pub max_txs: usize, - /// Max withdrawals - pub max_withdrawals: usize, - /// Max calldata - pub max_calldata: usize, - /// Mock randomness - pub mock_randomness: F, -} - impl SubCircuitConfig for SuperCircuitConfig { - type ConfigArgs = SuperCircuitConfigArgs; + type ConfigArgs = SuperCircuitParams; /// Configure SuperCircuitConfig fn new( @@ -122,6 +110,7 @@ impl SubCircuitConfig for SuperCircuitConfig { max_withdrawals, max_calldata, mock_randomness, + feature_config, }: Self::ConfigArgs, ) -> Self { let tx_table = TxTable::construct(meta); @@ -221,6 +210,7 @@ impl SubCircuitConfig for SuperCircuitConfig { exp_table, u8_table, u16_table, + feature_config, }, ); @@ -263,6 +253,8 @@ pub struct SuperCircuit { pub keccak_circuit: KeccakCircuit, /// Circuits Parameters pub circuits_params: FixedCParams, + /// Feature Config + pub feature_config: FeatureConfig, /// Mock randomness pub mock_randomness: F, } @@ -317,6 +309,7 @@ impl SubCircuit for SuperCircuit { exp_circuit, keccak_circuit, circuits_params: block.circuits_params, + feature_config: block.feature_config, mock_randomness: block.randomness, } } @@ -390,6 +383,7 @@ pub struct SuperCircuitParams { max_withdrawals: usize, max_calldata: usize, mock_randomness: F, + feature_config: FeatureConfig, } impl Circuit for SuperCircuit { @@ -407,19 +401,12 @@ impl Circuit for SuperCircuit { max_withdrawals: self.circuits_params.max_withdrawals, max_calldata: self.circuits_params.max_calldata, mock_randomness: self.mock_randomness, + feature_config: self.feature_config, } } fn configure_with_params(meta: &mut ConstraintSystem, params: Self::Params) -> Self::Config { - Self::Config::new( - meta, - SuperCircuitConfigArgs { - max_txs: params.max_txs, - max_withdrawals: params.max_withdrawals, - max_calldata: params.max_calldata, - mock_randomness: params.mock_randomness, - }, - ) + Self::Config::new(meta, params) } fn configure(_meta: &mut ConstraintSystem) -> Self::Config { diff --git a/zkevm-circuits/src/super_circuit/test.rs b/zkevm-circuits/src/super_circuit/test.rs index dd9b4aaf13..97f3cb4329 100644 --- a/zkevm-circuits/src/super_circuit/test.rs +++ b/zkevm-circuits/src/super_circuit/test.rs @@ -17,6 +17,7 @@ fn super_circuit_degree() { max_withdrawals: 5, max_calldata: 32, mock_randomness: Fr::from(0x100), + feature_config: FeatureConfig::default(), }; SuperCircuit::configure_with_params(&mut cs, params); log::info!("super circuit degree: {}", cs.degree()); diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index c150f60003..6968f8ce73 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -6,7 +6,10 @@ use crate::{ util::SubCircuit, witness::{Block, Rw}, }; -use bus_mapping::{circuit_input_builder::FixedCParams, mock::BlockData}; +use bus_mapping::{ + circuit_input_builder::{FeatureConfig, FixedCParams}, + mock::BlockData, +}; use eth_types::geth_types::GethData; use itertools::all; use std::cmp; @@ -81,6 +84,7 @@ const NUM_BLINDING_ROWS: usize = 64; pub struct CircuitTestBuilder { test_ctx: Option>, circuits_params: Option, + feature_config: Option, block: Option>, block_modifiers: Vec)>>, } @@ -91,6 +95,7 @@ impl CircuitTestBuilder { CircuitTestBuilder { test_ctx: None, circuits_params: None, + feature_config: None, block: None, block_modifiers: vec![], } @@ -126,6 +131,13 @@ impl CircuitTestBuilder { self } + /// Configure [`FeatureConfig`] + pub fn feature(mut self, feature_config: FeatureConfig) -> Self { + assert!(self.feature_config.is_none(), "Already configured"); + self.feature_config = Some(feature_config); + self + } + /// Allows to pass a [`Block`] already built to the constructor. pub fn block(mut self, block: Block) -> Self { self.block = Some(block); @@ -156,7 +168,8 @@ impl CircuitTestBuilder { .as_ref() .ok_or(CircuitTestError::NotEnoughAttributes)?; let block: GethData = block.clone().into(); - let builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); + let builder = BlockData::new_from_geth_data(block.clone()) + .new_circuit_input_builder_with_feature(self.feature_config.unwrap_or_default()); let builder = builder .handle_block(&block.eth_block, &block.geth_traces) .map_err(|err| CircuitTestError::CannotHandleBlock(err.to_string()))?; @@ -175,12 +188,19 @@ impl CircuitTestBuilder { let (active_gate_rows, active_lookup_rows) = EvmCircuit::::get_active_rows(&block); - let circuit = EvmCircuitCached::get_test_circuit_from_block(block); - let prover = MockProver::::run(k, &circuit, vec![]).map_err(|err| { - CircuitTestError::SynthesisFailure { - circuit: Circuit::EVM, - reason: err, - } + // Mainnet EVM circuit constraints can be cached for test performance. + // No cache for EVM circuit with customized features + let prover = if block.feature_config.is_mainnet() { + let circuit = EvmCircuitCached::get_test_circuit_from_block(block); + MockProver::::run(k, &circuit, vec![]) + } else { + let circuit = EvmCircuit::get_test_circuit_from_block(block); + MockProver::::run(k, &circuit, vec![]) + }; + + let prover = prover.map_err(|err| CircuitTestError::SynthesisFailure { + circuit: Circuit::EVM, + reason: err, })?; prover diff --git a/zkevm-circuits/src/util/word.rs b/zkevm-circuits/src/util/word.rs index 5b4078a625..98a31fa87a 100644 --- a/zkevm-circuits/src/util/word.rs +++ b/zkevm-circuits/src/util/word.rs @@ -18,6 +18,10 @@ use crate::evm_circuit::util::{from_bytes, CachedRegion, Cell}; /// evm word 32 bytes, half word 16 bytes const N_BYTES_HALF_WORD: usize = 16; +const BASE_128_BYTES: [u8; 32] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +]; + /// The EVM word for witness #[derive(Clone, Debug, Copy)] pub struct WordLimbs { @@ -343,25 +347,43 @@ impl Word> { } } -impl WordExpr for Word> { +impl + Clone> WordExpr for Word { fn to_word(&self) -> Word> { - self.word_expr().to_word() + self.map(|limb| limb.expr()) + } +} + +impl Word { + /// zero word + pub fn zero_f() -> Self { + Self::new([F::ZERO, F::ZERO]) + } + + /// one word + pub fn one_f() -> Self { + Self::new([F::ONE, F::ZERO]) + } + + /// Convert address (h160) to single field element. + /// This method is Address specific + pub fn compress_f(&self) -> F { + self.lo() + self.hi() * F::from_repr(BASE_128_BYTES).unwrap() } } impl Word> { /// create word from lo limb with hi limb as 0. caller need to guaranteed to be 128 bits. pub fn from_lo_unchecked(lo: Expression) -> Self { - Self(WordLimbs::, 2>::new([lo, 0.expr()])) + Self::new([lo, 0.expr()]) } /// zero word pub fn zero() -> Self { - Self(WordLimbs::, 2>::new([0.expr(), 0.expr()])) + Self::new([0.expr(), 0.expr()]) } /// one word pub fn one() -> Self { - Self(WordLimbs::, 2>::new([1.expr(), 0.expr()])) + Self::new([1.expr(), 0.expr()]) } /// select based on selector. Here assume selector is 1/0 therefore no overflow check @@ -398,11 +420,11 @@ impl Word> { pub fn mul_unchecked(self, rhs: Self) -> Self { Word::new([self.lo() * rhs.lo(), self.hi() * rhs.hi()]) } -} -impl WordExpr for Word> { - fn to_word(&self) -> Word> { - self.clone() + /// Convert address (h160) to single expression. + /// This method is Address specific + pub fn compress(&self) -> Expression { + self.lo() + self.hi() * Expression::Constant(F::from_repr(BASE_128_BYTES).unwrap()) } } diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index a8e6127ad6..5c839601e6 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -7,7 +7,7 @@ use crate::{ util::{log2_ceil, word, SubCircuit}, }; use bus_mapping::{ - circuit_input_builder::{self, CopyEvent, ExpEvent, FixedCParams, Withdrawal}, + circuit_input_builder::{self, CopyEvent, ExpEvent, FeatureConfig, FixedCParams, Withdrawal}, state_db::CodeDB, Error, }; @@ -43,6 +43,8 @@ pub struct Block { pub exp_circuit_pad_to: usize, /// Circuit Setup Parameters pub circuits_params: FixedCParams, + /// Feature Config + pub feature_config: FeatureConfig, /// Inputs to the SHA3 opcode pub sha3_inputs: Vec>, /// State root of the previous block @@ -288,6 +290,7 @@ pub fn block_convert( exp_events: block.exp_events.clone(), sha3_inputs: block.sha3_inputs.clone(), circuits_params: builder.circuits_params, + feature_config: builder.feature_config, exp_circuit_pad_to: ::default(), prev_state_root: block.prev_state_root, keccak_inputs: circuit_input_builder::keccak_inputs(block, code_db)?,