Skip to content

Commit f9d8663

Browse files
authored
test: shuffle archive URLs (#9472)
* test: shuffle archive URLs * fmt * chore: clippy * print
1 parent 22202a7 commit f9d8663

File tree

8 files changed

+102
-45
lines changed

8 files changed

+102
-45
lines changed

crates/anvil/tests/it/fork.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl LocalFork {
5555

5656
pub fn fork_config() -> NodeConfig {
5757
NodeConfig::test()
58-
.with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint()))
58+
.with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))
5959
.with_fork_block_number(Some(BLOCK_NUMBER))
6060
}
6161

@@ -287,7 +287,7 @@ async fn test_fork_reset_setup() {
287287
assert_eq!(local_balance, U256::ZERO);
288288

289289
api.anvil_reset(Some(Forking {
290-
json_rpc_url: Some(rpc::next_http_archive_rpc_endpoint()),
290+
json_rpc_url: Some(rpc::next_http_archive_rpc_url()),
291291
block_number: Some(BLOCK_NUMBER),
292292
}))
293293
.await
@@ -829,8 +829,7 @@ async fn test_fork_init_base_fee() {
829829
#[tokio::test(flavor = "multi_thread")]
830830
async fn test_reset_fork_on_new_blocks() {
831831
let (api, handle) =
832-
spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint())))
833-
.await;
832+
spawn(NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()))).await;
834833

835834
let anvil_provider = handle.http_provider();
836835
let endpoint = next_http_rpc_endpoint();
@@ -864,7 +863,7 @@ async fn test_fork_call() {
864863
let to: Address = "0x99d1Fa417f94dcD62BfE781a1213c092a47041Bc".parse().unwrap();
865864
let block_number = 14746300u64;
866865

867-
let provider = http_provider(rpc::next_http_archive_rpc_endpoint().as_str());
866+
let provider = http_provider(rpc::next_http_archive_rpc_url().as_str());
868867
let tx = TransactionRequest::default().to(to).with_input(input.clone());
869868
let tx = WithOtherFields::new(tx);
870869
let res0 = provider.call(&tx).block(BlockId::Number(block_number.into())).await.unwrap();

crates/forge/tests/cli/script.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,8 +200,7 @@ contract DeployScript is Script {
200200

201201
let deploy_contract = deploy_script.display().to_string() + ":DeployScript";
202202

203-
let node_config =
204-
NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint()));
203+
let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()));
205204
let (_api, handle) = spawn(node_config).await;
206205
let dev = handle.dev_accounts().next().unwrap();
207206
cmd.set_current_dir(prj.root());
@@ -302,8 +301,7 @@ contract DeployScript is Script {
302301

303302
let deploy_contract = deploy_script.display().to_string() + ":DeployScript";
304303

305-
let node_config =
306-
NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint()));
304+
let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()));
307305
let (_api, handle) = spawn(node_config).await;
308306
let private_key =
309307
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string();
@@ -492,8 +490,7 @@ contract DeployScript is Script {
492490

493491
let deploy_contract = deploy_script.display().to_string() + ":DeployScript";
494492

495-
let node_config =
496-
NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_endpoint()));
493+
let node_config = NodeConfig::test().with_eth_rpc_url(Some(rpc::next_http_archive_rpc_url()));
497494
let (_api, handle) = spawn(node_config).await;
498495
let private_key =
499496
"ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80".to_string();

crates/forge/tests/cli/test_cmd.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ contract Contract {
473473
)
474474
.unwrap();
475475

476-
let endpoint = rpc::next_http_archive_rpc_endpoint();
476+
let endpoint = rpc::next_http_archive_rpc_url();
477477

