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#[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 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 pub const fn with_interval(mut self, interval: Duration) -> Self {
42 self.interval = interval;
43 self
44 }
45
46 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 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}