Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/bin/ant-devnet/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,17 @@ pub struct Cli {

/// Start a local Anvil blockchain for EVM payment verification.
/// Starts Anvil, deploys contracts, and configures all nodes to verify
/// payments against the local chain.
/// payments against the local chain. With `--host`, Anvil binds the same
/// LAN IP (unless `ANVIL_IP_ADDR` overrides it) so the manifest's
/// `rpc_url` is reachable from other devices — external signers on the
/// LAN can pay, not just download.
#[arg(long)]
pub enable_evm: bool,

/// Advertise this IPv4 to peers/clients and bind 0.0.0.0, so the devnet is
/// reachable from other devices on the LAN. When omitted, nodes bind
/// loopback (127.0.0.1) as before (single-machine only).
/// loopback (127.0.0.1) as before (single-machine only). Also becomes
/// Anvil's bind/publish address under `--enable-evm` (see there).
#[arg(long)]
pub host: Option<std::net::Ipv4Addr>,

Expand Down
69 changes: 67 additions & 2 deletions src/bin/ant-devnet/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,13 @@ async fn main() -> color_eyre::Result<()> {
));
}
config.advertise_ip = cli.host;
let evm_info =
resolve_evm_info(cli.evm_network.as_deref(), cli.enable_evm, &mut config).await?;
let evm_info = resolve_evm_info(
cli.evm_network.as_deref(),
cli.enable_evm,
cli.host,
&mut config,
)
.await?;

let mut devnet = Devnet::new(config).await?;
devnet.start().await?;
Expand Down Expand Up @@ -141,6 +146,7 @@ async fn main() -> color_eyre::Result<()> {
async fn resolve_evm_info(
evm_network: Option<&str>,
enable_evm: bool,
host: Option<std::net::Ipv4Addr>,
config: &mut DevnetConfig,
) -> color_eyre::Result<Option<DevnetEvmInfo>> {
if let Some(net_name) = evm_network {
Expand All @@ -166,6 +172,19 @@ async fn resolve_evm_info(
payment_vault_address: vault_addr,
}))
} else if enable_evm {
// Anvil binds — and evmlib publishes in the manifest's `rpc_url` —
// the address in `ANVIL_IP_ADDR`, defaulting to localhost. A LAN
// devnet must expose the chain the way it exposes the nodes, or the
// published `rpc_url` is unreachable from every other device —
// exactly the audience `--host` serves (external signers can join
// and download, but not pay). An explicit `ANVIL_IP_ADDR` wins.
if let Some(anvil_ip) = anvil_ip_for_lan(host, std::env::var_os("ANVIL_IP_ADDR")) {
ant_node::logging::info!(
"Binding Anvil to {anvil_ip} (via ANVIL_IP_ADDR) so LAN clients \
can reach the manifest's rpc_url"
);
std::env::set_var("ANVIL_IP_ADDR", anvil_ip);
}
ant_node::logging::info!("Starting local Anvil blockchain for EVM payment enforcement...");
let testnet = evmlib::testnet::Testnet::new()
.await
Expand Down Expand Up @@ -319,3 +338,49 @@ fn spawn_manifest_server(
}
});
}

/// The Anvil bind/publish address a LAN devnet should use: the `--host` IP,
/// unless the operator set `ANVIL_IP_ADDR` explicitly (their override wins),
/// or there is no `--host` (loopback devnet — keep Anvil's localhost
/// default). Returns the value to write into `ANVIL_IP_ADDR`, or `None` to
/// leave the environment untouched.
fn anvil_ip_for_lan(
host: Option<std::net::Ipv4Addr>,
existing_override: Option<std::ffi::OsString>,
) -> Option<String> {
match (host, existing_override) {
(Some(host), None) => Some(host.to_string()),
_ => None,
}
}

#[cfg(test)]
mod tests {
use super::anvil_ip_for_lan;
use std::net::Ipv4Addr;

#[test]
fn lan_host_becomes_anvil_ip() {
assert_eq!(
anvil_ip_for_lan(Some(Ipv4Addr::new(192, 168, 0, 61)), None),
Some("192.168.0.61".to_string())
);
}

#[test]
fn explicit_override_wins() {
assert_eq!(
anvil_ip_for_lan(
Some(Ipv4Addr::new(192, 168, 0, 61)),
Some("10.0.0.9".into())
),
None
);
}

#[test]
fn loopback_devnet_keeps_anvil_default() {
assert_eq!(anvil_ip_for_lan(None, None), None);
assert_eq!(anvil_ip_for_lan(None, Some("10.0.0.9".into())), None);
}
}
Loading