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.get_fork().map(|f| f.block_number()).unwrap_or_default();
283 let to = self.backend.best_number();
284
285 for n in (from..=to).rev() {
287 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
288 for trace in traces.into_iter().rev() {
289 match (trace.trace.action, trace.trace.result) {
290 (
291 Action::Create(CreateAction { from, .. }),
292 Some(TraceOutput::Create(CreateOutput { address, .. })),
293 ) if address == addr => {
294 return Ok(Some(ContractCreator {
295 hash: trace.transaction_hash.unwrap(),
296 creator: from,
297 }));
298 }
299 _ => {}
300 }
301 }
302 }
303 }
304
305 Ok(None)
306 }
307 pub async fn build_ots_block_details(
325 &self,
326 block: AnyRpcBlock,
327 ) -> Result<BlockDetails<alloy_rpc_types::Header<AnyHeader>>> {
328 if block.transactions.is_uncle() {
329 return Err(BlockchainError::DataUnavailable);
330 }
331 let receipts_futs = block
332 .transactions
333 .hashes()
334 .map(|hash| async move { self.transaction_receipt(hash).await });
335
336 let receipts = join_all(receipts_futs)
338 .await
339 .into_iter()
340 .map(|r| match r {
341 Ok(Some(r)) => Ok(r),
342 _ => Err(BlockchainError::DataUnavailable),
343 })
344 .collect::<Result<Vec<_>>>()?;
345
346 let total_fees = receipts.iter().fold(0, |acc, receipt| {
347 acc + (receipt.gas_used() as u128) * receipt.effective_gas_price()
348 });
349
350 let Block { header, uncles, transactions, withdrawals } = block.into_inner();
351
352 let block =
353 OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals };
354
355 Ok(BlockDetails {
356 block,
357 total_fees: U256::from(total_fees),
358 issuance: Default::default(),
360 })
361 }
362
363 pub async fn build_ots_block_tx(
368 &self,
369 mut block: AnyRpcBlock,
370 page: usize,
371 page_size: usize,
372 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
373 if block.transactions.is_uncle() {
374 return Err(BlockchainError::DataUnavailable);
375 }
376
377 let transaction_count = block.transactions().len();
379
380 block.transactions = match block.transactions() {
381 BlockTransactions::Full(txs) => BlockTransactions::Full(
382 txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
383 ),
384 BlockTransactions::Hashes(txs) => BlockTransactions::Hashes(
385 txs.iter().skip(page * page_size).take(page_size).copied().collect(),
386 ),
387 BlockTransactions::Uncle => unreachable!(),
388 };
389
390 let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash));
391
392 let timestamp = block.header.timestamp();
394
395 let receipts = join_all(receipt_futs.map(|r| async move {
396 if let Ok(Some(r)) = r.await {
397 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
398 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
399 } else {
400 Err(BlockchainError::BlockNotFound)
401 }
402 }))
403 .await
404 .into_iter()
405 .collect::<Result<Vec<_>>>()?;
406
407 let fullblock = OtsBlock { block: block.inner.clone(), transaction_count };
408
409 let ots_block_txs = OtsBlockTransactions { fullblock, receipts };
410
411 Ok(ots_block_txs)
412 }
413
414 pub async fn build_ots_search_transactions(
415 &self,
416 hashes: Vec<B256>,
417 first_page: bool,
418 last_page: bool,
419 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
420 let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await });
421
422 let txs = join_all(txs_futs)
423 .await
424 .into_iter()
425 .map(|t| match t {
426 Ok(Some(t)) => Ok(t.into_inner()),
427 _ => Err(BlockchainError::DataUnavailable),
428 })
429 .collect::<Result<Vec<_>>>()?;
430
431 let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash));
432
433 let receipts = join_all(receipt_futs.map(|r| async {
434 if let Ok(Some(r)) = r.await {
435 let timestamp = if let Some(ts) = r.block_timestamp() {
438 ts
439 } else {
440 let block = self.block_by_number(r.block_number().unwrap().into()).await?;
441 block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp()
442 };
443 let receipt = r.as_ref().inner.clone().map_inner(OtsReceipt::from);
444 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
445 } else {
446 Err(BlockchainError::BlockNotFound)
447 }
448 }))
449 .await
450 .into_iter()
451 .collect::<Result<Vec<_>>>()?;
452
453 Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page })
454 }
455}