Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
772d9c1
missing stack logic
Jul 1, 2023
892340f
track new stack in trace step, use it in vm trace conversion
Jul 1, 2023
9eb1935
comment
Jul 1, 2023
86c4cfa
add todo
Jul 1, 2023
2ff63de
fix
Jul 1, 2023
b60a612
introduce graphwalker logic, move somethings to asscioated types on t…
Jul 2, 2023
db0b459
helper function to get all the address from a DF walk
Jul 2, 2023
19f3545
code layout
Jul 2, 2023
672acec
commnets + todos
Jul 2, 2023
816c707
use ref of vec rather than vec of regs
Jul 2, 2023
c443edb
forgot to push the current calltracenode to the count
Jul 2, 2023
01dcd43
isgts
Jul 2, 2023
6017856
trait bound gn
Jul 2, 2023
7cc6c66
fill in code
Jul 2, 2023
1416c66
merge main
Jul 2, 2023
9eb8012
remove get_code function
Jul 2, 2023
8aa012c
from -> new, comments removed
Jul 2, 2023
2a2fb85
trait wasnt doing anything
Jul 2, 2023
4fb2b3a
comments
Jul 2, 2023
3f5db80
comments + some tests
Jul 3, 2023
3035c34
assert stuff in test, layout
Jul 3, 2023
971f80d
use trait bounds instead of dyn dispatch, comments, naming
Jul 3, 2023
b0e4f4f
iterative walk of call graph
Jul 3, 2023
88d0c32
bad import
Jul 3, 2023
1ba15dd
expose addresses on walker
Jul 3, 2023
2b8f5a5
move make_trace & make_instructions to the walker
Jul 3, 2023
21092ce
start on iterative vm trace walker
Jul 3, 2023
1f14b56
naming, starting tests for vm trace walker
Jul 4, 2023
f90920c
lazy eval calltracenode walker
Jul 4, 2023
b0f1aea
clean up iterator
Jul 4, 2023
f5298c8
comment out vm trace walker for now
Jul 4, 2023
33f92c7
iterative build vm trace
Jul 4, 2023
acaddbd
start on BF walkers
Jul 4, 2023
d7fe088
comment out vmtrace walker
Jul 5, 2023
c896134
unused thing
Jul 5, 2023
5838f2e
Update crates/revm/revm-inspectors/src/tracing/builder/walker.rs
nhtyy Jul 5, 2023
699c93d
iterative byte code filling
Jul 5, 2023
c00ed89
Merge branch 'n/vm-trace' of https://github.com/nhtyy/reth into n/vm-…
Jul 5, 2023
3b69972
comments
Jul 5, 2023
9f9d2c4
comments
Jul 5, 2023
7fac645
comment unused types
Jul 5, 2023
849577c
better expects
Jul 5, 2023
21d2872
extend, remove comments
Jul 10, 2023
6bb08dd
ordering of iterator in params
Jul 10, 2023
175bbec
use code_by_hash
Jul 10, 2023
08d8d40
Merge branch 'main' of https://github.com/paradigmxyz/reth into n/vm-…
Jul 10, 2023
77acc0a
Merge branch 'main' into n/vm-trace
mattsse Jul 12, 2023
38c1483
nits
mattsse Jul 12, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions crates/revm/revm-inspectors/src/tracing/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ pub mod geth;

/// Parity style trace builders for `trace_` namespace
pub mod parity;

/// Walker types used for traversing various callgraphs
mod walker;
173 changes: 161 additions & 12 deletions crates/revm/revm-inspectors/src/tracing/builder/parity.rs
Copy link
Contributor Author

@nhtyy nhtyy Jul 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i still need to respect the config here, but the rest of the file doesnt seem to use the config yet + logging

Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::tracing::{types::CallTraceNode, TracingInspectorConfig};
use super::walker::CallTraceNodeWalkerBF;
use crate::tracing::{
types::{CallTraceNode, CallTraceStep},
TracingInspectorConfig,
};
use reth_primitives::{Address, U64};
use reth_rpc_types::{trace::parity::*, TransactionInfo};
use revm::{
db::DatabaseRef,
primitives::{AccountInfo, ExecutionResult, ResultAndState},
interpreter::opcode,
primitives::{AccountInfo, ExecutionResult, ResultAndState, KECCAK_EMPTY},
};
use std::collections::HashSet;
use std::collections::{HashSet, VecDeque};

