Skip to content

Commit

Permalink
Use SignedSignature and improve debug trace
Browse files Browse the repository at this point in the history
  • Loading branch information
agostbiro committed Sep 19, 2023
1 parent 90d7b1b commit f072615
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 309 deletions.
94 changes: 93 additions & 1 deletion crates/rethnet_eth/src/transaction/signed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ mod eip2930;
mod legacy;

use bytes::Bytes;
use revm_primitives::{Address, B256, U256};
use revm_primitives::{Address, CreateScheme, TransactTo, TxEnv, B256, U256};

use crate::{
access_list::AccessList,
Expand Down Expand Up @@ -225,6 +225,98 @@ impl rlp::Decodable for SignedTransaction {
}
}

impl TryFrom<SignedTransaction> for TxEnv {
type Error = SignatureError;

fn try_from(transaction: SignedTransaction) -> Result<Self, Self::Error> {
fn transact_to(kind: TransactionKind) -> TransactTo {
match kind {
TransactionKind::Call(address) => TransactTo::Call(address),
TransactionKind::Create => TransactTo::Create(CreateScheme::Create),
}
}

let caller = transaction.recover()?;
let chain_id = transaction.chain_id();

let result = match transaction {
SignedTransaction::PreEip155Legacy(LegacySignedTransaction {
nonce,
gas_price,
gas_limit,
kind,
value,
input,
..
})
| SignedTransaction::PostEip155Legacy(EIP155SignedTransaction {
nonce,
gas_price,
gas_limit,
kind,
value,
input,
..
}) => Self {
caller,
gas_limit,
gas_price,
gas_priority_fee: None,
transact_to: transact_to(kind),
value,
data: input,
chain_id,
nonce: Some(nonce),
access_list: Vec::new(),
},
SignedTransaction::Eip2930(EIP2930SignedTransaction {
nonce,
gas_price,
gas_limit,
kind,
value,
input,
access_list,
..
}) => Self {
caller,
gas_limit,
gas_price,
gas_priority_fee: None,
transact_to: transact_to(kind),
value,
data: input,
chain_id,
nonce: Some(nonce),
access_list: access_list.into(),
},
SignedTransaction::Eip1559(EIP1559SignedTransaction {
nonce,
max_priority_fee_per_gas,
max_fee_per_gas,
gas_limit,
kind,
value,
input,
access_list,
..
}) => Self {
caller,
gas_limit,
gas_price: max_fee_per_gas,
gas_priority_fee: Some(max_priority_fee_per_gas),
transact_to: transact_to(kind),
value,
data: input,
chain_id,
nonce: Some(nonce),
access_list: access_list.into(),
},
};
Ok(result)
}
}

