Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(anvil): add ability to listen on multiple IP addresses #5222

Merged
merged 13 commits into from
Jul 11, 2023
Merged
46 changes: 43 additions & 3 deletions anvil/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,16 @@ pub struct NodeArgs {
#[clap(long, visible_alias = "no-mine", conflicts_with = "block-time")]
pub no_mining: bool,

/// The host the server will listen on.
#[clap(long, value_name = "IP_ADDR", env = "ANVIL_IP_ADDR", help_heading = "Server options")]
pub host: Option<IpAddr>,
/// The hosts the server will listen on.
#[clap(
long,
value_name = "IP_ADDR",
env = "ANVIL_IP_ADDR",
default_value = "127.0.0.1",
help_heading = "Server options",
value_delimiter = ','
)]
pub host: Vec<IpAddr>,
0xZerohero marked this conversation as resolved.
Show resolved Hide resolved

/// How transactions are sorted in the mempool.
#[clap(long, default_value = "fees")]
Expand Down Expand Up @@ -608,6 +615,8 @@ impl FromStr for ForkUrl {

#[cfg(test)]
mod tests {
use std::{env, net::Ipv4Addr};

use super::*;

#[test]
Expand Down Expand Up @@ -664,4 +673,35 @@ mod tests {
NodeArgs::try_parse_from(["anvil", "--disable-block-gas-limit", "--gas-limit", "100"]);
assert!(args.is_err());
}

#[test]
fn can_parse_host() {
let args = NodeArgs::parse_from(["anvil"]);
assert_eq!(args.host, vec![IpAddr::V4(Ipv4Addr::LOCALHOST)]);

let args = NodeArgs::parse_from([
"anvil", "--host", "::1", "--host", "1.1.1.1", "--host", "2.2.2.2",
]);
assert_eq!(
args.host,
["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
);

let args = NodeArgs::parse_from(["anvil", "--host", "::1,1.1.1.1,2.2.2.2"]);
assert_eq!(
args.host,
["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
);

env::set_var("ANVIL_IP_ADDR", "1.1.1.1");
let args = NodeArgs::parse_from(["anvil"]);
assert_eq!(args.host, vec!["1.1.1.1".parse::<IpAddr>().unwrap()]);

env::set_var("ANVIL_IP_ADDR", "::1,1.1.1.1,2.2.2.2");
let args = NodeArgs::parse_from(["anvil"]);
assert_eq!(
args.host,
["::1", "1.1.1.1", "2.2.2.2"].map(|ip| ip.parse::<IpAddr>().unwrap()).to_vec()
);
}
}
15 changes: 10 additions & 5 deletions anvil/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,12 @@ use foundry_evm::{
use parking_lot::RwLock;
use serde_json::{json, to_writer, Value};
use std::{
collections::HashMap, fmt::Write as FmtWrite, fs::File, net::IpAddr, path::PathBuf, sync::Arc,
collections::HashMap,
fmt::Write as FmtWrite,
fs::File,
net::{IpAddr, Ipv4Addr},
path::PathBuf,
sync::Arc,
time::Duration,
};
use yansi::Paint;
Expand Down Expand Up @@ -128,7 +133,7 @@ pub struct NodeConfig {
/// How to configure the server
pub server_config: ServerConfig,
/// The host the server will listen on
pub host: Option<IpAddr>,
pub host: Vec<IpAddr>,
/// How transactions are sorted in the mempool
pub transaction_order: TransactionOrder,
/// Filename to write anvil output as json
Expand Down Expand Up @@ -376,7 +381,7 @@ impl Default for NodeConfig {
enable_steps_tracing: false,
no_storage_caching: false,
server_config: Default::default(),
host: None,
host: vec![IpAddr::V4(Ipv4Addr::LOCALHOST)],
transaction_order: Default::default(),
config_out: None,
genesis: None,
Expand Down Expand Up @@ -707,8 +712,8 @@ impl NodeConfig {

/// Sets the host the server will listen on
#[must_use]
pub fn with_host(mut self, host: Option<IpAddr>) -> Self {
self.host = host;
pub fn with_host(mut self, host: Vec<IpAddr>) -> Self {
self.host = if host.is_empty() { vec![IpAddr::V4(Ipv4Addr::LOCALHOST)] } else { host };
self
}

Expand Down
50 changes: 34 additions & 16 deletions anvil/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ use parking_lot::Mutex;
use std::{
future::Future,
io,
net::{IpAddr, Ipv4Addr, SocketAddr},
net::SocketAddr,
pin::Pin,
sync::Arc,
task::{Context, Poll},
Expand Down Expand Up @@ -162,15 +162,19 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) {
let node_service =
tokio::task::spawn(NodeService::new(pool, backend, miner, fee_history_service, filters));

let host = config.host.unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST));
let mut addr = SocketAddr::new(host, port);
let mut servers = Vec::new();
let mut addresses = Vec::new();

// configure the rpc server and use its actual local address
let server = server::serve(addr, api.clone(), server_config);
addr = server.local_addr();
for addr in config.host.iter() {
0xZerohero marked this conversation as resolved.
Show resolved Hide resolved
let sock_addr = SocketAddr::new(addr.to_owned(), port);
let srv = server::serve(sock_addr, api.clone(), server_config.clone());

// spawn the server on a new task
let serve = tokio::task::spawn(server.map_err(NodeError::from));
addresses.push(srv.local_addr());

// spawn the server on a new task
let srv = tokio::task::spawn(srv.map_err(NodeError::from));
servers.push(srv);
}

let tokio_handle = Handle::current();
let (signal, on_shutdown) = shutdown::signal();
Expand All @@ -181,9 +185,9 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) {
let handle = NodeHandle {
config,
node_service,
server: serve,
servers,
ipc_task,
address: addr,
addresses,
_signal: Some(signal),
task_manager,
};
Expand All @@ -201,11 +205,11 @@ type IpcTask = JoinHandle<io::Result<()>>;
pub struct NodeHandle {
config: NodeConfig,
/// The address of the running rpc server
address: SocketAddr,
addresses: Vec<SocketAddr>,
/// Join handle for the Node Service
pub node_service: JoinHandle<Result<(), NodeError>>,
/// Join handle for the Anvil server
pub server: JoinHandle<Result<(), NodeError>>,
/// Join handles (one per socket) for the Anvil server.
pub servers: Vec<JoinHandle<Result<(), NodeError>>>,
// The future that joins the ipc server, if any
ipc_task: Option<IpcTask>,
/// A signal that fires the shutdown, fired on drop.
Expand All @@ -224,7 +228,14 @@ impl NodeHandle {
pub(crate) fn print(&self, fork: Option<&ClientFork>) {
self.config.print(fork);
if !self.config.silent {
println!("Listening on {}", self.socket_address())
println!(
"Listening on {}",
self.addresses
.iter()
.map(|addr| { addr.to_string() })
.collect::<Vec<String>>()
.join(", ")
)
}
}

Expand All @@ -233,7 +244,7 @@ impl NodeHandle {
/// **N.B.** this may not necessarily be the same `host + port` as configured in the
/// `NodeConfig`, if port was set to 0, then the OS auto picks an available port
pub fn socket_address(&self) -> &SocketAddr {
&self.address
&self.addresses[0]
}

/// Returns the http endpoint
Expand Down Expand Up @@ -352,7 +363,14 @@ impl Future for NodeHandle {
return Poll::Ready(res)
}

pin.server.poll_unpin(cx)
// poll the axum server handles
for server in pin.servers.iter_mut() {
if let Poll::Ready(res) = server.poll_unpin(cx) {
return Poll::Ready(res)
}
}

Poll::Pending
}
}

Expand Down