Skip to content

Commit 3f3d846

Browse files
authored
Merge pull request #19 from base/cody/get-transaction-by-hash
Support get transaction by hash
2 parents 3c6167d + bb9edd9 commit 3f3d846

File tree

3 files changed

+180
-4
lines changed

3 files changed

+180
-4
lines changed

crates/flashblocks-rpc/src/flashblocks.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,24 @@ fn get_and_set_txs_and_receipts(
369369
) {
370370
error!("Failed to set transaction count in cache: {}", e);
371371
}
372+
373+
// also keep track of sender of each transaction
374+
if let Err(e) = cache.set(
375+
&format!("tx_sender:{}", transaction.tx_hash()),
376+
&from,
377+
Some(10),
378+
) {
379+
error!("Failed to set transaction sender in cache: {}", e);
380+
}
381+
382+
// also keep track of the block number of each transaction
383+
if let Err(e) = cache.set(
384+
&format!("tx_block_number:{}", transaction.tx_hash()),
385+
&block_number,
386+
Some(10),
387+
) {
388+
error!("Failed to set transaction sender in cache: {}", e);
389+
}
372390
}
373391
}
374392

@@ -589,6 +607,61 @@ mod tests {
589607
))
590608
.unwrap();
591609
assert_eq!(tx2_receipt.cumulative_gas_used(), 42000);
610+
611+
// verify tx_sender, tx_block_number, tx_idx
612+
let tx_sender = cache
613+
.get::<Address>(&format!(
614+
"tx_sender:{}",
615+
"0x3cbbc9a6811ac5b2a2e5780bdb67baffc04246a59f39e398be048f1b2d05460c"
616+
))
617+
.unwrap();
618+
assert_eq!(
619+
tx_sender,
620+
Address::from_str("0xb63d5fd2e6c53fe06680c47736aba771211105e4").unwrap()
621+
);
622+
623+
let tx_block_number = cache
624+
.get::<u64>(&format!(
625+
"tx_block_number:{}",
626+
"0x3cbbc9a6811ac5b2a2e5780bdb67baffc04246a59f39e398be048f1b2d05460c"
627+
))
628+
.unwrap();
629+
assert_eq!(tx_block_number, 1);
630+
631+
let tx_idx = cache
632+
.get::<u64>(&format!(
633+
"tx_idx:{}",
634+
"0x3cbbc9a6811ac5b2a2e5780bdb67baffc04246a59f39e398be048f1b2d05460c"
635+
))
636+
.unwrap();
637+
assert_eq!(tx_idx, 0);
638+
639+
let tx_sender2 = cache
640+
.get::<Address>(&format!(
641+
"tx_sender:{}",
642+
"0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8"
643+
))
644+
.unwrap();
645+
assert_eq!(
646+
tx_sender2,
647+
Address::from_str("0x6e5e56b972374e4fde8390df0033397df931a49d").unwrap()
648+
);
649+
650+
let tx_block_number2 = cache
651+
.get::<u64>(&format!(
652+
"tx_block_number:{}",
653+
"0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8"
654+
))
655+
.unwrap();
656+
assert_eq!(tx_block_number2, 1);
657+
658+
let tx_idx2 = cache
659+
.get::<u64>(&format!(
660+
"tx_idx:{}",
661+
"0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8"
662+
))
663+
.unwrap();
664+
assert_eq!(tx_idx2, 1);
592665
}
593666

594667
#[test]

