From aaa99f6b4cd14a2e7efc3ccdc9c33dd5fe3c8e34 Mon Sep 17 00:00:00 2001 From: chirag-bgh <76247491+chirag-bgh@users.noreply.github.com> Date: Thu, 23 Mar 2023 16:16:07 +0530 Subject: [PATCH] feat: add transaction_receipt (#1919) Co-authored-by: Matthias Seitz --- Cargo.lock | 1 + crates/rpc/rpc-builder/tests/it/http.rs | 1 - crates/rpc/rpc-types/src/eth/log.rs | 18 ++++ .../rpc-types/src/eth/transaction/receipt.rs | 5 +- crates/rpc/rpc/Cargo.toml | 1 + crates/rpc/rpc/src/eth/api/server.rs | 6 +- crates/rpc/rpc/src/eth/api/transactions.rs | 102 +++++++++++++++++- 7 files changed, 126 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0525c086cc..9fea3582ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5045,6 +5045,7 @@ dependencies = [ "reth-tasks", "reth-transaction-pool", "revm", + "revm-primitives", "schnellru", "secp256k1", "serde", diff --git a/crates/rpc/rpc-builder/tests/it/http.rs b/crates/rpc/rpc-builder/tests/it/http.rs index 2659660faa..dd0621fe01 100644 --- a/crates/rpc/rpc-builder/tests/it/http.rs +++ b/crates/rpc/rpc-builder/tests/it/http.rs @@ -98,7 +98,6 @@ where // Unimplemented assert!(is_unimplemented(EthApiClient::author(client).await.err().unwrap())); - assert!(is_unimplemented(EthApiClient::transaction_receipt(client, hash).await.err().unwrap())); assert!(is_unimplemented(EthApiClient::gas_price(client).await.err().unwrap())); assert!(is_unimplemented(EthApiClient::max_priority_fee_per_gas(client).await.err().unwrap())); assert!(is_unimplemented(EthApiClient::is_mining(client).await.err().unwrap())); diff --git a/crates/rpc/rpc-types/src/eth/log.rs b/crates/rpc/rpc-types/src/eth/log.rs index 06becfe555..68e370dea6 100644 --- a/crates/rpc/rpc-types/src/eth/log.rs +++ b/crates/rpc/rpc-types/src/eth/log.rs @@ -28,6 +28,24 @@ pub struct Log { pub removed: bool, } +impl Log { + /// Creates a new rpc Log from a primitive log type from DB + pub fn from_primitive(log: reth_primitives::Log) -> Self { + Self { + address: log.address, + topics: log.topics, + data: log.data, + block_hash: None, + block_number: None, + transaction_hash: None, + transaction_index: None, + log_index: None, + transaction_log_index: None, + removed: false, + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/rpc/rpc-types/src/eth/transaction/receipt.rs b/crates/rpc/rpc-types/src/eth/transaction/receipt.rs index 059556669e..a373d1b98e 100644 --- a/crates/rpc/rpc-types/src/eth/transaction/receipt.rs +++ b/crates/rpc/rpc-types/src/eth/transaction/receipt.rs @@ -1,4 +1,5 @@ -use reth_primitives::{rpc::Log, Address, Bloom, H256, U128, U256, U64}; +use crate::Log; +use reth_primitives::{Address, Bloom, H256, U128, U256, U64}; use serde::{Deserialize, Serialize}; /// Transaction receipt @@ -25,7 +26,7 @@ pub struct TransactionReceipt { pub contract_address: Option
, /// Logs emitted by this transaction. pub logs: Vec, - /// The state root + /// The post-transaction stateroot (pre Byzantium) /// /// EIP98 makes this optional field, if it's missing then skip serializing it #[serde(skip_serializing_if = "Option::is_none", rename = "root")] diff --git a/crates/rpc/rpc/Cargo.toml b/crates/rpc/rpc/Cargo.toml index 77ce298f54..30fe4953e7 100644 --- a/crates/rpc/rpc/Cargo.toml +++ b/crates/rpc/rpc/Cargo.toml @@ -27,6 +27,7 @@ reth-tasks = { path = "../../tasks" } # eth revm = { version = "3.0.0", features = ["optional_block_gas_limit"] } ethers-core = { git = "https://github.com/gakonst/ethers-rs", features = ["eip712"] } +revm-primitives = { version = "1.0", features = ["serde"] } # rpc diff --git a/crates/rpc/rpc/src/eth/api/server.rs b/crates/rpc/rpc/src/eth/api/server.rs index b4451eb357..db5a3098e1 100644 --- a/crates/rpc/rpc/src/eth/api/server.rs +++ b/crates/rpc/rpc/src/eth/api/server.rs @@ -21,6 +21,7 @@ use reth_rpc_types::{ Work, }; use reth_transaction_pool::TransactionPool; + use serde_json::Value; use std::collections::BTreeMap; use tracing::trace; @@ -160,8 +161,9 @@ where } /// Handler for: `eth_getTransactionReceipt` - async fn transaction_receipt(&self, _hash: H256) -> Result> { - Err(internal_rpc_err("unimplemented")) + async fn transaction_receipt(&self, hash: H256) -> Result> { + trace!(target: "rpc::eth", ?hash, "Serving eth_getTransactionReceipt"); + Ok(EthTransactions::transaction_receipt(self, hash).await?) } /// Handler for: `eth_getBalance` diff --git a/crates/rpc/rpc/src/eth/api/transactions.rs b/crates/rpc/rpc/src/eth/api/transactions.rs index 73ac1c54f2..8bc0a92b3e 100644 --- a/crates/rpc/rpc/src/eth/api/transactions.rs +++ b/crates/rpc/rpc/src/eth/api/transactions.rs @@ -11,14 +11,19 @@ use async_trait::async_trait; use crate::eth::error::SignError; use reth_primitives::{ Address, BlockId, BlockNumberOrTag, Bytes, FromRecoveredTransaction, IntoRecoveredTransaction, - TransactionSigned, TransactionSignedEcRecovered, H256, U256, + Receipt, Transaction as PrimitiveTransaction, + TransactionKind::{Call, Create}, + TransactionMeta, TransactionSigned, TransactionSignedEcRecovered, TxEip1559, TxEip2930, + TxLegacy, H256, U128, U256, U64, }; use reth_provider::{providers::ChainState, BlockProvider, EvmEnvProvider, StateProviderFactory}; use reth_rpc_types::{ - Index, Transaction, TransactionInfo, TransactionRequest, TypedTransactionRequest, + Index, Log, Transaction, TransactionInfo, TransactionReceipt, TransactionRequest, + TypedTransactionRequest, }; use reth_transaction_pool::{TransactionOrigin, TransactionPool}; use revm::primitives::{BlockEnv, CfgEnv}; +use revm_primitives::utilities::create_address; /// Commonly used transaction related functions for the [EthApi] type in the `eth_` namespace #[async_trait::async_trait] @@ -49,6 +54,12 @@ pub trait EthTransactions: Send + Sync { &self, hash: H256, ) -> EthResult>; + + /// Returns the transaction receipt for the given hash. + /// + /// Returns None if the transaction does not exist or is pending + /// Note: The tx receipt is not available for pending transactions. + async fn transaction_receipt(&self, hash: H256) -> EthResult>; } #[async_trait] @@ -143,6 +154,20 @@ where } } } + + async fn transaction_receipt(&self, hash: H256) -> EthResult> { + let (tx, meta) = match self.client().transaction_by_hash_with_meta(hash)? { + Some((tx, meta)) => (tx, meta), + None => return Ok(None), + }; + + let receipt = match self.client().receipt_by_hash(hash)? { + Some(recpt) => recpt, + None => return Ok(None), + }; + + Self::build_transaction_receipt(tx, meta, receipt).map(Some) + } } // === impl EthApi === @@ -240,8 +265,79 @@ where Ok(hash) } -} + /// Helper function for `eth_getTransactionReceipt` + /// + /// Returns the receipt + pub(crate) fn build_transaction_receipt( + tx: TransactionSigned, + meta: TransactionMeta, + mut receipt: Receipt, + ) -> EthResult { + let transaction = + tx.clone().into_ecrecovered().ok_or(EthApiError::InvalidTransactionSignature)?; + + let mut res_receipt = TransactionReceipt { + transaction_hash: Some(meta.tx_hash), + transaction_index: Some(U256::from(meta.index)), + block_hash: Some(meta.block_hash), + block_number: Some(U256::from(meta.block_number)), + from: transaction.signer(), + to: None, + cumulative_gas_used: U256::from(receipt.cumulative_gas_used), + gas_used: Some(U256::from(0)), + contract_address: None, + logs: std::mem::take(&mut receipt.logs).into_iter().map(Log::from_primitive).collect(), + effective_gas_price: U128::from(0), + transaction_type: U256::from(0), + // TODO: set state root after the block + state_root: None, + logs_bloom: receipt.bloom_slow(), + status_code: if receipt.success { Some(U64::from(1)) } else { Some(U64::from(0)) }, + }; + + match tx.transaction.kind() { + Create => { + // set contract address if creation was successful + if receipt.success { + res_receipt.contract_address = + Some(create_address(transaction.signer(), tx.transaction.nonce())); + } + } + Call(addr) => { + res_receipt.to = Some(*addr); + } + } + + match tx.transaction { + PrimitiveTransaction::Legacy(TxLegacy { gas_limit, gas_price, .. }) => { + // TODO: set actual gas used + res_receipt.gas_used = Some(U256::from(gas_limit)); + res_receipt.transaction_type = U256::from(0); + res_receipt.effective_gas_price = U128::from(gas_price); + } + PrimitiveTransaction::Eip2930(TxEip2930 { gas_limit, gas_price, .. }) => { + // TODO: set actual gas used + res_receipt.gas_used = Some(U256::from(gas_limit)); + res_receipt.transaction_type = U256::from(1); + res_receipt.effective_gas_price = U128::from(gas_price); + } + PrimitiveTransaction::Eip1559(TxEip1559 { + gas_limit, + max_fee_per_gas, + max_priority_fee_per_gas, + .. + }) => { + // TODO: set actual gas used + res_receipt.gas_used = Some(U256::from(gas_limit)); + res_receipt.transaction_type = U256::from(2); + res_receipt.effective_gas_price = + U128::from(max_fee_per_gas + max_priority_fee_per_gas) + } + } + Ok(res_receipt) + } +} /// Represents from where a transaction was fetched. #[derive(Debug, Clone, Eq, PartialEq)] pub enum TransactionSource {