1use crate::eth::{
2 error::{BlockchainError, Result},
3 macros::node_info,
4 EthApi,
5};
6use alloy_consensus::Transaction as TransactionTrait;
7use alloy_network::{
8 AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, BlockResponse,
9 TransactionResponse,
10};
11use alloy_primitives::{Address, Bytes, B256, U256};
12use alloy_rpc_types::{
13 trace::{
14 otterscan::{
15 BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions,
16 OtsReceipt, OtsSlimBlock, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts,
17 },
18 parity::{
19 Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace,
20 RewardAction, TraceOutput,
21 },
22 },
23 Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions,
24};
25use itertools::Itertools;
26
27use futures::future::join_all;
28
29pub fn mentions_address(trace: LocalizedTransactionTrace, address: Address) -> Option<B256> {
30 match (trace.trace.action, trace.trace.result) {
31 (Action::Call(CallAction { from, to, .. }), _) if from == address || to == address => {
32 trace.transaction_hash
33 }
34 (_, Some(TraceOutput::Create(CreateOutput { address: created_address, .. })))
35 if created_address == address =>
36 {
37 trace.transaction_hash
38 }
39 (Action::Create(CreateAction { from, .. }), _) if from == address => trace.transaction_hash,
40 (Action::Reward(RewardAction { author, .. }), _) if author == address => {
41 trace.transaction_hash
42 }
43 _ => None,
44 }
45}
46
47pub fn batch_build_ots_traces(traces: Vec<LocalizedTransactionTrace>) -> Vec<TraceEntry> {
51 traces
52 .into_iter()
53 .filter_map(|trace| {
54 let output = trace
55 .trace
56 .result
57 .map(|r| match r {
58 TraceOutput::Call(output) => output.output,
59 TraceOutput::Create(output) => output.code,
60 })
61 .unwrap_or_default();
62 match trace.trace.action {
63 Action::Call(call) => Some(TraceEntry {
64 r#type: match call.call_type {
65 CallType::Call => "CALL",
66 CallType::CallCode => "CALLCODE",
67 CallType::DelegateCall => "DELEGATECALL",
68 CallType::StaticCall => "STATICCALL",
69 CallType::AuthCall => "AUTHCALL",
70 CallType::None => "NONE",
71 }
72 .to_string(),
73 depth: trace.trace.trace_address.len() as u32,
74 from: call.from,
75 to: call.to,
76 value: call.value,
77 input: call.input,
78 output,
79 }),
80 Action::Create(_) | Action::Selfdestruct(_) | Action::Reward(_) => None,
81 }
82 })
83 .collect()
84}
85
86impl EthApi {
87 pub async fn erigon_get_header_by_number(
93 &self,
94 number: BlockNumber,
95 ) -> Result<Option<AnyRpcBlock>> {
96 node_info!("ots_getApiLevel");
97
98 self.backend.block_by_number(number).await
99 }
100
101 pub async fn ots_get_api_level(&self) -> Result<u64> {
104 node_info!("ots_getApiLevel");
105
106 Ok(8)
108 }
109
110 pub async fn ots_get_internal_operations(&self, hash: B256) -> Result<Vec<InternalOperation>> {
113 node_info!("ots_getInternalOperations");
114
115 self.backend
116 .mined_transaction(hash)
117 .map(|tx| tx.ots_internal_operations())
118 .ok_or_else(|| BlockchainError::DataUnavailable)
119 }
120
121 pub async fn ots_has_code(&self, address: Address, block_number: BlockNumber) -> Result<bool> {
123 node_info!("ots_hasCode");
124 let block_id = Some(BlockId::Number(block_number));
125 Ok(!self.get_code(address, block_id).await?.is_empty())
126 }
127
128 pub async fn ots_trace_transaction(&self, hash: B256) -> Result<Vec<TraceEntry>> {
130 node_info!("ots_traceTransaction");
131
132 Ok(batch_build_ots_traces(self.backend.trace_transaction(hash).await?))
133 }
134
135 pub async fn ots_get_transaction_error(&self, hash: B256) -> Result<Bytes> {
137 node_info!("ots_getTransactionError");
138
139 if let Some(receipt) = self.backend.mined_transaction_receipt(hash) {
140 if !receipt.inner.inner.as_receipt_with_bloom().receipt.status.coerce_status() {
141 return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default()));
142 }
143 }
144
145 Ok(Bytes::default())
146 }
147
148 pub async fn ots_get_block_details(
152 &self,
153 number: BlockNumber,
154 ) -> Result<BlockDetails<AnyRpcHeader>> {
155 node_info!("ots_getBlockDetails");
156
157 if let Some(block) = self.backend.block_by_number(number).await? {
158 let ots_block = self.build_ots_block_details(block).await?;
159 Ok(ots_block)
160 } else {
161 Err(BlockchainError::BlockNotFound)
162 }
163 }
164
165 pub async fn ots_get_block_details_by_hash(
169 &self,
170 hash: B256,
171 ) -> Result<BlockDetails<AnyRpcHeader>> {
172 node_info!("ots_getBlockDetailsByHash");
173
174 if let Some(block) = self.backend.block_by_hash(hash).await? {
175 let ots_block = self.build_ots_block_details(block).await?;
176 Ok(ots_block)
177 } else {
178 Err(BlockchainError::BlockNotFound)
179 }
180 }
181
182 pub async fn ots_get_block_transactions(
185 &self,
186 number: u64,
187 page: usize,
188 page_size: usize,
189 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
190 node_info!("ots_getBlockTransactions");
191
192 match self.backend.block_by_number_full(number.into()).await? {
193 Some(block) => self.build_ots_block_tx(block, page, page_size).await,
194 None => Err(BlockchainError::BlockNotFound),
195 }
196 }
197
198 pub async fn ots_search_transactions_before(
200 &self,
201 address: Address,
202 block_number: u64,
203 page_size: usize,
204 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
205 node_info!("ots_searchTransactionsBefore");
206
207 let best = self.backend.best_number();
208 let from = if block_number == 0 { best } else { block_number - 1 };
211 let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1);
212
213 let first_page = from >= best;
214 let mut last_page = false;
215
216 let mut res: Vec<_> = vec![];
217
218 for n in (to..=from).rev() {
219 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
220 let hashes = traces
221 .into_iter()
222 .rev()
223 .filter_map(|trace| mentions_address(trace, address))
224 .unique();
225
226 if res.len() >= page_size {
227 break;
228 }
229
230 res.extend(hashes);
231 }
232
233 if n == to {
234 last_page = true;
235 }
236 }
237
238 self.build_ots_search_transactions(res, first_page, last_page).await
239 }
240
241 pub async fn ots_search_transactions_after(
243 &self,
244 address: Address,
245 block_number: u64,
246 page_size: usize,
247 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
248 node_info!("ots_searchTransactionsAfter");
249
250 let best = self.backend.best_number();
251 let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1);
253 let from = if block_number == 0 { first_block } else { block_number + 1 };
254 let to = best;
255
256 let mut first_page = from >= best;
257 let mut last_page = false;
258
259 let mut res: Vec<_> = vec![];
260
261 for n in from..=to {
262 if n == first_block {
263 last_page = true;
264 }
265
266 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
267 let hashes = traces
268 .into_iter()
269 .rev()
270 .filter_map(|trace| mentions_address(trace, address))
271 .unique();
272
273 if res.len() >= page_size {
274 break;
275 }
276
277 res.extend(hashes);
278 }
279
280 if n == to {
281 first_page = true;
282 }
283 }
284
285 res.reverse();
287 self.build_ots_search_transactions(res, first_page, last_page).await
288 }
289
290 pub async fn ots_get_transaction_by_sender_and_nonce(
294 &self,
295 address: Address,
296 nonce: U256,
297 ) -> Result<Option<B256>> {
298 node_info!("ots_getTransactionBySenderAndNonce");
299
300 let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default();
301 let to = self.backend.best_number();
302
303 for n in (from..=to).rev() {
304 if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await {
305 for tx in txs {
306 if U256::from(tx.nonce()) == nonce && tx.from() == address {
307 return Ok(Some(tx.tx_hash()));
308 }
309 }
310 }
311 }
312
313 Ok(None)
314 }
315
316 pub async fn ots_get_contract_creator(&self, addr: Address) -> Result<Option<ContractCreator>> {
319 node_info!("ots_getContractCreator");
320
321 let from = self.get_fork().map(|f| f.block_number()).unwrap_or_default();
322 let to = self.backend.best_number();
323
324 for n in (from..=to).rev() {
326 if let Some(traces) = self.backend.mined_parity_trace_block(n) {
327 for trace in traces.into_iter().rev() {
328 match (trace.trace.action, trace.trace.result) {
329 (
330 Action::Create(CreateAction { from, .. }),
331 Some(TraceOutput::Create(CreateOutput { address, .. })),
332 ) if address == addr => {
333 return Ok(Some(ContractCreator {
334 hash: trace.transaction_hash.unwrap(),
335 creator: from,
336 }));
337 }
338 _ => {}
339 }
340 }
341 }
342 }
343
344 Ok(None)
345 }
346 pub async fn build_ots_block_details(
364 &self,
365 block: AnyRpcBlock,
366 ) -> Result<BlockDetails<alloy_rpc_types::Header<AnyHeader>>> {
367 if block.transactions.is_uncle() {
368 return Err(BlockchainError::DataUnavailable);
369 }
370 let receipts_futs = block
371 .transactions
372 .hashes()
373 .map(|hash| async move { self.transaction_receipt(hash).await });
374
375 let receipts = join_all(receipts_futs)
377 .await
378 .into_iter()
379 .map(|r| match r {
380 Ok(Some(r)) => Ok(r),
381 _ => Err(BlockchainError::DataUnavailable),
382 })
383 .collect::<Result<Vec<_>>>()?;
384
385 let total_fees = receipts
386 .iter()
387 .fold(0, |acc, receipt| acc + (receipt.gas_used as u128) * receipt.effective_gas_price);
388
389 let Block { header, uncles, transactions, withdrawals } = block.into_inner();
390
391 let block =
392 OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals };
393
394 Ok(BlockDetails {
395 block,
396 total_fees: U256::from(total_fees),
397 issuance: Default::default(),
399 })
400 }
401
402 pub async fn build_ots_block_tx(
407 &self,
408 mut block: AnyRpcBlock,
409 page: usize,
410 page_size: usize,
411 ) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
412 if block.transactions.is_uncle() {
413 return Err(BlockchainError::DataUnavailable);
414 }
415
416 block.transactions = match block.transactions() {
417 BlockTransactions::Full(txs) => BlockTransactions::Full(
418 txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
419 ),
420 BlockTransactions::Hashes(txs) => BlockTransactions::Hashes(
421 txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
422 ),
423 BlockTransactions::Uncle => unreachable!(),
424 };
425
426 let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash));
427
428 let receipts = join_all(receipt_futs.map(|r| async {
429 if let Ok(Some(r)) = r.await {
430 let block = self.block_by_number(r.block_number.unwrap().into()).await?;
431 let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp;
432 let receipt = r.map_inner(OtsReceipt::from);
433 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
434 } else {
435 Err(BlockchainError::BlockNotFound)
436 }
437 }))
438 .await
439 .into_iter()
440 .collect::<Result<Vec<_>>>()?;
441
442 let transaction_count = block.transactions().len();
443 let fullblock = OtsBlock { block: block.inner.clone(), transaction_count };
444
445 let ots_block_txs = OtsBlockTransactions { fullblock, receipts };
446
447 Ok(ots_block_txs)
448 }
449
450 pub async fn build_ots_search_transactions(
451 &self,
452 hashes: Vec<B256>,
453 first_page: bool,
454 last_page: bool,
455 ) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
456 let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await });
457
458 let txs = join_all(txs_futs)
459 .await
460 .into_iter()
461 .map(|t| match t {
462 Ok(Some(t)) => Ok(t.into_inner()),
463 _ => Err(BlockchainError::DataUnavailable),
464 })
465 .collect::<Result<Vec<_>>>()?;
466
467 let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash));
468
469 let receipts = join_all(receipt_futs.map(|r| async {
470 if let Ok(Some(r)) = r.await {
471 let block = self.block_by_number(r.block_number.unwrap().into()).await?;
472 let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp;
473 let receipt = r.map_inner(OtsReceipt::from);
474 Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
475 } else {
476 Err(BlockchainError::BlockNotFound)
477 }
478 }))
479 .await
480 .into_iter()
481 .collect::<Result<Vec<_>>>()?;
482
483 Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page })
484 }
485}