Skip to content

Commit

Permalink
Bus-mapping for opcode calldatacopy (privacy-scaling-explorations#393)
Browse files Browse the repository at this point in the history
* Support generating multiple exec-steps from one geth-step.

* Fix build error.

* Fix to use bus-mapping to generate bytecode.

* Update test cases.

* Add basic of calldatacopy bus-mappinng to just support zero call data length.

* Update bus-mapping calldatacopy.

* Push op of call_data_length and call data offset.

* Add `is_root_call` to BytecodeTestConfig.

* Replace `OpcodeId` with `ExecState` in bus-mapping ExcStep.

* Generate CopyToMemory exection step.

* Add TransactionConfig to bus-mapping handle_tx.

* Update test code.

* 1. Remove TransactionConfig and replace with `call_data`.
2. Remove gas calculation and set in `calldatacopy` bus-mapping.
3. Revert `test_ok_internal` since not want to set internal call.

* Update test cases which call `handle_tx` and `new_tx` of circuit input builder.

* Update constant max address of state circuit.

* Add unit test for calldatacopy bus-mapping.

* change api

* fix calldatacopy

* fix rebase

* Set exec_state for BeginTx and EndTx.

* Fix to return a new exec step in function `dummy_gen_associated_ops`.

* Update bus-mapping calldatacopy unit-test.

* Update for fmt and clippy.

* Fix doc test.

* Update according to code review.

* Fix a comment.

* Fix to directly use StepAuxiliaryData of bus-mapping in zkevm-circuits.

* Revert a comment and a fix.

* address comments

* fix doc

Co-authored-by: Haichen Shen <[email protected]>
Co-authored-by: Zhang Zhuo <[email protected]>
  • Loading branch information
3 people authored Mar 21, 2022
1 parent ae3385e commit ea5b823
Show file tree
Hide file tree
Showing 26 changed files with 758 additions and 461 deletions.
206 changes: 148 additions & 58 deletions bus-mapping/src/circuit_input_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::rpc::GethClient;
use ethers_providers::JsonRpcClient;

/// Out of Gas errors by opcode
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum OogError {
/// Out of Gas for opcodes which have non-zero constant gas cost
Constant,
Expand Down Expand Up @@ -65,7 +65,7 @@ pub enum OogError {
}

/// EVM Execution Error
#[derive(Debug, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub enum ExecError {
/// Invalid Opcode
InvalidOpcode,
Expand Down Expand Up @@ -101,11 +101,71 @@ pub enum ExecError {
MaxCodeSizeExceeded,
}

/// Execution state
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ExecState {
/// EVM Opcode ID
Op(OpcodeId),
/// Virtual step Begin Tx
BeginTx,
/// Virtual step End Tx
EndTx,
/// Virtual step Copy To Memory
CopyToMemory,
}

impl ExecState {
/// Returns `true` if `ExecState` is an opcode and the opcode is a `PUSHn`.
pub fn is_push(&self) -> bool {
if let ExecState::Op(op) = self {
op.is_push()
} else {
false
}
}

/// Returns `true` if `ExecState` is an opcode and the opcode is a `DUPn`.
pub fn is_dup(&self) -> bool {
if let ExecState::Op(op) = self {
op.is_dup()
} else {
false
}
}

/// Returns `true` if `ExecState` is an opcode and the opcode is a `SWAPn`.
pub fn is_swap(&self) -> bool {
if let ExecState::Op(op) = self {
op.is_swap()
} else {
false
}
}
}

/// Auxiliary data of Execution step
#[derive(Clone, Debug)]
pub enum StepAuxiliaryData {
/// Auxiliary data of Copy To Memory
CopyToMemory {
/// Source start address
src_addr: u64,
/// Destination address
dst_addr: u64,
/// Bytes left
bytes_left: u64,
/// Source end address
src_addr_end: u64,
/// Indicate if copy from transaction call data
from_tx: bool,
},
}

/// An execution step of the EVM.
#[derive(Debug)]
#[derive(Clone, Debug)]
pub struct ExecStep {
/// The opcode ID
pub op: OpcodeId,
/// Execution state
pub exec_state: ExecState,
/// Program Counter
pub pc: ProgramCounter,
/// Stack size
Expand All @@ -129,18 +189,20 @@ pub struct ExecStep {
pub bus_mapping_instance: Vec<OperationRef>,
/// Error generated by this step
pub error: Option<ExecError>,
/// Step auxiliary data
pub aux_data: Option<StepAuxiliaryData>,
}

impl ExecStep {
/// Create a new Self from a [`GethExecStep`].
/// Create a new Self from a `GethExecStep`.
pub fn new(
step: &GethExecStep,
call_index: usize,
rwc: RWCounter,
swc: usize, // State Write Counter
) -> Self {
ExecStep {
op: step.op,
exec_state: ExecState::Op(step.op),
pc: step.pc,
stack_size: step.stack.0.len(),
memory_size: step.memory.0.len(),
Expand All @@ -151,14 +213,15 @@ impl ExecStep {
swc,
bus_mapping_instance: Vec::new(),
error: None,
aux_data: None,
}
}
}

impl Default for ExecStep {
fn default() -> Self {
Self {
op: OpcodeId::INVALID(0),
exec_state: ExecState::Op(OpcodeId::INVALID(0)),
pc: ProgramCounter(0),
stack_size: 0,
memory_size: 0,
Expand All @@ -169,6 +232,7 @@ impl Default for ExecStep {
swc: 0,
bus_mapping_instance: Vec::new(),
error: None,
aux_data: None,
}
}
}
Expand Down Expand Up @@ -368,12 +432,12 @@ impl Call {
/// Context of a [`Call`].
#[derive(Debug, Default)]
pub struct CallContext {
// Index of call
index: usize,
/// Index of call
pub index: usize,
/// State Write Counter tracks the count of state write operations in the
/// call. When a subcall in this call succeeds, the `swc` increases by the
/// number of successful state writes in the subcall.
swc: usize,
pub swc: usize,
}

/// A reversion group is the collection of calls and the operations which are
Expand Down Expand Up @@ -466,6 +530,11 @@ impl TransactionContext {
self.is_last_tx
}

/// Return the calls in this transaction.
pub fn calls(&self) -> &[CallContext] {
&self.calls
}

/// Return the index of the current call (the last call in the call stack).
fn call_index(&self) -> Result<usize, Error> {
self.calls
Expand Down Expand Up @@ -541,8 +610,10 @@ pub struct Transaction {
/// Value
pub value: Word,
/// Input / Call Data
pub input: Vec<u8>, // call_data
pub input: Vec<u8>,
/// Calls made in the transaction
calls: Vec<Call>,
/// Execution steps
steps: Vec<ExecStep>,
}

Expand Down Expand Up @@ -654,21 +725,61 @@ pub struct CircuitInputStateRef<'a> {
pub tx: &'a mut Transaction,
/// Transaction Context
pub tx_ctx: &'a mut TransactionContext,
/// Step
pub step: &'a mut ExecStep,
}

impl<'a> CircuitInputStateRef<'a> {
/// Create a new step from a `GethExecStep`
pub fn new_step(&self, geth_step: &GethExecStep) -> Result<ExecStep, Error> {
let call_ctx = self.tx_ctx.call_ctx()?;
Ok(ExecStep::new(
geth_step,
call_ctx.index,
self.block_ctx.rwc,
call_ctx.swc,
))
}

/// Create a new BeginTx step
pub fn new_begin_tx_step(&self) -> ExecStep {
ExecStep {
exec_state: ExecState::BeginTx,
gas_left: Gas(self.tx.gas),
rwc: self.block_ctx.rwc,
..Default::default()
}
}

/// Create a new EndTx step
pub fn new_end_tx_step(&self) -> ExecStep {
let prev_step = self
.tx
.steps()
.last()
.expect("steps should have at least one BeginTx step");
ExecStep {
exec_state: ExecState::EndTx,
gas_left: Gas(prev_step.gas_left.0 - prev_step.gas_cost.0),
rwc: self.block_ctx.rwc,
// For tx without code execution
swc: if let Some(call_ctx) = self.tx_ctx.calls().last() {
call_ctx.swc
} else {
0
},
..Default::default()
}
}

/// Push an [`Operation`] into the [`OperationContainer`] with the next
/// [`RWCounter`] and then adds a reference to the stored operation
/// ([`OperationRef`]) inside the bus-mapping instance of the current
/// [`ExecStep`]. Then increase the block_ctx [`RWCounter`] by one.
pub fn push_op<T: Op>(&mut self, rw: RW, op: T) {
pub fn push_op<T: Op>(&mut self, step: &mut ExecStep, rw: RW, op: T) {
let op_ref =
self.block
.container
.insert(Operation::new(self.block_ctx.rwc.inc_pre(), rw, op));
self.step.bus_mapping_instance.push(op_ref);
step.bus_mapping_instance.push(op_ref);
}

/// Push an [`Operation`] with reversible to be true into the
Expand All @@ -679,13 +790,18 @@ impl<'a> CircuitInputStateRef<'a> {
/// This method should be used in `Opcode::gen_associated_ops` instead of
/// `push_op` when the operation is `RW::WRITE` and it can be reverted (for
/// example, a write `StorageOp`).
pub fn push_op_reversible<T: Op>(&mut self, rw: RW, op: T) -> Result<(), Error> {
pub fn push_op_reversible<T: Op>(
&mut self,
step: &mut ExecStep,
rw: RW,
op: T,
) -> Result<(), Error> {
let op_ref = self.block.container.insert(Operation::new_reversible(
self.block_ctx.rwc.inc_pre(),
rw,
op,
));
self.step.bus_mapping_instance.push(op_ref);
step.bus_mapping_instance.push(op_ref);

// Increase state_write_counter
self.call_ctx_mut()?.swc += 1;
Expand All @@ -710,12 +826,13 @@ impl<'a> CircuitInputStateRef<'a> {
/// [`RWCounter`] by one.
pub fn push_memory_op(
&mut self,
step: &mut ExecStep,
rw: RW,
address: MemoryAddress,
value: u8,
) -> Result<(), Error> {
let call_id = self.call()?.call_id;
self.push_op(rw, MemoryOp::new(call_id, address, value));
self.push_op(step, rw, MemoryOp::new(call_id, address, value));
Ok(())
}

Expand All @@ -726,12 +843,13 @@ impl<'a> CircuitInputStateRef<'a> {
/// [`RWCounter`] by one.
pub fn push_stack_op(
&mut self,
step: &mut ExecStep,
rw: RW,
address: StackAddress,
value: Word,
) -> Result<(), Error> {
let call_id = self.call()?.call_id;
self.push_op(rw, StackOp::new(call_id, address, value));
self.push_op(step, rw, StackOp::new(call_id, address, value));
Ok(())
}

Expand Down Expand Up @@ -1254,7 +1372,6 @@ impl<'a> CircuitInputBuilder {
&'a mut self,
tx: &'a mut Transaction,
tx_ctx: &'a mut TransactionContext,
step: &'a mut ExecStep,
) -> CircuitInputStateRef {
CircuitInputStateRef {
sdb: &mut self.sdb,
Expand All @@ -1263,7 +1380,6 @@ impl<'a> CircuitInputBuilder {
block_ctx: &mut self.block_ctx,
tx,
tx_ctx,
step,
}
}

Expand Down Expand Up @@ -1342,50 +1458,25 @@ impl<'a> CircuitInputBuilder {
// - execution_state: BeginTx
// - op: None
// Generate BeginTx step
let mut step = ExecStep {
gas_left: Gas(tx.gas),
rwc: self.block_ctx.rwc,
..Default::default()
};
gen_begin_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx, &mut step))?;
tx.steps.push(step);
let begin_tx_step = gen_begin_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx))?;
tx.steps.push(begin_tx_step);

for (index, geth_step) in geth_trace.struct_logs.iter().enumerate() {
let call_ctx = tx_ctx.call_ctx()?;
let mut step =
ExecStep::new(geth_step, call_ctx.index, self.block_ctx.rwc, call_ctx.swc);
let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx, &mut step);

gen_associated_ops(
let mut state_ref = self.state_ref(&mut tx, &mut tx_ctx);
let exec_steps = gen_associated_ops(
&geth_step.op,
&mut state_ref,
&geth_trace.struct_logs[index..],
)?;

tx.steps.push(step);
tx.steps.extend(exec_steps);
}

// TODO: Move into gen_associated_steps with
// - execution_state: EndTx
// - op: None
// Generate EndTx step
let step_prev = tx
.steps
.last()
.expect("steps should have at least one BeginTx step");
let mut step = ExecStep {
gas_left: Gas(step_prev.gas_left.0 - step_prev.gas_cost.0),
rwc: self.block_ctx.rwc,
// For tx without code execution
swc: if let Some(call_ctx) = tx_ctx.calls.last() {
call_ctx.swc
} else {
0
},
..Default::default()
};
gen_end_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx, &mut step))?;
tx.steps.push(step);
let end_tx_step = gen_end_tx_ops(&mut self.state_ref(&mut tx, &mut tx_ctx))?;
tx.steps.push(end_tx_step);

self.block.txs.push(tx);
self.sdb.clear_access_list_and_refund();
Expand Down Expand Up @@ -1898,8 +1989,7 @@ mod tracer_tests {
}

fn state_ref(&mut self) -> CircuitInputStateRef {
self.builder
.state_ref(&mut self.tx, &mut self.tx_ctx, &mut self.step)
self.builder.state_ref(&mut self.tx, &mut self.tx_ctx)
}
}

Expand Down Expand Up @@ -1970,7 +2060,7 @@ mod tracer_tests {
STOP
};
let block =
mock::new_single_tx_trace_code_gas(&code, Gas(1_000_000_000_000_000u64)).unwrap();
mock::new_single_tx_trace_code_gas(&code, Gas(1_000_000_000_000_000u64), None).unwrap();
let struct_logs = &block.geth_traces[0].struct_logs;

// get last CALL
Expand Down Expand Up @@ -2830,7 +2920,7 @@ mod tracer_tests {
PUSH1(0x1)
PUSH1(0x2)
};
let block = mock::new_single_tx_trace_code_gas(&code, Gas(21004)).unwrap();
let block = mock::new_single_tx_trace_code_gas(&code, Gas(21004), None).unwrap();
let struct_logs = &block.geth_traces[0].struct_logs;

assert_eq!(struct_logs[1].error, Some(GETH_ERR_OUT_OF_GAS.to_string()));
Expand Down
Loading

0 comments on commit ea5b823

Please sign in to comment.