Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] add call tracer and prestate tracer #1043

Merged
merged 14 commits into from
Jan 25, 2024
2 changes: 1 addition & 1 deletion .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[target.'cfg(target_os="macos")']
rustflags = ["-C", "link-args=-framework CoreFoundation -framework Security"]
rustflags = ["-C", "link-args=-framework CoreFoundation -framework Security -framework CoreServices"]
[net]
git-fetch-with-cli = true
[env]
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
uses: actions/setup-go@v3
with:
cache: false
go-version: ~1.19
go-version: ~1.20
# Go cache for building geth-utils
- name: Go cache
uses: actions/cache@v3
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ rayon = "1.5"
regex = "1.5"
serde = {version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_stacker = "0.1"
sha3 = "0.10"
snark-verifier = { git = "https://github.com/scroll-tech/snark-verifier", branch = "develop" }
snark-verifier-sdk = { git = "https://github.com/scroll-tech/snark-verifier", branch = "develop", default-features = false, features = ["loader_halo2", "loader_evm", "halo2-pse"] }
Expand Down
51 changes: 21 additions & 30 deletions bus-mapping/src/circuit_input_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ mod l2;
mod tracer_tests;
mod transaction;

use self::access::gen_state_access_trace;
pub use self::block::BlockHead;
use crate::{
error::Error,
Expand Down Expand Up @@ -947,29 +946,6 @@ pub struct BuilderClient<P: JsonRpcClient> {
circuits_params: CircuitsParams,
}

/// Get State Accesses from TxExecTraces
pub fn get_state_accesses(
eth_block: &EthBlock,
geth_traces: &[eth_types::GethExecTrace],
) -> Result<AccessSet, Error> {
let mut block_access_trace = vec![Access::new(
None,
RW::WRITE,
AccessValue::Account {
address: eth_block
.author
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?,
},
)];
for (tx_index, tx) in eth_block.transactions.iter().enumerate() {
let geth_trace = &geth_traces[tx_index];
let tx_access_trace = gen_state_access_trace(eth_block, tx, geth_trace)?;
block_access_trace.extend(tx_access_trace);
}

Ok(AccessSet::from(block_access_trace))
}

/// Build a partial StateDB from step 3
pub fn build_state_code_db(
proofs: Vec<eth_types::EIP1186ProofResponse>,
Expand Down Expand Up @@ -1061,11 +1037,26 @@ impl<P: JsonRpcClient> BuilderClient<P> {
}

/// Step 2. Get State Accesses from TxExecTraces
pub fn get_state_accesses(
eth_block: &EthBlock,
geth_traces: &[eth_types::GethExecTrace],
) -> Result<AccessSet, Error> {
get_state_accesses(eth_block, geth_traces)
pub async fn get_state_accesses(&self, eth_block: &EthBlock) -> Result<AccessSet, Error> {
let mut access_set = AccessSet::default();
access_set.add_account(
eth_block
.author
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?,
);
let traces = self
.cli
.trace_block_prestate_by_hash(
eth_block
.hash
.ok_or(Error::EthTypeError(eth_types::Error::IncompleteBlock))?,
)
.await?;
for trace in traces.into_iter() {
access_set.extend_from_traces(&trace);
}

Ok(access_set)
}

/// Step 3. Query geth for all accounts, storage keys, and codes from
Expand Down Expand Up @@ -1317,7 +1308,7 @@ impl<P: JsonRpcClient> BuilderClient<P> {
let mut access_set = AccessSet::default();
for block_num in block_num_begin..block_num_end {
let (eth_block, geth_traces, _, _) = self.get_block(block_num).await?;
let mut access_list = Self::get_state_accesses(&eth_block, &geth_traces)?;
let mut access_list = self.get_state_accesses(&eth_block).await?;
access_set.extend(&mut access_list);
blocks_and_traces.push((eth_block, geth_traces));
}
Expand Down
194 changes: 7 additions & 187 deletions bus-mapping/src/circuit_input_builder/access.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
use crate::{operation::RW, Error};
use eth_types::{
evm_types::OpcodeId, Address, GethExecStep, GethExecTrace, GethPrestateTrace, ToAddress, Word,
};
use ethers_core::utils::get_contract_address;
use crate::operation::RW;
use eth_types::{geth_types::GethData, Address, GethPrestateTrace, Word};
use std::collections::{hash_map::Entry, HashMap, HashSet};

use AccessValue::{Account, Code, Storage};
use RW::{READ, WRITE};

/// State and Code Access with "keys/index" used in the access operation.
#[derive(Debug, PartialEq, Eq)]
pub enum AccessValue {
Expand Down Expand Up @@ -48,16 +42,6 @@ impl Access {
}
}

/// 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)
.and_then(|s| s.stack.last().ok())
}

/// State and Code Access set.
#[derive(Debug, PartialEq, Eq, Default)]
pub struct AccessSet {
Expand Down Expand Up @@ -119,12 +103,13 @@ impl AccessSet {
self.state.extend(other.state.drain());
self.code.extend(other.code.drain());
}
}

impl From<Vec<Access>> for AccessSet {
fn from(list: Vec<Access>) -> Self {
pub(crate) fn from_geth_data(geth_data: &GethData) -> Self {
let mut access_set = AccessSet::default();
access_set.extend_from_access(list);
access_set.add_account(geth_data.eth_block.author.unwrap());
for trace in geth_data.geth_traces.iter() {
access_set.extend_from_traces(&trace.prestate);
}
access_set
}
}
Expand All @@ -145,168 +130,3 @@ impl Default for CodeSource {
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> {
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;
}

let result: Result<(), Error> = (|| {
match step.op {
OpcodeId::SSTORE => {
let address = contract_address;
let key = step.stack.last()?;
accs.push(Access::new(i, WRITE, Storage { address, key }));
}
OpcodeId::SLOAD => {
let address = contract_address;
let key = step.stack.last()?;
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.last()?.to_address();
accs.push(Access::new(i, READ, Account { address }));
}
OpcodeId::EXTCODEHASH => {
let address = step.stack.last()?.to_address();
accs.push(Access::new(i, READ, Account { address }));
}
OpcodeId::EXTCODESIZE => {
let address = step.stack.last()?.to_address();
accs.push(Access::new(i, READ, Code { address }));
}
OpcodeId::EXTCODECOPY => {
let address = step.stack.last()?.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.last()?.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)));
}
}
_ => {}
}
Ok(())
})();
if let Err(e) = result {
log::warn!("err when parsing access: {:?}, step {:?}", e, step);
}

if pop_call_stack {
if call_stack.len() == 1 {
return Err(Error::InvalidGethExecStep(
"gen_state_access_trace: call stack will be empty",
Box::new(step.clone()),
));
}
call_stack.pop().expect("call stack is empty");
}
}
Ok(accs)
}
31 changes: 20 additions & 11 deletions bus-mapping/src/circuit_input_builder/input_state_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -925,7 +925,8 @@ impl<'a> CircuitInputStateRef<'a> {
let call_id = call.call_id;
let call_idx = self.tx.calls().len();

self.tx_ctx.push_call_ctx(call_idx, call_data);
self.tx_ctx
.push_call_ctx(call_idx, call_data, call.is_success);
self.tx.push_call(call);

self.block_ctx
Expand Down Expand Up @@ -1008,13 +1009,8 @@ impl<'a> CircuitInputStateRef<'a> {
address.0[0..19] == [0u8; 19] && (1..=9).contains(&address.0[19])
}

/// Parse [`Call`] from a *CALL*/CREATE* step.
pub fn parse_call(&mut self, step: &GethExecStep) -> Result<Call, Error> {
let is_success = *self
.tx_ctx
.call_is_success
.get(self.tx.calls().len())
.unwrap();
/// Parse [`Call`] from a *CALL*/CREATE* step without information about success and persistent.
pub fn parse_call_partial(&mut self, step: &GethExecStep) -> Result<Call, Error> {
let kind = CallKind::try_from(step.op)?;
let caller = self.call()?;
let caller_ctx = self.call_ctx()?;
Expand Down Expand Up @@ -1089,8 +1085,8 @@ impl<'a> CircuitInputStateRef<'a> {
kind,
is_static: kind == CallKind::StaticCall || caller.is_static,
is_root: false,
is_persistent: caller.is_persistent && is_success,
is_success,
is_persistent: caller.is_persistent,
is_success: false,
rw_counter_end_of_reversion: 0,
caller_address,
address,
Expand All @@ -1110,6 +1106,19 @@ impl<'a> CircuitInputStateRef<'a> {
Ok(call)
}

/// Parse [`Call`] from a *CALL*/CREATE* step
pub fn parse_call(&mut self, step: &GethExecStep) -> Result<Call, Error> {
let is_success = *self
.tx_ctx
.call_is_success
.get(self.tx.calls().len() - self.tx_ctx.call_is_success_offset)
.unwrap();
let mut call = self.parse_call_partial(step)?;
call.is_success = is_success;
call.is_persistent = self.call()?.is_persistent && is_success;
Ok(call)
}

/// Return the reverted version of an op by op_ref only if the original op
/// was reversible.
fn get_rev_op_by_ref(&self, op_ref: &OperationRef) -> Option<OpEnum> {
Expand Down Expand Up @@ -1331,7 +1340,7 @@ impl<'a> CircuitInputStateRef<'a> {
caller.last_callee_memory = callee_memory;
}

self.tx_ctx.pop_call_ctx();
self.tx_ctx.pop_call_ctx(call.is_success);

Ok(())
}
Expand Down
Loading
Loading