/// A type for creating parity style traces
///
Expand All @@ -14,6 +19,7 @@ use std::collections::HashSet;
pub struct ParityTraceBuilder {
/// Recorded trace nodes
nodes: Vec<CallTraceNode>,

/// How the traces were recorded
_config: TracingInspectorConfig,
}
Expand Down Expand Up @@ -154,14 +160,31 @@ impl ParityTraceBuilder {
DB: DatabaseRef,
{
let ResultAndState { result, state } = res;

let breadth_first_addresses = if trace_types.contains(&TraceType::VmTrace) {
CallTraceNodeWalkerBF::new(&self.nodes)
.map(|node| node.trace.address)
.collect::<Vec<_>>()
} else {
vec![]
};

let mut trace_res = self.into_trace_results(result, trace_types);

// check the state diff case
if let Some(ref mut state_diff) = trace_res.state_diff {
populate_account_balance_nonce_diffs(
state_diff,
&db,
state.into_iter().map(|(addr, acc)| (addr, acc.info)),
)?;
}

// check the vm trace case
if let Some(ref mut vm_trace) = trace_res.vm_trace {
populate_vm_trace_bytecodes(&db, vm_trace, breadth_first_addresses)?;
}

Ok(trace_res)
}

Expand All @@ -177,11 +200,8 @@ impl ParityTraceBuilder {
let with_traces = trace_types.contains(&TraceType::Trace);
let with_diff = trace_types.contains(&TraceType::StateDiff);

let vm_trace = if trace_types.contains(&TraceType::VmTrace) {
Some(vm_trace(&self.nodes))
} else {
None
};
let vm_trace =
if trace_types.contains(&TraceType::VmTrace) { Some(self.vm_trace()) } else { None };

let mut traces = Vec::with_capacity(if with_traces { self.nodes.len() } else { 0 });
let mut diff = StateDiff::default();
Expand Down Expand Up @@ -218,13 +238,142 @@ impl ParityTraceBuilder {
pub fn into_transaction_traces(self) -> Vec<TransactionTrace> {
self.into_transaction_traces_iter().collect()
}

/// Creates a VM trace by walking over `CallTraceNode`s
///
/// does not have the code fields filled in
pub fn vm_trace(&self) -> VmTrace {
match self.nodes.get(0) {
Some(current) => self.make_vm_trace(current),
None => VmTrace { code: Default::default(), ops: Vec::new() },
}
}

/// returns a VM trace without the code filled in
///
/// iteratively creaters a VM trace by traversing an arena
fn make_vm_trace(&self, start: &CallTraceNode) -> VmTrace {
let mut child_idx_stack: Vec<usize> = Vec::with_capacity(self.nodes.len());
let mut sub_stack: VecDeque<Option<VmTrace>> = VecDeque::with_capacity(self.nodes.len());

let mut current = start;
let mut child_idx: usize = 0;

// finds the deepest nested calls of each call frame and fills them up bottom to top
let instructions = loop {
match current.children.get(child_idx) {
Some(child) => {
child_idx_stack.push(child_idx + 1);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the +1 here?

Copy link
Contributor Author

@nhtyy nhtyy Jul 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah to read the next child when it gets popped from the stack, i could pop from the stack then increment, not sure which u prefer


child_idx = 0;
current = self.nodes.get(*child).expect("there should be a child");
}
None => {
let mut instructions: Vec<VmInstruction> =
Vec::with_capacity(current.trace.steps.len());

for step in &current.trace.steps {
let maybe_sub = match step.op.u8() {
opcode::CALL |
opcode::CALLCODE |
opcode::DELEGATECALL |
opcode::STATICCALL |
opcode::CREATE |
opcode::CREATE2 => {
sub_stack.pop_front().expect("there should be a sub trace")
}
_ => None,
};

instructions.push(Self::make_instruction(step, maybe_sub));
}

match current.parent {
Some(parent) => {
sub_stack.push_back(Some(VmTrace {
code: Default::default(),
ops: instructions,
}));

child_idx = child_idx_stack.pop().expect("there should be a child idx");

current = self.nodes.get(parent).expect("there should be a parent");
}
None => break instructions,
}
}
}
};

VmTrace { code: Default::default(), ops: instructions }
}

/// Creates a VM instruction from a [CallTraceStep] and a [VmTrace] for the subcall if there is
/// one
fn make_instruction(step: &CallTraceStep, maybe_sub: Option<VmTrace>) -> VmInstruction {
let maybe_storage = step.storage_change.map(|storage_change| StorageDelta {
key: storage_change.key,
val: storage_change.value,
});

let maybe_memory = match step.memory.len() {
0 => None,
_ => {
Some(MemoryDelta { off: step.memory_size, data: step.memory.data().clone().into() })
}
};

let maybe_execution = Some(VmExecutedOperation {
used: step.gas_cost,
push: step.new_stack.map(|new_stack| new_stack.into()),
mem: maybe_memory,
store: maybe_storage,
});

VmInstruction {
pc: step.pc,
cost: 0, // TODO: use op gas cost
ex: maybe_execution,
sub: maybe_sub,
}
}
}

/// Construct the vmtrace for the entire callgraph
fn vm_trace(nodes: &[CallTraceNode]) -> VmTrace {
// TODO: populate vm trace
/// addresses are presorted via breadth first walk thru [CallTraceNode]s, this can be done by a
/// walker in [crate::tracing::builder::walker]
///
/// iteratively fill the [VmTrace] code fields
pub(crate) fn populate_vm_trace_bytecodes<DB, I>(
db: &DB,
trace: &mut VmTrace,
breadth_first_addresses: I,
) -> Result<(), DB::Error>
where
DB: DatabaseRef,
I: IntoIterator<Item = Address>,
{
let mut stack: VecDeque<&mut VmTrace> = VecDeque::new();
stack.push_back(trace);

let mut addrs = breadth_first_addresses.into_iter();

while let Some(curr_ref) = stack.pop_front() {
for op in curr_ref.ops.iter_mut() {
if let Some(sub) = op.sub.as_mut() {
stack.push_back(sub);
}
}

let addr = addrs.next().expect("there should be an address");

VmTrace { code: nodes[0].trace.data.clone().into(), ops: vec![] }
let db_acc = db.basic(addr)?.unwrap_or_default();

let code_hash = if db_acc.code_hash != KECCAK_EMPTY { db_acc.code_hash } else { continue };

curr_ref.code = db.code_by_hash(code_hash)?.bytecode.into();
}

Ok(())
}

/// Loops over all state accounts in the accounts diff that contains all accounts that are included
Expand Down
39 changes: 39 additions & 0 deletions crates/revm/revm-inspectors/src/tracing/builder/walker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use crate::tracing::types::CallTraceNode;
use std::collections::VecDeque;

/// Traverses Reths internal tracing structure breadth-first
///
/// This is a lazy iterator
pub(crate) struct CallTraceNodeWalkerBF<'trace> {
/// the entire arena
nodes: &'trace Vec<CallTraceNode>,

/// holds indexes of nodes to visit as we traverse
queue: VecDeque<usize>,
}

impl<'trace> CallTraceNodeWalkerBF<'trace> {
pub(crate) fn new(nodes: &'trace Vec<CallTraceNode>) -> Self {
let mut queue = VecDeque::with_capacity(nodes.len());
queue.push_back(0);

Self { nodes, queue }
}
}

impl<'trace> Iterator for CallTraceNodeWalkerBF<'trace> {
type Item = &'trace CallTraceNode;

fn next(&mut self) -> Option<Self::Item> {
match self.queue.pop_front() {
Some(idx) => {
let curr = self.nodes.get(idx).expect("there should be a node");

self.queue.extend(curr.children.iter());

Some(curr)
}
None => None,
}
}
}
5 changes: 5 additions & 0 deletions crates/revm/revm-inspectors/src/tracing/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,7 @@ impl TracingInspector {
op,
contract: interp.contract.address,
stack,
new_stack: None,
memory,
memory_size: interp.memory.len(),
gas_remaining: self.gas_inspector.gas_remaining(),
Expand All @@ -290,6 +291,10 @@ impl TracingInspector {
self.step_stack.pop().expect("can't fill step without starting a step first");
let step = &mut self.traces.arena[trace_idx].trace.steps[step_idx];

if interp.stack.len() > step.stack.len() {
step.new_stack = interp.stack.data().last().copied();
}

if self.config.record_memory_snapshots {
// resize memory so opcodes that allocated memory is correctly displayed
if interp.memory.len() > step.memory.len() {
Expand Down
4 changes: 3 additions & 1 deletion crates/revm/revm-inspectors/src/tracing/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -463,11 +463,13 @@ pub(crate) struct CallTraceStep {
pub(crate) contract: Address,
/// Stack before step execution
pub(crate) stack: Stack,
/// The new stack item placed by this step if any
pub(crate) new_stack: Option<U256>,
/// All allocated memory in a step
///
/// This will be empty if memory capture is disabled
pub(crate) memory: Memory,
/// Size of memory
/// Size of memory at the beginning of the step
pub(crate) memory_size: usize,
/// Remaining gas before step execution
pub(crate) gas_remaining: u64,
Expand Down