1use crate::eth::{
2 EthApi,
3 error::{BlockchainError, Result},
4 macros::node_info,
5};
6use alloy_consensus::{BlockHeader, Transaction as TransactionTrait};
7use alloy_network::{
8 AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, BlockResponse,
9 ReceiptResponse, TransactionResponse,
10};
11use alloy_primitives::{Address, B256, Bytes, U256};
12use alloy_rpc_types::{
13 Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions,
14 trace::{
15 otterscan::{
16 BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions,
17 OtsReceipt, OtsSlimBlock, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts,
18 },
19 parity::{Action, CreateAction, CreateOutput, TraceOutput},
20 },
21};
22use foundry_primitives::FoundryNetwork;
23use futures::future::join_all;
24use itertools::Itertools;
25
26impl EthApi<FoundryNetwork> {
27 pub async fn erigon_get_header_by_number(
33 &self,
34 number: BlockNumber,
35 ) -> Result<Option<AnyRpcBlock>> {
36 node_info!("erigon_getHeaderByNumber");
37
38 self.backend.block_by_number(number).await
39 }
40
41 pub async fn ots_get_api_level(&self) -> Result<u64> {
44 node_info!("ots_getApiLevel");
45
46 Ok(8)
48 }
49
50 pub async fn ots_get_internal_operations(&self, hash: B256) -> Result<Vec<InternalOperation>> {
53 node_info!("ots_getInternalOperations");
54
55 self.backend
56 .mined_transaction(hash)
57 .map(|tx| tx.ots_internal_operations())
58 .ok_or_else(|| BlockchainError::DataUnavailable)
59 }
60
61 pub async fn ots_has_code(&self, address: Address, block_number: BlockNumber) -> Result<bool> {
63 node_info!("ots_hasCode");
64 let block_id = Some(BlockId::Number(block_number));
65 Ok(!self.get_code(address, block_id).await?.is_empty())
66 }
67
68 pub async fn ots_trace_transaction(&self, hash: B256) -> Result<Vec<TraceEntry>> {
73 node_info!("ots_traceTransaction");
74 let traces = self
75 .backend
76 .trace_transaction(hash)
77 .await?
78 .into_iter()
79 .filter_map(|trace| TraceEntry::from_transaction_trace(&trace.trace))
80 .collect();
81 Ok(traces)
82 }
83
84 pub async fn ots_get_transaction_error(&self, hash: B256) -> Result<Bytes> {
86 node_info!("ots_getTransactionError");
87
88 if let Some(receipt) = self.backend.mined_transaction_receipt(hash)
89 && !receipt.inner.as_ref().status()
90 {
91 return Ok(receipt.out.unwrap_or_default());
92 }
93
94 Ok(Bytes::default())
95 }
96
97 pub async fn ots_get_block_details(
101 &self,
102 number: BlockNumber,
103 ) -> Result<BlockDetails<AnyRpcHeader>> {
104 node_info!("ots_getBlockDetails");
105
106 if let Some(block) = self.backend.block_by_number(number).await? {
107 let ots_block = self.build_ots_block_details(block).await?;
108 Ok(ots_block)
109 } else {
110 Err(BlockchainError::BlockNotFound)
111 }
112 }
113
114 pub async fn ots_get_block_details_by_hash(
118 &self,
119 hash: B256,
120 ) -> Result<BlockDetails<AnyRpcHeader>> {
121 node_info!("ots_getBlockDetailsByHash");
122
123 if let Some(block) = self.backend.block_by_hash(hash).await? {
124 let ots_block = self.build_ots_block_details(block).await?;
125 Ok(ots_block)
126 } else {
127 Err(BlockchainError::BlockNotFound)
128 }
129 }
130
131 pub async fn ots_get_block_transactions(
134 &self,
135 number: u64,
136 page: usize,
137 page_size: usize,
138 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
139 node_info!("ots_getBlockTransactions");
140
141 match self.backend.block_by_number_full(number.into()).await? {
142 Some(block) => self.build_ots_block_tx(block, page, page_size).await,
143 None => Err(BlockchainError::BlockNotFound),
144 }
145 }
146
147 pub async fn ots_search_transactions_before(
149 &self,
150 address: Address,
151 block_number: u64,
152 page_size: usize,
153 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
154 node_info!("ots_searchTransactionsBefore");
155
156 let best = self.backend.best_number();
157 let from = if block_number == 0 { best } else { block_number - 1 };
160 let to = self
161 .get_fork()
162 .map(|f| f.block_number() + 1)
163 .unwrap_or_else(|| self.backend.genesis_number() + 1);
164
165 let first_page = from >= best;
166 let mut last_page = false;
167
168 let mut res: Vec<_> = vec![];
169
170 for n in (to..=from).rev() {
171 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
172 let hashes = traces
173 .into_iter()
174 .rev()
175 .filter(|trace| trace.contains_address(address))
176 .filter_map(|trace| trace.transaction_hash)
177 .unique();
178
179 if res.len() >= page_size {
180 break;
181 }
182
183 res.extend(hashes);
184 }
185
186 if n == to {
187 last_page = true;
188 }
189 }
190
191 self.build_ots_search_transactions(res, first_page, last_page).await
192 }
193
194 pub async fn ots_search_transactions_after(
196 &self,
197 address: Address,
198 block_number: u64,
199 page_size: usize,
200 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
201 node_info!("ots_searchTransactionsAfter");
202
203 let best = self.backend.best_number();
204 let first_block = self
206 .get_fork()
207 .map(|f| f.block_number() + 1)
208 .unwrap_or_else(|| self.backend.genesis_number() + 1);
209 let from = if block_number == 0 { first_block } else { block_number + 1 };
210 let to = best;
211
212 let mut first_page = from >= best;
213 let mut last_page = false;
214
215 let mut res: Vec<_> = vec![];
216
217 for n in from..=to {
218 if n == first_block {
219 last_page = true;
220 }
221
222 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
223 let hashes = traces
224 .into_iter()
225 .rev()
226 .filter(|trace| trace.contains_address(address))
227 .filter_map(|trace| trace.transaction_hash)
228 .unique();
229
230 if res.len() >= page_size {
231 break;
232 }
233
234 res.extend(hashes);
235 }
236
237 if n == to {
238 first_page = true;
239 }
240 }
241
242 res.reverse();
244 self.build_ots_search_transactions(res, first_page, last_page).await
245 }
246
247 pub async fn ots_get_transaction_by_sender_and_nonce(
251 &self,
252 address: Address,
253 nonce: U256,
254 ) -> Result<Option<B256>> {
255 node_info!("ots_getTransactionBySenderAndNonce");
256
257 let from = self
258 .get_fork()
259 .map(|f| f.block_number() + 1)
260 .unwrap_or_else(|| self.backend.genesis_number() + 1);
261 let to = self.backend.best_number();
262
263 for n in (from..=to).rev() {
264 if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await {
265 for tx in txs {
266 if U256::from(tx.nonce()) == nonce && tx.from() == address {
267 return Ok(Some(tx.tx_hash()));
268 }
269 }
270 }
271 }
272
273 Ok(None)
274 }
275
276 pub async fn ots_get_contract_creator(&self, addr: Address) -> Result<Option<ContractCreator>> {
279 node_info!("ots_getContractCreator");
280
281 let from = self.get_fork().map(|f| f.block_number()).unwrap_or_default();
282 let to = self.backend.best_number();
283
284 for n in (from..=to).rev() {
286 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
287 for trace in traces.into_iter().rev() {
288 match (trace.trace.action, trace.trace.result) {
289 (
290 Action::Create(CreateAction { from, .. }),
291 Some(TraceOutput::Create(CreateOutput { address, .. })),
292 ) if address == addr => {
293 return Ok(Some(ContractCreator {
294 hash: trace.transaction_hash.unwrap(),
295 creator: from,
296 }));
297 }
298 _ => {}
299 }
300 }
301 }
302 }
303
304 Ok(None)
305 }
306 pub async fn build_ots_block_details(
324 &self,
325 block: AnyRpcBlock,
326 ) -> Result<BlockDetails<alloy_rpc_types::Header<AnyHeader>>> {
327 if block.transactions.is_uncle() {
328 return Err(BlockchainError::DataUnavailable);
329 }
330 let receipts_futs = block
331 .transactions
332 .hashes()
333 .map(|hash| async move { self.transaction_receipt(hash).await });
334
335 let receipts = join_all(receipts_futs)
337 .await
338 .into_iter()
339 .map(|r| match r {
340 Ok(Some(r)) => Ok(r),
341 _ => Err(BlockchainError::DataUnavailable),
342 })
343 .collect::<Result<Vec<_>>>()?;
344
345 let total_fees = receipts.iter().fold(0, |acc, receipt| {
346 acc + (receipt.gas_used() as u128) * receipt.effective_gas_price()
347 });
348
349 let Block { header, uncles, transactions, withdrawals } = block.into_inner();
350
351 let block =
352 OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals };
353
354 Ok(BlockDetails {
355 block,
356 total_fees: U256::from(total_fees),
357 issuance: Default::default(),
359 })
360 }
361
362 pub async fn build_ots_block_tx(
367 &self,
368 mut block: AnyRpcBlock,
369 page: usize,
370 page_size: usize,
371 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
372 if block.transactions.is_uncle() {
373 return Err(BlockchainError::DataUnavailable);
374 }
375
376 block.transactions = match block.transactions() {
377 BlockTransactions::Full(txs) => BlockTransactions::Full(
378 txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
379 ),
380 BlockTransactions::Hashes(txs) => BlockTransactions::Hashes(
381 txs.iter().skip(page * page_size).take(page_size).copied().collect(),
382 ),
383 BlockTransactions::Uncle => unreachable!(),
384 };
385
386 let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash));
387
388 let timestamp = block.header.timestamp();
390
391 let receipts = join_all(receipt_futs.map(|r| async move {
392 if let Ok(Some(r)) = r.await {
393 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
394 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
395 } else {
396 Err(BlockchainError::BlockNotFound)
397 }
398 }))
399 .await
400 .into_iter()
401 .collect::<Result<Vec<_>>>()?;
402
403 let transaction_count = block.transactions().len();
404 let fullblock = OtsBlock { block: block.inner.clone(), transaction_count };
405
406 let ots_block_txs = OtsBlockTransactions { fullblock, receipts };
407
408 Ok(ots_block_txs)
409 }
410
411 pub async fn build_ots_search_transactions(
412 &self,
413 hashes: Vec<B256>,
414 first_page: bool,
415 last_page: bool,
416 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
417 let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await });
418
419 let txs = join_all(txs_futs)
420 .await
421 .into_iter()
422 .map(|t| match t {
423 Ok(Some(t)) => Ok(t.into_inner()),
424 _ => Err(BlockchainError::DataUnavailable),
425 })
426 .collect::<Result<Vec<_>>>()?;
427
428 let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash));
429
430 let receipts = join_all(receipt_futs.map(|r| async {
431 if let Ok(Some(r)) = r.await {
432 let timestamp = if let Some(ts) = r.block_timestamp() {
435 ts
436 } else {
437 let block = self.block_by_number(r.block_number().unwrap().into()).await?;
438 block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp()
439 };
440 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
441 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
442 } else {
443 Err(BlockchainError::BlockNotFound)
444 }
445 }))
446 .await
447 .into_iter()
448 .collect::<Result<Vec<_>>>()?;
449
450 Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page })
451 }
452}