Skip to content
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
57 changes: 53 additions & 4 deletions crates/optimism/rpc/src/eth/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ 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,
};
Expand Down Expand Up @@ -179,6 +181,53 @@ where
OpEthApiError: FromEvmError<N::Evm>,
Rpc: RpcConvert<Primitives = N::Primitives, Error = OpEthApiError>,
{
async fn transaction_by_hash(
Copy link
Contributor Author

@0x00101010 0x00101010 Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: Steps 1 and 3 duplicate logic from the default trait implementation rather than extracting to a helper method. This keeps the override self-contained and avoids adding a helper method to the trait API that would only exist to support this one override.

Also we check flashblocks first before checking pool (in case people are using nodes with txpool gossip enabled to serve rpc requests)

&self,
hash: B256,
) -> Result<Option<TransactionSource<ProviderTx<Self::Provider>>>, 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)?;

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 meta = indexed_tx.meta();
return Ok(Some(TransactionSource::Block {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did not choose to add a new FlashBlock source type, current Block type seem to represent the situation well

transaction: indexed_tx.recovered_tx().cloned(),
index: meta.index,
block_hash: meta.block_hash,
block_number: meta.block_number,
base_fee: meta.base_fee,
}));
}

// 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<N, Rpc> OpEthApi<N, Rpc>
Expand Down
60 changes: 26 additions & 34 deletions crates/rpc/rpc-eth-api/src/helpers/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,45 +609,37 @@ 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
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reworked the function a bit to avoid unnecessary nested loops and improve readability

.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?
{
// 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)?;

return Ok(Some(TransactionSource::Block {
transaction,
index: meta.index,
block_hash: meta.block_hash,
block_number: meta.block_number,
base_fee: meta.base_fee,
}));
}

Ok(resp)
// 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(None)
}
}

Expand Down
Loading