diff --git a/Cargo.lock b/Cargo.lock index 2173af74..cf3431f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1322,6 +1322,7 @@ dependencies = [ "futures-util", "hex", "http", + "once_cell", "parking_lot", "pin-project", "reqwest", diff --git a/ethers-middleware/tests/nonce_manager.rs b/ethers-middleware/tests/nonce_manager.rs index 30b3fa03..5e0a23dc 100644 --- a/ethers-middleware/tests/nonce_manager.rs +++ b/ethers-middleware/tests/nonce_manager.rs @@ -4,14 +4,11 @@ async fn nonce_manager() { use ethers_core::types::*; use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware}; - use ethers_providers::{Http, Middleware, Provider}; + use ethers_providers::Middleware; use ethers_signers::{LocalWallet, Signer}; - use std::{convert::TryFrom, time::Duration}; + use std::time::Duration; - let provider = - Provider::::try_from("https://rinkeby.infura.io/v3/fd8b88b56aa84f6da87b60f5441d6778") - .unwrap() - .interval(Duration::from_millis(2000u64)); + let provider = ethers_providers::RINKEBY.provider().interval(Duration::from_millis(2000u64)); let chain_id = provider.get_chainid().await.unwrap().as_u64(); let wallet = std::env::var("RINKEBY_PRIVATE_KEY") diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs index e5cf3570..6ec3fea6 100644 --- a/ethers-middleware/tests/signer.rs +++ b/ethers-middleware/tests/signer.rs @@ -1,5 +1,5 @@ #![allow(unused)] -use ethers_providers::{Http, JsonRpcClient, Middleware, Provider}; +use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, RINKEBY}; use ethers_core::{ types::{BlockNumber, TransactionRequest}, @@ -8,7 +8,7 @@ use ethers_core::{ use ethers_middleware::signer::SignerMiddleware; use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer}; use once_cell::sync::Lazy; -use std::{convert::TryFrom, sync::atomic::AtomicU8, time::Duration}; +use std::{convert::TryFrom, iter::Cycle, sync::atomic::AtomicU8, time::Duration}; static WALLETS: Lazy = Lazy::new(|| { TestWallets { @@ -54,10 +54,7 @@ async fn send_eth() { #[tokio::test] #[cfg(not(feature = "celo"))] async fn pending_txs_with_confirmations_testnet() { - let provider = - Provider::::try_from("https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27") - .unwrap() - .interval(Duration::from_millis(3000)); + let provider = RINKEBY.provider().interval(Duration::from_millis(3000)); let chain_id = provider.get_chainid().await.unwrap(); let wallet = WALLETS.next().with_chain_id(chain_id.as_u64()); let address = wallet.address(); @@ -97,9 +94,7 @@ async fn generic_pending_txs_test(provider: M, who: Address) { #[tokio::test] #[cfg(not(feature = "celo"))] async fn typed_txs() { - let provider = - Provider::::try_from("https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27") - .unwrap(); + let provider = RINKEBY.provider(); let chain_id = provider.get_chainid().await.unwrap(); let wallet = WALLETS.next().with_chain_id(chain_id.as_u64()); diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml index 0d921325..56090340 100644 --- a/ethers-providers/Cargo.toml +++ b/ethers-providers/Cargo.toml @@ -39,6 +39,7 @@ tracing = { version = "0.1.32", default-features = false } tracing-futures = { version = "0.2.5", default-features = false, features = ["std-future"] } bytes = { version = "1.1.0", default-features = false, optional = true } +once_cell = "1.10.0" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] # tokio diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs index d169bcc2..00e90d9c 100644 --- a/ethers-providers/src/lib.rs +++ b/ethers-providers/src/lib.rs @@ -657,3 +657,53 @@ pub trait CeloMiddleware: Middleware { self.provider().get_validators_bls_public_keys(block_id).await.map_err(FromErr::from) } } + +pub use test_provider::{GOERLI, MAINNET, RINKEBY, ROPSTEN}; + +/// Pre-instantiated Infura HTTP clients which rotate through multiple API keys +/// to prevent rate limits +pub mod test_provider { + use super::*; + use crate::Http; + use once_cell::sync::Lazy; + use std::{convert::TryFrom, iter::Cycle, slice::Iter, sync::Mutex}; + + // List of infura keys to rotate through so we don't get rate limited + const INFURA_KEYS: &[&str] = &[ + "6770454bc6ea42c58aac12978531b93f", + "7a8769b798b642f6933f2ed52042bd70", + "631fd9a6539644088297dc605d35fff3", + "16a8be88795540b9b3903d8de0f7baa5", + "f4a0bdad42674adab5fc0ac077ffab2b", + "5c812e02193c4ba793f8c214317582bd", + ]; + + pub static RINKEBY: Lazy = + Lazy::new(|| TestProvider::new(INFURA_KEYS, "rinkeby")); + pub static MAINNET: Lazy = + Lazy::new(|| TestProvider::new(INFURA_KEYS, "mainnet")); + pub static GOERLI: Lazy = Lazy::new(|| TestProvider::new(INFURA_KEYS, "goerli")); + pub static ROPSTEN: Lazy = + Lazy::new(|| TestProvider::new(INFURA_KEYS, "ropsten")); + + #[derive(Debug)] + pub struct TestProvider { + network: String, + keys: Mutex>>, + } + + impl TestProvider { + pub fn new(keys: &'static [&'static str], network: &str) -> Self { + Self { keys: Mutex::new(keys.iter().cycle()), network: network.to_owned() } + } + + pub fn provider(&self) -> Provider { + let url = format!( + "https://{}.infura.io/v3/{}", + self.network, + self.keys.lock().unwrap().next().unwrap() + ); + Provider::try_from(url.as_str()).unwrap() + } + } +} diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs index dee01796..6f6c7ea8 100644 --- a/ethers-providers/src/provider.rs +++ b/ethers-providers/src/provider.rs @@ -1488,12 +1488,10 @@ mod tests { }; use futures_util::StreamExt; - const INFURA: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27"; - #[tokio::test] // Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2 async fn mainnet_resolve_name() { - let provider = Provider::::try_from(INFURA).unwrap(); + let provider = crate::test_provider::MAINNET.provider(); let addr = provider.resolve_name("registrar.firefly.eth").await.unwrap(); assert_eq!(addr, "6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap()); @@ -1508,7 +1506,7 @@ mod tests { #[tokio::test] // Test vector from: https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#id2 async fn mainnet_lookup_address() { - let provider = Provider::::try_from(INFURA).unwrap(); + let provider = crate::MAINNET.provider(); let name = provider .lookup_address("6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap()) @@ -1525,7 +1523,7 @@ mod tests { #[tokio::test] async fn mainnet_resolve_avatar() { - let provider = Provider::::try_from(INFURA).unwrap(); + let provider = crate::MAINNET.provider(); for (ens_name, res) in &[ // HTTPS diff --git a/ethers-providers/tests/provider.rs b/ethers-providers/tests/provider.rs index 3caec3cf..a6ffa2b8 100644 --- a/ethers-providers/tests/provider.rs +++ b/ethers-providers/tests/provider.rs @@ -1,5 +1,5 @@ #![cfg(not(target_arch = "wasm32"))] -use ethers_providers::{Http, Middleware, Provider}; +use ethers_providers::{Http, Middleware, Provider, RINKEBY}; use std::{convert::TryFrom, time::Duration}; #[cfg(not(feature = "celo"))] @@ -12,10 +12,7 @@ mod eth_tests { #[tokio::test] async fn non_existing_data_works() { - let provider = Provider::::try_from( - "https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27", - ) - .unwrap(); + let provider = RINKEBY.provider(); assert!(provider.get_transaction(H256::zero()).await.unwrap().is_none()); assert!(provider.get_transaction_receipt(H256::zero()).await.unwrap().is_none()); @@ -25,10 +22,7 @@ mod eth_tests { #[tokio::test] async fn client_version() { - let provider = Provider::::try_from( - "https://rinkeby.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27", - ) - .unwrap(); + let provider = RINKEBY.provider(); // e.g., Geth/v1.10.6-omnibus-1af33248/linux-amd64/go1.16.6 assert!(provider @@ -95,10 +89,7 @@ mod eth_tests { #[tokio::test] async fn eip1559_fee_estimation() { - let provider = Provider::::try_from( - "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27", - ) - .unwrap(); + let provider = ethers_providers::MAINNET.provider(); let (_max_fee_per_gas, _max_priority_fee_per_gas) = provider.estimate_eip1559_fees(None).await.unwrap();