Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement execution traces for the FVM #412

Merged
merged 2 commits into from
Apr 18, 2022
Merged

Implement execution traces for the FVM #412

merged 2 commits into from
Apr 18, 2022

Conversation

arajasek
Copy link
Contributor

Resolves #318

This gets us most of the way to matching Lotus execution traces -- fine gas tracing isn't implemented yet, but it will be impossible to meet that level of detail in M1 anyway.

The ExecutionTrace is created and populated by the call manager, which returns it on finish.

Comment on lines 128 to 132
ExecutionError::Syscall(s) => Receipt {
exit_code: match s.1.try_into() {
Ok(e) => e,
_ => ErrPlaceholder,
},
Copy link
Member

Choose a reason for hiding this comment

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

Hm. Any chance we could change the format? We're intentionally distinguishing between syscall errors and exit codes.

Copy link
Member

Choose a reason for hiding this comment

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

Basically, receipts are really for recording results on-chain, but aren't the best for debugging. In this case, it's also causing us to drop the error message.

@raulk raulk requested review from lemmih and noot March 31, 2022 12:01
@@ -110,12 +138,19 @@ where
res
}

fn finish(mut self) -> (i64, Backtrace, Self::Machine) {
fn finish(mut self) -> (FinishResult, Self::Machine) {
Copy link
Member

Choose a reason for hiding this comment

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

nit: FinishResult sounds like a specialized Result type. Can we change this type name to FinishRet, parallel with ApplyRet?

@@ -140,3 +146,9 @@ impl InvocationResult {
}
}
}

pub struct FinishResult {
Copy link
Member

Choose a reason for hiding this comment

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

nit: docs.

@@ -140,3 +146,9 @@ impl InvocationResult {
}
}
}

pub struct FinishResult {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe just make the type pub(crate) instead of its fields? It's a little bit useless to have a public method-less struct with all pub(crate) fields.

Copy link
Member

Choose a reason for hiding this comment

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

I'd just make it all public.

@@ -149,12 +153,15 @@ where
};

