From 1483175e2fc265f0a636648904cc6c1e5d1ef10c Mon Sep 17 00:00:00 2001 From: "Supernovahs.eth" <91280922+supernovahs@users.noreply.github.com> Date: Tue, 17 Oct 2023 19:03:31 +0530 Subject: [PATCH] example simulation transportless (#5025) Co-authored-by: Matthias Seitz --- Cargo.lock | 12 +++ Cargo.toml | 2 +- crates/primitives/src/lib.rs | 2 +- crates/primitives/src/transaction/mod.rs | 15 +++ .../rpc-types-compat/src/transaction/mod.rs | 42 +++++++- crates/rpc/rpc-types/src/eth/call.rs | 10 ++ crates/transaction-pool/src/traits.rs | 6 ++ crates/transaction-pool/src/validate/mod.rs | 5 + examples/Cargo.toml | 2 - examples/trace-transaction-cli/Cargo.toml | 16 +++ examples/trace-transaction-cli/src/main.rs | 101 ++++++++++++++++++ 11 files changed, 208 insertions(+), 5 deletions(-) create mode 100644 examples/trace-transaction-cli/Cargo.toml create mode 100644 examples/trace-transaction-cli/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 6e9343c89..b092d6c1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7989,6 +7989,18 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +[[package]] +name = "trace-transaction-cli" +version = "0.0.0" +dependencies = [ + "clap", + "eyre", + "futures-util", + "jsonrpsee", + "reth", + "tokio", +] + [[package]] name = "tracing" version = "0.1.39" diff --git a/Cargo.toml b/Cargo.toml index 4db0a74ad..1100498a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,6 +51,7 @@ members = [ "examples/cli-extension-event-hooks", "examples/rpc-db", "examples/manual-p2p", + "examples/trace-transaction-cli" ] default-members = ["bin/reth"] @@ -107,7 +108,6 @@ reth-discv4 = { path = "./crates/net/discv4" } reth-eth-wire = { path = "./crates/net/eth-wire" } reth-ecies = { path = "./crates/net/ecies" } reth-tracing = { path = "./crates/tracing" } - # revm revm = "3.5.0" revm-primitives = "1.3.0" diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 538181445..b00f0974a 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -44,7 +44,7 @@ pub mod snapshot; pub mod stage; mod storage; /// Helpers for working with transactions -mod transaction; +pub mod transaction; pub mod trie; mod withdrawal; diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index 5caf6e9c8..7b08e9098 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -235,6 +235,21 @@ impl Transaction { } } + /// Blob versioned hashes for eip4844 transaction, for legacy,eip1559 and eip2930 transactions + /// this is `None` + /// + /// This is also commonly referred to as the "blob versioned hashes" (`BlobVersionedHashes`). + pub fn blob_versioned_hashes(&self) -> Option> { + match self { + Transaction::Legacy(_) => None, + Transaction::Eip2930(_) => None, + Transaction::Eip1559(_) => None, + Transaction::Eip4844(TxEip4844 { blob_versioned_hashes, .. }) => { + Some(blob_versioned_hashes.to_vec()) + } + } + } + /// Max fee per blob gas for eip4844 transaction [TxEip4844]. /// /// Returns `None` for non-eip4844 transactions. diff --git a/crates/rpc/rpc-types-compat/src/transaction/mod.rs b/crates/rpc/rpc-types-compat/src/transaction/mod.rs index e4149c7ec..9f6a436b8 100644 --- a/crates/rpc/rpc-types-compat/src/transaction/mod.rs +++ b/crates/rpc/rpc-types-compat/src/transaction/mod.rs @@ -5,7 +5,7 @@ use reth_primitives::{ TransactionKind as PrimitiveTransactionKind, TransactionSignedEcRecovered, TxType, B256, U128, U256, U64, }; -use reth_rpc_types::Transaction; +use reth_rpc_types::{CallInput, CallRequest, Transaction}; use signature::from_primitive_signature; /// Create a new rpc transaction result for a mined transaction, using the given block hash, /// number, and tx index fields to populate the corresponding fields in the rpc result. @@ -132,3 +132,43 @@ fn fill( blob_versioned_hashes, } } + +/// Convert [TransactionSignedEcRecovered] to [CallRequest] +pub fn transaction_to_call_request(tx: TransactionSignedEcRecovered) -> CallRequest { + let from = tx.signer(); + let to = tx.transaction.to(); + let gas = tx.transaction.gas_limit(); + let value = tx.transaction.value(); + let input = tx.transaction.input().clone(); + let nonce = tx.transaction.nonce(); + let chain_id = tx.transaction.chain_id(); + let access_list = tx.transaction.access_list().cloned(); + let max_fee_per_blob_gas = tx.transaction.max_fee_per_blob_gas(); + let blob_versioned_hashes = tx.transaction.blob_versioned_hashes(); + let tx_type = tx.transaction.tx_type(); + + // fees depending on the transaction type + let (gas_price, max_fee_per_gas) = if tx.is_dynamic_fee() { + (None, Some(tx.max_fee_per_gas())) + } else { + (Some(tx.max_fee_per_gas()), None) + }; + let max_priority_fee_per_gas = tx.transaction.max_priority_fee_per_gas(); + + CallRequest { + from: Some(from), + to, + gas_price: gas_price.map(U256::from), + max_fee_per_gas: max_fee_per_gas.map(U256::from), + max_priority_fee_per_gas: max_priority_fee_per_gas.map(U256::from), + gas: Some(U256::from(gas)), + value: Some(value.into()), + input: CallInput::new(input), + nonce: Some(U64::from(nonce)), + chain_id: chain_id.map(U64::from), + access_list, + max_fee_per_blob_gas: max_fee_per_blob_gas.map(U256::from), + blob_versioned_hashes, + transaction_type: Some(tx_type.into()), + } +} diff --git a/crates/rpc/rpc-types/src/eth/call.rs b/crates/rpc/rpc-types/src/eth/call.rs index 8c65b85b1..cfda456c4 100644 --- a/crates/rpc/rpc-types/src/eth/call.rs +++ b/crates/rpc/rpc-types/src/eth/call.rs @@ -158,6 +158,16 @@ pub struct CallInput { } impl CallInput { + /// Creates a new instance with the given input data. + pub fn new(data: Bytes) -> Self { + Self::maybe_input(Some(data)) + } + + /// Creates a new instance with the given input data. + pub fn maybe_input(input: Option) -> Self { + Self { input, data: None } + } + /// Consumes the type and returns the optional input data. /// /// Returns an error if both `data` and `input` fields are set and not equal. diff --git a/crates/transaction-pool/src/traits.rs b/crates/transaction-pool/src/traits.rs index a3ace0b79..01162f427 100644 --- a/crates/transaction-pool/src/traits.rs +++ b/crates/transaction-pool/src/traits.rs @@ -723,6 +723,12 @@ pub trait PoolTransaction: /// [`TransactionKind::Create`] if the transaction is a contract creation. fn kind(&self) -> &TransactionKind; + /// Returns the recipient of the transaction if it is not a [TransactionKind::Create] + /// transaction. + fn to(&self) -> Option
{ + (*self.kind()).to() + } + /// Returns the input data of this transaction. fn input(&self) -> &[u8]; diff --git a/crates/transaction-pool/src/validate/mod.rs b/crates/transaction-pool/src/validate/mod.rs index 9debb7b60..7df86d51a 100644 --- a/crates/transaction-pool/src/validate/mod.rs +++ b/crates/transaction-pool/src/validate/mod.rs @@ -226,6 +226,11 @@ impl ValidPoolTransaction { self.transaction.sender() } + /// Returns the recipient of the transaction if it is not a CREATE transaction. + pub fn to(&self) -> Option
{ + self.transaction.to() + } + /// Returns the internal identifier for the sender of this transaction pub(crate) fn sender_id(&self) -> SenderId { self.transaction_id.sender diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 9f21bd4e0..89f344388 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -21,12 +21,10 @@ reth-network-api.workspace = true reth-network.workspace = true reth-transaction-pool.workspace = true reth-tasks.workspace = true - eyre.workspace = true futures.workspace = true async-trait.workspace = true tokio.workspace = true - [[example]] name = "db-access" path = "db-access.rs" diff --git a/examples/trace-transaction-cli/Cargo.toml b/examples/trace-transaction-cli/Cargo.toml new file mode 100644 index 000000000..ce0221619 --- /dev/null +++ b/examples/trace-transaction-cli/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "trace-transaction-cli" +version = "0.0.0" +publish = false +edition.workspace = true +license.workspace = true + +[dependencies] +reth.workspace = true +clap = { workspace = true, features = ["derive"] } +jsonrpsee = { workspace = true, features = ["server", "macros"] } +futures-util.workspace = true +eyre.workspace = true + +[dev-dependencies] +tokio.workspace = true \ No newline at end of file diff --git a/examples/trace-transaction-cli/src/main.rs b/examples/trace-transaction-cli/src/main.rs new file mode 100644 index 000000000..df4c71337 --- /dev/null +++ b/examples/trace-transaction-cli/src/main.rs @@ -0,0 +1,101 @@ +//! Example of how to trace new pending transactions in the reth CLI +//! +//! Run with +//! +//! ```not_rust +//! cargo run --release -p trace-transaction-cli -- node --http --ws --recipients 0x....,0x.... +//! ``` +//! +//! If no recipients are specified, all transactions will be traced. +use clap::Parser; +use futures_util::StreamExt; +use reth::{ + cli::{ + components::{RethNodeComponents, RethRpcComponents, RethRpcServerHandles}, + config::RethRpcConfig, + ext::{RethCliExt, RethNodeCommandConfig}, + Cli, + }, + primitives::{Address, IntoRecoveredTransaction}, + rpc::{compat::transaction_to_call_request, types::trace::parity::TraceType}, + tasks::TaskSpawner, + transaction_pool::TransactionPool, +}; +use std::collections::HashSet; + +fn main() { + Cli::::parse().run().unwrap(); +} + +/// The type that tells the reth CLI what extensions to use +struct MyRethCliExt; + +impl RethCliExt for MyRethCliExt { + /// This tells the reth CLI to trace addresses via `RethCliTxpoolExt` + type Node = RethCliTxpoolExt; +} + +/// Our custom cli args extension that adds one flag to reth default CLI. +#[derive(Debug, Clone, Default, clap::Args)] +struct RethCliTxpoolExt { + /// recipients addresses that we want to trace + #[arg(long, value_delimiter = ',')] + pub recipients: Vec
, +} + +impl RethNodeCommandConfig for RethCliTxpoolExt { + fn on_rpc_server_started( + &mut self, + _config: &Conf, + components: &Reth, + rpc_components: RethRpcComponents<'_, Reth>, + _handles: RethRpcServerHandles, + ) -> eyre::Result<()> + where + Conf: RethRpcConfig, + Reth: RethNodeComponents, + { + let recipients = self.recipients.iter().copied().collect::>(); + + // create a new subscription to pending transactions + let mut pending_transactions = components.pool().new_pending_pool_transactions_listener(); + + // get an instance of the `trace_` API handler + let traceapi = rpc_components.registry.trace_api(); + + println!("Spawning trace task!"); + // Spawn an async block to listen for transactions. + components.task_executor().spawn(Box::pin(async move { + // Waiting for new transactions + while let Some(event) = pending_transactions.next().await { + let tx = event.transaction; + println!("Transaction received: {:?}", tx); + + if let Some(tx_recipient_address) = tx.to() { + if recipients.is_empty() || recipients.contains(&tx_recipient_address) { + // trace the transaction with `trace_call` + let callrequest = + transaction_to_call_request(tx.to_recovered_transaction()); + if let Ok(trace_result) = traceapi + .trace_call( + callrequest, + HashSet::from([TraceType::Trace]), + None, + None, + None, + ) + .await + { + println!( + "trace result for transaction : {:?} is {:?}", + tx.hash(), + trace_result + ); + } + } + } + } + })); + Ok(()) + } +}