use crate::eth::{
error::{BlockchainError, Result},
macros::node_info,
EthApi,
};
use alloy_consensus::Transaction as TransactionTrait;
use alloy_network::{
AnyHeader, AnyRpcBlock, AnyRpcHeader, AnyRpcTransaction, AnyTxEnvelope, BlockResponse,
TransactionResponse,
};
use alloy_primitives::{Address, Bytes, B256, U256};
use alloy_rpc_types::{
trace::{
otterscan::{
BlockDetails, ContractCreator, InternalOperation, OtsBlock, OtsBlockTransactions,
OtsReceipt, OtsSlimBlock, OtsTransactionReceipt, TraceEntry, TransactionsWithReceipts,
},
parity::{
Action, CallAction, CallType, CreateAction, CreateOutput, LocalizedTransactionTrace,
RewardAction, TraceOutput,
},
},
Block, BlockId, BlockNumberOrTag as BlockNumber, BlockTransactions,
};
use itertools::Itertools;
use futures::future::join_all;
pub fn mentions_address(trace: LocalizedTransactionTrace, address: Address) -> Option<B256> {
match (trace.trace.action, trace.trace.result) {
(Action::Call(CallAction { from, to, .. }), _) if from == address || to == address => {
trace.transaction_hash
}
(_, Some(TraceOutput::Create(CreateOutput { address: created_address, .. })))
if created_address == address =>
{
trace.transaction_hash
}
(Action::Create(CreateAction { from, .. }), _) if from == address => trace.transaction_hash,
(Action::Reward(RewardAction { author, .. }), _) if author == address => {
trace.transaction_hash
}
_ => None,
}
}
pub fn batch_build_ots_traces(traces: Vec<LocalizedTransactionTrace>) -> Vec<TraceEntry> {
traces
.into_iter()
.filter_map(|trace| {
let output = trace
.trace
.result
.map(|r| match r {
TraceOutput::Call(output) => output.output,
TraceOutput::Create(output) => output.code,
})
.unwrap_or_default();
match trace.trace.action {
Action::Call(call) => Some(TraceEntry {
r#type: match call.call_type {
CallType::Call => "CALL",
CallType::CallCode => "CALLCODE",
CallType::DelegateCall => "DELEGATECALL",
CallType::StaticCall => "STATICCALL",
CallType::AuthCall => "AUTHCALL",
CallType::None => "NONE",
}
.to_string(),
depth: trace.trace.trace_address.len() as u32,
from: call.from,
to: call.to,
value: call.value,
input: call.input,
output,
}),
Action::Create(_) | Action::Selfdestruct(_) | Action::Reward(_) => None,
}
})
.collect()
}
impl EthApi {
pub async fn erigon_get_header_by_number(
&self,
number: BlockNumber,
) -> Result<Option<AnyRpcBlock>> {
node_info!("ots_getApiLevel");
self.backend.block_by_number(number).await
}
pub async fn ots_get_api_level(&self) -> Result<u64> {
node_info!("ots_getApiLevel");
Ok(8)
}
pub async fn ots_get_internal_operations(&self, hash: B256) -> Result<Vec<InternalOperation>> {
node_info!("ots_getInternalOperations");
self.backend
.mined_transaction(hash)
.map(|tx| tx.ots_internal_operations())
.ok_or_else(|| BlockchainError::DataUnavailable)
}
pub async fn ots_has_code(&self, address: Address, block_number: BlockNumber) -> Result<bool> {
node_info!("ots_hasCode");
let block_id = Some(BlockId::Number(block_number));
Ok(self.get_code(address, block_id).await?.len() > 0)
}
pub async fn ots_trace_transaction(&self, hash: B256) -> Result<Vec<TraceEntry>> {
node_info!("ots_traceTransaction");
Ok(batch_build_ots_traces(self.backend.trace_transaction(hash).await?))
}
pub async fn ots_get_transaction_error(&self, hash: B256) -> Result<Bytes> {
node_info!("ots_getTransactionError");
if let Some(receipt) = self.backend.mined_transaction_receipt(hash) {
if !receipt.inner.inner.as_receipt_with_bloom().receipt.status.coerce_status() {
return Ok(receipt.out.map(|b| b.0.into()).unwrap_or(Bytes::default()));
}
}
Ok(Bytes::default())
}
pub async fn ots_get_block_details(
&self,
number: BlockNumber,
) -> Result<BlockDetails<AnyRpcHeader>> {
node_info!("ots_getBlockDetails");
if let Some(block) = self.backend.block_by_number(number).await? {
let ots_block = self.build_ots_block_details(block).await?;
Ok(ots_block)
} else {
Err(BlockchainError::BlockNotFound)
}
}
pub async fn ots_get_block_details_by_hash(
&self,
hash: B256,
) -> Result<BlockDetails<AnyRpcHeader>> {
node_info!("ots_getBlockDetailsByHash");
if let Some(block) = self.backend.block_by_hash(hash).await? {
let ots_block = self.build_ots_block_details(block).await?;
Ok(ots_block)
} else {
Err(BlockchainError::BlockNotFound)
}
}
pub async fn ots_get_block_transactions(
&self,
number: u64,
page: usize,
page_size: usize,
) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
node_info!("ots_getBlockTransactions");
match self.backend.block_by_number_full(number.into()).await? {
Some(block) => self.build_ots_block_tx(block, page, page_size).await,
None => Err(BlockchainError::BlockNotFound),
}
}
pub async fn ots_search_transactions_before(
&self,
address: Address,
block_number: u64,
page_size: usize,
) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
node_info!("ots_searchTransactionsBefore");
let best = self.backend.best_number();
let from = if block_number == 0 { best } else { block_number - 1 };
let to = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1);
let first_page = from >= best;
let mut last_page = false;
let mut res: Vec<_> = vec![];
for n in (to..=from).rev() {
if let Some(traces) = self.backend.mined_parity_trace_block(n) {
let hashes = traces
.into_iter()
.rev()
.filter_map(|trace| mentions_address(trace, address))
.unique();
if res.len() >= page_size {
break;
}
res.extend(hashes);
}
if n == to {
last_page = true;
}
}
self.build_ots_search_transactions(res, first_page, last_page).await
}
pub async fn ots_search_transactions_after(
&self,
address: Address,
block_number: u64,
page_size: usize,
) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
node_info!("ots_searchTransactionsAfter");
let best = self.backend.best_number();
let first_block = self.get_fork().map(|f| f.block_number() + 1).unwrap_or(1);
let from = if block_number == 0 { first_block } else { block_number + 1 };
let to = best;
let mut first_page = from >= best;
let mut last_page = false;
let mut res: Vec<_> = vec![];
for n in from..=to {
if n == first_block {
last_page = true;
}
if let Some(traces) = self.backend.mined_parity_trace_block(n) {
let hashes = traces
.into_iter()
.rev()
.filter_map(|trace| mentions_address(trace, address))
.unique();
if res.len() >= page_size {
break;
}
res.extend(hashes);
}
if n == to {
first_page = true;
}
}
res.reverse();
self.build_ots_search_transactions(res, first_page, last_page).await
}
pub async fn ots_get_transaction_by_sender_and_nonce(
&self,
address: Address,
nonce: U256,
) -> Result<Option<B256>> {
node_info!("ots_getTransactionBySenderAndNonce");
let from = self.get_fork().map(|f| f.block_number() + 1).unwrap_or_default();
let to = self.backend.best_number();
for n in (from..=to).rev() {
if let Some(txs) = self.backend.mined_transactions_by_block_number(n.into()).await {
for tx in txs {
if U256::from(tx.nonce()) == nonce && tx.from == address {
return Ok(Some(tx.tx_hash()));
}
}
}
}
Ok(None)
}
pub async fn ots_get_contract_creator(&self, addr: Address) -> Result<Option<ContractCreator>> {
node_info!("ots_getContractCreator");
let from = self.get_fork().map(|f| f.block_number()).unwrap_or_default();
let to = self.backend.best_number();
for n in (from..=to).rev() {
if let Some(traces) = self.backend.mined_parity_trace_block(n) {
for trace in traces.into_iter().rev() {
match (trace.trace.action, trace.trace.result) {
(
Action::Create(CreateAction { from, .. }),
Some(TraceOutput::Create(CreateOutput { address, .. })),
) if address == addr => {
return Ok(Some(ContractCreator {
hash: trace.transaction_hash.unwrap(),
creator: from,
}));
}
_ => {}
}
}
}
}
Ok(None)
}
pub async fn build_ots_block_details(
&self,
block: AnyRpcBlock,
) -> Result<BlockDetails<alloy_rpc_types::Header<AnyHeader>>> {
if block.transactions.is_uncle() {
return Err(BlockchainError::DataUnavailable);
}
let receipts_futs = block
.transactions
.hashes()
.map(|hash| async move { self.transaction_receipt(hash).await });
let receipts = join_all(receipts_futs)
.await
.into_iter()
.map(|r| match r {
Ok(Some(r)) => Ok(r),
_ => Err(BlockchainError::DataUnavailable),
})
.collect::<Result<Vec<_>>>()?;
let total_fees = receipts
.iter()
.fold(0, |acc, receipt| acc + receipt.gas_used * receipt.effective_gas_price);
let Block { header, uncles, transactions, withdrawals } = block.inner;
let block =
OtsSlimBlock { header, uncles, transaction_count: transactions.len(), withdrawals };
Ok(BlockDetails {
block,
total_fees: U256::from(total_fees),
issuance: Default::default(),
})
}
pub async fn build_ots_block_tx(
&self,
mut block: AnyRpcBlock,
page: usize,
page_size: usize,
) -> Result<OtsBlockTransactions<AnyRpcTransaction, AnyRpcHeader>> {
if block.transactions.is_uncle() {
return Err(BlockchainError::DataUnavailable);
}
block.transactions = match block.transactions() {
BlockTransactions::Full(txs) => BlockTransactions::Full(
txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
),
BlockTransactions::Hashes(txs) => BlockTransactions::Hashes(
txs.iter().skip(page * page_size).take(page_size).cloned().collect(),
),
BlockTransactions::Uncle => unreachable!(),
};
let receipt_futs = block.transactions.hashes().map(|hash| self.transaction_receipt(hash));
let receipts = join_all(receipt_futs.map(|r| async {
if let Ok(Some(r)) = r.await {
let block = self.block_by_number(r.block_number.unwrap().into()).await?;
let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp;
let receipt = r.map_inner(OtsReceipt::from);
Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
} else {
Err(BlockchainError::BlockNotFound)
}
}))
.await
.into_iter()
.collect::<Result<Vec<_>>>()?;
let transaction_count = block.transactions().len();
let fullblock = OtsBlock { block: block.inner, transaction_count };
let ots_block_txs = OtsBlockTransactions { fullblock, receipts };
Ok(ots_block_txs)
}
pub async fn build_ots_search_transactions(
&self,
hashes: Vec<B256>,
first_page: bool,
last_page: bool,
) -> Result<TransactionsWithReceipts<alloy_rpc_types::Transaction<AnyTxEnvelope>>> {
let txs_futs = hashes.iter().map(|hash| async { self.transaction_by_hash(*hash).await });
let txs = join_all(txs_futs)
.await
.into_iter()
.map(|t| match t {
Ok(Some(t)) => Ok(t.inner),
_ => Err(BlockchainError::DataUnavailable),
})
.collect::<Result<Vec<_>>>()?;
let receipt_futs = hashes.iter().map(|hash| self.transaction_receipt(*hash));
let receipts = join_all(receipt_futs.map(|r| async {
if let Ok(Some(r)) = r.await {
let block = self.block_by_number(r.block_number.unwrap().into()).await?;
let timestamp = block.ok_or(BlockchainError::BlockNotFound)?.header.timestamp;
let receipt = r.map_inner(OtsReceipt::from);
Ok(OtsTransactionReceipt { receipt, timestamp: Some(timestamp) })
} else {
Err(BlockchainError::BlockNotFound)
}
}))
.await
.into_iter()
.collect::<Result<Vec<_>>>()?;
Ok(TransactionsWithReceipts { txs, receipts, first_page, last_page })
}
}