Skip to content

Commit

Permalink
[feat] add call tracer and prestate tracer (#1043)
Browse files Browse the repository at this point in the history
* add feature switch

* add missing

* add call tracer and prestate tracer

* handle precheck failed call

* why ignore not working

* add l2 prestate

* use go1.20

* 0x5c/5e assigned by cankun

* update l2geth

* fix mainnet tests
  • Loading branch information
lightsing committed Jan 25, 2024
1 parent 2369228 commit e31fe3f
Show file tree
Hide file tree
Showing 29 changed files with 747 additions and 703 deletions.
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

0 comments on commit e31fe3f

Please sign in to comment.