Skip to content

Commit

Permalink
feat: record the entire actor state on invoke (#1954)
Browse files Browse the repository at this point in the history
This change has two parts:

1. Record the _entire_ actor state on invoke. This will let us
accurately determine the state, address, etc. of an actor at the time of
invocation even if the transaction is later reverted.

2. Stop tracing non-"send" events. Specifically:

  - I've stopped tracing upgrades. They're not traditional "calls" and
  we'll want to handle them differently anyways.

  - Previously, we were tracing InvokeActor events for implicit
  constructor calls and upgrades. I've removed those as well as I want
  all InvokeActor events to be paired with some other event.

This should finally give us enough information in Lotus to accurately
translate an FVM trace into an EVM trace. Furthermore, the extra state
we're recording in the InvokeActor event will be useful for debugging.
  • Loading branch information
Stebalien authored Dec 15, 2023
1 parent 40bdf9c commit 6722164
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 25 deletions.
43 changes: 27 additions & 16 deletions fvm/src/call_manager/default.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,20 @@ where
K: Kernel<CallManager = Self>,
{
if self.machine.context().tracing {
self.trace(ExecutionEvent::Call {
from,
to,
entrypoint,
params: params.as_ref().map(Into::into),
value: value.clone(),
gas_limit: std::cmp::min(
gas_limit.unwrap_or(Gas::from_milligas(u64::MAX)).round_up(),
self.gas_tracker.gas_available().round_up(),
),
read_only,
});
if let Entrypoint::Invoke(method) = &entrypoint {
self.trace(ExecutionEvent::Call {
from,
to,
method: *method,
params: params.as_ref().map(Into::into),
value: value.clone(),
gas_limit: std::cmp::min(
gas_limit.unwrap_or(Gas::from_milligas(u64::MAX)).round_up(),
self.gas_tracker.gas_available().round_up(),
),
read_only,
});
}
}

// If a specific gas limit has been requested, push a new limit into the gas tracker.
Expand Down Expand Up @@ -222,7 +224,7 @@ where
})
}

if self.machine.context().tracing {
if self.machine.context().tracing && matches!(entrypoint, Entrypoint::Invoke(_)) {
self.trace(match &result {
Ok(InvocationResult { exit_code, value }) => {
ExecutionEvent::CallReturn(*exit_code, value.as_ref().map(Into::into))
Expand Down Expand Up @@ -578,7 +580,7 @@ where
self.call_actor_resolved::<K>(
system_actor::SYSTEM_ACTOR_ID,
id,
Entrypoint::Invoke(fvm_shared::METHOD_CONSTRUCTOR),
Entrypoint::ImplicitConstructor,
Some(Block::new(CBOR, params, Vec::new())),
&TokenAmount::zero(),
false,
Expand Down Expand Up @@ -660,8 +662,17 @@ where
.get_actor(to)?
.ok_or_else(|| syscall_error!(NotFound; "actor does not exist: {}", to))?;

if self.machine.context().tracing {
self.trace(ExecutionEvent::InvokeActor(state.code));
// We're only tracing explicit "invokes" (no upgrades or implicit constructions, for now) as
// we want to be able to pair the invoke with another event in the trace. I.e. call ->
// invoke, upgrade -> invoke, construct -> invoke.
//
// Once we add tracing support for upgrades, we can start recording those actor invocations
// as well.
if self.machine.context().tracing && matches!(entrypoint, Entrypoint::Invoke(_)) {
self.trace(ExecutionEvent::InvokeActor {
id: to,
state: state.clone(),
});
}

// Transfer, if necessary.
Expand Down
13 changes: 10 additions & 3 deletions fvm/src/call_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use fvm_shared::address::Address;
use fvm_shared::econ::TokenAmount;
use fvm_shared::error::ExitCode;
use fvm_shared::upgrade::UpgradeInfo;
use fvm_shared::{ActorID, MethodNum};
use fvm_shared::{ActorID, MethodNum, METHOD_CONSTRUCTOR};

use crate::engine::Engine;
use crate::gas::{Gas, GasCharge, GasTimer, GasTracker, PriceList};
Expand Down Expand Up @@ -205,7 +205,11 @@ pub struct FinishRet {

#[derive(Clone, Debug, Copy)]
pub enum Entrypoint {
/// Implicitly invoke a constructor. We keep this separate for better tracing.
ImplicitConstructor,
/// Invoke a method.
Invoke(MethodNum),
/// Upgrade to a new actor code CID.
Upgrade(UpgradeInfo),
}

Expand All @@ -217,6 +221,7 @@ const METHOD_UPGRADE: MethodNum = 932083;
impl std::fmt::Display for Entrypoint {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Entrypoint::ImplicitConstructor => write!(f, "implicit_constructor"),
Entrypoint::Invoke(method) => write!(f, "invoke({})", method),
Entrypoint::Upgrade(_) => write!(f, "upgrade"),
}
Expand All @@ -226,28 +231,30 @@ impl std::fmt::Display for Entrypoint {
impl Entrypoint {
fn method_num(&self) -> MethodNum {
match self {
Entrypoint::ImplicitConstructor => METHOD_CONSTRUCTOR,
Entrypoint::Invoke(num) => *num,
Entrypoint::Upgrade(_) => METHOD_UPGRADE,
}
}

fn func_name(&self) -> &'static str {
match self {
Entrypoint::Invoke(_) => INVOKE_FUNC_NAME,
Entrypoint::ImplicitConstructor | Entrypoint::Invoke(_) => INVOKE_FUNC_NAME,
Entrypoint::Upgrade(_) => UPGRADE_FUNC_NAME,
}
}

fn invokes(&self, method: MethodNum) -> bool {
match self {
Entrypoint::ImplicitConstructor => method == METHOD_CONSTRUCTOR,
Entrypoint::Invoke(num) => *num == method,
Entrypoint::Upgrade(_) => false,
}
}

fn into_params(self, br: &mut BlockRegistry) -> Result<Vec<wasmtime::Val>> {
match self {
Entrypoint::Invoke(_) => Ok(Vec::new()),
Entrypoint::ImplicitConstructor | Entrypoint::Invoke(_) => Ok(Vec::new()),
Entrypoint::Upgrade(ui) => {
let ui_params = to_vec(&ui)
.or_fatal()
Expand Down
14 changes: 8 additions & 6 deletions fvm/src/trace/mod.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
use cid::Cid;
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
use fvm_ipld_encoding::ipld_block::IpldBlock;
use fvm_shared::address::Address;
use fvm_shared::econ::TokenAmount;
use fvm_shared::error::ExitCode;
use fvm_shared::ActorID;
use fvm_shared::state::ActorState;
use fvm_shared::{ActorID, MethodNum};

use crate::call_manager::Entrypoint;
use crate::gas::GasCharge;
use crate::kernel::SyscallError;

Expand All @@ -26,14 +25,17 @@ pub enum ExecutionEvent {
Call {
from: ActorID,
to: Address,
entrypoint: Entrypoint,
method: MethodNum,
params: Option<IpldBlock>,
value: TokenAmount,
gas_limit: u64,
read_only: bool,
},
CallReturn(ExitCode, Option<IpldBlock>),
CallError(SyscallError),
/// Emitted every time we successfully invoke an actor
InvokeActor(Cid),
/// Emitted every time an actor is successfully invoked.
InvokeActor {
id: ActorID,
state: ActorState,
},
}

0 comments on commit 6722164

Please sign in to comment.