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

Feat(standalone): EVM tracing via SputnikVM #383

Merged
merged 1 commit into from
Dec 6, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ members = [
"engine-sdk",
"engine-standalone",
"engine-standalone-storage",
"engine-standalone-tracing",
"engine-tests",
"engine-types",
]
Expand Down
29 changes: 29 additions & 0 deletions engine-standalone-tracing/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
[package]
name = "engine-standalone-tracing"
version = "0.1.0"
edition = "2018"
authors = ["Aurora <[email protected]>"]
description = "Aurora engine standalone tracing library. Provides functions and types for extracing geth-like traces from standalone engine execution."
homepage = "https://github.com/aurora-is-near/aurora-engine"
repository = "https://github.com/aurora-is-near/aurora-engine"
license = "CC0-1.0"
publish = false
autobenches = false

[lib]
crate-type = ["lib"]

[dependencies]
aurora-engine = { path = "../engine", default-features = false, features = ["std"] }
aurora-engine-types = { path = "../engine-types", default-features = false, features = ["std"] }
aurora-engine-sdk = { path = "../engine-sdk", default-features = false, features = ["std"] }
evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std"] }
evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std", "tracing"] }
evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std", "tracing"] }
evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std", "tracing"] }

[features]
default = []
mainnet = []
testnet = []
betanet = []
7 changes: 7 additions & 0 deletions engine-standalone-tracing/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
pub mod sputnik;
pub mod types;

pub use types::{
Depth, LogMemory, LogStack, LogStorage, LogStorageKey, LogStorageValue, Logs, ProgramCounter,
StepTransactionTrace, TraceLog, TransactionTrace,
};
79 changes: 79 additions & 0 deletions engine-standalone-tracing/src/sputnik.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use std::cell::RefCell;
use std::ptr::NonNull;
use std::rc::Rc;

// TODO: implement evm* tracing traits for `crate::trace::Logs`, and tie this `traced_call` function to the FFI.
birchmd marked this conversation as resolved.
Show resolved Hide resolved
#[allow(dead_code)]
/// Capture all events from SputnikVM emitted from within the given closure using the given listener.
pub fn traced_call<T, R, F>(listener: &mut T, f: F) -> R
where
T: evm_gasometer::tracing::EventListener
+ evm_runtime::tracing::EventListener
+ evm::tracing::EventListener
+ 'static,
F: FnOnce() -> R,
{
let mut gas_listener = SharedMutableReference::new(listener);
let mut runtime_listener = gas_listener.clone();
let mut evm_listener = gas_listener.clone();

evm_gasometer::tracing::using(&mut gas_listener, || {
evm_runtime::tracing::using(&mut runtime_listener, || {
evm::tracing::using(&mut evm_listener, f)
})
})
}

/// This structure is intentionally private to this module as it is memory unsafe (contains a raw pointer).
/// Its purpose here is to allow a single event handling object to be used as the listener for
/// all SputnikVM events. It is needed because the listener must be passed as an object with a `'static`
/// lifetime, hence a normal reference cannot be used and we resort to raw pointers. The usage of this
/// struct in this module is safe because the `SharedMutableReference` objects created do not outlive
/// the reference they are based on (see `pub fn traced_call`). Moreover, because the SputnikVM code
/// is single-threaded, we do not need to worry about race conditions.
struct SharedMutableReference<T> {
pointer: Rc<RefCell<NonNull<T>>>,
}

impl<T> SharedMutableReference<T> {
fn new(reference: &mut T) -> Self {
let ptr = NonNull::new(reference as _).unwrap();
Self {
pointer: Rc::new(RefCell::new(ptr)),
}
}

fn clone(&self) -> Self {
Self {
pointer: Rc::clone(&self.pointer),
}
}
}

impl<T: evm_gasometer::tracing::EventListener> evm_gasometer::tracing::EventListener
for SharedMutableReference<T>
{
fn event(&mut self, event: evm_gasometer::tracing::Event) {
unsafe {
self.pointer.borrow_mut().as_mut().event(event);
}
}
}

impl<T: evm_runtime::tracing::EventListener> evm_runtime::tracing::EventListener
for SharedMutableReference<T>
{
fn event(&mut self, event: evm_runtime::tracing::Event) {
unsafe {
self.pointer.borrow_mut().as_mut().event(event);
}
}
}

impl<T: evm::tracing::EventListener> evm::tracing::EventListener for SharedMutableReference<T> {
fn event(&mut self, event: evm::tracing::Event) {
unsafe {
self.pointer.borrow_mut().as_mut().event(event);
}
}
}
5 changes: 3 additions & 2 deletions engine-standalone/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ publish = false
autobenches = false

