diff --git a/Cargo.toml b/Cargo.toml index e888652d9..eff67b880 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ primitive-types = { version = "0.9", default-features = false, features = ["rlp" serde = { version = "1.0", default-features = false, features = ["derive"], optional = true } codec = { package = "parity-scale-codec", version = "2.0", default-features = false, features = ["derive"], optional = true } ethereum = { version = "0.7", default-features = false } +environmental = { version = "1.1.2", default-features = false, optional = true } [dev-dependencies] criterion = "0.3" @@ -32,7 +33,10 @@ harness = false default = ["std"] with-codec = ["codec", "evm-core/with-codec", "primitive-types/codec", "ethereum/with-codec"] with-serde = ["serde", "evm-core/with-serde", "primitive-types/serde", "ethereum/with-serde"] -std = ["evm-core/std", "evm-gasometer/std", "evm-runtime/std", "sha3/std", "primitive-types/std", "serde/std", "codec/std", "log/std", "ethereum/std"] +std = ["evm-core/std", "evm-gasometer/std", "evm-runtime/std", "sha3/std", "primitive-types/std", "serde/std", "codec/std", "log/std", "ethereum/std", "environmental/std"] +tracing = [ + "environmental" +] [workspace] members = [ diff --git a/gasometer/Cargo.toml b/gasometer/Cargo.toml index 1cb0e0b11..0a5c015f3 100644 --- a/gasometer/Cargo.toml +++ b/gasometer/Cargo.toml @@ -12,6 +12,7 @@ edition = "2018" primitive-types = { version = "0.9", default-features = false } evm-core = { version = "0.26", path = "../core", default-features = false } evm-runtime = { version = "0.26", path = "../runtime", default-features = false } +environmental = { version = "1.1.2", default-features = false, optional = true } [features] default = ["std"] @@ -19,4 +20,8 @@ std = [ "evm-core/std", "evm-runtime/std", "primitive-types/std", + "environmental/std" +] +tracing = [ + "environmental" ] diff --git a/gasometer/src/lib.rs b/gasometer/src/lib.rs index 0d09828ae..790852055 100644 --- a/gasometer/src/lib.rs +++ b/gasometer/src/lib.rs @@ -1,10 +1,26 @@ //! EVM gasometer. #![deny(warnings)] -#![forbid(unsafe_code, unused_variables, unused_imports)] +#![forbid(unsafe_code, unused_variables)] #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "tracing")] +pub mod tracing; + +#[cfg(feature = "tracing")] +macro_rules! event { + ($x:expr) => { + use crate::tracing::Event::*; + $x.emit(); + } +} + +#[cfg(not(feature = "tracing"))] +macro_rules! event { + ($x:expr) => { } +} + mod consts; mod costs; mod memory; @@ -27,6 +43,14 @@ macro_rules! try_or_fail { ) } +#[derive(Debug, Copy, Clone)] +pub struct Snapshot { + pub gas_limit: u64, + pub memory_gas: u64, + pub used_gas: u64, + pub refunded_gas: i64, +} + /// EVM gasometer. #[derive(Clone)] pub struct Gasometer<'config> { @@ -115,6 +139,11 @@ impl<'config> Gasometer<'config> { &mut self, cost: u64, ) -> Result<(), ExitError> { + event!(RecordCost { + cost, + snapshot: self.snapshot()?, + }); + let all_gas_cost = self.total_used_gas() + cost; if self.gas_limit < all_gas_cost { self.inner = Err(ExitError::OutOfGas); @@ -131,6 +160,11 @@ impl<'config> Gasometer<'config> { &mut self, refund: i64, ) -> Result<(), ExitError> { + event!(RecordRefund { + refund, + snapshot: self.snapshot()?, + }); + self.inner_mut()?.refunded_gas += refund; Ok(()) } @@ -161,6 +195,13 @@ impl<'config> Gasometer<'config> { let gas_refund = self.inner_mut()?.gas_refund(cost); let used_gas = self.inner_mut()?.used_gas; + event!(RecordDynamicCost { + gas_cost, + memory_gas, + gas_refund, + snapshot: self.snapshot()?, + }); + let all_gas_cost = memory_gas + used_gas + gas_cost; if self.gas_limit < all_gas_cost { self.inner = Err(ExitError::OutOfGas); @@ -183,6 +224,11 @@ impl<'config> Gasometer<'config> { &mut self, stipend: u64, ) -> Result<(), ExitError> { + event!(RecordStipend { + stipend, + snapshot: self.snapshot()?, + }); + self.inner_mut()?.used_gas -= stipend; Ok(()) } @@ -205,6 +251,11 @@ impl<'config> Gasometer<'config> { }, }; + event!(RecordTransaction { + cost: gas_cost, + snapshot: self.snapshot()?, + }); + if self.gas() < gas_cost { self.inner = Err(ExitError::OutOfGas); return Err(ExitError::OutOfGas); @@ -213,6 +264,16 @@ impl<'config> Gasometer<'config> { self.inner_mut()?.used_gas += gas_cost; Ok(()) } + + pub fn snapshot(&self) -> Result { + let inner = self.inner.as_ref().map_err(|e| e.clone())?; + Ok(Snapshot { + gas_limit: self.gas_limit, + memory_gas: inner.memory_gas, + used_gas: inner.used_gas, + refunded_gas: inner.refunded_gas, + }) + } } /// Calculate the call transaction cost. diff --git a/gasometer/src/tracing.rs b/gasometer/src/tracing.rs new file mode 100644 index 000000000..c8901ccd8 --- /dev/null +++ b/gasometer/src/tracing.rs @@ -0,0 +1,58 @@ +//! Allows to listen to gasometer events. + +use super::Snapshot; + +environmental::environmental!(listener: dyn EventListener + 'static); + +pub trait EventListener { + fn event( + &mut self, + event: Event + ); +} + +impl Snapshot { + pub fn gas(&self) -> u64 { + self.gas_limit - self.used_gas - self.memory_gas + } +} + +#[derive(Debug, Copy, Clone)] +pub enum Event { + RecordCost { + cost: u64, + snapshot: Snapshot, + }, + RecordRefund { + refund: i64, + snapshot: Snapshot, + }, + RecordStipend { + stipend: u64, + snapshot: Snapshot, + }, + RecordDynamicCost { + gas_cost: u64, + memory_gas: u64, + gas_refund: i64, + snapshot: Snapshot, + }, + RecordTransaction { + cost: u64, + snapshot: Snapshot, + }, +} + +impl Event { + pub(crate) fn emit(self) { + listener::with(|listener| listener.event(self)); + } +} + +/// Run closure with provided listener. +pub fn using R>( + new: &mut (dyn EventListener + 'static), + f: F +) -> R { + listener::using(new, f) +} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 8f0668130..dbe7f5097 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -12,7 +12,11 @@ edition = "2018" evm-core = { version = "0.26", path = "../core", default-features = false } primitive-types = { version = "0.9", default-features = false } sha3 = { version = "0.8", default-features = false } +environmental = { version = "1.1.2", default-features = false, optional = true} [features] default = ["std"] -std = ["evm-core/std", "primitive-types/std", "sha3/std"] +std = ["evm-core/std", "primitive-types/std", "sha3/std", "environmental/std"] +tracing = [ + "environmental" +] \ No newline at end of file diff --git a/runtime/src/eval/system.rs b/runtime/src/eval/system.rs index 7d3321063..ef5b5f7d4 100644 --- a/runtime/src/eval/system.rs +++ b/runtime/src/eval/system.rs @@ -171,13 +171,27 @@ pub fn gaslimit(runtime: &mut Runtime, handler: &H) -> Control { pub fn sload(runtime: &mut Runtime, handler: &H) -> Control { pop!(runtime, index); - push!(runtime, handler.storage(runtime.context.address, index)); + let value = handler.storage(runtime.context.address, index); + push!(runtime, value); + + event!(SLoad { + address: runtime.context.address, + index, + value + }); Control::Continue } pub fn sstore(runtime: &mut Runtime, handler: &mut H) -> Control { pop!(runtime, index, value); + + event!(SStore { + address: runtime.context.address, + index, + value + }); + match handler.set_storage(runtime.context.address, index, value) { Ok(()) => Control::Continue, Err(e) => Control::Exit(e.into()), diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 35b391769..09e355f29 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1,12 +1,28 @@ //! Runtime layer for EVM. #![deny(warnings)] -#![forbid(unsafe_code, unused_variables, unused_imports)] +#![forbid(unsafe_code, unused_variables)] #![cfg_attr(not(feature = "std"), no_std)] extern crate alloc; +#[cfg(feature = "tracing")] +pub mod tracing; + +#[cfg(feature = "tracing")] +macro_rules! event { + ($x:expr) => { + use crate::tracing::Event::*; + $x.emit(); + } +} + +#[cfg(not(feature = "tracing"))] +macro_rules! event { + ($x:expr) => { } +} + mod eval; mod context; mod interrupt; @@ -24,6 +40,14 @@ use alloc::rc::Rc; macro_rules! step { ( $self:expr, $handler:expr, $return:tt $($err:path)?; $($ok:path)? ) => ({ if let Some((opcode, stack)) = $self.machine.inspect() { + event!(Step { + context: &$self.context, + opcode, + position: $self.machine.position(), + stack, + memory: $self.machine.memory() + }); + match $handler.pre_validate(&$self.context, opcode, stack) { Ok(()) => (), Err(e) => { @@ -41,7 +65,14 @@ macro_rules! step { }, } - match $self.machine.step() { + let result = $self.machine.step(); + + event!(StepResult { + result: &result, + return_value: &$self.machine.return_value(), + }); + + match result { Ok(()) => $($ok)?(()), Err(Capture::Exit(e)) => { $self.status = Err(e.clone()); diff --git a/runtime/src/tracing.rs b/runtime/src/tracing.rs new file mode 100644 index 000000000..14252ece3 --- /dev/null +++ b/runtime/src/tracing.rs @@ -0,0 +1,52 @@ +//! Allows to listen to runtime events. + +use crate::{Context, Opcode, Stack, Memory, Capture, ExitReason, Trap}; +use primitive_types::{H160, H256}; + +environmental::environmental!(listener: dyn EventListener + 'static); + +pub trait EventListener { + fn event( + &mut self, + event: Event + ); +} + +#[derive(Debug, Copy, Clone)] +pub enum Event<'a> { + Step { + context: &'a Context, + opcode: Opcode, + position: &'a Result, + stack: &'a Stack, + memory: &'a Memory + }, + StepResult { + result: &'a Result<(), Capture>, + return_value: &'a [u8], + }, + SLoad { + address: H160, + index: H256, + value: H256 + }, + SStore { + address: H160, + index: H256, + value: H256 + }, +} + +impl<'a> Event<'a> { + pub(crate) fn emit(self) { + listener::with(|listener| listener.event(self)); + } +} + +/// Run closure with provided listener. +pub fn using R>( + new: &mut (dyn EventListener + 'static), + f: F +) -> R { + listener::using(new, f) +} diff --git a/src/executor/stack/mod.rs b/src/executor/stack/mod.rs index 3f824215f..280237429 100644 --- a/src/executor/stack/mod.rs +++ b/src/executor/stack/mod.rs @@ -336,6 +336,17 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { gas - gas / 64 } + let address = self.create_address(scheme); + + event!(Create { + caller, + address, + scheme, + value, + init_code: &init_code, + target_gas + }); + if let Some(depth) = self.state.metadata().depth { if depth > self.config.call_stack_limit { return Capture::Exit((ExitError::CallTooDeep.into(), None, Vec::new())) @@ -366,7 +377,6 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { self.state.metadata_mut().gasometer.record_cost(gas_limit) ); - let address = self.create_address(scheme); self.state.inc_nonce(caller); self.enter_substate(gas_limit, false); @@ -483,6 +493,15 @@ impl<'config, S: StackState<'config>> StackExecutor<'config, S> { gas - gas / 64 } + event!(Call { + code_address, + transfer: &transfer, + input: &input, + target_gas, + is_static, + context: &context, + }); + let after_gas = if take_l64 && self.config.call_l64_after_gas { if self.config.estimate { let initial_after_gas = self.state.metadata().gasometer.gas(); @@ -659,9 +678,15 @@ impl<'config, S: StackState<'config>> Handler for StackExecutor<'config, S> { fn mark_delete(&mut self, address: H160, target: H160) -> Result<(), ExitError> { let balance = self.balance(address); + event!(Suicide { + target, + address, + balance, + }); + self.state.transfer(Transfer { source: address, - target: target, + target, value: balance, })?; self.state.reset_balance(address); diff --git a/src/lib.rs b/src/lib.rs index 612d4b845..395040a08 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ //! Ethereum Virtual Machine implementation in Rust #![deny(warnings)] -#![forbid(unsafe_code, unused_variables, unused_imports)] +#![forbid(unsafe_code, unused_variables)] #![cfg_attr(not(feature = "std"), no_std)] @@ -11,5 +11,21 @@ pub use evm_core::*; pub use evm_runtime::*; pub use evm_gasometer as gasometer; +#[cfg(feature = "tracing")] +pub mod tracing; + +#[cfg(feature = "tracing")] +macro_rules! event { + ($x:expr) => { + use crate::tracing::Event::*; + $x.emit(); + } +} + +#[cfg(not(feature = "tracing"))] +macro_rules! event { + ($x:expr) => { } +} + pub mod executor; pub mod backend; diff --git a/src/tracing.rs b/src/tracing.rs new file mode 100644 index 000000000..039fce91c --- /dev/null +++ b/src/tracing.rs @@ -0,0 +1,53 @@ +//! Allows to listen to runtime events. + +use crate::Context; +use evm_runtime::{CreateScheme, Transfer}; +use primitive_types::{H160, U256}; + +environmental::environmental!(listener: dyn EventListener + 'static); + +pub trait EventListener { + fn event( + &mut self, + event: Event + ); +} + +#[derive(Debug, Copy, Clone)] +pub enum Event<'a> { + Call { + code_address: H160, + transfer: &'a Option, + input: &'a [u8], + target_gas: Option, + is_static: bool, + context: &'a Context, + }, + Create { + caller: H160, + address: H160, + scheme: CreateScheme, + value: U256, + init_code: &'a [u8], + target_gas: Option, + }, + Suicide { + address: H160, + target: H160, + balance: U256, + }, +} + +impl<'a> Event<'a> { + pub(crate) fn emit(self) { + listener::with(|listener| listener.event(self)); + } +} + +/// Run closure with provided listener. +pub fn using R>( + new: &mut (dyn EventListener + 'static), + f: F +) -> R { + listener::using(new, f) +}