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.get_fork().map(|f| f.block_number() + 1).unwrap_or(1);
161
162 let first_page = from >= best;
163 let mut last_page = false;
164
165 let mut res: Vec<_> = vec![];
166
167 for n in (to..=from).rev() {
168 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
169 let hashes = traces
170 .into_iter()
171 .rev()
172 .filter(|trace| trace.contains_address(address))
173 .filter_map(|trace| trace.transaction_hash)
174 .unique();
175
176 if res.len() >= page_size {
177 break;
178 }
179
180 res.extend(hashes);
181 }
182
183 if n == to {
184 last_page = true;
185 }
186 }
187
188 self.build_ots_search_transactions(res, first_page, last_page).await
189 }
190
191 pub async fn ots_search_transactions_after(
193 &self,
194 address: Address,
195 block_number: u64,
196 page_size: usize,
197 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
198 node_info!("ots_searchTransactionsAfter");
199
200 let best = self.backend.best_number();
201 let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1);
203 let from = if block_number == 0 { first_block } else { block_number + 1 };
204 let to = best;
205
206 let mut first_page = from >= best;
207 let mut last_page = false;
208
209 let mut res: Vec<_> = vec![];
210
211 for n in from..=to {
212 if n == first_block {
213 last_page = true;
214 }
215
216 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
217 let hashes = traces
218 .into_iter()
219 .rev()
220 .filter(|trace| trace.contains_address(address))
221 .filter_map(|trace| trace.transaction_hash)
222 .unique();
223
224 if res.len() >= page_size {
225 break;
226 }
227
228 res.extend(hashes);
229 }
230
231 if n == to {
232 first_page = true;
233 }
234 }
235
236 res.reverse();
238 self.build_ots_search_transactions(res, first_page, last_page).await
239 }
240
241 pub async fn ots_get_transaction_by_sender_and_nonce(
245 &self,
246 address: Address,
247 nonce: U256,
248 ) -> Result<Option<B256>> {
249 node_info!("ots_getTransactionBySenderAndNonce");
250
251 let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default();
252 let to = self.backend.best_number();
253
254 for n in (from..=to).rev() {
255 if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await {
256 for tx in txs {
257 if U256::from(tx.nonce()) == nonce && tx.from() == address {
258 return Ok(Some(tx.tx_hash()));
259 }
260 }
261 }
262 }
263
264 Ok(None)
265 }
266
267 pub async fn ots_get_contract_creator(&self, addr: Address) -> Result<Option<ContractCreator>> {
270 node_info!("ots_getContractCreator");
271
272 let from = self.get_fork().map(|f| f.block_number()).unwrap_or_default();
273 let to = self.backend.best_number();
274
275 for n in (from..=to).rev() {
277 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
278 for trace in traces.into_iter().rev() {
279 match (trace.trace.action, trace.trace.result) {
280 (
281 Action::Create(CreateAction { from, .. }),
282 Some(TraceOutput::Create(CreateOutput { address, .. })),
283 ) if address == addr => {
284 return Ok(Some(ContractCreator {
285 hash: trace.transaction_hash.unwrap(),
286 creator: from,
287 }));
288 }
289 _ => {}
290 }
291 }
292 }
293 }
294
295 Ok(None)
296 }
297 pub async fn build_ots_block_details(
315 &self,
316 block: AnyRpcBlock,
317 ) -> Result<BlockDetails<alloy_rpc_types::Header<AnyHeader>>> {
318 if block.transactions.is_uncle() {
319 return Err(BlockchainError::DataUnavailable);
320 }
321 let receipts_futs = block
322 .transactions
323 .hashes()
324 .map(|hash| async move { self.transaction_receipt(hash).await });
325
326 let receipts = join_all(receipts_futs)
328 .await
329 .into_iter()
330 .map(|r| match r {
331 Ok(Some(r)) => Ok(r),
332 _ => Err(BlockchainError::DataUnavailable),
333 })
334 .collect::<Result<Vec<_>>>()?;
335
336 let total_fees = receipts.iter().fold(0, |acc, receipt| {
337 acc + (receipt.gas_used() as u128) * receipt.effective_gas_price()
338 });
339
340 let Block { header, uncles, transactions, withdrawals } = block.into_inner();
341
342 let block =
343 OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals };
344
345 Ok(BlockDetails {
346 block,
347 total_fees: U256::from(total_fees),
348 issuance: Default::default(),
350 })
351 }
352
353 pub async fn build_ots_block_tx(
358 &self,
359 mut block: AnyRpcBlock,
360 page: usize,
361 page_size: usize,
362 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
363 if block.transactions.is_uncle() {
364 return Err(BlockchainError::DataUnavailable);
365 }
366
367 block.transactions = match block.transactions() {
368 BlockTransactions::Full(txs) => BlockTransactions::Full(
369 txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
370 ),
371 BlockTransactions::Hashes(txs) => BlockTransactions::Hashes(
372 txs.iter().skip(page * page_size).take(page_size).copied().collect(),
373 ),
374 BlockTransactions::Uncle => unreachable!(),
375 };
376
377 let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash));
378
379 let timestamp = block.header.timestamp();
381
382 let receipts = join_all(receipt_futs.map(|r| async move {
383 if let Ok(Some(r)) = r.await {
384 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
385 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
386 } else {
387 Err(BlockchainError::BlockNotFound)
388 }
389 }))
390 .await
391 .into_iter()
392 .collect::<Result<Vec<_>>>()?;
393
394 let transaction_count = block.transactions().len();
395 let fullblock = OtsBlock { block: block.inner.clone(), transaction_count };
396
397 let ots_block_txs = OtsBlockTransactions { fullblock, receipts };
398
399 Ok(ots_block_txs)
400 }
401
402 pub async fn build_ots_search_transactions(
403 &self,
404 hashes: Vec<B256>,
405 first_page: bool,
406 last_page: bool,
407 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
408 let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await });
409
410 let txs = join_all(txs_futs)
411 .await
412 .into_iter()
413 .map(|t| match t {
414 Ok(Some(t)) => Ok(t.into_inner()),
415 _ => Err(BlockchainError::DataUnavailable),
416 })
417 .collect::<Result<Vec<_>>>()?;
418
419 let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash));
420
421 let receipts = join_all(receipt_futs.map(|r| async {
422 if let Ok(Some(r)) = r.await {
423 let timestamp = if let Some(ts) = r.block_timestamp() {
426 ts
427 } else {
428 let block = self.block_by_number(r.block_number().unwrap().into()).await?;
429 block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp()
430 };
431 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
432 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
433 } else {
434 Err(BlockchainError::BlockNotFound)
435 }
436 }))
437 .await
438 .into_iter()
439 .collect::<Result<Vec<_>>>()?;
440
441 Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page })
442 }
443}