match apply_kind {
ApplyKind::Explicit => self.finish_message(msg, receipt, failure_info, gas_cost),
ApplyKind::Explicit => {
self.finish_message(msg, receipt, failure_info, gas_cost, exec_trace)
Copy link
Member

Choose a reason for hiding this comment

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

Hm, this was confusing to me. I didn't expect finish_message to operate on the exec_trace, so I didn't understand why we passed it in. And indeed it doesn't.

Did you consider:

.map(|apply_ret| {
    apply_ret.exec_trace = exec_trace;
    apply_ret
})

It's a few more lines of code but it's less surprising (for me). Alternatively we could use a one-liner:

.inspect(|apply_ret| { apply_ret.exec_trace = exec_trace })

But it requires us to enable an experimental API: rust-lang/rust#91345.

self.exec_trace = Some(vec![call_event]);
} else {
self.exec_trace.as_mut().unwrap().push(call_event);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

should be written as

if let Some(ref mut exec_trace) = self.exec_trace {
  exec_trace.push(call_event);
} else {
  self.exec_trace = Some(vec![call_event]);
}

if self.machine.config().debug {
self.exec_trace
.as_mut()
.unwrap()
Copy link
Contributor

Choose a reason for hiding this comment

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

this should be at least an .expect explaining why this is already checked

Comment on lines 102 to 106
if self.exec_trace.is_none() {
self.exec_trace = Some(vec![call_event]);
} else {
self.exec_trace.as_mut().unwrap().push(call_event);
}
Copy link
Member

Choose a reason for hiding this comment

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

Given that this is just a vector, we can probably drop the option entirely and just start out with an empty vector.

But I also feel compelled to one-up @dignifiedquire:

Suggested change
if self.exec_trace.is_none() {
self.exec_trace = Some(vec![call_event]);
} else {
self.exec_trace.as_mut().unwrap().push(call_event);
}
self.exec_trace.get_or_insert_with(Vec::new).push(call_event);

Copy link
Contributor

Choose a reason for hiding this comment

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

👌

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Y'all and this entire language are outta control. But that's cool to see.

@@ -140,3 +146,9 @@ impl InvocationResult {
}
}
}

pub struct FinishResult {
Copy link
Member

Choose a reason for hiding this comment

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

I'd just make it all public.

Copy link
Member

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

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

Mostly LGTM with some comments.

(Ok((result, gas_used, backtrace)), machine)
let (res, machine) = cm.finish();
(
Ok((result, res.gas_used, res.backtrace, res.exec_trace)),
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Ok((result, res.gas_used, res.backtrace, res.exec_trace)),
Ok(((result, res), machine)),

?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Eh, but we use gas_used so many times, and they're not suuuper related concepts, and I don't like it.

Copy link
Member

Choose a reason for hiding this comment

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

We can destructure this on the outside. I'm just suggesting a way to slightly reduce the amount of code.

Copy link
Member

Choose a reason for hiding this comment

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

(but it doesn't really matter)

Comment on lines 40 to 48
impl Clone for ExecutionError {
fn clone(&self) -> Self {
match self {
OutOfGas => OutOfGas,
Syscall(v) => Syscall(v.clone()),
Fatal(v) => Fatal(anyhow::Error::msg(v.to_string())),
}
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Hm... this isn't really a clone. I'd handle these cases separately:

  1. fatal/out-of-gas: nothing? We can just cut the trace short because it doesn't really matter.
  2. Syscall error -> push that.

Copy link
Member

Choose a reason for hiding this comment

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

Basically, record Result<InvocationResult, SyscallError> instead of Result<InvocationResult, ExecutionError>.

@@ -125,7 +125,16 @@ where
self.call_stack_depth -= 1;

if self.machine.context().tracing {
self.exec_trace.push(ExecutionEvent::Return(result.clone()));
self.exec_trace.push(ExecutionEvent::Return(match result {
Err(ref e) => Err(match e {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This combination of refs and clones appeased the checker, but I feel like there's something better that can be done?

Copy link
Member

Choose a reason for hiding this comment

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

Not really...

Copy link
Member

Choose a reason for hiding this comment

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

Well, you can use &result and remove the ref e and friends, but you still need to clone. On the other hand, this will only happen when tracing is enabled.

Copy link
Member

@Stebalien Stebalien left a comment

Choose a reason for hiding this comment

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

LGTM.

(Ok((result, gas_used, backtrace)), machine)
let (res, machine) = cm.finish();
(
Ok((result, res.gas_used, res.backtrace, res.exec_trace)),
Copy link
Member

Choose a reason for hiding this comment

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

We can destructure this on the outside. I'm just suggesting a way to slightly reduce the amount of code.

(Ok((result, gas_used, backtrace)), machine)
let (res, machine) = cm.finish();
(
Ok((result, res.gas_used, res.backtrace, res.exec_trace)),
Copy link
Member

Choose a reason for hiding this comment

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

(but it doesn't really matter)

@@ -125,7 +125,16 @@ where
self.call_stack_depth -= 1;

if self.machine.context().tracing {
self.exec_trace.push(ExecutionEvent::Return(result.clone()));
self.exec_trace.push(ExecutionEvent::Return(match result {
Err(ref e) => Err(match e {
Copy link
Member

Choose a reason for hiding this comment

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

Not really...

@arajasek arajasek merged commit c9f3c6e into master Apr 18, 2022
@arajasek arajasek deleted the asr/exec-trace branch April 18, 2022 16:03
Copy link
Member

@raulk raulk left a comment

Choose a reason for hiding this comment

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

LGTM.

self.exec_trace.push(ExecutionEvent::Return(match result {
Err(ref e) => Err(match e {
ExecutionError::OutOfGas => {
SyscallError::new(ErrorNumber::Forbidden, "out of gas")
Copy link
Member

Choose a reason for hiding this comment

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

Are these syscall errors numbers the correct ones to use here?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

support execution traces
4 participants