use cfx_parity_trace_types::{Action, ExecTrace};
pub fn construct_parity_trace<'a>(
tx_traces: &'a [ExecTrace],
) -> Result<Box<dyn 'a + Iterator<Item = TraceWithPosition<'a>>>, String> {
let empty_traces = !tx_traces
.iter()
.any(|x| !matches!(x.action, Action::InternalTransferAction(_)));
if empty_traces {
return Ok(Box::new(std::iter::empty()));
}
let call_hierarchy = build_call_hierarchy(tx_traces)?;
Ok(call_hierarchy.flatten_into_traces(vec![]))
}
pub struct TraceWithPosition<'a> {
pub action: &'a ExecTrace,
pub result: &'a ExecTrace,
pub child_count: usize,
pub trace_path: Vec<usize>,
}
pub struct ResolvedTraceNode<'a> {
action_trace: ActionTrace<'a>,
result_trace: ResultTrace<'a>,
child_nodes: Vec<ResolvedTraceNode<'a>>,
total_child_count: usize,
}
impl<'a> ResolvedTraceNode<'a> {
fn new(
action: ActionTrace<'a>, result: ResultTrace<'a>,
children: Vec<ResolvedTraceNode<'a>>,
) -> Result<Self, String> {
match (&action.0.action, &result.0.action) {
(Action::Call(_), Action::CallResult(_))
| (Action::Create(_), Action::CreateResult(_)) => {}
_ => {
return Err(format!(
"Type mismatch. Action: {:?}, Result: {:?}",
action.0.action, result.0.action
))
}
}
let total_child_count = children.len()
+ children.iter().map(|x| x.total_child_count).sum::<usize>();
Ok(Self {
action_trace: action,
result_trace: result,
child_nodes: children,
total_child_count,
})
}
pub fn flatten_into_traces(
self, trace_path: Vec<usize>,
) -> Box<dyn 'a + Iterator<Item = TraceWithPosition<'a>>> {
let root_entry = std::iter::once(TraceWithPosition {
action: self.action_trace.0,
result: self.result_trace.0,
child_count: self.total_child_count,
trace_path: trace_path.clone(),
});
let child_entries = self.child_nodes.into_iter().enumerate().flat_map(
move |(idx, child)| {
let mut child_path = trace_path.clone();
child_path.push(idx);
child.flatten_into_traces(child_path)
},
);
Box::new(root_entry.chain(child_entries))
}
}
pub fn build_call_hierarchy<'a>(
traces: &'a [ExecTrace],
) -> Result<ResolvedTraceNode<'a>, String> {
let mut unclosed_actions: Vec<(ActionTrace, Vec<ResolvedTraceNode>)> =
vec![];
let mut iter = traces
.iter()
.filter(|x| !matches!(x.action, Action::InternalTransferAction(_)));
while let Some(trace) = iter.next() {
match trace.action {
Action::Call(_) | Action::Create(_) => {
let action = ActionTrace::try_from(trace).unwrap();
unclosed_actions.push((action, vec![]));
}
Action::CallResult(_) | Action::CreateResult(_) => {
let result = ResultTrace::try_from(trace).unwrap();
let Some((action, children)) = unclosed_actions.pop() else {
return Err(format!(
"Orphaned result without matching action: {:?}",
trace
));
};
let node = ResolvedTraceNode::new(action, result, children)?;
if let Some((_, parent_children)) = unclosed_actions.last_mut()
{
parent_children.push(node);
} else {
return if let Some(trace) = iter.next() {
Err(format!(
"Trailing traces after root node closure: {:?}",
trace
))
} else {
Ok(node)
};
}
}
Action::InternalTransferAction(_) => unreachable!(),
}
}
Err("Incomplete trace: missing result for the root-level".into())
}
struct ActionTrace<'a>(&'a ExecTrace);
struct ResultTrace<'a>(&'a ExecTrace);
impl<'a> TryFrom<&'a ExecTrace> for ActionTrace<'a> {
type Error = &'static str;
fn try_from(trace: &'a ExecTrace) -> Result<Self, Self::Error> {
match trace.action {
Action::Call(_) | Action::Create(_) => Ok(Self(trace)),
_ => Err("Not an action trace"),
}
}
}
impl<'a> TryFrom<&'a ExecTrace> for ResultTrace<'a> {
type Error = &'static str;
fn try_from(trace: &'a ExecTrace) -> Result<Self, Self::Error> {
match trace.action {
Action::CallResult(_) | Action::CreateResult(_) => Ok(Self(trace)),
_ => Err("Not a result trace"),
}
}
}