cfx_rpc_common_impl/trace/
matcher.rs

1use cfx_parity_trace_types::{Action, ExecTrace};
2
3/// Converts raw EVM execution steps into Parity-compatible trace entries,
4/// pairing each action (call/create) with its corresponding result.
5pub fn construct_parity_trace<'a>(
6    tx_traces: &'a [ExecTrace],
7) -> Result<Box<dyn 'a + Iterator<Item = TraceWithPosition<'a>>>, String> {
8    let empty_traces = !tx_traces.iter().any(|x| {
9        !matches!(
10            x.action,
11            Action::InternalTransferAction(_) | Action::SetAuth(_)
12        )
13    });
14    if empty_traces {
15        return Ok(Box::new(std::iter::empty()));
16    }
17
18    let call_hierarchy = build_call_hierarchy(tx_traces)?;
19    Ok(call_hierarchy.flatten_into_traces(vec![]))
20}
21
22pub enum TraceWithPosition<'a> {
23    CallCreate(CallCreateTraceWithPosition<'a>),
24    Suicide(SelfDestructTraceWithPosition<'a>),
25}
26
27/// Final trace output with execution position metadata
28pub struct CallCreateTraceWithPosition<'a> {
29    pub action: &'a ExecTrace,
30    pub result: &'a ExecTrace,
31    pub child_count: usize,
32    pub trace_path: Vec<usize>,
33}
34
35pub struct SelfDestructTraceWithPosition<'a> {
36    pub action: &'a ExecTrace,
37    pub child_count: usize,
38    pub trace_path: Vec<usize>,
39}
40
41/// Represents an EVM execution frame with matched action-result pair
42/// and nested child frames (sub-calls).
43pub struct CallCreateTraceNode<'a> {
44    action_trace: ActionTrace<'a>,
45    result_trace: ResultTrace<'a>,
46    child_nodes: Vec<TraceNode<'a>>,
47    total_child_count: usize,
48}
49
50enum TraceNode<'a> {
51    CallCreate(CallCreateTraceNode<'a>),
52    Suicide(SelfDestructTrace<'a>),
53}
54
55impl<'a> CallCreateTraceNode<'a> {
56    /// Creates a new node after validating action-result type consistency.
57    ///
58    /// # Arguments
59    /// * `action` - Initiation of an EVM operation (Call/Create)
60    /// * `result` - Completion of the operation (CallResult/CreateResult)
61    /// * `children` - Child nodes from nested operations (contract creation or
62    ///   message call)
63    fn new(
64        action: ActionTrace<'a>, result: ResultTrace<'a>,
65        children: Vec<TraceNode<'a>>,
66    ) -> Result<Self, String> {
67        // Validate action-result type pairing
68        match (&action.0.action, &result.0.action) {
69            (Action::Call(_), Action::CallResult(_))
70            | (Action::Create(_), Action::CreateResult(_)) => {}
71            _ => {
72                return Err(format!(
73                    "Type mismatch. Action: {:?}, Result: {:?}",
74                    action.0.action, result.0.action
75                ))
76            }
77        }
78
79        // Calculate total children count (direct + indirect)
80        let total_child_count = children.len()
81            + children
82                .iter()
83                .map(|x| match x {
84                    TraceNode::CallCreate(node) => node.total_child_count,
85                    TraceNode::Suicide(_) => 0,
86                })
87                .sum::<usize>();
88
89        Ok(Self {
90            action_trace: action,
91            result_trace: result,
92            child_nodes: children,
93            total_child_count,
94        })
95    }
96
97    /// Converts hierarchical structure into flat iterator with positional
98    /// metadata
99    pub fn flatten_into_traces(
100        self, trace_path: Vec<usize>,
101    ) -> Box<dyn 'a + Iterator<Item = TraceWithPosition<'a>>> {
102        // Current node's trace entry
103        let root_entry = std::iter::once(TraceWithPosition::CallCreate(
104            CallCreateTraceWithPosition {
105                action: self.action_trace.0,
106                result: self.result_trace.0,
107                child_count: self.total_child_count,
108                trace_path: trace_path.clone(),
109            },
110        ));
111
112        // Recursively process child nodes
113        let child_entries = self.child_nodes.into_iter().enumerate().flat_map(
114            move |(idx, child)| {
115                let mut child_path = trace_path.clone();
116                child_path.push(idx);
117                match child {
118                    TraceNode::CallCreate(child) => {
119                        child.flatten_into_traces(child_path)
120                    }
121                    TraceNode::Suicide(suicide) => {
122                        Box::new(std::iter::once(TraceWithPosition::Suicide(
123                            SelfDestructTraceWithPosition {
124                                action: suicide.0,
125                                child_count: 0,
126                                trace_path: child_path,
127                            },
128                        )))
129                    }
130                }
131            },
132        );
133
134        Box::new(root_entry.chain(child_entries))
135    }
136}
137
138/// Builds hierarchical call structure from raw traces.
139/// Returns root node of the execution tree.
140pub fn build_call_hierarchy<'a>(
141    traces: &'a [ExecTrace],
142) -> Result<CallCreateTraceNode<'a>, String> {
143    // Stack tracks unclosed actions and their collected children
144    let mut unclosed_actions: Vec<(ActionTrace, Vec<TraceNode>)> = vec![];
145
146    // Filter out internal transfer and set auth events (handled separately)
147    let mut iter = traces.iter().filter(|x| {
148        !matches!(
149            x.action,
150            Action::InternalTransferAction(_) | Action::SetAuth(_)
151        )
152    });
153
154    while let Some(trace) = iter.next() {
155        match trace.action {
156            // New operation - push to stack
157            Action::Call(_) | Action::Create(_) => {
158                let action = ActionTrace::try_from(trace).unwrap();
159                unclosed_actions.push((action, vec![]));
160            }
161
162            // Operation completion - pop stack and build node
163            Action::CallResult(_) | Action::CreateResult(_) => {
164                let result = ResultTrace::try_from(trace).unwrap();
165
166                let Some((action, children)) = unclosed_actions.pop() else {
167                    return Err(format!(
168                        "Orphaned result without matching action: {:?}",
169                        trace
170                    ));
171                };
172
173                let node = CallCreateTraceNode::new(action, result, children)?;
174
175                // Attach to parent if exists, otherwise return as root
176                if let Some((_, parent_children)) = unclosed_actions.last_mut()
177                {
178                    parent_children.push(TraceNode::CallCreate(node));
179                } else {
180                    return if let Some(trace) = iter.next() {
181                        Err(format!(
182                            "Trailing traces after root node closure: {:?}",
183                            trace
184                        ))
185                    } else {
186                        Ok(node)
187                    };
188                }
189            }
190            Action::SelfDestruct(_) => {
191                let action = SelfDestructTrace::try_from(trace).unwrap();
192                // Attach to parent if exists, otherwise return as root
193                if let Some((_, parent_children)) = unclosed_actions.last_mut()
194                {
195                    parent_children.push(TraceNode::Suicide(action));
196                } else {
197                    return Err("selfdestruct trace should have parent".into());
198                }
199            }
200            // Filtered out earlier
201            Action::InternalTransferAction(_) | Action::SetAuth(_) => {
202                unreachable!()
203            }
204        }
205    }
206    // Loop should only exit when stack is empty
207    Err("Incomplete trace: missing result for the root-level".into())
208}
209
210/// Helper types for type-safe action/result separation
211struct ActionTrace<'a>(&'a ExecTrace);
212
213/// Helper types for type-safe action/result separation
214struct ResultTrace<'a>(&'a ExecTrace);
215
216impl<'a> TryFrom<&'a ExecTrace> for ActionTrace<'a> {
217    type Error = &'static str;
218
219    fn try_from(trace: &'a ExecTrace) -> Result<Self, Self::Error> {
220        match trace.action {
221            Action::Call(_) | Action::Create(_) => Ok(Self(trace)),
222            _ => Err("Not an action trace"),
223        }
224    }
225}
226
227impl<'a> TryFrom<&'a ExecTrace> for ResultTrace<'a> {
228    type Error = &'static str;
229
230    fn try_from(trace: &'a ExecTrace) -> Result<Self, Self::Error> {
231        match trace.action {
232            Action::CallResult(_) | Action::CreateResult(_) => Ok(Self(trace)),
233            _ => Err("Not a result trace"),
234        }
235    }
236}
237
238struct SelfDestructTrace<'a>(&'a ExecTrace);
239
240impl<'a> TryFrom<&'a ExecTrace> for SelfDestructTrace<'a> {
241    type Error = &'static str;
242
243    fn try_from(trace: &'a ExecTrace) -> Result<Self, Self::Error> {
244        match trace.action {
245            Action::SelfDestruct(_) => Ok(Self(trace)),
246            _ => Err("Not a self-destruct trace"),
247        }
248    }
249}