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 .unwrap_or_default()
79 .into_iter()
80 .filter_map(|trace| TraceEntry::from_transaction_trace(&trace.trace))
81 .collect();
82 Ok(traces)
83 }
84
85 pub async fn ots_get_transaction_error(&self, hash: B256) -> Result<Bytes> {
87 node_info!("ots_getTransactionError");
88
89 if let Some(receipt) = self.backend.mined_transaction_receipt(hash)
90 && !receipt.inner.as_ref().status()
91 {
92 return Ok(receipt.out.unwrap_or_default());
93 }
94
95 Ok(Bytes::default())
96 }
97
98 pub async fn ots_get_block_details(
102 &self,
103 number: BlockNumber,
104 ) -> Result<BlockDetails<AnyRpcHeader>> {
105 node_info!("ots_getBlockDetails");
106
107 if let Some(block) = self.backend.block_by_number(number).await? {
108 let ots_block = self.build_ots_block_details(block).await?;
109 Ok(ots_block)
110 } else {
111 Err(BlockchainError::BlockNotFound)
112 }
113 }
114
115 pub async fn ots_get_block_details_by_hash(
119 &self,
120 hash: B256,
121 ) -> Result<BlockDetails<AnyRpcHeader>> {
122 node_info!("ots_getBlockDetailsByHash");
123
124 if let Some(block) = self.backend.block_by_hash(hash).await? {
125 let ots_block = self.build_ots_block_details(block).await?;
126 Ok(ots_block)
127 } else {
128 Err(BlockchainError::BlockNotFound)
129 }
130 }
131
132 pub async fn ots_get_block_transactions(
135 &self,
136 number: u64,
137 page: usize,
138 page_size: usize,
139 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
140 node_info!("ots_getBlockTransactions");
141
142 match self.backend.block_by_number_full(number.into()).await? {
143 Some(block) => self.build_ots_block_tx(block, page, page_size).await,
144 None => Err(BlockchainError::BlockNotFound),
145 }
146 }
147
148 pub async fn ots_search_transactions_before(
150 &self,
151 address: Address,
152 block_number: u64,
153 page_size: usize,
154 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
155 node_info!("ots_searchTransactionsBefore");
156
157 let best = self.backend.best_number();
158 let from = if block_number == 0 { best } else { block_number - 1 };
161 let to = self
162 .get_fork()
163 .map(|f| f.block_number() + 1)
164 .unwrap_or_else(|| self.backend.genesis_number() + 1);
165
166 let first_page = from >= best;
167 let mut last_page = false;
168
169 let mut res: Vec<_> = vec![];
170
171 for n in (to..=from).rev() {
172 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
173 let hashes = traces
174 .into_iter()
175 .rev()
176 .filter(|trace| trace.contains_address(address))
177 .filter_map(|trace| trace.transaction_hash)
178 .unique();
179
180 if res.len() >= page_size {
181 break;
182 }
183
184 res.extend(hashes);
185 }
186
187 if n == to {
188 last_page = true;
189 }
190 }
191
192 self.build_ots_search_transactions(res, first_page, last_page).await
193 }
194
195 pub async fn ots_search_transactions_after(
197 &self,
198 address: Address,
199 block_number: u64,
200 page_size: usize,
201 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
202 node_info!("ots_searchTransactionsAfter");
203
204 let best = self.backend.best_number();
205 let first_block = self
207 .get_fork()
208 .map(|f| f.block_number() + 1)
209 .unwrap_or_else(|| self.backend.genesis_number() + 1);
210 let from = if block_number == 0 { first_block } else { block_number + 1 };
211 let to = best;
212
213 let mut first_page = from >= best;
214 let mut last_page = false;
215
216 let mut res: Vec<_> = vec![];
217
218 for n in from..=to {
219 if n == first_block {
220 last_page = true;
221 }
222
223 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
224 let hashes = traces
225 .into_iter()
226 .rev()
227 .filter(|trace| trace.contains_address(address))
228 .filter_map(|trace| trace.transaction_hash)
229 .unique();
230
231 if res.len() >= page_size {
232 break;
233 }
234
235 res.extend(hashes);
236 }
237
238 if n == to {
239 first_page = true;
240 }
241 }
242
243 res.reverse();
245 self.build_ots_search_transactions(res, first_page, last_page).await
246 }
247
248 pub async fn ots_get_transaction_by_sender_and_nonce(
252 &self,
253 address: Address,
254 nonce: U256,
255 ) -> Result<Option<B256>> {
256 node_info!("ots_getTransactionBySenderAndNonce");
257
258 let from = self
259 .get_fork()
260 .map(|f| f.block_number() + 1)
261 .unwrap_or_else(|| self.backend.genesis_number() + 1);
262 let to = self.backend.best_number();
263
264 for n in (from..=to).rev() {
265 if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await {
266 for tx in txs {
267 if U256::from(tx.nonce()) == nonce && tx.from() == address {
268 return Ok(Some(tx.tx_hash()));
269 }
270 }
271 }
272 }
273
274 Ok(None)
275 }
276
277 pub async fn ots_get_contract_creator(&self, addr: Address) -> Result<Option<ContractCreator>> {
280 node_info!("ots_getContractCreator");
281
282 let from = self
283 .get_fork()
284 .map(|f| f.block_number())
285 .unwrap_or_else(|| self.backend.genesis_number());
286 let to = self.backend.best_number();
287
288 for n in (from..=to).rev() {
290 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
291 for trace in traces.into_iter().rev() {
292 match (trace.trace.action, trace.trace.result) {
293 (
294 Action::Create(CreateAction { from, .. }),
295 Some(TraceOutput::Create(CreateOutput { address, .. })),
296 ) if address == addr => {
297 return Ok(Some(ContractCreator {
298 hash: trace.transaction_hash.unwrap(),
299 creator: from,
300 }));
301 }
302 _ => {}
303 }
304 }
305 }
306 }
307
308 Ok(None)
309 }
310 pub async fn build_ots_block_details(
328 &self,
329 block: AnyRpcBlock,
330 ) -> Result<BlockDetails<alloy_rpc_types::Header<AnyHeader>>> {
331 if block.transactions.is_uncle() {
332 return Err(BlockchainError::DataUnavailable);
333 }
334 let receipts_futs = block
335 .transactions
336 .hashes()
337 .map(|hash| async move { self.transaction_receipt(hash).await });
338
339 let receipts = join_all(receipts_futs)
341 .await
342 .into_iter()
343 .map(|r| match r {
344 Ok(Some(r)) => Ok(r),
345 _ => Err(BlockchainError::DataUnavailable),
346 })
347 .collect::<Result<Vec<_>>>()?;
348
349 let total_fees = receipts.iter().fold(0, |acc, receipt| {
350 acc + (receipt.gas_used() as u128) * receipt.effective_gas_price()
351 });
352
353 let Block { header, uncles, transactions, withdrawals } = block.into_inner();
354
355 let block =
356 OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals };
357
358 Ok(BlockDetails {
359 block,
360 total_fees: U256::from(total_fees),
361 issuance: Default::default(),
363 })
364 }
365
366 pub async fn build_ots_block_tx(
371 &self,
372 mut block: AnyRpcBlock,
373 page: usize,
374 page_size: usize,
375 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
376 if block.transactions.is_uncle() {
377 return Err(BlockchainError::DataUnavailable);
378 }
379
380 let transaction_count = block.transactions().len();
382
383 block.transactions = match block.transactions() {
384 BlockTransactions::Full(txs) => BlockTransactions::Full(
385 txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
386 ),
387 BlockTransactions::Hashes(txs) => BlockTransactions::Hashes(
388 txs.iter().skip(page * page_size).take(page_size).copied().collect(),
389 ),
390 BlockTransactions::Uncle => unreachable!(),
391 };
392
393 let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash));
394
395 let timestamp = block.header.timestamp();
397
398 let receipts = join_all(receipt_futs.map(|r| async move {
399 if let Ok(Some(r)) = r.await {
400 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
401 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
402 } else {
403 Err(BlockchainError::BlockNotFound)
404 }
405 }))
406 .await
407 .into_iter()
408 .collect::<Result<Vec<_>>>()?;
409
410 let fullblock = OtsBlock { block: block.inner.clone(), transaction_count };
411
412 let ots_block_txs = OtsBlockTransactions { fullblock, receipts };
413
414 Ok(ots_block_txs)
415 }
416
417 pub async fn build_ots_search_transactions(
418 &self,
419 hashes: Vec<B256>,
420 first_page: bool,
421 last_page: bool,
422 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
423 let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await });
424
425 let txs = join_all(txs_futs)
426 .await
427 .into_iter()
428 .map(|t| match t {
429 Ok(Some(t)) => Ok(t.into_inner()),
430 _ => Err(BlockchainError::DataUnavailable),
431 })
432 .collect::<Result<Vec<_>>>()?;
433
434 let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash));
435
436 let receipts = join_all(receipt_futs.map(|r| async {
437 if let Ok(Some(r)) = r.await {
438 let timestamp = if let Some(ts) = r.block_timestamp() {
441 ts
442 } else {
443 let block = self.block_by_number(r.block_number().unwrap().into()).await?;
444 block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp()
445 };
446 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
447 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
448 } else {
449 Err(BlockchainError::BlockNotFound)
450 }
451 }))
452 .await
453 .into_iter()
454 .collect::<Result<Vec<_>>>()?;
455
456 Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page })
457 }
458}