reth_consensus_debug_client/providers/
etherscan.rs

1use crate::BlockProvider;
2use alloy_consensus::BlockHeader;
3use alloy_eips::BlockNumberOrTag;
4use reqwest::Client;
5use reth_tracing::tracing::warn;
6use serde::{de::DeserializeOwned, Deserialize, Serialize};
7use std::{sync::Arc, time::Duration};
8use tokio::{sync::mpsc, time::interval};
9
10/// Block provider that fetches new blocks from Etherscan API.
11#[derive(derive_more::Debug, Clone)]
12pub struct EtherscanBlockProvider<RpcBlock, PrimitiveBlock> {
13    http_client: Client,
14    base_url: String,
15    api_key: String,
16    interval: Duration,
17    #[debug(skip)]
18    convert: Arc<dyn Fn(RpcBlock) -> PrimitiveBlock + Send + Sync>,
19}
20
21impl<RpcBlock, PrimitiveBlock> EtherscanBlockProvider<RpcBlock, PrimitiveBlock>
22where
23    RpcBlock: Serialize + DeserializeOwned,
24{
25    /// Create a new Etherscan block provider with the given base URL and API key.
26    pub fn new(
27        base_url: String,
28        api_key: String,
29        convert: impl Fn(RpcBlock) -> PrimitiveBlock + Send + Sync + 'static,
30    ) -> Self {
31        Self {
32            http_client: Client::new(),
33            base_url,
34            api_key,
35            interval: Duration::from_secs(3),
36            convert: Arc::new(convert),
37        }
38    }
39
40    /// Sets the interval at which the provider fetches new blocks.
41    pub const fn with_interval(mut self, interval: Duration) -> Self {
42        self.interval = interval;
43        self
44    }
45
46    /// Load block using Etherscan API. Note: only `BlockNumberOrTag::Latest`,
47    /// `BlockNumberOrTag::Earliest`, `BlockNumberOrTag::Pending`, `BlockNumberOrTag::Number(u64)`
48    /// are supported.
49    pub async fn load_block(
50        &self,
51        block_number_or_tag: BlockNumberOrTag,
52    ) -> eyre::Result<PrimitiveBlock> {
53        let tag = match block_number_or_tag {
54            BlockNumberOrTag::Number(num) => format!("{num:#02x}"),
55            tag => tag.to_string(),
56        };
57        let block: EtherscanBlockResponse<RpcBlock> = self
58            .http_client
59            .get(&self.base_url)
60            .query(&[
61                ("module", "proxy"),
62                ("action", "eth_getBlockByNumber"),
63                ("tag", &tag),
64                ("boolean", "true"),
65                ("apikey", &self.api_key),
66            ])
67            .send()
68            .await?
69            .json()
70            .await?;
71        Ok((self.convert)(block.result))
72    }
73}
74
75impl<RpcBlock, PrimitiveBlock> BlockProvider for EtherscanBlockProvider<RpcBlock, PrimitiveBlock>
76where
77    RpcBlock: Serialize + DeserializeOwned + 'static,
78    PrimitiveBlock: reth_primitives_traits::Block + 'static,
79{
80    type Block = PrimitiveBlock;
81
82    async fn subscribe_blocks(&self, tx: mpsc::Sender<Self::Block>) {
83        let mut last_block_number: Option<u64> = None;
84        let mut interval = interval(self.interval);
85        loop {
86            interval.tick().await;
87            let block = match self.load_block(BlockNumberOrTag::Latest).await {
88                Ok(block) => block,
89                Err(err) => {
90                    warn!(
91                        target: "consensus::debug-client",
92                        %err,
93                        "Failed to fetch a block from Etherscan",
94                    );
95                    continue
96                }
97            };
98            let block_number = block.header().number();
99            if Some(block_number) == last_block_number {
100                continue;
101            }
102
103            if tx.send(block).await.is_err() {
104                // Channel closed.
105                break;
106            }
107
108            last_block_number = Some(block_number);
109        }
110    }
111
112    async fn get_block(&self, block_number: u64) -> eyre::Result<Self::Block> {
113        self.load_block(BlockNumberOrTag::Number(block_number)).await
114    }
115}
116
117#[derive(Deserialize, Debug)]
118struct EtherscanBlockResponse<B> {
119    result: B,
120}