Skip to content

Commit c9f3c6e

Browse files
authored
Merge pull request #412 from filecoin-project/asr/exec-trace
Implement execution traces for the FVM
2 parents bed676b + 39a7575 commit c9f3c6e

File tree

9 files changed

+121
-22
lines changed

9 files changed

+121
-22
lines changed

fvm/src/call_manager/default.rs

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ use fvm_ipld_encoding::{RawBytes, DAG_CBOR};
44
use fvm_shared::actor::builtin::Type;
55
use fvm_shared::address::{Address, Protocol};
66
use fvm_shared::econ::TokenAmount;
7-
use fvm_shared::error::ExitCode;
7+
use fvm_shared::error::{ErrorNumber, ExitCode};
88
use fvm_shared::version::NetworkVersion;
99
use fvm_shared::{ActorID, MethodNum, METHOD_SEND};
1010
use num_traits::Zero;
1111

1212
use super::{Backtrace, CallManager, InvocationResult, NO_DATA_BLOCK_ID};
1313
use crate::call_manager::backtrace::Frame;
14+
use crate::call_manager::FinishRet;
1415
use crate::gas::GasTracker;
15-
use crate::kernel::{ClassifyResult, ExecutionError, Kernel, Result};
16+
use crate::kernel::{ClassifyResult, ExecutionError, Kernel, Result, SyscallError};
1617
use crate::machine::Machine;
1718
use crate::syscalls::error::Abort;
19+
use crate::trace::{ExecutionEvent, ExecutionTrace, SendParams};
1820
use crate::{account_actor, syscall_error};
1921

2022
/// The default [`CallManager`] implementation.
@@ -40,6 +42,8 @@ pub struct InnerDefaultCallManager<M> {
4042
call_stack_depth: u32,
4143
/// The current chain of errors, if any.
4244
backtrace: Backtrace,
45+
/// The current execution trace.
46+
exec_trace: ExecutionTrace,
4347
}
4448

4549
#[doc(hidden)]
@@ -73,6 +77,7 @@ where
7377
num_actors_created: 0,
7478
call_stack_depth: 0,
7579
backtrace: Backtrace::default(),
80+
exec_trace: vec![],
7681
})))
7782
}
7883

@@ -87,6 +92,16 @@ where
8792
where
8893
K: Kernel<CallManager = Self>,
8994
{
95+
if self.machine.context().tracing {
96+
self.exec_trace.push(ExecutionEvent::Call(SendParams {
97+
from,
98+
to,
99+
method,
100+
params: params.clone(),
101+
value: value.clone(),
102+
}));
103+
}
104+
90105
// We check _then_ set because we don't count the top call. This effectivly allows a
91106
// call-stack depth of `max_call_depth + 1` (or `max_call_depth` sub-calls). While this is
92107
// likely a bug, this is how NV15 behaves so we mimic that behavior here.
@@ -108,6 +123,20 @@ where
108123
self.call_stack_depth += 1;
109124
let result = self.send_unchecked::<K>(from, to, method, params, value);
110125
self.call_stack_depth -= 1;
126+
127+
if self.machine.context().tracing {
128+
self.exec_trace.push(ExecutionEvent::Return(match result {
129+
Err(ref e) => Err(match e {
130+
ExecutionError::OutOfGas => {
131+
SyscallError::new(ErrorNumber::Forbidden, "out of gas")
132+
}
133+
ExecutionError::Fatal(_) => SyscallError::new(ErrorNumber::Forbidden, "fatal"),
134+
ExecutionError::Syscall(s) => s.clone(),
135+
}),
136+
Ok(ref v) => Ok(v.clone()),
137+
}));
138+
}
139+
111140
result
112141
}
113142

@@ -124,12 +153,19 @@ where
124153
res
125154
}
126155

127-
fn finish(mut self) -> (i64, Backtrace, Self::Machine) {
156+
fn finish(mut self) -> (FinishRet, Self::Machine) {
128157
let gas_used = self.gas_tracker.gas_used().max(0);
129158

130159
let inner = self.0.take().expect("call manager is poisoned");
131160
// TODO: Having to check against zero here is fishy, but this is what lotus does.
132-
(gas_used, inner.backtrace, inner.machine)
161+
(
162+
FinishRet {
163+
gas_used,
164+
backtrace: inner.backtrace,
165+
exec_trace: inner.exec_trace,
166+
},
167+
inner.machine,
168+
)
133169
}
134170

135171
// Accessor methods so the trait can implement some common methods by default.

fvm/src/call_manager/mod.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ use crate::state_tree::StateTree;
1111
use crate::Kernel;
1212

1313
pub mod backtrace;
14+
1415
pub use backtrace::Backtrace;
1516

1617
mod default;
1718

1819
pub use default::DefaultCallManager;
1920

21+
use crate::trace::ExecutionTrace;
22+
2023
/// BlockID representing nil parameters or return data.
2124
pub const NO_DATA_BLOCK_ID: u32 = 0;
2225

@@ -59,8 +62,8 @@ pub trait CallManager: 'static {
5962
f: impl FnOnce(&mut Self) -> Result<InvocationResult>,
6063
) -> Result<InvocationResult>;
6164

