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