edb_engine/utils/etherscan.rs
1// EDB - Ethereum Debugger
2// Copyright (C) 2024 Zhuo Zhang and Wuqi Zhang
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU Affero General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU Affero General Public License for more details.
13//
14// You should have received a copy of the GNU Affero General Public License
15// along with this program. If not, see <https://www.gnu.org/licenses/>.
16
17//! Etherscan API integration utilities.
18//!
19//! This module provides utilities for interacting with Etherscan APIs, including
20//! API key management and rotation to handle rate limits gracefully. The module
21//! maintains a pool of API keys and automatically rotates between them to avoid
22//! hitting rate limits during intensive debugging sessions.
23//!
24//! # Key Features
25//!
26//! - **API Key Rotation**: Automatic rotation between multiple API keys
27//! - **Rate Limit Handling**: Built-in support for managing API rate limits
28//! - **Randomized Key Pool**: Shuffled key order to distribute load
29//!
30//! # Usage
31//!
32//! The module provides a simple interface through [`next_etherscan_api_key()`]
33//! which returns the next available API key in the rotation pool.
34
35use once_cell::sync::Lazy;
36use rand::seq::SliceRandom;
37use std::sync::atomic::{AtomicUsize, Ordering};
38
39// List of etherscan keys for mainnet
40static ETHERSCAN_MAINNET_KEYS: Lazy<Vec<&'static str>> = Lazy::new(|| {
41 let mut keys = vec![
42 "MCAUM7WPE9XP5UQMZPCKIBUJHPM1C24FP6",
43 "JW6RWCG2C5QF8TANH4KC7AYIF1CX7RB5D1",
44 "ZSMDY6BI2H55MBE3G9CUUQT4XYUDBB6ZSK",
45 "4FYHTY429IXYMJNS4TITKDMUKW5QRYDX61",
46 "QYKNT5RHASZ7PGQE68FNQWH99IXVTVVD2I",
47 "VXMQ117UN58Y4RHWUB8K1UGCEA7UQEWK55",
48 "C7I2G4JTA5EPYS42Z8IZFEIMQNI5GXIJEV",
49 "A15KZUMZXXCK1P25Y1VP1WGIVBBHIZDS74",
50 "3IA6ASNQXN8WKN7PNFX7T72S9YG56X9FPG",
51 ];
52
53 keys.shuffle(&mut rand::thread_rng());
54
55 keys
56});
57
58// counts the next etherscan key to use
59static NEXT_ETHERSCAN_MAINNET_KEY: AtomicUsize = AtomicUsize::new(0);
60
61// returns the current value of the atomic counter and increments it
62fn next(c: &AtomicUsize) -> usize {
63 c.fetch_add(1, Ordering::SeqCst)
64}
65
66/// Returns the next Etherscan API key to use from the rotation pool.
67///
68/// This function implements a round-robin rotation strategy across the available
69/// API keys to distribute load and avoid hitting rate limits on any single key.
70pub fn next_etherscan_api_key() -> String {
71 let idx = next(&NEXT_ETHERSCAN_MAINNET_KEY) % ETHERSCAN_MAINNET_KEYS.len();
72 ETHERSCAN_MAINNET_KEYS[idx].to_string()
73}
74
75/// Automaticall pause the request if the rate limit is reached
76/// and resume it after the rate limit is reset.
77#[macro_export]
78macro_rules! etherscan_rate_limit_guard {
79 ($request:expr) => {
80 loop {
81 match $request {
82 Ok(response) => break Ok(response),
83 Err(foundry_block_explorers::errors::EtherscanError::RateLimitExceeded) => {
84 tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
85 continue;
86 }
87 Err(e) => break Err(e),
88 }
89 }
90 };
91
92 ($request:expr, $secs:expr) => {
93 loop {
94 match $request {
95 Ok(response) => break Ok(response),
96 Err(foundry_block_explorers::errors::EtherscanError::RateLimitExceeded) => {
97 tokio::time::sleep(tokio::time::Duration::from_secs($secs)).await;
98 continue;
99 }
100 Err(e) => break Err(e),
101 }
102 }
103 };
104}