[dependencies]
aurora-engine = { path = "../engine", default-features = false, features = ["std"] }
aurora-engine = { path = "../engine", default-features = false, features = ["std", "tracing"] }
aurora-engine-types = { path = "../engine-types", default-features = false, features = ["std"] }
aurora-engine-sdk = { path = "../engine-sdk", default-features = false, features = ["std"] }
engine-standalone-storage = { path = "../engine-standalone-storage", default-features = false }
engine-standalone-tracing = { path = "../engine-standalone-tracing", default-features = false }
borsh = { version = "0.8.2" }
evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false }
evm-core = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std"] }
libc = "0.2"
rocksdb = "0.16.0"

Expand Down
2 changes: 1 addition & 1 deletion engine-standalone/src/ffi.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::trace::{TraceLog, TransactionTrace};
use engine_standalone_tracing::{TraceLog, TransactionTrace};
use libc::{c_uchar, c_uint, c_ushort, size_t, uintmax_t};
use std::ffi::CString;

Expand Down
1 change: 0 additions & 1 deletion engine-standalone/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
mod ffi;
mod trace;

fn main() {
println!("Hello, World!");
Expand Down
11 changes: 7 additions & 4 deletions engine-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,18 @@ publish = false
autobenches = false

[dependencies]
aurora-engine = { path = "../engine", default-features = false }
aurora-engine-types = { path = "../engine-types", default-features = false }
aurora-engine-sdk = { path = "../engine-sdk", default-features = false }
aurora-engine = { path = "../engine", default-features = false, features = ["std", "tracing"] }
aurora-engine-types = { path = "../engine-types", default-features = false, features = ["std"] }
aurora-engine-sdk = { path = "../engine-sdk", default-features = false, features = ["std"] }
aurora-engine-precompiles = { path = "../engine-precompiles", default-features = false }
engine-standalone-storage = { path = "../engine-standalone-storage", default-features = false }
engine-standalone-tracing = { path = "../engine-standalone-tracing", default-features = false }
borsh = { version = "0.8.2", default-features = false }
byte-slice-cast = { version = "1.0", default-features = false }
sha3 = { version = "0.9.1", default-features = false }
evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false }
evm = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std", "tracing"] }
evm-runtime = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std", "tracing"] }
evm-gasometer = { git = "https://github.com/aurora-is-near/sputnikvm.git", default-features = false, features = ["std", "tracing"] }
rlp = { version = "0.5.0", default-features = false }

[dev-dependencies]
Expand Down
1 change: 1 addition & 0 deletions engine-tests/src/test_utils/standalone/mocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use crate::test_utils;
pub mod block;
pub mod promise;
pub mod storage;
pub mod tracing;

