use ethers::types::U256;
use ethers::types::{Block as EthersBlock, H64, H256, Transaction};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use crate::{Evm, types::EvmError};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BlockInfo {
pub number: Option<u64>,
pub hash: Option<H256>,
pub parent_hash: H256,
pub timestamp: U256,
pub gas_limit: U256,
pub gas_used: U256,
pub miner: ethers::types::Address,
pub difficulty: U256,
pub total_difficulty: Option<U256>,
pub size: Option<U256>,
pub transaction_count: usize,
pub transaction_hashes: Option<Vec<H256>>,
pub transactions: Option<Vec<Transaction>>,
pub base_fee_per_gas: Option<U256>,
pub extra_data: ethers::types::Bytes,
pub sha3_uncles: H256,
pub logs_bloom: Option<ethers::types::Bloom>,
pub receipts_root: H256,
pub state_root: H256,
pub transactions_root: H256,
pub nonce: Option<H64>,
pub mix_hash: Option<H256>,
pub uncles: Vec<H256>,
}
impl BlockInfo {
pub fn from_ethers_block(block: &EthersBlock<H256>) -> Self {
Self {
number: block.number.map(|n| n.as_u64()),
hash: block.hash,
parent_hash: block.parent_hash,
timestamp: block.timestamp,
gas_limit: block.gas_limit,
gas_used: block.gas_used,
miner: block.author.unwrap_or(ethers::types::Address::zero()),
difficulty: block.difficulty,
total_difficulty: block.total_difficulty,
size: block.size,
transaction_count: block.transactions.len(),
transaction_hashes: Some(block.transactions.clone()),
transactions: None,
base_fee_per_gas: block.base_fee_per_gas,
extra_data: block.extra_data.clone(),
sha3_uncles: block.uncles_hash,
logs_bloom: block.logs_bloom,
receipts_root: block.receipts_root,
state_root: block.state_root,
transactions_root: block.transactions_root,
nonce: block.nonce,
mix_hash: block.mix_hash,
uncles: block.uncles.clone(),
}
}
pub fn from_ethers_block_with_txs(block: &EthersBlock<Transaction>) -> Self {
let transaction_hashes: Vec<H256> = block.transactions.iter().map(|tx| tx.hash).collect();
Self {
number: block.number.map(|n| n.as_u64()),
hash: block.hash,
parent_hash: block.parent_hash,
timestamp: block.timestamp,
gas_limit: block.gas_limit,
gas_used: block.gas_used,
miner: block.author.unwrap_or(ethers::types::Address::zero()),
difficulty: block.difficulty,
total_difficulty: block.total_difficulty,
size: block.size,
transaction_count: block.transactions.len(),
transaction_hashes: Some(transaction_hashes),
transactions: Some(block.transactions.clone()),
base_fee_per_gas: block.base_fee_per_gas,
extra_data: block.extra_data.clone(),
sha3_uncles: block.uncles_hash,
logs_bloom: block.logs_bloom,
receipts_root: block.receipts_root,
state_root: block.state_root,
transactions_root: block.transactions_root,
nonce: block.nonce,
mix_hash: block.mix_hash,
uncles: block.uncles.clone(),
}
}
pub fn timestamp_u64(&self) -> Option<u64> {
self.timestamp.try_into().ok()
}
pub fn number_u64(&self) -> Option<u64> {
self.number
}
pub fn gas_limit_u64(&self) -> Option<u64> {
self.gas_limit.try_into().ok()
}
pub fn gas_used_u64(&self) -> Option<u64> {
self.gas_used.try_into().ok()
}
pub fn gas_used_percentage(&self) -> Option<f64> {
let gas_used: u128 = self.gas_used.try_into().ok()?;
let gas_limit: u128 = self.gas_limit.try_into().ok()?;
if gas_limit == 0 {
return None;
}
Some((gas_used as f64 / gas_limit as f64) * 100.0)
}
}
pub struct BlockService {
evm: Arc<Evm>,
}
impl BlockService {
pub fn new(evm: Arc<Evm>) -> Self {
Self { evm }
}
pub async fn get_block_by_number(
&self,
block_number: u64,
) -> Result<Option<BlockInfo>, EvmError> {
let block = self
.evm
.get_block_by_number(ethers::types::BlockNumber::Number(block_number.into()))
.await?;
Ok(block.map(|b| BlockInfo::from_ethers_block(&b)))
}
pub async fn get_block_by_hash(&self, block_hash: H256) -> Result<Option<BlockInfo>, EvmError> {
let block = self.evm.get_block_by_hash(block_hash).await?;
Ok(block.map(|b| BlockInfo::from_ethers_block(&b)))
}
pub async fn get_block_with_txs(
&self,
block_number: u64,
) -> Result<Option<BlockInfo>, EvmError> {
let block = self
.evm
.get_block_with_txs(ethers::types::BlockNumber::Number(block_number.into()))
.await?;
Ok(block.map(|b| BlockInfo::from_ethers_block_with_txs(&b)))
}
pub async fn get_block_with_txs_by_hash(
&self,
block_hash: H256,
) -> Result<Option<BlockInfo>, EvmError> {
let block = self.get_block_by_hash(block_hash).await?;
if let Some(block_info) = block {
if let Some(block_number) = block_info.number {
return self.get_block_with_txs(block_number).await;
}
}
Ok(None)
}
pub async fn get_latest_block(&self) -> Result<Option<BlockInfo>, EvmError> {
let block = self
.evm
.get_block_by_number(ethers::types::BlockNumber::Latest)
.await?;
Ok(block.map(|b| BlockInfo::from_ethers_block(&b)))
}
pub async fn get_latest_block_with_txs(&self) -> Result<Option<BlockInfo>, EvmError> {
let block = self
.evm
.get_block_with_txs(ethers::types::BlockNumber::Latest)
.await?;
Ok(block.map(|b| BlockInfo::from_ethers_block_with_txs(&b)))
}
pub async fn get_blocks_in_range(
&self,
start: u64,
end: u64,
) -> Result<Vec<Option<BlockInfo>>, EvmError> {
let mut blocks = Vec::new();
let mut futures = Vec::new();
for block_number in start..=end {
let service = self.evm.clone();
futures.push(async move {
service
.get_block_by_number(ethers::types::BlockNumber::Number(block_number.into()))
.await
.ok()
.flatten()
.map(|b| BlockInfo::from_ethers_block(&b))
});
}
for future in futures {
blocks.push(tokio::spawn(future).await.ok().flatten());
}
Ok(blocks)
}
}
#[cfg(test)]
mod tests {
use crate::trade::{self, Trade};
use super::*;
use ethers::types::H256;
use evm_client::EvmType;
use std::sync::Arc;
#[tokio::test]
async fn lisent_liquidity_last_transaction() {
let evm = Arc::new(Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap());
let trade = Trade::new(evm.clone());
let block_service = evm.clone().get_block_service();
loop {
match block_service.get_latest_block().await {
Ok(Some(block)) => {
for hash in block.transaction_hashes.unwrap() {
let trade = trade
.get_transactions_by_tx(&format!("{:?}", hash))
.await
.unwrap();
println!("transaction hash: {:?}", trade.hash);
println!("dex: {:?}", trade.get_dex_names());
println!(
"liquidity pool address: {:?}",
trade.get_liquidity_pool_addresses()
);
println!("received: {:?}", trade.get_received_token_eth());
println!("spent: {:?}", trade.get_spent_token_eth());
println!("direction: {:?}", trade.getDirection());
}
}
Ok(None) => println!("⚠️ Nont Block"),
Err(e) => println!("❌ Error: {}", e),
}
tokio::time::sleep(tokio::time::Duration::from_secs(
evm.clone().client.get_block_interval_time().unwrap(),
))
.await;
}
}
#[tokio::test]
async fn test_poll_latest_block_per_second() {
let evm = Arc::new(Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap());
let trade = Trade::new(evm.clone());
let block_service = evm.clone().get_block_service();
for i in 0..5 {
match block_service.get_latest_block().await {
Ok(Some(block)) => {
println!("✅ {} seconds: block #{:?}", i, block.number);
println!("Block hash: {:?}", block.transaction_hashes);
for hash in block.transaction_hashes.unwrap() {
let trade = trade
.get_transactions_by_tx(&format!("{:?}", hash))
.await
.unwrap();
println!("All transactions in the block: {:?}", trade);
}
}
Ok(None) => println!("⚠️ {} s: Nont Block", i),
Err(e) => println!("❌ {} s: Error: {}", i, e),
}
tokio::time::sleep(tokio::time::Duration::from_secs(
evm.clone().client.get_block_interval_time().unwrap(),
))
.await;
}
}
#[tokio::test]
async fn test_get_block_by_number_success() {
let evm = Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap();
let evm_arc = Arc::new(evm);
let block_service = evm_arc.get_block_service();
let latest_block = block_service.get_latest_block().await;
match latest_block {
Ok(Some(block_info)) => {
println!("✅ Successfully got latest block");
println!(" Block number: {:?}", block_info.number);
println!(" Block hash: {:?}", block_info.hash);
println!(" Timestamp: {}", block_info.timestamp);
println!(" Transaction count: {}", block_info.transaction_count);
println!(" Gas used: {}", block_info.gas_used);
assert!(
block_info.parent_hash != H256::zero(),
"Parent hash should not be zero"
);
assert!(
block_info.timestamp > U256::zero(),
"Timestamp should be greater than zero"
);
assert!(
block_info.miner != ethers::types::Address::zero(),
"Miner address should not be zero"
);
if let Some(timestamp_u64) = block_info.timestamp_u64() {
println!(" Timestamp(u64): {}", timestamp_u64);
}
if let Some(gas_percentage) = block_info.gas_used_percentage() {
println!(" Gas usage percentage: {:.2}%", gas_percentage);
assert!(
gas_percentage >= 0.0 && gas_percentage <= 100.0,
"Gas usage should be between 0-100%"
);
}
}
Ok(None) => {
println!("⚠️ Latest block is None (possible node issue)");
}
Err(e) => {
println!("❌ Failed to get latest block: {}", e);
if e.to_string().contains("Rpc Error") || e.to_string().contains("timeout") {
println!(" Skipping test (network issue)");
return;
}
panic!("Failed to get latest block: {}", e);
}
}
}
#[tokio::test]
async fn test_get_block_by_hash_and_compare() {
let evm = Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap();
let evm_arc = Arc::new(evm);
let block_service = evm_arc.get_block_service();
let latest_block_result = block_service.get_latest_block().await;
if let Ok(Some(latest_block)) = latest_block_result {
if let (Some(block_number), Some(block_hash)) = (latest_block.number, latest_block.hash)
{
println!("✅ Got latest block #{}", block_number);
println!(" Hash: {:?}", block_hash);
let by_number_result = block_service.get_block_by_number(block_number).await;
assert!(
by_number_result.is_ok(),
"Should succeed when getting by block number"
);
if let Ok(Some(by_number)) = by_number_result {
let by_hash_result = block_service.get_block_by_hash(block_hash).await;
assert!(
by_hash_result.is_ok(),
"Should succeed when getting by block hash"
);
if let Ok(Some(by_hash)) = by_hash_result {
println!(" Comparing results from block number and block hash...");
assert_eq!(
by_number.number, by_hash.number,
"Block numbers should match"
);
assert_eq!(by_number.hash, by_hash.hash, "Block hashes should match");
assert_eq!(
by_number.parent_hash, by_hash.parent_hash,
"Parent hashes should match"
);
assert_eq!(
by_number.timestamp, by_hash.timestamp,
"Timestamps should match"
);
assert_eq!(
by_number.transaction_count, by_hash.transaction_count,
"Transaction counts should match"
);
if let (Some(number_hashes), Some(hash_hashes)) =
(&by_number.transaction_hashes, &by_hash.transaction_hashes)
{
assert_eq!(
number_hashes.len(),
hash_hashes.len(),
"Transaction hash list lengths should match"
);
if !number_hashes.is_empty() {
assert_eq!(
number_hashes[0], hash_hashes[0],
"First transaction hash should match"
);
}
}
println!(" ✅ Results from block number and block hash match");
let with_txs_result = block_service.get_block_with_txs(block_number).await;
match with_txs_result {
Ok(Some(block_with_txs)) => {
println!(" ✅ Successfully got block with transactions");
println!(
" Transaction count: {}",
block_with_txs.transaction_count
);
assert!(
block_with_txs.transaction_hashes.is_some(),
"Transaction hashes should be Some"
);
if let Some(transactions) = &block_with_txs.transactions {
println!(
" Full transaction count: {}",
transactions.len()
);
assert_eq!(
transactions.len(),
block_with_txs.transaction_count,
"Transaction counts should match"
);
for tx in transactions {
assert_ne!(
tx.hash,
H256::zero(),
"Transaction hash should not be zero"
);
}
}
}
Ok(None) => {
println!(
" ⚠️ Block with transactions is None (node might not support)"
);
}
Err(e) => {
println!(" ⚠️ Failed to get block with transactions: {}", e);
}
}
}
}
} else {
println!("⚠️ Latest block missing number or hash");
}
} else {
println!("⚠️ Cannot get latest block, skipping comparison test");
}
}
#[tokio::test]
async fn test_get_blocks_in_range() {
let evm = Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap();
let evm_arc = Arc::new(evm);
let block_service = evm_arc.get_block_service();
let latest_block = block_service.get_latest_block().await;
if let Ok(Some(latest_block_info)) = latest_block {
if let Some(latest_number) = latest_block_info.number {
if latest_number >= 2 {
let start = latest_number - 1;
let end = latest_number;
println!("✅ Testing get blocks in range {}-{}", start, end);
let blocks_result = block_service.get_blocks_in_range(start, end).await;
assert!(
blocks_result.is_ok(),
"Should succeed when getting blocks in range"
);
let blocks = blocks_result.unwrap();
assert_eq!(blocks.len(), 2, "Should return 2 blocks");
let mut found_blocks = 0;
for (i, block_opt) in blocks.iter().enumerate() {
let expected_number = start + i as u64;
match block_opt {
Some(block) => {
found_blocks += 1;
if let Some(block_number) = block.number {
println!(" Block #{}: found", block_number);
assert_eq!(
block_number, expected_number,
"Block number mismatch"
);
}
}
None => {
println!(
" Block #{}: not found (possible node issue)",
expected_number
);
}
}
}
println!(" Found {}/2 blocks", found_blocks);
} else {
println!("⚠️ Chain too short for range test");
}
}
} else {
println!("⚠️ Cannot get latest block, skipping range test");
}
}
}