1use ethers::types::U256;
2use ethers::types::{Block as EthersBlock, H64, H256, Transaction};
3use serde::{Deserialize, Serialize};
4use std::sync::Arc;
5
6use crate::{Evm, types::EvmError};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct BlockInfo {
10 pub number: Option<u64>,
12 pub hash: Option<H256>,
14 pub parent_hash: H256,
16 pub timestamp: U256,
18 pub gas_limit: U256,
20 pub gas_used: U256,
22 pub miner: ethers::types::Address,
24 pub difficulty: U256,
26 pub total_difficulty: Option<U256>,
28 pub size: Option<U256>,
30 pub transaction_count: usize,
32 pub transaction_hashes: Option<Vec<H256>>,
34 pub transactions: Option<Vec<Transaction>>,
36 pub base_fee_per_gas: Option<U256>,
38 pub extra_data: ethers::types::Bytes,
40 pub sha3_uncles: H256,
42 pub logs_bloom: Option<ethers::types::Bloom>,
44 pub receipts_root: H256,
46 pub state_root: H256,
48 pub transactions_root: H256,
50 pub nonce: Option<H64>,
52 pub mix_hash: Option<H256>,
54 pub uncles: Vec<H256>,
56}
57
58impl BlockInfo {
59 pub fn from_ethers_block(block: &EthersBlock<H256>) -> Self {
61 Self {
62 number: block.number.map(|n| n.as_u64()),
63 hash: block.hash,
64 parent_hash: block.parent_hash,
65 timestamp: block.timestamp,
66 gas_limit: block.gas_limit,
67 gas_used: block.gas_used,
68 miner: block.author.unwrap_or(ethers::types::Address::zero()),
69 difficulty: block.difficulty,
70 total_difficulty: block.total_difficulty,
71 size: block.size,
72 transaction_count: block.transactions.len(),
73 transaction_hashes: Some(block.transactions.clone()),
74 transactions: None,
75 base_fee_per_gas: block.base_fee_per_gas,
76 extra_data: block.extra_data.clone(),
77 sha3_uncles: block.uncles_hash,
78 logs_bloom: block.logs_bloom,
79 receipts_root: block.receipts_root,
80 state_root: block.state_root,
81 transactions_root: block.transactions_root,
82 nonce: block.nonce,
83 mix_hash: block.mix_hash,
84 uncles: block.uncles.clone(),
85 }
86 }
87
88 pub fn from_ethers_block_with_txs(block: &EthersBlock<Transaction>) -> Self {
90 let transaction_hashes: Vec<H256> = block.transactions.iter().map(|tx| tx.hash).collect();
91
92 Self {
93 number: block.number.map(|n| n.as_u64()),
94 hash: block.hash,
95 parent_hash: block.parent_hash,
96 timestamp: block.timestamp,
97 gas_limit: block.gas_limit,
98 gas_used: block.gas_used,
99 miner: block.author.unwrap_or(ethers::types::Address::zero()),
100 difficulty: block.difficulty,
101 total_difficulty: block.total_difficulty,
102 size: block.size,
103 transaction_count: block.transactions.len(),
104 transaction_hashes: Some(transaction_hashes),
105 transactions: Some(block.transactions.clone()),
106 base_fee_per_gas: block.base_fee_per_gas,
107 extra_data: block.extra_data.clone(),
108 sha3_uncles: block.uncles_hash,
109 logs_bloom: block.logs_bloom,
110 receipts_root: block.receipts_root,
111 state_root: block.state_root,
112 transactions_root: block.transactions_root,
113 nonce: block.nonce,
114 mix_hash: block.mix_hash,
115 uncles: block.uncles.clone(),
116 }
117 }
118
119 pub fn timestamp_u64(&self) -> Option<u64> {
121 self.timestamp.try_into().ok()
122 }
123
124 pub fn number_u64(&self) -> Option<u64> {
126 self.number
127 }
128
129 pub fn gas_limit_u64(&self) -> Option<u64> {
131 self.gas_limit.try_into().ok()
132 }
133
134 pub fn gas_used_u64(&self) -> Option<u64> {
136 self.gas_used.try_into().ok()
137 }
138
139 pub fn gas_used_percentage(&self) -> Option<f64> {
141 let gas_used: u128 = self.gas_used.try_into().ok()?;
142 let gas_limit: u128 = self.gas_limit.try_into().ok()?;
143 if gas_limit == 0 {
144 return None;
145 }
146 Some((gas_used as f64 / gas_limit as f64) * 100.0)
147 }
148}
149
150pub struct BlockService {
151 evm: Arc<Evm>,
152}
153
154impl BlockService {
155 pub fn new(evm: Arc<Evm>) -> Self {
156 Self { evm }
157 }
158
159 pub async fn get_block_by_number(
161 &self,
162 block_number: u64,
163 ) -> Result<Option<BlockInfo>, EvmError> {
164 let block = self
165 .evm
166 .get_block_by_number(ethers::types::BlockNumber::Number(block_number.into()))
167 .await?;
168 Ok(block.map(|b| BlockInfo::from_ethers_block(&b)))
169 }
170
171 pub async fn get_block_by_hash(&self, block_hash: H256) -> Result<Option<BlockInfo>, EvmError> {
173 let block = self.evm.get_block_by_hash(block_hash).await?;
174 Ok(block.map(|b| BlockInfo::from_ethers_block(&b)))
175 }
176
177 pub async fn get_block_with_txs(
179 &self,
180 block_number: u64,
181 ) -> Result<Option<BlockInfo>, EvmError> {
182 let block = self
183 .evm
184 .get_block_with_txs(ethers::types::BlockNumber::Number(block_number.into()))
185 .await?;
186 Ok(block.map(|b| BlockInfo::from_ethers_block_with_txs(&b)))
187 }
188
189 pub async fn get_block_with_txs_by_hash(
191 &self,
192 block_hash: H256,
193 ) -> Result<Option<BlockInfo>, EvmError> {
194 let block = self.get_block_by_hash(block_hash).await?;
195 if let Some(block_info) = block {
196 if let Some(block_number) = block_info.number {
197 return self.get_block_with_txs(block_number).await;
198 }
199 }
200 Ok(None)
201 }
202
203 pub async fn get_latest_block(&self) -> Result<Option<BlockInfo>, EvmError> {
205 let block = self
206 .evm
207 .get_block_by_number(ethers::types::BlockNumber::Latest)
208 .await?;
209 Ok(block.map(|b| BlockInfo::from_ethers_block(&b)))
210 }
211
212 pub async fn get_latest_block_with_txs(&self) -> Result<Option<BlockInfo>, EvmError> {
214 let block = self
215 .evm
216 .get_block_with_txs(ethers::types::BlockNumber::Latest)
217 .await?;
218 Ok(block.map(|b| BlockInfo::from_ethers_block_with_txs(&b)))
219 }
220
221 pub async fn get_blocks_in_range(
223 &self,
224 start: u64,
225 end: u64,
226 ) -> Result<Vec<Option<BlockInfo>>, EvmError> {
227 let mut blocks = Vec::new();
228 let mut futures = Vec::new();
229 for block_number in start..=end {
230 let service = self.evm.clone();
231 futures.push(async move {
232 service
233 .get_block_by_number(ethers::types::BlockNumber::Number(block_number.into()))
234 .await
235 .ok()
236 .flatten()
237 .map(|b| BlockInfo::from_ethers_block(&b))
238 });
239 }
240 for future in futures {
241 blocks.push(tokio::spawn(future).await.ok().flatten());
242 }
243 Ok(blocks)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use crate::trade::{self, Trade};
250
251 use super::*;
252 use ethers::types::H256;
253 use evm_client::EvmType;
254 use std::sync::Arc;
255
256 #[tokio::test]
257 async fn lisent_liquidity_last_transaction() {
258 let evm = Arc::new(Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap());
259 let trade = Trade::new(evm.clone());
260 let block_service = evm.clone().get_block_service();
261 loop {
262 match block_service.get_latest_block().await {
263 Ok(Some(block)) => {
264 for hash in block.transaction_hashes.unwrap() {
265 let trade = trade
266 .get_transactions_by_tx(&format!("{:?}", hash))
267 .await
268 .unwrap();
269 println!("transaction hash: {:?}", trade.hash);
270 println!("dex: {:?}", trade.get_dex_names());
271 println!(
272 "liquidity pool address: {:?}",
273 trade.get_liquidity_pool_addresses()
274 );
275 println!("received: {:?}", trade.get_received_token_eth());
276 println!("spent: {:?}", trade.get_spent_token_eth());
277 println!("direction: {:?}", trade.getDirection());
278 }
279 }
280 Ok(None) => println!("⚠️ Nont Block"),
281 Err(e) => println!("❌ Error: {}", e),
282 }
283 tokio::time::sleep(tokio::time::Duration::from_secs(
284 evm.clone().client.get_block_interval_time().unwrap(),
285 ))
286 .await;
287 }
288 }
289
290 #[tokio::test]
291 async fn test_poll_latest_block_per_second() {
292 let evm = Arc::new(Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap());
293 let trade = Trade::new(evm.clone());
294 let block_service = evm.clone().get_block_service();
295 for i in 0..5 {
296 match block_service.get_latest_block().await {
297 Ok(Some(block)) => {
298 println!("✅ {} seconds: block #{:?}", i, block.number);
299 println!("Block hash: {:?}", block.transaction_hashes);
300 for hash in block.transaction_hashes.unwrap() {
301 let trade = trade
302 .get_transactions_by_tx(&format!("{:?}", hash))
303 .await
304 .unwrap();
305 println!("All transactions in the block: {:?}", trade);
306 }
307 }
308 Ok(None) => println!("⚠️ {} s: Nont Block", i),
309 Err(e) => println!("❌ {} s: Error: {}", i, e),
310 }
311 tokio::time::sleep(tokio::time::Duration::from_secs(
312 evm.clone().client.get_block_interval_time().unwrap(),
313 ))
314 .await;
315 }
316 }
317
318 #[tokio::test]
319 async fn test_get_block_by_number_success() {
320 let evm = Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap();
322 let evm_arc = Arc::new(evm);
323 let block_service = evm_arc.get_block_service();
324 let latest_block = block_service.get_latest_block().await;
326 match latest_block {
327 Ok(Some(block_info)) => {
328 println!("✅ Successfully got latest block");
329 println!(" Block number: {:?}", block_info.number);
330 println!(" Block hash: {:?}", block_info.hash);
331 println!(" Timestamp: {}", block_info.timestamp);
332 println!(" Transaction count: {}", block_info.transaction_count);
333 println!(" Gas used: {}", block_info.gas_used);
334 assert!(
336 block_info.parent_hash != H256::zero(),
337 "Parent hash should not be zero"
338 );
339 assert!(
340 block_info.timestamp > U256::zero(),
341 "Timestamp should be greater than zero"
342 );
343 assert!(
344 block_info.miner != ethers::types::Address::zero(),
345 "Miner address should not be zero"
346 );
347 if let Some(timestamp_u64) = block_info.timestamp_u64() {
349 println!(" Timestamp(u64): {}", timestamp_u64);
350 }
351
352 if let Some(gas_percentage) = block_info.gas_used_percentage() {
353 println!(" Gas usage percentage: {:.2}%", gas_percentage);
354 assert!(
355 gas_percentage >= 0.0 && gas_percentage <= 100.0,
356 "Gas usage should be between 0-100%"
357 );
358 }
359 }
360 Ok(None) => {
361 println!("⚠️ Latest block is None (possible node issue)");
362 }
363 Err(e) => {
364 println!("❌ Failed to get latest block: {}", e);
365 if e.to_string().contains("Rpc Error") || e.to_string().contains("timeout") {
367 println!(" Skipping test (network issue)");
368 return;
369 }
370 panic!("Failed to get latest block: {}", e);
371 }
372 }
373 }
374
375 #[tokio::test]
376 async fn test_get_block_by_hash_and_compare() {
377 let evm = Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap();
379 let evm_arc = Arc::new(evm);
380 let block_service = evm_arc.get_block_service();
381 let latest_block_result = block_service.get_latest_block().await;
383 if let Ok(Some(latest_block)) = latest_block_result {
384 if let (Some(block_number), Some(block_hash)) = (latest_block.number, latest_block.hash)
385 {
386 println!("✅ Got latest block #{}", block_number);
387 println!(" Hash: {:?}", block_hash);
388 let by_number_result = block_service.get_block_by_number(block_number).await;
390 assert!(
391 by_number_result.is_ok(),
392 "Should succeed when getting by block number"
393 );
394 if let Ok(Some(by_number)) = by_number_result {
395 let by_hash_result = block_service.get_block_by_hash(block_hash).await;
397 assert!(
398 by_hash_result.is_ok(),
399 "Should succeed when getting by block hash"
400 );
401 if let Ok(Some(by_hash)) = by_hash_result {
402 println!(" Comparing results from block number and block hash...");
404 assert_eq!(
406 by_number.number, by_hash.number,
407 "Block numbers should match"
408 );
409 assert_eq!(by_number.hash, by_hash.hash, "Block hashes should match");
410 assert_eq!(
411 by_number.parent_hash, by_hash.parent_hash,
412 "Parent hashes should match"
413 );
414 assert_eq!(
415 by_number.timestamp, by_hash.timestamp,
416 "Timestamps should match"
417 );
418 assert_eq!(
419 by_number.transaction_count, by_hash.transaction_count,
420 "Transaction counts should match"
421 );
422 if let (Some(number_hashes), Some(hash_hashes)) =
424 (&by_number.transaction_hashes, &by_hash.transaction_hashes)
425 {
426 assert_eq!(
427 number_hashes.len(),
428 hash_hashes.len(),
429 "Transaction hash list lengths should match"
430 );
431
432 if !number_hashes.is_empty() {
434 assert_eq!(
435 number_hashes[0], hash_hashes[0],
436 "First transaction hash should match"
437 );
438 }
439 }
440 println!(" ✅ Results from block number and block hash match");
441 let with_txs_result = block_service.get_block_with_txs(block_number).await;
443 match with_txs_result {
444 Ok(Some(block_with_txs)) => {
445 println!(" ✅ Successfully got block with transactions");
446 println!(
447 " Transaction count: {}",
448 block_with_txs.transaction_count
449 );
450
451 assert!(
453 block_with_txs.transaction_hashes.is_some(),
454 "Transaction hashes should be Some"
455 );
456
457 if let Some(transactions) = &block_with_txs.transactions {
459 println!(
460 " Full transaction count: {}",
461 transactions.len()
462 );
463 assert_eq!(
464 transactions.len(),
465 block_with_txs.transaction_count,
466 "Transaction counts should match"
467 );
468
469 for tx in transactions {
471 assert_ne!(
472 tx.hash,
473 H256::zero(),
474 "Transaction hash should not be zero"
475 );
476 }
477 }
478 }
479 Ok(None) => {
480 println!(
481 " ⚠️ Block with transactions is None (node might not support)"
482 );
483 }
484 Err(e) => {
485 println!(" ⚠️ Failed to get block with transactions: {}", e);
486 }
488 }
489 }
490 }
491 } else {
492 println!("⚠️ Latest block missing number or hash");
493 }
494 } else {
495 println!("⚠️ Cannot get latest block, skipping comparison test");
496 }
497 }
498
499 #[tokio::test]
500 async fn test_get_blocks_in_range() {
501 let evm = Evm::new(EvmType::ETHEREUM_MAINNET).await.unwrap();
503 let evm_arc = Arc::new(evm);
504 let block_service = evm_arc.get_block_service();
505 let latest_block = block_service.get_latest_block().await;
507 if let Ok(Some(latest_block_info)) = latest_block {
508 if let Some(latest_number) = latest_block_info.number {
509 if latest_number >= 2 {
510 let start = latest_number - 1;
512 let end = latest_number;
513 println!("✅ Testing get blocks in range {}-{}", start, end);
514 let blocks_result = block_service.get_blocks_in_range(start, end).await;
515 assert!(
516 blocks_result.is_ok(),
517 "Should succeed when getting blocks in range"
518 );
519 let blocks = blocks_result.unwrap();
520 assert_eq!(blocks.len(), 2, "Should return 2 blocks");
521 let mut found_blocks = 0;
522 for (i, block_opt) in blocks.iter().enumerate() {
523 let expected_number = start + i as u64;
524 match block_opt {
525 Some(block) => {
526 found_blocks += 1;
527 if let Some(block_number) = block.number {
528 println!(" Block #{}: found", block_number);
529 assert_eq!(
530 block_number, expected_number,
531 "Block number mismatch"
532 );
533 }
534 }
535 None => {
536 println!(
537 " Block #{}: not found (possible node issue)",
538 expected_number
539 );
540 }
541 }
542 }
543 println!(" Found {}/2 blocks", found_blocks);
544 } else {
545 println!("⚠️ Chain too short for range test");
546 }
547 }
548 } else {
549 println!("⚠️ Cannot get latest block, skipping range test");
550 }
551 }
552}