diff --git a/Cargo.lock b/Cargo.lock index 5f6781d1d6..a99cb46ac0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1675,6 +1675,7 @@ dependencies = [ "digest 0.7.6", "eth-types", "halo2_proofs", + "itertools 0.10.5", "rand", "sha3 0.7.3", "strum 0.24.1", @@ -2189,6 +2190,7 @@ dependencies = [ "ethers-contract-abigen", "glob", "halo2_proofs", + "itertools 0.10.5", "lazy_static", "log", "mock", diff --git a/bus-mapping/src/circuit_input_builder.rs b/bus-mapping/src/circuit_input_builder.rs index bfa77d1811..8f6e1d6a0c 100644 --- a/bus-mapping/src/circuit_input_builder.rs +++ b/bus-mapping/src/circuit_input_builder.rs @@ -4,6 +4,7 @@ mod access; mod block; mod call; +mod chunk; mod execution; mod input_state_ref; #[cfg(test)] @@ -11,17 +12,21 @@ mod tracer_tests; mod transaction; mod withdrawal; -use self::access::gen_state_access_trace; +use self::{access::gen_state_access_trace, chunk::Chunk}; use crate::{ error::Error, evm::opcodes::{gen_associated_ops, gen_associated_steps}, - operation::{CallContextField, Operation, RWCounter, StartOp, RW}, + operation::{ + CallContextField, Op, Operation, OperationContainer, PaddingOp, RWCounter, StartOp, + StepStateField, StepStateOp, RW, + }, rpc::GethClient, state_db::{self, CodeDB, StateDB}, }; pub use access::{Access, AccessSet, AccessValue, CodeSource}; pub use block::{Block, BlockContext}; pub use call::{Call, CallContext, CallKind}; +pub use chunk::ChunkContext; use core::fmt::Debug; use eth_types::{ self, geth_types, @@ -43,6 +48,9 @@ use std::{ pub use transaction::{Transaction, TransactionContext}; pub use withdrawal::{Withdrawal, WithdrawalContext}; +/// number of execution state fields +pub const N_EXEC_STATE: usize = 10; + /// Runtime Config /// /// Default to mainnet block @@ -80,9 +88,14 @@ impl FeatureConfig { } } +// RW_BUFFER_SIZE need to set to cover max rwc row contributed by a ExecStep +const RW_BUFFER_SIZE: usize = 30; + /// Circuit Setup Parameters -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub struct FixedCParams { + /// + pub total_chunks: usize, /// Maximum number of rw operations in the state circuit (RwTable length / /// number of rows). This must be at least the number of rw operations /// + 1, in order to allocate at least a Start row. @@ -125,29 +138,53 @@ pub struct FixedCParams { /// To reduce the testing overhead, we determine the parameters by the testing inputs. /// A new [`FixedCParams`] will be computed from the generated circuit witness. #[derive(Debug, Clone, Copy)] -pub struct DynamicCParams {} - +pub struct DynamicCParams { + /// Toatal number of chunks + pub total_chunks: usize, +} /// Circuit Setup Parameters. These can be fixed/concrete or unset/dynamic. pub trait CircuitsParams: Debug + Copy { - /// Returns the max number of rws allowed + /// Return the total number of chunks + fn total_chunks(&self) -> usize; + /// Set total number of chunks + fn set_total_chunk(&mut self, total_chunks: usize); + /// Return the maximun Rw fn max_rws(&self) -> Option; } impl CircuitsParams for FixedCParams { + fn total_chunks(&self) -> usize { + self.total_chunks + } + fn set_total_chunk(&mut self, total_chunks: usize) { + self.total_chunks = total_chunks; + } fn max_rws(&self) -> Option { Some(self.max_rws) } } impl CircuitsParams for DynamicCParams { + fn total_chunks(&self) -> usize { + self.total_chunks + } + fn set_total_chunk(&mut self, total_chunks: usize) { + self.total_chunks = total_chunks; + } fn max_rws(&self) -> Option { None } } +impl Default for DynamicCParams { + fn default() -> Self { + DynamicCParams { total_chunks: 1 } + } +} impl Default for FixedCParams { /// Default values for most of the unit tests of the Circuit Parameters fn default() -> Self { FixedCParams { + total_chunks: 1, max_rws: 1000, max_txs: 1, max_withdrawals: 1, @@ -173,7 +210,9 @@ impl Default for FixedCParams { /// the block. 2. For each [`eth_types::Transaction`] in the block, take the /// [`eth_types::GethExecTrace`] to build the circuit input associated with /// each transaction, and the bus-mapping operations associated with each -/// [`eth_types::GethExecStep`] in the [`eth_types::GethExecTrace`]. +/// [`eth_types::GethExecStep`] in the [`eth_types::GethExecTrace`]. 3. If `Rw`s +/// generated during Transactions exceed the `max_rws` threshold, seperate witness +/// into multiple chunks. /// /// The generated bus-mapping operations are: /// [`StackOp`](crate::operation::StackOp)s, @@ -182,7 +221,7 @@ impl Default for FixedCParams { /// [`OpcodeId`](crate::evm::OpcodeId)s used in each `ExecTrace` step so that /// the State Proof witnesses are already generated on a structured manner and /// ready to be added into the State circuit. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct CircuitInputBuilder { /// StateDB key-value DB pub sdb: StateDB, @@ -190,10 +229,14 @@ pub struct CircuitInputBuilder { pub code_db: CodeDB, /// Block pub block: Block, - /// Circuits Setup Parameters - pub circuits_params: C, + /// Chunk + pub chunks: Vec, /// Block Context pub block_ctx: BlockContext, + /// Chunk Context + pub chunk_ctx: ChunkContext, + /// Circuits Setup Parameters before chunking + pub circuits_params: C, /// Feature config pub feature_config: FeatureConfig, } @@ -208,16 +251,28 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { params: C, feature_config: FeatureConfig, ) -> Self { + let total_chunks = params.total_chunks(); + let chunks = vec![Chunk::default(); total_chunks]; Self { sdb, code_db, block, - circuits_params: params, + chunks, block_ctx: BlockContext::new(), + chunk_ctx: ChunkContext::new(total_chunks), + circuits_params: params, feature_config, } } + /// Set the total number of chunks for existing CircuitInputBuilder, + /// API for chunking the existing tests then run with a specific chunk + pub fn set_total_chunk(&mut self, total_chunks: usize) { + self.circuits_params.set_total_chunk(total_chunks); + self.chunks = vec![Chunk::default(); total_chunks]; + self.chunk_ctx.total_chunks = total_chunks; + } + /// Obtain a mutable reference to the state that the `CircuitInputBuilder` /// maintains, contextualized to a particular transaction and a /// particular execution step in that transaction. @@ -231,6 +286,7 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { code_db: &mut self.code_db, block: &mut self.block, block_ctx: &mut self.block_ctx, + chunk_ctx: &mut self.chunk_ctx, tx, tx_ctx, max_rws: self.circuits_params.max_rws(), @@ -288,81 +344,495 @@ impl<'a, C: CircuitsParams> CircuitInputBuilder { } } + // chunking and mutable bumping chunk_ctx once condition match + // return true on bumping to next chunk + fn check_and_chunk( + &mut self, + geth_trace: &GethExecTrace, + tx: Transaction, + tx_ctx: TransactionContext, + next_geth_step: Option<(usize, &GethExecStep)>, + last_call: Option, + ) -> Result { + // we dont chunk if + // 1. on last chunk + // 2. still got some buffer room before max_rws + let Some(max_rws) = self.circuits_params.max_rws() else { + // terminiate earlier due to no max_rws + return Ok(false); + }; + + if self.chunk_ctx.is_last_chunk() || self.chunk_rws() + RW_BUFFER_SIZE < max_rws { + return Ok(false); + }; + + // 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); + let mut 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) + } else { + log::trace!("chunk at EndTx"); + gen_associated_steps(&mut cib_ref, ExecState::EndTx)? + }; + + let last_copy = self.block.copy_events.len(); + // Generate EndChunk and proceed to the next if it's not the last chunk + // Set next step pre-state as end_chunk state + self.set_end_chunk(&next_ops, Some(&tx)); + + // need to update next_ops.rwc to catch block_ctx.rwc in `set_end_chunk` + next_ops.rwc = self.block_ctx.rwc; + + // tx.id start from 1, so it's equivalent to `next_tx_index` + self.commit_chunk_ctx(true, tx.id as usize, last_copy, last_call); + self.set_begin_chunk(&next_ops, Some(&tx)); + + Ok(true) + } + /// Handle a transaction with its corresponding execution trace to generate /// all the associated operations. Each operation is registered in /// `self.block.container`, and each step stores the /// [`OperationRef`](crate::exec_trace::OperationRef) to each of the /// generated operations. + /// When dynamic builder handles Tx with is_chuncked = false, we don't chunk + /// When fixed builder handles Tx with is_chuncked = true, we chunk fn handle_tx( &mut self, eth_tx: ð_types::Transaction, geth_trace: &GethExecTrace, is_last_tx: bool, tx_index: u64, - ) -> Result<(), Error> { + ) -> Result<(ExecStep, Option), Error> { let mut tx = self.new_tx(tx_index, eth_tx, !geth_trace.failed)?; let mut tx_ctx = TransactionContext::new(eth_tx, geth_trace, is_last_tx)?; - if !geth_trace.invalid { + let res = if !geth_trace.invalid { // Generate BeginTx step let begin_tx_step = gen_associated_steps( &mut self.state_ref(&mut tx, &mut tx_ctx), ExecState::BeginTx, )?; + let mut last_call = Some(tx.calls().get(begin_tx_step.call_index).unwrap().clone()); tx.steps_mut().push(begin_tx_step); - for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() { - let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx); - log::trace!("handle {}th opcode {:?} ", index, geth_step.op); + let mut trace = geth_trace.struct_logs.iter().enumerate().peekable(); + while let Some((peek_i, peek_step)) = trace.peek() { + // Check the peek step and chunk if needed + self.check_and_chunk( + geth_trace, + tx.clone(), + tx_ctx.clone(), + Some((*peek_i, peek_step)), + last_call.clone(), + )?; + // Proceed to the next step + let (i, step) = trace.next().expect("Peeked step should exist"); + log::trace!( + "handle {}th opcode {:?} {:?} rws = {:?}", + i, + step.op, + step, + self.chunk_rws() + ); let exec_steps = gen_associated_ops( - &geth_step.op, - &mut state_ref, - &geth_trace.struct_logs[index..], + &step.op, + &mut self.state_ref(&mut tx, &mut tx_ctx), + &geth_trace.struct_logs[i..], )?; + last_call = exec_steps + .last() + .map(|step| tx.calls().get(step.call_index).unwrap().clone()); tx.steps_mut().extend(exec_steps); } + // Peek the end_tx_step + self.check_and_chunk( + geth_trace, + tx.clone(), + tx_ctx.clone(), + None, + last_call.clone(), + )?; + // Generate EndTx step 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); + tx.steps_mut().push(end_tx_step.clone()); + (end_tx_step, last_call) } 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); + tx.steps_mut().push(invalid_tx_step.clone()); + // Peek the end_tx_step + let is_chunk = + self.check_and_chunk(geth_trace, tx.clone(), tx_ctx.clone(), None, None)?; + if is_chunk { + // TODO we dont support chunk after invalid_tx + // becasuse begin_chunk will constraints what next step execution state. + // And for next step either begin_tx or invalid_tx will both failed because + // begin_tx/invalid_tx define new execution state. + unimplemented!("dont support invalid_tx with multiple chunks") + } + + (invalid_tx_step, None) } else { panic!("invalid tx support not enabled") - } + }; self.sdb.commit_tx(); self.block.txs.push(tx); - Ok(()) + Ok(res) + } + + // generate chunk related steps + fn gen_chunk_associated_steps( + &mut self, + step: &mut ExecStep, + rw: RW, + tx: Option<&Transaction>, + ) { + let mut dummy_tx = Transaction::default(); + let mut dummy_tx_ctx = TransactionContext::default(); + + let rw_counters = (0..N_EXEC_STATE) + .map(|_| self.block_ctx.rwc.inc_pre()) + .collect::>(); + // just bump rwc in chunk_ctx as block_ctx rwc to assure same delta apply + let rw_counters_inner_chunk = (0..N_EXEC_STATE) + .map(|_| self.chunk_ctx.rwc.inc_pre()) + .collect::>(); + + let tags = { + let state = self.state_ref(&mut dummy_tx, &mut dummy_tx_ctx); + let last_call = tx + .map(|tx| tx.calls()[step.call_index].clone()) + .or_else(|| state.block.txs.last().map(|tx| tx.calls[0].clone())) + .unwrap(); + [ + (StepStateField::CodeHash, last_call.code_hash.to_word()), + (StepStateField::CallID, Word::from(last_call.call_id)), + (StepStateField::IsRoot, Word::from(last_call.is_root as u64)), + ( + StepStateField::IsCreate, + Word::from(last_call.is_create() as u64), + ), + (StepStateField::ProgramCounter, Word::from(step.pc)), + ( + StepStateField::StackPointer, + Word::from(step.stack_pointer()), + ), + (StepStateField::GasLeft, Word::from(step.gas_left)), + ( + StepStateField::MemoryWordSize, + Word::from(step.memory_word_size()), + ), + ( + StepStateField::ReversibleWriteCounter, + Word::from(step.reversible_write_counter), + ), + (StepStateField::LogID, Word::from(step.log_id)), + ] + }; + + debug_assert_eq!(N_EXEC_STATE, tags.len()); + let state = self.state_ref(&mut dummy_tx, &mut dummy_tx_ctx); + + tags.iter() + .zip_eq(rw_counters) + .zip_eq(rw_counters_inner_chunk) + .for_each(|(((tag, value), rw_counter), inner_rw_counter)| { + push_op( + &mut state.block.container, + step, + rw_counter, + inner_rw_counter, + rw, + StepStateOp { + field: tag.clone(), + value: *value, + }, + ); + }); + } + + /// Set the end status of a chunk including the current globle rwc + /// and commit the current chunk context, proceed to the next chunk + /// if needed + pub fn commit_chunk_ctx( + &mut self, + to_next: bool, + next_tx_index: usize, + next_copy_index: usize, + last_call: Option, + ) { + self.chunk_ctx.end_rwc = self.block_ctx.rwc.0; + self.chunk_ctx.end_tx_index = next_tx_index; + self.chunk_ctx.end_copy_index = next_copy_index; + self.cur_chunk_mut().ctx = self.chunk_ctx.clone(); + if to_next { + // add `-1` to include previous set and deal with transaction cross-chunk case + self.chunk_ctx + .bump(self.block_ctx.rwc.0, next_tx_index - 1, next_copy_index); + self.cur_chunk_mut().prev_last_call = last_call; + } + } + + fn set_begin_chunk(&mut self, first_step: &ExecStep, tx: Option<&Transaction>) { + let mut begin_chunk = ExecStep { + exec_state: ExecState::BeginChunk, + rwc: first_step.rwc, + gas_left: first_step.gas_left, + call_index: first_step.call_index, + ..ExecStep::default() + }; + self.gen_chunk_associated_steps(&mut begin_chunk, RW::READ, tx); + self.chunks[self.chunk_ctx.idx].begin_chunk = Some(begin_chunk); + } + + fn set_end_chunk(&mut self, next_step: &ExecStep, tx: Option<&Transaction>) { + let mut end_chunk = ExecStep { + exec_state: ExecState::EndChunk, + rwc: next_step.rwc, + rwc_inner_chunk: next_step.rwc_inner_chunk, + gas_left: next_step.gas_left, + call_index: next_step.call_index, + ..ExecStep::default() + }; + self.gen_chunk_associated_steps(&mut end_chunk, RW::WRITE, tx); + self.gen_chunk_padding(&mut end_chunk); + self.chunks[self.chunk_ctx.idx].end_chunk = Some(end_chunk); + } + + fn gen_chunk_padding(&mut self, step: &mut ExecStep) { + // rwc index start from 1 + let end_rwc = self.chunk_ctx.rwc.0; + let total_rws = end_rwc - 1; + let max_rws = self.cur_chunk().fixed_param.max_rws; + + assert!( + total_rws < max_rws, + "total_rws <= max_rws, total_rws={}, max_rws={}", + total_rws, + max_rws + ); + + let mut padding = step.clone(); + padding.exec_state = ExecState::Padding; + padding.bus_mapping_instance = vec![]; // there is no rw in padding step + + if self.chunk_ctx.is_first_chunk() { + push_op( + &mut self.block.container, + step, + RWCounter(1), + RWCounter(1), + RW::READ, + StartOp {}, + ); + } + + if max_rws - total_rws > 1 { + let (padding_start, padding_end) = (total_rws + 1, max_rws - 1); + push_op( + &mut self.block.container, + step, + RWCounter(padding_start), + RWCounter(padding_start), + RW::READ, + PaddingOp {}, + ); + if padding_end != padding_start { + push_op( + &mut self.block.container, + step, + RWCounter(padding_end), + RWCounter(padding_end), + RW::READ, + PaddingOp {}, + ); + } + } + self.chunks[self.chunk_ctx.idx].padding = Some(padding); + } + + /// Get the i-th mutable chunk + pub fn get_chunk_mut(&mut self, i: usize) -> &mut Chunk { + self.chunks.get_mut(i).expect("Chunk does not exist") + } + + /// Get the i-th chunk + pub fn get_chunk(&self, i: usize) -> Chunk { + self.chunks.get(i).expect("Chunk does not exist").clone() + } + + /// Get the current chunk + pub fn cur_chunk(&self) -> Chunk { + self.chunks[self.chunk_ctx.idx].clone() + } + + /// Get a mutable reference of current chunk + pub fn cur_chunk_mut(&mut self) -> &mut Chunk { + &mut self.chunks[self.chunk_ctx.idx] + } + + /// Get the previous chunk + pub fn prev_chunk(&self) -> Option { + if self.chunk_ctx.idx == 0 { + return None; + } + self.chunks.get(self.chunk_ctx.idx - 1).cloned() + } + + /// Total Rw in this chunk + pub fn chunk_rws(&self) -> usize { + self.chunk_ctx.rwc.0 - 1 } } impl CircuitInputBuilder { + /// First part of handle_block, only called by fixed Builder + pub fn begin_handle_block( + &mut self, + eth_block: &EthBlock, + geth_traces: &[eth_types::GethExecTrace], + ) -> Result<(Option, Option), Error> { + assert!( + self.circuits_params.max_rws().unwrap_or_default() > self.last_exec_step_rws_reserved(), + "Fixed max_rws not enough for rws reserve" + ); + + // accumulates gas across all txs in the block + let mut res = eth_block + .transactions + .iter() + .enumerate() + .map(|(idx, tx)| { + let geth_trace = &geth_traces[idx]; + // Transaction index starts from 1 + let tx_id = idx + 1; + self.handle_tx( + tx, + geth_trace, + tx_id == eth_block.transactions.len(), + tx_id as u64, + ) + .map(|(exec_step, last_call)| (Some(exec_step), last_call)) + }) + .collect::, Option)>, _>>()?; + // set eth_block + self.block.eth_block = eth_block.clone(); + self.set_value_ops_call_context_rwc_eor(); + if !res.is_empty() { + Ok(res.remove(res.len() - 1)) + } else { + Ok((None, None)) + } + } + /// Handle a block by handling each transaction to generate all the /// associated operations. pub fn handle_block( - &mut self, + mut self, eth_block: &EthBlock, geth_traces: &[eth_types::GethExecTrace], - ) -> Result<&CircuitInputBuilder, Error> { + ) -> Result, Error> { // accumulates gas across all txs in the block - self.begin_handle_block(eth_block, geth_traces)?; - self.set_end_block(self.circuits_params.max_rws)?; + let (last_step, last_call) = self.begin_handle_block(eth_block, geth_traces)?; + // since there is no next step, we cook dummy next step from last step to reuse + // existing field while update its `rwc`. + let mut dummy_next_step = { + let mut dummy_next_step = last_step.unwrap_or_default(); + // raise last step rwc to match with next step + (0..dummy_next_step.rw_indices_len()).for_each(|_| { + dummy_next_step.rwc.inc_pre(); + dummy_next_step.rwc_inner_chunk.inc_pre(); + }); + dummy_next_step + }; + + assert!(self.circuits_params.max_rws().is_some()); + + let last_copy = self.block.copy_events.len(); + + // TODO figure out and resolve generic param type and move fixed_param set inside + // commit_chunk_ctx. After fixed, then we can set fixed_param on all chunks + (0..self.circuits_params.total_chunks()).for_each(|idx| { + self.get_chunk_mut(idx).fixed_param = self.circuits_params; + }); + + // We fill dummy virtual steps: BeginChunk,EndChunk for redundant chunks + let last_process_chunk_id = self.chunk_ctx.idx; + (last_process_chunk_id..self.circuits_params.total_chunks()).try_for_each(|idx| { + if idx == self.circuits_params.total_chunks() - 1 { + self.set_end_block()?; + self.commit_chunk_ctx( + false, + eth_block.transactions.len(), + last_copy, + last_call.clone(), + ); + } else { + self.set_end_chunk(&dummy_next_step, None); + + self.commit_chunk_ctx( + true, + eth_block.transactions.len(), + last_copy, + last_call.clone(), + ); + // update dummy_next_step rwc to be used for next + dummy_next_step.rwc = self.block_ctx.rwc; + dummy_next_step.rwc_inner_chunk = self.chunk_ctx.rwc; + self.set_begin_chunk(&dummy_next_step, None); + dummy_next_step.rwc = self.block_ctx.rwc; + dummy_next_step.rwc_inner_chunk = self.chunk_ctx.rwc; + // update virtual step: end_block/padding so it can carry state context correctly + // TODO: enhance virtual step updating mechanism by having `running_next_step` + // defined in circuit_input_builder, so we dont need to + 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>(()) + })?; + + let used_chunks = self.chunk_ctx.idx + 1; + assert!( + used_chunks <= self.circuits_params.total_chunks(), + "Used more chunks than given total_chunks" + ); + assert!( + self.chunks.len() == self.chunk_ctx.idx + 1, + "number of chunks {} mis-match with chunk_ctx id {}", + self.chunks.len(), + self.chunk_ctx.idx + 1, + ); + + // Truncate chunks to the actual used amount & correct ctx.total_chunks + // Set length to the actual used amount of chunks + self.chunks.truncate(self.chunk_ctx.idx + 1); + self.chunks.iter_mut().for_each(|chunk| { + chunk.ctx.total_chunks = used_chunks; + }); + Ok(self) } - fn set_end_block(&mut self, max_rws: usize) -> Result<(), Error> { - let mut end_block_not_last = self.block.block_steps.end_block_not_last.clone(); - let mut end_block_last = self.block.block_steps.end_block_last.clone(); - end_block_not_last.rwc = self.block_ctx.rwc; - end_block_last.rwc = self.block_ctx.rwc; + 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(); let mut dummy_tx_ctx = TransactionContext::default(); @@ -370,65 +840,120 @@ impl CircuitInputBuilder { if let Some(call_id) = state.block.txs.last().map(|tx| tx.calls[0].call_id) { state.call_context_read( - &mut end_block_last, + &mut end_block, call_id, CallContextField::TxId, Word::from(state.block.txs.len() as u64), )?; } - let mut push_op = |step: &mut ExecStep, rwc: RWCounter, rw: RW, op: StartOp| { - let op_ref = state.block.container.insert(Operation::new(rwc, rw, op)); - step.bus_mapping_instance.push(op_ref); + // EndBlock step should also be padded to max_rws similar to EndChunk + self.gen_chunk_padding(&mut end_block); + self.block.end_block = end_block; + Ok(()) + } +} + +fn push_op( + container: &mut OperationContainer, + step: &mut ExecStep, + rwc: RWCounter, + rwc_inner_chunk: RWCounter, + rw: RW, + op: T, +) { + let op_ref = container.insert(Operation::new(rwc, rwc_inner_chunk, rw, op)); + step.bus_mapping_instance.push(op_ref); +} + +impl CircuitInputBuilder { + /// return the rw row reserved for end_block/end_chunk + pub fn last_exec_step_rws_reserved(&self) -> usize { + // rw ops reserved for EndBlock + let end_block_rws = if self.chunk_ctx.is_last_chunk() && self.chunk_rws() > 0 { + 1 + } else { + 0 }; + // rw ops reserved for EndChunk + let end_chunk_rws = if !self.chunk_ctx.is_last_chunk() { + N_EXEC_STATE + } else { + 0 + }; + end_block_rws + end_chunk_rws + 1 + } - // rwc index start from 1 - let total_rws = state.block_ctx.rwc.0 - 1; - // We need at least 1 extra Start row - #[allow(clippy::int_plus_one)] - { - assert!( - total_rws + 1 <= max_rws, - "total_rws + 1 <= max_rws, total_rws={}, max_rws={}", - total_rws, - max_rws - ); - } - let (padding_start, padding_end) = (1, max_rws - total_rws); // rw counter start from 1 - push_op( - &mut end_block_last, - RWCounter(padding_start), - RW::READ, - StartOp {}, - ); - if padding_end != padding_start { - push_op( - &mut end_block_last, - RWCounter(padding_end), - RW::READ, - StartOp {}, - ); - } + fn compute_param(&self, eth_block: &EthBlock) -> FixedCParams { + let max_txs = eth_block.transactions.len(); + let max_withdrawals = eth_block.withdrawals.as_ref().unwrap().len(); + let max_bytecode = self.code_db.num_rows_required_for_bytecode_table(); - self.block.block_steps.end_block_not_last = end_block_not_last; - self.block.block_steps.end_block_last = end_block_last; - Ok(()) + let max_calldata = eth_block + .transactions + .iter() + .fold(0, |acc, tx| acc + tx.input.len()); + let max_exp_steps = self + .block + .exp_events + .iter() + .fold(0usize, |acc, e| acc + e.steps.len()); + // The `+ 2` is used to take into account the two extra empty copy rows needed + // to satisfy the query at `Rotation(2)` performed inside of the + // `rows[2].value == rows[0].value * r + rows[1].value` requirement in the RLC + // Accumulation gate. + let max_copy_rows = self + .block + .copy_events + .iter() + .fold(0, |acc, c| acc + c.bytes.len()) + * 2 + + 4; // disabled and unused rows. + + let max_rws = >::into(self.block_ctx.rwc) - 1 + + self.last_exec_step_rws_reserved(); + + // Computing the number of rows for the EVM circuit requires the size of ExecStep, + // which is determined in the code of zkevm-circuits and cannot be imported here. + // When the evm circuit receives a 0 value it dynamically computes the minimum + // number of rows necessary. + let max_evm_rows = 0; + // Similarly, computing the number of rows for the Keccak circuit requires + // constants that cannot be accessed from here (NUM_ROUNDS and KECCAK_ROWS). + // With a 0 value the keccak circuit computes dynamically the minimum number of rows + // needed. + let max_keccak_rows = 0; + FixedCParams { + total_chunks: self.circuits_params.total_chunks(), + max_rws, + max_txs, + max_withdrawals, + max_calldata, + max_copy_rows, + max_exp_steps, + max_bytecode, + max_evm_rows, + max_keccak_rows, + max_vertical_circuit_rows: 0, + } } } -impl CircuitInputBuilder { - /// First part of handle_block, common for dynamic and static circuit parameters. - pub fn begin_handle_block( - &mut self, +impl CircuitInputBuilder { + fn dry_run( + &self, eth_block: &EthBlock, geth_traces: &[eth_types::GethExecTrace], - ) -> Result<(), Error> { + ) -> Result, Error> { + let mut cib = self.clone(); + cib.circuits_params.total_chunks = 1; + cib.chunk_ctx.total_chunks = 1; // accumulates gas across all txs in the block for (idx, tx) in eth_block.transactions.iter().enumerate() { let geth_trace = &geth_traces[idx]; // Transaction index starts from 1 let tx_id = idx + 1; - self.handle_tx( + cib.handle_tx( tx, geth_trace, tx_id == eth_block.transactions.len(), @@ -436,88 +961,51 @@ impl CircuitInputBuilder { )?; } // set eth_block - self.block.eth_block = eth_block.clone(); - self.set_value_ops_call_context_rwc_eor(); - Ok(()) + cib.block.eth_block = eth_block.clone(); + cib.set_value_ops_call_context_rwc_eor(); + + debug_assert!( + cib.chunk_ctx.idx == 0, + "processing {} > 1 chunk", + cib.chunk_ctx.idx + ); // dry run mode only one chunk + + Ok(cib) } -} -impl CircuitInputBuilder { + /// Handle a block by handling each transaction to generate all the - /// associated operations. From these operations, the optimal circuit parameters - /// are derived and set. + /// associated operations. Dry run the block to determind the target + /// [`FixedCParams`] from to total number of chunks. pub fn handle_block( - mut self, + self, eth_block: &EthBlock, geth_traces: &[eth_types::GethExecTrace], ) -> Result, Error> { - self.begin_handle_block(eth_block, geth_traces)?; - - // Compute subcircuits parameters - let c_params = { - let max_txs = eth_block.transactions.len(); - let max_withdrawals = eth_block.withdrawals.as_ref().unwrap().len(); - let max_bytecode = self.code_db.num_rows_required_for_bytecode_table(); - - let max_calldata = eth_block - .transactions - .iter() - .fold(0, |acc, tx| acc + tx.input.len()); - let max_exp_steps = self - .block - .exp_events - .iter() - .fold(0usize, |acc, e| acc + e.steps.len()); - // The `+ 2` is used to take into account the two extra empty copy rows needed - // to satisfy the query at `Rotation(2)` performed inside of the - // `rows[2].value == rows[0].value * r + rows[1].value` requirement in the RLC - // Accumulation gate. - let max_copy_rows = self - .block - .copy_events - .iter() - .fold(0, |acc, c| acc + c.bytes.len()) - * 2 - + 4; // disabled and unused rows. - - let total_rws_before_padding: usize = - >::into(self.block_ctx.rwc) - 1; // -1 since rwc start from index `1` - let max_rws_after_padding = total_rws_before_padding - + 1 // fill 1 to have exactly one StartOp padding in below `set_end_block` - + if total_rws_before_padding > 0 { 1 /*end_block -> CallContextFieldTag::TxId lookup*/ } else { 0 }; - // Computing the number of rows for the EVM circuit requires the size of ExecStep, - // which is determined in the code of zkevm-circuits and cannot be imported here. - // When the evm circuit receives a 0 value it dynamically computes the minimum - // number of rows necessary. - let max_evm_rows = 0; - // Similarly, computing the number of rows for the Keccak circuit requires - // constants that cannot be accessed from here (NUM_ROUNDS and KECCAK_ROWS). - // With a 0 value the keccak circuit computes dynamically the minimum number of rows - // needed. - let max_keccak_rows = 0; - FixedCParams { - max_rws: max_rws_after_padding, - max_txs, - max_withdrawals, - max_calldata, - max_copy_rows, - max_exp_steps, - max_bytecode, - max_evm_rows, - max_keccak_rows, - max_vertical_circuit_rows: 0, - } - }; - let mut cib = CircuitInputBuilder:: { + // Run the block without chunking and compute the blockwise params + let mut target_params = self + .dry_run(eth_block, geth_traces) + .expect("Dry run failure") + .compute_param(eth_block); + + // Calculate the chunkwise params from total number of chunks + let total_chunks = self.circuits_params.total_chunks; + target_params.total_chunks = total_chunks; + // count rws buffer here to left some space for extra virtual steps + target_params.max_rws = (target_params.max_rws + 1) / total_chunks + RW_BUFFER_SIZE; + + // Use a new builder with targeted params to handle the block + // chunking context is set to dynamic so for the actual param is update per chunk + let cib = CircuitInputBuilder:: { sdb: self.sdb, code_db: self.code_db, block: self.block, - circuits_params: c_params, + chunks: self.chunks, block_ctx: self.block_ctx, + chunk_ctx: ChunkContext::new(total_chunks), + circuits_params: target_params, feature_config: self.feature_config, }; - - cib.set_end_block(c_params.max_rws)?; - Ok(cib) + cib.handle_block(eth_block, geth_traces) } } @@ -809,14 +1297,14 @@ 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( + let builder = CircuitInputBuilder::new( sdb, code_db, block, self.circuits_params, self.feature_config, ); - builder.handle_block(eth_block, geth_traces)?; + let builder = builder.handle_block(eth_block, geth_traces)?; Ok(builder) } diff --git a/bus-mapping/src/circuit_input_builder/block.rs b/bus-mapping/src/circuit_input_builder/block.rs index 219a844ce4..eb592a73ec 100644 --- a/bus-mapping/src/circuit_input_builder/block.rs +++ b/bus-mapping/src/circuit_input_builder/block.rs @@ -43,19 +43,9 @@ impl BlockContext { } } -/// Block-wise execution steps that don't belong to any Transaction. -#[derive(Debug)] -pub struct BlockSteps { - /// EndBlock step that is repeated after the last transaction and before - /// reaching the last EVM row. - pub end_block_not_last: ExecStep, - /// Last EndBlock step that appears in the last EVM row. - pub end_block_last: ExecStep, -} - // TODO: Remove fields that are duplicated in`eth_block` /// Circuit Input related to a block. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Block { /// chain id pub chain_id: Word, @@ -80,8 +70,11 @@ pub struct Block { pub container: OperationContainer, /// Transactions contained in the block pub txs: Vec, - /// Block-wise steps - pub block_steps: BlockSteps, + /// End block step + pub end_block: ExecStep, + + // /// Chunk context + // pub chunk_context: ChunkContext, /// Copy events in this block. pub copy_events: Vec, /// Inputs to the SHA3 opcode as well as data hashed during the EVM execution like init code @@ -136,15 +129,9 @@ impl Block { prev_state_root, container: OperationContainer::new(), txs: Vec::new(), - block_steps: BlockSteps { - end_block_not_last: ExecStep { - exec_state: ExecState::EndBlock, - ..ExecStep::default() - }, - end_block_last: ExecStep { - exec_state: ExecState::EndBlock, - ..ExecStep::default() - }, + end_block: ExecStep { + exec_state: ExecState::EndBlock, + ..ExecStep::default() }, copy_events: Vec::new(), exp_events: Vec::new(), diff --git a/bus-mapping/src/circuit_input_builder/call.rs b/bus-mapping/src/circuit_input_builder/call.rs index 0f16847b36..2052e2c1ea 100644 --- a/bus-mapping/src/circuit_input_builder/call.rs +++ b/bus-mapping/src/circuit_input_builder/call.rs @@ -123,7 +123,7 @@ impl Call { } /// Context of a [`Call`]. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct CallContext { /// Index of call pub index: usize, @@ -151,7 +151,7 @@ impl CallContext { /// [`Operation::reversible`](crate::operation::Operation::reversible) that /// happened in them, that will be reverted at once when the call that initiated /// this reversion group eventually ends with failure (and thus reverts). -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct ReversionGroup { /// List of `index` and `reversible_write_counter_offset` of calls belong to /// this group. `reversible_write_counter_offset` is the number of diff --git a/bus-mapping/src/circuit_input_builder/chunk.rs b/bus-mapping/src/circuit_input_builder/chunk.rs new file mode 100644 index 0000000000..30ace0d91b --- /dev/null +++ b/bus-mapping/src/circuit_input_builder/chunk.rs @@ -0,0 +1,107 @@ +use super::{Call, ExecStep, FixedCParams}; +use crate::operation::RWCounter; + +#[derive(Debug, Default, Clone)] +pub struct Chunk { + /// current context + pub ctx: ChunkContext, + /// fixed param for the chunk + pub fixed_param: FixedCParams, + /// Begin op of a chunk + pub begin_chunk: Option, + /// End op of a chunk + pub end_chunk: Option, + /// Padding step that is repeated after the last transaction and before + /// reaching the last EVM row. + pub padding: Option, + /// + pub prev_last_call: Option, +} + +/// Context of chunking, used to track the current chunk index and inner rw counter +/// also the global rw counter from start to end. +#[derive(Debug, Clone)] +pub struct ChunkContext { + /// Index of current chunk, start from 0 + pub idx: usize, + /// Used to track the inner chunk counter in every operation in the chunk. + /// it will be reset for every new chunk. + /// Contains the next available value. + pub rwc: RWCounter, + /// Number of chunks + pub total_chunks: usize, + /// Initial global rw counter + pub initial_rwc: usize, + /// End global rw counter + pub end_rwc: usize, + /// tx range in block: [initial_tx_index, end_tx_index) + pub initial_tx_index: usize, + /// + pub end_tx_index: usize, + /// copy range in block: [initial_copy_index, end_copy_index) + pub initial_copy_index: usize, + /// + pub end_copy_index: usize, +} + +impl Default for ChunkContext { + fn default() -> Self { + Self::new(1) + } +} + +impl ChunkContext { + /// Create a new Self + pub fn new(total_chunks: usize) -> Self { + Self { + rwc: RWCounter::new(), + idx: 0, + total_chunks, + initial_rwc: 1, // rw counter start from 1 + end_rwc: 0, // end_rwc should be set in later phase + initial_tx_index: 0, + end_tx_index: 0, + initial_copy_index: 0, + end_copy_index: 0, + } + } + + /// New chunking context with one chunk + pub fn new_one_chunk() -> Self { + Self { + rwc: RWCounter::new(), + idx: 0, + total_chunks: 1, + initial_rwc: 1, // rw counter start from 1 + end_rwc: 0, // end_rwc should be set in later phase + initial_tx_index: 0, + end_tx_index: 0, + initial_copy_index: 0, + end_copy_index: 0, + } + } + + /// Proceed the context to next chunk, record the initial rw counter + /// update the chunk idx and reset the inner rw counter + pub fn bump(&mut self, initial_rwc: usize, initial_tx: usize, initial_copy: usize) { + assert!(self.idx + 1 < self.total_chunks, "Exceed total chunks"); + self.idx += 1; + self.rwc = RWCounter::new(); + self.initial_rwc = initial_rwc; + self.initial_tx_index = initial_tx; + self.initial_copy_index = initial_copy; + self.end_rwc = 0; + self.end_tx_index = 0; + self.end_copy_index = 0; + } + + /// Is first chunk + pub fn is_first_chunk(&self) -> bool { + self.idx == 0 + } + + /// Is last chunk + pub fn is_last_chunk(&self) -> bool { + self.total_chunks - self.idx - 1 == 0 + } +} diff --git a/bus-mapping/src/circuit_input_builder/execution.rs b/bus-mapping/src/circuit_input_builder/execution.rs index f99cb3bac7..fd5fc4310c 100644 --- a/bus-mapping/src/circuit_input_builder/execution.rs +++ b/bus-mapping/src/circuit_input_builder/execution.rs @@ -35,6 +35,8 @@ pub struct ExecStep { pub call_index: usize, /// The global counter when this step was executed. pub rwc: RWCounter, + /// The inner chunk counter when this step was executed. + pub rwc_inner_chunk: RWCounter, /// Reversible Write Counter. Counter of write operations in the call that /// will need to be undone in case of a revert. Value at the beginning of /// the step. @@ -59,6 +61,7 @@ impl ExecStep { step: &GethExecStep, call_ctx: &CallContext, rwc: RWCounter, + rwc_inner_chunk: RWCounter, reversible_write_counter: usize, log_id: usize, ) -> Self { @@ -73,6 +76,7 @@ impl ExecStep { gas_refund: step.refund, call_index: call_ctx.index, rwc, + rwc_inner_chunk, reversible_write_counter, reversible_write_counter_delta: 0, log_id, @@ -143,12 +147,18 @@ pub enum ExecState { Op(OpcodeId), /// Precompile call Precompile(PrecompileCalls), + /// Virtual step Begin Chunk + BeginChunk, /// Virtual step Begin Tx BeginTx, /// Virtual step End Tx EndTx, + /// Virtual step Padding + Padding, /// Virtual step End Block EndBlock, + /// Virtual step End Chunk + EndChunk, /// Invalid Tx InvalidTx, } diff --git a/bus-mapping/src/circuit_input_builder/input_state_ref.rs b/bus-mapping/src/circuit_input_builder/input_state_ref.rs index 07c250ead4..214564d7a5 100644 --- a/bus-mapping/src/circuit_input_builder/input_state_ref.rs +++ b/bus-mapping/src/circuit_input_builder/input_state_ref.rs @@ -2,8 +2,8 @@ use super::{ get_call_memory_offset_length, get_create_init_code, Block, BlockContext, Call, CallContext, - CallKind, CodeSource, CopyEvent, ExecState, ExecStep, ExpEvent, PrecompileEvent, Transaction, - TransactionContext, + CallKind, ChunkContext, CodeSource, CopyEvent, ExecState, ExecStep, ExpEvent, PrecompileEvent, + Transaction, TransactionContext, }; use crate::{ error::{DepthError, ExecError, InsufficientBalanceError, NonceUintOverflowError}, @@ -37,6 +37,8 @@ pub struct CircuitInputStateRef<'a> { pub block: &'a mut Block, /// Block Context pub block_ctx: &'a mut BlockContext, + /// Chunk Context + pub chunk_ctx: &'a mut ChunkContext, /// Transaction pub tx: &'a mut Transaction, /// Transaction Context @@ -49,11 +51,11 @@ impl<'a> CircuitInputStateRef<'a> { /// Create a new step from a `GethExecStep` pub fn new_step(&self, geth_step: &GethExecStep) -> Result { let call_ctx = self.tx_ctx.call_ctx()?; - Ok(ExecStep::new( geth_step, call_ctx, self.block_ctx.rwc, + self.chunk_ctx.rwc, call_ctx.reversible_write_counter, self.tx_ctx.log_id, )) @@ -65,6 +67,7 @@ impl<'a> CircuitInputStateRef<'a> { exec_state: ExecState::InvalidTx, gas_left: self.tx.gas(), rwc: self.block_ctx.rwc, + rwc_inner_chunk: self.chunk_ctx.rwc, ..Default::default() } } @@ -75,6 +78,7 @@ impl<'a> CircuitInputStateRef<'a> { exec_state: ExecState::BeginTx, gas_left: self.tx.gas(), rwc: self.block_ctx.rwc, + rwc_inner_chunk: self.chunk_ctx.rwc, ..Default::default() } } @@ -110,6 +114,7 @@ impl<'a> CircuitInputStateRef<'a> { 0 }, rwc: self.block_ctx.rwc, + rwc_inner_chunk: self.chunk_ctx.rwc, // For tx without code execution reversible_write_counter: if let Some(call_ctx) = self.tx_ctx.calls().last() { call_ctx.reversible_write_counter @@ -131,10 +136,12 @@ impl<'a> CircuitInputStateRef<'a> { if let OpEnum::Account(op) = op.clone().into_enum() { self.check_update_sdb_account(rw, &op) } - let op_ref = - self.block - .container - .insert(Operation::new(self.block_ctx.rwc.inc_pre(), rw, op)); + let op_ref = self.block.container.insert(Operation::new( + self.block_ctx.rwc.inc_pre(), + self.chunk_ctx.rwc.inc_pre(), + rw, + op, + )); step.bus_mapping_instance.push(op_ref); self.check_rw_num_limit() } @@ -142,9 +149,13 @@ impl<'a> CircuitInputStateRef<'a> { /// Check whether rws will overflow circuit limit. pub fn check_rw_num_limit(&self) -> Result<(), Error> { if let Some(max_rws) = self.max_rws { - let rwc = self.block_ctx.rwc.0; + let rwc = self.chunk_ctx.rwc.0; if rwc > max_rws { - log::error!("rwc > max_rws, rwc={}, max_rws={}", rwc, max_rws); + log::error!( + "chunk inner rwc > max_rws, rwc={}, max_rws={}", + rwc, + max_rws + ); return Err(Error::RwsNotEnough(max_rws, rwc)); }; } @@ -210,6 +221,7 @@ impl<'a> CircuitInputStateRef<'a> { self.check_apply_op(&op.clone().into_enum()); let op_ref = self.block.container.insert(Operation::new_reversible( self.block_ctx.rwc.inc_pre(), + self.chunk_ctx.rwc.inc_pre(), RW::WRITE, op, )); @@ -1009,6 +1021,7 @@ impl<'a> CircuitInputStateRef<'a> { self.check_apply_op(&op); let rev_op_ref = self.block.container.insert_op_enum( self.block_ctx.rwc.inc_pre(), + self.chunk_ctx.rwc.inc_pre(), RW::WRITE, false, op, diff --git a/bus-mapping/src/circuit_input_builder/transaction.rs b/bus-mapping/src/circuit_input_builder/transaction.rs index 6ca84d27ce..6783a78191 100644 --- a/bus-mapping/src/circuit_input_builder/transaction.rs +++ b/bus-mapping/src/circuit_input_builder/transaction.rs @@ -12,7 +12,7 @@ use crate::{ use super::{call::ReversionGroup, Call, CallContext, CallKind, CodeSource, ExecStep}; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] /// Context of a [`Transaction`] which can mutate in an [`ExecStep`]. pub struct TransactionContext { /// Unique identifier of transaction of the block. The value is `index + 1`. diff --git a/bus-mapping/src/exec_trace.rs b/bus-mapping/src/exec_trace.rs index b5d49e0166..28c886240b 100644 --- a/bus-mapping/src/exec_trace.rs +++ b/bus-mapping/src/exec_trace.rs @@ -14,6 +14,7 @@ impl fmt::Debug for OperationRef { "OperationRef{{ {}, {} }}", match self.0 { Target::Start => "Start", + Target::Padding => "Padding", Target::Memory => "Memory", Target::Stack => "Stack", Target::Storage => "Storage", @@ -24,6 +25,7 @@ impl fmt::Debug for OperationRef { Target::CallContext => "CallContext", Target::TxReceipt => "TxReceipt", Target::TxLog => "TxLog", + Target::StepState => "StepState", }, self.1 )) diff --git a/bus-mapping/src/mock.rs b/bus-mapping/src/mock.rs index 31c449c9a5..892eb41c3f 100644 --- a/bus-mapping/src/mock.rs +++ b/bus-mapping/src/mock.rs @@ -96,8 +96,15 @@ impl BlockData { } impl BlockData { - /// Create a new block from the given Geth data with default CircuitsParams. + /// Create a new block with one chunk + /// from the given Geth data with default CircuitsParams. pub fn new_from_geth_data(geth_data: GethData) -> Self { + Self::new_from_geth_data_chunked(geth_data, 1) + } + + /// Create a new block with given number of chunks + /// from the given Geth data with default CircuitsParams. + pub fn new_from_geth_data_chunked(geth_data: GethData, total_chunks: usize) -> Self { let (sdb, code_db) = Self::init_dbs(&geth_data); Self { @@ -107,7 +114,7 @@ impl BlockData { history_hashes: geth_data.history_hashes, eth_block: geth_data.eth_block, geth_traces: geth_data.geth_traces, - circuits_params: DynamicCParams {}, + circuits_params: DynamicCParams { total_chunks }, } } } diff --git a/bus-mapping/src/operation.rs b/bus-mapping/src/operation.rs index e2a3061f1d..c0b4ab4e78 100644 --- a/bus-mapping/src/operation.rs +++ b/bus-mapping/src/operation.rs @@ -94,7 +94,7 @@ impl RWCounter { /// This is also used as the RwTableTag for the RwTable. #[derive(Debug, Clone, PartialEq, Eq, Copy, EnumIter, Hash)] pub enum Target { - /// Start is a padding operation. + /// Start operation in the first row Start = 1, /// Means the target of the operation is the Memory. Memory, @@ -116,6 +116,11 @@ pub enum Target { TxReceipt, /// Means the target of the operation is the TxLog. TxLog, + + /// Chunking: StepState + StepState, + /// Chunking: padding operation. + Padding, } impl_expr!(Target); @@ -886,6 +891,116 @@ impl Op for StartOp { } } +/// Represents a field parameter of the StepStateField. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum StepStateField { + /// caller id field + CallID, + /// is_root field + IsRoot, + /// is_create field + IsCreate, + /// code_hash field + CodeHash, + /// program_counter field + ProgramCounter, + /// stack_pointer field + StackPointer, + /// gas_left field + GasLeft, + /// memory_word_size field + MemoryWordSize, + /// reversible_write_counter field + ReversibleWriteCounter, + /// log_id field + LogID, +} + +/// StepStateOp represents exec state store and load +#[derive(Clone, PartialEq, Eq)] +pub struct StepStateOp { + /// field of CallContext + pub field: StepStateField, + /// value of CallContext + pub value: Word, +} + +impl fmt::Debug for StepStateOp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("StepStateOp { ")?; + f.write_fmt(format_args!( + "field: {:?}, value: {:?}", + self.field, self.value, + ))?; + f.write_str(" }") + } +} + +impl PartialOrd for StepStateOp { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for StepStateOp { + fn cmp(&self, other: &Self) -> Ordering { + self.field.cmp(&other.field) + } +} + +impl Op for StepStateOp { + fn into_enum(self) -> OpEnum { + OpEnum::StepState(self) + } + + fn reverse(&self) -> Self { + unreachable!("StepStateOp can't be reverted") + } +} + +impl StepStateOp { + /// Create a new instance of a `StepStateOp` from it's components. + pub const fn new(field: StepStateField, value: Word) -> StepStateOp { + StepStateOp { field, value } + } + + /// Returns the [`Target`] (operation type) of this operation. + pub const fn target(&self) -> Target { + Target::StepState + } + + /// Returns the [`Word`] read or written by this operation. + pub const fn value(&self) -> &Word { + &self.value + } +} + +/// Represent a Padding padding operation +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct PaddingOp {} + +impl PartialOrd for PaddingOp { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PaddingOp { + fn cmp(&self, _other: &Self) -> Ordering { + Ordering::Equal + } +} + +impl Op for PaddingOp { + fn into_enum(self) -> OpEnum { + OpEnum::Padding(self) + } + + fn reverse(&self) -> Self { + unreachable!("Padding can't be reverted") + } +} + /// Represents TxReceipt read/write operation. #[derive(Clone, PartialEq, Eq)] pub struct TxReceiptOp { @@ -956,12 +1071,17 @@ pub enum OpEnum { TxLog(TxLogOp), /// Start Start(StartOp), + /// Padding + Padding(PaddingOp), + /// StepState + StepState(StepStateOp), } /// Operation is a Wrapper over a type that implements Op with a RWCounter. #[derive(Debug, Clone)] pub struct Operation { rwc: RWCounter, + rwc_inner_chunk: RWCounter, rw: RW, /// True when this Operation should be reverted or not when /// handle_reversion. @@ -994,9 +1114,10 @@ impl Ord for Operation { impl Operation { /// Create a new Operation from an `op` with a `rwc` - pub fn new(rwc: RWCounter, rw: RW, op: T) -> Self { + pub fn new(rwc: RWCounter, rwc_inner_chunk: RWCounter, rw: RW, op: T) -> Self { Self { rwc, + rwc_inner_chunk, rw, reversible: false, op, @@ -1004,9 +1125,10 @@ impl Operation { } /// Create a new reversible Operation from an `op` with a `rwc` - pub fn new_reversible(rwc: RWCounter, rw: RW, op: T) -> Self { + pub fn new_reversible(rwc: RWCounter, rwc_inner_chunk: RWCounter, rw: RW, op: T) -> Self { Self { rwc, + rwc_inner_chunk, rw, reversible: true, op, @@ -1018,6 +1140,11 @@ impl Operation { self.rwc } + /// Return this `Operation` `rwc_inner_chunk` + pub fn rwc_inner_chunk(&self) -> RWCounter { + self.rwc_inner_chunk + } + /// Return this `Operation` `rw` pub fn rw(&self) -> RW { self.rw @@ -1101,11 +1228,13 @@ mod operation_tests { fn unchecked_op_transmutations_are_safe() { let stack_op = StackOp::new(1, StackAddress::from(1024), Word::from(0x40)); - let stack_op_as_operation = Operation::new(RWCounter(1), RW::WRITE, stack_op.clone()); + let stack_op_as_operation = + Operation::new(RWCounter(1), RWCounter(1), RW::WRITE, stack_op.clone()); let memory_op = MemoryOp::new(1, MemoryAddress(0x40), 0x40); - let memory_op_as_operation = Operation::new(RWCounter(1), RW::WRITE, memory_op.clone()); + let memory_op_as_operation = + Operation::new(RWCounter(1), RWCounter(1), RW::WRITE, memory_op.clone()); assert_eq!(stack_op, stack_op_as_operation.op); assert_eq!(memory_op, memory_op_as_operation.op) diff --git a/bus-mapping/src/operation/container.rs b/bus-mapping/src/operation/container.rs index cf2e4dcf22..2ccac336cd 100644 --- a/bus-mapping/src/operation/container.rs +++ b/bus-mapping/src/operation/container.rs @@ -1,7 +1,7 @@ use super::{ - AccountOp, CallContextOp, MemoryOp, Op, OpEnum, Operation, RWCounter, StackOp, StartOp, - StorageOp, Target, TxAccessListAccountOp, TxAccessListAccountStorageOp, TxLogOp, TxReceiptOp, - TxRefundOp, RW, + AccountOp, CallContextOp, MemoryOp, Op, OpEnum, Operation, PaddingOp, RWCounter, StackOp, + StartOp, StepStateOp, StorageOp, Target, TxAccessListAccountOp, TxAccessListAccountStorageOp, + TxLogOp, TxReceiptOp, TxRefundOp, RW, }; use crate::exec_trace::OperationRef; use itertools::Itertools; @@ -44,6 +44,10 @@ pub struct OperationContainer { pub tx_log: Vec>, /// Operations of Start pub start: Vec>, + /// Operations of Padding + pub padding: Vec>, + /// Operations of StepState + pub step_state: Vec>, } impl Default for OperationContainer { @@ -68,6 +72,8 @@ impl OperationContainer { tx_receipt: Vec::new(), tx_log: Vec::new(), start: Vec::new(), + padding: Vec::new(), + step_state: Vec::new(), } } @@ -77,9 +83,10 @@ impl OperationContainer { /// vector. pub fn insert(&mut self, op: Operation) -> OperationRef { let rwc = op.rwc(); + let rwc_inner_chunk = op.rwc_inner_chunk(); let rw = op.rw(); let reversible = op.reversible(); - self.insert_op_enum(rwc, rw, reversible, op.op.into_enum()) + self.insert_op_enum(rwc, rwc_inner_chunk, rw, reversible, op.op.into_enum()) } /// Inserts an [`OpEnum`] into the container returning a lightweight @@ -89,32 +96,35 @@ impl OperationContainer { pub fn insert_op_enum( &mut self, rwc: RWCounter, + rwc_inner_chunk: RWCounter, rw: RW, reversible: bool, op_enum: OpEnum, ) -> OperationRef { match op_enum { OpEnum::Memory(op) => { - self.memory.push(Operation::new(rwc, rw, op)); + self.memory + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); OperationRef::from((Target::Memory, self.memory.len() - 1)) } OpEnum::Stack(op) => { - self.stack.push(Operation::new(rwc, rw, op)); + self.stack + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); OperationRef::from((Target::Stack, self.stack.len() - 1)) } OpEnum::Storage(op) => { self.storage.push(if reversible { - Operation::new_reversible(rwc, rw, op) + Operation::new_reversible(rwc, rwc_inner_chunk, rw, op) } else { - Operation::new(rwc, rw, op) + Operation::new(rwc, rwc_inner_chunk, rw, op) }); OperationRef::from((Target::Storage, self.storage.len() - 1)) } OpEnum::TxAccessListAccount(op) => { self.tx_access_list_account.push(if reversible { - Operation::new_reversible(rwc, rw, op) + Operation::new_reversible(rwc, rwc_inner_chunk, rw, op) } else { - Operation::new(rwc, rw, op) + Operation::new(rwc, rwc_inner_chunk, rw, op) }); OperationRef::from(( Target::TxAccessListAccount, @@ -123,9 +133,9 @@ impl OperationContainer { } OpEnum::TxAccessListAccountStorage(op) => { self.tx_access_list_account_storage.push(if reversible { - Operation::new_reversible(rwc, rw, op) + Operation::new_reversible(rwc, rwc_inner_chunk, rw, op) } else { - Operation::new(rwc, rw, op) + Operation::new(rwc, rwc_inner_chunk, rw, op) }); OperationRef::from(( Target::TxAccessListAccountStorage, @@ -134,36 +144,50 @@ impl OperationContainer { } OpEnum::TxRefund(op) => { self.tx_refund.push(if reversible { - Operation::new_reversible(rwc, rw, op) + Operation::new_reversible(rwc, rwc_inner_chunk, rw, op) } else { - Operation::new(rwc, rw, op) + Operation::new(rwc, rwc_inner_chunk, rw, op) }); OperationRef::from((Target::TxRefund, self.tx_refund.len() - 1)) } OpEnum::Account(op) => { self.account.push(if reversible { - Operation::new_reversible(rwc, rw, op) + Operation::new_reversible(rwc, rwc_inner_chunk, rw, op) } else { - Operation::new(rwc, rw, op) + Operation::new(rwc, rwc_inner_chunk, rw, op) }); OperationRef::from((Target::Account, self.account.len() - 1)) } OpEnum::CallContext(op) => { - self.call_context.push(Operation::new(rwc, rw, op)); + self.call_context + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); OperationRef::from((Target::CallContext, self.call_context.len() - 1)) } OpEnum::TxReceipt(op) => { - self.tx_receipt.push(Operation::new(rwc, rw, op)); + self.tx_receipt + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); OperationRef::from((Target::TxReceipt, self.tx_receipt.len() - 1)) } OpEnum::TxLog(op) => { - self.tx_log.push(Operation::new(rwc, rw, op)); + self.tx_log + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); OperationRef::from((Target::TxLog, self.tx_log.len() - 1)) } OpEnum::Start(op) => { - self.start.push(Operation::new(rwc, rw, op)); + self.start + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); OperationRef::from((Target::Start, self.start.len() - 1)) } + OpEnum::Padding(op) => { + self.padding + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); + OperationRef::from((Target::Padding, self.padding.len() - 1)) + } + OpEnum::StepState(op) => { + self.step_state + .push(Operation::new(rwc, rwc_inner_chunk, rw, op)); + OperationRef::from((Target::StepState, self.step_state.len() - 1)) + } } } @@ -198,20 +222,25 @@ mod container_test { #[test] fn operation_container_test() { + // global counter same as intra_chunk_counter in single chunk let mut global_counter = RWCounter::default(); + let mut intra_chunk_counter = RWCounter::default(); let mut operation_container = OperationContainer::default(); let stack_operation = Operation::new( global_counter.inc_pre(), + intra_chunk_counter.inc_pre(), RW::WRITE, StackOp::new(1, StackAddress(1023), Word::from(0x100)), ); let memory_operation = Operation::new( global_counter.inc_pre(), + intra_chunk_counter.inc_pre(), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(1), 1), ); let storage_operation = Operation::new( global_counter.inc_pre(), + intra_chunk_counter.inc_pre(), RW::WRITE, StorageOp::new( Address::zero(), diff --git a/circuit-benchmarks/src/copy_circuit.rs b/circuit-benchmarks/src/copy_circuit.rs index d77155f8bc..8638b1a1e1 100644 --- a/circuit-benchmarks/src/copy_circuit.rs +++ b/circuit-benchmarks/src/copy_circuit.rs @@ -26,7 +26,7 @@ mod tests { use std::env::var; use zkevm_circuits::{ copy_circuit::TestCopyCircuit, - evm_circuit::witness::{block_convert, Block}, + evm_circuit::witness::{block_convert, chunk_convert, Block, Chunk}, util::SubCircuit, }; @@ -51,10 +51,11 @@ mod tests { ]); // Create the circuit - let mut block = generate_full_events_block(degree); + let (mut block, chunk) = generate_full_events_block(degree); block.circuits_params.max_rws = 10_000; block.circuits_params.max_copy_rows = 10_000; - let circuit = TestCopyCircuit::::new_from_block(&block); + let circuit = TestCopyCircuit::::new_from_block(&block, &chunk); + // Bench setup generation let setup_message = format!("{} {} with degree = {}", BENCHMARK_ID, setup_prfx, degree); let start1 = start_timer!(|| setup_message); @@ -109,7 +110,7 @@ mod tests { } /// generate enough copy events to fillup copy circuit - fn generate_full_events_block(degree: u32) -> Block { + fn generate_full_events_block(degree: u32) -> (Block, Chunk) { // A empiric value 55 here to let the code generate enough copy event without // exceed the max_rws limit. let copy_event_num = (1 << degree) / 55; @@ -144,19 +145,19 @@ mod tests { ) .unwrap(); let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data_with_params( + let builder = BlockData::new_from_geth_data_with_params( block.clone(), FixedCParams { max_rws: 1 << (degree - 1), ..Default::default() }, ) - .new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); + .new_circuit_input_builder() + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); let block = block_convert(&builder).unwrap(); + let chunk = chunk_convert(&block, &builder).unwrap().remove(0); assert_eq!(block.copy_events.len(), copy_event_num); - block + (block, chunk) } } diff --git a/circuit-benchmarks/src/evm_circuit.rs b/circuit-benchmarks/src/evm_circuit.rs index 7ceeb80f8f..10af1a3e10 100644 --- a/circuit-benchmarks/src/evm_circuit.rs +++ b/circuit-benchmarks/src/evm_circuit.rs @@ -24,7 +24,10 @@ mod evm_circ_benches { use rand::SeedableRng; use rand_xorshift::XorShiftRng; use std::env::var; - use zkevm_circuits::evm_circuit::{witness::block_convert, TestEvmCircuit}; + use zkevm_circuits::evm_circuit::{ + witness::{block_convert, chunk_convert}, + TestEvmCircuit, + }; #[cfg_attr(not(feature = "benches"), ignore)] #[test] @@ -44,17 +47,16 @@ mod evm_circ_benches { .unwrap() .into(); - let mut builder = + let builder = BlockData::new_from_geth_data_with_params(empty_data.clone(), FixedCParams::default()) - .new_circuit_input_builder(); - - builder - .handle_block(&empty_data.eth_block, &empty_data.geth_traces) - .unwrap(); + .new_circuit_input_builder() + .handle_block(&empty_data.eth_block, &empty_data.geth_traces) + .unwrap(); let block = block_convert(&builder).unwrap(); + let chunk = chunk_convert(&block, &builder).unwrap().remove(0); - let circuit = TestEvmCircuit::::new(block); + let circuit = TestEvmCircuit::::new(block, chunk); let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, diff --git a/circuit-benchmarks/src/exp_circuit.rs b/circuit-benchmarks/src/exp_circuit.rs index 8b3d6830a3..11d91b69dc 100644 --- a/circuit-benchmarks/src/exp_circuit.rs +++ b/circuit-benchmarks/src/exp_circuit.rs @@ -26,7 +26,7 @@ mod tests { use rand_xorshift::XorShiftRng; use std::env::var; use zkevm_circuits::{ - evm_circuit::witness::{block_convert, Block}, + evm_circuit::witness::{block_convert, chunk_convert, Block, Chunk}, exp_circuit::TestExpCircuit, }; @@ -49,11 +49,8 @@ mod tests { let base = Word::from(132); let exponent = Word::from(27); - let block = generate_full_events_block(degree, base, exponent); - let circuit = TestExpCircuit::::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ); + let (block, chunk) = generate_full_events_block(degree, base, exponent); + let circuit = TestExpCircuit::::new(block.exp_events, chunk.fixed_param.max_exp_steps); // Initialize the polynomial commitment parameters let mut rng = XorShiftRng::from_seed([ @@ -121,7 +118,11 @@ mod tests { end_timer!(start3); } - fn generate_full_events_block(degree: u32, base: Word, exponent: Word) -> Block { + fn generate_full_events_block( + degree: u32, + base: Word, + exponent: Word, + ) -> (Block, Chunk) { let code = bytecode! { PUSH32(exponent) PUSH32(base) @@ -137,17 +138,18 @@ mod tests { ) .unwrap(); let block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data_with_params( + let builder = BlockData::new_from_geth_data_with_params( block.clone(), FixedCParams { max_rws: 1 << (degree - 1), ..Default::default() }, ) - .new_circuit_input_builder(); - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - block_convert(&builder).unwrap() + .new_circuit_input_builder() + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + let block = block_convert(&builder).unwrap(); + let chunk = chunk_convert(&block, &builder).unwrap().remove(0); + (block, chunk) } } diff --git a/circuit-benchmarks/src/state_circuit.rs b/circuit-benchmarks/src/state_circuit.rs index 2235930f7d..251a4089ad 100644 --- a/circuit-benchmarks/src/state_circuit.rs +++ b/circuit-benchmarks/src/state_circuit.rs @@ -21,9 +21,7 @@ mod tests { use rand::SeedableRng; use rand_xorshift::XorShiftRng; use std::env::var; - use zkevm_circuits::{ - evm_circuit::witness::RwMap, state_circuit::StateCircuit, util::SubCircuit, - }; + use zkevm_circuits::{state_circuit::StateCircuit, util::SubCircuit, witness::Chunk}; #[cfg_attr(not(feature = "benches"), ignore)] #[test] @@ -39,7 +37,7 @@ mod tests { .parse() .expect("Cannot parse DEGREE env var as u32"); - let empty_circuit = StateCircuit::::new(RwMap::default(), 1 << 16); + let empty_circuit = StateCircuit::::new(&Chunk::default()); // Initialize the polynomial commitment parameters let mut rng = XorShiftRng::from_seed([ diff --git a/circuit-benchmarks/src/super_circuit.rs b/circuit-benchmarks/src/super_circuit.rs index e9868aabbe..e800ec5806 100644 --- a/circuit-benchmarks/src/super_circuit.rs +++ b/circuit-benchmarks/src/super_circuit.rs @@ -80,6 +80,7 @@ mod tests { block.sign(&wallets); let circuits_params = FixedCParams { + total_chunks: 1, max_txs: 1, max_withdrawals: 1, max_calldata: 32, @@ -91,8 +92,9 @@ mod tests { max_keccak_rows: 0, max_vertical_circuit_rows: 0, }; - let (_, circuit, instance, _) = + let (_, mut circuits, mut instances, _) = SuperCircuit::build(block, circuits_params, Fr::from(0x100)).unwrap(); + let (circuit, instance) = (circuits.remove(0), instances.remove(0)); let instance_refs: Vec<&[Fr]> = instance.iter().map(|v| &v[..]).collect(); // Bench setup generation diff --git a/gadgets/Cargo.toml b/gadgets/Cargo.toml index e1869999d5..c156da2c78 100644 --- a/gadgets/Cargo.toml +++ b/gadgets/Cargo.toml @@ -11,6 +11,7 @@ sha3 = "0.7.2" eth-types = { path = "../eth-types" } digest = "0.7.6" strum = "0.24" +itertools = "0.10" [dev-dependencies] rand = "0.8" diff --git a/gadgets/src/lib.rs b/gadgets/src/lib.rs index a7150262ee..fea14604d0 100644 --- a/gadgets/src/lib.rs +++ b/gadgets/src/lib.rs @@ -16,6 +16,7 @@ pub mod binary_number; pub mod is_zero; pub mod less_than; pub mod mul_add; +pub mod permutation; pub mod util; use eth_types::Field; diff --git a/gadgets/src/permutation.rs b/gadgets/src/permutation.rs new file mode 100644 index 0000000000..796086cb35 --- /dev/null +++ b/gadgets/src/permutation.rs @@ -0,0 +1,358 @@ +//! Chip that implements permutation fingerprints +//! fingerprints &= \prod_{row_j} \left ( \alpha - \sum_k (\gamma^k \cdot cell_j(k)) \right ) +//! power of gamma are defined in columns to trade more columns with less degrees +use std::iter; +#[rustfmt::skip] +// | q_row_non_first | q_row_enable | q_row_last | alpha | gamma | gamma power 2 | ... | row fingerprint | accumulated fingerprint | +// |-----------------|--------------|------------|-----------|-----------|-----------------| | --------------- | ---------------------- | +// | 0 |1 |0 |alpha | gamma | gamma **2 | ... | F | F | +// | 1 |1 |0 |alpha | gamma | gamma **2 | ... | F | F | +// | 1 |1 |1 |alpha | gamma | gamma **2 | ... | F | F | + +use std::marker::PhantomData; + +use crate::util::Expr; +use eth_types::Field; +use halo2_proofs::{ + circuit::{AssignedCell, Region, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + poly::Rotation, +}; +use itertools::Itertools; + +/// Config for PermutationChipConfig +#[derive(Clone, Debug)] +pub struct PermutationChipConfig { + /// acc_fingerprints + pub acc_fingerprints: Column, + row_fingerprints: Column, + alpha: Column, + power_of_gamma: Vec>, + /// q_row_non_first + pub q_row_non_first: Selector, // 1 between (first, end], exclude first + /// q_row_enable + pub q_row_enable: Selector, // 1 for all rows (including first) + /// q_row_last + pub q_row_last: Selector, // 1 in the last row + + _phantom: PhantomData, + + row_fingerprints_cur_expr: Expression, + acc_fingerprints_cur_expr: Expression, +} + +/// (alpha, gamma, row_fingerprints_prev_cell, row_fingerprints_next_cell, prev_acc_fingerprints, +/// next_acc_fingerprints) +type PermutationAssignedCells = ( + AssignedCell, + AssignedCell, + AssignedCell, + AssignedCell, + AssignedCell, + AssignedCell, +); + +impl PermutationChipConfig { + /// assign + pub fn assign( + &self, + region: &mut Region<'_, F>, + alpha: Value, + gamma: Value, + acc_fingerprints_prev: Value, + col_values: &[Vec>], + prefix: &'static str, + ) -> Result, Error> { + self.annotate_columns_in_region(region, prefix); + + // get accumulated fingerprints of each row + let fingerprints = + get_permutation_fingerprints(col_values, alpha, gamma, acc_fingerprints_prev); + + // power_of_gamma start from gamma**1 + let power_of_gamma = { + let num_of_col = col_values.first().map(|row| row.len()).unwrap_or_default(); + std::iter::successors(Some(gamma), |prev| (*prev * gamma).into()) + .take(num_of_col.saturating_sub(1)) + .collect::>>() + }; + + let mut alpha_first_cell = None; + let mut gamma_first_cell = None; + let mut acc_fingerprints_prev_cell = None; + let mut acc_fingerprints_next_cell = None; + + let mut row_fingerprints_prev_cell = None; + let mut row_fingerprints_next_cell = None; + + for (offset, (row_acc_fingerprints, row_fingerprints)) in fingerprints.iter().enumerate() { + // skip first fingerprint for its prev_fingerprint + if offset != 0 { + self.q_row_non_first.enable(region, offset)?; + } + + self.q_row_enable.enable(region, offset)?; + + let row_acc_fingerprint_cell = region.assign_advice( + || format!("acc_fingerprints at index {}", offset), + self.acc_fingerprints, + offset, + || *row_acc_fingerprints, + )?; + + let row_fingerprints_cell = region.assign_advice( + || format!("row_fingerprints at index {}", offset), + self.row_fingerprints, + offset, + || *row_fingerprints, + )?; + + let alpha_cell = region.assign_advice( + || format!("alpha at index {}", offset), + self.alpha, + offset, + || alpha, + )?; + let gamma_cells = self + .power_of_gamma + .iter() + .zip_eq(power_of_gamma.iter()) + .map(|(col, value)| { + region.assign_advice( + || format!("gamma at index {}", offset), + *col, + offset, + || *value, + ) + }) + .collect::>, Error>>()?; + + if offset == 0 { + alpha_first_cell = Some(alpha_cell); + gamma_first_cell = Some(gamma_cells[0].clone()); + acc_fingerprints_prev_cell = Some(row_acc_fingerprint_cell.clone()); + row_fingerprints_prev_cell = Some(row_fingerprints_cell.clone()) + } + // last offset + if offset == fingerprints.len() - 1 { + self.q_row_last.enable(region, offset)?; + acc_fingerprints_next_cell = Some(row_acc_fingerprint_cell); + row_fingerprints_next_cell = Some(row_fingerprints_cell) + } + } + + Ok(( + alpha_first_cell.unwrap(), + gamma_first_cell.unwrap(), + row_fingerprints_prev_cell.unwrap(), + row_fingerprints_next_cell.unwrap(), + acc_fingerprints_prev_cell.unwrap(), + acc_fingerprints_next_cell.unwrap(), + )) + } + + /// Annotates columns of this gadget embedded within a circuit region. + pub fn annotate_columns_in_region(&self, region: &mut Region, prefix: &str) { + [ + ( + self.acc_fingerprints, + "GADGETS_PermutationChipConfig_acc_fingerprints".to_string(), + ), + ( + self.row_fingerprints, + "GADGETS_PermutationChipConfig_row_fingerprints".to_string(), + ), + ( + self.alpha, + "GADGETS_PermutationChipConfig_alpha".to_string(), + ), + ] + .iter() + .cloned() + .chain(self.power_of_gamma.iter().enumerate().map(|(i, col)| { + ( + *col, + format!("GADGETS_PermutationChipConfig_gamma_{}", i + 1), + ) + })) + .for_each(|(col, ann)| region.name_column(|| format!("{}_{}", prefix, ann), col)); + } + + /// acc_fingerprints_cur_expr + pub fn acc_fingerprints_cur_expr(&self) -> Expression { + self.acc_fingerprints_cur_expr.clone() + } + + /// row_fingerprints_cur_expr + pub fn row_fingerprints_cur_expr(&self) -> Expression { + self.row_fingerprints_cur_expr.clone() + } +} + +/// permutation fingerprint gadget +#[derive(Debug, Clone)] +pub struct PermutationChip { + /// config + pub config: PermutationChipConfig, +} + +impl PermutationChip { + /// configure + pub fn configure( + meta: &mut ConstraintSystem, + cols: Vec>, + ) -> PermutationChipConfig { + let acc_fingerprints = meta.advice_column(); + let row_fingerprints = meta.advice_column(); + let alpha = meta.advice_column(); + + // trade more columns with less degrees + let power_of_gamma = (0..cols.len() - 1) + .map(|_| meta.advice_column()) + .collect::>>(); // first element is gamma**1 + + let q_row_non_first = meta.complex_selector(); + let q_row_enable = meta.complex_selector(); + let q_row_last = meta.selector(); + + meta.enable_equality(acc_fingerprints); + meta.enable_equality(row_fingerprints); + meta.enable_equality(alpha); + meta.enable_equality(power_of_gamma[0]); + + let mut acc_fingerprints_cur_expr: Expression = 0.expr(); + let mut row_fingerprints_cur_expr: Expression = 0.expr(); + + meta.create_gate( + "acc_fingerprints_cur = acc_fingerprints_prev * row_fingerprints_cur", + |meta| { + let q_row_non_first = meta.query_selector(q_row_non_first); + let acc_fingerprints_prev = meta.query_advice(acc_fingerprints, Rotation::prev()); + let acc_fingerprints_cur = meta.query_advice(acc_fingerprints, Rotation::cur()); + let row_fingerprints_cur = meta.query_advice(row_fingerprints, Rotation::cur()); + + acc_fingerprints_cur_expr = acc_fingerprints_cur.clone(); + + [q_row_non_first + * (acc_fingerprints_cur - acc_fingerprints_prev * row_fingerprints_cur)] + }, + ); + + meta.create_gate( + "row_fingerprints_cur = fingerprints(column_exprs)", + |meta| { + let alpha = meta.query_advice(alpha, Rotation::cur()); + let row_fingerprints_cur = meta.query_advice(row_fingerprints, Rotation::cur()); + + row_fingerprints_cur_expr = row_fingerprints_cur.clone(); + + let power_of_gamma = iter::once(1.expr()) + .chain( + power_of_gamma + .iter() + .map(|column| meta.query_advice(*column, Rotation::cur())), + ) + .collect::>>(); + + let q_row_enable = meta.query_selector(q_row_enable); + let cols_cur_exprs = cols + .iter() + .map(|col| meta.query_advice(*col, Rotation::cur())) + .collect::>>(); + + let perf_term = cols_cur_exprs + .iter() + .zip_eq(power_of_gamma.iter()) + .map(|(a, b)| a.clone() * b.clone()) + .fold(0.expr(), |a, b| a + b); + [q_row_enable * (row_fingerprints_cur - (alpha - perf_term))] + }, + ); + + meta.create_gate("challenges consistency", |meta| { + let q_row_non_first = meta.query_selector(q_row_non_first); + let alpha_prev = meta.query_advice(alpha, Rotation::prev()); + let alpha_cur = meta.query_advice(alpha, Rotation::cur()); + + [ + vec![q_row_non_first.clone() * (alpha_prev - alpha_cur)], + power_of_gamma + .iter() + .map(|col| { + let gamma_prev = meta.query_advice(*col, Rotation::prev()); + let gamma_cur = meta.query_advice(*col, Rotation::cur()); + q_row_non_first.clone() * (gamma_prev - gamma_cur) + }) + .collect(), + ] + .concat() + }); + + meta.create_gate("power of gamma", |meta| { + let q_row_non_first = meta.query_selector(q_row_non_first); + let gamma = meta.query_advice(power_of_gamma[0], Rotation::cur()); + power_of_gamma + .iter() + .tuple_windows() + .map(|(col1, col2)| { + let col1_cur = meta.query_advice(*col1, Rotation::cur()); + let col2_cur = meta.query_advice(*col2, Rotation::cur()); + q_row_non_first.clone() * (col2_cur - col1_cur * gamma.clone()) + }) + .collect::>>() + }); + + PermutationChipConfig { + acc_fingerprints, + acc_fingerprints_cur_expr, + row_fingerprints, + row_fingerprints_cur_expr, + q_row_non_first, + q_row_enable, + q_row_last, + alpha, + power_of_gamma, + _phantom: PhantomData:: {}, + } + } +} + +impl PermutationChip {} + +/// get permutation fingerprint of rows +pub fn get_permutation_fingerprints( + col_values: &[Vec>], + alpha: Value, + gamma: Value, + acc_fingerprints_prev: Value, +) -> Vec<(Value, Value)> { + let power_of_gamma = { + let num_of_col = col_values.first().map(|row| row.len()).unwrap_or_default(); + std::iter::successors(Some(Value::known(F::ONE)), |prev| (*prev * gamma).into()) + .take(num_of_col) + .collect::>>() + }; + let mut result = vec![]; + col_values + .iter() + .map(|row| { + // row = alpha - (gamma^1 x1 + gamma^2 x2 + ...) + let tmp = row + .iter() + .zip_eq(power_of_gamma.iter()) + .map(|(a, b)| *a * b) + .fold(Value::known(F::ZERO), |prev, cur| prev + cur); + alpha - tmp + }) + .enumerate() + .for_each(|(i, value)| { + // fingerprint = row0 * row1 * ... rowi + // (fingerprinti, rowi) + if i == 0 { + result.push((acc_fingerprints_prev, value)); + } else { + result.push((result[result.len() - 1].0 * value, value)); + } + }); + result +} diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index bd8e732c20..2990ac5cbb 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -24,6 +24,7 @@ rand_chacha = "0.3" paste = "1.0" rand_xorshift = "0.3.0" rand_core = "0.6.4" +itertools = "0.10" mock = { path = "../mock" } [dev-dependencies] diff --git a/integration-tests/src/integration_test_circuits.rs b/integration-tests/src/integration_test_circuits.rs index bfa239d236..3a44ffa6f4 100644 --- a/integration-tests/src/integration_test_circuits.rs +++ b/integration-tests/src/integration_test_circuits.rs @@ -8,10 +8,13 @@ use halo2_proofs::{ self, circuit::Value, dev::{CellValue, MockProver}, - halo2curves::bn256::{Bn256, Fr, G1Affine}, + halo2curves::{ + bn256::{Bn256, Fr, G1Affine}, + pairing::Engine, + }, plonk::{ create_proof, keygen_pk, keygen_vk, permutation::Assembly, verify_proof, Circuit, - ProvingKey, + ConstraintSystem, ProvingKey, }, poly::{ commitment::ParamsProver, @@ -22,6 +25,7 @@ use halo2_proofs::{ }, }, }; +use itertools::Itertools; use lazy_static::lazy_static; use mock::TestContext; use rand_chacha::rand_core::SeedableRng; @@ -37,17 +41,19 @@ use zkevm_circuits::{ pi_circuit::TestPiCircuit, root_circuit::{ compile, Config, EvmTranscript, NativeLoader, PoseidonTranscript, RootCircuit, Shplonk, + SnarkWitness, UserChallenge, }, state_circuit::TestStateCircuit, super_circuit::SuperCircuit, tx_circuit::TestTxCircuit, util::SubCircuit, - witness::{block_convert, Block}, + witness::{block_convert, chunk_convert, Block, Chunk}, }; /// TEST_MOCK_RANDOMNESS const TEST_MOCK_RANDOMNESS: u64 = 0x100; - +/// +const TOTAL_CHUNKS: usize = 1; /// MAX_TXS const MAX_TXS: usize = 4; /// MAX_WITHDRAWALS @@ -70,6 +76,7 @@ const MAX_KECCAK_ROWS: usize = 38000; const MAX_VERTICAL_CIRCUIT_ROWS: usize = 0; const CIRCUITS_PARAMS: FixedCParams = FixedCParams { + total_chunks: TOTAL_CHUNKS, max_rws: MAX_RWS, max_txs: MAX_TXS, max_withdrawals: MAX_WITHDRAWALS, @@ -286,8 +293,8 @@ impl + Circuit> IntegrationTest { match self.key.clone() { Some(key) => key, None => { - let block = new_empty_block(); - let circuit = C::new_from_block(&block); + let (block, chunk) = new_empty_block_chunk(); + let circuit = C::new_from_block(&block, &chunk); let general_params = get_general_params(self.degree); let verifying_key = @@ -307,8 +314,8 @@ impl + Circuit> IntegrationTest { let params = get_general_params(self.degree); let pk = self.get_key(); - let block = new_empty_block(); - let circuit = C::new_from_block(&block); + let (block, chunk) = new_empty_block_chunk(); + let circuit = C::new_from_block(&block, &chunk); let instance = circuit.instance(); let protocol = compile( @@ -321,8 +328,12 @@ impl + Circuit> IntegrationTest { let circuit = RootCircuit::>::new( ¶ms, &protocol, - Value::unknown(), - Value::unknown(), + vec![SnarkWitness::new( + &protocol, + Value::unknown(), + Value::unknown(), + )], + None, ) .unwrap(); @@ -351,10 +362,26 @@ impl + Circuit> IntegrationTest { let fixed = mock_prover.fixed(); if let Some(prev_fixed) = self.fixed.clone() { - assert!( - fixed.eq(&prev_fixed), - "circuit fixed columns are not constant for different witnesses" - ); + fixed + .iter() + .enumerate() + .zip_eq(prev_fixed.iter()) + .for_each(|((index, col1), col2)| { + if !col1.eq(col2) { + println!("on column index {} not equal", index); + col1.iter().enumerate().zip_eq(col2.iter()).for_each( + |((index, cellv1), cellv2)| { + assert!( + cellv1.eq(cellv2), + "cellv1 {:?} != cellv2 {:?} on index {}", + cellv1, + cellv2, + index + ); + }, + ); + } + }); } else { self.fixed = Some(fixed.clone()); } @@ -376,6 +403,24 @@ impl + Circuit> IntegrationTest { match self.root_fixed.clone() { Some(prev_fixed) => { + fixed.iter().enumerate().zip_eq(prev_fixed.iter()).for_each( + |((index, col1), col2)| { + if !col1.eq(col2) { + println!("on column index {} not equal", index); + col1.iter().enumerate().zip_eq(col2.iter()).for_each( + |((index, cellv1), cellv2)| { + assert!( + cellv1.eq(cellv2), + "cellv1 {:?} != cellv2 {:?} on index {}", + cellv1, + cellv2, + index + ); + }, + ); + } + }, + ); assert!( fixed.eq(&prev_fixed), "root circuit fixed columns are not constant for different witnesses" @@ -417,8 +462,9 @@ impl + Circuit> IntegrationTest { block_tag, ); let mut block = block_convert(&builder).unwrap(); + let chunk = chunk_convert(&block, &builder).unwrap().remove(0); block.randomness = Fr::from(TEST_MOCK_RANDOMNESS); - let circuit = C::new_from_block(&block); + let circuit = C::new_from_block(&block, &chunk); let instance = circuit.instance(); #[allow(clippy::collapsible_else_if)] @@ -432,6 +478,11 @@ impl + Circuit> IntegrationTest { .with_num_instance(instance.iter().map(|instance| instance.len()).collect()), ); + // get chronological_rwtable and byaddr_rwtable columns index + let mut cs = ConstraintSystem::<::Fr>::default(); + let config = SuperCircuit::configure(&mut cs); + let rwtable_columns = config.get_rwtable_columns(); + let proof = { let mut proof_cache = PROOF_CACHE.lock().await; if let Some(proof) = proof_cache.get(&proof_name) { @@ -447,11 +498,19 @@ impl + Circuit> IntegrationTest { }; log::info!("root circuit new"); + let user_challenge = &UserChallenge { + column_indexes: rwtable_columns, + num_challenges: 2, // alpha, gamma + }; let root_circuit = RootCircuit::>::new( ¶ms, &protocol, - Value::known(&instance), - Value::known(&proof), + vec![SnarkWitness::new( + &protocol, + Value::known(&instance), + Value::known(&proof), + )], + Some(user_challenge), ) .unwrap(); @@ -486,16 +545,17 @@ impl + Circuit> IntegrationTest { } } -fn new_empty_block() -> Block { +fn new_empty_block_chunk() -> (Block, Chunk) { let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) .unwrap() .into(); - let mut builder = BlockData::new_from_geth_data_with_params(block.clone(), CIRCUITS_PARAMS) - .new_circuit_input_builder(); - builder + let builder = BlockData::new_from_geth_data_with_params(block.clone(), CIRCUITS_PARAMS) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); - block_convert(&builder).unwrap() + let block = block_convert(&builder).unwrap(); + let chunk = chunk_convert(&block, &builder).unwrap().remove(0); + (block, chunk) } fn get_general_params(degree: u32) -> ParamsKZG { diff --git a/integration-tests/tests/circuit_input_builder.rs b/integration-tests/tests/circuit_input_builder.rs index f5eb9de752..e68f2eb67a 100644 --- a/integration-tests/tests/circuit_input_builder.rs +++ b/integration-tests/tests/circuit_input_builder.rs @@ -16,6 +16,7 @@ async fn test_circuit_input_builder_block(block_num: u64) { let cli = BuilderClient::new( cli, FixedCParams { + total_chunks: 1, max_rws: 16384, max_txs: 1, max_withdrawals: 1, diff --git a/testool/src/statetest/executor.rs b/testool/src/statetest/executor.rs index a2b2a3c849..2282d41eb8 100644 --- a/testool/src/statetest/executor.rs +++ b/testool/src/statetest/executor.rs @@ -17,7 +17,7 @@ use thiserror::Error; use zkevm_circuits::{ super_circuit::SuperCircuit, test_util::{CircuitTestBuilder, CircuitTestError}, - witness::Block, + witness::{Block, Chunk}, }; #[derive(PartialEq, Eq, Error, Debug)] @@ -324,10 +324,11 @@ pub fn run_test( eth_block: eth_block.clone(), }; - let mut builder; + let builder; if !circuits_config.super_circuit { let circuits_params = FixedCParams { + total_chunks: 1, max_txs: 1, max_withdrawals: 1, max_rws: 55000, @@ -341,15 +342,16 @@ pub fn run_test( }; let block_data = BlockData::new_from_geth_data_with_params(geth_data, circuits_params); - builder = block_data.new_circuit_input_builder(); - builder + builder = block_data + .new_circuit_input_builder() .handle_block(ð_block, &geth_traces) .map_err(|err| StateTestError::CircuitInput(err.to_string()))?; let block: Block = zkevm_circuits::evm_circuit::witness::block_convert(&builder).unwrap(); - - CircuitTestBuilder::<1, 1>::new_from_block(block) + 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, .. } => { @@ -367,6 +369,7 @@ pub fn run_test( geth_data.sign(&wallets); let circuits_params = FixedCParams { + total_chunks: 1, max_txs: 1, max_withdrawals: 1, max_calldata: 32, @@ -378,10 +381,13 @@ pub fn run_test( max_keccak_rows: 0, max_vertical_circuit_rows: 0, }; - let (k, circuit, instance, _builder) = + let (k, mut circuits, mut instances, _builder) = SuperCircuit::::build(geth_data, circuits_params, Fr::from(0x100)).unwrap(); builder = _builder; + let circuit = circuits.remove(0); + let instance = instances.remove(0); + let prover = MockProver::run(k, &circuit, instance).unwrap(); prover .verify() diff --git a/zkevm-circuits/src/bin/stats/main.rs b/zkevm-circuits/src/bin/stats/main.rs index dbf9b4b9a3..da58dd0615 100644 --- a/zkevm-circuits/src/bin/stats/main.rs +++ b/zkevm-circuits/src/bin/stats/main.rs @@ -31,11 +31,11 @@ use zkevm_circuits::{ pi_circuit::{PiCircuitConfig, PiCircuitConfigArgs}, state_circuit::{StateCircuitConfig, StateCircuitConfigArgs}, table::{ - BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, RwTable, SigTable, - TxTable, UXTable, WdTable, + BlockTable, BytecodeTable, ChunkCtxTable, CopyTable, ExpTable, KeccakTable, MptTable, + RwTable, SigTable, TxTable, UXTable, WdTable, }, tx_circuit::{TxCircuitConfig, TxCircuitConfigArgs}, - util::{Challenges, SubCircuitConfig}, + util::{chunk_ctx::ChunkContextConfig, Challenges, SubCircuitConfig}, }; fn main() { @@ -222,7 +222,11 @@ fn get_exec_steps_occupancy() { keccak_table, LOOKUP_CONFIG[6].1, exp_table, - LOOKUP_CONFIG[7].1 + LOOKUP_CONFIG[7].1, + sig_table, + LOOKUP_CONFIG[8].1, + chunk_ctx_table, + LOOKUP_CONFIG[9].1 ); } @@ -266,6 +270,10 @@ fn record_stats( let u16_table = UXTable::construct(meta); stats.record_shared("u16_table", meta); + let chunkctx_table = ChunkCtxTable::construct(meta); + // chunkctx table with gates + stats.record("chunkctx_table", meta); + // Use a mock randomness instead of the randomness derived from the challenge // (either from mock or real prover) to help debugging assignments. let power_of_randomness: [Expression; 31] = @@ -343,6 +351,10 @@ fn record_stats( stats.record("state", meta); let exp_circuit = ExpCircuitConfig::new(meta, exp_table); stats.record("exp", meta); + + let sig_table = SigTable::construct(meta); + + let chunk_ctx_config = ChunkContextConfig::new(meta, &challenges); let evm_circuit = EvmCircuitConfig::new( meta, EvmCircuitConfigArgs { @@ -357,6 +369,7 @@ fn record_stats( u8_table, u16_table, sig_table, + chunk_ctx_config, feature_config, }, ); diff --git a/zkevm-circuits/src/bytecode_circuit.rs b/zkevm-circuits/src/bytecode_circuit.rs index b02f233868..3cc4859a0a 100644 --- a/zkevm-circuits/src/bytecode_circuit.rs +++ b/zkevm-circuits/src/bytecode_circuit.rs @@ -20,7 +20,7 @@ use crate::{ word::{empty_code_hash_word_value, Word32, WordExpr, WordLoHi}, Challenges, Expr, SubCircuit, SubCircuitConfig, }, - witness::{self}, + witness::{self, Chunk}, }; use bus_mapping::state_db::{CodeDB, EMPTY_CODE_HASH_LE}; use eth_types::{Bytecode, Field}; @@ -800,15 +800,15 @@ impl SubCircuit for BytecodeCircuit { 6 } - fn new_from_block(block: &witness::Block) -> Self { - Self::new(block.bytecodes.clone(), block.circuits_params.max_bytecode) + fn new_from_block(block: &witness::Block, chunk: &Chunk) -> Self { + Self::new(block.bytecodes.clone(), chunk.fixed_param.max_bytecode) } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize) { ( block.bytecodes.num_rows_required_for_bytecode_table(), - block.circuits_params.max_bytecode, + chunk.fixed_param.max_bytecode, ) } diff --git a/zkevm-circuits/src/copy_circuit.rs b/zkevm-circuits/src/copy_circuit.rs index 644a951900..4466bf9694 100644 --- a/zkevm-circuits/src/copy_circuit.rs +++ b/zkevm-circuits/src/copy_circuit.rs @@ -18,7 +18,7 @@ use crate::{ }, util::{Challenges, SubCircuit, SubCircuitConfig}, witness, - witness::{RwMap, Transaction}, + witness::{Chunk, Rw, RwMap, Transaction}, }; use bus_mapping::{ circuit_input_builder::{CopyDataType, CopyEvent}, @@ -793,6 +793,8 @@ pub struct ExternalData { pub max_rws: usize, /// StateCircuit -> rws pub rws: RwMap, + /// Prev chunk last Rw + pub prev_chunk_last_rw: Option, /// BytecodeCircuit -> bytecodes pub bytecodes: CodeDB, } @@ -838,11 +840,8 @@ impl CopyCircuit { /// to assign lookup tables. This constructor is only suitable to be /// used by the SuperCircuit, which already assigns the external lookup /// tables. - pub fn new_from_block_no_external(block: &witness::Block) -> Self { - Self::new( - block.copy_events.clone(), - block.circuits_params.max_copy_rows, - ) + pub fn new_from_block_no_external(block: &witness::Block, chunk: &Chunk) -> Self { + Self::new(block.copy_events.clone(), chunk.fixed_param.max_copy_rows) } } @@ -855,23 +854,28 @@ impl SubCircuit for CopyCircuit { 6 } - fn new_from_block(block: &witness::Block) -> Self { + fn new_from_block(block: &witness::Block, chunk: &Chunk) -> Self { + let chunked_copy_events = block + .copy_events + .get(chunk.chunk_context.initial_copy_index..chunk.chunk_context.end_copy_index) + .unwrap_or_default(); Self::new_with_external_data( - block.copy_events.clone(), - block.circuits_params.max_copy_rows, + chunked_copy_events.to_owned(), + chunk.fixed_param.max_copy_rows, ExternalData { - max_txs: block.circuits_params.max_txs, - max_calldata: block.circuits_params.max_calldata, + max_txs: chunk.fixed_param.max_txs, + max_calldata: chunk.fixed_param.max_calldata, txs: block.txs.clone(), - max_rws: block.circuits_params.max_rws, - rws: block.rws.clone(), + max_rws: chunk.fixed_param.max_rws, + rws: chunk.chrono_rws.clone(), + prev_chunk_last_rw: chunk.prev_chunk_last_chrono_rw, bytecodes: block.bytecodes.clone(), }, ) } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize) { ( block .copy_events @@ -879,7 +883,7 @@ impl SubCircuit for CopyCircuit { .map(|c| c.bytes.len() * 2) .sum::() + 2, - block.circuits_params.max_copy_rows, + chunk.fixed_param.max_copy_rows, ) } diff --git a/zkevm-circuits/src/copy_circuit/dev.rs b/zkevm-circuits/src/copy_circuit/dev.rs index cd5b4c2a5c..4e45dd2b0c 100644 --- a/zkevm-circuits/src/copy_circuit/dev.rs +++ b/zkevm-circuits/src/copy_circuit/dev.rs @@ -61,8 +61,9 @@ impl Circuit for CopyCircuit { config.0.rw_table.load( &mut layouter, - &self.external_data.rws.table_assignments(), + &self.external_data.rws.table_assignments(true), self.external_data.max_rws, + self.external_data.prev_chunk_last_rw, )?; config diff --git a/zkevm-circuits/src/copy_circuit/test.rs b/zkevm-circuits/src/copy_circuit/test.rs index 62a690f6ba..7f2056847b 100644 --- a/zkevm-circuits/src/copy_circuit/test.rs +++ b/zkevm-circuits/src/copy_circuit/test.rs @@ -2,7 +2,7 @@ use crate::{ copy_circuit::*, evm_circuit::{test::rand_bytes, witness::block_convert}, util::unusable_rows, - witness::Block, + witness::{chunk_convert, Block}, }; use bus_mapping::{ circuit_input_builder::{CircuitInputBuilder, FixedCParams}, @@ -43,17 +43,23 @@ pub fn test_copy_circuit( pub fn test_copy_circuit_from_block( k: u32, block: Block, + chunk: Chunk, ) -> Result<(), Vec> { + let chunked_copy_events = block + .copy_events + .get(chunk.chunk_context.initial_copy_index..chunk.chunk_context.end_copy_index) + .unwrap_or_default(); test_copy_circuit::( k, - block.copy_events, - block.circuits_params.max_copy_rows, + chunked_copy_events.to_owned(), + chunk.fixed_param.max_copy_rows, ExternalData { - max_txs: block.circuits_params.max_txs, - max_calldata: block.circuits_params.max_calldata, + max_txs: chunk.fixed_param.max_txs, + max_calldata: chunk.fixed_param.max_calldata, txs: block.txs, - max_rws: block.circuits_params.max_rws, - rws: block.rws, + max_rws: chunk.fixed_param.max_rws, + rws: chunk.chrono_rws, + prev_chunk_last_rw: chunk.prev_chunk_last_chrono_rw, bytecodes: block.bytecodes, }, ) @@ -163,48 +169,51 @@ fn gen_tx_log_data() -> CircuitInputBuilder { let test_ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(code).unwrap(); let block: GethData = test_ctx.into(); // Needs default params for variadic check - let mut builder = - BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) - .new_circuit_input_builder(); - builder + + BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder + .unwrap() } #[test] fn copy_circuit_valid_calldatacopy() { let builder = gen_calldatacopy_data(); let block = block_convert::(&builder).unwrap(); - assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + assert_eq!(test_copy_circuit_from_block(14, block, chunk), Ok(())); } #[test] fn copy_circuit_valid_codecopy() { let builder = gen_codecopy_data(); let block = block_convert::(&builder).unwrap(); - assert_eq!(test_copy_circuit_from_block(10, block), Ok(())); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + assert_eq!(test_copy_circuit_from_block(10, block, chunk), Ok(())); } #[test] fn copy_circuit_valid_extcodecopy() { let builder = gen_extcodecopy_data(); let block = block_convert::(&builder).unwrap(); - assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + assert_eq!(test_copy_circuit_from_block(14, block, chunk), Ok(())); } #[test] fn copy_circuit_valid_sha3() { let builder = gen_sha3_data(); let block = block_convert::(&builder).unwrap(); - assert_eq!(test_copy_circuit_from_block(14, block), Ok(())); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + assert_eq!(test_copy_circuit_from_block(14, block, chunk), Ok(())); } #[test] fn copy_circuit_valid_tx_log() { let builder = gen_tx_log_data(); let block = block_convert::(&builder).unwrap(); - assert_eq!(test_copy_circuit_from_block(10, block), Ok(())); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + assert_eq!(test_copy_circuit_from_block(10, block, chunk), Ok(())); } #[test] @@ -216,9 +225,10 @@ fn copy_circuit_invalid_calldatacopy() { builder.block.copy_events[0].bytes[0].0.wrapping_add(1); let block = block_convert::(&builder).unwrap(); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); assert_error_matches( - test_copy_circuit_from_block(14, block), + test_copy_circuit_from_block(14, block, chunk), vec!["Memory lookup", "Tx calldata lookup"], ); } @@ -232,9 +242,10 @@ fn copy_circuit_invalid_codecopy() { builder.block.copy_events[0].bytes[0].0.wrapping_add(1); let block = block_convert::(&builder).unwrap(); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); assert_error_matches( - test_copy_circuit_from_block(10, block), + test_copy_circuit_from_block(10, block, chunk), vec!["Memory lookup", "Bytecode lookup"], ); } @@ -248,9 +259,10 @@ fn copy_circuit_invalid_extcodecopy() { builder.block.copy_events[0].bytes[0].0.wrapping_add(1); let block = block_convert::(&builder).unwrap(); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); assert_error_matches( - test_copy_circuit_from_block(14, block), + test_copy_circuit_from_block(14, block, chunk), vec!["Memory lookup", "Bytecode lookup"], ); } @@ -264,9 +276,10 @@ fn copy_circuit_invalid_sha3() { builder.block.copy_events[0].bytes[0].0.wrapping_add(1); let block = block_convert::(&builder).unwrap(); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); assert_error_matches( - test_copy_circuit_from_block(14, block), + test_copy_circuit_from_block(14, block, chunk), vec!["Memory lookup"], ); } @@ -280,9 +293,10 @@ fn copy_circuit_invalid_tx_log() { builder.block.copy_events[0].bytes[0].0.wrapping_add(1); let block = block_convert::(&builder).unwrap(); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); assert_error_matches( - test_copy_circuit_from_block(10, block), + test_copy_circuit_from_block(10, block, chunk), vec!["Memory lookup", "TxLog lookup"], ); } @@ -295,10 +309,8 @@ fn variadic_size_check() { let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) .unwrap() .into(); - let mut builder = - BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) - .new_circuit_input_builder(); - builder + let builder = BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); let block2 = block_convert::(&builder).unwrap(); diff --git a/zkevm-circuits/src/evm_circuit.rs b/zkevm-circuits/src/evm_circuit.rs index 6e1e5b1147..b595225b31 100644 --- a/zkevm-circuits/src/evm_circuit.rs +++ b/zkevm-circuits/src/evm_circuit.rs @@ -1,5 +1,6 @@ //! The EVM circuit implementation. +use gadgets::permutation::{PermutationChip, PermutationChipConfig}; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, plonk::*, @@ -13,9 +14,9 @@ pub(crate) mod util; #[cfg(test)] pub(crate) mod test; -use self::step::HasExecutionState; #[cfg(feature = "test-circuits")] pub use self::EvmCircuit as TestEvmCircuit; +use self::{step::HasExecutionState, witness::rw::ToVec}; pub use crate::witness; use crate::{ @@ -24,7 +25,8 @@ use crate::{ BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, LookupTable, RwTable, SigTable, TxTable, UXTable, }, - util::{Challenges, SubCircuit, SubCircuitConfig}, + util::{chunk_ctx::ChunkContextConfig, Challenges, SubCircuit, SubCircuitConfig}, + witness::{Chunk, RwMap}, }; use bus_mapping::{circuit_input_builder::FeatureConfig, evm::OpcodeId}; use eth_types::Field; @@ -44,13 +46,21 @@ pub struct EvmCircuitConfig { pub execution: Box>, // External tables tx_table: TxTable, - rw_table: RwTable, + pub(crate) rw_table: RwTable, bytecode_table: BytecodeTable, block_table: BlockTable, copy_table: CopyTable, keccak_table: KeccakTable, exp_table: ExpTable, sig_table: SigTable, + /// rw permutation config + pub rw_permutation_config: PermutationChipConfig, + + // pi for chunk context continuity + pi_chunk_continuity: Column, + + // chunk_ctx_config + chunk_ctx_config: ChunkContextConfig, } /// Circuit configuration arguments @@ -77,6 +87,8 @@ pub struct EvmCircuitConfigArgs { pub u16_table: UXTable<16>, /// SigTable pub sig_table: SigTable, + /// chunk_ctx config + pub chunk_ctx_config: ChunkContextConfig, /// Feature config pub feature_config: FeatureConfig, } @@ -99,10 +111,12 @@ impl SubCircuitConfig for EvmCircuitConfig { u8_table, u16_table, sig_table, + chunk_ctx_config, feature_config, }: Self::ConfigArgs, ) -> Self { let fixed_table = [(); 4].map(|_| meta.fixed_column()); + let execution = Box::new(ExecutionConfig::configure( meta, challenges, @@ -117,11 +131,12 @@ impl SubCircuitConfig for EvmCircuitConfig { &keccak_table, &exp_table, &sig_table, + &chunk_ctx_config.chunk_ctx_table, + &chunk_ctx_config.is_first_chunk, + &chunk_ctx_config.is_last_chunk, feature_config, )); - u8_table.annotate_columns(meta); - u16_table.annotate_columns(meta); fixed_table.iter().enumerate().for_each(|(idx, &col)| { meta.annotate_lookup_any_column(col, || format!("fix_table_{}", idx)) }); @@ -135,6 +150,15 @@ impl SubCircuitConfig for EvmCircuitConfig { u8_table.annotate_columns(meta); u16_table.annotate_columns(meta); sig_table.annotate_columns(meta); + chunk_ctx_config.chunk_ctx_table.annotate_columns(meta); + + let rw_permutation_config = PermutationChip::configure( + meta, + >::advice_columns(&rw_table), + ); + + let pi_chunk_continuity = meta.instance_column(); + meta.enable_equality(pi_chunk_continuity); Self { fixed_table, @@ -149,6 +173,9 @@ impl SubCircuitConfig for EvmCircuitConfig { keccak_table, exp_table, sig_table, + rw_permutation_config, + chunk_ctx_config, + pi_chunk_continuity, } } } @@ -183,31 +210,35 @@ impl EvmCircuitConfig { pub struct EvmCircuit { /// Block pub block: Option>, + /// Chunk + pub chunk: Option>, fixed_table_tags: Vec, } impl EvmCircuit { /// Return a new EvmCircuit - pub fn new(block: Block) -> Self { + pub fn new(block: Block, chunk: Chunk) -> Self { Self { block: Some(block), + chunk: Some(chunk), fixed_table_tags: FixedTableTag::iter().collect(), } } #[cfg(any(test, feature = "test-circuits"))] /// Construct the EvmCircuit with only subset of Fixed table tags required by tests to save /// testing time - pub(crate) fn get_test_circuit_from_block(block: Block) -> Self { + pub(crate) fn get_test_circuit_from_block(block: Block, chunk: Chunk) -> Self { let fixed_table_tags = detect_fixed_table_tags(&block); Self { block: Some(block), + chunk: Some(chunk), fixed_table_tags, } } #[cfg(any(test, feature = "test-circuits"))] /// Calculate which rows are "actually" used in the circuit - pub(crate) fn get_active_rows(block: &Block) -> (Vec, Vec) { - let max_offset = Self::get_num_rows_required(block); + pub(crate) fn get_active_rows(block: &Block, chunk: &Chunk) -> (Vec, Vec) { + let max_offset = Self::get_num_rows_required(block, chunk); // some gates are enabled on all rows let gates_row_ids = (0..max_offset).collect(); // lookups are enabled at "q_step" rows and byte lookup rows @@ -216,25 +247,29 @@ impl EvmCircuit { } /// Get the minimum number of rows required to process the block /// If unspecified, then compute it - pub(crate) fn get_num_rows_required(block: &Block) -> usize { - let evm_rows = block.circuits_params.max_evm_rows; + pub(crate) fn get_num_rows_required(block: &Block, chunk: &Chunk) -> usize { + let evm_rows = chunk.fixed_param.max_evm_rows; if evm_rows == 0 { - Self::get_min_num_rows_required(block) + Self::get_min_num_rows_required(block, chunk) } else { // It must have at least one unused row. - block.circuits_params.max_evm_rows + 1 + chunk.fixed_param.max_evm_rows + 1 } } /// Compute the minimum number of rows required to process the block - fn get_min_num_rows_required(block: &Block) -> usize { + fn get_min_num_rows_required(block: &Block, chunk: &Chunk) -> usize { let mut num_rows = 0; for transaction in &block.txs { for step in transaction.steps() { - num_rows += step.execution_state().get_step_height(); + if chunk.chunk_context.initial_rwc <= step.rwc.0 + || step.rwc.0 < chunk.chunk_context.end_rwc + { + num_rows += step.execution_state().get_step_height(); + } } } - // It must have one row for EndBlock and at least one unused one + // It must have one row for EndBlock/EndChunk and at least one unused one num_rows + 2 } } @@ -248,13 +283,14 @@ impl SubCircuit for EvmCircuit { MAX_STEP_HEIGHT + STEP_STATE_HEIGHT + 3 } - fn new_from_block(block: &witness::Block) -> Self { - Self::new(block.clone()) + fn new_from_block(block: &witness::Block, chunk: &witness::Chunk) -> Self { + Self::new(block.clone(), chunk.clone()) } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { - let num_rows_required_for_execution_steps: usize = Self::get_num_rows_required(block); + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize) { + let num_rows_required_for_execution_steps: usize = + Self::get_num_rows_required(block, chunk); let num_rows_required_for_fixed_table: usize = detect_fixed_table_tags(block) .iter() .map(|tag| tag.build::().count()) @@ -264,7 +300,7 @@ impl SubCircuit for EvmCircuit { num_rows_required_for_execution_steps, num_rows_required_for_fixed_table, ), - block.circuits_params.max_evm_rows, + chunk.fixed_param.max_evm_rows, ) } @@ -276,9 +312,93 @@ impl SubCircuit for EvmCircuit { layouter: &mut impl Layouter, ) -> Result<(), Error> { let block = self.block.as_ref().unwrap(); + let chunk = self.chunk.as_ref().unwrap(); config.load_fixed_table(layouter, self.fixed_table_tags.clone())?; - config.execution.assign_block(layouter, block, challenges) + + let _max_offset_index = config + .execution + .assign_block(layouter, block, chunk, challenges)?; + + let (rw_rows_padding, _) = RwMap::table_assignments_padding( + &chunk.chrono_rws.table_assignments(true), + chunk.fixed_param.max_rws, + chunk.prev_chunk_last_chrono_rw, + ); + let ( + alpha_cell, + gamma_cell, + row_fingerprints_prev_cell, + row_fingerprints_next_cell, + acc_fingerprints_prev_cell, + acc_fingerprints_next_cell, + ) = layouter.assign_region( + || "evm circuit", + |mut region| { + region.name_column(|| "EVM_pi_chunk_continuity", config.pi_chunk_continuity); + config.rw_table.load_with_region( + &mut region, + // pass non-padding rws to `load_with_region` since it will be padding + // inside + &chunk.chrono_rws.table_assignments(true), + // align with state circuit to padding to same max_rws + chunk.fixed_param.max_rws, + chunk.prev_chunk_last_chrono_rw, + )?; + let permutation_cells = config.rw_permutation_config.assign( + &mut region, + Value::known(chunk.permu_alpha), + Value::known(chunk.permu_gamma), + // Value::known(chunk.chrono_rw_prev_fingerprint), + Value::known(chunk.chrono_rw_fingerprints.prev_mul_acc), + &rw_rows_padding.to2dvec(), + "evm circuit", + )?; + Ok(permutation_cells) + }, + )?; + + // constrain fields related to proof chunk in public input + [ + alpha_cell, + gamma_cell, + row_fingerprints_prev_cell, + row_fingerprints_next_cell, + acc_fingerprints_prev_cell, + acc_fingerprints_next_cell, + ] + .iter() + .enumerate() + .try_for_each(|(i, cell)| { + layouter.constrain_instance(cell.cell(), config.pi_chunk_continuity, i) + })?; + Ok(()) + } + + /// Compute the public inputs for this circuit. + fn instance(&self) -> Vec> { + let chunk = self.chunk.as_ref().unwrap(); + + let (rw_table_chunked_index, rw_table_total_chunks) = + (chunk.chunk_context.idx, chunk.chunk_context.total_chunks); + + vec![ + vec![ + F::from(rw_table_chunked_index as u64), + F::from(rw_table_chunked_index as u64) + F::ONE, + F::from(rw_table_total_chunks as u64), + F::from(chunk.chunk_context.initial_rwc as u64), + F::from(chunk.chunk_context.end_rwc as u64), + ], + vec![ + chunk.permu_alpha, + chunk.permu_gamma, + chunk.chrono_rw_fingerprints.prev_ending_row, + chunk.chrono_rw_fingerprints.ending_row, + chunk.chrono_rw_fingerprints.prev_mul_acc, + chunk.chrono_rw_fingerprints.mul_acc, + ], + ] } } @@ -358,8 +478,12 @@ pub(crate) mod cached { } impl EvmCircuitCached { - pub(crate) fn get_test_circuit_from_block(block: Block) -> Self { - Self(EvmCircuit::::get_test_circuit_from_block(block)) + pub(crate) fn get_test_circuit_from_block(block: Block, chunk: Chunk) -> Self { + Self(EvmCircuit::::get_test_circuit_from_block(block, chunk)) + } + + pub(crate) fn instance(&self) -> Vec> { + self.0.instance() } } } @@ -395,6 +519,7 @@ impl Circuit for EvmCircuit { let u16_table = UXTable::construct(meta); let challenges = Challenges::construct(meta); let challenges_expr = challenges.exprs(meta); + let chunk_ctx_config = ChunkContextConfig::new(meta, &challenges_expr); let sig_table = SigTable::construct(meta); ( @@ -412,6 +537,7 @@ impl Circuit for EvmCircuit { u8_table, u16_table, sig_table, + chunk_ctx_config, feature_config: params, }, ), @@ -429,6 +555,7 @@ impl Circuit for EvmCircuit { mut layouter: impl Layouter, ) -> Result<(), Error> { let block = self.block.as_ref().unwrap(); + let chunk = self.chunk.as_ref().unwrap(); let (config, challenges) = config; let challenges = challenges.values(&mut layouter); @@ -436,29 +563,33 @@ impl Circuit for EvmCircuit { config.tx_table.load( &mut layouter, &block.txs, - block.circuits_params.max_txs, - block.circuits_params.max_calldata, - )?; - block.rws.check_rw_counter_sanity(); - config.rw_table.load( - &mut layouter, - &block.rws.table_assignments(), - block.circuits_params.max_rws, + chunk.fixed_param.max_txs, + chunk.fixed_param.max_calldata, )?; + chunk.chrono_rws.check_rw_counter_sanity(); config .bytecode_table .load(&mut layouter, block.bytecodes.clone())?; config.block_table.load(&mut layouter, &block.context)?; - config.copy_table.load(&mut layouter, block, &challenges)?; + config + .copy_table + .load(&mut layouter, block, chunk, &challenges)?; config .keccak_table .dev_load(&mut layouter, &block.sha3_inputs, &challenges)?; - config.exp_table.load(&mut layouter, block)?; + config.exp_table.load(&mut layouter, block, chunk)?; config.u8_table.load(&mut layouter)?; config.u16_table.load(&mut layouter)?; config.sig_table.dev_load(&mut layouter, block)?; + // synthesize chunk context + config.chunk_ctx_config.assign_chunk_context( + &mut layouter, + &chunk.chunk_context, + Self::get_num_rows_required(block, chunk) - 1, + )?; + self.synthesize_sub(&config, &challenges, &mut layouter) } } @@ -469,14 +600,14 @@ mod evm_circuit_stats { evm_circuit::EvmCircuit, test_util::CircuitTestBuilder, util::{unusable_rows, SubCircuit}, - witness::block_convert, + witness::{block_convert, chunk_convert}, }; use bus_mapping::{ circuit_input_builder::{FeatureConfig, FixedCParams}, mock::BlockData, }; - use eth_types::{bytecode, geth_types::GethData}; + use eth_types::{address, bytecode, geth_types::GethData, Word}; use halo2_proofs::{self, dev::MockProver, halo2curves::bn256::Fr}; use mock::test_ctx::{ @@ -516,12 +647,72 @@ mod evm_circuit_stats { CircuitTestBuilder::new_from_test_ctx( TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b).unwrap(), ) - .block_modifier(Box::new(|block| { - block.circuits_params.max_evm_rows = (1 << 18) - 100 + .block_modifier(Box::new(|_block, chunk| { + chunk + .iter_mut() + .for_each(|chunk| chunk.fixed_param.max_evm_rows = (1 << 18) - 100); })) .run(); } + #[test] + fn reproduce_heavytest_error() { + let bytecode = bytecode! { + GAS + STOP + }; + + let addr_a = address!("0x000000000000000000000000000000000000AAAA"); + let addr_b = address!("0x000000000000000000000000000000000000BBBB"); + + let block: GethData = TestContext::<2, 1>::new( + None, + |accs| { + accs[0] + .address(addr_b) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + accs[1].address(addr_a).balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap() + .into(); + + let circuits_params = FixedCParams { + total_chunks: 1, + max_txs: 1, + max_withdrawals: 5, + max_calldata: 32, + max_rws: 256, + max_copy_rows: 256, + max_exp_steps: 256, + max_bytecode: 512, + max_evm_rows: 0, + max_keccak_rows: 0, + max_vertical_circuit_rows: 0, + }; + let builder = BlockData::new_from_geth_data_with_params(block.clone(), circuits_params) + .new_circuit_input_builder() + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + let block = block_convert::(&builder).unwrap(); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + let k = block.get_test_degree(&chunk); + let circuit = EvmCircuit::::get_test_circuit_from_block(block, chunk); + let instance = circuit.instance(); + let prover1 = MockProver::::run(k, &circuit, instance).unwrap(); + let res = prover1.verify(); + if let Err(err) = res { + panic!("Failed verification {:?}", err); + } + } #[test] fn variadic_size_check() { let params = FixedCParams { @@ -532,16 +723,17 @@ mod evm_circuit_stats { let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) .unwrap() .into(); - let mut builder = BlockData::new_from_geth_data_with_params(block.clone(), params) - .new_circuit_input_builder(); - builder + let builder = BlockData::new_from_geth_data_with_params(block.clone(), params) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); let block = block_convert::(&builder).unwrap(); - let k = block.get_test_degree(); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + let k = block.get_test_degree(&chunk); - let circuit = EvmCircuit::::get_test_circuit_from_block(block); - let prover1 = MockProver::::run(k, &circuit, vec![]).unwrap(); + let circuit = EvmCircuit::::get_test_circuit_from_block(block, chunk); + let instance = circuit.instance(); + let prover1 = MockProver::::run(k, &circuit, instance).unwrap(); let code = bytecode! { STOP @@ -554,16 +746,31 @@ mod evm_circuit_stats { ) .unwrap() .into(); - let mut builder = BlockData::new_from_geth_data_with_params(block.clone(), params) - .new_circuit_input_builder(); - builder + let builder = BlockData::new_from_geth_data_with_params(block.clone(), params) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); let block = block_convert::(&builder).unwrap(); - let k = block.get_test_degree(); - let circuit = EvmCircuit::::get_test_circuit_from_block(block); - let prover2 = MockProver::::run(k, &circuit, vec![]).unwrap(); - + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + let k = block.get_test_degree(&chunk); + let circuit = EvmCircuit::::get_test_circuit_from_block(block, chunk); + let instance = circuit.instance(); + let prover2 = MockProver::::run(k, &circuit, instance).unwrap(); + + assert_eq!(prover1.fixed().len(), prover2.fixed().len()); + prover1 + .fixed() + .iter() + .zip(prover2.fixed().iter()) + .enumerate() + .for_each(|(i, (f1, f2))| { + assert_eq!( + f1, f2, + "at index {}. Usually it happened when mismatch constant constraint, e.g. + region.constrain_constant() are calling in-consisntent", + i + ) + }); assert_eq!(prover1.fixed(), prover2.fixed()); assert_eq!(prover1.permutation(), prover2.permutation()); } diff --git a/zkevm-circuits/src/evm_circuit/execution.rs b/zkevm-circuits/src/evm_circuit/execution.rs index 0e106b8412..e4c628ed16 100644 --- a/zkevm-circuits/src/evm_circuit/execution.rs +++ b/zkevm-circuits/src/evm_circuit/execution.rs @@ -1,8 +1,9 @@ use super::{ param::{ - BLOCK_TABLE_LOOKUPS, BYTECODE_TABLE_LOOKUPS, COPY_TABLE_LOOKUPS, EXP_TABLE_LOOKUPS, - FIXED_TABLE_LOOKUPS, KECCAK_TABLE_LOOKUPS, N_COPY_COLUMNS, N_PHASE1_COLUMNS, N_U16_LOOKUPS, - N_U8_LOOKUPS, RW_TABLE_LOOKUPS, SIG_TABLE_LOOKUPS, TX_TABLE_LOOKUPS, + BLOCK_TABLE_LOOKUPS, BYTECODE_TABLE_LOOKUPS, CHUNK_CTX_TABLE_LOOKUPS, COPY_TABLE_LOOKUPS, + EXP_TABLE_LOOKUPS, FIXED_TABLE_LOOKUPS, KECCAK_TABLE_LOOKUPS, N_COPY_COLUMNS, + N_PHASE1_COLUMNS, N_U16_LOOKUPS, N_U8_LOOKUPS, RW_TABLE_LOOKUPS, SIG_TABLE_LOOKUPS, + TX_TABLE_LOOKUPS, }, step::HasExecutionState, util::{instrumentation::Instrument, CachedRegion, StoredExpression}, @@ -18,9 +19,9 @@ use crate::{ }, evaluate_expression, rlc, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, - table::LookupTable, + table::{chunk_ctx_table::ChunkCtxFieldTag, LookupTable}, util::{ cell_manager::{CMFixedWidthStrategy, CellManager, CellType}, Challenges, Expr, @@ -28,7 +29,8 @@ use crate::{ }; use bus_mapping::{circuit_input_builder::FeatureConfig, operation::Target}; use eth_types::{evm_unimplemented, Field}; -use gadgets::util::not; + +use gadgets::{is_zero::IsZeroConfig, util::not}; use halo2_proofs::{ circuit::{Layouter, Region, Value}, plonk::{ @@ -38,7 +40,7 @@ use halo2_proofs::{ poly::Rotation, }; use std::{ - collections::{BTreeSet, HashMap}, + collections::{BTreeSet, HashMap, HashSet}, iter, }; use strum::IntoEnumIterator; @@ -47,6 +49,7 @@ mod add_sub; mod addmod; mod address; mod balance; +mod begin_chunk; mod begin_tx; mod bitwise; mod block_ctx; @@ -66,6 +69,7 @@ mod create; mod dummy; mod dup; mod end_block; +mod end_chunk; mod end_tx; mod error_code_store; mod error_invalid_creation_code; @@ -106,6 +110,7 @@ mod mulmod; #[path = "execution/not.rs"] mod opcode_not; mod origin; +mod padding; mod pc; mod pop; mod precompiles; @@ -125,7 +130,10 @@ mod sstore; mod stop; mod swap; -use self::{block_ctx::BlockCtxGadget, sha3::Sha3Gadget}; +use self::{ + begin_chunk::BeginChunkGadget, block_ctx::BlockCtxGadget, end_chunk::EndChunkGadget, + sha3::Sha3Gadget, +}; use add_sub::AddSubGadget; use addmod::AddModGadget; use address::AddressGadget; @@ -188,6 +196,7 @@ use mul_div_mod::MulDivModGadget; use mulmod::MulModGadget; use opcode_not::NotGadget; use origin::OriginGadget; +use padding::PaddingGadget; use pc::PcGadget; use pop::PopGadget; use precompiles::{EcrecoverGadget, IdentityGadget}; @@ -213,11 +222,13 @@ pub(crate) trait ExecutionGadget { fn configure(cb: &mut EVMConstraintBuilder) -> Self; + #[allow(clippy::too_many_arguments)] fn assign_exec_step( &self, region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + chunk: &Chunk, transaction: &Transaction, call: &Call, step: &ExecStep, @@ -250,7 +261,10 @@ pub struct ExecutionConfig { // internal state gadgets begin_tx_gadget: Box>, end_block_gadget: Box>, + padding_gadget: Box>, end_tx_gadget: Box>, + begin_chunk_gadget: Box>, + end_chunk_gadget: Box>, // opcode gadgets add_sub_gadget: Box>, addmod_gadget: Box>, @@ -343,6 +357,8 @@ pub struct ExecutionConfig { invalid_tx: Option>>, } +type TxCallStep<'a> = (&'a Transaction, &'a Call, &'a ExecStep); + impl ExecutionConfig { #[allow(clippy::too_many_arguments)] #[allow(clippy::redundant_closure_call)] @@ -360,6 +376,9 @@ impl ExecutionConfig { keccak_table: &dyn LookupTable, exp_table: &dyn LookupTable, sig_table: &dyn LookupTable, + chunk_ctx_table: &dyn LookupTable, + is_first_chunk: &IsZeroConfig, + is_last_chunk: &IsZeroConfig, feature_config: FeatureConfig, ) -> Self { let mut instrument = Instrument::default(); @@ -390,6 +409,22 @@ impl ExecutionConfig { let step_curr = Step::new(meta, advices, 0); let mut height_map = HashMap::new(); + let (execute_state_first_step_whitelist, execute_state_last_step_whitelist) = ( + HashSet::from_iter( + vec![ + ExecutionState::BeginTx, + ExecutionState::Padding, + ExecutionState::BeginChunk, + ] + .into_iter() + .chain( + feature_config + .invalid_tx + .then_some(ExecutionState::InvalidTx), + ), + ), + HashSet::from([ExecutionState::EndBlock, ExecutionState::EndChunk]), + ); meta.create_gate("Constrain execution state", |meta| { let q_usable = meta.query_selector(q_usable); @@ -399,37 +434,55 @@ impl ExecutionConfig { let execution_state_selector_constraints = step_curr.state.execution_state.configure(); - // 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::EndBlock] - .into_iter() - .chain( - feature_config - .invalid_tx - .then_some(ExecutionState::InvalidTx), - ), - ); + let first_step_first_chunk_check = { + let exestates = step_curr + .execution_state_selector(execute_state_first_step_whitelist.iter().cloned()); iter::once(( - "First step should be BeginTx, InvalidTx or EndBlock", - q_step_first * (1.expr() - begin_tx_invalid_tx_end_block_selector), + "First step first chunk should be BeginTx or EndBlock or BeginChunk", + (1.expr() - is_first_chunk.expr()) + * q_step_first.clone() + * (1.expr() - exestates), )) }; - let last_step_check = { + let first_step_non_first_chunk_check = { + let begin_chunk_selector = + step_curr.execution_state_selector([ExecutionState::BeginChunk]); + iter::once(( + "First step (non first chunk) should be BeginChunk", + (1.expr() - is_first_chunk.expr()) + * q_step_first + * (1.expr() - begin_chunk_selector), + )) + }; + + let last_step_last_chunk_check = { let end_block_selector = step_curr.execution_state_selector([ExecutionState::EndBlock]); iter::once(( - "Last step should be EndBlock", - q_step_last * (1.expr() - end_block_selector), + "Last step last chunk should be EndBlock", + is_last_chunk.expr() * q_step_last.clone() * (1.expr() - end_block_selector), + )) + }; + + let last_step_non_last_chunk_check = { + let end_chunk_selector = + step_curr.execution_state_selector([ExecutionState::EndChunk]); + iter::once(( + "Last step (non last chunk) should be EndChunk", + (1.expr() - is_last_chunk.expr()) + * q_step_last + * (1.expr() - end_chunk_selector), )) }; execution_state_selector_constraints .into_iter() .map(move |(name, poly)| (name, q_usable.clone() * q_step.clone() * poly)) - .chain(first_step_check) - .chain(last_step_check) + .chain(first_step_first_chunk_check) + .chain(first_step_non_first_chunk_check) + .chain(last_step_last_chunk_check) + .chain(last_step_non_last_chunk_check) }); meta.create_gate("q_step", |meta| { @@ -447,8 +500,8 @@ impl ExecutionConfig { cb.condition(q_step_first, |cb| { cb.require_equal("q_step == 1", q_step.clone(), 1.expr()); cb.require_equal( - "rw_counter is initialized to be 1", - step_curr.state.rw_counter.expr(), + "inner_rw_counter is initialized to be 1", + step_curr.state.inner_rw_counter.expr(), 1.expr(), ) }); @@ -501,13 +554,16 @@ impl ExecutionConfig { Box::new(Self::configure_gadget( meta, advices, + &challenges, q_usable, q_step, num_rows_until_next_step, q_step_first, q_step_last, - &challenges, &step_curr, + chunk_ctx_table, + &execute_state_first_step_whitelist, + &execute_state_last_step_whitelist, &mut height_map, &mut stored_expressions_map, &mut debug_expressions_map, @@ -531,8 +587,11 @@ impl ExecutionConfig { advices, // internal states begin_tx_gadget: configure_gadget!(), - end_block_gadget: configure_gadget!(), + padding_gadget: configure_gadget!(), end_tx_gadget: configure_gadget!(), + begin_chunk_gadget: configure_gadget!(), + end_chunk_gadget: configure_gadget!(), + end_block_gadget: configure_gadget!(), invalid_tx: feature_config.invalid_tx.then(|| configure_gadget!()), // opcode gadgets add_sub_gadget: configure_gadget!(), @@ -640,6 +699,7 @@ impl ExecutionConfig { keccak_table, exp_table, sig_table, + chunk_ctx_table, &challenges, &cell_manager, ); @@ -654,13 +714,16 @@ impl ExecutionConfig { fn configure_gadget>( meta: &mut ConstraintSystem, advices: [Column; STEP_WIDTH], + challenges: &Challenges>, q_usable: Selector, q_step: Column, num_rows_until_next_step: Column, q_step_first: Selector, q_step_last: Selector, - challenges: &Challenges>, step_curr: &Step, + chunk_ctx_table: &dyn LookupTable, + execute_state_first_step_whitelist: &HashSet, + execute_state_last_step_whitelist: &HashSet, height_map: &mut HashMap, stored_expressions_map: &mut HashMap>>, debug_expressions_map: &mut HashMap)>>, @@ -686,6 +749,7 @@ impl ExecutionConfig { // Now actually configure the gadget with the correct minimal height let step_next = &Step::new(meta, advices, height); + let mut cb = EVMConstraintBuilder::new( meta, step_curr.clone(), @@ -708,11 +772,15 @@ impl ExecutionConfig { height_map, stored_expressions_map, debug_expressions_map, + execute_state_first_step_whitelist, + execute_state_last_step_whitelist, instrument, G::NAME, G::EXECUTION_STATE, height, cb, + chunk_ctx_table, + challenges, ); gadget @@ -730,11 +798,15 @@ impl ExecutionConfig { height_map: &mut HashMap, stored_expressions_map: &mut HashMap>>, debug_expressions_map: &mut HashMap)>>, + execute_state_first_step_whitelist: &HashSet, + execute_state_last_step_whitelist: &HashSet, instrument: &mut Instrument, name: &'static str, execution_state: ExecutionState, height: usize, mut cb: EVMConstraintBuilder, + chunk_ctx_table: &dyn LookupTable, + challenges: &Challenges>, ) { // Enforce the step height for this opcode let num_rows_until_next_step_next = cb @@ -747,6 +819,12 @@ impl ExecutionConfig { instrument.on_gadget_built(execution_state, &cb); + 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. @@ -794,6 +872,59 @@ impl ExecutionConfig { } } + // constraint global rw counter value at first/last step via chunk_ctx_table lookup + // we can't do it inside constraint_builder(cb) + // because lookup expression in constraint builder DO NOT support apply conditional + // `step_first/step_last` selector at lookup cell. + if execute_state_first_step_whitelist.contains(&execution_state) { + meta.lookup_any("first must lookup initial rw_counter", |meta| { + let q_usable = meta.query_selector(q_usable); + let q_step_first = meta.query_selector(q_step_first); + let execute_state_selector = step_curr.execution_state_selector([execution_state]); + + vec![( + q_usable + * q_step_first + * execute_state_selector + * rlc::expr( + &[ + ChunkCtxFieldTag::InitialRWC.expr(), + step_curr.state.rw_counter.expr(), + ], + challenges.lookup_input(), + ), + rlc::expr( + &chunk_ctx_table.table_exprs(meta), + challenges.lookup_input(), + ), + )] + }); + } + + if execute_state_last_step_whitelist.contains(&execution_state) { + meta.lookup_any("last step must lookup end rw_counter", |meta| { + let q_usable = meta.query_selector(q_usable); + let q_step_last = meta.query_selector(q_step_last); + let execute_state_selector = step_curr.execution_state_selector([execution_state]); + vec![( + q_usable + * q_step_last + * execute_state_selector + * rlc::expr( + &[ + ChunkCtxFieldTag::EndRWC.expr(), + step_curr_rw_counter.expr() + step_curr_rw_counter_offset.clone(), + ], + challenges.lookup_input(), + ), + rlc::expr( + &chunk_ctx_table.table_exprs(meta), + challenges.lookup_input(), + ), + )] + }); + } + // Enforce the state transitions for this opcode meta.create_gate("Constrain state machine transitions", |meta| { let q_usable = meta.query_selector(q_usable); @@ -805,12 +936,30 @@ impl ExecutionConfig { .chain( [ ( - "EndTx can only transit to BeginTx, InvalidTx or EndBlock", + "EndTx can only transit to BeginTx or Padding or EndBlock or EndChunk or InvalidTx", ExecutionState::EndTx, - vec![ExecutionState::BeginTx, ExecutionState::EndBlock] - .into_iter() - .chain(enable_invalid_tx.then_some(ExecutionState::InvalidTx)) - .collect(), + vec![ + ExecutionState::BeginTx, + ExecutionState::EndBlock, + ExecutionState::Padding, + ExecutionState::EndChunk, + ].into_iter() + .chain(enable_invalid_tx.then_some(ExecutionState::InvalidTx)) + .collect(), + ), + ( + "EndChunk can only transit to EndChunk", + ExecutionState::EndChunk, + vec![ExecutionState::EndChunk], + ), + ( + "Padding can only transit to Padding or EndBlock or EndChunk", + ExecutionState::Padding, + vec![ + ExecutionState::Padding, + ExecutionState::EndBlock, + ExecutionState::EndChunk, + ], ), ( "EndBlock can only transit to EndBlock", @@ -840,12 +989,21 @@ impl ExecutionConfig { .collect(), ), ( - "Only EndTx, InvalidTx or EndBlock can transit to EndBlock", + "Only BeginChunk or EndTx or InvalidTx or EndBlock or Padding can transit to EndBlock", ExecutionState::EndBlock, - vec![ExecutionState::EndTx, ExecutionState::EndBlock] - .into_iter() - .chain(enable_invalid_tx.then_some(ExecutionState::InvalidTx)) - .collect(), + vec![ + ExecutionState::BeginChunk, + ExecutionState::EndTx, + ExecutionState::EndBlock, + ExecutionState::Padding, + ].into_iter() + .chain(enable_invalid_tx.then_some(ExecutionState::InvalidTx)) + .collect(), + ), + ( + "Only BeginChunk can transit to BeginChunk", + ExecutionState::BeginChunk, + vec![ExecutionState::BeginChunk], ), ] .into_iter() @@ -886,6 +1044,7 @@ impl ExecutionConfig { keccak_table: &dyn LookupTable, exp_table: &dyn LookupTable, sig_table: &dyn LookupTable, + chunk_ctx_table: &dyn LookupTable, challenges: &Challenges>, cell_manager: &CellManager, ) { @@ -906,6 +1065,7 @@ impl ExecutionConfig { Table::Keccak => keccak_table, Table::Exp => exp_table, Table::Sig => sig_table, + Table::ChunkCtx => chunk_ctx_table, } .table_exprs(meta); vec![( @@ -962,8 +1122,9 @@ impl ExecutionConfig { &self, layouter: &mut impl Layouter, block: &Block, + chunk: &Chunk, challenges: &Challenges>, - ) -> Result<(), Error> { + ) -> Result { // Track number of calls to `layouter.assign_region` as layouter assignment passes. let mut assign_pass = 0; layouter.assign_region( @@ -977,116 +1138,190 @@ impl ExecutionConfig { self.q_step_first.enable(&mut region, offset)?; let dummy_tx = Transaction::default(); - let last_call = block + // chunk_txs is just a super set of execstep including both belong to this chunk and + // outside of this chunk + let chunk_txs: &[Transaction] = block .txs + .get(chunk.chunk_context.initial_tx_index..chunk.chunk_context.end_tx_index) + .unwrap_or_default(); + + // If it's the very first chunk in a block set last call & begin_chunk to default + let prev_chunk_last_call = chunk.prev_last_call.clone().unwrap_or_default(); + let cur_chunk_last_call = chunk_txs .last() .map(|tx| tx.calls()[0].clone()) - .unwrap_or_default(); - let end_block_not_last = &block.end_block_not_last; - let end_block_last = &block.end_block_last; - // Collect all steps - let mut steps = block - .txs - .iter() - .flat_map(|tx| { + .unwrap_or_else(|| prev_chunk_last_call.clone()); + + let padding = chunk.padding.as_ref().expect("padding can't be None"); + + // conditionally adding first step as begin chunk + let maybe_begin_chunk = { + if let Some(begin_chunk) = &chunk.begin_chunk { + vec![(&dummy_tx, &prev_chunk_last_call, begin_chunk)] + } else { + vec![] + } + }; + + let mut tx_call_steps = maybe_begin_chunk + .into_iter() + .chain(chunk_txs.iter().flat_map(|tx| { tx.steps() .iter() + // chunk_txs is just a super set of execstep. To filter targetting + // execstep we need to further filter by [initial_rwc, end_rwc) + .filter(|step| { + step.rwc.0 >= chunk.chunk_context.initial_rwc + && step.rwc.0 < chunk.chunk_context.end_rwc + }) .map(move |step| (tx, &tx.calls()[step.call_index], step)) - }) - .chain(std::iter::once((&dummy_tx, &last_call, end_block_not_last))) + })) + // this dummy step is just for real step assignment proceed to `second last` + .chain(std::iter::once((&dummy_tx, &cur_chunk_last_call, padding))) .peekable(); - let evm_rows = block.circuits_params.max_evm_rows; - let no_padding = evm_rows == 0; + let evm_rows = chunk.fixed_param.max_evm_rows; + + let mut assign_padding_or_step = |cur_tx_call_step: TxCallStep, + mut offset: usize, + next_tx_call_step: Option, + padding_end: Option| + -> Result { + let (_tx, call, step) = cur_tx_call_step; + let height = step.execution_state().get_step_height(); + + // If padding, assign padding range with (dummy_tx, call, step) + // otherwise, assign one row with cur (tx, call, step), with next (tx, call, + // step) to lookahead + if let Some(padding_end) = padding_end { + // padding_end is the absolute position over all rows, + // must be greater then current offset + if offset >= padding_end { + log::error!( + "evm circuit offset larger than padding: {} > {}", + offset, + padding_end + ); + return Err(Error::Synthesis); + } + log::trace!("assign Padding in range [{},{})", offset, padding_end); + self.assign_same_exec_step_in_range( + &mut region, + offset, + padding_end, + block, + chunk, + (&dummy_tx, call, step), + height, + challenges, + assign_pass, + )?; + let padding_start = offset; + for row_idx in padding_start..padding_end { + self.assign_q_step(&mut region, row_idx, height)?; + offset += height; + } + } else { + self.assign_exec_step( + &mut region, + offset, + block, + chunk, + cur_tx_call_step, + height, + next_tx_call_step, + challenges, + assign_pass, + )?; + self.assign_q_step(&mut region, offset, height)?; + offset += height; + } + + Ok(offset) // return latest offset + }; + + let mut second_last_real_step = None; + let mut second_last_real_step_offset = 0; // part1: assign real steps - loop { - let (transaction, call, step) = steps.next().expect("should not be empty"); - let next = steps.peek(); + while let Some(cur) = tx_call_steps.next() { + let next = tx_call_steps.peek(); if next.is_none() { break; } - let height = step.execution_state().get_step_height(); - // Assign the step witness - self.assign_exec_step( - &mut region, - offset, - block, - transaction, - call, - step, - height, - next.copied(), - challenges, - assign_pass, - )?; + second_last_real_step = Some(cur); + // record offset of current step before assignment + second_last_real_step_offset = offset; + offset = assign_padding_or_step(cur, offset, next.copied(), None)?; + } - // q_step logic - self.assign_q_step(&mut region, offset, height)?; + // next step priority: padding > end_chunk > end_block + let mut next_step_after_real_step = None; - offset += height; + // part2: assign padding + if evm_rows > 0 { + if next_step_after_real_step.is_none() { + next_step_after_real_step = Some(padding.clone()); + } + offset = assign_padding_or_step( + (&dummy_tx, &cur_chunk_last_call, padding), + offset, + None, + Some(evm_rows - 1), + )?; } - // part2: assign non-last EndBlock steps when padding needed - if !no_padding { - if offset >= evm_rows { - log::error!( - "evm circuit offset larger than padding: {} > {}", - offset, - evm_rows - ); - return Err(Error::Synthesis); - } - let height = ExecutionState::EndBlock.get_step_height(); - debug_assert_eq!(height, 1); - let last_row = evm_rows - 1; - log::trace!( - "assign non-last EndBlock in range [{},{})", + // part3: assign end chunk or end block + if let Some(end_chunk) = &chunk.end_chunk { + debug_assert_eq!(ExecutionState::EndChunk.get_step_height(), 1); + offset = assign_padding_or_step( + (&dummy_tx, &cur_chunk_last_call, end_chunk), offset, - last_row + None, + None, + )?; + if next_step_after_real_step.is_none() { + next_step_after_real_step = Some(end_chunk.clone()); + } + } else { + assert!( + chunk.chunk_context.is_last_chunk(), + "If not end_chunk, must be end_block at last chunk" ); - self.assign_same_exec_step_in_range( - &mut region, + debug_assert_eq!(ExecutionState::EndBlock.get_step_height(), 1); + offset = assign_padding_or_step( + (&dummy_tx, &cur_chunk_last_call, &block.end_block), offset, - last_row, - block, - &dummy_tx, - &last_call, - end_block_not_last, - height, - challenges, - assign_pass, + None, + None, )?; - - for row_idx in offset..last_row { - self.assign_q_step(&mut region, row_idx, height)?; + if next_step_after_real_step.is_none() { + next_step_after_real_step = Some(block.end_block.clone()); } - offset = last_row; } - // part3: assign the last EndBlock at offset `evm_rows - 1` - let height = ExecutionState::EndBlock.get_step_height(); - debug_assert_eq!(height, 1); - log::trace!("assign last EndBlock at offset {}", offset); - self.assign_exec_step( - &mut region, - offset, - block, - &dummy_tx, - &last_call, - end_block_last, - height, - None, - challenges, - assign_pass, - )?; - self.assign_q_step(&mut region, offset, height)?; - // enable q_step_last - self.q_step_last.enable(&mut region, offset)?; - offset += height; - // part4: + // re-assigned real second last step, because we know next_step_after_real_step now + assert!(next_step_after_real_step.is_some()); + if let Some(last_real_step) = second_last_real_step { + _ = assign_padding_or_step( + last_real_step, + second_last_real_step_offset, + Some(( + &dummy_tx, + &cur_chunk_last_call, + &next_step_after_real_step.unwrap(), + )), + None, + )?; + } + + // part5: + // enable last row + self.q_step_last.enable(&mut region, offset - 1)?; // offset - 1 is the last row + + // part6: // These are still referenced (but not used) in next rows region.assign_advice( || "step height", @@ -1102,7 +1337,7 @@ impl ExecutionConfig { )?; assign_pass += 1; - Ok(()) + Ok(offset) }, ) } @@ -1118,6 +1353,7 @@ impl ExecutionConfig { ("EVM_lookup_keccak", KECCAK_TABLE_LOOKUPS), ("EVM_lookup_exp", EXP_TABLE_LOOKUPS), ("EVM_lookup_sig", SIG_TABLE_LOOKUPS), + ("EVM_lookupchunk_ctx", CHUNK_CTX_TABLE_LOOKUPS), ("EVM_adv_phase2", N_PHASE2_COLUMNS), ("EVM_copy", N_COPY_COLUMNS), ("EVM_lookup_u8", N_U8_LOOKUPS), @@ -1149,9 +1385,8 @@ impl ExecutionConfig { offset_begin: usize, offset_end: usize, block: &Block, - transaction: &Transaction, - call: &Call, - step: &ExecStep, + chunk: &Chunk, + cur_step: TxCallStep, height: usize, challenges: &Challenges>, assign_pass: usize, @@ -1159,9 +1394,10 @@ impl ExecutionConfig { if offset_end <= offset_begin { return Ok(()); } + let (_, _, step) = cur_step; assert_eq!(height, 1); assert!(step.rw_indices_len() == 0); - assert!(matches!(step.execution_state(), ExecutionState::EndBlock)); + assert!(matches!(step.execution_state(), ExecutionState::Padding)); // Disable access to next step deliberately for "repeatable" step let region = &mut CachedRegion::<'_, '_, F>::new( @@ -1175,9 +1411,8 @@ impl ExecutionConfig { region, offset_begin, block, - transaction, - call, - step, + chunk, + cur_step, false, assign_pass, )?; @@ -1197,15 +1432,15 @@ impl ExecutionConfig { region: &mut Region<'_, F>, offset: usize, block: &Block, - transaction: &Transaction, - call: &Call, - step: &ExecStep, + chunk: &Chunk, + cur_step: TxCallStep, height: usize, - next: Option<(&Transaction, &Call, &ExecStep)>, + next_step: Option, challenges: &Challenges>, assign_pass: usize, ) -> Result<(), Error> { - if !matches!(step.execution_state(), ExecutionState::EndBlock) { + let (_transaction, call, step) = cur_step; + if !matches!(step.execution_state(), ExecutionState::Padding) { log::trace!( "assign_exec_step offset: {} state {:?} step: {:?} call: {:?}", offset, @@ -1229,29 +1464,19 @@ impl ExecutionConfig { // These may be used in stored expressions and // so their witness values need to be known to be able // to correctly calculate the intermediate value. - if let Some((transaction_next, call_next, step_next)) = next { + if let Some(next_step) = next_step { self.assign_exec_step_int( region, offset + height, block, - transaction_next, - call_next, - step_next, + chunk, + next_step, true, assign_pass, )?; } - self.assign_exec_step_int( - region, - offset, - block, - transaction, - call, - step, - false, - assign_pass, - ) + self.assign_exec_step_int(region, offset, block, chunk, cur_step, false, assign_pass) } #[allow(clippy::too_many_arguments)] @@ -1260,9 +1485,8 @@ impl ExecutionConfig { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, - transaction: &Transaction, - call: &Call, - step: &ExecStep, + chunk: &Chunk, + tx_call_step: TxCallStep, // Set to true when we're assigning the next step before the current step to have // next step assignments for evaluation of the stored expressions in current step that // depend on the next step. @@ -1270,12 +1494,13 @@ impl ExecutionConfig { // Layouter assignment pass assign_pass: usize, ) -> Result<(), Error> { + let (transaction, call, step) = tx_call_step; self.step .assign_exec_step(region, offset, block, call, step)?; macro_rules! assign_exec_step { ($gadget:expr) => { - $gadget.assign_exec_step(region, offset, block, transaction, call, step)? + $gadget.assign_exec_step(region, offset, block, chunk, transaction, call, step)? }; } @@ -1283,7 +1508,10 @@ impl ExecutionConfig { // internal states ExecutionState::BeginTx => assign_exec_step!(self.begin_tx_gadget), ExecutionState::EndTx => assign_exec_step!(self.end_tx_gadget), + ExecutionState::Padding => assign_exec_step!(self.padding_gadget), ExecutionState::EndBlock => assign_exec_step!(self.end_block_gadget), + ExecutionState::BeginChunk => assign_exec_step!(self.begin_chunk_gadget), + ExecutionState::EndChunk => assign_exec_step!(self.end_chunk_gadget), ExecutionState::InvalidTx => { assign_exec_step!(self .invalid_tx @@ -1445,14 +1673,14 @@ impl ExecutionConfig { // enable with `RUST_LOG=debug` if log::log_enabled!(log::Level::Debug) { - let is_padding_step = matches!(step.execution_state(), ExecutionState::EndBlock) - && step.rw_indices_len() == 0; + let is_padding_step = matches!(step.execution_state(), ExecutionState::Padding); if !is_padding_step { // expensive function call Self::check_rw_lookup( &assigned_stored_expressions, step, block, + chunk, region.challenges(), ); } @@ -1510,6 +1738,7 @@ impl ExecutionConfig { assigned_stored_expressions: &[(String, F)], step: &ExecStep, block: &Block, + _chunk: &Chunk, challenges: &Challenges>, ) { let mut lookup_randomness = F::ZERO; diff --git a/zkevm-circuits/src/evm_circuit/execution/add_sub.rs b/zkevm-circuits/src/evm_circuit/execution/add_sub.rs index 5355bed337..3429cfb567 100644 --- a/zkevm-circuits/src/evm_circuit/execution/add_sub.rs +++ b/zkevm-circuits/src/evm_circuit/execution/add_sub.rs @@ -8,7 +8,7 @@ use crate::{ math_gadget::{AddWordsGadget, PairSelectGadget}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHi}, @@ -80,6 +80,7 @@ impl ExecutionGadget for AddSubGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/addmod.rs b/zkevm-circuits/src/evm_circuit/execution/addmod.rs index a952b166bc..fe1d598b9c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/addmod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/addmod.rs @@ -14,7 +14,7 @@ use crate::{ }, not, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr, WordLoHi}, @@ -152,6 +152,7 @@ impl ExecutionGadget for AddModGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/address.rs b/zkevm-circuits/src/evm_circuit/execution/address.rs index 0a98827638..024566b71a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/address.rs +++ b/zkevm-circuits/src/evm_circuit/execution/address.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -60,6 +60,7 @@ impl ExecutionGadget for AddressGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/balance.rs b/zkevm-circuits/src/evm_circuit/execution/balance.rs index ef3f6dfeea..487a63089f 100644 --- a/zkevm-circuits/src/evm_circuit/execution/balance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/balance.rs @@ -11,7 +11,7 @@ use crate::{ math_gadget::IsZeroWordGadget, not, select, AccountAddress, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, CallContextFieldTag}, util::{ @@ -111,6 +111,7 @@ impl ExecutionGadget for BalanceGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_chunk.rs b/zkevm-circuits/src/evm_circuit/execution/begin_chunk.rs new file mode 100644 index 0000000000..c8e519e682 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/begin_chunk.rs @@ -0,0 +1,59 @@ +use std::marker::PhantomData; + +use crate::{ + evm_circuit::{ + step::ExecutionState, + util::{ + constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, + CachedRegion, + }, + witness::{Block, Call, Chunk, ExecStep, Transaction}, + }, + util::Expr, +}; +use eth_types::Field; +use halo2_proofs::plonk::Error; + +use super::ExecutionGadget; + +#[derive(Clone, Debug)] +pub(crate) struct BeginChunkGadget { + _marker: PhantomData, +} + +impl ExecutionGadget for BeginChunkGadget { + const NAME: &'static str = "BeginChunk"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::BeginChunk; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + // state lookup + cb.step_state_lookup(0.expr()); + 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 {}, + } + } + + fn assign_exec_step( + &self, + _region: &mut CachedRegion<'_, '_, F>, + _offset: usize, + _block: &Block, + _chunk: &Chunk, + _: &Transaction, + _: &Call, + _step: &ExecStep, + ) -> Result<(), Error> { + Ok(()) + } +} + +#[cfg(test)] +mod test { + // begin_chunk unittest covered by end_chunk +} diff --git a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs index 307d2198f0..8e25071d80 100644 --- a/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/begin_tx.rs @@ -19,7 +19,7 @@ use crate::{ tx::{BeginTxHelperGadget, TxDataGadget}, AccountAddress, CachedRegion, Cell, StepRws, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, BlockContextFieldTag, CallContextFieldTag, TxContextFieldTag}, util::{ @@ -489,6 +489,7 @@ impl ExecutionGadget for BeginTxGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs index 947ee2a7dd..b5d95cf791 100644 --- a/zkevm-circuits/src/evm_circuit/execution/bitwise.rs +++ b/zkevm-circuits/src/evm_circuit/execution/bitwise.rs @@ -8,7 +8,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr}, @@ -84,6 +84,7 @@ impl ExecutionGadget for BitwiseGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs b/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs index b247438a2d..dcc6b97cf1 100644 --- a/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/block_ctx.rs @@ -6,7 +6,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::BlockContextFieldTag, util::{ @@ -66,6 +66,7 @@ impl ExecutionGadget for BlockCtxGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/blockhash.rs b/zkevm-circuits/src/evm_circuit/execution/blockhash.rs index ea37a199b7..ec7708185d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/blockhash.rs +++ b/zkevm-circuits/src/evm_circuit/execution/blockhash.rs @@ -13,7 +13,7 @@ use crate::{ math_gadget::LtGadget, CachedRegion, Cell, WordLoHi, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::BlockContextFieldTag, util::word::WordExpr, @@ -98,6 +98,7 @@ impl ExecutionGadget for BlockHashGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/byte.rs b/zkevm-circuits/src/evm_circuit/execution/byte.rs index fb06fb0579..42002e91e8 100644 --- a/zkevm-circuits/src/evm_circuit/execution/byte.rs +++ b/zkevm-circuits/src/evm_circuit/execution/byte.rs @@ -8,7 +8,7 @@ use crate::{ math_gadget::{IsEqualGadget, IsZeroGadget}, sum, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr, WordLoHi}, @@ -97,6 +97,7 @@ impl ExecutionGadget for ByteGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs index 99f6cdd933..1384d23670 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatacopy.rs @@ -15,7 +15,7 @@ use crate::{ }, not, select, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -178,6 +178,7 @@ impl ExecutionGadget for CallDataCopyGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs index 67b2efda19..ac59cdd2bc 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldataload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldataload.rs @@ -19,7 +19,7 @@ use crate::{ memory_gadget::BufferReaderGadget, not, select, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{CallContextFieldTag, TxContextFieldTag}, util::{ @@ -205,6 +205,7 @@ impl ExecutionGadget for CallDataLoadGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs index 1ead801063..06972e6dd4 100644 --- a/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/calldatasize.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -65,6 +65,7 @@ impl ExecutionGadget for CallDataSizeGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/caller.rs b/zkevm-circuits/src/evm_circuit/execution/caller.rs index 6a3839ab34..ea35c8111b 100644 --- a/zkevm-circuits/src/evm_circuit/execution/caller.rs +++ b/zkevm-circuits/src/evm_circuit/execution/caller.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -65,6 +65,7 @@ impl ExecutionGadget for CallerGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/callop.rs b/zkevm-circuits/src/evm_circuit/execution/callop.rs index 571a33dc5b..0a7b27b905 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callop.rs @@ -21,9 +21,14 @@ use crate::{ }, witness::{Block, Call, ExecStep, Transaction}, }, + util::word::WordExpr, +}; + +use crate::{ + evm_circuit::witness::Chunk, table::{AccountFieldTag, CallContextFieldTag}, util::{ - word::{WordExpr, WordLoHi, WordLoHiCell}, + word::{WordLoHi, WordLoHiCell}, Expr, }, }; @@ -801,6 +806,7 @@ impl ExecutionGadget for CallOpGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, @@ -1384,7 +1390,7 @@ mod test { CircuitTestBuilder::new_from_test_ctx(ctx) .params(FixedCParams { - max_rws: 500, + max_rws: 1 << 12, ..Default::default() }) .run(); diff --git a/zkevm-circuits/src/evm_circuit/execution/callvalue.rs b/zkevm-circuits/src/evm_circuit/execution/callvalue.rs index f590984d77..6b809785ef 100644 --- a/zkevm-circuits/src/evm_circuit/execution/callvalue.rs +++ b/zkevm-circuits/src/evm_circuit/execution/callvalue.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -67,6 +67,7 @@ impl ExecutionGadget for CallValueGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/chainid.rs b/zkevm-circuits/src/evm_circuit/execution/chainid.rs index c355c47c47..ed1e476632 100644 --- a/zkevm-circuits/src/evm_circuit/execution/chainid.rs +++ b/zkevm-circuits/src/evm_circuit/execution/chainid.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::BlockContextFieldTag, util::{ @@ -65,6 +65,7 @@ impl ExecutionGadget for ChainIdGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs index fc019ec674..2a8450efd3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/codecopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/codecopy.rs @@ -17,7 +17,7 @@ use crate::{ }, not, select, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHi}, @@ -145,6 +145,7 @@ impl ExecutionGadget for CodeCopyGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/codesize.rs b/zkevm-circuits/src/evm_circuit/execution/codesize.rs index cab8f2253c..59fa6e8ba3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/codesize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/codesize.rs @@ -12,7 +12,7 @@ use crate::{ }, CachedRegion, Cell, U64Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{word::WordExpr, Expr}, }; @@ -69,6 +69,7 @@ impl ExecutionGadget for CodesizeGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _transaction: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/comparator.rs b/zkevm-circuits/src/evm_circuit/execution/comparator.rs index a0f81f8109..26c9f60cc5 100644 --- a/zkevm-circuits/src/evm_circuit/execution/comparator.rs +++ b/zkevm-circuits/src/evm_circuit/execution/comparator.rs @@ -8,7 +8,7 @@ use crate::{ math_gadget::{CmpWordsGadget, IsEqualGadget}, select, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHi, WordLoHiCell}, @@ -92,6 +92,7 @@ impl ExecutionGadget for ComparatorGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/create.rs b/zkevm-circuits/src/evm_circuit/execution/create.rs index c999a73e40..d0b21b8509 100644 --- a/zkevm-circuits/src/evm_circuit/execution/create.rs +++ b/zkevm-circuits/src/evm_circuit/execution/create.rs @@ -21,7 +21,7 @@ use crate::{ }, not, AccountAddress, CachedRegion, Cell, StepRws, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, CallContextFieldTag}, util::{ @@ -514,6 +514,7 @@ impl ExecutionGadget< region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/dummy.rs b/zkevm-circuits/src/evm_circuit/execution/dummy.rs index ad85b35bc4..f861cf60f9 100644 --- a/zkevm-circuits/src/evm_circuit/execution/dummy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/dummy.rs @@ -5,7 +5,7 @@ use crate::{ execution::ExecutionGadget, step::ExecutionState, util::{constraint_builder::EVMConstraintBuilder, CachedRegion}, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::word::{WordExpr, WordLoHiCell}, }; @@ -47,6 +47,7 @@ impl region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/dup.rs b/zkevm-circuits/src/evm_circuit/execution/dup.rs index 71a8c8d7e8..a49c0ec4df 100644 --- a/zkevm-circuits/src/evm_circuit/execution/dup.rs +++ b/zkevm-circuits/src/evm_circuit/execution/dup.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHiCell}, @@ -62,6 +62,7 @@ impl ExecutionGadget for DupGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/end_block.rs b/zkevm-circuits/src/evm_circuit/execution/end_block.rs index 63c5e530e1..73188658f2 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_block.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_block.rs @@ -3,19 +3,19 @@ use crate::{ execution::ExecutionGadget, step::ExecutionState, util::{ + common_gadget::RwTablePaddingGadget, constraint_builder::{ ConstrainBuilderCommon, EVMConstraintBuilder, StepStateTransition, Transition::Same, }, math_gadget::IsEqualGadget, not, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{CallContextFieldTag, TxContextFieldTag}, util::{word::WordLoHi, Expr}, }; use eth_types::{Field, OpsIdentity}; -use gadgets::util::select; use halo2_proofs::{circuit::Value, plonk::Error}; #[derive(Clone, Debug)] @@ -23,8 +23,8 @@ pub(crate) struct EndBlockGadget { total_txs: Cell, total_txs_is_max_txs: IsEqualGadget, is_empty_block: IsEqualGadget, - max_rws: Cell, max_txs: Cell, + rw_table_padding_gadget: RwTablePaddingGadget, } impl ExecutionGadget for EndBlockGadget { @@ -34,19 +34,11 @@ impl ExecutionGadget for EndBlockGadget { fn configure(cb: &mut EVMConstraintBuilder) -> Self { let max_txs = cb.query_copy_cell(); - let max_rws = cb.query_copy_cell(); let total_txs = cb.query_cell(); let total_txs_is_max_txs = cb.is_eq(total_txs.expr(), max_txs.expr()); // Note that rw_counter starts at 1 let is_empty_block = cb.is_eq(cb.curr.state.rw_counter.clone().expr(), 1.expr()); - let total_rws_before_padding = cb.curr.state.rw_counter.clone().expr() - 1.expr() - + select::expr( - is_empty_block.expr(), - 0.expr(), - 1.expr(), // If the block is not empty, we will do 1 call_context lookup below - ); - // 1. Constraint total_rws and total_txs witness values depending on the empty // block case. cb.condition(is_empty_block.expr(), |cb| { @@ -82,11 +74,16 @@ impl ExecutionGadget for EndBlockGadget { // meaningful txs in the tx_table is total_tx. }); + let total_inner_rws_before_padding = cb.curr.state.inner_rw_counter.clone().expr() + - 1.expr() // start from 1 + + cb.rw_counter_offset(); // 3. Verify rw_counter counts to the same number of meaningful rows in // rw_table to ensure there is no malicious insertion. // Verify that there are at most total_rws meaningful entries in the rw_table - cb.rw_table_start_lookup(1.expr()); - cb.rw_table_start_lookup(max_rws.expr() - total_rws_before_padding.expr()); + // - startop only exist in first chunk + + let rw_table_padding_gadget = + RwTablePaddingGadget::construct(cb, total_inner_rws_before_padding); // Since every lookup done in the EVM circuit must succeed and uses // a unique rw_counter, we know that at least there are // total_rws meaningful entries in the rw_table. @@ -108,10 +105,10 @@ impl ExecutionGadget for EndBlockGadget { Self { max_txs, - max_rws, total_txs, total_txs_is_max_txs, is_empty_block, + rw_table_padding_gadget, } } @@ -120,28 +117,39 @@ impl ExecutionGadget for EndBlockGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, ) -> Result<(), Error> { self.is_empty_block .assign(region, offset, F::from(u64::from(step.rwc)), F::ONE)?; - let max_rws = F::from(block.circuits_params.max_rws as u64); - let max_rws_assigned = self.max_rws.assign(region, offset, Value::known(max_rws))?; + + let inner_rws_before_padding = + step.rwc_inner_chunk.0 as u64 - 1 + if u64::from(step.rwc) > 1 { 1 } else { 0 }; + self.rw_table_padding_gadget.assign_exec_step( + region, + offset, + block, + chunk, + inner_rws_before_padding, + step, + )?; let total_txs = F::from(block.txs.len() as u64); - let max_txs = F::from(block.circuits_params.max_txs as u64); + let max_txs = F::from(chunk.fixed_param.max_txs as u64); self.total_txs .assign(region, offset, Value::known(total_txs))?; self.total_txs_is_max_txs .assign(region, offset, total_txs, max_txs)?; let max_txs_assigned = self.max_txs.assign(region, offset, Value::known(max_txs))?; - // When rw_indices is not empty, we're at the last row (at a fixed offset), - // where we need to access the max_rws and max_txs constant. + // When rw_indices is not empty, means current endblock is non-padding step, we're at the + // last row (at a fixed offset), where we need to access max_txs + // constant. if step.rw_indices_len() != 0 { - region.constrain_constant(max_rws_assigned, max_rws)?; region.constrain_constant(max_txs_assigned, max_txs)?; } + Ok(()) } } @@ -164,23 +172,17 @@ mod test { // finish required tests using this witness block CircuitTestBuilder::<2, 1>::new_from_test_ctx(ctx) - .block_modifier(Box::new(move |block| { - block.circuits_params.max_evm_rows = evm_circuit_pad_to + .block_modifier(Box::new(move |_block, chunk| { + chunk + .iter_mut() + .for_each(|chunk| chunk.fixed_param.max_evm_rows = evm_circuit_pad_to); })) .run(); } - // Test where the EVM circuit contains an exact number of rows corresponding to - // the trace steps + 1 EndBlock + // Test steps + 1 EndBlock without padding #[test] - fn end_block_exact() { + fn end_block_no_padding() { test_circuit(0); } - - // Test where the EVM circuit has a fixed size and contains several padding - // EndBlocks at the end after the trace steps - #[test] - fn end_block_padding() { - test_circuit(50); - } } diff --git a/zkevm-circuits/src/evm_circuit/execution/end_chunk.rs b/zkevm-circuits/src/evm_circuit/execution/end_chunk.rs new file mode 100644 index 0000000000..b10e4e1e34 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/end_chunk.rs @@ -0,0 +1,215 @@ +use std::marker::PhantomData; + +use crate::{ + evm_circuit::{ + step::ExecutionState, + util::{ + common_gadget::RwTablePaddingGadget, + constraint_builder::{EVMConstraintBuilder, StepStateTransition}, + CachedRegion, + }, + witness::{Block, Call, Chunk, ExecStep, Transaction}, + }, + util::Expr, +}; +use bus_mapping::{exec_trace::OperationRef, operation::Target}; +use eth_types::Field; +use halo2_proofs::plonk::Error; + +use super::ExecutionGadget; + +#[derive(Clone, Debug)] +pub(crate) struct EndChunkGadget { + _marker: PhantomData, + rw_table_padding_gadget: RwTablePaddingGadget, +} + +impl ExecutionGadget for EndChunkGadget { + const NAME: &'static str = "EndChunk"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::EndChunk; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + // State transition on non-last evm step + // TODO/FIXME make EndChunk must be in last evm step and remove below constraint + cb.not_step_last(|cb| { + // Propagate all the way down. + cb.require_step_state_transition(StepStateTransition::same()); + }); + + // step state write to rw_table + cb.step_state_lookup(1.expr()); + + let rw_table_padding_gadget = RwTablePaddingGadget::construct( + cb, + cb.curr.state.inner_rw_counter.clone().expr() - 1.expr() + cb.rw_counter_offset(), /* start from 1 */ + ); + + Self { + rw_table_padding_gadget, + _marker: PhantomData {}, + } + } + + fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + block: &Block, + chunk: &Chunk, + _: &Transaction, + _: &Call, + step: &ExecStep, + ) -> Result<(), Error> { + let rwc_before_padding = step + .bus_mapping_instance + .iter() + .filter(|x| { + let OperationRef(c, _) = x; + *c != Target::Start && *c != Target::Padding + }) + .count(); + self.rw_table_padding_gadget.assign_exec_step( + region, + offset, + block, + chunk, + (step.rwc_inner_chunk.0 - 1 + rwc_before_padding) as u64, + step, + )?; + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::{ + test_util::CircuitTestBuilder, + witness::{block_convert, chunk_convert}, + }; + use bus_mapping::{circuit_input_builder::FixedCParams, mock::BlockData}; + use eth_types::{address, bytecode, geth_types::GethData, Word}; + use halo2_proofs::halo2curves::bn256::Fr; + use mock::TestContext; + + macro_rules! test_2_txs_with_various_chunk_size { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (total_chunks, total_rws) = $value; + test_2_txs_with_chunk_size(total_chunks, total_rws); + } + )* + } + } + #[test] + fn test_chunking_rwmap_logic() { + let bytecode = bytecode! { + GAS + STOP + }; + let addr_a = address!("0x000000000000000000000000000000000000AAAA"); + let addr_b = address!("0x000000000000000000000000000000000000BBBB"); + let test_ctx = TestContext::<2, 2>::new( + None, + |accs| { + accs[0] + .address(addr_b) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + accs[1].address(addr_a).balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + txs[1] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap(); + let block: GethData = test_ctx.into(); + let builder = BlockData::new_from_geth_data_with_params( + block.clone(), + FixedCParams { + total_chunks: 4, + max_rws: 64, + max_txs: 2, + ..Default::default() + }, + ) + .new_circuit_input_builder() + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); + let block = block_convert::(&builder).unwrap(); + let chunks = chunk_convert(&block, &builder).unwrap(); + // assert last fingerprint acc are equal + if let Some(last_chunk) = chunks.last() { + assert_eq!( + last_chunk.by_address_rw_fingerprints.mul_acc, + last_chunk.chrono_rw_fingerprints.mul_acc + ) + } + } + + fn test_2_txs_with_chunk_size(total_chunks: usize, total_rws: usize) { + let bytecode = bytecode! { + GAS + STOP + }; + let addr_a = address!("0x000000000000000000000000000000000000AAAA"); + let addr_b = address!("0x000000000000000000000000000000000000BBBB"); + let test_ctx = TestContext::<2, 2>::new( + None, + |accs| { + accs[0] + .address(addr_b) + .balance(Word::from(1u64 << 20)) + .code(bytecode); + accs[1].address(addr_a).balance(Word::from(1u64 << 20)); + }, + |mut txs, accs| { + txs[0] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + txs[1] + .from(accs[1].address) + .to(accs[0].address) + .gas(Word::from(1_000_000u64)); + }, + |block, _tx| block.number(0xcafeu64), + ) + .unwrap(); + CircuitTestBuilder::new_from_test_ctx(test_ctx) + .params({ + FixedCParams { + total_chunks, + max_evm_rows: 1 << 12, + max_rws: total_rws / total_chunks, + max_txs: 2, + ..Default::default() + } + }) + .run_multiple_chunks_with_result(Some(total_chunks)) + .unwrap(); + } + + test_2_txs_with_various_chunk_size! { + test_2_txs_with_1_400: (1, 400), + test_2_txs_with_2_400: (2, 400), + test_2_txs_with_3_400: (3, 400), + test_2_txs_with_4_400: (4, 400), + test_2_txs_with_1_600: (1, 600), + test_2_txs_with_2_600: (2, 600), + test_2_txs_with_3_600: (3, 600), + test_2_txs_with_4_600: (4, 600), + test_2_txs_with_5_600: (5, 600), + test_2_txs_with_6_600: (6, 600), + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs index 27ca5f119e..ce2cc770bf 100644 --- a/zkevm-circuits/src/evm_circuit/execution/end_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/end_tx.rs @@ -13,7 +13,7 @@ use crate::{ tx::EndTxHelperGadget, CachedRegion, Cell, StepRws, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, BlockContextFieldTag, CallContextFieldTag, TxContextFieldTag}, util::{ @@ -145,6 +145,7 @@ impl ExecutionGadget for EndTxGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, @@ -416,7 +417,7 @@ mod test { fn end_tx_consistent_tx_id_write() { // check there is no consecutive txid write with same txid in rw_table - let block = CircuitTestBuilder::new_from_test_ctx( + let (block, _) = CircuitTestBuilder::new_from_test_ctx( TestContext::<2, 3>::new( None, account_0_code_account_1_no_code(bytecode! { STOP }), @@ -442,7 +443,7 @@ mod test { max_txs: 5, ..Default::default() }) - .build_block() + .build_block(None) .unwrap(); block.rws.0[&Target::CallContext] diff --git a/zkevm-circuits/src/evm_circuit/execution/error_code_store.rs b/zkevm-circuits/src/evm_circuit/execution/error_code_store.rs index 2506639307..0421d8d54c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_code_store.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_code_store.rs @@ -10,7 +10,7 @@ use crate::{ memory_gadget::{CommonMemoryAddressGadget, MemoryAddressGadget}, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{word::WordExpr, Expr}, @@ -99,6 +99,7 @@ impl ExecutionGadget for ErrorCodeStoreGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_invalid_creation_code.rs b/zkevm-circuits/src/evm_circuit/execution/error_invalid_creation_code.rs index 6b7482046a..beb2f871d9 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_invalid_creation_code.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_invalid_creation_code.rs @@ -9,7 +9,7 @@ use crate::{ memory_gadget::{CommonMemoryAddressGadget, MemoryAddressGadget}, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{word::WordExpr, Expr}, }; @@ -69,6 +69,7 @@ impl ExecutionGadget for ErrorInvalidCreationCodeGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs b/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs index 6fab8e624b..453f5ef011 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_invalid_jump.rs @@ -9,7 +9,7 @@ use crate::{ math_gadget::{IsEqualGadget, IsZeroWordGadget}, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHi, WordLoHiCell}, @@ -111,6 +111,7 @@ impl ExecutionGadget for ErrorInvalidJumpGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_invalid_opcode.rs b/zkevm-circuits/src/evm_circuit/execution/error_invalid_opcode.rs index f5257605bb..2726c8cc2a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_invalid_opcode.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_invalid_opcode.rs @@ -6,7 +6,7 @@ use crate::evm_circuit::{ common_gadget::CommonErrorGadget, constraint_builder::EVMConstraintBuilder, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }; use eth_types::Field; use gadgets::util::Expr; @@ -52,6 +52,7 @@ impl ExecutionGadget for ErrorInvalidOpcodeGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_account_access.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_account_access.rs index 7878b67252..49b65ae27c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_account_access.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_account_access.rs @@ -9,7 +9,7 @@ use crate::{ math_gadget::LtGadget, select, AccountAddress, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{word::WordExpr, Expr}, @@ -87,6 +87,7 @@ impl ExecutionGadget for ErrorOOGAccountAccessGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs index 53b61484aa..4c2a851d27 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_call.rs @@ -13,7 +13,7 @@ use crate::{ }, table::CallContextFieldTag, util::Expr, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }; use bus_mapping::evm::OpcodeId; use eth_types::{Field, U256}; @@ -119,6 +119,7 @@ impl ExecutionGadget for ErrorOOGCallGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs index c0061b31c5..fdf5c01889 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_constant.rs @@ -9,7 +9,7 @@ use crate::{ math_gadget::LtGadget, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::Expr, }; @@ -60,6 +60,7 @@ impl ExecutionGadget for ErrorOOGConstantGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs index 99756d1d23..2f5a78d2b6 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_create.rs @@ -15,7 +15,7 @@ use crate::{ }, }, util::{word::Word32Cell, Expr}, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }; use eth_types::{ evm_types::{ @@ -117,6 +117,7 @@ impl ExecutionGadget for ErrorOOGCreateGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_dynamic_memory.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_dynamic_memory.rs index a2662ccb78..81edf3ab9e 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_dynamic_memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_dynamic_memory.rs @@ -13,7 +13,7 @@ use crate::{ CachedRegion, Cell, }, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }; use eth_types::{evm_types::OpcodeId, Field}; use gadgets::util::{or, Expr}; @@ -77,6 +77,7 @@ impl ExecutionGadget for ErrorOOGDynamicMemoryGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_exp.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_exp.rs index dc819860bb..4f4921c3bb 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_exp.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_exp.rs @@ -9,7 +9,7 @@ use crate::{ math_gadget::{ByteSizeGadget, LtGadget}, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr}, @@ -97,6 +97,7 @@ impl ExecutionGadget for ErrorOOGExpGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_log.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_log.rs index bf129c5088..9bbe5f45fd 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_log.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_log.rs @@ -12,7 +12,7 @@ use crate::{ }, or, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::Expr, @@ -98,6 +98,7 @@ impl ExecutionGadget for ErrorOOGLogGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs index 697279a626..e7b8087647 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_memory_copy.rs @@ -13,7 +13,7 @@ use crate::{ }, or, select, AccountAddress, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -149,6 +149,7 @@ impl ExecutionGadget for ErrorOOGMemoryCopyGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, transaction: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_precompile.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_precompile.rs index 6514a47790..828687fda2 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_precompile.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_precompile.rs @@ -11,7 +11,7 @@ use crate::{ }, }, table::CallContextFieldTag, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }; use bus_mapping::precompile::PrecompileCalls; use eth_types::{evm_types::GasCost, Field, ToScalar}; @@ -145,6 +145,7 @@ impl ExecutionGadget for ErrorOOGPrecompileGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _transaction: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_sha3.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_sha3.rs index 480d1ec077..ab3db568d5 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_sha3.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_sha3.rs @@ -13,7 +13,7 @@ use crate::{ }, or, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::Expr, }; @@ -89,6 +89,7 @@ impl ExecutionGadget for ErrorOOGSha3Gadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_sload_sstore.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_sload_sstore.rs index b0b8bc97dc..c6ed23f111 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_sload_sstore.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_sload_sstore.rs @@ -13,7 +13,7 @@ use crate::{ math_gadget::{LtGadget, PairSelectGadget}, or, select, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -155,6 +155,7 @@ impl ExecutionGadget for ErrorOOGSloadSstoreGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_oog_static_memory.rs b/zkevm-circuits/src/evm_circuit/execution/error_oog_static_memory.rs index 948062ce7d..b19b3cb2df 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_oog_static_memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_oog_static_memory.rs @@ -12,7 +12,7 @@ use crate::{ }, select, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{word::WordLoHi, Expr}, }; @@ -102,6 +102,7 @@ impl ExecutionGadget for ErrorOOGStaticMemoryGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_precompile_failed.rs b/zkevm-circuits/src/evm_circuit/execution/error_precompile_failed.rs index 7eb8f82813..54b3dc63b4 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_precompile_failed.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_precompile_failed.rs @@ -14,7 +14,7 @@ use crate::{ word::{Word32Cell, WordExpr, WordLoHi}, Expr, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }; use bus_mapping::evm::OpcodeId; use eth_types::{Field, OpsIdentity, U256}; @@ -119,6 +119,7 @@ impl ExecutionGadget for ErrorPrecompileFailedGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_return_data_oo_bound.rs b/zkevm-circuits/src/evm_circuit/execution/error_return_data_oo_bound.rs index 52a9839239..42234d499a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_return_data_oo_bound.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_return_data_oo_bound.rs @@ -10,7 +10,7 @@ use crate::{ math_gadget::{AddWordsGadget, IsZeroGadget, LtGadget}, not, or, sum, CachedRegion, Cell, U64Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -121,6 +121,7 @@ impl ExecutionGadget for ErrorReturnDataOutOfBoundGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_stack.rs b/zkevm-circuits/src/evm_circuit/execution/error_stack.rs index 08a40469e3..ea5974c0f5 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_stack.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_stack.rs @@ -7,7 +7,7 @@ use crate::{ common_gadget::CommonErrorGadget, constraint_builder::EVMConstraintBuilder, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::Expr, }; @@ -53,6 +53,7 @@ impl ExecutionGadget for ErrorStackGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/error_write_protection.rs b/zkevm-circuits/src/evm_circuit/execution/error_write_protection.rs index 531bdc9813..baf2b244de 100644 --- a/zkevm-circuits/src/evm_circuit/execution/error_write_protection.rs +++ b/zkevm-circuits/src/evm_circuit/execution/error_write_protection.rs @@ -8,7 +8,7 @@ use crate::{ math_gadget::{IsEqualGadget, IsZeroWordGadget}, AccountAddress, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -99,6 +99,7 @@ impl ExecutionGadget for ErrorWriteProtectionGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/exp.rs b/zkevm-circuits/src/evm_circuit/execution/exp.rs index 24de01a70e..41deac1b77 100644 --- a/zkevm-circuits/src/evm_circuit/execution/exp.rs +++ b/zkevm-circuits/src/evm_circuit/execution/exp.rs @@ -17,7 +17,7 @@ use crate::{ math_gadget::{ByteSizeGadget, IsEqualGadget, IsZeroGadget}, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::word::{Word32Cell, Word4, WordExpr}, }; @@ -191,6 +191,7 @@ impl ExecutionGadget for ExponentiationGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs b/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs index 5cda8007d8..ef72eb0a9d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodecopy.rs @@ -15,7 +15,7 @@ use crate::{ }, not, select, AccountAddress, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, CallContextFieldTag}, util::word::{Word32Cell, WordExpr, WordLoHi}, @@ -176,6 +176,7 @@ impl ExecutionGadget for ExtcodecopyGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, transaction: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs index 99cd8b5b6a..fd9fcf370d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodehash.rs @@ -10,7 +10,7 @@ use crate::{ }, select, AccountAddress, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, CallContextFieldTag}, util::{ @@ -100,6 +100,7 @@ impl ExecutionGadget for ExtcodehashGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/extcodesize.rs b/zkevm-circuits/src/evm_circuit/execution/extcodesize.rs index 908b1e2b7b..b80a2c3532 100644 --- a/zkevm-circuits/src/evm_circuit/execution/extcodesize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/extcodesize.rs @@ -12,7 +12,7 @@ use crate::{ math_gadget::IsZeroWordGadget, not, select, AccountAddress, CachedRegion, Cell, U64Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, CallContextFieldTag}, util::{ @@ -117,6 +117,7 @@ impl ExecutionGadget for ExtcodesizeGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/gas.rs b/zkevm-circuits/src/evm_circuit/execution/gas.rs index 1e626ccc95..05f5fc0b14 100644 --- a/zkevm-circuits/src/evm_circuit/execution/gas.rs +++ b/zkevm-circuits/src/evm_circuit/execution/gas.rs @@ -10,7 +10,7 @@ use crate::{ }, CachedRegion, U64Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{word::WordExpr, Expr}, }; @@ -63,8 +63,9 @@ impl ExecutionGadget for GasGadget { &self, region: &mut CachedRegion<'_, '_, F>, offset: usize, - _block: &Block, - _transaction: &Transaction, + _: &Block, + _: &Chunk, + _: &Transaction, _call: &Call, step: &ExecStep, ) -> Result<(), Error> { @@ -139,7 +140,7 @@ mod test { .unwrap(); CircuitTestBuilder::<2, 1>::new_from_test_ctx(ctx) - .block_modifier(Box::new(|block| { + .block_modifier(Box::new(|block, _chunk| { // The above block has 2 steps (GAS and STOP). We forcefully assign a // wrong `gas_left` value for the second step, to assert that // the circuit verification fails for this scenario. diff --git a/zkevm-circuits/src/evm_circuit/execution/gasprice.rs b/zkevm-circuits/src/evm_circuit/execution/gasprice.rs index 53ff2ab71f..c9160f4356 100644 --- a/zkevm-circuits/src/evm_circuit/execution/gasprice.rs +++ b/zkevm-circuits/src/evm_circuit/execution/gasprice.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{CallContextFieldTag, TxContextFieldTag}, util::{ @@ -71,6 +71,7 @@ impl ExecutionGadget for GasPriceGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs b/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs index 1100175592..286d0a5152 100644 --- a/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs +++ b/zkevm-circuits/src/evm_circuit/execution/invalid_tx.rs @@ -13,6 +13,7 @@ use crate::{ }, table::AccountFieldTag, util::word::{Word32Cell, WordExpr, WordLoHi}, + witness::Chunk, }; use eth_types::{Field, ToScalar}; use gadgets::util::{not, or, Expr, Scalar}; @@ -95,6 +96,7 @@ impl ExecutionGadget for InvalidTxGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/is_zero.rs b/zkevm-circuits/src/evm_circuit/execution/is_zero.rs index c2f52e4100..82a9db9bdd 100644 --- a/zkevm-circuits/src/evm_circuit/execution/is_zero.rs +++ b/zkevm-circuits/src/evm_circuit/execution/is_zero.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, math_gadget, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHi, WordLoHiCell}, @@ -61,6 +61,7 @@ impl ExecutionGadget for IsZeroGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/jump.rs b/zkevm-circuits/src/evm_circuit/execution/jump.rs index 615d2ba7c3..38ed5d680d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jump.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jump.rs @@ -10,7 +10,7 @@ use crate::{ }, CachedRegion, U64Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{word::WordExpr, Expr}, }; @@ -59,6 +59,7 @@ impl ExecutionGadget for JumpGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs b/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs index 57cf0edd67..c5652a0d2d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpdest.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::Expr, }; @@ -43,6 +43,7 @@ impl ExecutionGadget for JumpdestGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, _: &Block, + _: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs index 40b9878ea0..08b07e6ce7 100644 --- a/zkevm-circuits/src/evm_circuit/execution/jumpi.rs +++ b/zkevm-circuits/src/evm_circuit/execution/jumpi.rs @@ -12,7 +12,7 @@ use crate::{ math_gadget::IsZeroWordGadget, select, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHi, WordLoHiCell}, @@ -90,6 +90,7 @@ impl ExecutionGadget for JumpiGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/logs.rs b/zkevm-circuits/src/evm_circuit/execution/logs.rs index 327cae451f..12b472de3a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/logs.rs +++ b/zkevm-circuits/src/evm_circuit/execution/logs.rs @@ -14,7 +14,7 @@ use crate::{ }, not, sum, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{CallContextFieldTag, TxLogFieldTag}, util::{ @@ -195,6 +195,7 @@ impl ExecutionGadget for LogGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/memory.rs b/zkevm-circuits/src/evm_circuit/execution/memory.rs index 2956730d7f..798f2ecfbc 100644 --- a/zkevm-circuits/src/evm_circuit/execution/memory.rs +++ b/zkevm-circuits/src/evm_circuit/execution/memory.rs @@ -13,7 +13,7 @@ use crate::{ memory_gadget::MemoryExpansionGadget, not, CachedRegion, MemoryAddress, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr}, @@ -119,6 +119,7 @@ impl ExecutionGadget for MemoryGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/msize.rs b/zkevm-circuits/src/evm_circuit/execution/msize.rs index b3c2274b44..857e499f68 100644 --- a/zkevm-circuits/src/evm_circuit/execution/msize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/msize.rs @@ -11,7 +11,7 @@ use crate::{ }, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{word::WordLoHi, Expr}, }; @@ -66,6 +66,7 @@ impl ExecutionGadget for MsizeGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, _: &Block, + _: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/mul_div_mod.rs b/zkevm-circuits/src/evm_circuit/execution/mul_div_mod.rs index 19bf5f2735..2847fd45c6 100644 --- a/zkevm-circuits/src/evm_circuit/execution/mul_div_mod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/mul_div_mod.rs @@ -11,7 +11,7 @@ use crate::{ math_gadget::{IsZeroWordGadget, LtWordGadget, MulAddWordsGadget}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr, WordLoHi}, @@ -125,6 +125,7 @@ impl ExecutionGadget for MulDivModGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/mulmod.rs b/zkevm-circuits/src/evm_circuit/execution/mulmod.rs index 65bf39dd28..ba46cc3304 100644 --- a/zkevm-circuits/src/evm_circuit/execution/mulmod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/mulmod.rs @@ -11,7 +11,7 @@ use crate::{ math_gadget::{IsZeroWordGadget, LtWordGadget, ModGadget, MulAddWords512Gadget}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr, WordLoHi}, @@ -112,6 +112,7 @@ impl ExecutionGadget for MulModGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/not.rs b/zkevm-circuits/src/evm_circuit/execution/not.rs index d60e4dbbd8..610d8d77ed 100644 --- a/zkevm-circuits/src/evm_circuit/execution/not.rs +++ b/zkevm-circuits/src/evm_circuit/execution/not.rs @@ -8,7 +8,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr}, @@ -71,6 +71,7 @@ impl ExecutionGadget for NotGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/origin.rs b/zkevm-circuits/src/evm_circuit/execution/origin.rs index 2c51b35397..9ccf8e8b44 100644 --- a/zkevm-circuits/src/evm_circuit/execution/origin.rs +++ b/zkevm-circuits/src/evm_circuit/execution/origin.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, AccountAddress, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{CallContextFieldTag, TxContextFieldTag}, util::{word::WordExpr, Expr}, @@ -67,6 +67,7 @@ impl ExecutionGadget for OriginGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/padding.rs b/zkevm-circuits/src/evm_circuit/execution/padding.rs new file mode 100644 index 0000000000..bd663f6a62 --- /dev/null +++ b/zkevm-circuits/src/evm_circuit/execution/padding.rs @@ -0,0 +1,79 @@ +use std::marker::PhantomData; + +use crate::evm_circuit::{ + execution::ExecutionGadget, + step::ExecutionState, + util::{ + constraint_builder::{EVMConstraintBuilder, StepStateTransition}, + CachedRegion, + }, + witness::{Block, Call, Chunk, ExecStep, Transaction}, +}; +use eth_types::Field; +use halo2_proofs::plonk::Error; + +#[derive(Clone, Debug)] +pub(crate) struct PaddingGadget { + _phantom: PhantomData, +} + +impl ExecutionGadget for PaddingGadget { + const NAME: &'static str = "Padding"; + + const EXECUTION_STATE: ExecutionState = ExecutionState::Padding; + + fn configure(cb: &mut EVMConstraintBuilder) -> Self { + cb.require_step_state_transition(StepStateTransition { + ..StepStateTransition::same() + }); + + Self { + _phantom: PhantomData, + } + } + + fn assign_exec_step( + &self, + _region: &mut CachedRegion<'_, '_, F>, + _offset: usize, + _block: &Block, + _chunk: &Chunk, + _: &Transaction, + _: &Call, + _step: &ExecStep, + ) -> Result<(), Error> { + Ok(()) + } +} + +#[cfg(test)] +mod test { + use crate::test_util::CircuitTestBuilder; + + use eth_types::bytecode; + + use mock::TestContext; + + fn test_circuit(evm_circuit_pad_to: usize) { + let bytecode = bytecode! { + PUSH1(0) + STOP + }; + + let ctx = TestContext::<2, 1>::simple_ctx_with_bytecode(bytecode).unwrap(); + + // finish required tests using this witness block + CircuitTestBuilder::<2, 1>::new_from_test_ctx(ctx) + .block_modifier(Box::new(move |block, _chunk| { + block.circuits_params.max_evm_rows = evm_circuit_pad_to + })) + .run(); + } + + // Test where the EVM circuit has a fixed size and contains several Padding + // at the end after the trace steps + #[test] + fn padding() { + test_circuit(50); + } +} diff --git a/zkevm-circuits/src/evm_circuit/execution/pc.rs b/zkevm-circuits/src/evm_circuit/execution/pc.rs index b89f28e789..89f5501dcd 100644 --- a/zkevm-circuits/src/evm_circuit/execution/pc.rs +++ b/zkevm-circuits/src/evm_circuit/execution/pc.rs @@ -10,7 +10,7 @@ use crate::{ }, CachedRegion, U64Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{word::WordExpr, Expr}, }; @@ -64,6 +64,7 @@ impl ExecutionGadget for PcGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, _: &Block, + _: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/pop.rs b/zkevm-circuits/src/evm_circuit/execution/pop.rs index 59d0ac32dd..f197268806 100644 --- a/zkevm-circuits/src/evm_circuit/execution/pop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/pop.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHiCell}, @@ -57,6 +57,7 @@ impl ExecutionGadget for PopGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/ecrecover.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/ecrecover.rs index 1e14430c99..bfb194f44a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/ecrecover.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/ecrecover.rs @@ -18,7 +18,7 @@ use crate::{ }, table::CallContextFieldTag, util::word::{Word32Cell, WordExpr, WordLimbs, WordLoHi, WordLoHiCell}, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }; #[derive(Clone, Debug)] @@ -206,6 +206,7 @@ impl ExecutionGadget for EcrecoverGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/precompiles/identity.rs b/zkevm-circuits/src/evm_circuit/execution/precompiles/identity.rs index ffec75d633..07250e3da3 100644 --- a/zkevm-circuits/src/evm_circuit/execution/precompiles/identity.rs +++ b/zkevm-circuits/src/evm_circuit/execution/precompiles/identity.rs @@ -14,7 +14,7 @@ use crate::{ }, }, table::CallContextFieldTag, - witness::{Block, ExecStep, Transaction}, + witness::{Block, Chunk, ExecStep, Transaction}, }; #[derive(Clone, Debug)] @@ -105,6 +105,7 @@ impl ExecutionGadget for IdentityGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/push.rs b/zkevm-circuits/src/evm_circuit/execution/push.rs index d6e95b6552..21bb13b3d9 100644 --- a/zkevm-circuits/src/evm_circuit/execution/push.rs +++ b/zkevm-circuits/src/evm_circuit/execution/push.rs @@ -13,7 +13,7 @@ use crate::{ math_gadget::{IsEqualGadget, LtGadget}, not, or, select, sum, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr}, @@ -159,6 +159,7 @@ impl ExecutionGadget for PushGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/return_revert.rs b/zkevm-circuits/src/evm_circuit/execution/return_revert.rs index 02a1a5854a..48f9e352e1 100644 --- a/zkevm-circuits/src/evm_circuit/execution/return_revert.rs +++ b/zkevm-circuits/src/evm_circuit/execution/return_revert.rs @@ -15,7 +15,7 @@ use crate::{ }, not, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, CallContextFieldTag}, util::{ @@ -282,6 +282,7 @@ impl ExecutionGadget for ReturnRevertGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/returndatacopy.rs b/zkevm-circuits/src/evm_circuit/execution/returndatacopy.rs index 7e1a8b57aa..1bf7334d4c 100644 --- a/zkevm-circuits/src/evm_circuit/execution/returndatacopy.rs +++ b/zkevm-circuits/src/evm_circuit/execution/returndatacopy.rs @@ -16,7 +16,7 @@ use crate::{ }, CachedRegion, Cell, MemoryAddress, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -170,6 +170,7 @@ impl ExecutionGadget for ReturnDataCopyGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/returndatasize.rs b/zkevm-circuits/src/evm_circuit/execution/returndatasize.rs index dfd410e684..f17b61e27e 100644 --- a/zkevm-circuits/src/evm_circuit/execution/returndatasize.rs +++ b/zkevm-circuits/src/evm_circuit/execution/returndatasize.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -65,6 +65,7 @@ impl ExecutionGadget for ReturnDataSizeGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/sar.rs b/zkevm-circuits/src/evm_circuit/execution/sar.rs index 1bd8dc9805..6a73a0d83a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sar.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sar.rs @@ -12,7 +12,7 @@ use crate::{ math_gadget::{IsEqualGadget, IsZeroGadget, LtGadget}, select, sum, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, Word4, WordExpr}, @@ -268,6 +268,7 @@ impl ExecutionGadget for SarGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/sdiv_smod.rs b/zkevm-circuits/src/evm_circuit/execution/sdiv_smod.rs index b6779c420f..966edf9944 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sdiv_smod.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sdiv_smod.rs @@ -13,7 +13,7 @@ use crate::{ }, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr, WordLoHi}, @@ -156,6 +156,7 @@ impl ExecutionGadget for SignedDivModGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs b/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs index f6ec95b612..7bb83d4f1a 100644 --- a/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs +++ b/zkevm-circuits/src/evm_circuit/execution/selfbalance.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::{AccountFieldTag, CallContextFieldTag}, util::{ @@ -65,6 +65,7 @@ impl ExecutionGadget for SelfbalanceGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/sha3.rs b/zkevm-circuits/src/evm_circuit/execution/sha3.rs index 12cbac2ee4..9cff5746c4 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sha3.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sha3.rs @@ -18,7 +18,7 @@ use crate::{ }, rlc, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::word::{WordExpr, WordLoHi, WordLoHiCell}, }; @@ -117,6 +117,7 @@ impl ExecutionGadget for Sha3Gadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _tx: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/shl_shr.rs b/zkevm-circuits/src/evm_circuit/execution/shl_shr.rs index 5dba3de3e4..6b795db87d 100644 --- a/zkevm-circuits/src/evm_circuit/execution/shl_shr.rs +++ b/zkevm-circuits/src/evm_circuit/execution/shl_shr.rs @@ -12,7 +12,7 @@ use crate::{ math_gadget::{IsZeroGadget, IsZeroWordGadget, LtWordGadget, MulAddWordsGadget}, sum, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr, WordLoHi}, @@ -169,6 +169,7 @@ impl ExecutionGadget for ShlShrGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs b/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs index 2211fa90d5..23be044ca4 100644 --- a/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs +++ b/zkevm-circuits/src/evm_circuit/execution/signed_comparator.rs @@ -9,7 +9,7 @@ use crate::{ math_gadget::{ComparisonGadget, IsEqualGadget, LtGadget}, select, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32Cell, WordExpr, WordLoHi}, @@ -144,6 +144,7 @@ impl ExecutionGadget for SignedComparatorGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _transaction: &Transaction, _call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/signextend.rs b/zkevm-circuits/src/evm_circuit/execution/signextend.rs index faec033ad9..d9c703e68e 100644 --- a/zkevm-circuits/src/evm_circuit/execution/signextend.rs +++ b/zkevm-circuits/src/evm_circuit/execution/signextend.rs @@ -13,7 +13,7 @@ use crate::{ math_gadget::{IsEqualGadget, IsZeroGadget}, select, sum, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{Word32, Word32Cell, WordExpr}, @@ -158,6 +158,7 @@ impl ExecutionGadget for SignextendGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/sload.rs b/zkevm-circuits/src/evm_circuit/execution/sload.rs index 7a222c9b44..0065e00ad8 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sload.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sload.rs @@ -9,7 +9,7 @@ use crate::{ }, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -103,6 +103,7 @@ impl ExecutionGadget for SloadGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/sstore.rs b/zkevm-circuits/src/evm_circuit/execution/sstore.rs index ebcb7b52db..bcc946cf8b 100644 --- a/zkevm-circuits/src/evm_circuit/execution/sstore.rs +++ b/zkevm-circuits/src/evm_circuit/execution/sstore.rs @@ -12,7 +12,7 @@ use crate::{ math_gadget::{IsEqualWordGadget, IsZeroWordGadget, LtGadget}, not, CachedRegion, Cell, U64Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -167,6 +167,7 @@ impl ExecutionGadget for SstoreGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, tx: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/stop.rs b/zkevm-circuits/src/evm_circuit/execution/stop.rs index 8ce8ca02ec..de2a943ab6 100644 --- a/zkevm-circuits/src/evm_circuit/execution/stop.rs +++ b/zkevm-circuits/src/evm_circuit/execution/stop.rs @@ -12,7 +12,7 @@ use crate::{ math_gadget::ComparisonGadget, CachedRegion, Cell, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, table::CallContextFieldTag, util::{ @@ -108,6 +108,7 @@ impl ExecutionGadget for StopGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, call: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/execution/swap.rs b/zkevm-circuits/src/evm_circuit/execution/swap.rs index 5bbd27df22..92d2706cbb 100644 --- a/zkevm-circuits/src/evm_circuit/execution/swap.rs +++ b/zkevm-circuits/src/evm_circuit/execution/swap.rs @@ -7,7 +7,7 @@ use crate::{ constraint_builder::{EVMConstraintBuilder, StepStateTransition, Transition::Delta}, CachedRegion, }, - witness::{Block, Call, ExecStep, Transaction}, + witness::{Block, Call, Chunk, ExecStep, Transaction}, }, util::{ word::{WordExpr, WordLoHiCell}, @@ -66,6 +66,7 @@ impl ExecutionGadget for SwapGadget { region: &mut CachedRegion<'_, '_, F>, offset: usize, block: &Block, + _chunk: &Chunk, _: &Transaction, _: &Call, step: &ExecStep, diff --git a/zkevm-circuits/src/evm_circuit/param.rs b/zkevm-circuits/src/evm_circuit/param.rs index e2e4550418..f205e14475 100644 --- a/zkevm-circuits/src/evm_circuit/param.rs +++ b/zkevm-circuits/src/evm_circuit/param.rs @@ -9,7 +9,7 @@ use halo2_proofs::{ use std::collections::HashMap; // Step dimension -pub(crate) const STEP_WIDTH: usize = 128; +pub(crate) const STEP_WIDTH: usize = 139; /// Step height pub const MAX_STEP_HEIGHT: usize = 19; /// The height of the state of a step, used by gates that connect two @@ -43,7 +43,8 @@ pub(crate) const EVM_LOOKUP_COLS: usize = FIXED_TABLE_LOOKUPS + COPY_TABLE_LOOKUPS + KECCAK_TABLE_LOOKUPS + EXP_TABLE_LOOKUPS - + SIG_TABLE_LOOKUPS; + + SIG_TABLE_LOOKUPS + + CHUNK_CTX_TABLE_LOOKUPS; /// Lookups done per row. pub const LOOKUP_CONFIG: &[(Table, usize)] = &[ @@ -56,6 +57,7 @@ pub const LOOKUP_CONFIG: &[(Table, usize)] = &[ (Table::Keccak, KECCAK_TABLE_LOOKUPS), (Table::Exp, EXP_TABLE_LOOKUPS), (Table::Sig, SIG_TABLE_LOOKUPS), + (Table::ChunkCtx, CHUNK_CTX_TABLE_LOOKUPS), ]; /// Fixed Table lookups done in EVMCircuit @@ -65,7 +67,7 @@ pub const FIXED_TABLE_LOOKUPS: usize = 8; pub const TX_TABLE_LOOKUPS: usize = 4; /// Rw Table lookups done in EVMCircuit -pub const RW_TABLE_LOOKUPS: usize = 8; +pub const RW_TABLE_LOOKUPS: usize = 13; /// Bytecode Table lookups done in EVMCircuit pub const BYTECODE_TABLE_LOOKUPS: usize = 4; @@ -85,6 +87,9 @@ pub const EXP_TABLE_LOOKUPS: usize = 1; /// Sig Table lookups done in EVMCircuit pub const SIG_TABLE_LOOKUPS: usize = 1; +/// chunk_ctx Table lookups done in EVMCircuit +pub const CHUNK_CTX_TABLE_LOOKUPS: usize = 1; + /// Maximum number of bytes that an integer can fit in field without wrapping /// around. pub(crate) const MAX_N_BYTES_INTEGER: usize = 31; @@ -168,7 +173,7 @@ pub(crate) const N_BYTES_TX: usize = N_BYTES_TX_NONCE + N_BYTES_TX_CALLDATA_GASCOST + N_BYTES_TX_TXSIGNHASH; -pub(crate) const N_BYTES_WITHDRAWAL: usize = N_BYTES_U64 //id +pub(crate) const N_BYTES_WITHDRAWAL: usize = N_BYTES_U64 //id + N_BYTES_U64 // validator id + N_BYTES_ACCOUNT_ADDRESS // address + N_BYTES_U64; // amount diff --git a/zkevm-circuits/src/evm_circuit/step.rs b/zkevm-circuits/src/evm_circuit/step.rs index 195cc77321..f26f9290f6 100644 --- a/zkevm-circuits/src/evm_circuit/step.rs +++ b/zkevm-circuits/src/evm_circuit/step.rs @@ -58,6 +58,9 @@ pub enum ExecutionState { BeginTx, EndTx, EndBlock, + Padding, + BeginChunk, + EndChunk, InvalidTx, // Opcode successful cases STOP, @@ -342,7 +345,10 @@ impl From<&ExecStep> for ExecutionState { }, ExecState::BeginTx => ExecutionState::BeginTx, ExecState::EndTx => ExecutionState::EndTx, + ExecState::Padding => ExecutionState::Padding, ExecState::EndBlock => ExecutionState::EndBlock, + ExecState::BeginChunk => ExecutionState::BeginChunk, + ExecState::EndChunk => ExecutionState::EndChunk, ExecState::InvalidTx => ExecutionState::InvalidTx, } } @@ -738,6 +744,8 @@ pub(crate) struct StepState { pub(crate) execution_state: DynamicSelectorHalf, /// The Read/Write counter pub(crate) rw_counter: Cell, + /// The Read/Write counter accumulated in current chunk + pub(crate) inner_rw_counter: Cell, /// The unique identifier of call in the whole proof, using the /// `rw_counter` at the call step. pub(crate) call_id: Cell, @@ -792,6 +800,7 @@ impl Step { ExecutionState::amount(), ), rw_counter: cell_manager.query_cell(meta, CellType::StoragePhase1), + inner_rw_counter: cell_manager.query_cell(meta, CellType::StoragePhase1), call_id: cell_manager.query_cell(meta, CellType::StoragePhase1), is_root: cell_manager.query_cell(meta, CellType::StoragePhase1), is_create: cell_manager.query_cell(meta, CellType::StoragePhase1), @@ -836,6 +845,11 @@ impl Step { self.state .rw_counter .assign(region, offset, Value::known(F::from(step.rwc.into())))?; + self.state.inner_rw_counter.assign( + region, + offset, + Value::known(F::from(step.rwc_inner_chunk.into())), + )?; self.state .call_id .assign(region, offset, Value::known(F::from(call.call_id as u64)))?; diff --git a/zkevm-circuits/src/evm_circuit/table.rs b/zkevm-circuits/src/evm_circuit/table.rs index cfa5170fd6..b0efede59f 100644 --- a/zkevm-circuits/src/evm_circuit/table.rs +++ b/zkevm-circuits/src/evm_circuit/table.rs @@ -193,6 +193,8 @@ pub enum Table { Exp, /// Lookup for sig table Sig, + /// Lookup for chunk context + ChunkCtx, } #[derive(Clone, Debug)] @@ -369,6 +371,13 @@ pub(crate) enum Lookup { recovered_addr: Expression, is_valid: Expression, }, + /// Lookup to block table, which contains constants of this block. + ChunkCtx { + /// Tag to specify which field to read. + field_tag: Expression, + /// value + value: Expression, + }, /// Conditional lookup enabled by the first element. Conditional(Expression, Box>), @@ -382,6 +391,7 @@ impl Lookup { pub(crate) fn table(&self) -> Table { match self { Self::Fixed { .. } => Table::Fixed, + Self::ChunkCtx { .. } => Table::ChunkCtx, Self::Tx { .. } => Table::Tx, Self::Rw { .. } => Table::Rw, Self::Bytecode { .. } => Table::Bytecode, @@ -527,6 +537,7 @@ impl Lookup { recovered_addr.clone(), is_valid.clone(), ], + Self::ChunkCtx { field_tag, value } => vec![field_tag.clone(), value.clone()], Self::Conditional(condition, lookup) => lookup .input_exprs() .into_iter() diff --git a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs index ebf88f135f..a887b3fda7 100644 --- a/zkevm-circuits/src/evm_circuit/util/common_gadget.rs +++ b/zkevm-circuits/src/evm_circuit/util/common_gadget.rs @@ -20,12 +20,12 @@ use crate::{ not, Cell, }, }, - table::{AccountFieldTag, CallContextFieldTag}, + table::{chunk_ctx_table::ChunkCtxFieldTag, AccountFieldTag, CallContextFieldTag}, util::{ word::{Word32, Word32Cell, WordExpr, WordLoHi, WordLoHiCell}, Expr, }, - witness::{Block, Call, ExecStep}, + witness::{Block, Call, Chunk, ExecStep}, }; use bus_mapping::state_db::CodeDB; use eth_types::{ @@ -234,7 +234,7 @@ impl RestoreContextGadget { gas_left: To(gas_left), memory_word_size: To(caller_memory_word_size.expr()), reversible_write_counter: To(reversible_write_counter), - log_id: Same, + ..Default::default() }); Self { @@ -1169,3 +1169,106 @@ impl WordByteRangeGadget { self.not_overflow.expr() } } + +/// current SRS size < 2^30 so use 4 bytes (2^32) in LtGadet should be enough +const MAX_RW_BYTES: usize = u32::BITS as usize / 8; + +/// Check consecutive Rw::Padding in rw_table to assure rw_table no malicious insertion +#[derive(Clone, Debug)] +pub(crate) struct RwTablePaddingGadget { + is_empty_rwc: IsZeroGadget, + max_rws: Cell, + chunk_index: Cell, + is_end_padding_exist: LtGadget, + is_first_chunk: IsZeroGadget, +} + +impl RwTablePaddingGadget { + pub(crate) fn construct( + cb: &mut EVMConstraintBuilder, + inner_rws_before_padding: Expression, + ) -> Self { + let max_rws = cb.query_copy_cell(); + let chunk_index = cb.query_cell(); + let is_empty_rwc = + IsZeroGadget::construct(cb, cb.curr.state.rw_counter.clone().expr() - 1.expr()); + + cb.chunk_context_lookup(ChunkCtxFieldTag::CurrentChunkIndex, chunk_index.expr()); + let is_first_chunk = IsZeroGadget::construct(cb, chunk_index.expr()); + + // Verify rw_counter counts to the same number of meaningful rows in + // rw_table to ensure there is no malicious insertion. + // Verify that there are at most total_rws meaningful entries in the rw_table + // - startop only exist in first chunk + // - end paddings are consecutively + cb.condition(is_first_chunk.expr(), |cb| { + cb.rw_table_start_lookup(1.expr()); + }); + + let is_end_padding_exist = LtGadget::<_, MAX_RW_BYTES>::construct( + cb, + 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()); + }); + + // Since every lookup done in the EVM circuit must succeed and uses + // a unique rw_counter, we know that at least there are + // total_rws meaningful entries in the rw_table. + // We conclude that the number of meaningful entries in the rw_table + // is total_rws. + + Self { + is_empty_rwc, + max_rws, + chunk_index, + is_first_chunk, + is_end_padding_exist, + } + } + + pub(crate) fn assign_exec_step( + &self, + region: &mut CachedRegion<'_, '_, F>, + offset: usize, + _block: &Block, + chunk: &Chunk, + inner_rws_before_padding: u64, + step: &ExecStep, + ) -> Result<(), Error> { + let total_rwc = u64::from(step.rwc) - 1; + self.is_empty_rwc + .assign(region, offset, F::from(total_rwc))?; + + let max_rws = F::from(chunk.fixed_param.max_rws as u64); + let max_rws_assigned = self.max_rws.assign(region, offset, Value::known(max_rws))?; + + self.chunk_index.assign( + region, + offset, + Value::known(F::from(chunk.chunk_context.idx as u64)), + )?; + + self.is_first_chunk + .assign(region, offset, F::from(chunk.chunk_context.idx as u64))?; + + self.is_end_padding_exist.assign( + region, + offset, + F::ONE, + max_rws.sub(F::from(inner_rws_before_padding)), + )?; + + // When rw_indices is not empty, means current step is non-padding step, we're at the + // last row (at a fixed offset), where we need to access the max_rws + // constant. + if step.rw_indices_len() != 0 { + region.constrain_constant(max_rws_assigned, max_rws)?; + } + Ok(()) + } +} diff --git a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs index 5e20bff23f..6fdcd6d12b 100644 --- a/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs +++ b/zkevm-circuits/src/evm_circuit/util/constraint_builder.rs @@ -14,8 +14,8 @@ use crate::{ util::{Cell, RandomLinearCombination}, }, table::{ - AccountFieldTag, BytecodeFieldTag, CallContextFieldTag, TxContextFieldTag, TxLogFieldTag, - TxReceiptFieldTag, + chunk_ctx_table::ChunkCtxFieldTag, AccountFieldTag, BytecodeFieldTag, CallContextFieldTag, + StepStateFieldTag, TxContextFieldTag, TxLogFieldTag, TxReceiptFieldTag, }, util::{ build_tx_log_expression, query_expression, @@ -97,6 +97,22 @@ impl StepStateTransition { log_id: Transition::Any, } } + + pub(crate) fn same() -> Self { + Self { + rw_counter: Transition::Same, + call_id: Transition::Same, + is_root: Transition::Same, + is_create: Transition::Same, + code_hash: Transition::Same, + program_counter: Transition::Same, + stack_pointer: Transition::Same, + gas_left: Transition::Same, + memory_word_size: Transition::Same, + reversible_write_counter: Transition::Same, + log_id: Transition::Same, + } + } } /// ReversionInfo counts `rw_counter` of reversion for gadgets, by tracking how @@ -573,6 +589,27 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { }; } + // Note: special case handling: inner_rw_counter shared the same Transition with + // rw_conuter + match &step_state_transition.rw_counter { + Transition::Same => self.require_equal( + "State transition (same) constraint of inner_rw_counter", + self.next.state.inner_rw_counter.expr(), + self.curr.state.inner_rw_counter.expr(), + ), + Transition::Delta(delta) => self.require_equal( + concat!("State transition (delta) constraint of inner_rw_counter"), + self.next.state.inner_rw_counter.expr(), + self.curr.state.inner_rw_counter.expr() + delta.clone(), + ), + Transition::To(to) => self.require_equal( + "State transition (to) constraint of inner_rw_counter", + self.next.state.inner_rw_counter.expr(), + to.clone(), + ), + _ => {} + } + constrain!(rw_counter); constrain!(call_id); constrain!(is_root); @@ -1432,8 +1469,104 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { ); } - // RwTable Padding (Start tag) + pub(crate) fn chunk_context_lookup( + &mut self, + field_tag: ChunkCtxFieldTag, + value: Expression, + ) { + self.add_lookup( + "chunk_ctx lookup", + Lookup::ChunkCtx { + field_tag: field_tag.expr(), + value, + }, + ); + } + + pub(crate) fn step_state_lookup(&mut self, is_write: Expression) { + self.rw_lookup( + "StepState lookup codehash", + is_write.clone(), + Target::StepState, + RwValues::new( + 0.expr(), + 0.expr(), + StepStateFieldTag::CodeHash.expr(), + WordLoHi::zero(), + self.curr.state.code_hash.to_word(), + WordLoHi::zero(), + WordLoHi::zero(), + ), + ); + + [ + ( + "StepState lookup CallID", + self.curr.state.call_id.clone(), + StepStateFieldTag::CallID, + ), + ( + "StepState lookup IsRoot", + self.curr.state.is_root.clone(), + StepStateFieldTag::IsRoot, + ), + ( + "StepState lookup IsCreate", + self.curr.state.is_create.clone(), + StepStateFieldTag::IsCreate, + ), + ( + "StepState lookup ProgramCounter", + self.curr.state.program_counter.clone(), + StepStateFieldTag::ProgramCounter, + ), + ( + "StepState lookup StackPointer", + self.curr.state.stack_pointer.clone(), + StepStateFieldTag::StackPointer, + ), + ( + "StepState lookup GasLeft", + self.curr.state.gas_left.clone(), + StepStateFieldTag::GasLeft, + ), + ( + "StepState lookup MemoryWordSize", + self.curr.state.memory_word_size.clone(), + StepStateFieldTag::MemoryWordSize, + ), + ( + "StepState lookup ReversibleWriteCounter", + self.curr.state.reversible_write_counter.clone(), + StepStateFieldTag::ReversibleWriteCounter, + ), + ( + "StepState lookup LogID", + self.curr.state.log_id.clone(), + StepStateFieldTag::LogID, + ), + ] + .iter() + .for_each(|(name, cell, field_tag)| { + self.rw_lookup( + name, + is_write.clone(), + Target::StepState, + RwValues::new( + 0.expr(), + 0.expr(), + field_tag.expr(), + WordLoHi::zero(), + WordLoHi::from_lo_unchecked(cell.expr()), + WordLoHi::zero(), + WordLoHi::zero(), + ), + ); + }); + } + // RwTable Start (Start tag) + #[allow(dead_code)] pub(crate) fn rw_table_start_lookup(&mut self, counter: Expression) { self.rw_lookup_with_counter( "Start lookup", @@ -1452,6 +1585,26 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { ); } + // RwTable Padding (Padding tag) + #[allow(dead_code)] + pub(crate) fn rw_table_padding_lookup(&mut self, counter: Expression) { + self.rw_lookup_with_counter( + "Padding lookup", + counter, + 0.expr(), + Target::Padding, + RwValues::new( + 0.expr(), + 0.expr(), + 0.expr(), + WordLoHi::zero(), + WordLoHi::zero(), + WordLoHi::zero(), + WordLoHi::zero(), + ), + ); + } + // Copy Table #[allow(clippy::too_many_arguments)] @@ -1552,7 +1705,6 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { } // Validation - pub(crate) fn validate_degree(&self, degree: usize, name: &'static str) { // We need to subtract IMPLICIT_DEGREE from MAX_DEGREE because all expressions // will be multiplied by state selector and q_step/q_step_first @@ -1685,11 +1837,13 @@ impl<'a, F: Field> EVMConstraintBuilder<'a, F> { Some(condition) => lookup.conditional(condition), None => lookup, }; + let compressed_expr = self.split_expression( "Lookup compression", rlc::expr(&lookup.input_exprs(), self.challenges.lookup_input()), MAX_DEGREE - IMPLICIT_DEGREE, ); + self.store_expression(name, compressed_expr, CellType::Lookup(lookup.table())); } diff --git a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs index 059f896745..5c820dea92 100644 --- a/zkevm-circuits/src/evm_circuit/util/instrumentation.rs +++ b/zkevm-circuits/src/evm_circuit/util/instrumentation.rs @@ -107,6 +107,9 @@ impl Instrument { CellType::Lookup(Table::Sig) => { report.sig_table = data_entry; } + CellType::Lookup(Table::ChunkCtx) => { + report.chunk_ctx_table = data_entry; + } } } report_collection.push(report); @@ -135,6 +138,7 @@ pub struct ExecStateReport { pub keccak_table: StateReportRow, pub exp_table: StateReportRow, pub sig_table: StateReportRow, + pub chunk_ctx_table: StateReportRow, } impl From for ExecStateReport { diff --git a/zkevm-circuits/src/evm_circuit/util/tx.rs b/zkevm-circuits/src/evm_circuit/util/tx.rs index d99b029ffd..162aa685d2 100644 --- a/zkevm-circuits/src/evm_circuit/util/tx.rs +++ b/zkevm-circuits/src/evm_circuit/util/tx.rs @@ -143,7 +143,8 @@ impl EndTxHelperGadget { }); }); cb.condition( - cb.next.execution_state_selector([ExecutionState::EndBlock]), + cb.next + .execution_state_selector([ExecutionState::EndBlock, ExecutionState::Padding]), |cb| { cb.require_step_state_transition(StepStateTransition { rw_counter: Delta(rw_counter_offset.expr()), diff --git a/zkevm-circuits/src/exp_circuit.rs b/zkevm-circuits/src/exp_circuit.rs index 5251308dcd..63749a2ffa 100644 --- a/zkevm-circuits/src/exp_circuit.rs +++ b/zkevm-circuits/src/exp_circuit.rs @@ -12,7 +12,7 @@ use crate::{ evm_circuit::util::constraint_builder::BaseConstraintBuilder, table::{ExpTable, LookupTable}, util::{Challenges, SubCircuit, SubCircuitConfig}, - witness, + witness::{self, Chunk}, }; use bus_mapping::circuit_input_builder::{ExpEvent, ExpStep}; use eth_types::{Field, ToScalar, U256}; @@ -521,20 +521,17 @@ impl SubCircuit for ExpCircuit { 11 } - fn new_from_block(block: &witness::Block) -> Self { + fn new_from_block(block: &witness::Block, chunk: &Chunk) -> Self { // Hardcoded to pass unit tests for now. In the future, insert: - // "block.circuits_params.max_exp_rows" - Self::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ) + // "chunk.fixed_param.max_exp_rows" + Self::new(block.exp_events.clone(), chunk.fixed_param.max_exp_steps) } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize) { ( Self::Config::min_num_rows(&block.exp_events), - block.circuits_params.max_exp_steps, + chunk.fixed_param.max_exp_steps, ) } diff --git a/zkevm-circuits/src/exp_circuit/test.rs b/zkevm-circuits/src/exp_circuit/test.rs index 01304fda34..6f2129bda6 100644 --- a/zkevm-circuits/src/exp_circuit/test.rs +++ b/zkevm-circuits/src/exp_circuit/test.rs @@ -2,6 +2,7 @@ use crate::{ evm_circuit::witness::{block_convert, Block}, exp_circuit::ExpCircuit, util::{unusable_rows, SubCircuit}, + witness::{chunk_convert, Chunk}, }; use bus_mapping::{ circuit_input_builder::{CircuitInputBuilder, FixedCParams}, @@ -20,11 +21,8 @@ fn exp_circuit_unusable_rows() { } /// Test exponentiation circuit with the provided block witness -pub fn test_exp_circuit(k: u32, block: Block) { - let circuit = ExpCircuit::::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ); +pub fn test_exp_circuit(k: u32, block: Block, chunk: Chunk) { + let circuit = ExpCircuit::::new(block.exp_events, chunk.fixed_param.max_exp_steps); let prover = MockProver::::run(k, &circuit, vec![]).unwrap(); prover.assert_satisfied() } @@ -51,35 +49,34 @@ fn gen_data(code: Bytecode, default_params: bool) -> CircuitInputBuilder::simple_ctx_with_bytecode(code).unwrap(); let block: GethData = test_ctx.into(); // Needs default parameters for variadic size test - let builder = if default_params { - let mut builder = - BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) - .new_circuit_input_builder(); - builder + + if default_params { + BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - builder + .unwrap() } else { - let builder = BlockData::new_from_geth_data(block.clone()).new_circuit_input_builder(); - builder + BlockData::new_from_geth_data(block.clone()) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) .unwrap() - }; - builder + } } fn test_ok(base: Word, exponent: Word, k: Option) { let code = gen_code_single(base, exponent); let builder = gen_data(code, false); let block = block_convert::(&builder).unwrap(); - test_exp_circuit(k.unwrap_or(18), block); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + test_exp_circuit(k.unwrap_or(18), block, chunk); } fn test_ok_multiple(args: Vec<(Word, Word)>) { let code = gen_code_multiple(args); let builder = gen_data(code, false); let block = block_convert::(&builder).unwrap(); - test_exp_circuit(20, block); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + test_exp_circuit(20, block, chunk); } #[test] @@ -123,17 +120,13 @@ fn variadic_size_check() { let block: GethData = TestContext::<0, 0>::new(None, |_| {}, |_, _| {}, |b, _| b) .unwrap() .into(); - let mut builder = - BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) - .new_circuit_input_builder(); - builder + let builder = BlockData::new_from_geth_data_with_params(block.clone(), FixedCParams::default()) + .new_circuit_input_builder() .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); let block = block_convert::(&builder).unwrap(); - let circuit = ExpCircuit::::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + let circuit = ExpCircuit::::new(block.exp_events, chunk.fixed_param.max_exp_steps); let prover1 = MockProver::::run(k, &circuit, vec![]).unwrap(); // Non-empty @@ -148,10 +141,8 @@ fn variadic_size_check() { }; let builder = gen_data(code, true); let block = block_convert::(&builder).unwrap(); - let circuit = ExpCircuit::::new( - block.exp_events.clone(), - block.circuits_params.max_exp_steps, - ); + let chunk = chunk_convert::(&block, &builder).unwrap().remove(0); + let circuit = ExpCircuit::::new(block.exp_events, chunk.fixed_param.max_exp_steps); let prover2 = MockProver::::run(k, &circuit, vec![]).unwrap(); assert_eq!(prover1.fixed(), prover2.fixed()); diff --git a/zkevm-circuits/src/keccak_circuit.rs b/zkevm-circuits/src/keccak_circuit.rs index 529cd61c9d..14cf9453de 100644 --- a/zkevm-circuits/src/keccak_circuit.rs +++ b/zkevm-circuits/src/keccak_circuit.rs @@ -35,7 +35,7 @@ use crate::{ word::{Word32, WordExpr}, Challenges, SubCircuit, SubCircuitConfig, }, - witness, + witness::{self, Chunk}, }; use eth_types::Field; use gadgets::util::{and, not, select, sum, Expr}; @@ -1005,26 +1005,26 @@ impl SubCircuit for KeccakCircuit { keccak_unusable_rows() } - /// The `block.circuits_params.keccak_padding` parameter, when enabled, sets + /// The `chunk.fixed_param.keccak_padding` parmeter, when enabled, sets /// up the circuit to support a fixed number of permutations/keccak_f's, /// independently of the permutations required by `inputs`. - fn new_from_block(block: &witness::Block) -> Self { + fn new_from_block(block: &witness::Block, chunk: &Chunk) -> Self { Self::new( - block.circuits_params.max_keccak_rows, + chunk.fixed_param.max_keccak_rows, block.keccak_inputs.clone(), ) } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { - let rows_per_chunk = (NUM_ROUNDS + 1) * get_num_rows_per_round(); + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize) { + let rows_perchunk = (NUM_ROUNDS + 1) * get_num_rows_per_round(); ( block .keccak_inputs .iter() - .map(|bytes| (bytes.len() as f64 / 136.0).ceil() as usize * rows_per_chunk) + .map(|bytes| (bytes.len() as f64 / 136.0).ceil() as usize * rows_perchunk) .sum(), - block.circuits_params.max_keccak_rows, + chunk.fixed_param.max_keccak_rows, ) } diff --git a/zkevm-circuits/src/pi_circuit.rs b/zkevm-circuits/src/pi_circuit.rs index 1c9f700202..920b0e13d7 100644 --- a/zkevm-circuits/src/pi_circuit.rs +++ b/zkevm-circuits/src/pi_circuit.rs @@ -34,7 +34,7 @@ use crate::{ table::{BlockTable, KeccakTable, LookupTable, TxFieldTag, TxTable, WdTable}, tx_circuit::TX_LEN, util::{word::WordLoHi, Challenges, SubCircuit, SubCircuitConfig}, - witness, + witness::{self, Chunk}, }; use gadgets::{ is_zero::IsZeroChip, @@ -1460,25 +1460,25 @@ impl SubCircuit for PiCircuit { 6 } - fn new_from_block(block: &witness::Block) -> Self { + fn new_from_block(block: &witness::Block, chunk: &Chunk) -> Self { let public_data = public_data_convert(block); PiCircuit::new( - block.circuits_params.max_txs, - block.circuits_params.max_withdrawals, - block.circuits_params.max_calldata, + chunk.fixed_param.max_txs, + chunk.fixed_param.max_withdrawals, + chunk.fixed_param.max_calldata, public_data, ) } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize) { let calldata_len = block.txs.iter().map(|tx| tx.call_data.len()).sum(); ( Self::Config::circuit_len_all(block.txs.len(), block.withdrawals().len(), calldata_len), Self::Config::circuit_len_all( - block.circuits_params.max_txs, - block.circuits_params.max_withdrawals, - block.circuits_params.max_calldata, + chunk.fixed_param.max_txs, + chunk.fixed_param.max_withdrawals, + chunk.fixed_param.max_calldata, ), ) } diff --git a/zkevm-circuits/src/pi_circuit/test.rs b/zkevm-circuits/src/pi_circuit/test.rs index 49c9fe21a6..e8f39ed19e 100644 --- a/zkevm-circuits/src/pi_circuit/test.rs +++ b/zkevm-circuits/src/pi_circuit/test.rs @@ -1,6 +1,10 @@ use std::collections::HashMap; -use crate::{pi_circuit::dev::PiCircuitParams, util::unusable_rows, witness::block_convert}; +use crate::{ + pi_circuit::dev::PiCircuitParams, + util::unusable_rows, + witness::{block_convert, chunk_convert}, +}; use super::*; use bus_mapping::{ @@ -129,7 +133,7 @@ fn test_1tx_1maxtx() { wallets.insert(wallet_a.address(), wallet_a); let mut block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data_with_params( + let builder = BlockData::new_from_geth_data_with_params( block.clone(), FixedCParams { max_txs: MAX_TXS, @@ -139,17 +143,15 @@ fn test_1tx_1maxtx() { ..Default::default() }, ) - .new_circuit_input_builder(); + .new_circuit_input_builder() + .handle_block(&block.eth_block, &block.geth_traces) + .unwrap(); block.sign(&wallets); - - builder - .handle_block(&block.eth_block, &block.geth_traces) - .unwrap(); - let block = block_convert(&builder).unwrap(); + let chunk = chunk_convert(&block, &builder).unwrap().remove(0); // MAX_TXS, MAX_TXS align with `CircuitsParams` - let circuit = PiCircuit::::new_from_block(&block); + let circuit = PiCircuit::::new_from_block(&block, &chunk); let public_inputs = circuit.instance(); let prover = match MockProver::run(degree, &circuit, public_inputs) { @@ -209,7 +211,7 @@ fn test_1wd_1wdmax() { wallets.insert(wallet_a.address(), wallet_a); let mut block: GethData = test_ctx.into(); - let mut builder = BlockData::new_from_geth_data_with_params( + let builder = BlockData::new_from_geth_data_with_params( block.clone(), FixedCParams { max_txs: MAX_TXS, @@ -223,13 +225,14 @@ fn test_1wd_1wdmax() { block.sign(&wallets); - builder + let builder = builder .handle_block(&block.eth_block, &block.geth_traces) .unwrap(); let block = block_convert(&builder).unwrap(); + let chunk = chunk_convert(&block, &builder).unwrap().remove(0); // MAX_TXS, MAX_TXS align with `CircuitsParams` - let circuit = PiCircuit::::new_from_block(&block); + let circuit = PiCircuit::::new_from_block(&block, &chunk); let public_inputs = circuit.instance(); let prover = match MockProver::run(degree, &circuit, public_inputs) { diff --git a/zkevm-circuits/src/root_circuit.rs b/zkevm-circuits/src/root_circuit.rs index 6428adcbb1..18fb5f72f9 100644 --- a/zkevm-circuits/src/root_circuit.rs +++ b/zkevm-circuits/src/root_circuit.rs @@ -4,11 +4,11 @@ use halo2_proofs::{ arithmetic::Field as Halo2Field, circuit::{Layouter, SimpleFloorPlanner, Value}, halo2curves::{serde::SerdeObject, CurveAffine, CurveExt}, - plonk::{Circuit, ConstraintSystem, Error}, + plonk::{Any, Circuit, Column, ConstraintSystem, Error}, poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, }; use itertools::Itertools; -use maingate::MainGateInstructions; +use maingate::{MainGateInstructions, RegionCtx}; use snark_verifier::{ pcs::{ @@ -19,7 +19,7 @@ use snark_verifier::{ util::arithmetic::MultiMillerLoop, verifier::plonk::PlonkProtocol, }; -use std::{iter, marker::PhantomData, rc::Rc}; +use std::{marker::PhantomData, rc::Rc}; mod aggregation; @@ -27,6 +27,7 @@ mod aggregation; mod dev; #[cfg(test)] mod test; + #[cfg(feature = "test-circuits")] pub use self::RootCircuit as TestRootCircuit; @@ -42,6 +43,76 @@ pub use snark_verifier::{ system::halo2::{compile, transcript::evm::EvmTranscript, Config}, }; +const NUM_OF_SUPERCIRCUIT_INSTANCES: usize = 2; + +/// SuperCircuitInstance is to demystifying supercircuit instance to meaningful name. +#[derive(Clone)] +struct SuperCircuitInstance { + // chunk_ctx + pub chunk_index: T, + pub chunk_index_next: T, + pub total_chunk: T, + pub initial_rwc: T, + pub end_rwc: T, + + // pi circuit + pub pi_digest_lo: T, + pub pi_digest_hi: T, + + // state circuit + pub sc_permu_alpha: T, + pub sc_permu_gamma: T, + pub sc_rwtable_row_prev_fingerprint: T, + pub sc_rwtable_row_curr_fingerprint: T, + pub sc_rwtable_prev_fingerprint: T, + pub sc_rwtable_curr_fingerprint: T, + + // evm circuit + pub ec_permu_alpha: T, + pub ec_permu_gamma: T, + pub ec_rwtable_row_prev_fingerprint: T, + pub ec_rwtable_row_curr_fingerprint: T, + pub ec_rwtable_prev_fingerprint: T, + pub ec_rwtable_curr_fingerprint: T, +} + +impl SuperCircuitInstance { + /// Construct `SnarkInstance` with vector + pub fn new(instances: impl IntoIterator) -> Self { + let mut iter_instances = instances.into_iter(); + Self { + chunk_index: iter_instances.next().unwrap(), + total_chunk: iter_instances.next().unwrap(), + initial_rwc: iter_instances.next().unwrap(), + chunk_index_next: iter_instances.next().unwrap(), + end_rwc: iter_instances.next().unwrap(), + pi_digest_lo: iter_instances.next().unwrap(), + pi_digest_hi: iter_instances.next().unwrap(), + sc_permu_alpha: iter_instances.next().unwrap(), + sc_permu_gamma: iter_instances.next().unwrap(), + sc_rwtable_row_prev_fingerprint: iter_instances.next().unwrap(), + sc_rwtable_row_curr_fingerprint: iter_instances.next().unwrap(), + sc_rwtable_prev_fingerprint: iter_instances.next().unwrap(), + sc_rwtable_curr_fingerprint: iter_instances.next().unwrap(), + ec_permu_alpha: iter_instances.next().unwrap(), + ec_permu_gamma: iter_instances.next().unwrap(), + ec_rwtable_row_prev_fingerprint: iter_instances.next().unwrap(), + ec_rwtable_row_curr_fingerprint: iter_instances.next().unwrap(), + ec_rwtable_prev_fingerprint: iter_instances.next().unwrap(), + ec_rwtable_curr_fingerprint: iter_instances.next().unwrap(), + } + } +} + +/// UserChallange +#[derive(Clone)] +pub struct UserChallenge { + /// column_indexes + pub column_indexes: Vec>, + /// num_challenges + pub num_challenges: usize, +} + /// RootCircuit for aggregating SuperCircuit into a much smaller proof. #[derive(Clone)] pub struct RootCircuit<'a, M: MultiMillerLoop, As> @@ -49,8 +120,10 @@ where M::G1Affine: CurveAffine, { svk: KzgSvk, - snark: SnarkWitness<'a, M::G1Affine>, + protocol: &'a PlonkProtocol, + snark_witnesses: Vec>, instance: Vec, + user_challenges: Option<&'a UserChallenge>, _marker: PhantomData, } @@ -76,43 +149,63 @@ where /// proof and its instance. Returns `None` if given proof is invalid. pub fn new( params: &ParamsKZG, - super_circuit_protocol: &'a PlonkProtocol, - super_circuit_instances: Value<&'a Vec>>, - super_circuit_proof: Value<&'a [u8]>, + protocol: &'a PlonkProtocol, + snark_witnesses: Vec>, + user_challenges: Option<&'a UserChallenge>, ) -> Result { - let num_instances = super_circuit_protocol.num_instance.iter().sum::() + 4 * LIMBS; - let instance = { - let mut instance = Ok(vec![M::Fr::ZERO; num_instances]); - super_circuit_instances - .as_ref() - .zip(super_circuit_proof.as_ref()) - .map(|(super_circuit_instances, super_circuit_proof)| { - let snark = Snark::new( - super_circuit_protocol, - super_circuit_instances, - super_circuit_proof, - ); - instance = aggregate::(params, [snark]).map(|accumulator_limbs| { - iter::empty() - // Propagate `SuperCircuit`'s instance - .chain(super_circuit_instances.iter().flatten().cloned()) - // Output aggregated accumulator limbs - .chain(accumulator_limbs) - .collect_vec() - }); + let num_raw_instances = protocol.num_instance.iter().sum::(); + + // compute real instance value + let (flatten_first_chunk_instances, accumulator_limbs) = { + let (mut instance, mut accumulator_limbs) = ( + vec![M::Fr::ZERO; num_raw_instances], + Ok(vec![M::Fr::ZERO; 4 * LIMBS]), + ); + // compute aggregate_limbs + snark_witnesses + .iter() + .fold(Value::known(vec![]), |acc_snark, snark_witness| { + snark_witness + .instances + .as_ref() + .zip(snark_witness.proof.as_ref()) + .map(|(super_circuit_instances, super_circuit_proof)| { + Snark::new(protocol, super_circuit_instances, super_circuit_proof) + }) + .zip(acc_snark) + .map(|(snark, mut acc_snark)| { + acc_snark.push(snark); + acc_snark + }) + }) + .map(|snarks| { + if !snarks.is_empty() { + accumulator_limbs = aggregate::(params, snarks) + .map(|accumulator_limbs| accumulator_limbs.to_vec()); + } }); - instance? + + // retrieve first instance + if let Some(snark_witness) = snark_witnesses.first() { + snark_witness + .instances + .map(|instances| instance = instances.iter().flatten().cloned().collect_vec()); + } + + (instance, accumulator_limbs?) }; - debug_assert_eq!(instance.len(), num_instances); + + debug_assert_eq!(flatten_first_chunk_instances.len(), num_raw_instances); + let mut flatten_instance = + exposed_instances(&SuperCircuitInstance::new(flatten_first_chunk_instances)); + flatten_instance.extend(accumulator_limbs); Ok(Self { svk: KzgSvk::::new(params.get_g()[0]), - snark: SnarkWitness::new( - super_circuit_protocol, - super_circuit_instances, - super_circuit_proof, - ), - instance, + protocol, + snark_witnesses, + instance: flatten_instance, + user_challenges, _marker: PhantomData, }) } @@ -120,13 +213,15 @@ where /// Returns accumulator indices in instance columns, which will be in /// the last `4 * LIMBS` rows of instance column in `MainGate`. pub fn accumulator_indices(&self) -> Vec<(usize, usize)> { - let offset = self.snark.protocol().num_instance.iter().sum::(); - (offset..).map(|idx| (0, idx)).take(4 * LIMBS).collect() + (NUM_OF_SUPERCIRCUIT_INSTANCES..) + .map(|idx| (0, idx)) + .take(4 * LIMBS) + .collect() } /// Returns number of instance pub fn num_instance(&self) -> Vec { - vec![self.snark.protocol().num_instance.iter().sum::() + 4 * LIMBS] + vec![self.instance.len()] } /// Returns instance @@ -159,7 +254,13 @@ where fn without_witnesses(&self) -> Self { Self { svk: self.svk, - snark: self.snark.without_witnesses(), + protocol: self.protocol, + snark_witnesses: self + .snark_witnesses + .iter() + .map(|snark_witness| snark_witness.without_witnesses()) + .collect_vec(), + user_challenges: self.user_challenges, instance: vec![M::Fr::ZERO; self.instance.len()], _marker: PhantomData, } @@ -175,21 +276,197 @@ where mut layouter: impl Layouter, ) -> Result<(), Error> { config.load_table(&mut layouter)?; - let (instance, accumulator_limbs) = - config.aggregate::(&mut layouter, &self.svk, [self.snark])?; + let key = &self.svk; + let (instances, accumulator_limbs) = layouter.assign_region( + || "Aggregate snarks", + |mut region| { + config.named_column_in_region(&mut region); + let ctx = RegionCtx::new(region, 0); + let (loaded_instances, accumulator_limbs, loader, proofs) = + config.aggregate::(ctx, &key.clone(), &self.snark_witnesses)?; + + // aggregate user challenge for rwtable permutation challenge + let (alpha, gamma) = { + let (mut challenges, _) = config.aggregate_user_challenges::( + loader.clone(), + self.user_challenges, + proofs, + )?; + (challenges.remove(0), challenges.remove(0)) + }; + + // convert vector instances SuperCircuitInstance struct + let supercircuit_instances = loaded_instances + .iter() + .map(SuperCircuitInstance::new) + .collect::>>(); + + // constraint first and last chunk + supercircuit_instances + .first() + .zip(supercircuit_instances.last()) + .map(|(first_chunk, _last)| { + // `last.sc_rwtable_next_fingerprint == + // last.ec_rwtable_next_fingerprint` will be checked inside super circuit so + // here no need to checked + // Other field in last instances already be checked in chunk + // continuity + + // define 0, 1, total_chunk as constant + let (zero_const, one_const, total_chunk_const) = { + let zero_const = loader + .scalar_chip() + .assign_constant(&mut loader.ctx_mut(), M::Fr::from(0)) + .unwrap(); + let one_const = loader + .scalar_chip() + .assign_constant(&mut loader.ctx_mut(), M::Fr::from(1)) + .unwrap(); + let total_chunk_const = loader + .scalar_chip() + .assign_constant(&mut loader.ctx_mut(), M::Fr::from(1)) + .unwrap(); + + (zero_const, one_const, total_chunk_const) + }; + + // `first.sc_rwtable_row_prev_fingerprint == + // first.ec_rwtable_row_prev_fingerprint` will be checked inside circuit + vec![ + // chunk ctx + (first_chunk.chunk_index.assigned(), &zero_const), + (first_chunk.total_chunk.assigned(), &total_chunk_const), + // rwc + (first_chunk.initial_rwc.assigned(), &one_const), + // constraint permutation fingerprint + // challenge: alpha + (first_chunk.sc_permu_alpha.assigned(), &alpha.assigned()), + (first_chunk.ec_permu_alpha.assigned(), &alpha.assigned()), + // challenge: gamma + (first_chunk.sc_permu_gamma.assigned(), &gamma.assigned()), + (first_chunk.ec_permu_gamma.assigned(), &gamma.assigned()), + // fingerprint + ( + first_chunk.ec_rwtable_prev_fingerprint.assigned(), + &one_const, + ), + ( + first_chunk.sc_rwtable_prev_fingerprint.assigned(), + &one_const, + ), + ] + .iter() + .for_each(|(a, b)| { + loader + .scalar_chip() + .assert_equal(&mut loader.ctx_mut(), a, b) + .unwrap(); + }); + + first_chunk + }) + .expect("error"); + + // constraint consistency between chunk + supercircuit_instances.iter().tuple_windows().for_each( + |(instance_i, instance_i_plus_one)| { + vec![ + ( + instance_i.chunk_index_next.assigned(), + instance_i_plus_one.chunk_index.assigned(), + ), + ( + instance_i.total_chunk.assigned(), + instance_i_plus_one.total_chunk.assigned(), + ), + ( + instance_i.end_rwc.assigned(), + instance_i_plus_one.initial_rwc.assigned(), + ), + ( + instance_i.pi_digest_lo.assigned(), + instance_i_plus_one.pi_digest_lo.assigned(), + ), + ( + instance_i.pi_digest_hi.assigned(), + instance_i_plus_one.pi_digest_hi.assigned(), + ), + // state circuit + ( + instance_i.sc_permu_alpha.assigned(), + instance_i_plus_one.sc_permu_alpha.assigned(), + ), + ( + instance_i.sc_permu_gamma.assigned(), + instance_i_plus_one.sc_permu_gamma.assigned(), + ), + ( + instance_i.sc_rwtable_row_curr_fingerprint.assigned(), + instance_i_plus_one + .sc_rwtable_row_prev_fingerprint + .assigned(), + ), + ( + instance_i.sc_rwtable_curr_fingerprint.assigned(), + instance_i_plus_one.sc_rwtable_prev_fingerprint.assigned(), + ), + // evm circuit + ( + instance_i.ec_permu_alpha.assigned(), + instance_i_plus_one.ec_permu_alpha.assigned(), + ), + ( + instance_i.ec_permu_gamma.assigned(), + instance_i_plus_one.ec_permu_gamma.assigned(), + ), + ( + instance_i.ec_rwtable_curr_fingerprint.assigned(), + instance_i_plus_one.ec_rwtable_prev_fingerprint.assigned(), + ), + ( + instance_i.ec_rwtable_row_curr_fingerprint.assigned(), + instance_i_plus_one + .ec_rwtable_row_prev_fingerprint + .assigned(), + ), + ] + .iter() + .for_each(|(a, b)| { + loader + .scalar_chip() + .assert_equal(&mut loader.ctx_mut(), a, b) + .unwrap(); + }); + }, + ); + + Ok(( + exposed_instances(supercircuit_instances.first().unwrap()) + .iter() + .map(|instance| instance.assigned().to_owned()) + .collect_vec(), + accumulator_limbs, + )) + }, + )?; // Constrain equality to instance values let main_gate = config.main_gate(); - for (row, limb) in instance - .into_iter() - .flatten() - .flatten() - .chain(accumulator_limbs) - .enumerate() - { + for (row, limb) in instances.into_iter().chain(accumulator_limbs).enumerate() { main_gate.expose_public(layouter.namespace(|| ""), limb, row)?; } Ok(()) } } + +/// get instances to expose +fn exposed_instances(supercircuit_instances: &SuperCircuitInstance) -> Vec { + let instances = vec![ + // pi circuit + supercircuit_instances.pi_digest_lo, + supercircuit_instances.pi_digest_hi, + ]; + assert_eq!(NUM_OF_SUPERCIRCUIT_INSTANCES, instances.len()); + instances +} diff --git a/zkevm-circuits/src/root_circuit/aggregation.rs b/zkevm-circuits/src/root_circuit/aggregation.rs index f53ae78b26..90d161eda5 100644 --- a/zkevm-circuits/src/root_circuit/aggregation.rs +++ b/zkevm-circuits/src/root_circuit/aggregation.rs @@ -1,5 +1,5 @@ use eth_types::Field; -use halo2::halo2curves::CurveExt; +use halo2::{circuit::Region, halo2curves::CurveExt}; use halo2_proofs::{ circuit::{AssignedCell, Layouter, Value}, halo2curves::{ @@ -15,7 +15,7 @@ use snark_verifier::{ self, halo2::{ halo2_wrong_ecc::{self, integer::rns::Rns, maingate::*, EccConfig}, - Scalar, + EcPoint, Scalar, }, native::NativeLoader, }, @@ -24,11 +24,20 @@ use snark_verifier::{ PolynomialCommitmentScheme, }, system::halo2::transcript, - util::arithmetic::{fe_to_limbs, FromUniformBytes, MultiMillerLoop, PrimeField}, - verifier::{self, plonk::PlonkProtocol, SnarkVerifier}, + util::{ + arithmetic::{fe_to_limbs, FromUniformBytes, MultiMillerLoop, PrimeField}, + transcript::Transcript, + }, + verifier::{ + self, + plonk::{PlonkProof, PlonkProtocol}, + SnarkVerifier, + }, }; use std::{io, iter, rc::Rc}; +use super::UserChallenge; + /// Number of limbs to decompose a elliptic curve base field element into. pub const LIMBS: usize = 4; /// Number of bits of each decomposed limb. @@ -57,6 +66,8 @@ const R_P: usize = 60; pub type EccChip = halo2_wrong_ecc::BaseFieldEccChip; /// `Halo2Loader` with hardcoded `EccChip`. pub type Halo2Loader<'a, C> = loader::halo2::Halo2Loader<'a, C, EccChip>; +/// `LoadedScalar` with hardcoded `EccChip`. +pub type LoadedScalar<'a, C> = Scalar<'a, C, EccChip>; /// `PoseidonTranscript` with hardcoded parameter with 128-bits security. pub type PoseidonTranscript = transcript::halo2::PoseidonTranscript; @@ -97,9 +108,12 @@ impl<'a, C: CurveAffine> From> for SnarkWitness<'a, C> { /// SnarkWitness #[derive(Clone, Copy)] pub struct SnarkWitness<'a, C: CurveAffine> { - protocol: &'a PlonkProtocol, - instances: Value<&'a Vec>>, - proof: Value<&'a [u8]>, + /// protocol + pub protocol: &'a PlonkProtocol, + /// instance + pub instances: Value<&'a Vec>>, + /// proof + pub proof: Value<&'a [u8]>, } impl<'a, C: CurveAffine> SnarkWitness<'a, C> { @@ -203,19 +217,94 @@ impl AggregationConfig { self.range_chip().load_table(layouter) } + /// named columns in region + pub fn named_column_in_region(&self, region: &mut Region<'_, F>) { + // annotate advices columns of `main_gate`. We can't annotate fixed_columns of + // `main_gate` bcs there is no methods exported. + for (i, col) in self.main_gate_config.advices().iter().enumerate() { + region.name_column(|| format!("ROOT_main_gate_{}", i), *col); + } + } + + /// Aggregate witnesses into challenge and return + #[allow(clippy::type_complexity)] + pub fn aggregate_user_challenges<'c, M, As>( + &self, + loader: Rc>, + user_challenges: Option<&UserChallenge>, + proofs: Vec>, As>>, + ) -> Result< + ( + Vec>, + Vec>>, + ), + Error, + > + where + M: MultiMillerLoop, + M::Fr: Field, + M::G1Affine: CurveAffine, + for<'b> As: PolynomialCommitmentScheme< + M::G1Affine, + Rc>, + VerifyingKey = KzgSvk, + Output = KzgAccumulator>>, + > + AccumulationScheme< + M::G1Affine, + Rc>, + Accumulator = KzgAccumulator>>, + VerifyingKey = KzgAsVerifyingKey, + >, + { + type PoseidonTranscript<'a, C, S> = + transcript::halo2::PoseidonTranscript>, S, T, RATE, R_F, R_P>; + + let witnesses = proofs + .iter() + .flat_map(|proof| { + user_challenges + .map(|user_challenges| { + user_challenges + .column_indexes + .iter() + .map(|col| &proof.witnesses[col.index()]) + .collect::>() + }) + .unwrap_or_default() + }) + .collect_vec(); + + let empty_proof = Vec::new(); + let mut transcript = PoseidonTranscript::new(&loader, Value::known(empty_proof.as_slice())); + witnesses.iter().for_each(|rwtable_col_ec_point| { + transcript.common_ec_point(rwtable_col_ec_point).unwrap(); + }); + let num_challenges = user_challenges + .map(|user_challenges| user_challenges.num_challenges) + .unwrap_or_default(); + + let witnesses = witnesses + .into_iter() + .cloned() + .collect::>>>(); + Ok((transcript.squeeze_n_challenges(num_challenges), witnesses)) + } + /// Aggregate snarks into a single accumulator and decompose it into /// `4 * LIMBS` limbs. /// Returns assigned instances of snarks and aggregated accumulator limbs. #[allow(clippy::type_complexity)] - pub fn aggregate<'a, M, As>( + pub fn aggregate<'a, 'c, M, As>( &self, - layouter: &mut impl Layouter, + ctx: RegionCtx<'c, M::Fr>, svk: &KzgSvk, - snarks: impl IntoIterator>, + snarks: &[SnarkWitness<'a, M::G1Affine>], ) -> Result< ( - Vec>>>, + Vec>>>, Vec>, + Rc>, + Vec>, As>>, ), Error, > @@ -235,92 +324,84 @@ impl AggregationConfig { VerifyingKey = KzgAsVerifyingKey, >, { + let ecc_chip = self.ecc_chip::(); + let loader = Halo2Loader::<'c, _>::new(ecc_chip, ctx); + type PoseidonTranscript<'a, C, S> = transcript::halo2::PoseidonTranscript>, S, T, RATE, R_F, R_P>; - let snarks = snarks.into_iter().collect_vec(); - layouter.assign_region( - || "Aggregate snarks", - |mut region| { - // annotate advices columns of `main_gate`. We can't annotate fixed_columns of - // `main_gate` bcs there is no methods exported. - for (i, col) in self.main_gate_config.advices().iter().enumerate() { - region.name_column(|| format!("ROOT_main_gate_{}", i), *col); - } - - let ctx = RegionCtx::new(region, 0); - let ecc_chip = self.ecc_chip::(); - let loader = Halo2Loader::new(ecc_chip, ctx); - - // Verify the cheap part and get accumulator (left-hand and right-hand side of - // pairing) of individual proof. - let (instances, accumulators) = snarks - .iter() - .map(|snark| { - let protocol = snark.protocol.loaded(&loader); - let instances = snark.loaded_instances(&loader); - let mut transcript = PoseidonTranscript::new(&loader, snark.proof()); - let proof = PlonkSuccinctVerifier::::read_proof( - svk, - &protocol, - &instances, - &mut transcript, - ) - .unwrap(); - let accumulators = - PlonkSuccinctVerifier::verify(svk, &protocol, &instances, &proof) - .unwrap(); - (instances, accumulators) - }) - .collect_vec() - .into_iter() - .unzip::<_, _, Vec<_>, Vec<_>>(); - - // Verify proof for accumulation of all accumulators into new one. - let accumulator = { - let as_vk = Default::default(); - let as_proof = Vec::new(); - let accumulators = accumulators.into_iter().flatten().collect_vec(); - let mut transcript = - PoseidonTranscript::new(&loader, Value::known(as_proof.as_slice())); - let proof = >::read_proof( - &as_vk, - &accumulators, - &mut transcript, - ) - .unwrap(); - >::verify(&as_vk, &accumulators, &proof).unwrap() - }; + let mut plonk_svp = vec![]; + // Verify the cheap part and get accumulator (left-hand and right-hand side of + // pairing) of individual proof. + // Verify the cheap part and get accumulator (left-hand and right-hand side of + // pairing) of individual proof. + let (instances, accumulators) = snarks + .iter() + .map(|snark| { + let protocol = snark.protocol.loaded(&loader); + + let instances = snark.loaded_instances(&loader); + let mut transcript = PoseidonTranscript::new(&loader, snark.proof()); + let proof = PlonkSuccinctVerifier::::read_proof( + svk, + &protocol, + &instances, + &mut transcript, + ) + .unwrap(); + let accumulators = + PlonkSuccinctVerifier::verify(svk, &protocol, &instances, &proof).unwrap(); + plonk_svp.push(proof); + (instances, accumulators) + }) + .collect_vec() + .into_iter() + .unzip::<_, _, Vec<_>, Vec<_>>(); + + // Verify proof for accumulation of all accumulators into new one. + let accumulator = { + let as_vk = Default::default(); + let as_proof = Vec::new(); + let accumulators = accumulators.into_iter().flatten().collect_vec(); + let mut transcript = + PoseidonTranscript::new(&loader, Value::known(as_proof.as_slice())); + let proof = >::read_proof( + &as_vk, + &accumulators, + &mut transcript, + ) + .unwrap(); + >::verify(&as_vk, &accumulators, &proof).unwrap() + }; - let instances = instances + let instances = instances + .iter() + .map(|instances| { + instances .iter() - .map(|instances| { + .flat_map(|instances| { instances .iter() - .map(|instances| { - instances - .iter() - .map(|instance| instance.assigned().to_owned()) - .collect_vec() - }) + .map(|instance| instance.to_owned()) .collect_vec() }) - .collect_vec(); - let accumulator_limbs = [accumulator.lhs, accumulator.rhs] - .iter() - .map(|ec_point| { - loader - .ecc_chip() - .assign_ec_point_to_limbs(&mut loader.ctx_mut(), ec_point.assigned()) - }) - .collect::, Error>>()? - .into_iter() - .flatten() - .collect(); + .collect_vec() + }) + .collect_vec(); + + let accumulator_limbs = [accumulator.lhs, accumulator.rhs] + .iter() + .map(|ec_point| { + loader + .ecc_chip() + .assign_ec_point_to_limbs(&mut loader.ctx_mut(), ec_point.assigned()) + }) + .collect::, Error>>()? + .into_iter() + .flatten() + .collect(); - Ok((instances, accumulator_limbs)) - }, - ) + Ok((instances, accumulator_limbs, loader, plonk_svp)) } } @@ -625,6 +706,7 @@ pub mod test { let aggregation = TestAggregationCircuit::>::new( ¶ms, snarks.iter().map(SnarkOwned::as_snark), + None, ) .unwrap(); let instances = aggregation.instances(); @@ -645,6 +727,7 @@ pub mod test { let aggregation = TestAggregationCircuit::>::new( ¶ms, snarks.iter().map(SnarkOwned::as_snark), + None, ) .unwrap(); let mut instances = aggregation.instances(); diff --git a/zkevm-circuits/src/root_circuit/dev.rs b/zkevm-circuits/src/root_circuit/dev.rs index a2e82a7a68..3124540523 100644 --- a/zkevm-circuits/src/root_circuit/dev.rs +++ b/zkevm-circuits/src/root_circuit/dev.rs @@ -1,13 +1,15 @@ -use super::{aggregate, AggregationConfig, Halo2Loader, KzgSvk, Snark, SnarkWitness, LIMBS}; +use super::{ + aggregate, AggregationConfig, Halo2Loader, KzgSvk, Snark, SnarkWitness, UserChallenge, LIMBS, +}; use eth_types::Field; use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, + circuit::{Layouter, SimpleFloorPlanner, Value}, halo2curves::{ff::Field as Halo2Field, serde::SerdeObject, CurveAffine, CurveExt}, - plonk::{Circuit, ConstraintSystem, Error}, + plonk::{Circuit, ConstraintSystem, Error, Error::InvalidInstances}, poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, }; use itertools::Itertools; -use maingate::MainGateInstructions; +use maingate::{MainGateInstructions, RegionCtx}; use snark_verifier::{ loader::native::NativeLoader, pcs::{ @@ -20,12 +22,14 @@ use std::{iter, marker::PhantomData, rc::Rc}; /// Aggregation circuit for testing purpose. #[derive(Clone)] +#[allow(clippy::type_complexity)] pub struct TestAggregationCircuit<'a, M: MultiMillerLoop, As> where M::G1Affine: CurveAffine, { svk: KzgSvk, snarks: Vec>, + user_challenge: Option<(UserChallenge, Vec, Vec)>, instances: Vec, _marker: PhantomData, } @@ -50,9 +54,11 @@ where { /// Create an Aggregation circuit with aggregated accumulator computed. /// Returns `None` if any given snark is invalid. + #[allow(clippy::type_complexity)] pub fn new( params: &ParamsKZG, snarks: impl IntoIterator>, + user_challenge: Option<(UserChallenge, Vec, Vec)>, ) -> Result { let snarks = snarks.into_iter().collect_vec(); @@ -71,6 +77,7 @@ where Ok(Self { svk: KzgSvk::::new(params.get_g()[0]), + user_challenge, snarks: snarks.into_iter().map_into().collect(), instances, _marker: PhantomData, @@ -120,6 +127,7 @@ where fn without_witnesses(&self) -> Self { Self { svk: self.svk, + user_challenge: self.user_challenge.clone(), snarks: self .snarks .iter() @@ -140,18 +148,84 @@ where mut layouter: impl Layouter, ) -> Result<(), Error> { config.load_table(&mut layouter)?; - let (instances, accumulator_limbs) = - config.aggregate::(&mut layouter, &self.svk, self.snarks.clone())?; + + let (instances, accumulator_limbs) = layouter.assign_region( + || "Aggregate snarks", + |mut region| { + config.named_column_in_region(&mut region); + let ctx = RegionCtx::new(region, 0); + let (instances, accumulator_limbs, loader, proofs) = + config.aggregate::(ctx, &self.svk, &self.snarks)?; + + // aggregate user challenge for rwtable permutation challenge + let user_challenge = self + .user_challenge + .as_ref() + .map(|(challenge, _, _)| challenge); + let (challenges, commitments) = config.aggregate_user_challenges::( + loader.clone(), + user_challenge, + proofs, + )?; + if !challenges.is_empty() { + let Some((_, expected_commitments, expected_challenges)) = + self.user_challenge.as_ref() + else { + return Err(InvalidInstances); + }; + // check commitment equality + let expected_commitments_loaded = expected_commitments + .iter() + .map(|expected_commitment| { + loader.ecc_chip().assign_point( + &mut loader.ctx_mut(), + Value::known(*expected_commitment), + ) + }) + .collect::, Error>>()?; + expected_commitments_loaded + .iter() + .zip(commitments.iter()) + .try_for_each(|(expected_commitment, commitment)| { + loader.ecc_chip().assert_equal( + &mut loader.ctx_mut(), + expected_commitment, + &commitment.assigned(), + ) + })?; + + // check challenge equality + let expected_challenges_loaded = expected_challenges + .iter() + .map(|value| loader.assign_scalar(Value::known(*value))) + .collect::>(); + expected_challenges_loaded + .iter() + .zip(challenges.iter()) + .try_for_each(|(expected_challenge, challenge)| { + loader.scalar_chip().assert_equal( + &mut loader.ctx_mut(), + &expected_challenge.assigned(), + &challenge.assigned(), + ) + })?; + } + + let instances = instances + .iter() + .flat_map(|instances| { + instances + .iter() + .map(|instance| instance.assigned().to_owned()) + }) + .collect_vec(); + Ok((instances, accumulator_limbs)) + }, + )?; // Constrain equality to instance values let main_gate = config.main_gate(); - for (row, limb) in instances - .into_iter() - .flatten() - .flatten() - .chain(accumulator_limbs) - .enumerate() - { + for (row, limb) in instances.into_iter().chain(accumulator_limbs).enumerate() { main_gate.expose_public(layouter.namespace(|| ""), limb, row)?; } diff --git a/zkevm-circuits/src/root_circuit/test.rs b/zkevm-circuits/src/root_circuit/test.rs index de87bd58e1..b938c0a9a3 100644 --- a/zkevm-circuits/src/root_circuit/test.rs +++ b/zkevm-circuits/src/root_circuit/test.rs @@ -1,74 +1,362 @@ +use std::iter; + use crate::{ - root_circuit::{compile, Config, Gwc, PoseidonTranscript, RootCircuit}, + root_circuit::{ + aggregation::test::SnarkOwned, compile, Config, Gwc, PoseidonTranscript, RootCircuit, + SnarkWitness, TestAggregationCircuit, UserChallenge, + }, super_circuit::{test::block_1tx, SuperCircuit}, + table::{ + self, rw_table::get_rwtable_cols_commitment, AccountFieldTag, LookupTable, TxLogFieldTag, + }, + witness::Rw, }; use bus_mapping::circuit_input_builder::FixedCParams; +use eth_types::{address, Address, Field, U256}; +use gadgets::util::Expr; use halo2_proofs::{ - circuit::Value, + circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, - halo2curves::bn256::Bn256, - plonk::{create_proof, keygen_pk, keygen_vk}, - poly::kzg::{ - commitment::{KZGCommitmentScheme, ParamsKZG}, - multiopen::ProverGWC, + halo2curves::{bn256::Bn256, pairing::Engine}, + plonk::{create_proof, keygen_pk, keygen_vk, Circuit, ConstraintSystem, Error, Selector}, + poly::{ + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::ProverGWC, + }, + Rotation, }, }; use itertools::Itertools; use rand::rngs::OsRng; +use snark_verifier::util::transcript::Transcript; +use table::RwTable; + +struct RwTableCircuit<'a> { + rws: &'a [Rw], + n_rows: usize, + prev_chunk_last_rw: Option, +} + +impl<'a> RwTableCircuit<'a> { + #[allow(dead_code)] + pub(crate) fn new(rws: &'a [Rw], n_rows: usize, prev_chunk_last_rw: Option) -> Self { + Self { + rws, + n_rows, + prev_chunk_last_rw, + } + } +} + +#[derive(Clone)] +pub(crate) struct RwTableCircuitConfig { + pub rw_table: RwTable, + pub enable: Selector, +} + +impl RwTableCircuitConfig {} + +impl<'a, F: Field> Circuit for RwTableCircuit<'a> { + type Config = RwTableCircuitConfig; + + type FloorPlanner = SimpleFloorPlanner; + + type Params = (); + + fn without_witnesses(&self) -> Self { + todo!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let rw_table = RwTable::construct(meta); + let enable = meta.selector(); + + meta.create_gate("dummy id 1", |meta| { + let dummy = meta.query_advice(rw_table.id, Rotation::cur()); + let enable = meta.query_selector(enable); + + vec![ + enable * dummy.clone() * dummy.clone() * dummy.clone() * (dummy.clone() - 1.expr()), + ] + }); + RwTableCircuitConfig { rw_table, enable } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "XXXX", + |mut region| { + let _ = config.rw_table.load_with_region( + &mut region, + self.rws, + self.n_rows, + self.prev_chunk_last_rw, + ); + config.enable.enable(&mut region, 0)?; + Ok(()) + }, + )?; + Ok(()) + } +} + +#[test] +fn test_user_challenge_aggregation() { + let num_challenges = 1; + let k = 12; + let rows = vec![ + Rw::Stack { + rw_counter: 9, + is_write: true, + call_id: 3, + stack_pointer: 100, + value: U256::MAX - 1, + }, + Rw::Stack { + rw_counter: 13, + is_write: true, + call_id: 3, + stack_pointer: 102, + value: U256::MAX - 1, + }, + Rw::Stack { + rw_counter: 1, + is_write: true, + call_id: 1, + stack_pointer: 1023, + value: U256::MAX - 1, + }, + Rw::TxLog { + rw_counter: 2, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Address, + index: 0usize, + value: U256::MAX - 1, + }, + Rw::TxLog { + rw_counter: 3, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Topic, + index: 0usize, + value: U256::MAX - 1, + }, + Rw::TxLog { + rw_counter: 4, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Topic, + index: 1usize, + value: U256::MAX - 1, + }, + Rw::TxLog { + rw_counter: 5, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Data, + index: 10usize, + value: U256::MAX - 1, + }, + Rw::TxLog { + rw_counter: 6, + is_write: true, + tx_id: 1, + log_id: 1, + field_tag: TxLogFieldTag::Data, + index: 1usize, + value: U256::MAX - 1, + }, + Rw::Account { + rw_counter: 1, + is_write: false, + account_address: address!("0x000000000000000000000000000000000cafe002"), + field_tag: AccountFieldTag::CodeHash, + value: U256::MAX - 1, + value_prev: U256::MAX - 1, + }, + Rw::AccountStorage { + rw_counter: 1, + is_write: false, + account_address: Address::default(), + storage_key: U256::MAX - 1, + value: U256::MAX - 1, + value_prev: U256::MAX - 1, + tx_id: 4, + committed_value: U256::MAX - 1, + }, + ]; + + let mut cs = ConstraintSystem::<::Fr>::default(); + let config = RwTableCircuit::configure(&mut cs); + let rwtable_columns = + ::Fr>>::columns(&config.rw_table); + + let params = ParamsKZG::::setup(k, OsRng); + let advice_commitments = get_rwtable_cols_commitment::>( + k.try_into().unwrap(), + &rows, + rows.len() + 1, + ¶ms, + ); + let mut transcript = PoseidonTranscript::new(Vec::::new()); + advice_commitments.iter().for_each(|commit| { + transcript.common_ec_point(commit).unwrap(); + }); + let expected_challenges = transcript.squeeze_n_challenges(num_challenges); + + let circuits = iter::repeat_with(|| RwTableCircuit::new(&rows, rows.len() + 1, None)) + .take(1) + .collect_vec(); + + let pk = keygen_pk( + ¶ms, + keygen_vk(¶ms, &circuits[0]).unwrap(), + &circuits[0], + ) + .unwrap(); + let protocol = compile( + ¶ms, + pk.get_vk(), + Config::kzg().with_num_instance(vec![0]), + ); + // Create proof + let proofs: Vec> = circuits + .into_iter() + .map(|circuit| { + let mut transcript = PoseidonTranscript::new(Vec::new()); + // Create proof + create_proof::, ProverGWC<_>, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[&[]], + OsRng, + &mut transcript, + ) + .unwrap(); + transcript.finalize() + }) + .collect(); + let user_challenge = UserChallenge { + column_indexes: rwtable_columns, + num_challenges, + }; + let snark_witnesses: Vec<_> = proofs + .into_iter() + .map(|proof| SnarkOwned::new(protocol.clone(), vec![vec![]], proof)) + .collect(); + let aggregation = TestAggregationCircuit::>::new( + ¶ms, + snark_witnesses.iter().map(SnarkOwned::as_snark), + Some((user_challenge, advice_commitments, expected_challenges)), + // None, + ) + .unwrap(); + + let instances = aggregation.instances(); + assert_eq!( + MockProver::run(21, &aggregation, instances) + .unwrap() + .verify(), + Ok(()) + ); +} #[ignore = "Due to high memory requirement"] #[test] -fn test_root_circuit() { - let (params, protocol, proof, instance) = { +fn test_root_circuit_multiple_chunk() { + let (params, protocol, proofs, instances, rwtable_columns) = { // Preprocess const TEST_MOCK_RANDOMNESS: u64 = 0x100; let circuits_params = FixedCParams { + total_chunks: 3, max_txs: 1, max_withdrawals: 5, max_calldata: 32, - max_rws: 256, + max_rws: 100, max_copy_rows: 256, max_exp_steps: 256, max_bytecode: 512, - max_evm_rows: 0, + max_evm_rows: 1 << 12, max_keccak_rows: 0, max_vertical_circuit_rows: 0, }; - let (k, circuit, instance, _) = + let (k, circuits, instances, _) = SuperCircuit::<_>::build(block_1tx(), circuits_params, TEST_MOCK_RANDOMNESS.into()) .unwrap(); + assert!(!circuits.is_empty()); + assert!(circuits.len() == instances.len()); + + // get chronological_rwtable and byaddr_rwtable columns index + let mut cs = ConstraintSystem::default(); + let config = SuperCircuit::configure_with_params(&mut cs, circuits[0].params()); + let rwtable_columns = config.get_rwtable_columns(); + let params = ParamsKZG::::setup(k, OsRng); - let pk = keygen_pk(¶ms, keygen_vk(¶ms, &circuit).unwrap(), &circuit).unwrap(); + let pk = keygen_pk( + ¶ms, + keygen_vk(¶ms, &circuits[0]).unwrap(), + &circuits[0], + ) + .unwrap(); let protocol = compile( ¶ms, pk.get_vk(), Config::kzg() - .with_num_instance(instance.iter().map(|instance| instance.len()).collect()), + .with_num_instance(instances[0].iter().map(|instance| instance.len()).collect()), ); - // Create proof - let proof = { - let mut transcript = PoseidonTranscript::new(Vec::new()); - create_proof::, ProverGWC<_>, _, _, _, _>( - ¶ms, - &pk, - &[circuit], - &[&instance.iter().map(Vec::as_slice).collect_vec()], - OsRng, - &mut transcript, - ) - .unwrap(); - transcript.finalize() - }; + let proofs: Vec> = circuits + .into_iter() + .zip(instances.iter()) + .map(|(circuit, instance)| { + // Create proof + let proof = { + let mut transcript = PoseidonTranscript::new(Vec::new()); + create_proof::, ProverGWC<_>, _, _, _, _>( + ¶ms, + &pk, + &[circuit], + &[&instance.iter().map(Vec::as_slice).collect_vec()], + OsRng, + &mut transcript, + ) + .unwrap(); + transcript.finalize() + }; + proof + }) + .collect(); + (params, protocol, proofs, instances, rwtable_columns) + }; - (params, protocol, proof, instance) + let user_challenge = UserChallenge { + column_indexes: rwtable_columns, + num_challenges: 2, // alpha, gamma }; + let snark_witnesses: Vec<_> = proofs + .iter() + .zip(instances.iter()) + .map(|(proof, instance)| { + SnarkWitness::new(&protocol, Value::known(instance), Value::known(proof)) + }) + .collect(); let root_circuit = RootCircuit::>::new( ¶ms, &protocol, - Value::known(&instance), - Value::known(&proof), + snark_witnesses, + Some(&user_challenge), ) .unwrap(); assert_eq!( diff --git a/zkevm-circuits/src/sig_circuit.rs b/zkevm-circuits/src/sig_circuit.rs index 7cb3883815..403d784fde 100644 --- a/zkevm-circuits/src/sig_circuit.rs +++ b/zkevm-circuits/src/sig_circuit.rs @@ -199,8 +199,8 @@ pub struct SigCircuit { impl SubCircuit for SigCircuit { type Config = SigCircuitConfig; - fn new_from_block(block: &crate::witness::Block) -> Self { - assert!(block.circuits_params.max_txs <= MAX_NUM_SIG); + fn new_from_block(block: &crate::witness::Block, chunk: &crate::witness::Chunk) -> Self { + assert!(chunk.fixed_param.max_txs <= MAX_NUM_SIG); SigCircuit { max_verif: MAX_NUM_SIG, @@ -235,11 +235,14 @@ impl SubCircuit for SigCircuit // Since sig circuit / halo2-lib use veticle cell assignment, // so the returned pair is consisted of same values - fn min_num_rows_block(block: &crate::witness::Block) -> (usize, usize) { - let row_num = if block.circuits_params.max_vertical_circuit_rows == 0 { + fn min_num_rows_block( + block: &crate::witness::Block, + chunk: &crate::witness::Chunk, + ) -> (usize, usize) { + let row_num = if chunk.fixed_param.max_vertical_circuit_rows == 0 { Self::min_num_rows() } else { - block.circuits_params.max_vertical_circuit_rows + chunk.fixed_param.max_vertical_circuit_rows }; let ecdsa_verif_count = diff --git a/zkevm-circuits/src/state_circuit.rs b/zkevm-circuits/src/state_circuit.rs index 141eab0e51..74b8f1f4cb 100644 --- a/zkevm-circuits/src/state_circuit.rs +++ b/zkevm-circuits/src/state_circuit.rs @@ -20,19 +20,24 @@ use self::{ use crate::{ table::{AccountFieldTag, LookupTable, MPTProofType, MptTable, RwTable, UXTable}, util::{word::WordLoHi, Challenges, Expr, SubCircuit, SubCircuitConfig}, - witness::{self, MptUpdates, Rw, RwMap}, + witness::{ + self, + rw::{RwFingerprints, ToVec}, + Chunk, MptUpdates, Rw, RwMap, + }, }; use constraint_builder::{ConstraintBuilder, Queries}; use eth_types::{Address, Field, Word}; use gadgets::{ batched_is_zero::{BatchedIsZeroChip, BatchedIsZeroConfig}, binary_number::{BinaryNumberBits, BinaryNumberChip, BinaryNumberConfig}, + permutation::{PermutationChip, PermutationChipConfig}, }; use halo2_proofs::{ circuit::{Layouter, Region, Value}, plonk::{ - Advice, Column, ConstraintSystem, Error, Expression, FirstPhase, Fixed, SecondPhase, - VirtualCells, + Advice, Column, ConstraintSystem, Error, Expression, FirstPhase, Fixed, Instance, + SecondPhase, VirtualCells, }, poly::Rotation, }; @@ -51,7 +56,8 @@ pub struct StateCircuitConfig { // Figure out why you get errors when this is Selector. selector: Column, // https://github.com/privacy-scaling-explorations/zkevm-circuits/issues/407 - rw_table: RwTable, + /// rw table + pub rw_table: RwTable, sort_keys: SortKeysConfig, // Assigned value at the start of the block. For Rw::Account and // Rw::AccountStorage rows this is the committed value in the MPT, for @@ -69,6 +75,13 @@ pub struct StateCircuitConfig { lookups: LookupsConfig, // External tables mpt_table: MptTable, + + /// rw permutation config + pub rw_permutation_config: PermutationChipConfig, + + // pi for chunk context continuity + pi_chunk_continuity: Column, + _marker: PhantomData, } @@ -150,6 +163,11 @@ impl SubCircuitConfig for StateCircuitConfig { let lexicographic_ordering = LexicographicOrderingConfig::configure(meta, sort_keys, lookups, power_of_randomness); + let rw_permutation_config = PermutationChip::configure( + meta, + >::advice_columns(&rw_table), + ); + // annotate columns rw_table.annotate_columns(meta); mpt_table.annotate_columns(meta); @@ -157,6 +175,9 @@ impl SubCircuitConfig for StateCircuitConfig { u10_table.annotate_columns(meta); u16_table.annotate_columns(meta); + let pi_chunk_continuity = meta.instance_column(); + meta.enable_equality(pi_chunk_continuity); + let config = Self { selector, sort_keys, @@ -169,6 +190,8 @@ impl SubCircuitConfig for StateCircuitConfig { lookups, rw_table, mpt_table, + rw_permutation_config, + pi_chunk_continuity, _marker: PhantomData, }; @@ -178,9 +201,7 @@ impl SubCircuitConfig for StateCircuitConfig { constraint_builder.build(&queries); constraint_builder.gate(queries.selector) }); - for (name, lookup) in constraint_builder.lookups() { - meta.lookup_any(name, |_| lookup); - } + constraint_builder.lookups(meta, config.selector); config } @@ -198,11 +219,14 @@ impl StateCircuitConfig { layouter: &mut impl Layouter, rows: &[Rw], n_rows: usize, // 0 means dynamically calculated from `rows`. + prev_chunk_last_rw: Option, ) -> Result<(), Error> { let updates = MptUpdates::mock_from(rows); layouter.assign_region( || "state circuit", - |mut region| self.assign_with_region(&mut region, rows, &updates, n_rows), + |mut region| { + self.assign_with_region(&mut region, rows, &updates, n_rows, prev_chunk_last_rw) + }, ) } @@ -212,10 +236,12 @@ impl StateCircuitConfig { rows: &[Rw], updates: &MptUpdates, n_rows: usize, // 0 means dynamically calculated from `rows`. + prev_chunk_last_rw: Option, ) -> Result<(), Error> { let tag_chip = BinaryNumberChip::construct(self.sort_keys.tag); - let (rows, padding_length) = RwMap::table_assignments_prepad(rows, n_rows); + let (rows, padding_length) = + RwMap::table_assignments_padding(rows, n_rows, prev_chunk_last_rw); let rows_len = rows.len(); let mut state_root = updates.old_root(); @@ -228,11 +254,12 @@ impl StateCircuitConfig { log::trace!("state circuit assign offset:{} row:{:#?}", offset, row); } + // disable selector on offset 0 since it will be copy constraints by public input region.assign_fixed( || "selector", self.selector, offset, - || Value::known(F::ONE), + || Value::known(if offset == 0 { F::ZERO } else { F::ONE }), )?; tag_chip.assign(region, offset, &row.tag())?; @@ -399,6 +426,7 @@ impl StateCircuitConfig { region.name_column(|| "STATE_mpt_proof_type", self.mpt_proof_type); region.name_column(|| "STATE_state_root lo", self.state_root.lo()); region.name_column(|| "STATE_state_root hi", self.state_root.hi()); + region.name_column(|| "STATE_pi_chunk_continuity", self.pi_chunk_continuity); } } @@ -427,27 +455,43 @@ impl SortKeysConfig { /// State Circuit for proving RwTable is valid #[derive(Default, Clone, Debug)] -pub struct StateCircuit { +pub struct StateCircuit { /// Rw rows pub rows: Vec, + #[cfg(test)] + row_padding_and_overrides: Vec>>, updates: MptUpdates, pub(crate) n_rows: usize, #[cfg(test)] overrides: HashMap<(dev::AdviceColumn, isize), F>, + + /// permutation challenge + permu_alpha: F, + permu_gamma: F, + rw_fingerprints: RwFingerprints, + + prev_chunk_last_rw: Option, + _marker: PhantomData, } impl StateCircuit { /// make a new state circuit from an RwMap - pub fn new(rw_map: RwMap, n_rows: usize) -> Self { - let rows = rw_map.table_assignments(); + pub fn new(chunk: &Chunk) -> Self { + let rows = chunk.by_address_rws.table_assignments(false); // address sorted let updates = MptUpdates::mock_from(&rows); Self { rows, + #[cfg(test)] + row_padding_and_overrides: Default::default(), updates, - n_rows, + n_rows: chunk.fixed_param.max_rws, #[cfg(test)] overrides: HashMap::new(), + permu_alpha: chunk.permu_alpha, + permu_gamma: chunk.permu_gamma, + rw_fingerprints: chunk.by_address_rw_fingerprints.clone(), + prev_chunk_last_rw: chunk.prev_chunk_last_by_address_rw, _marker: PhantomData, } } @@ -456,8 +500,8 @@ impl StateCircuit { impl SubCircuit for StateCircuit { type Config = StateCircuitConfig; - fn new_from_block(block: &witness::Block) -> Self { - Self::new(block.rws.clone(), block.circuits_params.max_rws) + fn new_from_block(_block: &witness::Block, chunk: &Chunk) -> Self { + Self::new(chunk) } fn unusable_rows() -> usize { @@ -467,10 +511,10 @@ impl SubCircuit for StateCircuit { } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + fn min_num_rows_block(_block: &witness::Block, chunk: &Chunk) -> (usize, usize) { ( - block.rws.0.values().flatten().count() + 1, - block.circuits_params.max_rws, + chunk.by_address_rws.0.values().flatten().count() + 1, + chunk.fixed_param.max_rws, ) } @@ -486,21 +530,67 @@ impl SubCircuit for StateCircuit { // Assigning to same columns in different regions should be avoided. // Here we use one single region to assign `overrides` to both rw table and // other parts. - layouter.assign_region( + let ( + alpha_cell, + gamma_cell, + row_fingerprints_prev_cell, + row_fingerprints_next_cell, + acc_fingerprints_prev_cell, + acc_fingerprints_next_cell, + ) = layouter.assign_region( || "state circuit", |mut region| { - config - .rw_table - .load_with_region(&mut region, &self.rows, self.n_rows)?; + // TODO optimise RwMap::table_assignments_prepad calls from 3 times -> 1 + let padded_rows = config.rw_table.load_with_region( + &mut region, + &self.rows, + self.n_rows, + self.prev_chunk_last_rw, + )?; + + config.assign_with_region( + &mut region, + &self.rows, + &self.updates, + self.n_rows, + self.prev_chunk_last_rw, + )?; - config.assign_with_region(&mut region, &self.rows, &self.updates, self.n_rows)?; + // permu_next_continuous_fingerprint and rows override for negative-test + #[allow(unused_assignments, unused_mut)] + let rows = if cfg!(test) { + let mut row_padding_and_overridess = None; + // NOTE need wrap in cfg(test) block even already under if cfg!(test) to make + // compiler happy + #[cfg(test)] + { + row_padding_and_overridess = if self.row_padding_and_overrides.is_empty() { + debug_assert!( + self.overrides.is_empty(), + "overrides size > 0 but row_padding_and_overridess = 0" + ); + Some(padded_rows.to2dvec()) + } else { + Some(self.row_padding_and_overrides.clone()) + }; + } + row_padding_and_overridess.unwrap() + } else { + padded_rows.to2dvec() + }; + let permutation_cells = config.rw_permutation_config.assign( + &mut region, + Value::known(self.permu_alpha), + Value::known(self.permu_gamma), + Value::known(self.rw_fingerprints.prev_mul_acc), + &rows, + "state_circuit", + )?; #[cfg(test)] { - let first_non_padding_index = if self.rows.len() < self.n_rows { - RwMap::padding_len(self.rows.len(), self.n_rows) - } else { - 1 // at least 1 StartOp padding in idx 0, so idx 1 is first non-padding row - }; + // we already handle rw_table override for negative test + // below is to support override value other than rw_table + let first_non_padding_index = 1; for ((column, row_offset), &f) in &self.overrides { let advice_column = column.value(config); @@ -517,14 +607,35 @@ impl SubCircuit for StateCircuit { } } - Ok(()) + Ok(permutation_cells) }, - ) + )?; + // constrain permutation challenges + [ + alpha_cell, + gamma_cell, + row_fingerprints_prev_cell, + row_fingerprints_next_cell, + acc_fingerprints_prev_cell, + acc_fingerprints_next_cell, + ] + .iter() + .enumerate() + .try_for_each(|(i, cell)| { + layouter.constrain_instance(cell.cell(), config.pi_chunk_continuity, i) + })?; + Ok(()) } - /// powers of randomness for instance columns fn instance(&self) -> Vec> { - vec![] + vec![vec![ + self.permu_alpha, + self.permu_gamma, + self.rw_fingerprints.prev_ending_row, + self.rw_fingerprints.ending_row, + self.rw_fingerprints.prev_mul_acc, + self.rw_fingerprints.mul_acc, + ]] } } diff --git a/zkevm-circuits/src/state_circuit/constraint_builder.rs b/zkevm-circuits/src/state_circuit/constraint_builder.rs index f2abbd6faf..5a35a3ec56 100644 --- a/zkevm-circuits/src/state_circuit/constraint_builder.rs +++ b/zkevm-circuits/src/state_circuit/constraint_builder.rs @@ -9,7 +9,10 @@ use crate::{ use bus_mapping::operation::Target; use eth_types::Field; use gadgets::binary_number::BinaryNumberConfig; -use halo2_proofs::plonk::Expression; +use halo2_proofs::{ + plonk::{Column, ConstraintSystem, Expression, Fixed}, + poly::Rotation, +}; use strum::IntoEnumIterator; use word::WordLoHi; @@ -110,12 +113,23 @@ impl ConstraintBuilder { .collect() } - pub fn lookups(&self) -> Vec> { - self.lookups.clone() + pub fn lookups(&self, meta: &mut ConstraintSystem, selector: Column) { + self.lookups.iter().cloned().for_each(|(name, mut lookup)| { + meta.lookup_any(name, |meta| { + let selector = meta.query_fixed(selector, Rotation::cur()); + for (expression, _) in lookup.iter_mut() { + *expression = expression.clone() * selector.clone(); + } + lookup + }); + }); } pub fn build(&mut self, q: &Queries) { self.build_general_constraints(q); + self.condition(q.tag_matches(Target::Padding), |cb| { + cb.build_padding_constraints(q) + }); self.condition(q.tag_matches(Target::Start), |cb| { cb.build_start_constraints(q) }); @@ -202,6 +216,36 @@ impl ConstraintBuilder { }); } + fn build_padding_constraints(&mut self, q: &Queries) { + // 1.0. Unused keys are 0 + self.require_zero("field_tag is 0 for Start", q.field_tag()); + self.require_zero("address is 0 for Start", q.rw_table.address.clone()); + self.require_zero("id is 0 for Start", q.id()); + self.require_word_zero("storage_key is 0 for Start", q.rw_table.storage_key.clone()); + // 1.1. rw_counter increases by 0 or 1 for every non-first row + // this is to serve multiple chunk usage, for padding rw counter is only local unique + // and not global unique + self.condition(q.not_first_access.clone(), |cb| { + cb.require_boolean( + "if previous row is also Padding. rw counter change is 0 or 1", + q.rw_counter_change(), + ) + }); + // 1.2. Start value is 0 + self.require_word_zero("Start value is 0", q.value()); + // 1.3. Start initial value is 0 + self.require_word_zero("Start initial_value is 0", q.initial_value()); + // 1.4. state_root is unchanged for every non-first row + self.condition(q.lexicographic_ordering_selector.clone(), |cb| { + cb.require_word_equal( + "state_root is unchanged for Start", + q.state_root(), + q.state_root_prev(), + ) + }); + self.require_word_zero("value_prev column is 0 for Start", q.value_prev_column()); + } + fn build_start_constraints(&mut self, q: &Queries) { // 1.0. Unused keys are 0 self.require_zero("field_tag is 0 for Start", q.field_tag()); diff --git a/zkevm-circuits/src/state_circuit/dev.rs b/zkevm-circuits/src/state_circuit/dev.rs index 193cf2e52c..c7012a20d0 100644 --- a/zkevm-circuits/src/state_circuit/dev.rs +++ b/zkevm-circuits/src/state_circuit/dev.rs @@ -61,7 +61,18 @@ where } #[cfg(test)] -use halo2_proofs::plonk::{Advice, Column}; +use crate::util::word::WordLoHi; + +#[cfg(test)] +use crate::state_circuit::HashMap; +#[cfg(test)] +use crate::witness::{Rw, RwRow}; + +#[cfg(test)] +use halo2_proofs::{ + circuit::Value, + plonk::{Advice, Column}, +}; #[cfg(test)] #[derive(Hash, Eq, PartialEq, Clone, Debug)] @@ -134,4 +145,51 @@ impl AdviceColumn { Self::NonEmptyWitness => config.is_non_exist.nonempty_witness, } } + + pub(crate) fn rw_row_overrides(&self, row: &mut RwRow>, value: F) { + match self { + Self::IsWrite => row.is_write = Value::known(value), + Self::_Address => row.address = Value::known(value), + Self::_StorageKeyLo => { + row.storage_key = WordLoHi::new([Value::known(value), row.storage_key.hi()]) + } + Self::_StorageKeyHi => { + row.storage_key = WordLoHi::new([row.storage_key.lo(), Value::known(value)]) + } + Self::ValueLo => row.value = WordLoHi::new([Value::known(value), row.value.hi()]), + Self::ValueHi => row.value = WordLoHi::new([row.value.lo(), Value::known(value)]), + Self::ValuePrevLo => { + row.value_prev = WordLoHi::new([Value::known(value), row.value_prev.hi()]) + } + Self::ValuePrevHi => { + row.value_prev = WordLoHi::new([row.value_prev.lo(), Value::known(value)]) + } + Self::RwCounter => row.rw_counter = Value::known(value), + Self::Tag => row.tag = Value::known(value), + Self::InitialValueLo => { + row.init_val = WordLoHi::new([Value::known(value), row.init_val.hi()]) + } + Self::InitialValueHi => { + row.init_val = WordLoHi::new([row.init_val.lo(), Value::known(value)]) + } + _ => (), + }; + } +} + +#[cfg(test)] +pub(crate) fn rw_overrides_skip_first_padding( + rws: &[Rw], + overrides: &HashMap<(AdviceColumn, isize), F>, +) -> Vec>> { + let first_non_padding_index = 1; + let mut rws: Vec>> = rws.iter().map(|row| row.table_assignment()).collect(); + + for ((column, row_offset), &f) in overrides { + let offset = + usize::try_from(isize::try_from(first_non_padding_index).unwrap() + *row_offset) + .unwrap(); + column.rw_row_overrides(&mut rws[offset], f); + } + rws } diff --git a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs index b8a5f4de45..1d2e448578 100644 --- a/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs +++ b/zkevm-circuits/src/state_circuit/lexicographic_ordering.rs @@ -97,7 +97,6 @@ pub struct Config { pub(crate) selector: Column, pub first_different_limb: BinaryNumberConfig, limb_difference: Column, - limb_difference_inverse: Column, } impl Config { @@ -111,27 +110,17 @@ impl Config { let bits = BinaryNumberBits::construct(meta); let first_different_limb = BinaryNumberChip::configure(meta, bits, selector, None); let limb_difference = meta.advice_column(); - let limb_difference_inverse = meta.advice_column(); let config = Config { selector, first_different_limb, limb_difference, - limb_difference_inverse, }; lookup.range_check_u16(meta, "limb_difference fits into u16", |meta| { meta.query_advice(limb_difference, Rotation::cur()) }); - meta.create_gate("limb_difference is not zero", |meta| { - let selector = meta.query_fixed(selector, Rotation::cur()); - let limb_difference = meta.query_advice(limb_difference, Rotation::cur()); - let limb_difference_inverse = - meta.query_advice(limb_difference_inverse, Rotation::cur()); - vec![selector * (1.expr() - limb_difference * limb_difference_inverse)] - }); - meta.create_gate( "limb differences before first_different_limb are all 0", |meta| { @@ -220,24 +209,15 @@ impl Config { offset, || Value::known(limb_difference), )?; - region.assign_advice( - || "limb_difference_inverse", - self.limb_difference_inverse, - offset, - || Value::known(limb_difference.invert().unwrap()), - )?; Ok(index) } /// Annotates columns of this gadget embedded within a circuit region. pub fn annotate_columns_in_region(&self, region: &mut Region, prefix: &str) { - [ - (self.limb_difference, "LO_limb_difference"), - (self.limb_difference_inverse, "LO_limb_difference_inverse"), - ] - .iter() - .for_each(|(col, ann)| region.name_column(|| format!("{}_{}", prefix, ann), *col)); + [(self.limb_difference, "LO_limb_difference")] + .iter() + .for_each(|(col, ann)| region.name_column(|| format!("{}_{}", prefix, ann), *col)); // fixed column region.name_column( || format!("{}_LO_upper_limb_difference", prefix), diff --git a/zkevm-circuits/src/state_circuit/test.rs b/zkevm-circuits/src/state_circuit/test.rs index e072443f88..97826d0746 100644 --- a/zkevm-circuits/src/state_circuit/test.rs +++ b/zkevm-circuits/src/state_circuit/test.rs @@ -2,7 +2,7 @@ pub use super::{dev::*, *}; use crate::{ table::{AccountFieldTag, CallContextFieldTag, TxLogFieldTag, TxReceiptFieldTag}, util::{unusable_rows, SubCircuit}, - witness::{MptUpdates, Rw, RwMap}, + witness::{chunk::*, MptUpdates, Rw, RwMap}, }; use bus_mapping::operation::{ MemoryOp, Operation, OperationContainer, RWCounter, StackOp, StorageOp, RW, @@ -34,6 +34,26 @@ fn state_circuit_unusable_rows() { ) } +fn new_chunk_from_rw_map(rws: &RwMap, padding_start_rw: Option) -> Chunk { + let (alpha, gamma) = get_permutation_randomness(); + let mut chunk = Chunk { + by_address_rws: rws.clone(), + ..Default::default() + }; + + let rw_fingerprints = get_permutation_fingerprint_of_rwmap( + &chunk.by_address_rws, + chunk.fixed_param.max_rws, + alpha, + gamma, + F::from(1), + false, + padding_start_rw, + ); + chunk.by_address_rw_fingerprints = rw_fingerprints; + chunk +} + fn test_state_circuit_ok( memory_ops: Vec>, stack_ops: Vec>, @@ -45,8 +65,9 @@ fn test_state_circuit_ok( storage: storage_ops, ..Default::default() }); + let chunk = new_chunk_from_rw_map(&rw_map, None); - let circuit = StateCircuit::::new(rw_map, N_ROWS); + let circuit = StateCircuit::::new(&chunk); let instance = circuit.instance(); let prover = MockProver::::run(19, &circuit, instance).unwrap(); @@ -64,19 +85,23 @@ fn degree() { #[test] fn verifying_key_independent_of_rw_length() { let params = ParamsKZG::::setup(17, rand_chacha::ChaCha20Rng::seed_from_u64(2)); + let mut chunk = Chunk::default(); - let no_rows = StateCircuit::::new(RwMap::default(), N_ROWS); - let one_row = StateCircuit::::new( - RwMap::from(&OperationContainer { + let no_rows = StateCircuit::::new(&chunk); + + chunk = new_chunk_from_rw_map( + &RwMap::from(&OperationContainer { memory: vec![Operation::new( + RWCounter::from(1), RWCounter::from(1), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(0), 32), )], ..Default::default() }), - N_ROWS, + None, ); + let one_row = StateCircuit::::new(&chunk); let vk_no_rows = keygen_vk(¶ms, &no_rows).unwrap(); let vk_one_rows = keygen_vk(¶ms, &one_row).unwrap(); @@ -93,39 +118,46 @@ fn verifying_key_independent_of_rw_length() { #[test] fn state_circuit_simple_2() { let memory_op_0 = Operation::new( + RWCounter::from(12), RWCounter::from(12), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_1 = Operation::new( + RWCounter::from(24), RWCounter::from(24), RW::READ, MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_2 = Operation::new( + RWCounter::from(17), RWCounter::from(17), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(1), 32), ); let memory_op_3 = Operation::new( + RWCounter::from(87), RWCounter::from(87), RW::READ, MemoryOp::new(1, MemoryAddress::from(1), 32), ); let stack_op_0 = Operation::new( + RWCounter::from(17), RWCounter::from(17), RW::WRITE, StackOp::new(1, StackAddress::from(1), Word::from(32)), ); let stack_op_1 = Operation::new( + RWCounter::from(87), RWCounter::from(87), RW::READ, StackOp::new(1, StackAddress::from(1), Word::from(32)), ); let storage_op_0 = Operation::new( + RWCounter::from(0), RWCounter::from(0), RW::WRITE, StorageOp::new( @@ -138,6 +170,7 @@ fn state_circuit_simple_2() { ), ); let storage_op_1 = Operation::new( + RWCounter::from(18), RWCounter::from(18), RW::WRITE, StorageOp::new( @@ -150,6 +183,7 @@ fn state_circuit_simple_2() { ), ); let storage_op_2 = Operation::new( + RWCounter::from(19), RWCounter::from(19), RW::WRITE, StorageOp::new( @@ -172,16 +206,19 @@ fn state_circuit_simple_2() { #[test] fn state_circuit_simple_6() { let memory_op_0 = Operation::new( + RWCounter::from(12), RWCounter::from(12), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_1 = Operation::new( + RWCounter::from(13), RWCounter::from(13), RW::READ, MemoryOp::new(1, MemoryAddress::from(0), 32), ); let storage_op_2 = Operation::new( + RWCounter::from(19), RWCounter::from(19), RW::WRITE, StorageOp::new( @@ -199,11 +236,13 @@ fn state_circuit_simple_6() { #[test] fn lexicographic_ordering_test_1() { let memory_op = Operation::new( + RWCounter::from(12), RWCounter::from(12), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(0), 32), ); let storage_op = Operation::new( + RWCounter::from(19), RWCounter::from(19), RW::WRITE, StorageOp::new( @@ -221,11 +260,13 @@ fn lexicographic_ordering_test_1() { #[test] fn lexicographic_ordering_test_2() { let memory_op_0 = Operation::new( + RWCounter::from(12), RWCounter::from(12), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(0), 32), ); let memory_op_1 = Operation::new( + RWCounter::from(13), RWCounter::from(13), RW::WRITE, MemoryOp::new(1, MemoryAddress::from(0), 32), @@ -655,18 +696,21 @@ fn all_padding() { } #[test] -fn skipped_start_rw_counter() { +fn invalid_padding_rw_counter_change() { let overrides = HashMap::from([ ( - (AdviceColumn::RwCounter, -1), + (AdviceColumn::RwCounter, 0), // The original assignment is 1 << 16. Fr::from((1 << 16) + 1), ), - ((AdviceColumn::RwCounterLimb0, -1), Fr::ONE), + ((AdviceColumn::RwCounterLimb0, 0), Fr::ONE), ]); - let result = prover(vec![], overrides).verify_at_rows(N_ROWS - 1..N_ROWS, N_ROWS - 1..N_ROWS); - assert_error_matches(result, "rw_counter increases by 1 for every non-first row"); + let result = prover(vec![], overrides).verify_at_rows(2..3, 2..3); + assert_error_matches( + result, + "if previous row is also Padding. rw counter change is 0 or 1", + ); } #[test] @@ -783,7 +827,7 @@ fn invalid_stack_address_change() { #[test] fn invalid_tags() { - let first_row_offset = -isize::try_from(N_ROWS).unwrap(); + let first_row_offset = 0; let tags: BTreeSet = Target::iter().map(|x| x as usize).collect(); for i in 0..16 { if tags.contains(&i) { @@ -798,8 +842,8 @@ fn invalid_tags() { ((AdviceColumn::Tag, first_row_offset), Fr::from(i as u64)), ]); - let result = prover(vec![], overrides).verify_at_rows(0..1, 0..1); - + // offset 0 is padding + let result = prover(vec![], overrides).verify_at_rows(1..2, 1..2); assert_error_matches(result, "binary number value in range"); } } @@ -922,15 +966,8 @@ fn variadic_size_check() { value: U256::from(394500u64), }, ]; - - let updates = MptUpdates::mock_from(&rows); - let circuit = StateCircuit:: { - rows: rows.clone(), - updates, - overrides: HashMap::default(), - n_rows: N_ROWS, - _marker: std::marker::PhantomData, - }; + // let rw_map: RwMap = rows.clone().into(); + let circuit = StateCircuit::new(&new_chunk_from_rw_map(&RwMap::from(rows.clone()), None)); let power_of_randomness = circuit.instance(); let prover1 = MockProver::::run(17, &circuit, power_of_randomness).unwrap(); @@ -951,14 +988,7 @@ fn variadic_size_check() { }, ]); - let updates = MptUpdates::mock_from(&rows); - let circuit = StateCircuit:: { - rows, - updates, - overrides: HashMap::default(), - n_rows: N_ROWS, - _marker: std::marker::PhantomData, - }; + let circuit = StateCircuit::new(&new_chunk_from_rw_map(&rows.into(), None)); let power_of_randomness = circuit.instance(); let prover2 = MockProver::::run(17, &circuit, power_of_randomness).unwrap(); @@ -991,12 +1021,26 @@ fn bad_initial_tx_receipt_value() { } fn prover(rows: Vec, overrides: HashMap<(AdviceColumn, isize), Fr>) -> MockProver { + // permu_next_continuous_fingerprint and rows override for negative-test + #[allow(unused_assignments, unused_mut)] + let (rw_rows, _) = RwMap::table_assignments_padding(&rows, N_ROWS, None); + let rw_rows: Vec>> = + rw_overrides_skip_first_padding(&rw_rows, &overrides); + let rwtable_fingerprints = + get_permutation_fingerprint_of_rwrowvec(&rw_rows, N_ROWS, Fr::ONE, Fr::ONE, Fr::ONE, None); + let row_padding_and_overridess = rw_rows.to2dvec(); + let updates = MptUpdates::mock_from(&rows); let circuit = StateCircuit:: { rows, + row_padding_and_overrides: row_padding_and_overridess, updates, overrides, n_rows: N_ROWS, + permu_alpha: Fr::from(1), + permu_gamma: Fr::from(1), + rw_fingerprints: rwtable_fingerprints, + prev_chunk_last_rw: None, _marker: std::marker::PhantomData, }; let instance = circuit.instance(); @@ -1006,8 +1050,7 @@ fn prover(rows: Vec, overrides: HashMap<(AdviceColumn, isize), Fr>) -> MockP fn verify(rows: Vec) -> Result<(), Vec> { let used_rows = rows.len(); - prover(rows, HashMap::new()) - .verify_at_rows(N_ROWS - used_rows..N_ROWS, N_ROWS - used_rows..N_ROWS) + prover(rows, HashMap::new()).verify_at_rows(1..used_rows + 1, 1..used_rows + 1) } fn verify_with_overrides( @@ -1018,33 +1061,36 @@ fn verify_with_overrides( assert_eq!(verify(rows.clone()), Ok(())); let n_active_rows = rows.len(); - prover(rows, overrides).verify_at_rows( - N_ROWS - n_active_rows..N_ROWS, - N_ROWS - n_active_rows..N_ROWS, - ) + prover(rows, overrides).verify_at_rows(1..n_active_rows + 1, 1..n_active_rows + 1) } fn assert_error_matches(result: Result<(), Vec>, name: &str) { let errors = result.expect_err("result is not an error"); - assert_eq!(errors.len(), 1, "{:?}", errors); - match &errors[0] { - VerifyFailure::ConstraintNotSatisfied { constraint, .. } => { - // fields of halo2_proofs::dev::metadata::Constraint aren't public, so we have - // to match off of its format string. - let constraint = format!("{}", constraint); - if !constraint.contains(name) { - panic!("{} does not contain {}", constraint, name); + errors + .iter() + .find(|err| match err { + VerifyFailure::ConstraintNotSatisfied { constraint, .. } => { + // fields of halo2_proofs::dev::metadata::Constraint aren't public, so we have + // to match off of its format string. + let constraint = format!("{}", constraint); + constraint.contains(name) } - } - VerifyFailure::Lookup { - name: lookup_name, .. - } => { - assert_eq!(lookup_name, &name) - } - VerifyFailure::CellNotAssigned { .. } => panic!(), - VerifyFailure::ConstraintPoisoned { .. } => panic!(), - VerifyFailure::Permutation { .. } => panic!(), - VerifyFailure::InstanceCellNotAssigned { .. } => panic!(), - VerifyFailure::Shuffle { .. } => panic!(), - } + VerifyFailure::Lookup { + name: lookup_name, .. + } => { + assert_eq!(lookup_name, &name); + true + } + VerifyFailure::CellNotAssigned { .. } => false, + VerifyFailure::ConstraintPoisoned { .. } => false, + VerifyFailure::Permutation { .. } => false, + VerifyFailure::InstanceCellNotAssigned { .. } => false, + VerifyFailure::Shuffle { .. } => false, + }) + .unwrap_or_else(|| { + panic!( + "there is no constraints contain {}; err {:#?}", + name, errors + ) + }); } diff --git a/zkevm-circuits/src/super_circuit.rs b/zkevm-circuits/src/super_circuit.rs index 55fabdf08a..5829f85c9e 100644 --- a/zkevm-circuits/src/super_circuit.rs +++ b/zkevm-circuits/src/super_circuit.rs @@ -62,22 +62,24 @@ use crate::{ pi_circuit::{PiCircuit, PiCircuitConfig, PiCircuitConfigArgs}, state_circuit::{StateCircuit, StateCircuitConfig, StateCircuitConfigArgs}, table::{ - BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, MptTable, RwTable, SigTable, - TxTable, UXTable, WdTable, + BlockTable, BytecodeTable, CopyTable, ExpTable, KeccakTable, LookupTable, MptTable, + RwTable, SigTable, TxTable, UXTable, WdTable, }, tx_circuit::{TxCircuit, TxCircuitConfig, TxCircuitConfigArgs}, - util::{log2_ceil, Challenges, SubCircuit, SubCircuitConfig}, - witness::{block_convert, Block, MptUpdates}, + util::{chunk_ctx::ChunkContextConfig, log2_ceil, Challenges, SubCircuit, SubCircuitConfig}, + witness::{block_convert, chunk_convert, Block, Chunk, MptUpdates}, }; use bus_mapping::{ circuit_input_builder::{CircuitInputBuilder, FeatureConfig, FixedCParams}, mock::BlockData, }; use eth_types::{geth_types::GethData, Field}; +use gadgets::util::Expr; use halo2_proofs::{ circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{Circuit, ConstraintSystem, Error, Expression}, + plonk::{Any, Circuit, Column, ConstraintSystem, Error, Expression}, }; +use itertools::Itertools; use std::array; @@ -97,10 +99,33 @@ pub struct SuperCircuitConfig { keccak_circuit: KeccakCircuitConfig, pi_circuit: PiCircuitConfig, exp_circuit: ExpCircuitConfig, + chunk_ctx_config: ChunkContextConfig, #[cfg(not(feature = "mock-challenge"))] challenges: Challenges, } +/// Circuit configuration arguments +pub struct SuperCircuitConfigArgs { + /// Max txs + pub max_txs: usize, + /// Max calldata + pub max_calldata: usize, + /// Mock randomness + pub mock_randomness: F, +} + +impl SuperCircuitConfig { + /// get chronological_rwtable and byaddr_rwtable advice columns + pub fn get_rwtable_columns(&self) -> Vec> { + // concat rw_table columns: [chronological_rwtable] ++ [byaddr_rwtable] + let mut columns = >::columns(&self.evm_circuit.rw_table); + columns.append(&mut >::columns( + &self.state_circuit.rw_table, + )); + columns + } +} + impl SubCircuitConfig for SuperCircuitConfig { type ConfigArgs = SuperCircuitParams; @@ -117,7 +142,10 @@ impl SubCircuitConfig for SuperCircuitConfig { ) -> Self { let tx_table = TxTable::construct(meta); let wd_table = WdTable::construct(meta); - let rw_table = RwTable::construct(meta); + + let chronological_rw_table = RwTable::construct(meta); + let by_address_rw_table = RwTable::construct(meta); + let mpt_table = MptTable::construct(meta); let bytecode_table = BytecodeTable::construct(meta); let block_table = BlockTable::construct(meta); @@ -150,6 +178,8 @@ impl SubCircuitConfig for SuperCircuitConfig { let sig_table = SigTable::construct(meta); + let chunk_ctx_config = ChunkContextConfig::new(meta, &challenges_exprs); + let keccak_circuit = KeccakCircuitConfig::new( meta, KeccakCircuitConfigArgs { @@ -191,7 +221,7 @@ impl SubCircuitConfig for SuperCircuitConfig { meta, CopyCircuitConfigArgs { tx_table: tx_table.clone(), - rw_table, + rw_table: chronological_rw_table, bytecode_table: bytecode_table.clone(), copy_table, q_enable: q_copy_table, @@ -201,7 +231,7 @@ impl SubCircuitConfig for SuperCircuitConfig { let state_circuit = StateCircuitConfig::new( meta, StateCircuitConfigArgs { - rw_table, + rw_table: by_address_rw_table, mpt_table, u8_table, u10_table, @@ -215,7 +245,7 @@ impl SubCircuitConfig for SuperCircuitConfig { EvmCircuitConfigArgs { challenges: challenges_exprs, tx_table, - rw_table, + rw_table: chronological_rw_table, bytecode_table, block_table: block_table.clone(), copy_table, @@ -224,10 +254,68 @@ impl SubCircuitConfig for SuperCircuitConfig { u8_table, u16_table, sig_table, + chunk_ctx_config: chunk_ctx_config.clone(), feature_config, }, ); + // chronological/by address rwtable fingerprint must be the same in last chunk + // last row. + meta.create_gate( + "chronological rwtable fingerprint == by address rwtable fingerprint", + |meta| { + let is_last_chunk = chunk_ctx_config.is_last_chunk.expr(); + let chronological_rwtable_acc_fingerprint = evm_circuit + .rw_permutation_config + .acc_fingerprints_cur_expr(); + let by_address_rwtable_acc_fingerprint = state_circuit + .rw_permutation_config + .acc_fingerprints_cur_expr(); + + let q_row_last = meta.query_selector(evm_circuit.rw_permutation_config.q_row_last); + + vec![ + is_last_chunk + * q_row_last + * (chronological_rwtable_acc_fingerprint + - by_address_rwtable_acc_fingerprint), + ] + }, + ); + + // chronological/by address rwtable `row fingerprint` must be the same in first + // chunk first row. + // `row fingerprint` is not a constant so root circuit can NOT constraint it. + // so we constraints here by gate + // Furthermore, first row in rw_table should be `Rw::Start`, which will be lookup by + // `BeginChunk` at first chunk + meta.create_gate( + "chronological rwtable row fingerprint == by address rwtable row fingerprint", + |meta| { + let is_first_chunk = chunk_ctx_config.is_first_chunk.expr(); + let chronological_rwtable_row_fingerprint = evm_circuit + .rw_permutation_config + .row_fingerprints_cur_expr(); + let by_address_rwtable_row_fingerprint = state_circuit + .rw_permutation_config + .row_fingerprints_cur_expr(); + + let q_row_first = 1.expr() + - meta.query_selector(evm_circuit.rw_permutation_config.q_row_non_first); + + let q_row_enable = + meta.query_selector(evm_circuit.rw_permutation_config.q_row_enable); + + vec![ + is_first_chunk + * q_row_first + * q_row_enable + * (chronological_rwtable_row_fingerprint + - by_address_rwtable_row_fingerprint), + ] + }, + ); + Self { block_table, mpt_table, @@ -242,6 +330,7 @@ impl SubCircuitConfig for SuperCircuitConfig { keccak_circuit, pi_circuit, exp_circuit, + chunk_ctx_config, #[cfg(not(feature = "mock-challenge"))] challenges, } @@ -251,6 +340,8 @@ impl SubCircuitConfig for SuperCircuitConfig { /// The Super Circuit contains all the zkEVM circuits #[derive(Clone, Default, Debug)] pub struct SuperCircuit { + /// Chunk + pub chunk: Option>, /// EVM Circuit pub evm_circuit: EvmCircuit, /// State Circuit @@ -277,10 +368,10 @@ pub struct SuperCircuit { impl SuperCircuit { /// Return the number of rows required to verify a given block - pub fn get_num_rows_required(block: &Block) -> usize { - let num_rows_evm_circuit = EvmCircuit::::get_num_rows_required(block); + pub fn get_num_rows_required(block: &Block, chunk: &Chunk) -> usize { + let num_rows_evm_circuit = EvmCircuit::::get_num_rows_required(block, chunk); let num_rows_tx_circuit = - TxCircuitConfig::::get_num_rows_required(block.circuits_params.max_txs); + TxCircuitConfig::::get_num_rows_required(chunk.fixed_param.max_txs); num_rows_evm_circuit.max(num_rows_tx_circuit) } } @@ -305,17 +396,18 @@ impl SubCircuit for SuperCircuit { .unwrap() } - fn new_from_block(block: &Block) -> Self { - let evm_circuit = EvmCircuit::new_from_block(block); - let state_circuit = StateCircuit::new_from_block(block); - let tx_circuit = TxCircuit::new_from_block(block); - let pi_circuit = PiCircuit::new_from_block(block); - let bytecode_circuit = BytecodeCircuit::new_from_block(block); - let copy_circuit = CopyCircuit::new_from_block_no_external(block); - let exp_circuit = ExpCircuit::new_from_block(block); - let keccak_circuit = KeccakCircuit::new_from_block(block); + fn new_from_block(block: &Block, chunk: &Chunk) -> Self { + let evm_circuit = EvmCircuit::new_from_block(block, chunk); + let state_circuit = StateCircuit::new_from_block(block, chunk); + let tx_circuit = TxCircuit::new_from_block(block, chunk); + let pi_circuit = PiCircuit::new_from_block(block, chunk); + let bytecode_circuit = BytecodeCircuit::new_from_block(block, chunk); + let copy_circuit = CopyCircuit::new_from_block_no_external(block, chunk); + let exp_circuit = ExpCircuit::new_from_block(block, chunk); + let keccak_circuit = KeccakCircuit::new_from_block(block, chunk); SuperCircuit::<_> { + chunk: Some(chunk.clone()), evm_circuit, state_circuit, tx_circuit, @@ -324,7 +416,7 @@ impl SubCircuit for SuperCircuit { copy_circuit, exp_circuit, keccak_circuit, - circuits_params: block.circuits_params, + circuits_params: chunk.fixed_param, feature_config: block.feature_config, mock_randomness: block.randomness, } @@ -333,6 +425,17 @@ impl SubCircuit for SuperCircuit { /// Returns suitable inputs for the SuperCircuit. fn instance(&self) -> Vec> { let mut instance = Vec::new(); + + let chunk = self.chunk.as_ref().unwrap(); + + instance.extend_from_slice(&[vec![ + F::from(chunk.chunk_context.idx as u64), + F::from(chunk.chunk_context.idx as u64) + F::ONE, + F::from(chunk.chunk_context.total_chunks as u64), + F::from(chunk.chunk_context.initial_rwc as u64), + F::from(chunk.chunk_context.end_rwc as u64), + ]]); + instance.extend_from_slice(&self.keccak_circuit.instance()); instance.extend_from_slice(&self.pi_circuit.instance()); instance.extend_from_slice(&self.tx_circuit.instance()); @@ -340,21 +443,23 @@ impl SubCircuit for SuperCircuit { instance.extend_from_slice(&self.copy_circuit.instance()); instance.extend_from_slice(&self.state_circuit.instance()); instance.extend_from_slice(&self.exp_circuit.instance()); - instance.extend_from_slice(&self.evm_circuit.instance()); + // remove first vector which is chunk_ctx + // which supercircuit already supply globally on top + instance.extend_from_slice(&self.evm_circuit.instance()[1..]); instance } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &Block) -> (usize, usize) { - let evm = EvmCircuit::min_num_rows_block(block); - let state = StateCircuit::min_num_rows_block(block); - let bytecode = BytecodeCircuit::min_num_rows_block(block); - let copy = CopyCircuit::min_num_rows_block(block); - let keccak = KeccakCircuit::min_num_rows_block(block); - let tx = TxCircuit::min_num_rows_block(block); - let exp = ExpCircuit::min_num_rows_block(block); - let pi = PiCircuit::min_num_rows_block(block); + fn min_num_rows_block(block: &Block, chunk: &Chunk) -> (usize, usize) { + let evm = EvmCircuit::min_num_rows_block(block, chunk); + let state = StateCircuit::min_num_rows_block(block, chunk); + let bytecode = BytecodeCircuit::min_num_rows_block(block, chunk); + let copy = CopyCircuit::min_num_rows_block(block, chunk); + let keccak = KeccakCircuit::min_num_rows_block(block, chunk); + let tx = TxCircuit::min_num_rows_block(block, chunk); + let exp = ExpCircuit::min_num_rows_block(block, chunk); + let pi = PiCircuit::min_num_rows_block(block, chunk); let rows: Vec<(usize, usize)> = vec![evm, state, bytecode, copy, keccak, tx, exp, pi]; let (rows_without_padding, rows_with_padding): (Vec, Vec) = @@ -372,6 +477,12 @@ impl SubCircuit for SuperCircuit { challenges: &Challenges>, layouter: &mut impl Layouter, ) -> Result<(), Error> { + // synthesize chunk context + config.chunk_ctx_config.assign_chunk_context( + layouter, + &self.chunk.as_ref().unwrap().chunk_context, + self.chunk.as_ref().unwrap().fixed_param.max_rws - 1, + )?; self.keccak_circuit .synthesize_sub(&config.keccak_circuit, challenges, layouter)?; self.bytecode_circuit @@ -470,12 +581,19 @@ impl SuperCircuit { geth_data: GethData, circuits_params: FixedCParams, mock_randomness: F, - ) -> Result<(u32, Self, Vec>, CircuitInputBuilder), bus_mapping::Error> - { + ) -> Result< + ( + u32, + Vec, + Vec>>, + CircuitInputBuilder, + ), + bus_mapping::Error, + > { let block_data = BlockData::new_from_geth_data_with_params(geth_data.clone(), circuits_params); - let mut builder = block_data.new_circuit_input_builder(); - builder + let builder = block_data + .new_circuit_input_builder() .handle_block(&geth_data.eth_block, &geth_data.geth_traces) .expect("could not handle block tx"); @@ -488,20 +606,43 @@ impl SuperCircuit { /// /// Also, return with it the minimum required SRS degree for the circuit and /// the Public Inputs needed. + #[allow(clippy::type_complexity)] pub fn build_from_circuit_input_builder( builder: &CircuitInputBuilder, mock_randomness: F, - ) -> Result<(u32, Self, Vec>), bus_mapping::Error> { + ) -> Result<(u32, Vec, Vec>>), bus_mapping::Error> { let mut block = block_convert(builder).unwrap(); + let chunks = chunk_convert(&block, builder).unwrap(); block.randomness = mock_randomness; - let (_, rows_needed) = Self::min_num_rows_block(&block); - let k = log2_ceil(Self::unusable_rows() + rows_needed); + let (rows_needed, circuit_instance_pairs): (Vec, Vec<(_, _)>) = chunks + .iter() + .map(|chunk| { + let (_, rows_needed) = Self::min_num_rows_block(&block, chunk); + + let circuit = SuperCircuit::new_from_block(&block, chunk); + let instance = circuit.instance(); + (rows_needed, (circuit, instance)) + }) + .unzip(); + + // assert all rows needed are equal + rows_needed + .iter() + .tuple_windows() + .for_each(|rows_needed: (&usize, &usize)| { + assert!( + rows_needed.0 == rows_needed.1, + "mismatched super_circuit rows_needed {:?} != {:?}", + rows_needed.0, + rows_needed.1 + ) + }); + + let k = log2_ceil(Self::unusable_rows() + rows_needed[0]); log::debug!("super circuit uses k = {}", k); - let circuit = SuperCircuit::new_from_block(&block); - - let instance = circuit.instance(); - Ok((k, circuit, instance)) + let (circuits, instances) = circuit_instance_pairs.into_iter().unzip(); + Ok((k, circuits, instances)) } } diff --git a/zkevm-circuits/src/super_circuit/test.rs b/zkevm-circuits/src/super_circuit/test.rs index 741fbdad8b..062d324aa7 100644 --- a/zkevm-circuits/src/super_circuit/test.rs +++ b/zkevm-circuits/src/super_circuit/test.rs @@ -1,14 +1,26 @@ +use crate::{table::rw_table::get_rwtable_cols_commitment, witness::RwMap}; + pub use super::*; +use bus_mapping::operation::OperationContainer; +use eth_types::{address, bytecode, geth_types::GethData, Word}; use ethers_signers::{LocalWallet, Signer}; -use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; +use halo2_proofs::{ + dev::MockProver, + halo2curves::{ + bn256::{Bn256, Fr}, + ff::WithSmallOrderMulGroup, + }, + poly::{ + commitment::CommitmentScheme, + kzg::commitment::{KZGCommitmentScheme, ParamsKZG}, + }, +}; use log::error; use mock::{TestContext, MOCK_CHAIN_ID}; use rand::SeedableRng; -use rand_chacha::ChaCha20Rng; +use rand_chacha::{rand_core::OsRng, ChaCha20Rng}; use std::collections::HashMap; -use eth_types::{address, bytecode, geth_types::GethData, Word}; - #[test] fn super_circuit_degree() { let mut cs = ConstraintSystem::::default(); @@ -26,14 +38,20 @@ fn super_circuit_degree() { } fn test_super_circuit(block: GethData, circuits_params: FixedCParams, mock_randomness: Fr) { - let (k, circuit, instance, _) = + let (k, circuits, instances, _) = SuperCircuit::::build(block, circuits_params, mock_randomness).unwrap(); - let prover = MockProver::run(k, &circuit, instance).unwrap(); - let res = prover.verify(); - if let Err(err) = res { - error!("Verification failures: {:#?}", err); - panic!("Failed verification"); - } + circuits + .into_iter() + .zip(instances) + .enumerate() + .for_each(|(i, (circuit, instance))| { + let prover = MockProver::run(k, &circuit, instance).unwrap(); + let res = prover.verify(); + if let Err(err) = res { + error!("{}th supercircuit Verification failures: {:#?}", i, err); + panic!("Failed verification"); + } + }); } pub(crate) fn block_1tx() -> GethData { @@ -131,6 +149,7 @@ const TEST_MOCK_RANDOMNESS: u64 = 0x100; fn serial_test_super_circuit_1tx_1max_tx() { let block = block_1tx(); let circuits_params = FixedCParams { + total_chunks: 1, max_txs: 1, max_withdrawals: 5, max_calldata: 32, @@ -149,6 +168,7 @@ fn serial_test_super_circuit_1tx_1max_tx() { fn serial_test_super_circuit_1tx_2max_tx() { let block = block_1tx(); let circuits_params = FixedCParams { + total_chunks: 1, max_txs: 2, max_withdrawals: 5, max_calldata: 32, @@ -167,6 +187,7 @@ fn serial_test_super_circuit_1tx_2max_tx() { fn serial_test_super_circuit_2tx_2max_tx() { let block = block_2tx(); let circuits_params = FixedCParams { + total_chunks: 1, max_txs: 2, max_withdrawals: 5, max_calldata: 32, @@ -180,3 +201,74 @@ fn serial_test_super_circuit_2tx_2max_tx() { }; test_super_circuit(block, circuits_params, Fr::from(TEST_MOCK_RANDOMNESS)); } + +#[ignore] +#[test] +fn serial_test_multi_chunk_super_circuit_2tx_2max_tx() { + let block = block_2tx(); + let circuits_params = FixedCParams { + total_chunks: 4, + max_txs: 2, + max_withdrawals: 5, + max_calldata: 32, + max_rws: 90, + max_copy_rows: 256, + max_exp_steps: 256, + max_bytecode: 512, + max_evm_rows: 0, + max_keccak_rows: 0, + max_vertical_circuit_rows: 0, + }; + test_super_circuit(block, circuits_params, Fr::from(TEST_MOCK_RANDOMNESS)); +} + +#[ignore] +#[test] +fn test_rw_table_commitment() { + let k = 18; + let params = ParamsKZG::::setup(k, OsRng); + rw_table_commitment::>(¶ms); +} + +fn rw_table_commitment(params: &Scheme::ParamsProver) +where + ::Scalar: WithSmallOrderMulGroup<3> + eth_types::Field, +{ + let circuits_params = FixedCParams { + max_txs: 1, + max_withdrawals: 5, + max_calldata: 32, + max_rws: 256, + max_copy_rows: 256, + max_exp_steps: 256, + max_bytecode: 512, + max_evm_rows: 0, + max_keccak_rows: 0, + total_chunks: 1, + max_vertical_circuit_rows: 0, + }; + let rw_map = RwMap::from(&OperationContainer { + ..Default::default() + }); + let rows = rw_map.table_assignments(false); + + const TEST_MOCK_RANDOMNESS: u64 = 0x100; + + // synthesize to get degree + let mut cs = ConstraintSystem::<::Scalar>::default(); + let _config = SuperCircuit::configure_with_params( + &mut cs, + SuperCircuitParams { + max_txs: circuits_params.max_txs, + max_withdrawals: circuits_params.max_withdrawals, + max_calldata: circuits_params.max_calldata, + mock_randomness: TEST_MOCK_RANDOMNESS.into(), + feature_config: FeatureConfig::default(), + }, + ); + let degree = cs.degree(); + + let advice_commitments = + get_rwtable_cols_commitment::(degree, &rows, circuits_params.max_rws, params); + println!("advice_commitments len() {:?}", advice_commitments.len()); +} diff --git a/zkevm-circuits/src/table.rs b/zkevm-circuits/src/table.rs index 93c1d0f1e1..5322a0bcb2 100644 --- a/zkevm-circuits/src/table.rs +++ b/zkevm-circuits/src/table.rs @@ -27,6 +27,8 @@ use strum_macros::{EnumCount, EnumIter}; pub(crate) mod block_table; /// bytecode table pub(crate) mod bytecode_table; +/// chunk context table +pub(crate) mod chunk_ctx_table; /// copy Table pub(crate) mod copy_table; /// exp(exponentiation) table @@ -48,6 +50,7 @@ pub(crate) mod wd_table; pub use block_table::{BlockContextFieldTag, BlockTable}; pub use bytecode_table::{BytecodeFieldTag, BytecodeTable}; +pub use chunk_ctx_table::ChunkCtxTable; pub use copy_table::CopyTable; pub use exp_table::ExpTable; pub use keccak_table::KeccakTable; @@ -190,3 +193,29 @@ pub enum CallContextFieldTag { ReversibleWriteCounter, } impl_expr!(CallContextFieldTag); + +/// Tag for an StepState in RwTable +#[derive(Clone, Copy, Debug, EnumIter, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum StepStateFieldTag { + /// caller id field + CallID = 1, + /// is_root field + IsRoot, + /// is_create field + IsCreate, + /// code_hash field + CodeHash, + /// program_counter field + ProgramCounter, + /// stack_pointer field + StackPointer, + /// gas_left field + GasLeft, + /// memory_word_size field + MemoryWordSize, + /// reversible_write_counter field + ReversibleWriteCounter, + /// log_id field + LogID, +} +impl_expr!(StepStateFieldTag); diff --git a/zkevm-circuits/src/table/chunk_ctx_table.rs b/zkevm-circuits/src/table/chunk_ctx_table.rs new file mode 100644 index 0000000000..1f9bb85957 --- /dev/null +++ b/zkevm-circuits/src/table/chunk_ctx_table.rs @@ -0,0 +1,145 @@ +use bus_mapping::circuit_input_builder::ChunkContext; +use gadgets::util::Expr; +use halo2_proofs::circuit::AssignedCell; + +use super::*; + +/// Tag to identify the field in a Chunk Context row +// Keep the sequence consistent with OpcodeId for scalar +#[derive(Clone, Copy, Debug)] +pub enum ChunkCtxFieldTag { + /// Coinbase field + CurrentChunkIndex = 1, + /// NextChunk field + NextChunkIndex, + /// Total Chunks field + TotalChunks, + /// initial rw counter + InitialRWC, + /// end rw counter + EndRWC, +} +impl_expr!(ChunkCtxFieldTag); + +/// Table with Chunk context fields +#[derive(Clone, Debug)] +pub struct ChunkCtxTable { + q_enable: Selector, + /// Tag + pub tag: Column, + /// Value + pub value: Column, +} + +type ChunkCtxTableAssignedCells = ( + AssignedCell, + AssignedCell, + AssignedCell, + AssignedCell, + AssignedCell, +); + +impl ChunkCtxTable { + /// Construct a new ChunkCtxTable + pub fn construct(meta: &mut ConstraintSystem) -> Self { + let (q_enable, tag, value) = (meta.selector(), meta.fixed_column(), meta.advice_column()); + + // constrain NextChunkIndex = CurrentChunkIndex + 1 + meta.create_gate("NextChunkIndex = CurrentChunkIndex + 1", |meta| { + let q_enable = meta.query_selector(q_enable); + let value_cur = meta.query_advice(value, Rotation::cur()); + let value_next = meta.query_advice(value, Rotation::next()); + [q_enable * (value_next - value_cur - 1.expr())] + }); + + meta.enable_equality(value); + + Self { + q_enable, + tag, + value, + } + } + + /// Assign the `ChunkCtxTable` from a `BlockContext`. + pub fn load( + &self, + layouter: &mut impl Layouter, + chunk_ctx: &ChunkContext, + ) -> Result, Error> { + layouter.assign_region( + || "chunk_ctx table", + |mut region| { + let mut offset = 0; + + self.q_enable.enable(&mut region, offset)?; + + let assigned_cells = [ + // CurrentChunkIndex + ( + F::from(ChunkCtxFieldTag::CurrentChunkIndex as u64), + F::from(chunk_ctx.idx as u64), + ), + // NextChunkIndex + ( + F::from(ChunkCtxFieldTag::NextChunkIndex as u64), + F::from(chunk_ctx.idx as u64 + 1u64), + ), + // TotalChunks + ( + F::from(ChunkCtxFieldTag::TotalChunks as u64), + F::from(chunk_ctx.total_chunks as u64), + ), + // InitialRWC + ( + F::from(ChunkCtxFieldTag::InitialRWC as u64), + F::from(chunk_ctx.initial_rwc as u64), + ), + // EndRWC + ( + F::from(ChunkCtxFieldTag::EndRWC as u64), + F::from(chunk_ctx.end_rwc as u64), + ), + // Empty row for disable lookup + (F::ZERO, F::ZERO), + ] + .iter() + .map(|(tag, value)| { + region.assign_fixed( + || format!("chunk_ctx table tag {}", offset), + self.tag, + offset, + || Value::known(*tag), + )?; + + let assigned_value = region.assign_advice( + || format!("chunk_ctx table value {}", offset), + self.value, + offset, + || Value::known(*value), + )?; + + offset += 1; + + Ok(assigned_value) + }) + .collect::>, Error>>()?; + + // remove last empty cell + let assigned_cells = assigned_cells.split_last().unwrap().1; + + Ok(assigned_cells.iter().cloned().collect_tuple().unwrap()) + }, + ) + } +} + +impl LookupTable for ChunkCtxTable { + fn columns(&self) -> Vec> { + vec![self.tag.into(), self.value.into()] + } + + fn annotations(&self) -> Vec { + vec![String::from("tag"), String::from("value")] + } +} diff --git a/zkevm-circuits/src/table/copy_table.rs b/zkevm-circuits/src/table/copy_table.rs index 4b93bdf2ba..1fea1d8240 100644 --- a/zkevm-circuits/src/table/copy_table.rs +++ b/zkevm-circuits/src/table/copy_table.rs @@ -1,3 +1,5 @@ +use crate::witness::Chunk; + use super::*; use gadgets::binary_number::AsBits; @@ -218,6 +220,7 @@ impl CopyTable { &self, layouter: &mut impl Layouter, block: &Block, + chunk: &Chunk, challenges: &Challenges>, ) -> Result<(), Error> { layouter.assign_region( @@ -250,7 +253,7 @@ impl CopyTable { } // Enable selector at all rows - let max_copy_rows = block.circuits_params.max_copy_rows; + let max_copy_rows = chunk.fixed_param.max_copy_rows; for offset in 0..max_copy_rows { region.assign_fixed( || "q_enable", diff --git a/zkevm-circuits/src/table/exp_table.rs b/zkevm-circuits/src/table/exp_table.rs index 628a4c30ff..7067182cb4 100644 --- a/zkevm-circuits/src/table/exp_table.rs +++ b/zkevm-circuits/src/table/exp_table.rs @@ -3,7 +3,7 @@ use super::*; use crate::{ exp_circuit::param::{OFFSET_INCREMENT, ROWS_PER_STEP}, table::LookupTable, - witness::Block, + witness::{Block, Chunk}, }; use bus_mapping::circuit_input_builder::ExpEvent; @@ -118,6 +118,7 @@ impl ExpTable { &self, layouter: &mut impl Layouter, block: &Block, + chunk: &Chunk, ) -> Result<(), Error> { layouter.assign_region( || "exponentiation table", @@ -150,7 +151,7 @@ impl ExpTable { } // Enable selector at all rows - let max_exp_steps = block.circuits_params.max_exp_steps; + let max_exp_steps = chunk.fixed_param.max_exp_steps; for offset in 0..max_exp_steps * OFFSET_INCREMENT { let is_step = if offset % OFFSET_INCREMENT == 0 { F::ONE diff --git a/zkevm-circuits/src/table/rw_table.rs b/zkevm-circuits/src/table/rw_table.rs index 6ade04d386..65ef5ea8dc 100644 --- a/zkevm-circuits/src/table/rw_table.rs +++ b/zkevm-circuits/src/table/rw_table.rs @@ -1,3 +1,22 @@ +use halo2_proofs::{ + self, + circuit::{AssignedCell, SimpleFloorPlanner}, + halo2curves::ff::{BatchInvert, WithSmallOrderMulGroup}, +}; + +use halo2_proofs::{ + halo2curves::{ + bn256::Fr, + group::{prime::PrimeCurveAffine, Curve}, + CurveAffine, + }, + plonk::{Advice, Assigned, Assignment, Challenge, Fixed, FloorPlanner, Instance, Selector}, + poly::{ + commitment::{Blind, CommitmentScheme, Params}, + EvaluationDomain, LagrangeCoeff, Polynomial, + }, +}; + use super::*; /// The RwTable shared between EVM Circuit and State Circuit, which contains @@ -65,28 +84,42 @@ impl LookupTable for RwTable { ] } } + impl RwTable { /// Construct a new RwTable pub fn construct(meta: &mut ConstraintSystem) -> Self { Self { - rw_counter: meta.advice_column(), - is_write: meta.advice_column(), - tag: meta.advice_column(), - id: meta.advice_column(), - address: meta.advice_column(), - field_tag: meta.advice_column(), - storage_key: WordLoHi::new([meta.advice_column(), meta.advice_column()]), - value: WordLoHi::new([meta.advice_column(), meta.advice_column()]), - value_prev: WordLoHi::new([meta.advice_column(), meta.advice_column()]), - init_val: WordLoHi::new([meta.advice_column(), meta.advice_column()]), + rw_counter: meta.unblinded_advice_column(), + is_write: meta.unblinded_advice_column(), + tag: meta.unblinded_advice_column(), + id: meta.unblinded_advice_column(), + address: meta.unblinded_advice_column(), + field_tag: meta.unblinded_advice_column(), + storage_key: WordLoHi::new([ + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + ]), + value: WordLoHi::new([ + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + ]), + value_prev: WordLoHi::new([ + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + ]), + init_val: WordLoHi::new([ + meta.unblinded_advice_column(), + meta.unblinded_advice_column(), + ]), } } - fn assign( + pub(crate) fn assign( &self, region: &mut Region<'_, F>, offset: usize, row: &RwRow>, - ) -> Result<(), Error> { + ) -> Result>, Error> { + let mut assigned_cells = vec![]; for (column, value) in [ (self.rw_counter, row.rw_counter), (self.is_write, row.is_write), @@ -95,7 +128,12 @@ impl RwTable { (self.address, row.address), (self.field_tag, row.field_tag), ] { - region.assign_advice(|| "assign rw row on rw table", column, offset, || value)?; + assigned_cells.push(region.assign_advice( + || "assign rw row on rw table", + column, + offset, + || value, + )?); } for (column, value) in [ (self.storage_key, row.storage_key), @@ -103,10 +141,15 @@ impl RwTable { (self.value_prev, row.value_prev), (self.init_val, row.init_val), ] { - value.assign_advice(region, || "assign rw row on rw table", column, offset)?; + assigned_cells.extend( + value + .assign_advice(region, || "assign rw row on rw table", column, offset)? + .limbs + .clone(), + ); } - Ok(()) + Ok(assigned_cells) } /// Assign the `RwTable` from a `RwMap`, following the same @@ -116,10 +159,14 @@ impl RwTable { layouter: &mut impl Layouter, rws: &[Rw], n_rows: usize, + prev_chunk_last_rw: Option, ) -> Result<(), Error> { layouter.assign_region( || "rw table", - |mut region| self.load_with_region(&mut region, rws, n_rows), + |mut region| { + self.load_with_region(&mut region, rws, n_rows, prev_chunk_last_rw) + .map(|_| ()) + }, ) } @@ -128,11 +175,300 @@ impl RwTable { region: &mut Region<'_, F>, rws: &[Rw], n_rows: usize, - ) -> Result<(), Error> { - let (rows, _) = RwMap::table_assignments_prepad(rws, n_rows); + prev_chunk_last_rw: Option, + ) -> Result, Error> { + let (rows, _) = RwMap::table_assignments_padding(rws, n_rows, prev_chunk_last_rw); for (offset, row) in rows.iter().enumerate() { self.assign(region, offset, &row.table_assignment())?; } + Ok(rows) + } +} + +/// get rw table column commitment +/// implementation snippet from halo2 `create_proof` https://github.com/privacy-scaling-explorations/halo2/blob/9b33f9ce524dbb9133fc8b9638b2afd0571659a8/halo2_proofs/src/plonk/prover.rs#L37 +#[allow(unused)] +pub fn get_rwtable_cols_commitment( + degree: usize, + rws: &[Rw], + n_rows: usize, + params_prover: &Scheme::ParamsProver, +) -> Vec<::Curve> +where + ::Scalar: WithSmallOrderMulGroup<3> + Field, +{ + struct WitnessCollection { + advice: Vec, LagrangeCoeff>>, + _marker: std::marker::PhantomData, + } + + impl Assignment for WitnessCollection { + fn enter_region(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about regions in this context. + } + + fn exit_region(&mut self) { + // Do nothing; we don't care about regions in this context. + } + + fn enable_selector(&mut self, _: A, _: &Selector, _: usize) -> Result<(), Error> + where + A: FnOnce() -> AR, + AR: Into, + { + // We only care about advice columns here + + Ok(()) + } + + fn annotate_column(&mut self, _annotation: A, _column: Column) + where + A: FnOnce() -> AR, + AR: Into, + { + // Do nothing + } + + fn query_instance( + &self, + _column: Column, + _row: usize, + ) -> Result, Error> { + Err(Error::BoundsFailure) + } + + fn assign_advice( + &mut self, + _: A, + column: Column, + row: usize, + to: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + to().into_field().map(|v| { + *self + .advice + .get_mut(column.index()) + .and_then(|v| v.get_mut(row)) + .ok_or(Error::BoundsFailure) + .unwrap() = v; + }); + Ok(()) + } + + fn assign_fixed( + &mut self, + _: A, + _: Column, + _: usize, + _: V, + ) -> Result<(), Error> + where + V: FnOnce() -> Value, + VR: Into>, + A: FnOnce() -> AR, + AR: Into, + { + // We only care about advice columns here + + Ok(()) + } + + fn copy( + &mut self, + _: Column, + _: usize, + _: Column, + _: usize, + ) -> Result<(), Error> { + // We only care about advice columns here + + Ok(()) + } + + fn fill_from_row( + &mut self, + _: Column, + _: usize, + _: Value>, + ) -> Result<(), Error> { + Ok(()) + } + + fn get_challenge(&self, _challenge: Challenge) -> Value { + Value::unknown() + } + + fn push_namespace(&mut self, _: N) + where + NR: Into, + N: FnOnce() -> NR, + { + // Do nothing; we don't care about namespaces in this context. + } + + fn pop_namespace(&mut self, _: Option) { + // Do nothing; we don't care about namespaces in this context. + } + } + + let rwtable_circuit = RwTableCircuit::new(rws, n_rows, None); + + let domain = EvaluationDomain::<::Scalar>::new( + degree as u32, + params_prover.k(), + ); + + let mut cs = ConstraintSystem::<::Scalar>::default(); + let rwtable_circuit_config = RwTableCircuit::configure(&mut cs); + let mut witness = WitnessCollection { + advice: vec![ + domain.empty_lagrange_assigned(); + ::Scalar>>::advice_columns( + &rwtable_circuit_config.rw_table + ) + .len() + ], + _marker: std::marker::PhantomData, + }; + + // Synthesize the circuit to obtain the witness and other information. + as Circuit>::FloorPlanner::synthesize( + &mut witness, + &rwtable_circuit, + rwtable_circuit_config, + cs.constants().clone(), + ) + .unwrap(); + + let len = witness.advice.len(); + let advice_values = + batch_invert_assigned::(domain, witness.advice.into_iter().collect()); + + // Compute commitments to advice column polynomials + let blinds = vec![Blind::default(); len]; + let advice_commitments_projective: Vec<_> = advice_values + .iter() + .zip(blinds.iter()) + .map(|(poly, blind)| params_prover.commit_lagrange(poly, *blind)) + .collect(); + let mut advice_commitments = + vec![Scheme::Curve::identity(); advice_commitments_projective.len()]; + + ::CurveExt::batch_normalize( + &advice_commitments_projective, + &mut advice_commitments, + ); + + advice_commitments +} + +struct RwTableCircuit<'a> { + rws: &'a [Rw], + n_rows: usize, + prev_chunk_last_rw: Option, +} + +impl<'a> RwTableCircuit<'a> { + #[allow(dead_code)] + pub(crate) fn new(rws: &'a [Rw], n_rows: usize, prev_chunk_last_rw: Option) -> Self { + Self { + rws, + n_rows, + prev_chunk_last_rw, + } + } +} + +#[derive(Clone)] +pub(crate) struct RwTableCircuitConfig { + pub rw_table: RwTable, +} + +impl RwTableCircuitConfig {} + +impl<'a, F: Field> Circuit for RwTableCircuit<'a> { + type Config = RwTableCircuitConfig; + + type FloorPlanner = SimpleFloorPlanner; + + type Params = (); + + fn without_witnesses(&self) -> Self { + todo!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + RwTableCircuitConfig { + rw_table: RwTable::construct(meta), + } + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "XXXX", + |mut region| { + config.rw_table.load_with_region( + &mut region, + self.rws, + self.n_rows, + self.prev_chunk_last_rw, + ) + }, + )?; Ok(()) } } + +// migrate from halo2 library +#[allow(unused)] +fn batch_invert_assigned>( + domain: EvaluationDomain, + assigned: Vec, LagrangeCoeff>>, +) -> Vec> { + let mut assigned_denominators: Vec<_> = assigned + .iter() + .map(|f| { + f.iter() + .map(|value| value.denominator()) + .collect::>() + }) + .collect(); + + assigned_denominators + .iter_mut() + .flat_map(|f| { + f.iter_mut() + // If the denominator is trivial, we can skip it, reducing the + // size of the batch inversion. + .filter_map(|d| d.as_mut()) + }) + .batch_invert(); + + assigned + .iter() + .zip(assigned_denominators) + .map(|(poly, inv_denoms)| { + let inv_denoms = inv_denoms.into_iter().map(|d| d.unwrap_or(F::ONE)); + domain.lagrange_from_vec( + poly.iter() + .zip(inv_denoms) + .map(|(a, inv_den)| a.numerator() * inv_den) + .collect(), + ) + }) + .collect() +} diff --git a/zkevm-circuits/src/test_util.rs b/zkevm-circuits/src/test_util.rs index 9a890b7364..a809631a7b 100644 --- a/zkevm-circuits/src/test_util.rs +++ b/zkevm-circuits/src/test_util.rs @@ -4,14 +4,14 @@ use crate::{ evm_circuit::{cached::EvmCircuitCached, EvmCircuit}, state_circuit::StateCircuit, util::SubCircuit, - witness::{Block, Rw}, + witness::{Block, Chunk, Rw}, }; use bus_mapping::{ circuit_input_builder::{FeatureConfig, FixedCParams}, mock::BlockData, }; use eth_types::geth_types::GethData; -use itertools::all; +use itertools::{all, Itertools}; use std::cmp; use thiserror::Error; @@ -78,15 +78,20 @@ const NUM_BLINDING_ROWS: usize = 64; /// .unwrap(); /// /// CircuitTestBuilder::new_from_test_ctx(ctx) -/// .block_modifier(Box::new(|block| block.circuits_params.max_evm_rows = (1 << 18) - 100)) -/// .run(); +/// .block_modifier(Box::new(|_block, chunk| { +/// chunk +/// .iter_mut() +/// .for_each(|chunk| chunk.fixed_param.max_evm_rows = (1 << 18) - 100); +/// })) +/// .run() /// ``` pub struct CircuitTestBuilder { test_ctx: Option>, circuits_params: Option, feature_config: Option, block: Option>, - block_modifiers: Vec)>>, + chunks: Option>>, + block_modifiers: Vec, &mut Vec>)>>, } impl CircuitTestBuilder { @@ -97,6 +102,7 @@ impl CircuitTestBuilder { circuits_params: None, feature_config: None, block: None, + chunks: None, block_modifiers: vec![], } } @@ -109,8 +115,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) -> Self { - Self::empty().block(block) + 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 @@ -138,19 +144,23 @@ impl CircuitTestBuilder { self } - /// Allows to pass a [`Block`] already built to the constructor. - pub fn block(mut self, block: Block) -> 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.chunks = Some(chunks); self } #[allow(clippy::type_complexity)] - /// Allows to provide modifier functions for the [`Block`] that will be + /// Allows to provide modifier functions for the [`Block, Chunk`] that will be /// generated within this builder. /// /// 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)>) -> Self { + pub fn block_modifier( + mut self, + modifier: Box, &mut Vec>)>, + ) -> Self { self.block_modifiers.push(modifier); self } @@ -158,97 +168,257 @@ impl CircuitTestBuilder { impl CircuitTestBuilder { /// build block - pub fn build_block(&self) -> Result, CircuitTestError> { - if let Some(block) = &self.block { + pub fn build_block( + &self, + 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()); + return Ok((block.clone(), chunks.clone())); } let block = self .test_ctx .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_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()))?; + let builder = match self.circuits_params { + Some(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_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()))?, + }; // 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 chunks = crate::witness::chunk_convert(&block, &builder).unwrap(); for modifier_fn in &self.block_modifiers { - modifier_fn.as_ref()(&mut block); + modifier_fn.as_ref()(&mut block, &mut chunks); } - Ok(block) + Ok((block, chunks)) } - fn run_evm_circuit_test(&self, block: Block) -> Result<(), CircuitTestError> { - let k = block.get_test_degree(); + fn run_evm_circuit_test( + &self, + block: Block, + chunks: Vec>, + ) -> Result<(), CircuitTestError> { + if chunks.is_empty() { + return Err(CircuitTestError::SanityCheckChunks( + "empty chunks vector".to_string(), + )); + } - let (active_gate_rows, active_lookup_rows) = EvmCircuit::::get_active_rows(&block); + let k = block.get_test_degree(&chunks[0]); - // 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 (active_gate_rows, active_lookup_rows) = + EvmCircuit::::get_active_rows(&block, &chunks[0]); + + // 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(), + ))); + } - let prover = prover.map_err(|err| CircuitTestError::SynthesisFailure { - circuit: Circuit::EVM, - reason: err, - })?; - - 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() + .enumerate() + // terminate on first error + .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) -> Result<(), CircuitTestError> { - let rows_needed = StateCircuit::::min_num_rows_block(&block).1; + fn run_state_circuit_test( + &self, + block: Block, + chunks: Vec>, + ) -> Result<(), CircuitTestError> { + // sanity check + assert!(!chunks.is_empty()); + chunks.iter().tuple_windows().for_each(|(chunk1, chunk2)| { + let (rows_needed_1, rows_needed_2) = ( + StateCircuit::::min_num_rows_block(&block, chunk1).1, + StateCircuit::::min_num_rows_block(&block, chunk2).1, + ); + assert!(rows_needed_1 == rows_needed_2); + + assert!(chunk1.fixed_param == chunk2.fixed_param); + }); + + 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 max_rws = block.circuits_params.max_rws; - let state_circuit = StateCircuit::::new(block.rws, max_rws); - let instance = state_circuit.instance(); - let prover = MockProver::::run(k, &state_circuit, instance).map_err(|err| { - CircuitTestError::SynthesisFailure { - circuit: Circuit::State, - reason: err, - } - })?; - // Skip verification of Start rows to accelerate testing - let non_start_rows_len = state_circuit - .rows + + chunks .iter() - .filter(|rw| !matches!(rw, Rw::Start { .. })) - .count(); - 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, - reasons: err, - } - }) + // terminate on first error + .find_map(|chunk| { + let state_circuit = StateCircuit::::new(chunk); + let instance = state_circuit.instance(); + let prover = MockProver::::run(k, &state_circuit, instance).map_err(|err| { + CircuitTestError::SynthesisFailure { + circuit: Circuit::State, + reason: err, + } + }); + if let Err(err) = prover { + return Some(Err(err)); + } + let prover = prover.unwrap(); + // Skip verification of Start and Padding rows accelerate testing + let non_padding_rows_len = state_circuit + .rows + .iter() + .filter(|rw| { + !matches!(rw, Rw::Start { .. }) && !matches!(rw, Rw::Padding { .. }) + }) + .count(); + let rows = 1..1 + non_padding_rows_len; + let result: Result<(), CircuitTestError> = prover + .verify_at_rows(rows.clone(), rows) + .map_err(|err| CircuitTestError::VerificationFailed { + circuit: Circuit::EVM, + reasons: err, + }); + if result.is_ok() { + None + } else { + Some(result) + } + }) + .unwrap_or_else(|| Ok(())) } + /// 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 = self.build_block()?; + self.run_multiple_chunks_with_result(None) + } + + /// 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_multiple_chunks_with_result( + self, + total_chunks: Option, + ) -> Result<(), CircuitTestError> { + let (block, chunks) = self.build_block(total_chunks)?; - self.run_evm_circuit_test(block.clone())?; - self.run_state_circuit_test(block) + self.run_evm_circuit_test(block.clone(), chunks.clone())?; + self.run_state_circuit_test(block, chunks) } /// Convenient method to run in test cases that error handling is not required. @@ -278,6 +448,9 @@ pub enum CircuitTestError { /// Something wrong 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/tx_circuit.rs b/zkevm-circuits/src/tx_circuit.rs index 4ea61edfcc..3fae849fcb 100644 --- a/zkevm-circuits/src/tx_circuit.rs +++ b/zkevm-circuits/src/tx_circuit.rs @@ -16,7 +16,7 @@ pub use dev::TxCircuit as TestTxCircuit; use crate::{ table::{KeccakTable, TxFieldTag, TxTable}, util::{word::WordLoHi, Challenges, SubCircuit, SubCircuitConfig}, - witness, + witness::{self, Chunk}, }; use eth_types::{geth_types::Transaction, sign_types::SignData, Field}; use halo2_proofs::{ @@ -309,26 +309,23 @@ impl SubCircuit for TxCircuit { 6 } - fn new_from_block(block: &witness::Block) -> Self { + fn new_from_block(block: &witness::Block, chunk: &Chunk) -> Self { Self::new( - block.circuits_params.max_txs, - block.circuits_params.max_calldata, + chunk.fixed_param.max_txs, + chunk.fixed_param.max_calldata, block.context.chain_id.as_u64(), block.txs.iter().map(|tx| tx.deref().clone()).collect_vec(), ) } /// Return the minimum number of rows required to prove the block - fn min_num_rows_block(block: &witness::Block) -> (usize, usize) { + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize) { ( Self::min_num_rows( block.txs.len(), block.txs.iter().map(|tx| tx.call_data.len()).sum(), ), - Self::min_num_rows( - block.circuits_params.max_txs, - block.circuits_params.max_calldata, - ), + Self::min_num_rows(chunk.fixed_param.max_txs, chunk.fixed_param.max_calldata), ) } diff --git a/zkevm-circuits/src/util.rs b/zkevm-circuits/src/util.rs index b0d8d0449f..cd614d42ca 100644 --- a/zkevm-circuits/src/util.rs +++ b/zkevm-circuits/src/util.rs @@ -10,7 +10,10 @@ use halo2_proofs::{ }, }; -use crate::{table::TxLogFieldTag, witness}; +use crate::{ + table::TxLogFieldTag, + witness::{self, Chunk}, +}; use eth_types::{keccak256, Field, ToAddress, Word}; pub use ethers_core::types::{Address, U256}; pub use gadgets::util::Expr; @@ -20,6 +23,9 @@ pub mod cell_manager; /// Cell Placement strategies pub mod cell_placement_strategy; +/// Chunk context config +pub mod chunk_ctx; + /// Steal the expression from gate pub fn query_expression( meta: &mut ConstraintSystem, @@ -152,7 +158,7 @@ pub trait SubCircuit { fn unusable_rows() -> usize; /// Create a new SubCircuit from a witness Block - fn new_from_block(block: &witness::Block) -> Self; + fn new_from_block(block: &witness::Block, chunk: &Chunk) -> Self; /// Returns the instance columns required for this circuit. fn instance(&self) -> Vec> { @@ -171,7 +177,7 @@ pub trait SubCircuit { /// Return the minimum number of rows required to prove the block. /// Row numbers without/with padding are both returned. - fn min_num_rows_block(block: &witness::Block) -> (usize, usize); + fn min_num_rows_block(block: &witness::Block, chunk: &Chunk) -> (usize, usize); } /// SubCircuit configuration @@ -204,6 +210,14 @@ pub(crate) fn get_push_size(byte: u8) -> u64 { } } +pub(crate) fn unwrap_value(value: Value) -> T { + let mut inner = None; + _ = value.map(|v| { + inner = Some(v); + }); + inner.unwrap() +} + #[cfg(test)] use halo2_proofs::plonk::Circuit; diff --git a/zkevm-circuits/src/util/chunk_ctx.rs b/zkevm-circuits/src/util/chunk_ctx.rs new file mode 100644 index 0000000000..2f5a223ff6 --- /dev/null +++ b/zkevm-circuits/src/util/chunk_ctx.rs @@ -0,0 +1,226 @@ +use bus_mapping::circuit_input_builder::ChunkContext; +use gadgets::{ + is_zero::{IsZeroChip, IsZeroConfig, IsZeroInstruction}, + less_than::{LtChip, LtConfig}, + util::Expr, +}; +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Expression, Instance, Selector}, + poly::Rotation, +}; + +use crate::{ + evm_circuit::util::rlc, + table::{ + chunk_ctx_table::{ChunkCtxFieldTag, ChunkCtxTable}, + LookupTable, + }, +}; +use eth_types::Field; +use gadgets::less_than::LtInstruction; + +use super::Challenges; + +/// chunk context config +#[derive(Clone, Debug)] +pub struct ChunkContextConfig { + chunk_index: Column, + chunk_index_next: Column, + total_chunks: Column, + q_chunk_context: Selector, + + /// is_first_chunk config + pub is_first_chunk: IsZeroConfig, + /// is_last_chunk config + pub is_last_chunk: IsZeroConfig, + + /// ChunkCtxTable + pub chunk_ctx_table: ChunkCtxTable, + /// instance column for chunk context + pub pi_chunk_ctx: Column, + + /// Lt chip to check: chunk_index < total_chunks. + /// Assume `total_chunks` < 2**8 = 256 + pub is_chunk_index_lt_total_chunks: LtConfig, +} + +impl ChunkContextConfig { + /// new a chunk context config + pub fn new(meta: &mut ConstraintSystem, challenges: &Challenges>) -> Self { + let q_chunk_context = meta.complex_selector(); + let chunk_index = meta.advice_column(); + let chunk_index_inv = meta.advice_column(); + let chunk_index_next = meta.advice_column(); + let chunk_diff = meta.advice_column(); + let total_chunks = meta.advice_column(); + + let pi_chunk_ctx = meta.instance_column(); + meta.enable_equality(pi_chunk_ctx); + + let chunk_ctx_table = ChunkCtxTable::construct(meta); + chunk_ctx_table.annotate_columns(meta); + + [ + (ChunkCtxFieldTag::CurrentChunkIndex.expr(), chunk_index), + (ChunkCtxFieldTag::NextChunkIndex.expr(), chunk_index_next), + (ChunkCtxFieldTag::TotalChunks.expr(), total_chunks), + ] + .iter() + .for_each(|(tag_expr, value_col)| { + meta.lookup_any("chunk context lookup", |meta| { + let q_chunk_context = meta.query_selector(q_chunk_context); + let value_col_expr = meta.query_advice(*value_col, Rotation::cur()); + + vec![( + q_chunk_context + * rlc::expr( + &[tag_expr.clone(), value_col_expr], + challenges.lookup_input(), + ), + rlc::expr( + &chunk_ctx_table.table_exprs(meta), + challenges.lookup_input(), + ), + )] + }); + }); + + // assume max total_chunks < 2^8 + let is_chunk_index_lt_total_chunks = LtChip::<_, 1>::configure( + meta, + |meta| meta.query_selector(q_chunk_context), + |meta| meta.query_advice(chunk_index, Rotation::cur()), + |meta| meta.query_advice(total_chunks, Rotation::cur()), + ); + + meta.create_gate("chunk_index < total_chunks", |meta| { + [meta.query_selector(q_chunk_context) + * (1.expr() - is_chunk_index_lt_total_chunks.is_lt(meta, None))] + }); + + let is_first_chunk = IsZeroChip::configure( + meta, + |meta| meta.query_selector(q_chunk_context), + |meta| meta.query_advice(chunk_index, Rotation::cur()), + chunk_index_inv, + ); + + let is_last_chunk = IsZeroChip::configure( + meta, + |meta| meta.query_selector(q_chunk_context), + |meta| { + let chunk_index = meta.query_advice(chunk_index, Rotation::cur()); + let total_chunks = meta.query_advice(total_chunks, Rotation::cur()); + + total_chunks - chunk_index - 1.expr() + }, + chunk_diff, + ); + + Self { + q_chunk_context, + chunk_index, + chunk_index_next, + total_chunks, + is_first_chunk, + is_last_chunk, + chunk_ctx_table, + pi_chunk_ctx, + is_chunk_index_lt_total_chunks, + } + } + + /// assign chunk context + pub fn assign_chunk_context( + &self, + layouter: &mut impl Layouter, + chunk_context: &ChunkContext, + max_offset_index: usize, + ) -> Result<(), Error> { + let is_chunk_index_lt_total_chunks = LtChip::construct(self.is_chunk_index_lt_total_chunks); + is_chunk_index_lt_total_chunks.load(layouter)?; + + let ( + chunk_index_cell, + chunk_index_next_cell, + total_chunk_cell, + initial_rwc_cell, + end_rwc_cell, + ) = self.chunk_ctx_table.load(layouter, chunk_context)?; + + let is_first_chunk = IsZeroChip::construct(self.is_first_chunk.clone()); + let is_last_chunk = IsZeroChip::construct(self.is_last_chunk.clone()); + layouter.assign_region( + || "chunk context", + |mut region| { + region.name_column(|| "chunk_index", self.chunk_index); + region.name_column(|| "chunk_index_next", self.chunk_index_next); + region.name_column(|| "total_chunks", self.total_chunks); + region.name_column(|| "pi_chunk_ctx", self.pi_chunk_ctx); + self.is_first_chunk + .annotate_columns_in_region(&mut region, "is_first_chunk"); + self.is_last_chunk + .annotate_columns_in_region(&mut region, "is_last_chunk"); + self.chunk_ctx_table.annotate_columns_in_region(&mut region); + + for offset in 0..max_offset_index + 1 { + self.q_chunk_context.enable(&mut region, offset)?; + + region.assign_advice( + || "chunk_index", + self.chunk_index, + offset, + || Value::known(F::from(chunk_context.idx as u64)), + )?; + + region.assign_advice( + || "chunk_index_next", + self.chunk_index_next, + offset, + || Value::known(F::from(chunk_context.idx as u64 + 1u64)), + )?; + + region.assign_advice( + || "total_chunks", + self.total_chunks, + offset, + || Value::known(F::from(chunk_context.total_chunks as u64)), + )?; + + is_first_chunk.assign( + &mut region, + offset, + Value::known(F::from(chunk_context.idx as u64)), + )?; + is_last_chunk.assign( + &mut region, + offset, + Value::known(F::from( + (chunk_context.total_chunks - chunk_context.idx - 1) as u64, + )), + )?; + is_chunk_index_lt_total_chunks.assign( + &mut region, + offset, + Value::known(F::from(chunk_context.idx as u64)), + Value::known(F::from(chunk_context.total_chunks as u64)), + )?; + } + Ok(()) + }, + )?; + + [ + chunk_index_cell, + chunk_index_next_cell, + total_chunk_cell, + initial_rwc_cell, + end_rwc_cell, + ] + .iter() + .enumerate() + .try_for_each(|(i, cell)| layouter.constrain_instance(cell.cell(), self.pi_chunk_ctx, i))?; + Ok(()) + } +} diff --git a/zkevm-circuits/src/witness.rs b/zkevm-circuits/src/witness.rs index 97b3a44cb1..ab7fdda89a 100644 --- a/zkevm-circuits/src/witness.rs +++ b/zkevm-circuits/src/witness.rs @@ -3,9 +3,12 @@ //! used to generate witnesses for circuits. mod block; +/// +pub mod chunk; pub use block::{block_convert, Block, BlockContext}; +pub use chunk::{chunk_convert, Chunk}; mod mpt; pub use mpt::{MptUpdate, MptUpdateRow, MptUpdates}; -mod rw; +pub mod rw; pub use bus_mapping::circuit_input_builder::{Call, ExecStep, Transaction, Withdrawal}; pub use rw::{Rw, RwMap, RwRow}; diff --git a/zkevm-circuits/src/witness/block.rs b/zkevm-circuits/src/witness/block.rs index 2718c795e9..6d3c4417fb 100644 --- a/zkevm-circuits/src/witness/block.rs +++ b/zkevm-circuits/src/witness/block.rs @@ -1,10 +1,16 @@ -use super::{ExecStep, Rw, RwMap, Transaction}; +use std::collections::BTreeMap; + +use super::{ + rw::{RwFingerprints, ToVec}, + ExecStep, Rw, RwMap, Transaction, +}; use crate::{ evm_circuit::{detect_fixed_table_tags, EvmCircuit}, exp_circuit::param::OFFSET_INCREMENT, instance::public_data_convert, table::BlockContextFieldTag, - util::{log2_ceil, word::WordLoHi, SubCircuit}, + util::{log2_ceil, unwrap_value, word::WordLoHi, SubCircuit}, + witness::Chunk, }; use bus_mapping::{ circuit_input_builder::{ @@ -14,25 +20,27 @@ use bus_mapping::{ Error, }; use eth_types::{sign_types::SignData, Address, Field, ToScalar, Word, H256}; + +use gadgets::permutation::get_permutation_fingerprints; use halo2_proofs::circuit::Value; use itertools::Itertools; // TODO: Remove fields that are duplicated in`eth_block` -/// Block is the struct used by all circuits, which contains all the needed -/// data for witness generation. +/// [`Block`] is the struct used by all circuits, which contains blockwise +/// data for witness generation. Used with [`Chunk`] for the i-th chunck witness. #[derive(Debug, Clone, Default)] pub struct Block { /// The randomness for random linear combination pub randomness: F, /// Transactions in the block pub txs: Vec, - /// EndBlock step that is repeated after the last transaction and before + /// Padding step that is repeated after the last transaction and before /// reaching the last EVM row. - pub end_block_not_last: ExecStep, - /// Last EndBlock step that appears in the last EVM row. - pub end_block_last: ExecStep, + pub end_block: ExecStep, /// Read write events in the RwTable pub rws: RwMap, + /// Read write events in the RwTable, sorted by address + pub by_address_rws: Vec, /// Bytecode used in the block pub bytecodes: CodeDB, /// The block context @@ -57,6 +65,8 @@ pub struct Block { pub precompile_events: PrecompileEvents, /// Original Block from geth pub eth_block: eth_types::Block, + /// rw_table padding meta data + pub rw_padding_meta: BTreeMap, } impl Block { @@ -129,9 +139,9 @@ impl Block { /// Obtains the expected Circuit degree needed in order to be able to test /// the EvmCircuit with this block without needing to configure the /// `ConstraintSystem`. - pub fn get_test_degree(&self) -> u32 { + pub fn get_test_degree(&self, chunk: &Chunk) -> u32 { let num_rows_required_for_execution_steps: usize = - EvmCircuit::::get_num_rows_required(self); + EvmCircuit::::get_num_rows_required(self, chunk); let num_rows_required_for_rw_table: usize = self.circuits_params.max_rws; let num_rows_required_for_fixed_table: usize = detect_fixed_table_tags(self) .iter() @@ -300,15 +310,35 @@ pub fn block_convert( let block = &builder.block; let code_db = &builder.code_db; let rws = RwMap::from(&block.container); + let by_address_rws = rws.table_assignments(false); rws.check_value(); + + // get padding statistics data via BtreeMap + // TODO we can implement it in more efficient version via range sum + let rw_padding_meta = builder + .chunks + .iter() + .fold(BTreeMap::new(), |mut map, chunk| { + assert!( + chunk.ctx.rwc.0.saturating_sub(1) <= builder.circuits_params.max_rws, + "max_rws size {} must larger than chunk rws size {}", + builder.circuits_params.max_rws, + chunk.ctx.rwc.0.saturating_sub(1), + ); + // [chunk.ctx.rwc.0, builder.circuits_params.max_rws) + (chunk.ctx.rwc.0..builder.circuits_params.max_rws).for_each(|padding_rw_counter| { + *map.entry(padding_rw_counter).or_insert(0) += 1; + }); + map + }); + let mut block = Block { // randomness: F::from(0x100), // Special value to reveal elements after RLC randomness: F::from(0xcafeu64), context: block.into(), rws, + by_address_rws, txs: block.txs().to_vec(), - end_block_not_last: block.block_steps.end_block_not_last.clone(), - end_block_last: block.block_steps.end_block_last.clone(), bytecodes: code_db.clone(), copy_events: block.copy_events.clone(), exp_events: block.exp_events.clone(), @@ -320,8 +350,13 @@ pub fn block_convert( keccak_inputs: circuit_input_builder::keccak_inputs(block, code_db)?, precompile_events: block.precompile_events.clone(), eth_block: block.eth_block.clone(), + end_block: block.end_block.clone(), + rw_padding_meta, }; let public_data = public_data_convert(&block); + + // We can use params from block + // because max_txs and max_calldata are independent from Chunk let rpi_bytes = public_data.get_pi_bytes( block.circuits_params.max_txs, block.circuits_params.max_withdrawals, @@ -329,5 +364,35 @@ pub fn block_convert( ); // PI Circuit block.keccak_inputs.extend_from_slice(&[rpi_bytes]); + Ok(block) } + +#[allow(dead_code)] +fn get_rwtable_fingerprints( + alpha: F, + gamma: F, + prev_continuous_fingerprint: F, + rows: &Vec, +) -> RwFingerprints { + let x = rows.to2dvec(); + let fingerprints = get_permutation_fingerprints( + &x, + Value::known(alpha), + Value::known(gamma), + Value::known(prev_continuous_fingerprint), + ); + + fingerprints + .first() + .zip(fingerprints.last()) + .map(|((first_acc, first_row), (last_acc, last_row))| { + RwFingerprints::new( + unwrap_value(*first_row), + unwrap_value(*last_row), + unwrap_value(*first_acc), + unwrap_value(*last_acc), + ) + }) + .unwrap_or_default() +} diff --git a/zkevm-circuits/src/witness/chunk.rs b/zkevm-circuits/src/witness/chunk.rs new file mode 100755 index 0000000000..92097737a1 --- /dev/null +++ b/zkevm-circuits/src/witness/chunk.rs @@ -0,0 +1,317 @@ +use std::iter; + +/// +use super::{ + rw::{RwFingerprints, ToVec}, + Block, ExecStep, Rw, RwMap, RwRow, +}; +use crate::util::unwrap_value; +use bus_mapping::{ + circuit_input_builder::{self, Call, ChunkContext, FixedCParams}, + operation::Target, + Error, +}; +use eth_types::Field; +use gadgets::permutation::get_permutation_fingerprints; +use halo2_proofs::circuit::Value; +use itertools::Itertools; + +/// [`Chunk`]` is the struct used by all circuits, which contains chunkwise +/// data for witness generation. Used with [`Block`] for blockwise witness. +#[derive(Debug, Clone)] +pub struct Chunk { + /// BeginChunk step to propagate State + pub begin_chunk: Option, + /// EndChunk step that appears in the last EVM row for all the chunks other than the last. + pub end_chunk: Option, + /// Padding step that is repeated before max_rws is reached + pub padding: Option, + /// Chunk context + pub chunk_context: ChunkContext, + /// Read write events in the chronological sorted RwTable + pub chrono_rws: RwMap, + /// Read write events in the by address sorted RwTable + pub by_address_rws: RwMap, + /// Permutation challenge alpha + pub permu_alpha: F, + /// Permutation challenge gamma + pub permu_gamma: F, + + /// Current rw_table permutation fingerprint + pub by_address_rw_fingerprints: RwFingerprints, + /// Current chronological rw_table permutation fingerprint + pub chrono_rw_fingerprints: RwFingerprints, + + /// Fixed param for the chunk + pub fixed_param: FixedCParams, + + /// The last call of previous chunk if any, used for assigning continuation + pub prev_last_call: Option, + /// + pub prev_chunk_last_chrono_rw: Option, + /// + pub prev_chunk_last_by_address_rw: Option, +} + +impl Default for Chunk { + fn default() -> Self { + // One fixed param chunk with randomness = 1 + // RwFingerprints rw acc starts with 0 and fingerprints = 1 + Self { + begin_chunk: None, + end_chunk: None, + padding: None, + chunk_context: ChunkContext::default(), + chrono_rws: RwMap::default(), + by_address_rws: RwMap::default(), + permu_alpha: F::from(1), + permu_gamma: F::from(1), + by_address_rw_fingerprints: RwFingerprints::default(), + chrono_rw_fingerprints: RwFingerprints::default(), + fixed_param: FixedCParams::default(), + prev_last_call: None, + prev_chunk_last_chrono_rw: None, + prev_chunk_last_by_address_rw: None, + } + } +} + +/// Convert the idx-th chunk struct in bus-mapping to a witness chunk used in circuits +pub fn chunk_convert( + block: &Block, + builder: &circuit_input_builder::CircuitInputBuilder, +) -> Result>, Error> { + let (by_address_rws, padding_meta) = (&block.by_address_rws, &block.rw_padding_meta); + + // Todo: poseidon hash to compute alpha/gamma + let alpha = F::from(103); + let gamma = F::from(101); + + let mut chunks: Vec> = Vec::with_capacity(builder.chunks.len()); + for (i, (prev_chunk, chunk)) in iter::once(None) // left append `None` to make iteration easier + .chain(builder.chunks.iter().map(Some)) + .tuple_windows() + .enumerate() + { + let chunk = chunk.unwrap(); // current chunk always there + let prev_chunk_last_chrono_rw = prev_chunk.map(|prev_chunk| { + assert!(builder.circuits_params.max_rws > 0); + let chunk_inner_rwc = prev_chunk.ctx.rwc.0; + if chunk_inner_rwc.saturating_sub(1) == builder.circuits_params.max_rws { + // if prev chunk rws are full, then get the last rwc + RwMap::get_rw(&builder.block.container, prev_chunk.ctx.end_rwc - 1) + .expect("Rw does not exist") + } else { + // last is the padding row + Rw::Padding { + rw_counter: builder.circuits_params.max_rws - 1, + } + } + }); + + // Get the rws in the i-th chunk + let chrono_rws = { + let mut chrono_rws = RwMap::from(&builder.block.container); + // remove paading here since it will be attached later + if let Some(padding_vec) = chrono_rws.0.get_mut(&Target::Padding) { + padding_vec.clear() + } + chrono_rws.take_rw_counter_range(chunk.ctx.initial_rwc, chunk.ctx.end_rwc) + }; + + let (prev_chunk_last_by_address_rw, by_address_rws) = { + // by_address_rws + let start = chunk.ctx.idx * builder.circuits_params.max_rws; + let size = builder.circuits_params.max_rws; + // by_address_rws[start..end].to_vec() + + let skipped = by_address_rws + .iter() + // remove paading here since it will be attached later + .filter(|rw| rw.tag() != Target::Padding) + .cloned() // TODO avoid clone here + .chain(padding_meta.iter().flat_map(|(k, v)| { + vec![ + Rw::Padding { rw_counter: *k }; + >::try_into(*v).unwrap() + ] + })); + // there is no previous chunk + if start == 0 { + (None, RwMap::from(skipped.take(size).collect::>())) + } else { + // here we got `chunk.ctx.idx - 1` because each chunk first row are propagated from + // prev chunk. giving idx>0 th chunk, there will be (idx-1) placeholders. + let mut skipped = skipped.skip(start - 1 - (chunk.ctx.idx - 1)); + let prev_chunk_last_by_address_rw = skipped.next(); + ( + prev_chunk_last_by_address_rw, + RwMap::from(skipped.take(size).collect::>()), + ) + } + }; + + // Compute cur fingerprints from last fingerprints and current Rw rows + let by_address_rw_fingerprints = get_permutation_fingerprint_of_rwmap( + &by_address_rws, + chunk.fixed_param.max_rws, + alpha, + gamma, + if i == 0 { + F::from(1) + } else { + chunks[i - 1].by_address_rw_fingerprints.mul_acc + }, + false, + prev_chunk_last_by_address_rw, + ); + + let chrono_rw_fingerprints = get_permutation_fingerprint_of_rwmap( + &chrono_rws, + chunk.fixed_param.max_rws, + alpha, + gamma, + if i == 0 { + F::from(1) + } else { + chunks[i - 1].chrono_rw_fingerprints.mul_acc + }, + true, + prev_chunk_last_chrono_rw, + ); + chunks.push(Chunk { + permu_alpha: alpha, + permu_gamma: gamma, + by_address_rw_fingerprints, + chrono_rw_fingerprints, + begin_chunk: chunk.begin_chunk.clone(), + end_chunk: chunk.end_chunk.clone(), + padding: chunk.padding.clone(), + chunk_context: chunk.ctx.clone(), + chrono_rws, + by_address_rws, + fixed_param: chunk.fixed_param, + prev_last_call: chunk.prev_last_call.clone(), + prev_chunk_last_chrono_rw, + prev_chunk_last_by_address_rw, + }); + } + + if log::log_enabled!(log::Level::Debug) { + chunks + .iter() + .enumerate() + .for_each(|(i, chunk)| log::debug!("{}th chunk context {:?}", i, chunk,)); + } + + Ok(chunks) +} + +/// +pub fn get_rwtable_fingerprints( + alpha: F, + gamma: F, + prev_continuous_fingerprint: F, + rows: &Vec, +) -> RwFingerprints { + let x = rows.to2dvec(); + let fingerprints = get_permutation_fingerprints( + &x, + Value::known(alpha), + Value::known(gamma), + Value::known(prev_continuous_fingerprint), + ); + + fingerprints + .first() + .zip(fingerprints.last()) + .map(|((first_acc, first_row), (last_acc, last_row))| { + RwFingerprints::new( + unwrap_value(*first_row), + unwrap_value(*last_row), + unwrap_value(*first_acc), + unwrap_value(*last_acc), + ) + }) + .unwrap_or_default() +} + +/// +pub fn get_permutation_fingerprint_of_rwmap( + rwmap: &RwMap, + max_row: usize, + alpha: F, + gamma: F, + prev_continuous_fingerprint: F, + is_chrono: bool, + padding_start_rw: Option, +) -> RwFingerprints { + get_permutation_fingerprint_of_rwvec( + &rwmap.table_assignments(is_chrono), + max_row, + alpha, + gamma, + prev_continuous_fingerprint, + padding_start_rw, + ) +} + +/// +pub fn get_permutation_fingerprint_of_rwvec( + rwvec: &[Rw], + max_row: usize, + alpha: F, + gamma: F, + prev_continuous_fingerprint: F, + padding_start_rw: Option, +) -> RwFingerprints { + get_permutation_fingerprint_of_rwrowvec( + &rwvec + .iter() + .map(|row| row.table_assignment()) + .collect::>>>(), + max_row, + alpha, + gamma, + prev_continuous_fingerprint, + padding_start_rw.map(|r| r.table_assignment()), + ) +} + +/// +pub fn get_permutation_fingerprint_of_rwrowvec( + rwrowvec: &[RwRow>], + max_row: usize, + alpha: F, + gamma: F, + prev_continuous_fingerprint: F, + padding_start_rwrow: Option>>, +) -> RwFingerprints { + let (rows, _) = RwRow::padding(rwrowvec, max_row, padding_start_rwrow); + let x = rows.to2dvec(); + let fingerprints = get_permutation_fingerprints( + &x, + Value::known(alpha), + Value::known(gamma), + Value::known(prev_continuous_fingerprint), + ); + + fingerprints + .first() + .zip(fingerprints.last()) + .map(|((first_acc, first_row), (last_acc, last_row))| { + RwFingerprints::new( + unwrap_value(*first_row), + unwrap_value(*last_row), + unwrap_value(*first_acc), + unwrap_value(*last_acc), + ) + }) + .unwrap_or_default() +} + +/// +pub fn get_permutation_randomness() -> (F, F) { + // Todo + (F::from(1), F::from(1)) +} diff --git a/zkevm-circuits/src/witness/rw.rs b/zkevm-circuits/src/witness/rw.rs index 950a8329b1..a87f819d9a 100644 --- a/zkevm-circuits/src/witness/rw.rs +++ b/zkevm-circuits/src/witness/rw.rs @@ -1,21 +1,30 @@ //! The Read-Write table related structs -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet}, + iter, +}; use bus_mapping::{ exec_trace::OperationRef, - operation::{self, AccountField, CallContextField, Target, TxLogField, TxReceiptField}, + operation::{ + self, AccountField, CallContextField, StepStateField, Target, TxLogField, TxReceiptField, + }, }; use eth_types::{Address, Field, ToAddress, ToScalar, Word, U256}; use halo2_proofs::circuit::Value; use itertools::Itertools; use crate::{ - table::{AccountFieldTag, CallContextFieldTag, TxLogFieldTag, TxReceiptFieldTag}, - util::{build_tx_log_address, word::WordLoHi}, + table::{ + AccountFieldTag, CallContextFieldTag, StepStateFieldTag, TxLogFieldTag, TxReceiptFieldTag, + }, + util::{build_tx_log_address, unwrap_value, word::WordLoHi}, }; use super::MptUpdates; +const U64_BYTES: usize = u64::BITS as usize / 8usize; + /// Rw container for a witness block #[derive(Debug, Default, Clone)] pub struct RwMap(pub HashMap>); @@ -37,25 +46,25 @@ impl std::ops::Index for RwMap { } impl RwMap { - /// Check rw_counter is continuous and starting from 1 + /// Check rw_counter is continuous pub fn check_rw_counter_sanity(&self) { - for (idx, rw_counter) in self + for (rw_counter_prev, rw_counter_cur) in self .0 .iter() - .filter(|(tag, _rs)| !matches!(tag, Target::Start)) + .filter(|(tag, _rs)| !matches!(tag, Target::Padding) && !matches!(tag, Target::Start)) .flat_map(|(_tag, rs)| rs) .map(|r| r.rw_counter()) .sorted() - .enumerate() + .tuple_windows() { - debug_assert_eq!(idx, rw_counter - 1); + debug_assert_eq!(rw_counter_cur - rw_counter_prev, 1); } } /// Check value in the same way like StateCircuit pub fn check_value(&self) { let err_msg_first = "first access reads don't change value"; let err_msg_non_first = "non-first access reads don't change value"; - let rows = self.table_assignments(); + let rows = self.table_assignments(false); let updates = MptUpdates::mock_from(&rows); let mut errs = Vec::new(); for idx in 1..rows.len() { @@ -77,12 +86,13 @@ impl RwMap { let value = row.value_assignment(); if is_first { // value == init_value - let init_value = updates - .get(row) - .map(|u| u.value_assignments().1) - .unwrap_or_default(); - if value != init_value { - errs.push((idx, err_msg_first, *row, *prev_row)); + if let Some(init_value) = updates.get(row).map(|u| u.value_assignments().1) { + if value != init_value { + errs.push((idx, err_msg_first, *row, *prev_row)); + } + } + if row.tag() == Target::CallContext { + println!("call context value: {:?}", row); } } else { // value == prev_value @@ -107,11 +117,11 @@ impl RwMap { } } } - /// Calculates the number of Rw::Start rows needed. + /// Calculates the number of Rw::Padding rows needed. /// `target_len` is allowed to be 0 as an "auto" mode, - /// then only 1 Rw::Start row will be prepadded. + /// return padding size also allow to be 0, means no padding pub(crate) fn padding_len(rows_len: usize, target_len: usize) -> usize { - if target_len > rows_len { + if target_len >= rows_len { target_len - rows_len } else { if target_len != 0 { @@ -120,38 +130,95 @@ impl RwMap { target_len, rows_len ); } - 1 + 0 } } - /// Prepad Rw::Start rows to target length - pub fn table_assignments_prepad(rows: &[Rw], target_len: usize) -> (Vec, usize) { - // Remove Start rows as we will add them from scratch. - let rows: Vec = rows + /// padding Rw::Start/Rw::Padding accordingly + pub fn table_assignments_padding( + rows: &[Rw], + target_len: usize, + padding_start_rw: Option, + ) -> (Vec, usize) { + let mut padding_exist = HashSet::new(); + // Remove Start/Padding rows as we will add them from scratch. + let rows_trimmed: Vec = rows .iter() - .skip_while(|rw| matches!(rw, Rw::Start { .. })) + .filter(|rw| { + if let Rw::Padding { rw_counter } = rw { + padding_exist.insert(*rw_counter); + } + + !matches!(rw, Rw::Start { .. }) + }) .cloned() .collect(); - let padding_length = Self::padding_len(rows.len(), target_len); - let padding = (1..=padding_length).map(|rw_counter| Rw::Start { rw_counter }); - (padding.chain(rows).collect(), padding_length) + let padding_length = { + let length = Self::padding_len(rows_trimmed.len(), target_len); + length.saturating_sub(1) + }; + + // padding rw_counter starting from + // +1 for to including padding_start row + let start_padding_rw_counter = rows_trimmed.len() + 1; + + let padding = (start_padding_rw_counter..).flat_map(|rw_counter| { + if padding_exist.contains(&rw_counter) { + None + } else { + Some(Rw::Padding { rw_counter }) + } + }); + ( + iter::empty() + .chain([padding_start_rw.unwrap_or(Rw::Start { rw_counter: 1 })]) + .chain(rows_trimmed) + .chain(padding) + .take(target_len) + .collect(), + padding_length, + ) } /// Build Rws for assignment - pub fn table_assignments(&self) -> Vec { + pub fn table_assignments(&self, keep_chronological_order: bool) -> Vec { let mut rows: Vec = self.0.values().flatten().cloned().collect(); - rows.sort_by_key(|row| { - ( - row.tag() as u64, - row.id().unwrap_or_default(), - row.address().unwrap_or_default(), - row.field_tag().unwrap_or_default(), - row.storage_key().unwrap_or_default(), - row.rw_counter(), - ) - }); + if keep_chronological_order { + rows.sort_by_key(|row| (row.rw_counter(), row.tag() as u64)); + } else { + rows.sort_by_key(|row| { + ( + row.tag() as u64, + row.id().unwrap_or_default(), + row.address().unwrap_or_default(), + row.field_tag().unwrap_or_default(), + row.storage_key().unwrap_or_default(), + row.rw_counter(), + ) + }); + } + rows } -} + /// take only rw_counter within range + pub fn take_rw_counter_range(mut self, start_rwc: usize, end_rwc: usize) -> Self { + for rw in self.0.values_mut() { + rw.retain(|r| r.rw_counter() >= start_rwc && r.rw_counter() < end_rwc) + } + self + } + /// Get one Rw for a chunk specified by index + pub fn get_rw(container: &operation::OperationContainer, counter: usize) -> Option { + let rws: Self = container.into(); + for rwv in rws.0.values() { + for rw in rwv { + if rw.rw_counter() == counter { + return Some(*rw); + } + } + } + None + } +} #[allow( missing_docs, reason = "Some of the docs are tedious and can be found at https://github.com/privacy-scaling-explorations/zkevm-specs/blob/master/specs/tables.md" @@ -256,6 +323,40 @@ pub enum Rw { field_tag: TxReceiptFieldTag, value: u64, }, + + /// StepState + StepState { + rw_counter: usize, + is_write: bool, + field_tag: StepStateFieldTag, + value: Word, + }, + + /// ... + + /// Padding, must be the largest enum + Padding { rw_counter: usize }, +} + +/// general to vector +pub trait ToVec { + /// to 2d vec + fn to2dvec(&self) -> Vec>; +} + +impl ToVec> for Vec { + fn to2dvec(&self) -> Vec>> { + self.iter() + .map(|row| { + row.table_assignment::() + .unwrap() + .values() + .iter() + .map(|f| Value::known(*f)) + .collect::>>() + }) + .collect::>>>() + } } /// Rw table row assignment @@ -309,7 +410,7 @@ impl RwRow> { _ = f.map(|v| { inner = Some(v); }); - inner.unwrap() + inner.unwrap_or_default() }; let unwrap_w = |f: WordLoHi>| { let (lo, hi) = f.into_lo_hi(); @@ -329,6 +430,92 @@ impl RwRow> { init_val: unwrap_w(self.init_val), } } + + /// padding Rw::Start/Rw::Padding accordingly + pub fn padding( + rows: &[RwRow>], + target_len: usize, + padding_start_rwrow: Option>>, + ) -> (Vec>>, usize) { + let mut padding_exist = HashSet::new(); + // Remove Start/Padding rows as we will add them from scratch. + let rows_trimmed = rows + .iter() + .filter(|rw| { + let tag = unwrap_value(rw.tag); + + if tag == F::from(Target::Padding as u64) { + let rw_counter = u64::from_le_bytes( + unwrap_value(rw.rw_counter).to_repr()[..U64_BYTES] + .try_into() + .unwrap(), + ); + padding_exist.insert(rw_counter); + } + tag != F::from(Target::Start as u64) && tag != F::ZERO // 0 is invalid tag + }) + .cloned() + .collect::>>>(); + let padding_length = { + let length = RwMap::padding_len(rows_trimmed.len(), target_len); + length.saturating_sub(1) // first row always got padding + }; + let start_padding_rw_counter = { + let start_padding_rw_counter = F::from(rows_trimmed.len() as u64) + F::ONE; + // Assume root of unity < 2**64 + assert!( + start_padding_rw_counter.to_repr()[U64_BYTES..] + .iter() + .cloned() + .sum::() + == 0, + "rw counter > 2 ^ 64" + ); + u64::from_le_bytes( + start_padding_rw_counter.to_repr()[..U64_BYTES] + .try_into() + .unwrap(), + ) + } as usize; + + let padding = (start_padding_rw_counter..).flat_map(|rw_counter| { + if padding_exist.contains(&rw_counter.try_into().unwrap()) { + None + } else { + Some(RwRow { + rw_counter: Value::known(F::from(rw_counter as u64)), + tag: Value::known(F::from(Target::Padding as u64)), + ..Default::default() + }) + } + }); + ( + iter::once(padding_start_rwrow.unwrap_or(RwRow { + rw_counter: Value::known(F::ONE), + tag: Value::known(F::from(Target::Start as u64)), + ..Default::default() + })) + .chain(rows_trimmed) + .chain(padding) + .take(target_len) + .collect(), + padding_length, + ) + } +} + +impl ToVec> for Vec>> { + fn to2dvec(&self) -> Vec>> { + self.iter() + .map(|row| { + row.unwrap() + .values() + .iter() + .map(|f| Value::known(*f)) + .collect::>>() + }) + .collect::>>>() + } } impl Rw { @@ -474,6 +661,7 @@ impl Rw { pub(crate) fn rw_counter(&self) -> usize { match self { Self::Start { rw_counter } + | Self::Padding { rw_counter } | Self::Memory { rw_counter, .. } | Self::Stack { rw_counter, .. } | Self::AccountStorage { rw_counter, .. } @@ -482,6 +670,7 @@ impl Rw { | Self::TxRefund { rw_counter, .. } | Self::Account { rw_counter, .. } | Self::CallContext { rw_counter, .. } + | Self::StepState { rw_counter, .. } | Self::TxLog { rw_counter, .. } | Self::TxReceipt { rw_counter, .. } => *rw_counter, } @@ -489,7 +678,7 @@ impl Rw { pub(crate) fn is_write(&self) -> bool { match self { - Self::Start { .. } => false, + Self::Padding { .. } | Self::Start { .. } => false, Self::Memory { is_write, .. } | Self::Stack { is_write, .. } | Self::AccountStorage { is_write, .. } @@ -498,6 +687,7 @@ impl Rw { | Self::TxRefund { is_write, .. } | Self::Account { is_write, .. } | Self::CallContext { is_write, .. } + | Self::StepState { is_write, .. } | Self::TxLog { is_write, .. } | Self::TxReceipt { is_write, .. } => *is_write, } @@ -505,6 +695,7 @@ impl Rw { pub(crate) fn tag(&self) -> Target { match self { + Self::Padding { .. } => Target::Padding, Self::Start { .. } => Target::Start, Self::Memory { .. } => Target::Memory, Self::Stack { .. } => Target::Stack, @@ -516,6 +707,7 @@ impl Rw { Self::CallContext { .. } => Target::CallContext, Self::TxLog { .. } => Target::TxLog, Self::TxReceipt { .. } => Target::TxReceipt, + Self::StepState { .. } => Target::StepState, } } @@ -530,7 +722,10 @@ impl Rw { Self::CallContext { call_id, .. } | Self::Stack { call_id, .. } | Self::Memory { call_id, .. } => Some(*call_id), - Self::Start { .. } | Self::Account { .. } => None, + Self::Padding { .. } + | Self::Start { .. } + | Self::Account { .. } + | Self::StepState { .. } => None, } } @@ -561,8 +756,10 @@ impl Rw { // make field_tag fit into one limb (16 bits) Some(build_tx_log_address(*index as u64, *field_tag, *log_id)) } - Self::Start { .. } + Self::Padding { .. } + | Self::Start { .. } | Self::CallContext { .. } + | Self::StepState { .. } | Self::TxRefund { .. } | Self::TxReceipt { .. } => None, } @@ -572,8 +769,10 @@ impl Rw { match self { Self::Account { field_tag, .. } => Some(*field_tag as u64), Self::CallContext { field_tag, .. } => Some(*field_tag as u64), + Self::StepState { field_tag, .. } => Some(*field_tag as u64), Self::TxReceipt { field_tag, .. } => Some(*field_tag as u64), - Self::Start { .. } + Self::Padding { .. } + | Self::Start { .. } | Self::Memory { .. } | Self::Stack { .. } | Self::AccountStorage { .. } @@ -588,8 +787,10 @@ impl Rw { match self { Self::AccountStorage { storage_key, .. } | Self::TxAccessListAccountStorage { storage_key, .. } => Some(*storage_key), - Self::Start { .. } + Self::Padding { .. } + | Self::Start { .. } | Self::CallContext { .. } + | Self::StepState { .. } | Self::Stack { .. } | Self::Memory { .. } | Self::TxRefund { .. } @@ -602,8 +803,9 @@ impl Rw { pub(crate) fn value_assignment(&self) -> Word { match self { - Self::Start { .. } => U256::zero(), + Self::Padding { .. } | Self::Start { .. } => U256::zero(), Self::CallContext { value, .. } + | Self::StepState { value, .. } | Self::Account { value, .. } | Self::AccountStorage { value, .. } | Self::Stack { value, .. } @@ -625,10 +827,12 @@ impl Rw { Some(U256::from(*is_warm_prev as u64)) } Self::TxRefund { value_prev, .. } => Some(U256::from(*value_prev)), - Self::Start { .. } + Self::Padding { .. } + | Self::Start { .. } | Self::Stack { .. } | Self::Memory { .. } | Self::CallContext { .. } + | Self::StepState { .. } | Self::TxLog { .. } | Self::TxReceipt { .. } => None, } @@ -644,10 +848,123 @@ impl Rw { } } +impl From> for RwMap { + fn from(rws: Vec) -> Self { + let mut rw_map = HashMap::>::default(); + for rw in rws { + match rw { + Rw::Account { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::Account) { + vrw.push(rw) + } else { + rw_map.insert(Target::Account, vec![rw]); + } + } + Rw::AccountStorage { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::Storage) { + vrw.push(rw) + } else { + rw_map.insert(Target::Storage, vec![rw]); + } + } + Rw::TxAccessListAccount { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::TxAccessListAccount) { + vrw.push(rw) + } else { + rw_map.insert(Target::TxAccessListAccount, vec![rw]); + } + } + Rw::TxAccessListAccountStorage { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::TxAccessListAccountStorage) { + vrw.push(rw) + } else { + rw_map.insert(Target::TxAccessListAccountStorage, vec![rw]); + } + } + Rw::Padding { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::Padding) { + vrw.push(rw) + } else { + rw_map.insert(Target::Padding, vec![rw]); + } + } + Rw::Start { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::Start) { + vrw.push(rw) + } else { + rw_map.insert(Target::Start, vec![rw]); + } + } + Rw::Stack { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::Stack) { + vrw.push(rw) + } else { + rw_map.insert(Target::Stack, vec![rw]); + } + } + Rw::Memory { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::Memory) { + vrw.push(rw) + } else { + rw_map.insert(Target::Memory, vec![rw]); + } + } + Rw::CallContext { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::CallContext) { + vrw.push(rw) + } else { + rw_map.insert(Target::CallContext, vec![rw]); + } + } + Rw::TxLog { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::TxLog) { + vrw.push(rw) + } else { + rw_map.insert(Target::TxLog, vec![rw]); + } + } + Rw::TxReceipt { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::TxReceipt) { + vrw.push(rw) + } else { + rw_map.insert(Target::TxReceipt, vec![rw]); + } + } + Rw::TxRefund { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::TxRefund) { + vrw.push(rw) + } else { + rw_map.insert(Target::TxRefund, vec![rw]); + } + } + Rw::StepState { .. } => { + if let Some(vrw) = rw_map.get_mut(&Target::StepState) { + vrw.push(rw) + } else { + rw_map.insert(Target::StepState, vec![rw]); + } + } + }; + } + Self(rw_map) + } +} + impl From<&operation::OperationContainer> for RwMap { fn from(container: &operation::OperationContainer) -> Self { - let mut rws = HashMap::default(); + // Get rws raning all indices from the whole container + let mut rws = HashMap::>::default(); + rws.insert( + Target::Padding, + container + .padding + .iter() + .map(|op| Rw::Padding { + rw_counter: op.rwc().into(), + }) + .collect(), + ); rws.insert( Target::Start, container @@ -811,7 +1128,9 @@ impl From<&operation::OperationContainer> for RwMap { is_write: op.rw().is_write(), call_id: op.op().call_id(), memory_address: u64::from_le_bytes( - op.op().address().to_le_bytes()[..8].try_into().unwrap(), + op.op().address().to_le_bytes()[..U64_BYTES] + .try_into() + .unwrap(), ), byte: op.op().value(), }) @@ -855,7 +1174,63 @@ impl From<&operation::OperationContainer> for RwMap { }) .collect(), ); - + rws.insert( + Target::StepState, + container + .step_state + .iter() + .map(|op| Rw::StepState { + rw_counter: op.rwc().into(), + is_write: op.rw().is_write(), + field_tag: match op.op().field { + StepStateField::CallID => StepStateFieldTag::CallID, + StepStateField::IsRoot => StepStateFieldTag::IsRoot, + StepStateField::IsCreate => StepStateFieldTag::IsCreate, + StepStateField::CodeHash => StepStateFieldTag::CodeHash, + StepStateField::ProgramCounter => StepStateFieldTag::ProgramCounter, + StepStateField::StackPointer => StepStateFieldTag::StackPointer, + StepStateField::GasLeft => StepStateFieldTag::GasLeft, + StepStateField::MemoryWordSize => StepStateFieldTag::MemoryWordSize, + StepStateField::ReversibleWriteCounter => { + StepStateFieldTag::ReversibleWriteCounter + } + StepStateField::LogID => StepStateFieldTag::LogID, + }, + value: op.op().value, + }) + .collect(), + ); Self(rws) } } + +/// RwFingerprints +#[derive(Debug, Clone)] +pub struct RwFingerprints { + /// last chunk fingerprint = row0 * row1 * ... rowi + pub prev_mul_acc: F, + /// cur chunk fingerprint + pub mul_acc: F, + /// last chunk last row = alpha - (gamma^1 x1 + gamma^2 x2 + ...) + pub prev_ending_row: F, + /// cur chunk last row + pub ending_row: F, +} + +impl RwFingerprints { + /// new by value + pub fn new(row_prev: F, row: F, acc_prev: F, acc: F) -> Self { + Self { + prev_mul_acc: acc_prev, + mul_acc: acc, + prev_ending_row: row_prev, + ending_row: row, + } + } +} + +impl Default for RwFingerprints { + fn default() -> Self { + Self::new(F::from(0), F::from(0), F::from(1), F::from(1)) + } +} diff --git a/zkevm-circuits/tests/prover_error.rs b/zkevm-circuits/tests/prover_error.rs index f19375ca1c..3b6ef967df 100644 --- a/zkevm-circuits/tests/prover_error.rs +++ b/zkevm-circuits/tests/prover_error.rs @@ -13,7 +13,11 @@ use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; use mock::{test_ctx::LoggerConfig, test_ctx2::gen_geth_traces}; use serde_json::{from_value, Value}; use std::{collections::HashMap, fs::File, io::BufReader}; -use zkevm_circuits::{super_circuit::SuperCircuit, util::SubCircuit, witness::block_convert}; +use zkevm_circuits::{ + super_circuit::SuperCircuit, + util::SubCircuit, + witness::{block_convert, chunk_convert}, +}; #[derive(serde::Deserialize)] struct MyAccount { @@ -91,12 +95,16 @@ fn prover_error() { let builder = builder .handle_block(&geth_data.eth_block, &geth_data.geth_traces) .expect("handle_block"); - let block_witness = { + let (block, chunk) = { let mut block = block_convert(&builder).expect("block_convert"); + let chunk = chunk_convert(&block, &builder) + .expect("chunk_convert") + .remove(0); + block.randomness = Fr::from(MOCK_RANDOMNESS); - block + (block, chunk) }; - let circuit = SuperCircuit::new_from_block(&block_witness); + let circuit = SuperCircuit::new_from_block(&block, &chunk); let res = MockProver::run(k, &circuit, circuit.instance()) .expect("MockProver::run") .verify();