62-
/// Finishes execution, returning the gas used and the machine.
63-
fn finish(self) -> (i64, backtrace::Backtrace, Self::Machine);
65+
/// Finishes execution, returning the gas used, machine, and exec trace if requested.
66+
fn finish(self) -> (FinishRet, Self::Machine);
6467

6568
/// Returns a reference to the machine.
6669
fn machine(&self) -> &Self::Machine;
@@ -119,6 +122,7 @@ pub trait CallManager: 'static {
119122
}
120123

121124
/// The result of a method invocation.
125+
#[derive(Clone, Debug)]
122126
pub enum InvocationResult {
123127
/// Indicates that the actor successfully returned. The value may be empty.
124128
Return(RawBytes),
@@ -142,3 +146,10 @@ impl InvocationResult {
142146
}
143147
}
144148
}
149+
150+
/// The returned values upon finishing a call manager.
151+
pub struct FinishRet {
152+
pub gas_used: i64,
153+
pub backtrace: Backtrace,
154+
pub exec_trace: ExecutionTrace,
155+
}

fvm/src/executor/default.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,12 +59,12 @@ where
5959
};
6060

6161
// Apply the message.
62-
let (res, gas_used, mut backtrace) = self.map_machine(|machine| {
62+
let (res, gas_used, mut backtrace, exec_trace) = self.map_machine(|machine| {
6363
let mut cm = K::CallManager::new(machine, msg.gas_limit, msg.from, msg.sequence);
64-
// This error is fatal because it should have already been acounted for inside
64+
// This error is fatal because it should have already been accounted for inside
6565
// preflight_message.
6666
if let Err(e) = cm.charge_gas(inclusion_cost) {
67-
return (Err(e), cm.finish().2);
67+
return (Err(e), cm.finish().1);
6868
}
6969

7070
let result = cm.with_transaction(|cm| {
@@ -79,8 +79,11 @@ where
7979

8080
Ok(ret)
8181
});
82-
let (gas_used, backtrace, machine) = cm.finish();
83-
(Ok((result, gas_used, backtrace)), machine)
82+
let (res, machine) = cm.finish();
83+
(
84+
Ok((result, res.gas_used, res.backtrace, res.exec_trace)),
85+
machine,
86+
)
8487
})?;
8588

8689
// Extract the exit code and build the result of the message application.
@@ -142,7 +145,7 @@ where
142145
msg.sequence,
143146
msg.method_num,
144147
self.context().epoch
145-
)))
148+
)));
146149
}
147150
};
148151

@@ -153,12 +156,18 @@ where
153156
};
154157

155158
match apply_kind {
156-
ApplyKind::Explicit => self.finish_message(msg, receipt, failure_info, gas_cost),
159+
ApplyKind::Explicit => self
160+
.finish_message(msg, receipt, failure_info, gas_cost)
161+
.map(|mut apply_ret| {
162+
apply_ret.exec_trace = exec_trace;
163+
apply_ret
164+
}),
157165
ApplyKind::Implicit => Ok(ApplyRet {
158166
msg_receipt: receipt,
159167
failure_info,
160168
penalty: TokenAmount::zero(),
161169
miner_tip: TokenAmount::zero(),
170+
exec_trace,
162171
}),
163172
}
164173
}
@@ -234,7 +243,7 @@ where
234243
ExitCode::SYS_SENDER_INVALID,
235244
"Sender invalid",
236245
miner_penalty_amount,
237-
)))
246+
)));
238247
}
239248
};
240249

@@ -253,7 +262,7 @@ where
253262
ExitCode::SYS_SENDER_INVALID,
254263
"Sender invalid",
255264
miner_penalty_amount,
256-
)))
265+
)));
257266
}
258267
};
259268

@@ -365,6 +374,7 @@ where
365374
failure_info,
366375
penalty: miner_penalty,
367376
miner_tip,
377+
exec_trace: vec![],
368378
})
369379
}
370380

fvm/src/executor/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use fvm_shared::receipt::Receipt;
1111
use num_traits::Zero;
1212

1313
use crate::call_manager::Backtrace;
14+
use crate::trace::ExecutionTrace;
1415
use crate::Kernel;
1516

1617
/// An executor executes messages on the underlying machine/kernel. It's responsible for:
@@ -71,6 +72,8 @@ pub struct ApplyRet {
7172
pub miner_tip: BigInt,
7273
/// Additional failure information for debugging, if any.
7374
pub failure_info: Option<ApplyFailure>,
75+
/// Execution trace information, for debugging.
76+
pub exec_trace: ExecutionTrace,
7477
}
7578

