From 16dad5ed2fd2f840232948f2b0661e30019735f0 Mon Sep 17 00:00:00 2001 From: "sm.wu" Date: Wed, 28 Feb 2024 14:20:47 +0800 Subject: [PATCH] [wip] testing intermediate chunk --- bus-mapping/src/circuit_input_builder.rs | 49 ++- testool/src/statetest/executor.rs | 9 +- zkevm-circuits/src/evm_circuit.rs | 4 +- zkevm-circuits/src/evm_circuit/execution.rs | 25 ++ .../src/evm_circuit/execution/begin_chunk.rs | 7 +- .../src/evm_circuit/execution/end_block.rs | 4 +- .../src/evm_circuit/execution/end_chunk.rs | 28 +- .../src/evm_circuit/execution/end_tx.rs | 2 +- zkevm-circuits/src/evm_circuit/step.rs | 5 + .../src/evm_circuit/util/common_gadget.rs | 1 + zkevm-circuits/src/test_util.rs | 349 +++++++++--------- zkevm-circuits/src/witness/chunk.rs | 1 + 12 files changed, 271 insertions(+), 213 deletions(-) diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index 95f7ae1ca68..c2bf0bdf4c4 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -357,6 +357,7 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { // Optain the first op of the next GethExecStep, for fixed case also lookahead let (mut cib, mut tx, mut tx_ctx) = (self.clone(), tx, tx_ctx); let mut cib_ref = cib.state_ref(&mut tx, &mut tx_ctx); + println!("aa before self.block_ctx.rwc.0 {}", self.block_ctx.rwc.0); let next_ops = if let Some((i, step)) = next_geth_step { log::trace!("chunk at {}th opcode {:?} ", i, step.op); gen_associated_ops(&step.op, &mut cib_ref, &geth_trace.struct_logs[i..])?.remove(0) @@ -364,6 +365,7 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { log::trace!("chunk at EndTx"); gen_associated_steps(&mut cib_ref, ExecState::EndTx)? }; + println!("aa before self.block_ctx.rwc.0 {}", self.block_ctx.rwc.0); let last_copy = self.block.copy_events.len(); // Generate EndChunk and proceed to the next if it's not the last chunk @@ -421,6 +423,12 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { step.op, self.chunk_rws() ); + println!( + "handle {}th opcode {:?} rws = {:?}", + i, + step.op, + self.chunk_rws() + ); let exec_steps = gen_associated_ops( &step.op, &mut self.state_ref(&mut tx, &mut tx_ctx), @@ -431,7 +439,8 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { .map(|step| tx.calls().get(step.call_index).unwrap().clone()); tx.steps_mut().extend(exec_steps); } - + println!("before end_tx rws = {:?}", self.chunk_rws()); + println!("before end_tx block rws = {:?}", self.block_ctx.rwc.0); // Peek the end_tx_step self.check_and_chunk( geth_trace, @@ -445,6 +454,8 @@ 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.clone()); + println!("after end_tx rws = {:?}", self.chunk_rws()); + println!("after end_tx block rws = {:?}", self.block_ctx.rwc.0); (end_tx_step, last_call) } else if self.feature_config.invalid_tx { // chunk before hands to avoid chunk between [InvalidTx, BeginTx) @@ -466,8 +477,7 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { Ok(res) } - // TODO Fix this, for current logic on processing `call` is incorrect - // TODO re-design `gen_chunk_associated_steps` to separate RW + // generate chunk related steps fn gen_chunk_associated_steps(&mut self, step: &mut ExecStep, rw: RW) { let STEP_STATE_LEN = 10; let mut dummy_tx = Transaction::default(); @@ -560,15 +570,20 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { fn set_begin_chunk(&mut self, first_step: &ExecStep) { let mut begin_chunk = first_step.clone(); begin_chunk.exec_state = ExecState::BeginChunk; + begin_chunk.bus_mapping_instance = Vec::new(); self.gen_chunk_associated_steps(&mut begin_chunk, RW::READ); self.chunks[self.chunk_ctx.idx].begin_chunk = Some(begin_chunk); } fn set_end_chunk(&mut self, next_step: &ExecStep) { + println!("before self.block_ctx.rwc.0 {}", self.block_ctx.rwc.0); + println!("next step {:?}", next_step); let mut end_chunk = next_step.clone(); end_chunk.exec_state = ExecState::EndChunk; + end_chunk.bus_mapping_instance = Vec::new(); self.gen_chunk_associated_steps(&mut end_chunk, RW::WRITE); self.gen_chunk_padding(&mut end_chunk); + println!("after self.block_ctx.rwc.0 {}", self.block_ctx.rwc.0); self.chunks[self.chunk_ctx.idx].end_chunk = Some(end_chunk); } @@ -710,7 +725,11 @@ impl CircuitInputBuilder { println!("--------------{:?}", self.circuits_params); // accumulates gas across all txs in the block let (last_step, last_call) = self.begin_handle_block(eth_block, geth_traces)?; - let last_step = last_step.unwrap_or_default(); + let mut dummy_next_step = last_step.unwrap_or_default(); + (0..dummy_next_step.rw_indices_len()).for_each(|_| { + dummy_next_step.rwc.inc_pre(); + dummy_next_step.rwc_inner_chunk.inc_pre(); + }); assert!(self.circuits_params.max_rws().is_some()); @@ -734,16 +753,29 @@ impl CircuitInputBuilder { last_call.clone(), ); } else { - // it doent matter what step was put to set_end_chunk/set_begin_chunk on no-used - // chunks before end_block. Just need to make sure it's step lookup is consistency. - self.set_end_chunk(&last_step); + // since there is no next step, we cook dummy next step from last step to reuse + // existing field while update its `rwc`. + println!("for end_chunk dummy_next_step {:?}", dummy_next_step); + self.set_end_chunk(&dummy_next_step); + dummy_next_step.rwc = self.block_ctx.rwc; self.commit_chunk_ctx( true, eth_block.transactions.len(), last_copy, last_call.clone(), ); - self.set_begin_chunk(&last_step); + dummy_next_step.rwc_inner_chunk = self.chunk_ctx.rwc; + println!("for begin_chunk dummy_next_step {:?}", dummy_next_step); + self.set_begin_chunk(&dummy_next_step); + dummy_next_step.rwc = self.block_ctx.rwc; + dummy_next_step.rwc_inner_chunk = self.chunk_ctx.rwc; + println!("for end_block dummy_next_step {:?}", dummy_next_step); + self.block.end_block = dummy_next_step.clone(); + self.cur_chunk_mut().padding = { + let mut padding = dummy_next_step.clone(); + padding.exec_state = ExecState::Padding; + Some(padding) + }; } Ok::<(), Error>(()) })?; @@ -773,6 +805,7 @@ impl CircuitInputBuilder { fn set_end_block(&mut self) -> Result<(), Error> { let mut end_block = self.block.end_block.clone(); end_block.rwc = self.block_ctx.rwc; + end_block.exec_state = ExecState::EndBlock; end_block.rwc_inner_chunk = self.chunk_ctx.rwc; let mut dummy_tx = Transaction::default(); diff --git a/testool/src/statetest/executor.rs b/testool/src/statetest/executor.rs index 824370510ec..a731188ca7c 100644 --- a/testool/src/statetest/executor.rs +++ b/testool/src/statetest/executor.rs @@ -348,12 +348,9 @@ pub fn run_test( let block: Block = zkevm_circuits::evm_circuit::witness::block_convert(&builder).unwrap(); - let chunk: Chunk = - zkevm_circuits::evm_circuit::witness::chunk_convert(&block, &builder) - .unwrap() - .remove(0); - - CircuitTestBuilder::<1, 1>::new_from_block(block, chunk) + let chunks: Vec> = + zkevm_circuits::evm_circuit::witness::chunk_convert(&block, &builder).unwrap(); + CircuitTestBuilder::<1, 1>::new_from_block(block, chunks) .run_with_result() .map_err(|err| match err { CircuitTestError::VerificationFailed { reasons, .. } => { diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index e50b619fe3f..53e8cdbde17 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -638,7 +638,9 @@ mod evm_circuit_stats { TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b).unwrap(), ) .block_modifier(Box::new(|_block, chunk| { - chunk.fixed_param.max_evm_rows = (1 << 18) - 100 + chunk + .iter_mut() + .for_each(|chunk| chunk.fixed_param.max_evm_rows = (1 << 18) - 100); })) .run(); } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index fa80d03e2ef..fd7e99f2cb4 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -815,6 +815,10 @@ impl ExecutionConfig { let step_curr_rw_counter = cb.curr.state.rw_counter.clone(); let step_curr_rw_counter_offset = cb.rw_counter_offset(); + if execution_state == ExecutionState::BeginChunk { + cb.debug_expression("step_curr_rw_counter.expr()", step_curr_rw_counter.expr()); + } + let debug_expressions = cb.debug_expressions.clone(); // Extract feature config here before cb is built. @@ -1228,6 +1232,11 @@ impl ExecutionConfig { if next.is_none() { break; } + println!( + "in assignment. ExecStep {:?} rwc {:?} rwc inner {:?}", + cur.2.exec_state, cur.2.rwc, cur.2.rwc_inner_chunk + ); + second_last_real_step = Some(cur); // record offset of current step before assignment second_last_real_step_offset = offset; @@ -1252,6 +1261,7 @@ impl ExecutionConfig { // part3: assign end chunk or end block if let Some(end_chunk) = &chunk.end_chunk { + println!("assigning end chunk"); debug_assert_eq!(ExecutionState::EndChunk.get_step_height(), 1); offset = assign_padding_or_step( (&dummy_tx, &last_call, end_chunk), @@ -1263,6 +1273,7 @@ impl ExecutionConfig { next_step_after_real_step = Some(end_chunk.clone()); } } else { + println!("assigning end block"); assert!( chunk.chunk_context.is_last_chunk(), "If not end_chunk, must be end_block at last chunk" @@ -1421,6 +1432,13 @@ impl ExecutionConfig { step, call ); + println!( + "assign_exec_step offset: {} state {:?} step: {:?} call: {:?}", + offset, + step.execution_state(), + step, + call + ); } // Make the region large enough for the current step and the next step. // The next step's next step may also be accessed, so make the region large @@ -1438,6 +1456,13 @@ impl ExecutionConfig { // so their witness values need to be known to be able // to correctly calculate the intermediate value. if let Some(next_step) = next_step { + println!( + "assign_exec_step nextstep offset: {} state {:?} step: {:?} call: {:?}", + offset, + next_step.2.execution_state(), + next_step.2, + next_step.1 + ); self.assign_exec_step_int( region, offset + height, diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_chunk.rs b/zkevm-circuits/src/evm_circuit/execution/begin_chunk.rs index 9687b720914..149dfe050c9 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_chunk.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_chunk.rs @@ -4,7 +4,7 @@ use crate::{ evm_circuit::{ step::ExecutionState, util::{ - constraint_builder::{EVMConstraintBuilder, StepStateTransition}, + constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, witness::{Block, Call, Chunk, ExecStep, Transaction}, @@ -29,7 +29,10 @@ impl ExecutionGadget for BeginChunkGadget { fn configure(cb: &mut EVMConstraintBuilder) -> Self { // state lookup cb.step_state_lookup(0.expr()); - let step_state_transition = StepStateTransition::same(); + let step_state_transition = StepStateTransition { + rw_counter: Delta(cb.rw_counter_offset()), + ..StepStateTransition::same() + }; cb.require_step_state_transition(step_state_transition); Self { _marker: PhantomData {}, diff --git a/zkevm-circuits/src/evm_circuit/execution/end_block.rs b/zkevm-circuits/src/evm_circuit/execution/end_block.rs index bbe2b38ad88..9854d01368f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_block.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_block.rs @@ -174,7 +174,9 @@ mod test { // finish required tests using this witness block CircuitTestBuilder::<2, 1>::new_from_test_ctx(ctx) .block_modifier(Box::new(move |_block, chunk| { - chunk.fixed_param.max_evm_rows = evm_circuit_pad_to + chunk + .iter_mut() + .for_each(|chunk| chunk.fixed_param.max_evm_rows = evm_circuit_pad_to); })) .run(); } diff --git a/zkevm-circuits/src/evm_circuit/execution/end_chunk.rs b/zkevm-circuits/src/evm_circuit/execution/end_chunk.rs index 4231a06cd3f..ef37bd7a213 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_chunk.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_chunk.rs @@ -60,12 +60,15 @@ impl ExecutionGadget for EndChunkGadget { _: &Call, step: &ExecStep, ) -> Result<(), Error> { + let rwc_before_padding = step.bus_mapping_instance.len() + - 1 // start op + - 2; // 2 padding op self.rw_table_padding_gadget.assign_exec_step( region, offset, block, chunk, - (step.rwc_inner_chunk.0 - 1 + step.bus_mapping_instance.len()) as u64, + (step.rwc_inner_chunk.0 - 1 + rwc_before_padding) as u64, step, )?; Ok(()) @@ -167,18 +170,17 @@ mod test { |block, _tx| block.number(0xcafeu64), ) .unwrap(); - (0..6).for_each(|chunk_id| { - CircuitTestBuilder::new_from_test_ctx(test_ctx.clone()) - .params({ - FixedCParams { - total_chunks: 6, - max_rws: 90, - max_txs: 2, - ..Default::default() - } - }) - .run_chunk(chunk_id); - }); + CircuitTestBuilder::new_from_test_ctx(test_ctx.clone()) + .params({ + FixedCParams { + total_chunks: 2, + max_rws: 180, + max_txs: 2, + ..Default::default() + } + }) + .run_multiple_chunks_with_result(Some(2)) + .unwrap(); } #[test] diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs index 422d79e97ac..14509543587 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -453,7 +453,7 @@ mod test { max_txs: 5, ..Default::default() }) - .build_block(0, 1) + .build_block(None) .unwrap(); block.rws.0[&Target::CallContext] diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 9d90677b420..4ea3f08afaf 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -850,6 +850,11 @@ impl Step { offset, Value::known(F::from(step.rwc_inner_chunk.into())), )?; + println!( + "execstate {:?} self.state.call_id {:?}", + step.execution_state(), + F::from(call.call_id as u64) + ); self.state .call_id .assign(region, offset, Value::known(F::from(call.call_id as u64)))?; diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index 76ccd319676..38dcba8c76b 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -1336,6 +1336,7 @@ impl RwTablePaddingGadget { 1.expr(), max_rws.expr() - inner_rws_before_padding.expr(), ); + cb.condition(is_end_padding_exist.expr(), |cb| { cb.rw_table_padding_lookup(inner_rws_before_padding.expr() + 1.expr()); cb.rw_table_padding_lookup(max_rws.expr() - 1.expr()); diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index b594fadeb41..3a609f25eef 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -11,7 +11,7 @@ use bus_mapping::{ mock::BlockData, }; use eth_types::geth_types::GethData; -use itertools::all; +use itertools::{all, Itertools}; use std::cmp; use thiserror::Error; @@ -86,8 +86,8 @@ pub struct CircuitTestBuilder { circuits_params: Option, feature_config: Option, block: Option>, - chunk: Option>, - block_modifiers: Vec, &mut Chunk)>>, + chunks: Option>>, + block_modifiers: Vec, &mut Vec>)>>, } impl CircuitTestBuilder { @@ -98,7 +98,7 @@ impl CircuitTestBuilder { circuits_params: None, feature_config: None, block: None, - chunk: None, + chunks: None, block_modifiers: vec![], } } @@ -111,8 +111,8 @@ impl CircuitTestBuilder { /// Generates a CTBC from a [`Block`] passed with all the other fields /// set to [`Default`]. - pub fn new_from_block(block: Block, chunk: Chunk) -> Self { - Self::empty().block_chunk(block, chunk) + pub fn new_from_block(block: Block, chunks: Vec>) -> Self { + Self::empty().set_block_chunk(block, chunks) } /// Allows to produce a [`TestContext`] which will serve as the generator of @@ -140,10 +140,10 @@ impl CircuitTestBuilder { self } - /// Allows to pass a [`Block`] already built to the constructor. - pub fn block_chunk(mut self, block: Block, chunk: Chunk) -> Self { + /// Allows to pass a [`Block`], [`Chunk`] vectors already built to the constructor. + pub fn set_block_chunk(mut self, block: Block, chunks: Vec>) -> Self { self.block = Some(block); - self.chunk = Some(chunk); + self.chunks = Some(chunks); self } @@ -153,7 +153,10 @@ impl CircuitTestBuilder { /// /// That removes the need in a lot of tests to build the block outside of /// the builder because they need to modify something particular. - pub fn block_modifier(mut self, modifier: Box, &mut Chunk)>) -> Self { + pub fn block_modifier( + mut self, + modifier: Box, &mut Vec>)>, + ) -> Self { self.block_modifiers.push(modifier); self } @@ -163,12 +166,11 @@ impl CircuitTestBuilder { /// build block pub fn build_block( &self, - chunk_index: usize, - total_chunk: usize, - ) -> Result<(Block, Chunk), CircuitTestError> { - if let (Some(block), Some(chunk)) = (&self.block, &self.chunk) { + total_chunks: Option, + ) -> Result<(Block, Vec>), CircuitTestError> { + if let (Some(block), Some(chunks)) = (&self.block, &self.chunks) { // If a block is specified, no need to modify the block - return Ok((block.clone(), chunk.clone())); + return Ok((block.clone(), chunks.clone())); } let block = self .test_ctx @@ -177,16 +179,19 @@ impl CircuitTestBuilder { let block: GethData = block.clone().into(); let builder = match self.circuits_params { Some(fixed_param) => { - assert!( - fixed_param.total_chunks == total_chunk, - "Total chunks unmatched with fixed param" - ); + if let Some(total_chunks) = total_chunks { + assert!( + fixed_param.total_chunks == total_chunks, + "Total chunks unmatched with fixed param" + ); + } + BlockData::new_from_geth_data_with_params(block.clone(), fixed_param) .new_circuit_input_builder_with_feature(self.feature_config.unwrap_or_default()) .handle_block(&block.eth_block, &block.geth_traces) .map_err(|err| CircuitTestError::CannotHandleBlock(err.to_string()))? } - None => BlockData::new_from_geth_data_chunked(block.clone(), total_chunk) + None => BlockData::new_from_geth_data_chunked(block.clone(), total_chunks.unwrap_or(1)) .new_circuit_input_builder_with_feature(self.feature_config.unwrap_or_default()) .handle_block(&block.eth_block, &block.geth_traces) .map_err(|err| CircuitTestError::CannotHandleBlock(err.to_string()))?, @@ -194,64 +199,153 @@ impl CircuitTestBuilder { // Build a witness block from trace result. let mut block = crate::witness::block_convert(&builder) .map_err(|err| CircuitTestError::CannotConvertBlock(err.to_string()))?; - let mut chunk = crate::witness::chunk_convert(&block, &builder) - .unwrap() - .remove(chunk_index); + let mut chunks = crate::witness::chunk_convert(&block, &builder).unwrap(); for modifier_fn in &self.block_modifiers { - modifier_fn.as_ref()(&mut block, &mut chunk); + modifier_fn.as_ref()(&mut block, &mut chunks); } - Ok((block, chunk)) + Ok((block, chunks)) } fn run_evm_circuit_test( &self, block: Block, - chunk: Chunk, + chunks: Vec>, ) -> Result<(), CircuitTestError> { - let k = block.get_test_degree(&chunk); + if chunks.is_empty() { + return Err(CircuitTestError::SanityCheckChunks( + "empty chunks vector".to_string(), + )); + } + + let k = block.get_test_degree(&chunks[0]); let (active_gate_rows, active_lookup_rows) = - EvmCircuit::::get_active_rows(&block, &chunk); - - // 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, chunk); - let instance = circuit.instance(); - MockProver::::run(k, &circuit, instance) - } else { - let circuit = EvmCircuit::get_test_circuit_from_block(block, chunk); - let instance = circuit.instance(); - MockProver::::run(k, &circuit, instance) - }; + EvmCircuit::::get_active_rows(&block, &chunks[0]); - let prover = prover.map_err(|err| CircuitTestError::SynthesisFailure { - circuit: Circuit::EVM, - reason: err, - })?; + // check consistency between chunk + chunks + .iter() + .tuple_windows() + .find_map(|(prev_chunk, chunk)| { + // global consistent + if prev_chunk.permu_alpha != chunk.permu_alpha { + return Some(Err(CircuitTestError::SanityCheckChunks( + "mismatch challenge alpha".to_string(), + ))); + } + if prev_chunk.permu_gamma != chunk.permu_gamma { + return Some(Err(CircuitTestError::SanityCheckChunks( + "mismatch challenge gamma".to_string(), + ))); + } - prover - .verify_at_rows( - active_gate_rows.iter().cloned(), - active_lookup_rows.iter().cloned(), - ) - .map_err(|err| CircuitTestError::VerificationFailed { - circuit: Circuit::EVM, - reasons: err, + if prev_chunk.by_address_rw_fingerprints.ending_row + != chunk.by_address_rw_fingerprints.prev_ending_row + { + return Some(Err(CircuitTestError::SanityCheckChunks( + "mismatch by_address_rw_fingerprints ending_row".to_string(), + ))); + } + if prev_chunk.by_address_rw_fingerprints.mul_acc + != chunk.by_address_rw_fingerprints.prev_mul_acc + { + return Some(Err(CircuitTestError::SanityCheckChunks( + "mismatch by_address_rw_fingerprints mul_acc".to_string(), + ))); + } + + if prev_chunk.chrono_rw_fingerprints.ending_row + != chunk.chrono_rw_fingerprints.prev_ending_row + { + return Some(Err(CircuitTestError::SanityCheckChunks( + "mismatch chrono_rw_fingerprints ending_row".to_string(), + ))); + } + if prev_chunk.chrono_rw_fingerprints.mul_acc + != chunk.chrono_rw_fingerprints.prev_mul_acc + { + return Some(Err(CircuitTestError::SanityCheckChunks( + "mismatch chrono_rw_fingerprints mul_acc".to_string(), + ))); + } + None + }) + .unwrap_or_else(|| Ok(()))?; + + // check last chunk fingerprints + chunks + .last() + .map(|last_chunk| { + if last_chunk.by_address_rw_fingerprints.mul_acc + != last_chunk.chrono_rw_fingerprints.mul_acc + { + Err(CircuitTestError::SanityCheckChunks( + "mismatch last rw_fingerprint mul_acc".to_string(), + )) + } else { + Ok(()) + } + }) + .unwrap_or_else(|| Ok(()))?; + + // stop on first chunk validation error + chunks + .into_iter() + .skip(1) + .enumerate() + .find_map(|(i, chunk)| { + // 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.clone(), chunk); + let instance = circuit.instance(); + MockProver::::run(k, &circuit, instance) + } else { + let circuit = EvmCircuit::get_test_circuit_from_block(block.clone(), chunk); + let instance = circuit.instance(); + MockProver::::run(k, &circuit, instance) + }; + + if let Err(err) = prover { + return Some(Err(CircuitTestError::SynthesisFailure { + circuit: Circuit::EVM, + reason: err, + })); + } + + let prover = prover.unwrap(); + + let res = prover + .verify_at_rows( + active_gate_rows.iter().cloned(), + active_lookup_rows.iter().cloned(), + ) + .map_err(|err| CircuitTestError::VerificationFailed { + circuit: Circuit::EVM, + reasons: err, + }); + if res.is_err() { + println!("failed on chunk index {}", i); + Some(res) + } else { + None + } }) + .unwrap_or_else(|| Ok(())) } // TODO: use randomness as one of the circuit public input, since randomness in // state circuit and evm circuit must be same fn run_state_circuit_test( &self, block: Block, - chunk: Chunk, + chunks: Vec>, ) -> Result<(), CircuitTestError> { - let rows_needed = StateCircuit::::min_num_rows_block(&block, &chunk).1; + let rows_needed = StateCircuit::::min_num_rows_block(&block, &chunks[0]).1; let k = cmp::max(log2_ceil(rows_needed + NUM_BLINDING_ROWS), 18); - let state_circuit = StateCircuit::::new(&chunk); - let max_rws = chunk.fixed_param.max_rws; + let state_circuit = StateCircuit::::new(&chunks[0]); + let max_rws = &chunks[0].fixed_param.max_rws; let instance = state_circuit.instance(); let prover = MockProver::::run(k, &state_circuit, instance).map_err(|err| { CircuitTestError::SynthesisFailure { @@ -265,7 +359,7 @@ impl CircuitTestBuilder { .iter() .filter(|rw| !matches!(rw, Rw::Start { .. })) .count(); - let rows = max_rws - non_start_rows_len..max_rws; + let rows = max_rws - non_start_rows_len..*max_rws; prover.verify_at_rows(rows.clone(), rows).map_err(|err| { CircuitTestError::VerificationFailed { circuit: Circuit::EVM, @@ -273,140 +367,30 @@ impl CircuitTestBuilder { } }) } - /// Triggers the `CircuitTestBuilder` to convert the [`TestContext`] if any, - /// into a [`Block`] and specified numbers of [`Chunk`]s - /// and apply the default or provided modifiers or - /// circuit checks to the provers generated for the State and EVM circuits. - /// One [`Chunk`] is generated by default and the first one is run unless - /// [`FixedCParams`] is set. - pub fn run(self) { - println!("--------------{:?}", self.circuits_params); - if let Some(fixed_params) = self.circuits_params { - self.run_dynamic_chunk(fixed_params.total_chunks, 0); - } else { - self.run_dynamic_chunk(1, 0); - } - } /// Triggers the `CircuitTestBuilder` to convert the [`TestContext`] if any, /// into a [`Block`] and apply the default or provided block_modifiers or /// circuit checks to the provers generated for the State and EVM circuits. pub fn run_with_result(self) -> Result<(), CircuitTestError> { - let (block, chunk) = self.build_block(0, 1)?; - - self.run_evm_circuit_test(block.clone(), chunk.clone())?; - self.run_state_circuit_test(block, chunk) + self.run_multiple_chunks_with_result(None) } /// Triggers the `CircuitTestBuilder` to convert the [`TestContext`] if any, - /// into a [`Block`] and specified numbers of [`Chunk`]s. - /// [`FixedCParams`] must be set to build the amount of chunks - /// and run the indexed [`Chunk`]. - pub fn run_chunk(self, chunk_index: usize) { - let total_chunk = self - .circuits_params - .expect("Fixed param not specified") - .total_chunks; - self.run_dynamic_chunk(total_chunk, chunk_index); - } + /// into a [`Block`] and apply the default or provided block_modifiers or + /// circuit checks to the provers generated for the State and EVM circuits. + pub fn run_multiple_chunks_with_result( + self, + total_chunk: Option, + ) -> Result<(), CircuitTestError> { + let (block, chunks) = self.build_block(total_chunk)?; - /// Triggers the `CircuitTestBuilder` to convert the [`TestContext`] if any, - /// into a [`Block`] and specified numbers of [`Chunk`]s with dynamic chunk size - /// if [`FixedCParams`] is not set, otherwise the `total_chunk` must matched. - pub fn run_dynamic_chunk(self, total_chunk: usize, chunk_index: usize) { - assert!(chunk_index < total_chunk, "Chunk index exceed total chunks"); - let (block, chunk) = if self.block.is_some() && self.chunk.is_some() { - (self.block.unwrap(), self.chunk.unwrap()) - } else if self.test_ctx.is_some() { - let block: GethData = self.test_ctx.unwrap().into(); - let builder = match self.circuits_params { - Some(fixed_param) => { - assert_eq!( - fixed_param.total_chunks, total_chunk, - "Total chunks unmatched with fixed param" - ); - BlockData::new_from_geth_data_with_params(block.clone(), fixed_param) - .new_circuit_input_builder_with_feature( - self.feature_config.unwrap_or_default(), - ) - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap() - } - None => BlockData::new_from_geth_data_chunked(block.clone(), total_chunk) - .new_circuit_input_builder_with_feature(self.feature_config.unwrap_or_default()) - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(), - }; - // FIXME(Cecilia): debug - println!("----after-handle-block-----"); - builder.chunks.iter().for_each(|c| { - println!( - "{:?}\n{:?}\nbegin {:?}\nend {:?}\n", - c.ctx, - c.fixed_param, - c.begin_chunk.is_some(), - c.end_chunk.is_some() - ); - println!("----------"); - }); - println!("block rwc = {:?}", builder.block_ctx.rwc); - - // Build a witness block from trace result. - let mut block = crate::witness::block_convert(&builder).unwrap(); - - let mut chunk = crate::witness::chunk_convert(&block, &builder) - .unwrap() - .remove(chunk_index); - - for modifier_fn in self.block_modifiers { - modifier_fn.as_ref()(&mut block, &mut chunk); - } - (block, chunk) - } else { - panic!("No attribute to build a block was passed to the CircuitTestBuilder") - }; - let params = chunk.fixed_param; - - // Run evm circuit test - { - let k = block.get_test_degree(&chunk); - - let (_active_gate_rows, _active_lookup_rows) = - EvmCircuit::::get_active_rows(&block, &chunk); - - let _prover = if block.feature_config.is_mainnet() { - let circuit = - EvmCircuitCached::get_test_circuit_from_block(block.clone(), chunk.clone()); - let instance = circuit.instance(); - MockProver::::run(k, &circuit, instance) - } else { - let circuit = EvmCircuit::get_test_circuit_from_block(block.clone(), chunk.clone()); - let instance = circuit.instance(); - MockProver::::run(k, &circuit, instance) - }; - - // self.evm_checks.as_ref()(prover, &active_gate_rows, &active_lookup_rows) - } + self.run_evm_circuit_test(block.clone(), chunks.clone())?; + self.run_state_circuit_test(block, chunks) + } - // Run state circuit test - // TODO: use randomness as one of the circuit public input, since randomness in - // state circuit and evm circuit must be same - { - let rows_needed = StateCircuit::::min_num_rows_block(&block, &chunk).1; - let k = cmp::max(log2_ceil(rows_needed + NUM_BLINDING_ROWS), 18); - let state_circuit = StateCircuit::::new(&chunk); - let instance = state_circuit.instance(); - let _prover = MockProver::::run(k, &state_circuit, instance).unwrap(); - // Skip verification of Start rows to accelerate testing - let non_start_rows_len = state_circuit - .rows - .iter() - .filter(|rw| !matches!(rw, Rw::Padding { .. })) - .count(); - let _rows: Vec = (params.max_rws - non_start_rows_len..params.max_rws).collect(); - - // self.state_checks.as_ref()(prover, &rows, &rows); - } + /// Convenient method to run in test cases that error handling is not required. + pub fn run(self) { + self.run_with_result().unwrap() } } @@ -431,6 +415,9 @@ pub enum CircuitTestError { /// Something worng in the block_convert #[error("CannotConvertBlock({0})")] CannotConvertBlock(String), + /// Something worng in the chunk_convert + #[error("SanityCheckChunks({0})")] + SanityCheckChunks(String), /// Problem constructing MockProver #[error("SynthesisFailure({circuit:?}, reason: {reason:?})")] SynthesisFailure { diff --git a/zkevm-circuits/src/witness/chunk.rs b/zkevm-circuits/src/witness/chunk.rs index 15127e172aa..1b061e1790e 100755 --- a/zkevm-circuits/src/witness/chunk.rs +++ b/zkevm-circuits/src/witness/chunk.rs @@ -186,6 +186,7 @@ pub fn chunk_convert( prev_chunk_last_chrono_rw, ); + println!("{i} chunk.ctx.clone() {:?}", chunk.ctx.clone()); chunks.push(Chunk { permu_alpha: alpha, permu_gamma: gamma,