Skip to content

Hook into EVM execution using tracing-like events #33

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

Merged
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 = [
Expand Down
5 changes: 5 additions & 0 deletions gasometer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ 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"]
std = [
"evm-core/std",
"evm-runtime/std",
"primitive-types/std",
"environmental/std"
]
tracing = [
"environmental"
]
63 changes: 62 additions & 1 deletion gasometer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,26 @@
//! EVM gasometer.

#![deny(warnings)]
#![forbid(unsafe_code, unused_variables, unused_imports)]
Copy link
Member

Choose a reason for hiding this comment

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

Can you add back unused_improts warning here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It prevented to use environmental macro :/ Tried to allow it locally for the macro call but Rust complained.

#![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;
Expand All @@ -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> {
Expand Down Expand Up @@ -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);
Expand All @@ -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(())
}
Expand Down Expand Up @@ -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);
Expand All @@ -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(())
}
Expand All @@ -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);
Expand All @@ -213,6 +264,16 @@ impl<'config> Gasometer<'config> {
self.inner_mut()?.used_gas += gas_cost;
Ok(())
}

pub fn snapshot(&self) -> Result<Snapshot, ExitError> {
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.
Expand Down
58 changes: 58 additions & 0 deletions gasometer/src/tracing.rs
Original file line number Diff line number Diff line change
@@ -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, F: FnOnce() -> R>(
new: &mut (dyn EventListener + 'static),
f: F
) -> R {
listener::using(new, f)
}
6 changes: 5 additions & 1 deletion runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
16 changes: 15 additions & 1 deletion runtime/src/eval/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,13 +171,27 @@ pub fn gaslimit<H: Handler>(runtime: &mut Runtime, handler: &H) -> Control<H> {

pub fn sload<H: Handler>(runtime: &mut Runtime, handler: &H) -> Control<H> {
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<H: Handler>(runtime: &mut Runtime, handler: &mut H) -> Control<H> {
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()),
Expand Down
35 changes: 33 additions & 2 deletions runtime/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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) => {
Expand All @@ -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());
Expand Down
52 changes: 52 additions & 0 deletions runtime/src/tracing.rs
Original file line number Diff line number Diff line change
@@ -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<usize, ExitReason>,
stack: &'a Stack,
memory: &'a Memory
},
StepResult {
result: &'a Result<(), Capture<ExitReason, Trap>>,
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, F: FnOnce() -> R>(
new: &mut (dyn EventListener + 'static),
f: F
) -> R {
listener::using(new, f)
}
Loading