crates/flashblocks-rpc/src/integration/integration_test.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ mod tests {
165165
.http_port(1238)
166166
.websocket_url("ws://localhost:1239");
167167

168-
framework.start("reth-flashblocks", &reth).await.unwrap();
168+
framework.start("base-reth-node", &reth).await.unwrap();
169169

170170
// Wait for some time to allow messages to be processed
171171
tokio::time::sleep(Duration::from_secs(3)).await;
@@ -224,6 +224,27 @@ mod tests {
224224
assert!(receipt.is_some());
225225
assert_eq!(receipt.unwrap().gas_used(), 24000); // 45000 - 21000
226226

227+
// check transaction by hash
228+
let tx = provider
229+
.get_transaction_by_hash(
230+
B256::from_str(
231+
"0x2be2e6f8b01b03b87ae9f0ebca8bbd420f174bef0fbcc18c7802c5378b78f548",
232+
)
233+
.unwrap(),
234+
)
235+
.await?;
236+
assert!(tx.is_some());
237+
238+
let tx = provider
239+
.get_transaction_by_hash(
240+
B256::from_str(
241+
"0xa6155b295085d3b87a3c86e342fe11c3b22f9952d0d85d9d34d223b7d6a17cd8",
242+
)
243+
.unwrap(),
244+
)
245+
.await?;
246+
assert!(tx.is_some());
247+
227248
// check balance
228249
// use curl command to get balance with pending tag, since alloy provider doesn't support pending tag
229250
let output = std::process::Command::new("curl")

crates/flashblocks-rpc/src/rpc.rs

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,19 @@ use jsonrpsee::{
1515
use op_alloy_consensus::OpTxEnvelope;
1616
use op_alloy_network::Optimism;
1717
use op_alloy_rpc_types::Transaction;
18+
use reth::providers::TransactionsProvider;
19+
use reth::rpc::server_types::eth::TransactionSource;
1820
use reth::{api::BlockBody, providers::HeaderProvider};
1921
use reth_optimism_chainspec::OpChainSpec;
2022
use reth_optimism_primitives::{OpBlock, OpReceipt, OpTransactionSigned};
2123
use reth_optimism_rpc::OpReceiptBuilder;
2224
use reth_rpc_eth_api::helpers::EthTransactions;
23-
use reth_rpc_eth_api::RpcReceipt;
2425
use reth_rpc_eth_api::{helpers::FullEthApi, RpcBlock};
2526
use reth_rpc_eth_api::{
2627
helpers::{EthBlocks, EthState},
2728
RpcNodeCore,
2829
};
30+
use reth_rpc_eth_api::{RpcReceipt, RpcTransaction};
2931
use tracing::{debug, info};
3032

3133
#[cfg_attr(not(test), rpc(server, namespace = "eth"))]
@@ -54,6 +56,12 @@ pub trait EthApiOverride {
5456
address: Address,
5557
block_number: Option<BlockId>,
5658
) -> RpcResult<U256>;
59+
60+
#[method(name = "getTransactionByHash")]
61+
async fn transaction_by_hash(
62+
&self,
63+
tx_hash: TxHash,
64+
) -> RpcResult<Option<RpcTransaction<Optimism>>>;
5765
}
5866

5967
#[derive(Debug)]
@@ -89,10 +97,10 @@ impl<E> EthApiExt<E> {
8997
let signed_tx_ec_recovered = Recovered::new_unchecked(tx.clone(), sender);
9098
let tx_info = TransactionInfo {
9199
hash: Some(tx.tx_hash()),
92-
block_hash: None,
100+
block_hash: Some(block.header.hash_slow()),
93101
block_number: Some(block.number),
94102
index: Some(idx as u64),
95-
base_fee: None,
103+
base_fee: block.base_fee_per_gas,
96104
};
97105
self.transform_tx(signed_tx_ec_recovered, tx_info)
98106
})
@@ -226,12 +234,14 @@ where
226234
Eth: FullEthApi<NetworkTypes = Optimism> + Send + Sync + 'static,
227235
Eth: RpcNodeCore,
228236
<Eth as RpcNodeCore>::Provider: HeaderProvider<Header = alloy_consensus::Header>,
237+
<Eth as RpcNodeCore>::Provider: TransactionsProvider<Transaction = OpTransactionSigned>,
229238
{
230239
async fn block_by_number(
231240
&self,
232241
number: BlockNumberOrTag,
233242
_full: bool,
234243
) -> RpcResult<Option<RpcBlock<Optimism>>> {
244+
debug!("block_by_number: {:?}", number);
235245
match number {
236246
BlockNumberOrTag::Pending => {
237247
debug!("pending block by number, delegating to flashblocks");
@@ -255,6 +265,7 @@ where
255265
&self,
256266
tx_hash: TxHash,
257267
) -> RpcResult<Option<RpcReceipt<Optimism>>> {
268+
debug!("get_transaction_receipt: {:?}", tx_hash);
258269
let receipt = EthTransactions::transaction_receipt(&self.eth_api, tx_hash).await;
259270

260271
// check if receipt is none
@@ -285,6 +296,7 @@ where
285296
address: Address,
286297
block_number: Option<BlockId>,
287298
) -> RpcResult<U256> {
299+
debug!("get_balance: {:?}", address);
288300
let block_id = block_number.unwrap_or_default();
289301
if block_id.is_pending() {
290302
self.metrics.get_balance.increment(1);
@@ -304,6 +316,7 @@ where
304316
address: Address,
305317
block_number: Option<BlockId>,
306318
) -> RpcResult<U256> {
319+
debug!("get_transaction_count: {:?}", address);
307320
let block_id = block_number.unwrap_or_default();
308321
if block_id.is_pending() {
309322
self.metrics.get_transaction_count.increment(1);
@@ -341,4 +354,73 @@ where
341354
.await
342355
.map_err(Into::into)
343356
}
357+
358+
async fn transaction_by_hash(
359+
&self,
360+
tx_hash: TxHash,
361+
) -> RpcResult<Option<RpcTransaction<Optimism>>> {
362+
debug!("transaction_by_hash: {:?}", tx_hash);
363+
let tx = EthTransactions::transaction_by_hash(&self.eth_api, tx_hash)
364+
.await
365+
.map_err(Into::into)?;
366+
367+
// Process the result without using map() and transpose()
368+
if let Some(tx_source) = tx {
369+
match tx_source {
370+
TransactionSource::Pool(tx) => {
371+
// Convert the pool transaction
372+
let tx_info = TransactionInfo::default();
373+
Ok(Some(self.transform_tx(tx, tx_info)))
374+
}
375+
TransactionSource::Block {
376+
transaction,
377+
index,
378+
block_hash,
379+
block_number,
380+
base_fee,
381+
} => {
382+
// Convert the block transaction
383+
let tx_info = TransactionInfo {
384+
hash: Some(tx_hash),
385+
index: Some(index),
386+
block_hash: Some(block_hash),
387+
block_number: Some(block_number),
388+
base_fee,
389+
};
390+
Ok(Some(self.transform_tx(transaction, tx_info)))
391+
}
392+
}
393+
} else {
394+
// Handle cache lookup for transactions not found in the main lookup
395+
if let Some(tx) = self.cache.get::<OpTransactionSigned>(&tx_hash.to_string()) {
396+
let sender = self
397+
.cache
398+
.get::<Address>(&format!("tx_sender:{}", tx_hash))
399+
.unwrap();
400+
let block_number = self
401+
.cache
402+
.get::<u64>(&format!("tx_block_number:{}", tx_hash))
403+
.unwrap();
404+
let block = self
405+
.cache
406+
.get::<OpBlock>(&format!("block:{:?}", block_number))
407+
.unwrap();
408+
let index = self
409+
.cache
410+
.get::<u64>(&format!("tx_idx:{}", &tx_hash.to_string()))
411+
.unwrap();
412+
let tx_info = TransactionInfo {
413+
hash: Some(tx.tx_hash()),
414+
block_hash: Some(block.header.hash_slow()),
415+
block_number: Some(block.number),
416+
index: Some(index),
417+
base_fee: block.base_fee_per_gas,
418+
};
419+
let tx = Recovered::new_unchecked(tx, sender);
420+
Ok(Some(self.transform_tx(tx, tx_info)))
421+
} else {
422+
Ok(None)
423+
}
424+
}
425+
}
344426
}

0 commit comments

Comments
 (0)