#[cfg(test)]
mod tests {
use bytes::Bytes;
Expand Down
110 changes: 61 additions & 49 deletions crates/rethnet_evm/src/debug_trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use crate::blockchain::SyncBlockchain;
use crate::evm::build_evm;
use crate::state::SyncState;
use crate::TransactionError;
use rethnet_eth::transaction::TransactionRequest;
use rethnet_eth::signature::SignatureError;
use rethnet_eth::transaction::SignedTransaction;
use rethnet_eth::B256;
use revm::inspectors::GasInspector;
use revm::interpreter::{
Expand All @@ -11,16 +12,20 @@ use revm::interpreter::{
use revm::primitives::{hex, B160, U256};
use revm::primitives::{BlockEnv, Bytes, CfgEnv, ExecutionResult, ResultAndState, SpecId};
use revm::{EVMData, Inspector};
use std::collections::HashMap;
use std::fmt::Debug;

/// Get trace output for `debug_traceTransaction`
#[cfg_attr(feature = "tracing", tracing::instrument)]
pub fn debug_trace_transaction<BlockchainErrorT, StateErrorT>(
blockchain: &dyn SyncBlockchain<BlockchainErrorT, StateErrorT>,
// Take ownership of the state so that we can apply throw-away modifications on it
// TODO depends on https://github.com/NomicFoundation/hardhat/pull/4254
// mut state: Box<dyn SyncState<StateErrorT>>,
state: &mut dyn SyncState<StateErrorT>,
cfg: CfgEnv,
block_env: BlockEnv,
transactions: Vec<TransactionRequest>,
transactions: Vec<SignedTransaction>,
transaction_hash: B256,
) -> Result<DebugTraceResult, DebugTraceError<BlockchainErrorT, StateErrorT>>
where
Expand All @@ -31,23 +36,23 @@ where
// Matching Hardhat Network behaviour: https://github.com/NomicFoundation/hardhat/blob/af7e4ce6a18601ec9cd6d4aa335fa7e24450e638/packages/hardhat-core/src/internal/hardhat-network/provider/vm/ethereumjs.ts#L427
return Err(DebugTraceError::InvalidSpecId {
spec_id: cfg.spec_id,
message: "`debug_traceTransaction` is not supported prior to Spurious Dragon"
.to_string(),
});
}

if cfg.spec_id > SpecId::MERGE && block_env.prevrandao.is_none() {
return Err(TransactionError::MissingPrevrandao.into());
}

// Clone the state so that we can apply throw-away modifications on it
// TODO figure out why this doesn't work
// let mut state = state.clone();

for tx in transactions {
let tx_hash = tx.hash();

let evm = build_evm(blockchain, state, cfg.clone(), tx.into(), block_env.clone());
let evm = build_evm(
blockchain,
state,
cfg.clone(),
tx.try_into()?,
block_env.clone(),
);

if tx_hash == transaction_hash {
let mut tracer = TracerEip3155::new();
Expand All @@ -61,19 +66,19 @@ where
ExecutionResult::Success {
gas_used, output, ..
} => DebugTraceResult {
failed: false,
pass: true,
gas_used,
output: Some(output.into_data()),
logs: tracer.logs,
},
ExecutionResult::Revert { gas_used, output } => DebugTraceResult {
failed: true,
pass: false,
gas_used,
output: Some(output),
logs: tracer.logs,
},
ExecutionResult::Halt { gas_used, .. } => DebugTraceResult {
failed: true,
pass: false,
gas_used,
output: None,
logs: tracer.logs,
Expand All @@ -97,26 +102,34 @@ where
#[derive(Debug, thiserror::Error)]
pub enum DebugTraceError<BlockchainErrorT, StateErrorT> {
/// Invalid hardfork spec argument.
#[error("Invalid spec id {spec_id:?}: {message}")]
InvalidSpecId { message: String, spec_id: SpecId },
#[error("Invalid spec id: {spec_id:?}. `debug_traceTransaction` is not supported prior to Spurious Dragon")]
InvalidSpecId { spec_id: SpecId },
/// Invalid transaction hash argument.
#[error("Transaction hash {tx_hash} not found in block {block_number}")]
InvalidTransactionHash { tx_hash: B256, block_number: U256 },
#[error(transparent)]
SignatureError(#[from] SignatureError),
#[error(transparent)]
TransactionError(#[from] TransactionError<BlockchainErrorT, StateErrorT>),
}

/// Result of a `debug_traceTransaction` call.
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct DebugTraceResult {
pub failed: bool,
/// Whether transaction was executed successfully.
pub pass: bool,
/// All gas used by the transaction.
pub gas_used: u64,
/// Return values of the function.
pub output: Option<Bytes>,
/// The EIP-3155 debug logs.
pub logs: Vec<DebugTraceLogItem>,
}

/// The output of an EIP-3155 trace.
/// Fields match: https://eips.ethereum.org/EIPS/eip-3155#output
/// Not all optional fields are supported.
/// The required fields match <https://eips.ethereum.org/EIPS/eip-3155#output> except for
/// `returnData` and `refund` which are not used currently by Hardhat.
/// The `opName`, `error`, `memory` and `storage` optional fields are supported as well.
#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)]
pub struct DebugTraceLogItem {
/// Program Counter
Expand All @@ -131,14 +144,16 @@ pub struct DebugTraceLogItem {
pub stack: Vec<String>,
/// Depth of the call stack
pub depth: u64,
/// Data returned by function call as hex string.
pub return_data: String,
/// Amount of global gas refunded as hex number.
pub refund: String,
/// Size of memory array
/// Size of memory array.
pub mem_size: u64,
/// Name of the operation
/// Name of the operation.
pub op_name: Option<String>,
/// Description of an error as a hex string.
pub error: Option<String>,
/// Array of all allocated values as hex strings.
pub memory: Vec<String>,
/// Map of all stored values with keys and values encoded as hex strings.
pub storage: HashMap<String, String>,
}

// Based on https://github.com/bluealloy/revm/blob/70cf969a25a45e3bb4e503926297d61a90c7eec5/crates/revm/src/inspector/tracer_eip3155.rs
Expand All @@ -152,6 +167,7 @@ struct TracerEip3155 {
pc: usize,
opcode: u8,
gas: u64,
memory: Vec<u8>,
mem_size: usize,
skip: bool,
}
Expand All @@ -165,27 +181,37 @@ impl TracerEip3155 {
pc: 0,
opcode: 0,
gas: 0,
memory: Vec::default(),
mem_size: 0,
skip: false,
}
}

fn record_log(&mut self, depth: u64) {
let short_stack: Vec<String> = self.stack.data().iter().map(|&b| short_hex(b)).collect();
// TODO the fields with todo!() are not supported by revm::TraceEip3155, but they're
// mandatory according to the EIP. We need to figure out if we can add support for them
// or if they're really needed by Hardhat.
let stack: Vec<String> = self
.stack
.data()
.iter()
.map(|b| trimmed_hex(b.to_be_bytes_vec()))
.collect();
let memory = self
.memory
.chunks(32)
.into_iter()
.map(hex::encode)
.collect();
let log_item = DebugTraceLogItem {
pc: self.pc as u64,
op: self.opcode,
gas: format!("0x{:x}", self.gas),
gas_cost: format!("0x{:x}", self.gas_inspector.last_gas_cost()),
stack: short_stack,
stack,
depth,
return_data: todo!(),
refund: todo!(),
mem_size: todo!(),
mem_size: self.mem_size as u64,
op_name: opcode::OPCODE_JUMPMAP[self.opcode as usize].map(String::from),
error: todo!(),
memory,
storage: todo!(),
};
self.logs.push(log_item);
}
Expand All @@ -212,6 +238,7 @@ impl<DatabaseErrorT> Inspector<DatabaseErrorT> for TracerEip3155 {
self.stack = interp.stack.clone();
self.pc = interp.program_counter();
self.opcode = interp.current_opcode();
self.memory = interp.memory.data().clone();
self.mem_size = interp.memory.len();
self.gas = self.gas_inspector.gas_remaining();
//
Expand Down Expand Up @@ -254,19 +281,6 @@ impl<DatabaseErrorT> Inspector<DatabaseErrorT> for TracerEip3155 {
self.gas_inspector
.call_end(data, inputs, remaining_gas, ret, out.clone());
self.skip = true;
if data.journaled_state().depth() == 0 {
todo!()
// let log_line = json!({
// //stateroot
// "output": format!("0x{}", hex::encode(out.as_ref())),
// "gasUsed": format!("0x{:x}", self.gas_inspector.gas_remaining()),
// //time
// //fork
// });
//
// writeln!(self.output, "{}", serde_json::to_string(&log_line).unwrap())
// .expect("If output fails we can ignore the logging");
}
(ret, remaining_gas, out)
}

Expand Down Expand Up @@ -300,12 +314,10 @@ impl<DatabaseErrorT> Inspector<DatabaseErrorT> for TracerEip3155 {
}
}

// From https://github.com/bluealloy/revm/blob/70cf969a25a45e3bb4e503926297d61a90c7eec5/crates/revm/src/inspector/tracer_eip3155.rs
// Based on https://github.com/bluealloy/revm/blob/70cf969a25a45e3bb4e503926297d61a90c7eec5/crates/revm/src/inspector/tracer_eip3155.rs
// Original licensed under the MIT license.
fn short_hex(b: U256) -> String {
let s = hex::encode(b.to_be_bytes_vec())
.trim_start_matches('0')
.to_string();
fn trimmed_hex(bytes: impl AsRef<[u8]>) -> String {
let s = hex::encode(bytes).trim_start_matches('0').to_string();
if s.is_empty() {
"0x0".to_string()
} else {
Expand Down
10 changes: 5 additions & 5 deletions crates/rethnet_evm_napi/src/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ impl Block {
#[napi(getter)]
pub fn transactions(
&self,
env: Env,
_env: Env,
) -> napi::Result<
// HACK: napi does not convert Rust type aliases to its underlaying types when generating bindings
// so manually do that here
Expand All @@ -312,16 +312,16 @@ impl Block {
.iter()
.map(|transaction| match transaction {
rethnet_eth::transaction::SignedTransaction::PreEip155Legacy(transaction) => {
LegacySignedTransaction::from_legacy(&env, transaction).map(Either3::A)
LegacySignedTransaction::from_legacy(transaction).map(Either3::A)
}
rethnet_eth::transaction::SignedTransaction::PostEip155Legacy(transaction) => {
LegacySignedTransaction::from_eip155(&env, transaction).map(Either3::A)
LegacySignedTransaction::from_eip155(transaction).map(Either3::A)
}
rethnet_eth::transaction::SignedTransaction::Eip2930(transaction) => {
EIP2930SignedTransaction::new(&env, transaction).map(Either3::B)
EIP2930SignedTransaction::new(transaction).map(Either3::B)
}
rethnet_eth::transaction::SignedTransaction::Eip1559(transaction) => {
EIP1559SignedTransaction::new(&env, transaction).map(Either3::C)
EIP1559SignedTransaction::new(transaction).map(Either3::C)
}
})
.collect()
Expand Down
Loading

0 comments on commit f072615

Please sign in to comment.