7679
impl ApplyRet {
@@ -89,6 +92,7 @@ impl ApplyRet {
8992
penalty: miner_penalty,
9093
failure_info: Some(ApplyFailure::PreValidation(message.into())),
9194
miner_tip: BigInt::zero(),
95+
exec_trace: vec![],
9296
}
9397
}
9498

fvm/src/kernel/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ macro_rules! syscall_error {
2626
};
2727
}
2828

29-
// NOTE: this intentionally does not implemnent error so we can make the context impl work out
29+
// NOTE: this intentionally does not implement error so we can make the context impl work out
3030
// below.
3131
#[derive(Display, Debug)]
3232
pub enum ExecutionError {

fvm/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ mod power_actor;
3030
mod reward_actor;
3131
mod system_actor;
3232

33+
pub mod trace;
34+
3335
use cid::multihash::{Code, MultihashDigest};
3436
use cid::Cid;
3537
use fvm_ipld_encoding::{to_vec, DAG_CBOR};

fvm/src/machine/mod.rs

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,8 @@ impl NetworkConfig {
129129

130130
/// Enable actor debugging. This is a consensus-critical option (affects gas usage) so it should
131131
/// only be enabled for local testing or as a network-wide parameter.
132-
pub fn enable_actor_debugging(&mut self, enabled: bool) -> &mut Self {
133-
self.actor_debugging = enabled;
132+
pub fn enable_actor_debugging(&mut self) -> &mut Self {
133+
self.actor_debugging = true;
134134
self
135135
}
136136

@@ -149,6 +149,7 @@ impl NetworkConfig {
149149
initial_state_root: initial_state,
150150
base_fee: TokenAmount::zero(),
151151
circ_supply: fvm_shared::TOTAL_FILECOIN.clone(),
152+
tracing: false,
152153
}
153154
}
154155
}
@@ -178,18 +179,28 @@ pub struct MachineContext {
178179
///
179180
/// DEFAULT: Total FIL supply (likely not what you want).
180181
pub circ_supply: TokenAmount,
182+
183+
/// Whether or not to produce execution traces in the returned result.
184+
/// Not consensus-critical, but has a performance impact.
185+
pub tracing: bool,
181186
}
182187

183188
impl MachineContext {
184-
/// Sets [`EpochContext::base_fee`].
189+
/// Sets [`MachineContext::base_fee`].
185190
pub fn set_base_fee(&mut self, amt: TokenAmount) -> &mut Self {
186191
self.base_fee = amt;
187192
self
188193
}
189194

190-
/// Set [`EpochContext::circ_supply`].
195+
/// Set [`MachineContext::circ_supply`].
191196
pub fn set_circulating_supply(&mut self, amt: TokenAmount) -> &mut Self {
192197
self.circ_supply = amt;
193198
self
194199
}
200+
201+
/// Enable execution traces. [`MachineContext::tracing`].
202+
pub fn enable_tracing(&mut self) -> &mut Self {
203+
self.tracing = true;
204+
self
205+
}
195206
}

fvm/src/trace/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use fvm_ipld_encoding::RawBytes;
2+
use fvm_shared::address::Address;
3+
use fvm_shared::econ::TokenAmount;
4+
use fvm_shared::{ActorID, MethodNum};
5+
6+
use crate::call_manager::InvocationResult;
7+
use crate::kernel::SyscallError;
8+
9+
/// Execution Trace, only for informational and debugging purposes.
10+
pub type ExecutionTrace = Vec<ExecutionEvent>;
11+
12+
#[derive(Clone, Debug)]
13+
pub enum ExecutionEvent {
14+
Call(SendParams),
15+
Return(Result<InvocationResult, SyscallError>),
16+
}
17+
18+
#[derive(Clone, Debug)]
19+
pub struct SendParams {
20+
pub from: ActorID,
21+
pub to: Address,
22+
pub method: MethodNum,
23+
pub params: RawBytes,
24+
pub value: TokenAmount,
25+
}

testing/conformance/src/vm.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::convert::TryFrom;
33

44
use cid::Cid;
55
use futures::executor::block_on;
6-
use fvm::call_manager::{Backtrace, CallManager, DefaultCallManager, InvocationResult};
6+
use fvm::call_manager::{CallManager, DefaultCallManager, FinishRet, InvocationResult};
77
use fvm::gas::{GasTracker, PriceList};
88
use fvm::kernel::*;
99
use fvm::machine::{DefaultMachine, Engine, Machine, MachineContext, NetworkConfig};
@@ -211,7 +211,7 @@ where
211211
})
212212
}
213213

214-
fn finish(self) -> (i64, Backtrace, Self::Machine) {
214+
fn finish(self) -> (FinishRet, Self::Machine) {
215215
self.0.finish()
216216
}
217217

0 commit comments

Comments
 (0)