reth_consensus_debug_client/providers/
etherscan.rs

1use crate::BlockProvider;
2use alloy_eips::BlockNumberOrTag;
3use alloy_rpc_types_eth::Block;
4use reqwest::Client;
5use reth_tracing::tracing::warn;
6use serde::Deserialize;
7use std::time::Duration;
8use tokio::{sync::mpsc, time::interval};
9
10/// Block provider that fetches new blocks from Etherscan API.
11#[derive(Debug, Clone)]
12pub struct EtherscanBlockProvider {
13    http_client: Client,
14    base_url: String,
15    api_key: String,
16    interval: Duration,
17}
18
19impl EtherscanBlockProvider {
20    /// Create a new Etherscan block provider with the given base URL and API key.
21    pub fn new(base_url: String, api_key: String) -> Self {
22        Self { http_client: Client::new(), base_url, api_key, interval: Duration::from_secs(3) }
23    }
24
25    /// Sets the interval at which the provider fetches new blocks.
26    pub const fn with_interval(mut self, interval: Duration) -> Self {
27        self.interval = interval;
28        self
29    }
30
31    /// Load block using Etherscan API. Note: only `BlockNumberOrTag::Latest`,
32    /// `BlockNumberOrTag::Earliest`, `BlockNumberOrTag::Pending`, `BlockNumberOrTag::Number(u64)`
33    /// are supported.
34    pub async fn load_block(&self, block_number_or_tag: BlockNumberOrTag) -> eyre::Result<Block> {
35        let block: EtherscanBlockResponse = self
36            .http_client
37            .get(&self.base_url)
38            .query(&[
39                ("module", "proxy"),
40                ("action", "eth_getBlockByNumber"),
41                ("tag", &block_number_or_tag.to_string()),
42                ("boolean", "true"),
43                ("apikey", &self.api_key),
44            ])
45            .send()
46            .await?
47            .json()
48            .await?;
49        Ok(block.result)
50    }
51}
52
53impl BlockProvider for EtherscanBlockProvider {
54    async fn subscribe_blocks(&self, tx: mpsc::Sender<Block>) {
55        let mut last_block_number: Option<u64> = None;
56        let mut interval = interval(self.interval);
57        loop {
58            interval.tick().await;
59            let block = match self.load_block(BlockNumberOrTag::Latest).await {
60                Ok(block) => block,
61                Err(err) => {
62                    warn!(target: "consensus::debug-client", %err, "failed to fetch a block from Etherscan");
63                    continue
64                }
65            };
66            let block_number = block.header.number;
67            if Some(block_number) == last_block_number {
68                continue;
69            }
70
71            if tx.send(block).await.is_err() {
72                // channel closed
73                break;
74            }
75
76            last_block_number = Some(block_number);
77        }
78    }
79
80    async fn get_block(&self, block_number: u64) -> eyre::Result<Block> {
81        self.load_block(BlockNumberOrTag::Number(block_number)).await
82    }
83}
84
85#[derive(Deserialize, Debug)]
86struct EtherscanBlockResponse {
87    result: Block,
88}