Skip to content

Commit

Permalink
Split circuit_input_builder file and improve APIs for it (privacy-sca…
Browse files Browse the repository at this point in the history
…ling-explorations#482)

* change: Move Error types out from circuit_input_builder

Moved all error type definitions like `OOGError` or `ExecError` to the
`error.rs` file and inter-connected them.

* change: Split CIB file into it's own module

As mentioned in privacy-scaling-explorations#426, CIB file was already more than 3000 lines of code
and was turning into a deposit of all sorts of code for different
purposes.

This is a preliminary work towards adding a bit of order into the
CIB/bus-mapping crate.

* change: Simplify StepAuxiliaryData struct

Instead of being an enum with repeated fields for Code or CallData
origins, turn it into a single struct which can specify both.

* fix: Add getters to avoid public attrs in structs

* fix: Update codebase to new AuxiliaryData API

* fix: Update codebase to new ReversionGroup API

* fix: Update codebase to new extended Access API

* fix: Adapt Log case for StepAuxData

The latest addition of Log support for ZKEVM in privacy-scaling-explorations#335 also has needed an
adaptation to the API used to handle the step auxiliary data.

* fix: Apply clippy & rustdoc lints

* Fix BlockContext rwc inc

* fix: Remove getters and use `pub(crate)` when possible

Co-authored-by: Eduard S <[email protected]>
  • Loading branch information
CPerezz and ed255 authored May 5, 2022
1 parent 2792434 commit 287a3f4
Show file tree
Hide file tree
Showing 16 changed files with 4,214 additions and 4,123 deletions.
4,297 changes: 294 additions & 4,003 deletions bus-mapping/src/circuit_input_builder.rs

Large diffs are not rendered by default.

271 changes: 271 additions & 0 deletions bus-mapping/src/circuit_input_builder/access.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
use crate::{operation::RW, Error};
use eth_types::{evm_types::OpcodeId, Address, GethExecStep, GethExecTrace, ToAddress, Word};
use ethers_core::utils::get_contract_address;
use std::collections::{hash_map::Entry, HashMap, HashSet};

/// State and Code Access with "keys/index" used in the access operation.
#[derive(Debug, PartialEq)]
pub enum AccessValue {
/// Account access
Account {
/// Account address
address: Address,
},
/// Storage access
Storage {
/// Storage account address
address: Address,
/// Storage key
key: Word,
},
/// Code access
Code {
/// Code address
address: Address,
},
}

/// State Access caused by a transaction or an execution step
#[derive(Debug, PartialEq)]
pub struct Access {
step_index: Option<usize>,
rw: RW,
value: AccessValue,
}

impl Access {
pub(crate) fn new(step_index: Option<usize>, rw: RW, value: AccessValue) -> Self {
Self {
step_index,
rw,
value,
}
}
}

/// Given a trace and assuming that the first step is a *CALL*/CREATE* kind
/// opcode, return the result if found.
fn get_call_result(trace: &[GethExecStep]) -> Option<Word> {
let depth = trace[0].depth;
trace[1..]
.iter()
.find(|s| s.depth == depth)
.map(|s| s.stack.nth_last(0).ok())
.flatten()
}

/// State and Code Access set.
#[derive(Debug, PartialEq)]
pub struct AccessSet {
/// Set of accounts
pub state: HashMap<Address, HashSet<Word>>,
/// Set of accounts code
pub code: HashSet<Address>,
}

impl From<Vec<Access>> for AccessSet {
fn from(list: Vec<Access>) -> Self {
let mut state: HashMap<Address, HashSet<Word>> = HashMap::new();
let mut code: HashSet<Address> = HashSet::new();
for access in list {
match access.value {
AccessValue::Account { address } => {
state.entry(address).or_insert_with(HashSet::new);
}
AccessValue::Storage { address, key } => match state.entry(address) {
Entry::Vacant(entry) => {
let mut storage = HashSet::new();
storage.insert(key);
entry.insert(storage);
}
Entry::Occupied(mut entry) => {
entry.get_mut().insert(key);
}
},
AccessValue::Code { address } => {
state.entry(address).or_insert_with(HashSet::new);
code.insert(address);
}
}
}
Self { state, code }
}
}

/// Source of the code in the EVM execution.
#[derive(Debug, Clone, Copy)]
pub enum CodeSource {
/// Code comes from a deployed contract at `Address`.
Address(Address),
/// Code comes from tx.data when tx.to == null.
Tx,
/// Code comes from Memory by a CREATE* opcode.
Memory,
}

impl Default for CodeSource {
fn default() -> Self {
Self::Tx
}
}

/// Generate the State Access trace from the given trace. All state read/write
/// accesses are reported, without distinguishing those that happen in revert
/// sections.
pub fn gen_state_access_trace<TX>(
_block: &eth_types::Block<TX>,
tx: &eth_types::Transaction,
geth_trace: &GethExecTrace,
) -> Result<Vec<Access>, Error> {
use AccessValue::{Account, Code, Storage};
use RW::{READ, WRITE};

let mut call_stack: Vec<(Address, CodeSource)> = Vec::new();
let mut accs = vec![Access::new(None, WRITE, Account { address: tx.from })];
if let Some(to) = tx.to {
call_stack.push((to, CodeSource::Address(to)));
accs.push(Access::new(None, WRITE, Account { address: to }));
// Code may be null if the account is not a contract
accs.push(Access::new(None, READ, Code { address: to }));
} else {
let address = get_contract_address(tx.from, tx.nonce);
call_stack.push((address, CodeSource::Tx));
accs.push(Access::new(None, WRITE, Account { address }));
accs.push(Access::new(None, WRITE, Code { address }));
}

for (index, step) in geth_trace.struct_logs.iter().enumerate() {
let next_step = geth_trace.struct_logs.get(index + 1);
let i = Some(index);
let (contract_address, code_source) = &call_stack[call_stack.len() - 1];
let (contract_address, code_source) = (*contract_address, *code_source);

let (mut push_call_stack, mut pop_call_stack) = (false, false);
if let Some(next_step) = next_step {
push_call_stack = step.depth + 1 == next_step.depth;
pop_call_stack = step.depth - 1 == next_step.depth;
}

match step.op {
OpcodeId::SSTORE => {
let address = contract_address;
let key = step.stack.nth_last(0)?;
accs.push(Access::new(i, WRITE, Storage { address, key }));
}
OpcodeId::SLOAD => {
let address = contract_address;
let key = step.stack.nth_last(0)?;
accs.push(Access::new(i, READ, Storage { address, key }));
}
OpcodeId::SELFBALANCE => {
let address = contract_address;
accs.push(Access::new(i, READ, Account { address }));
}
OpcodeId::CODESIZE => {
if let CodeSource::Address(address) = code_source {
accs.push(Access::new(i, READ, Code { address }));
}
}
OpcodeId::CODECOPY => {
if let CodeSource::Address(address) = code_source {
accs.push(Access::new(i, READ, Code { address }));
}
}
OpcodeId::BALANCE => {
let address = step.stack.nth_last(0)?.to_address();
accs.push(Access::new(i, READ, Account { address }));
}
OpcodeId::EXTCODEHASH => {
let address = step.stack.nth_last(0)?.to_address();
accs.push(Access::new(i, READ, Account { address }));
}
OpcodeId::EXTCODESIZE => {
let address = step.stack.nth_last(0)?.to_address();
accs.push(Access::new(i, READ, Code { address }));
}
OpcodeId::EXTCODECOPY => {
let address = step.stack.nth_last(0)?.to_address();
accs.push(Access::new(i, READ, Code { address }));
}
OpcodeId::SELFDESTRUCT => {
let address = contract_address;
accs.push(Access::new(i, WRITE, Account { address }));
let address = step.stack.nth_last(0)?.to_address();
accs.push(Access::new(i, WRITE, Account { address }));
}
OpcodeId::CREATE => {
if push_call_stack {
// Find CREATE result
let address = get_call_result(&geth_trace.struct_logs[index..])
.unwrap_or_else(Word::zero)
.to_address();
if !address.is_zero() {
accs.push(Access::new(i, WRITE, Account { address }));
accs.push(Access::new(i, WRITE, Code { address }));
}
call_stack.push((address, CodeSource::Address(address)));
}
}
OpcodeId::CREATE2 => {
if push_call_stack {
// Find CREATE2 result
let address = get_call_result(&geth_trace.struct_logs[index..])
.unwrap_or_else(Word::zero)
.to_address();
if !address.is_zero() {
accs.push(Access::new(i, WRITE, Account { address }));
accs.push(Access::new(i, WRITE, Code { address }));
}
call_stack.push((address, CodeSource::Address(address)));
}
}
OpcodeId::CALL => {
let address = contract_address;
accs.push(Access::new(i, WRITE, Account { address }));

let address = step.stack.nth_last(1)?.to_address();
accs.push(Access::new(i, WRITE, Account { address }));
accs.push(Access::new(i, READ, Code { address }));
if push_call_stack {
call_stack.push((address, CodeSource::Address(address)));
}
}
OpcodeId::CALLCODE => {
let address = contract_address;
accs.push(Access::new(i, WRITE, Account { address }));

let address = step.stack.nth_last(1)?.to_address();
accs.push(Access::new(i, WRITE, Account { address }));
accs.push(Access::new(i, READ, Code { address }));
if push_call_stack {
call_stack.push((address, CodeSource::Address(address)));
}
}
OpcodeId::DELEGATECALL => {
let address = step.stack.nth_last(1)?.to_address();
accs.push(Access::new(i, READ, Code { address }));
if push_call_stack {
call_stack.push((contract_address, CodeSource::Address(address)));
}
}
OpcodeId::STATICCALL => {
let address = step.stack.nth_last(1)?.to_address();
accs.push(Access::new(i, READ, Code { address }));
if push_call_stack {
call_stack.push((address, CodeSource::Address(address)));
}
}
_ => {}
}
if pop_call_stack {
if call_stack.len() == 1 {
return Err(Error::InvalidGethExecStep(
"gen_state_access_trace: call stack will be empty",
step.clone(),
));
}
call_stack.pop().expect("call stack is empty");
}
}
Ok(accs)
}
108 changes: 108 additions & 0 deletions bus-mapping/src/circuit_input_builder/block.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! Block-related utility module
use super::transaction::Transaction;
use crate::{
operation::{OperationContainer, RWCounter},
Error,
};
use eth_types::{Address, Hash, Word};
use std::collections::HashMap;

/// Context of a [`Block`] which can mutate in a [`Transaction`].
#[derive(Debug)]
pub struct BlockContext {
/// Used to track the global counter in every operation in the block.
/// Contains the next available value.
pub(crate) rwc: RWCounter,
/// Map call_id to (tx_index, call_index) (where tx_index is the index used
/// in Block.txs and call_index is the index used in Transaction.
/// calls).
pub(crate) call_map: HashMap<usize, (usize, usize)>,
}

impl Default for BlockContext {
fn default() -> Self {
Self::new()
}
}

impl BlockContext {
/// Create a new Self
pub fn new() -> Self {
Self {
rwc: RWCounter::new(),
call_map: HashMap::new(),
}
}
}

/// Circuit Input related to a block.
#[derive(Debug)]
pub struct Block {
/// chain id
pub chain_id: Word,
/// history hashes contains most recent 256 block hashes in history, where
/// the lastest one is at history_hashes[history_hashes.len() - 1].
pub history_hashes: Vec<Word>,
/// coinbase
pub coinbase: Address,
/// time
pub gas_limit: u64,
/// number
pub number: Word,
/// difficulty
pub timestamp: Word,
/// gas limit
pub difficulty: Word,
/// base fee
pub base_fee: Word,
/// Container of operations done in this block.
pub container: OperationContainer,
/// Transactions contained in the block
pub txs: Vec<Transaction>,
code: HashMap<Hash, Vec<u8>>,
}

impl Block {
/// Create a new block.
pub fn new<TX>(
chain_id: Word,
history_hashes: Vec<Word>,
eth_block: &eth_types::Block<TX>,
) -> Result<Self, Error> {
if eth_block.base_fee_per_gas.is_none() {
// FIXME: resolve this once we have proper EIP-1559 support
log::warn!(
"This does not look like a EIP-1559 block - base_fee_per_gas defaults to zero"
);
}

Ok(Self {
chain_id,
history_hashes,
coinbase: eth_block.author,
gas_limit: eth_block.gas_limit.low_u64(),
number: eth_block
.number
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?
.low_u64()
.into(),
timestamp: eth_block.timestamp,
difficulty: eth_block.difficulty,
base_fee: eth_block.base_fee_per_gas.unwrap_or_default(),
container: OperationContainer::new(),
txs: Vec::new(),
code: HashMap::new(),
})
}

/// Return the list of transactions of this block.
pub fn txs(&self) -> &[Transaction] {
&self.txs
}

#[cfg(test)]
pub fn txs_mut(&mut self) -> &mut Vec<Transaction> {
&mut self.txs
}
}
Loading

0 comments on commit 287a3f4

Please sign in to comment.