From 411b91f0601a0dbd14451c385b0c26f5cc849457 Mon Sep 17 00:00:00 2001 From: Agost Biro Date: Tue, 27 Jun 2023 20:05:34 +0200 Subject: [PATCH] fix: prevent firewall permission popups on MacOS Fixes #276. --- workspaces/Cargo.toml | 1 - workspaces/src/lib.rs | 1 + workspaces/src/network/mod.rs | 2 +- workspaces/src/network/server.rs | 62 ++++++++++++++++++++++++++------ workspaces/tests/deploy.rs | 8 ++--- 5 files changed, 56 insertions(+), 18 deletions(-) diff --git a/workspaces/Cargo.toml b/workspaces/Cargo.toml index 9dfee876..957b49e2 100644 --- a/workspaces/Cargo.toml +++ b/workspaces/Cargo.toml @@ -20,7 +20,6 @@ cargo-near = "0.3.1" chrono = "0.4.19" fs2 = "0.4" hex = "0.4.2" -portpicker = "0.1.1" rand = "0.8.4" reqwest = { version = "0.11", features = ["json"] } serde = "1.0" diff --git a/workspaces/src/lib.rs b/workspaces/src/lib.rs index 926862d1..c4d1381a 100644 --- a/workspaces/src/lib.rs +++ b/workspaces/src/lib.rs @@ -18,6 +18,7 @@ pub mod result; pub mod rpc; pub mod types; +pub use network::pick_unused_port; pub use network::variants::{DevNetwork, Network}; pub use result::Result; pub use types::account::{Account, AccountDetails, Contract}; diff --git a/workspaces/src/network/mod.rs b/workspaces/src/network/mod.rs index 57cbc67f..af373d67 100644 --- a/workspaces/src/network/mod.rs +++ b/workspaces/src/network/mod.rs @@ -20,7 +20,7 @@ pub use self::betanet::Betanet; pub use self::info::Info; pub use self::mainnet::Mainnet; pub use self::sandbox::Sandbox; -pub use self::server::ValidatorKey; +pub use self::server::{pick_unused_port, ValidatorKey}; pub use self::testnet::Testnet; pub use self::variants::{ AllowDevAccountCreation, NetworkClient, NetworkInfo, TopLevelAccountCreator, diff --git a/workspaces/src/network/server.rs b/workspaces/src/network/server.rs index 31e5d928..44bb2a0a 100644 --- a/workspaces/src/network/server.rs +++ b/workspaces/src/network/server.rs @@ -1,4 +1,5 @@ use std::fs::File; +use std::net::{Ipv4Addr, SocketAddrV4}; use std::path::PathBuf; use crate::error::{ErrorKind, SandboxErrorCode}; @@ -8,21 +9,41 @@ use crate::types::SecretKey; use async_process::Child; use fs2::FileExt; use near_account_id::AccountId; -use portpicker::pick_unused_port; use reqwest::Url; use tempfile::TempDir; use tracing::info; use near_sandbox_utils as sandbox; +use tokio::net::TcpListener; -pub const DEFAULT_RPC_URL: &str = "http://localhost"; +// Must be an IP address as `neard` expects socket address for network address. +const DEFAULT_RPC_HOST: &str = "127.0.0.1"; + +fn rpc_socket(port: u16) -> String { + format!("{DEFAULT_RPC_HOST}:{}", port) +} + +/// Request an unused port from the OS. +pub async fn pick_unused_port() -> Result { + // Port 0 means the OS gives us an unused port + // Important to use localhost as using 0.0.0.0 leads to users getting brief firewall popups to + // allow inbound connections on MacOS. + let addr = SocketAddrV4::new(Ipv4Addr::LOCALHOST, 0); + let listener = TcpListener::bind(addr) + .await + .map_err(|err| ErrorKind::Io.full("failed to bind to random port", err))?; + let port = listener + .local_addr() + .map_err(|err| ErrorKind::Io.full("failed to get local address for random port", err))? + .port(); + Ok(port) +} /// Acquire an unused port and lock it for the duration until the sandbox server has /// been started. -fn acquire_unused_port() -> Result<(u16, File)> { +async fn acquire_unused_port() -> Result<(u16, File)> { loop { - let port = pick_unused_port() - .ok_or_else(|| SandboxErrorCode::InitFailure.message("no ports free"))?; + let port = pick_unused_port().await?; let lockpath = std::env::temp_dir().join(format!("near-sandbox-port{}.lock", port)); let lockfile = File::create(lockpath).map_err(|err| { ErrorKind::Io.full(format!("failed to create lockfile for port {}", port), err) @@ -90,18 +111,37 @@ impl SandboxServer { crate::network::config::set_sandbox_configs(&home_dir)?; // Try running the server with the follow provided rpc_ports and net_ports - let (rpc_port, rpc_port_lock) = acquire_unused_port()?; - let (net_port, net_port_lock) = acquire_unused_port()?; - let rpc_addr = format!("{}:{}", DEFAULT_RPC_URL, rpc_port); - // This is guaranteed to be a valid URL, since this is using the default URL. - let rpc_addr = Url::parse(&rpc_addr).unwrap(); + let (rpc_port, rpc_port_lock) = acquire_unused_port().await?; + let (net_port, net_port_lock) = acquire_unused_port().await?; + // It's important that the address doesn't have a scheme, since the sandbox expects + // a valid socket address. + let rpc_addr = rpc_socket(rpc_port); + let net_addr = rpc_socket(net_port); info!(target: "workspaces", "Starting up sandbox at localhost:{}", rpc_port); - let child = sandbox::run(&home_dir, rpc_port, net_port) + + let options = &[ + "--home", + home_dir + .as_os_str() + .to_str() + .expect("home_dir is valid utf8"), + "run", + "--rpc-addr", + &rpc_addr, + "--network-addr", + &net_addr, + ]; + + let child = sandbox::run_with_options(options) .map_err(|e| SandboxErrorCode::RunFailure.custom(e))?; info!(target: "workspaces", "Started up sandbox at localhost:{} with pid={:?}", rpc_port, child.id()); + let rpc_addr: Url = format!("http://{rpc_addr}") + .parse() + .expect("static scheme and host name with variable u16 port numbers form valid urls"); + Ok(Self { validator_key: ValidatorKey::HomeDir(home_dir), rpc_addr, diff --git a/workspaces/tests/deploy.rs b/workspaces/tests/deploy.rs index a81ed90a..bf810b63 100644 --- a/workspaces/tests/deploy.rs +++ b/workspaces/tests/deploy.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use test_log::test; use workspaces::network::{Sandbox, ValidatorKey}; -use workspaces::Worker; +use workspaces::{pick_unused_port, Worker}; const NFT_WASM_FILEPATH: &str = "../examples/res/non_fungible_token.wasm"; const EXPECTED_NFT_METADATA: &str = r#"{ @@ -59,10 +59,8 @@ async fn test_dev_deploy() -> anyhow::Result<()> { #[test(tokio::test)] async fn test_manually_spawned_deploy() -> anyhow::Result<()> { - let rpc_port = - portpicker::pick_unused_port().ok_or_else(|| anyhow::anyhow!("no free ports"))?; - let net_port = - portpicker::pick_unused_port().ok_or_else(|| anyhow::anyhow!("no free ports"))?; + let rpc_port = pick_unused_port().await?; + let net_port = pick_unused_port().await?; let mut home_dir = std::env::temp_dir(); home_dir.push(format!("test-sandbox-{}", rpc_port));