Skip to content

Commit cb779e6

Browse files
authored
Fix port acquisition in BackgroundAnvil (#708)
* Fix BackgroundDevnet: remove clippy suppression; fix unresponsiveness msg * Reuse port acquisition logic from background_devnet * Expand use of anyhow::anyhow! macro * Fix old typos: hardat -> hardhat
1 parent 25bcd17 commit cb779e6

File tree

8 files changed

+56
-63
lines changed

8 files changed

+56
-63
lines changed

crates/starknet-devnet-core/src/messaging/ethereum.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ impl EthereumMessaging {
269269
&self,
270270
cancellation_delay_seconds: U256,
271271
) -> DevnetResult<Address> {
272-
// Default value from anvil and hardat multiplied by 20.
272+
// Default value from anvil and hardhat multiplied by 20.
273273
let gas_price: U256 = 20000000000_u128.into();
274274

275275
let contract = abigen::MockStarknetMessaging::deploy(

tests/integration/common/background_anvil.rs

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use ethers::prelude::*;
66
use ethers::providers::{Http, Provider};
77
use ethers::types::Address;
88
use k256::ecdsa::SigningKey;
9-
use rand::Rng;
109
use reqwest::StatusCode;
1110

11+
use super::background_server::get_acquired_port;
1212
use super::constants::{DEFAULT_ETH_ACCOUNT_PRIVATE_KEY, HOST};
1313
use super::errors::TestError;
1414
use super::safe_child::SafeChild;
@@ -30,31 +30,29 @@ mod abigen {
3030
}
3131

3232
impl BackgroundAnvil {
33-
/// To avoid TOCTOU or binding issues, we try random ports and try to start
34-
/// Anvil on this port (as Anvil will actually open the socket right after binding).
35-
#[allow(dead_code)] // dead_code needed to pass clippy
3633
pub(crate) async fn spawn() -> Result<Self, TestError> {
3734
BackgroundAnvil::spawn_with_additional_args(&[]).await
3835
}
3936

37+
/// Spawns an instance at random port. Assumes CLI args in `args` don't contain `--port`.
4038
pub(crate) async fn spawn_with_additional_args(args: &[&str]) -> Result<Self, TestError> {
41-
// Relies on `background_devnet::BackgroundDevnet` starting its check from smaller values
42-
// (1025). Relies on the probability of M simultaneously spawned Anvils occupying
43-
// different ports being fairly big (N*(N-1)*...*(N-M+1) / N**M; N=65_000-20_000+1)
44-
let port = rand::thread_rng().gen_range(20_000..=65_000);
45-
4639
let process = Command::new("anvil")
4740
.arg("--port")
48-
.arg(port.to_string())
41+
.arg("0")
4942
.arg("--silent")
5043
.args(args)
5144
.spawn()
5245
.expect("Could not start background Anvil");
53-
let safe_process = SafeChild { process };
46+
let mut safe_process = SafeChild { process };
47+
48+
let sleep_time = time::Duration::from_millis(500);
49+
let max_retries = 30;
50+
let port = get_acquired_port(&mut safe_process, sleep_time, max_retries)
51+
.await
52+
.map_err(|e| TestError::AnvilNotStartable(format!("Cannot determine port: {e:?}")))?;
5453

5554
let url = format!("http://{HOST}:{port}");
5655
let client = reqwest::Client::new();
57-
let max_retries = 30;
5856
for _ in 0..max_retries {
5957
if let Ok(anvil_block_rsp) = send_dummy_request(&client, &url).await {
6058
assert_eq!(anvil_block_rsp.status(), StatusCode::OK);
@@ -65,34 +63,30 @@ impl BackgroundAnvil {
6563
return Ok(Self { process: safe_process, url, provider, provider_signer });
6664
}
6765

68-
tokio::time::sleep(time::Duration::from_millis(500)).await;
66+
tokio::time::sleep(sleep_time).await;
6967
}
7068

71-
Err(TestError::AnvilNotStartable)
69+
Err(TestError::AnvilNotStartable("Not responsive for too long".into()))
7270
}
7371

7472
pub async fn deploy_l1l2_contract(
7573
&self,
7674
messaging_address: Address,
7775
) -> Result<Address, TestError> {
78-
let contract = abigen::L1L2Example::deploy(
79-
self.provider_signer.clone(),
80-
messaging_address,
81-
)
76+
// Required by the new version of anvil, as default is no longer accepted.
77+
// We use here the default value from anvil and hardhat multiplied by 2.
78+
let gas_price = 2_000_000_000;
79+
let contract = abigen::L1L2Example::deploy(self.provider_signer.clone(), messaging_address)
8280
.map_err(|e| {
8381
TestError::EthersError(format!(
8482
"Error formatting messaging contract deploy request: {e}"
8583
))
8684
})?
87-
// Required by the new version of anvil, as default is no longer accepted.
88-
// We use here the default value from anvil and hardat multiplied by 2.
89-
.gas_price(2000000000)
85+
.gas_price(gas_price)
9086
.send()
9187
.await
9288
.map_err(|e| {
93-
TestError::EthersError(format!(
94-
"Error deploying messaging contract: {e}"
95-
))
89+
TestError::EthersError(format!("Error deploying messaging contract: {e}"))
9690
})?;
9791

9892
Ok(contract.address())

tests/integration/common/background_devnet.rs

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::fmt::LowerHex;
33
use std::process::{Command, Stdio};
44
use std::time;
55

6+
use anyhow::anyhow;
67
use lazy_static::lazy_static;
78
use reqwest::{Client, StatusCode};
89
use serde_json::json;
@@ -23,6 +24,7 @@ use super::constants::{
2324
use super::errors::{RpcError, TestError};
2425
use super::reqwest_client::{PostReqwestSender, ReqwestClient};
2526
use super::utils::{to_hex_felt, FeeUnit, ImpersonationAction};
27+
use crate::common::background_server::get_acquired_port;
2628
use crate::common::constants::{
2729
DEVNET_EXECUTABLE_BINARY_PATH, DEVNET_MANIFEST_PATH, ETH_ERC20_CONTRACT_ADDRESS,
2830
};
@@ -63,31 +65,6 @@ fn get_devnet_command() -> Command {
6365
}
6466
}
6567

66-
async fn get_acquired_port(
67-
process: &mut SafeChild,
68-
sleep_time: time::Duration,
69-
max_retries: usize,
70-
) -> Result<u16, anyhow::Error> {
71-
let pid = process.id();
72-
for _ in 0..max_retries {
73-
if let Ok(ports) = listeners::get_ports_by_pid(pid) {
74-
if ports.len() == 1 {
75-
return Ok(ports.into_iter().next().unwrap());
76-
}
77-
}
78-
79-
if let Ok(Some(status)) = process.process.try_wait() {
80-
return Err(anyhow::Error::msg(format!(
81-
"Background Devnet process exited with status {status}"
82-
)));
83-
}
84-
85-
tokio::time::sleep(sleep_time).await;
86-
}
87-
88-
Err(anyhow::Error::msg(format!("Could not identify a unique port used by PID {pid}")))
89-
}
90-
9168
async fn wait_for_successful_response(
9269
client: &Client,
9370
healthcheck_url: &str,
@@ -98,7 +75,7 @@ async fn wait_for_successful_response(
9875
if let Ok(alive_resp) = client.get(healthcheck_url).send().await {
9976
let status = alive_resp.status();
10077
if status != StatusCode::OK {
101-
return Err(anyhow::Error::msg(format!("Server responded with: {status}")));
78+
return Err(anyhow!("Server responded with: {status}"));
10279
}
10380

10481
return Ok(());
@@ -107,18 +84,15 @@ async fn wait_for_successful_response(
10784
tokio::time::sleep(sleep_time).await;
10885
}
10986

110-
Err(anyhow::Error::msg("Server not reachable at {}"))
87+
Err(anyhow!("Not responsive: {healthcheck_url}"))
11188
}
11289

11390
impl BackgroundDevnet {
114-
/// Ensures the background instance spawns at a free port, checks at most `MAX_RETRIES`
115-
/// times
116-
#[allow(dead_code)] // dead_code needed to pass clippy
11791
pub(crate) async fn spawn() -> Result<Self, TestError> {
11892
BackgroundDevnet::spawn_with_additional_args(&[]).await
11993
}
12094

121-
pub async fn spawn_forkable_devnet() -> Result<BackgroundDevnet, anyhow::Error> {
95+
pub(crate) async fn spawn_forkable_devnet() -> Result<BackgroundDevnet, anyhow::Error> {
12296
let args = ["--state-archive-capacity", "full"];
12397
let devnet = BackgroundDevnet::spawn_with_additional_args(&args).await?;
12498
Ok(devnet)
@@ -180,7 +154,7 @@ impl BackgroundDevnet {
180154
println!("Spawned background devnet at {devnet_url}");
181155

182156
let devnet_rpc_url = Url::parse(format!("{devnet_url}{RPC_PATH}").as_str())?;
183-
Ok(BackgroundDevnet {
157+
Ok(Self {
184158
reqwest_client: ReqwestClient::new(devnet_url.clone(), client),
185159
json_rpc_client: JsonRpcClient::new(HttpTransport::new(devnet_rpc_url.clone())),
186160
port,
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use std::time;
2+
3+
use super::safe_child::SafeChild;
4+
5+
pub(crate) async fn get_acquired_port(
6+
process: &mut SafeChild,
7+
sleep_time: time::Duration,
8+
max_retries: usize,
9+
) -> Result<u16, anyhow::Error> {
10+
let pid = process.id();
11+
for _ in 0..max_retries {
12+
if let Ok(ports) = listeners::get_ports_by_pid(pid) {
13+
if ports.len() == 1 {
14+
return Ok(ports.into_iter().next().unwrap());
15+
}
16+
}
17+
18+
if let Ok(Some(status)) = process.process.try_wait() {
19+
return Err(anyhow::anyhow!("Background server process exited with status {status}"));
20+
}
21+
22+
tokio::time::sleep(sleep_time).await;
23+
}
24+
25+
Err(anyhow::anyhow!("Could not identify a unique port used by PID {pid}"))
26+
}

tests/integration/common/errors.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ pub enum TestError {
1414
#[error("Could not start Devnet. Make sure you built it with `cargo build --release`: {0}")]
1515
DevnetNotStartable(String),
1616

17-
#[error("Could not start Anvil")]
18-
AnvilNotStartable,
17+
#[error("Could not start Anvil: {0}")]
18+
AnvilNotStartable(String),
1919

2020
#[error("Ethers error: {0}")]
2121
EthersError(String),

tests/integration/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![cfg(test)]
22
pub mod background_anvil;
33
pub mod background_devnet;
4+
mod background_server;
45
pub mod constants;
56
pub mod errors;
67
pub mod fees;

tests/integration/common/safe_child.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ impl SafeChild {
1111
}
1212
}
1313

14-
/// By implementing Drop, we ensure there are no zombie background Devnet processes
15-
/// in case of an early test failure
14+
/// By implementing Drop, we ensure there are no zombie processes in case of early test failure
1615
impl Drop for SafeChild {
1716
fn drop(&mut self) {
1817
self.process.kill().expect("Cannot kill process");

tests/integration/common/utils.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,8 +300,7 @@ pub async fn deploy_argent_account(
300300

301301
let account_address = deployment.address();
302302
devnet.mint(account_address, 1e18 as u128).await;
303-
let deployment_result =
304-
deployment.send().await.map_err(|e| anyhow::Error::msg(format!("{e:?}")))?;
303+
let deployment_result = deployment.send().await.map_err(|e| anyhow::anyhow!("{e:?}"))?;
305304

306305
Ok((deployment_result, signer))
307306
}

0 commit comments

Comments
 (0)