pub fn compute_block_hash(block_height: u64) -> H256 {
aurora_engine::engine::compute_block_hash([0u8; 32], block_height, b"aurora")
Expand Down
22 changes: 22 additions & 0 deletions engine-tests/src/test_utils/standalone/mocks/tracing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Listener {
pub events: Vec<String>,
}

impl evm_gasometer::tracing::EventListener for Listener {
fn event(&mut self, event: evm_gasometer::tracing::Event) {
self.events.push(format!("{:?}", event));
}
}

impl evm_runtime::tracing::EventListener for Listener {
fn event(&mut self, event: evm_runtime::tracing::Event) {
self.events.push(format!("{:?}", event));
}
}

impl evm::tracing::EventListener for Listener {
fn event(&mut self, event: evm::tracing::Event) {
self.events.push(format!("{:?}", event));
}
}
1 change: 1 addition & 0 deletions engine-tests/src/tests/standalone/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
mod sanity;
mod storage;
mod tracing;
128 changes: 128 additions & 0 deletions engine-tests/src/tests/standalone/tracing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use aurora_engine_types::{types::Wei, Address, U256};
use engine_standalone_tracing::sputnik;

use crate::test_utils::{self, standalone};

/// Test based on expected trace of
/// https://rinkeby.etherscan.io/tx/0xfc9359e49278b7ba99f59edac0e3de49956e46e530a53c15aa71226b7aa92c6f
/// (geth example found at https://gist.github.com/karalabe/c91f95ac57f5e57f8b950ec65ecc697f).
#[test]
fn test_evm_tracing() {
let mut runner = standalone::StandaloneRunner::default();
let mut signer = test_utils::Signer::random();

// Initialize EVM
runner.init_evm();

// Deploy contract
let deploy_tx = aurora_engine::transaction::legacy::TransactionLegacy {
nonce: signer.use_nonce().into(),
gas_price: U256::zero(),
gas_limit: u64::MAX.into(),
to: None,
value: Wei::zero(),
data: hex::decode(CONTRACT_CODE).unwrap(),
};
let result = runner
.submit_transaction(&signer.secret_key, deploy_tx)
.unwrap();
let contract_address = Address::from_slice(test_utils::unwrap_success_slice(&result));

// Interact with contract (and trace execution)
let tx = aurora_engine::transaction::legacy::TransactionLegacy {
nonce: signer.use_nonce().into(),
gas_price: U256::zero(),
gas_limit: 90_000.into(),
to: Some(contract_address),
value: Wei::zero(),
data: hex::decode(CONTRACT_INPUT).unwrap(),
};
let mut listener = standalone::mocks::tracing::Listener::default();
let result = sputnik::traced_call(&mut listener, || {
runner.submit_transaction(&signer.secret_key, tx).unwrap()
});
assert!(result.status.is_ok());

// Check trace
let positions: Vec<u8> = listener
.events
.iter()
.filter_map(|e| {
if e.starts_with("Step ") {
Some(parse_numer_in_parentheses("position:", e))
} else {
None
}
})
.collect();
assert_eq!(positions.as_slice(), &EXPECTED_POSITIONS);

let costs: Vec<u32> = listener
.events
.iter()
.filter_map(|e| {
if e.starts_with("RecordCost") {
Some(parse_field("cost:", e).parse().unwrap())
} else if e.starts_with("RecordDynamicCost") {
let gas_cost: u32 = parse_field("gas_cost:", e).parse().unwrap();
let memory_gas: u32 = parse_field("memory_gas:", e).parse().unwrap();
Some(gas_cost + memory_gas)
} else {
None
}
})
.skip(1) // The first cost is for the transaction itself; not included in geth trace
.collect();
assert_eq!(costs.as_slice(), &EXPECTED_COSTS);

let op_codes: Vec<u8> = listener
.events
.iter()
.filter_map(|e| {
if e.starts_with("Step ") {
Some(parse_numer_in_parentheses("opcode:", e))
} else {
None
}
})
.collect();
assert_eq!(op_codes.as_slice(), &EXPECTED_OP_CODES);
}

fn parse_field<'a>(name: &'static str, data: &'a str) -> &'a str {
data.split(name)
.skip(1)
.next()
.unwrap()
.trim()
.split(',')
.next()
.unwrap()
}

fn parse_numer_in_parentheses(name: &'static str, data: &str) -> u8 {
let token = parse_field(name, data);
let number = token
.split('(')
.skip(1)
.next()
.unwrap()
.split(')')
.next()
.unwrap();
number.parse().unwrap()
}

const CONTRACT_CODE: &str = "60606040525b60008054600160a060020a03191633600160a060020a0316179055346001555b5b61011e806100356000396000f3006060604052361560465763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166383197ef08114604a5780638da5cb5b14605c575b5b5b005b3415605457600080fd5b60466095565b005b3415606657600080fd5b606c60d6565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b6000543373ffffffffffffffffffffffffffffffffffffffff9081169116141560d35760005473ffffffffffffffffffffffffffffffffffffffff16ff5b5b565b60005473ffffffffffffffffffffffffffffffffffffffff16815600a165627a7a7230582080eeb07bf95bf0cca20d03576cbb3a25de3bd0d1275c173d370dcc90ce23158d0029";
const CONTRACT_INPUT: &str = "2df07fbaabbe40e3244445af30759352e348ec8bebd4dd75467a9f29ec55d98d6cf6c418de0e922b1c55be39587364b88224451e7901d10a4a2ee2eeab3cccf51c";
const EXPECTED_POSITIONS: [u8; 27] = [
0, 2, 4, 5, 6, 7, 9, 10, 15, 45, 47, 48, 49, 50, 55, 56, 57, 59, 60, 61, 66, 67, 69, 70, 71,
72, 73,
];
const EXPECTED_COSTS: [u32; 27] = [
3, 3, 12, 2, 3, 3, 10, 3, 3, 3, 3, 5, 3, 3, 3, 3, 3, 10, 3, 3, 3, 3, 10, 1, 1, 1, 0,
];
const EXPECTED_OP_CODES: [u8; 27] = [
96, 96, 82, 54, 21, 96, 87, 99, 124, 96, 53, 4, 22, 99, 129, 20, 96, 87, 128, 99, 20, 96, 87,
91, 91, 91, 0,
];
1 change: 1 addition & 0 deletions engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ std = ["borsh/std", "evm/std", "primitive-types/std", "rlp/std", "sha3/std", "et
contract = ["aurora-engine-sdk/contract", "aurora-engine-precompiles/contract"]
evm_bully = []
log = ["aurora-engine-sdk/log", "aurora-engine-precompiles/log"]
tracing = ["evm/tracing"]
meta-call = []
error_refund = ["aurora-engine-precompiles/error_refund"]
integration-test = ["log"]
Expand Down