478478
prj.add_test(
479479
"Contract.t.sol",
@@ -545,7 +545,7 @@ forgetest_init!(exit_code_error_on_fail_fast_with_json, |prj, cmd| {
545545
forgetest_init!(fork_traces, |prj, cmd| {
546546
prj.wipe_contracts();
547547

548-
let endpoint = rpc::next_http_archive_rpc_endpoint();
548+
let endpoint = rpc::next_http_archive_rpc_url();
549549

550550
prj.add_test(
551551
"Contract.t.sol",
@@ -699,7 +699,7 @@ contract TransientTest is Test {
699699
forgetest_init!(can_disable_block_gas_limit, |prj, cmd| {
700700
prj.wipe_contracts();
701701

702-
let endpoint = rpc::next_http_archive_rpc_endpoint();
702+
let endpoint = rpc::next_http_archive_rpc_url();
703703

704704
prj.add_test(
705705
"Contract.t.sol",

crates/forge/tests/cli/verify_bytecode.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use foundry_compilers::artifacts::{BytecodeHash, EvmVersion};
22
use foundry_config::Config;
33
use foundry_test_utils::{
44
forgetest_async,
5-
rpc::{next_http_archive_rpc_endpoint, next_mainnet_etherscan_api_key},
5+
rpc::{next_http_archive_rpc_url, next_mainnet_etherscan_api_key},
66
util::OutputExt,
77
TestCommand, TestProject,
88
};
@@ -20,7 +20,7 @@ fn test_verify_bytecode(
2020
expected_matches: (&str, &str),
2121
) {
2222
let etherscan_key = next_mainnet_etherscan_api_key();
23-
let rpc_url = next_http_archive_rpc_endpoint();
23+
let rpc_url = next_http_archive_rpc_url();
2424

2525
// fetch and flatten source code
2626
let source_code = cmd
@@ -75,7 +75,7 @@ fn test_verify_bytecode_with_ignore(
7575
chain: &str,
7676
) {
7777
let etherscan_key = next_mainnet_etherscan_api_key();
78-
let rpc_url = next_http_archive_rpc_endpoint();
78+
let rpc_url = next_http_archive_rpc_url();
7979

8080
// fetch and flatten source code
8181
let source_code = cmd

crates/forge/tests/it/fork.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ async fn test_rpc_fork() {
6868
/// Tests that we can launch in forking mode
6969
#[tokio::test(flavor = "multi_thread")]
7070
async fn test_launch_fork() {
71-
let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_endpoint();
71+
let rpc_url = foundry_test_utils::rpc::next_http_archive_rpc_url();
7272
let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await;
7373
let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch"));
7474
TestConfig::with_filter(runner, filter).run().await;
@@ -77,7 +77,7 @@ async fn test_launch_fork() {
7777
/// Smoke test that forking workings with websockets
7878
#[tokio::test(flavor = "multi_thread")]
7979
async fn test_launch_fork_ws() {
80-
let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_endpoint();
80+
let rpc_url = foundry_test_utils::rpc::next_ws_archive_rpc_url();
8181
let runner = TEST_DATA_DEFAULT.forked_runner(&rpc_url).await;
8282
let filter = Filter::new(".*", ".*", &format!(".*fork{RE_PATH_SEPARATOR}Launch"));
8383
TestConfig::with_filter(runner, filter).run().await;

crates/test-utils/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
55
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
66
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
7+
#![allow(clippy::disallowed_macros)]
78

89
#[macro_use]
910
extern crate foundry_common;

crates/test-utils/src/rpc.rs

Lines changed: 85 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -101,53 +101,109 @@ fn next<T>(list: &[T]) -> &T {
101101
&list[next_idx() % list.len()]
102102
}
103103

104-
/// Returns the next _mainnet_ rpc endpoint in inline
104+
/// Returns the next _mainnet_ rpc URL in inline
105105
///
106106
/// This will rotate all available rpc endpoints
107107
pub fn next_http_rpc_endpoint() -> String {
108108
next_rpc_endpoint(NamedChain::Mainnet)
109109
}
110110

111-
/// Returns the next _mainnet_ rpc endpoint in inline
111+
/// Returns the next _mainnet_ rpc URL in inline
112112
///
113113
/// This will rotate all available rpc endpoints
114114
pub fn next_ws_rpc_endpoint() -> String {
115115
next_ws_endpoint(NamedChain::Mainnet)
116116
}
117117

118-
/// Returns the next HTTP RPC endpoint.
118+
/// Returns the next HTTP RPC URL.
119119
pub fn next_rpc_endpoint(chain: NamedChain) -> String {
120120
next_url(false, chain)
121121
}
122122

123-
/// Returns the next WS RPC endpoint.
123+
/// Returns the next WS RPC URL.
124124
pub fn next_ws_endpoint(chain: NamedChain) -> String {
125125
next_url(true, chain)
126126
}
127127

128-
/// Returns endpoint that has access to archive state
129-
pub fn next_http_archive_rpc_endpoint() -> String {
130-
next_archive_endpoint(false)
128+
/// Returns a websocket URL that has access to archive state
129+
pub fn next_http_archive_rpc_url() -> String {
130+
next_archive_url(false)
131131
}
132132

133-
/// Returns endpoint that has access to archive state
134-
pub fn next_ws_archive_rpc_endpoint() -> String {
135-
next_archive_endpoint(true)
133+
/// Returns an HTTP URL that has access to archive state
134+
pub fn next_ws_archive_rpc_url() -> String {
135+
next_archive_url(true)
136136
}
137137

138-
/// Returns endpoint that has access to archive state, http or ws.
139-
/// Use env vars (comma separated urls) or default inline keys (Alchemy for ws, Infura for http).
140-
fn next_archive_endpoint(is_ws: bool) -> String {
141-
let env_urls = if is_ws { ENV_WS_ARCHIVE_ENDPOINTS } else { ENV_HTTP_ARCHIVE_ENDPOINTS };
142-
143-
let rpc_env_vars = env::var(env_urls).unwrap_or_default();
144-
if !rpc_env_vars.is_empty() {
145-
let urls = rpc_env_vars.split(',').collect::<Vec<&str>>();
146-
next(&urls).to_string()
147-
} else if is_ws {
148-
format!("wss://eth-mainnet.g.alchemy.com/v2/{}", next(&ALCHEMY_KEYS))
138+
/// Returns a URL that has access to archive state.
139+
///
140+
/// Uses either environment variables (comma separated urls) or default keys.
141+
fn next_archive_url(is_ws: bool) -> String {
142+
let urls = archive_urls(is_ws);
143+
let url = if env_archive_urls(is_ws).is_empty() {
144+
next(urls)
149145
} else {
150-
format!("https://eth-mainnet.g.alchemy.com/v2/{}", next(&ALCHEMY_KEYS))
146+
urls.choose_weighted(&mut rand::thread_rng(), |url| {
147+
if url.contains("reth") {
148+
2usize
149+
} else {
150+
1usize
151+
}
152+
})
153+
.unwrap()
154+
};
155+
eprintln!("--- next_archive_url(is_ws={is_ws}) = {url} ---");
156+
url.clone()
157+
}
158+
159+
fn archive_urls(is_ws: bool) -> &'static [String] {
160+
static WS: LazyLock<Vec<String>> = LazyLock::new(|| get(true));
161+
static HTTP: LazyLock<Vec<String>> = LazyLock::new(|| get(false));
162+
163+
fn get(is_ws: bool) -> Vec<String> {
164+
let env_urls = env_archive_urls(is_ws);
165+
if !env_urls.is_empty() {
166+
let mut urls = env_urls.to_vec();
167+
urls.shuffle(&mut rand::thread_rng());
168+
return urls;
169+
}
170+
171+
let mut urls = Vec::new();
172+
for &key in ALCHEMY_KEYS.iter() {
173+
if is_ws {
174+
urls.push(format!("wss://eth-mainnet.g.alchemy.com/v2/{key}"));
175+
} else {
176+
urls.push(format!("https://eth-mainnet.g.alchemy.com/v2/{key}"));
177+
}
178+
}
179+
urls
180+
}
181+
182+
if is_ws {
183+
&WS
184+
} else {
185+
&HTTP
186+
}
187+
}
188+
189+
fn env_archive_urls(is_ws: bool) -> &'static [String] {
190+
static WS: LazyLock<Vec<String>> = LazyLock::new(|| get(true));
191+
static HTTP: LazyLock<Vec<String>> = LazyLock::new(|| get(false));
192+
193+
fn get(is_ws: bool) -> Vec<String> {
194+
let env = if is_ws { ENV_WS_ARCHIVE_ENDPOINTS } else { ENV_HTTP_ARCHIVE_ENDPOINTS };
195+
let env = env::var(env).unwrap_or_default();
196+
let env = env.trim();
197+
if env.is_empty() {
198+
return vec![];
199+
}
200+
env.split(',').map(str::trim).filter(|s| !s.is_empty()).map(ToString::to_string).collect()
201+
}
202+
203+
if is_ws {
204+
&WS
205+
} else {
206+
&HTTP
151207
}
152208
}
153209

@@ -162,7 +218,9 @@ pub fn next_etherscan_api_key(chain: NamedChain) -> String {
162218
Optimism => &ETHERSCAN_OPTIMISM_KEYS,
163219
_ => &ETHERSCAN_MAINNET_KEYS,
164220
};
165-
next(keys).to_string()
221+
let key = next(keys).to_string();
222+
eprintln!("--- next_etherscan_api_key(chain={chain:?}) = {key} ---");
223+
key
166224
}
167225

168226
fn next_url(is_ws: bool, chain: NamedChain) -> String {
@@ -206,12 +264,14 @@ fn next_url(is_ws: bool, chain: NamedChain) -> String {
206264
};
207265
let full = if prefix.is_empty() { network.to_string() } else { format!("{prefix}-{network}") };
208266

209-
match (is_ws, is_infura) {
267+
let url = match (is_ws, is_infura) {
210268
(false, true) => format!("https://{full}.infura.io/v3/{key}"),
211269
(true, true) => format!("wss://{full}.infura.io/ws/v3/{key}"),
212270
(false, false) => format!("https://{full}.g.alchemy.com/v2/{key}"),
213271
(true, false) => format!("wss://{full}.g.alchemy.com/v2/{key}"),
214-
}
272+
};
273+
eprintln!("--- next_url(is_ws={is_ws}, chain={chain:?}) = {url} ---");
274+
url
215275
}
216276

217277
#[cfg(test)]

crates/test-utils/src/util.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ impl ExtTester {
202202

203203
test_cmd.envs(self.envs.iter().map(|(k, v)| (k, v)));
204204
if let Some(fork_block) = self.fork_block {
205-
test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_endpoint());
205+
test_cmd.env("FOUNDRY_ETH_RPC_URL", crate::rpc::next_http_archive_rpc_url());
206206
test_cmd.env("FOUNDRY_FORK_BLOCK_NUMBER", fork_block.to_string());
207207
}
208208
test_cmd.env("FOUNDRY_INVARIANT_DEPTH", "15");

0 commit comments

Comments
 (0)