fix: process geth steps iteratively (#2980)

Co-authored-by: Georgios Konstantopoulos <me@gakonst.com>
This commit is contained in:
Matthias Seitz
2023-06-05 04:49:24 +02:00
committed by GitHub
parent f042548689
commit ef6fa6dca2
2 changed files with 79 additions and 32 deletions

View File

@ -1,10 +1,13 @@
//! Geth trace builder
use crate::tracing::{types::CallTraceNode, TracingInspectorConfig};
use crate::tracing::{
types::{CallTraceNode, CallTraceStepStackItem},
TracingInspectorConfig,
};
use reth_primitives::{Address, JsonU256, H256, U256};
use reth_rpc_types::trace::geth::*;
use revm::interpreter::opcode;
use std::collections::{BTreeMap, HashMap};
use std::collections::{BTreeMap, HashMap, VecDeque};
/// A type for creating geth style traces
#[derive(Clone, Debug)]
@ -21,19 +24,29 @@ impl GethTraceBuilder {
Self { nodes, _config }
}
/// Recursively fill in the geth trace by going through the traces
///
/// TODO rewrite this iteratively
fn add_to_geth_trace(
/// Fill in the geth trace with all steps of the trace and its children traces in the order they
/// appear in the transaction.
fn fill_geth_trace(
&self,
storage: &mut HashMap<Address, BTreeMap<H256, H256>>,
trace_node: &CallTraceNode,
struct_logs: &mut Vec<StructLog>,
main_trace_node: &CallTraceNode,
opts: &GethDefaultTracingOptions,
storage: &mut HashMap<Address, BTreeMap<H256, H256>>,
struct_logs: &mut Vec<StructLog>,
) {
let mut child_id = 0;
// A stack with all the steps of the trace and all its children's steps.
// This is used to process the steps in the order they appear in the transactions.
// Steps are grouped by their Call Trace Node, in order to process them all in the order
// they appear in the transaction, we need to process steps of call nodes when they appear.
// When we find a call step, we push all the steps of the child trace on the stack, so they
// are processed next. The very next step is the last item on the stack
let mut step_stack = VecDeque::with_capacity(main_trace_node.trace.steps.len());
main_trace_node.push_steps_on_stack(&mut step_stack);
// Iterate over the steps inside the given trace
for step in trace_node.trace.steps.iter() {
while let Some(CallTraceStepStackItem { trace_node, step, call_child_id }) =
step_stack.pop_back()
{
let mut log: StructLog = step.into();
// Fill in memory and storage depending on the options
@ -59,23 +72,11 @@ impl GethTraceBuilder {
// Add step to geth trace
struct_logs.push(log);
// If the opcode is a call, the descend into child trace
match step.op.u8() {
opcode::CREATE |
opcode::CREATE2 |
opcode::DELEGATECALL |
opcode::CALL |
opcode::STATICCALL |
opcode::CALLCODE => {
self.add_to_geth_trace(
storage,
&self.nodes[trace_node.children[child_id]],
struct_logs,
opts,
);
child_id += 1;
}
_ => {}
// If the step is a call, we first push all the steps of the child trace on the stack,
// so they are processed next
if let Some(call_child_id) = call_child_id {
let child_trace = &self.nodes[call_child_id];
child_trace.push_steps_on_stack(&mut step_stack);
}
}
}
@ -96,7 +97,7 @@ impl GethTraceBuilder {
let mut struct_logs = Vec::new();
let mut storage = HashMap::new();
self.add_to_geth_trace(&mut storage, main_trace_node, &mut struct_logs, &opts);
self.fill_geth_trace(main_trace_node, &opts, &mut storage, &mut struct_logs);
DefaultFrame {
// If the top-level trace succeeded, then it was a success

View File

@ -11,10 +11,10 @@ use reth_rpc_types::trace::{
},
};
use revm::interpreter::{
CallContext, CallScheme, CreateScheme, InstructionResult, Memory, OpCode, Stack,
opcode, CallContext, CallScheme, CreateScheme, InstructionResult, Memory, OpCode, Stack,
};
use serde::{Deserialize, Serialize};
use std::collections::btree_map::Entry;
use std::collections::{btree_map::Entry, VecDeque};
/// A unified representation of a call
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
@ -196,6 +196,43 @@ pub(crate) struct CallTraceNode {
}
impl CallTraceNode {
/// Pushes all steps onto the stack in reverse order
/// so that the first step is on top of the stack
pub(crate) fn push_steps_on_stack<'a>(
&'a self,
stack: &mut VecDeque<CallTraceStepStackItem<'a>>,
) {
stack.extend(self.call_step_stack().into_iter().rev());
}
/// Returns a list of all steps in this trace in the order they were executed
///
/// If the step is a call, the id of the child trace is set.
pub(crate) fn call_step_stack(&self) -> Vec<CallTraceStepStackItem<'_>> {
let mut stack = Vec::with_capacity(self.trace.steps.len());
let mut child_id = 0;
for step in self.trace.steps.iter() {
let mut item = CallTraceStepStackItem { trace_node: self, step, call_child_id: None };
// If the opcode is a call, put the child trace on the stack
match step.op.u8() {
opcode::CREATE |
opcode::CREATE2 |
opcode::DELEGATECALL |
opcode::CALL |
opcode::STATICCALL |
opcode::CALLCODE => {
let call_id = self.children[child_id];
item.call_child_id = Some(call_id);
child_id += 1;
}
_ => {}
}
stack.push(item);
}
stack
}
/// Returns the kind of call the trace belongs to
pub(crate) fn kind(&self) -> CallKind {
self.trace.kind
@ -356,6 +393,15 @@ impl CallTraceNode {
}
}
pub(crate) struct CallTraceStepStackItem<'a> {
/// The trace node that contains this step
pub(crate) trace_node: &'a CallTraceNode,
/// The step that this stack item represents
pub(crate) step: &'a CallTraceStep,
/// The index of the child call in the CallArena if this step's opcode is a call
pub(crate) call_child_id: Option<usize>,
}
/// Ordering enum for calls and logs
///
/// i.e. if Call 0 occurs before Log 0, it will be pushed into the `CallTraceNode`'s ordering before