From 58cd0d41c06fc777e034d22f5b384d06f4895bd0 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Mon, 24 Nov 2025 17:19:53 -0800 Subject: [PATCH 1/5] feat(flashblock): Enable eth_getTransactionByHash support for flashblock --- crates/optimism/rpc/src/eth/transaction.rs | 65 +++++++++++++++++-- .../rpc-eth-api/src/helpers/transaction.rs | 59 +++++++---------- 2 files changed, 86 insertions(+), 38 deletions(-) diff --git a/crates/optimism/rpc/src/eth/transaction.rs b/crates/optimism/rpc/src/eth/transaction.rs index 58d367012f1..f7ade0f5d85 100644 --- a/crates/optimism/rpc/src/eth/transaction.rs +++ b/crates/optimism/rpc/src/eth/transaction.rs @@ -1,20 +1,23 @@ //! Loads and formats OP transaction RPC response. use crate::{OpEthApi, OpEthApiError, SequencerClient}; +use alloy_consensus::BlockHeader; use alloy_primitives::{Bytes, B256}; use alloy_rpc_types_eth::TransactionInfo; use futures::StreamExt; use op_alloy_consensus::{transaction::OpTransactionInfo, OpTransaction}; use reth_chain_state::CanonStateSubscriptions; use reth_optimism_primitives::DepositReceipt; -use reth_primitives_traits::{BlockBody, Recovered, SignedTransaction, WithEncoded}; +use reth_primitives_traits::{ + BlockBody, Recovered, SignedTransaction, SignerRecoverable, WithEncoded, +}; use reth_rpc_eth_api::{ - helpers::{spec::SignersForRpc, EthTransactions, LoadReceipt, LoadTransaction}, + helpers::{spec::SignersForRpc, EthTransactions, LoadReceipt, LoadTransaction, SpawnBlocking}, try_into_op_tx_info, EthApiTypes as _, FromEthApiError, FromEvmError, RpcConvert, RpcNodeCore, RpcReceipt, TxInfoMapper, }; -use reth_rpc_eth_types::EthApiError; -use reth_storage_api::{errors::ProviderError, ReceiptProvider}; +use reth_rpc_eth_types::{EthApiError, TransactionSource}; +use reth_storage_api::{errors::ProviderError, ProviderTx, ReceiptProvider, TransactionsProvider}; use reth_transaction_pool::{ AddedTransactionOutcome, PoolPooledTx, PoolTransaction, TransactionOrigin, TransactionPool, }; @@ -179,6 +182,60 @@ where OpEthApiError: FromEvmError, Rpc: RpcConvert, { + async fn transaction_by_hash( + &self, + hash: B256, + ) -> Result>>, Self::Error> { + // 1. Try to find the transaction on disk (historical blocks) + if let Some((tx, meta)) = self + .spawn_blocking_io(move |this| { + this.provider() + .transaction_by_hash_with_meta(hash) + .map_err(Self::Error::from_eth_err) + }) + .await? + { + let transaction = tx + .try_into_recovered_unchecked() + .map_err(|_| EthApiError::InvalidTransactionSignature) + .map_err(Self::Error::from_eth_err)?; + + return Ok(Some(TransactionSource::Block { + transaction, + index: meta.index, + block_hash: meta.block_hash, + block_number: meta.block_number, + base_fee: meta.base_fee, + })); + } + + // 2. check flashblocks (sequencer preconfirmations) + if let Ok(Some(pending_block)) = self.pending_flashblock().await && + let Some(indexed_tx) = pending_block.block().find_indexed(hash) + { + let recovered_tx = pending_block + .block() + .recovered_transaction(indexed_tx.index()) + .expect("transaction must exist at index returned by find_indexed") + .cloned(); + + let meta = indexed_tx.meta(); + return Ok(Some(TransactionSource::Block { + transaction: recovered_tx, + index: meta.index, + block_hash: meta.block_hash, + block_number: meta.block_number, + base_fee: pending_block.block().header().base_fee_per_gas(), + })); + } + + // 3. check local pool + if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone().into_consensus()) { + return Ok(Some(TransactionSource::Pool(tx))); + } + + Ok(None) + } } impl OpEthApi diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 2f6c3674ede..93e6fa1bab0 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -609,45 +609,36 @@ pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt { > + Send { async move { // Try to find the transaction on disk - let mut resp = self + if let Some((tx, meta)) = self .spawn_blocking_io(move |this| { - match this - .provider() + this.provider() .transaction_by_hash_with_meta(hash) - .map_err(Self::Error::from_eth_err)? - { - None => Ok(None), - Some((tx, meta)) => { - // Note: we assume this transaction is valid, because it's mined (or - // part of pending block) and already. We don't need to - // check for pre EIP-2 because this transaction could be pre-EIP-2. - let transaction = tx - .try_into_recovered_unchecked() - .map_err(|_| EthApiError::InvalidTransactionSignature)?; - - let tx = TransactionSource::Block { - transaction, - index: meta.index, - block_hash: meta.block_hash, - block_number: meta.block_number, - base_fee: meta.base_fee, - }; - Ok(Some(tx)) - } - } + .map_err(Self::Error::from_eth_err) }) - .await?; - - if resp.is_none() { - // tx not found on disk, check pool - if let Some(tx) = - self.pool().get(&hash).map(|tx| tx.transaction.clone().into_consensus()) - { - resp = Some(TransactionSource::Pool(tx.into())); - } + .await? + { + let transaction = tx + .try_into_recovered_unchecked() + .map_err(|_| EthApiError::InvalidTransactionSignature) + .map_err(Self::Error::from_eth_err)?; + + return Ok(Some(TransactionSource::Block { + transaction, + index: meta.index, + block_hash: meta.block_hash, + block_number: meta.block_number, + base_fee: meta.base_fee, + })); + } + + // tx not found on disk, check pool + if let Some(tx) = + self.pool().get(&hash).map(|tx| tx.transaction.clone().into_consensus()) + { + return Ok(Some(TransactionSource::Pool(tx.into()))); } - Ok(resp) + Ok(None) } } From 1dcb7161844152f1e9c638e025a63e572428edf7 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Mon, 24 Nov 2025 17:31:20 -0800 Subject: [PATCH 2/5] Minor simplifications --- crates/optimism/rpc/src/eth/transaction.rs | 3 +-- crates/rpc/rpc-eth-api/src/helpers/transaction.rs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/optimism/rpc/src/eth/transaction.rs b/crates/optimism/rpc/src/eth/transaction.rs index f7ade0f5d85..a45c4e9c60f 100644 --- a/crates/optimism/rpc/src/eth/transaction.rs +++ b/crates/optimism/rpc/src/eth/transaction.rs @@ -197,8 +197,7 @@ where { let transaction = tx .try_into_recovered_unchecked() - .map_err(|_| EthApiError::InvalidTransactionSignature) - .map_err(Self::Error::from_eth_err)?; + .map_err(|_| EthApiError::InvalidTransactionSignature)?; return Ok(Some(TransactionSource::Block { transaction, diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 93e6fa1bab0..d1f180900bc 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -619,8 +619,7 @@ pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt { { let transaction = tx .try_into_recovered_unchecked() - .map_err(|_| EthApiError::InvalidTransactionSignature) - .map_err(Self::Error::from_eth_err)?; + .map_err(|_| EthApiError::InvalidTransactionSignature)?; return Ok(Some(TransactionSource::Block { transaction, From de6f4245a8ab8e4124d2bc813e3b4efc26dc7da7 Mon Sep 17 00:00:00 2001 From: Francis Li Date: Mon, 24 Nov 2025 17:38:55 -0800 Subject: [PATCH 3/5] Simplify code with existing functions --- crates/optimism/rpc/src/eth/transaction.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/crates/optimism/rpc/src/eth/transaction.rs b/crates/optimism/rpc/src/eth/transaction.rs index a45c4e9c60f..02c392e28ef 100644 --- a/crates/optimism/rpc/src/eth/transaction.rs +++ b/crates/optimism/rpc/src/eth/transaction.rs @@ -1,7 +1,6 @@ //! Loads and formats OP transaction RPC response. use crate::{OpEthApi, OpEthApiError, SequencerClient}; -use alloy_consensus::BlockHeader; use alloy_primitives::{Bytes, B256}; use alloy_rpc_types_eth::TransactionInfo; use futures::StreamExt; @@ -212,19 +211,13 @@ where if let Ok(Some(pending_block)) = self.pending_flashblock().await && let Some(indexed_tx) = pending_block.block().find_indexed(hash) { - let recovered_tx = pending_block - .block() - .recovered_transaction(indexed_tx.index()) - .expect("transaction must exist at index returned by find_indexed") - .cloned(); - let meta = indexed_tx.meta(); return Ok(Some(TransactionSource::Block { - transaction: recovered_tx, + transaction: indexed_tx.recovered_tx().cloned(), index: meta.index, block_hash: meta.block_hash, block_number: meta.block_number, - base_fee: pending_block.block().header().base_fee_per_gas(), + base_fee: meta.base_fee, })); } From dd13356dffe878901c4256592de300078e63782d Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 25 Nov 2025 08:57:42 +0100 Subject: [PATCH 4/5] chore: use clone_into_consensus --- crates/optimism/rpc/src/eth/transaction.rs | 2 +- crates/rpc/rpc-eth-api/src/helpers/transaction.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/optimism/rpc/src/eth/transaction.rs b/crates/optimism/rpc/src/eth/transaction.rs index 02c392e28ef..cec86728756 100644 --- a/crates/optimism/rpc/src/eth/transaction.rs +++ b/crates/optimism/rpc/src/eth/transaction.rs @@ -222,7 +222,7 @@ where } // 3. check local pool - if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone().into_consensus()) { + if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone_into_consensus()) { return Ok(Some(TransactionSource::Pool(tx))); } diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index d1f180900bc..889c49001da 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -631,8 +631,7 @@ pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt { } // tx not found on disk, check pool - if let Some(tx) = - self.pool().get(&hash).map(|tx| tx.transaction.clone().into_consensus()) + if let Some(tx) = self.pool().get(&hash).map(|tx| tx.transaction.clone_into_consensus()) { return Ok(Some(TransactionSource::Pool(tx.into()))); } From 11bd0721154f04affb8d7bb5aa096f1d4174931f Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Tue, 25 Nov 2025 09:00:00 +0100 Subject: [PATCH 5/5] chore: add comment back --- crates/rpc/rpc-eth-api/src/helpers/transaction.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs index 889c49001da..a6b7285d0c5 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/transaction.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/transaction.rs @@ -617,6 +617,9 @@ pub trait LoadTransaction: SpawnBlocking + FullEthApiTypes + RpcNodeCoreExt { }) .await? { + // Note: we assume this transaction is valid, because it's mined (or + // part of pending block) and already. We don't need to + // check for pre EIP-2 because this transaction could be pre-EIP-2. let transaction = tx .try_into_recovered_unchecked() .map_err(|_| EthApiError::InvalidTransactionSignature)?;