forked from privacy-scaling-explorations/zkevm-circuits
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Split circuit_input_builder file and improve APIs for it (privacy-sca…
…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
Showing
16 changed files
with
4,214 additions
and
4,123 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: ð_types::Block<TX>, | ||
tx: ð_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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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: ð_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 | ||
} | ||
} |
Oops, something went wrong.