diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ca39c1b..6d396375 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +* feat: Add `bitcoind-addr` and `dogecoind-addr` options for managed networks to connect to Bitcoin and Dogecoin nodes * feat: Init/call arg files now support raw binary without conversion to hex * feat!: Remove argument type inference in init/call args in commands and manifest. Args are always assumed Candid, new parameters allow specifying other formats like hex, and alternate parameters are used to specify loading from a file. * feat: Network gateway now supports a `domains` key diff --git a/crates/icp/src/context/tests.rs b/crates/icp/src/context/tests.rs index dc2a5257..a314374b 100644 --- a/crates/icp/src/context/tests.rs +++ b/crates/icp/src/context/tests.rs @@ -609,6 +609,8 @@ async fn test_get_agent_defaults_inside_project_with_default_local() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })), }, @@ -677,6 +679,8 @@ async fn test_get_agent_defaults_with_overridden_local_network() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })), }, @@ -747,6 +751,8 @@ async fn test_get_agent_defaults_with_overridden_local_environment() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })), }, @@ -767,6 +773,8 @@ async fn test_get_agent_defaults_with_overridden_local_environment() { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })), }, diff --git a/crates/icp/src/lib.rs b/crates/icp/src/lib.rs index 1e13fa54..1e8d44bf 100644 --- a/crates/icp/src/lib.rs +++ b/crates/icp/src/lib.rs @@ -426,6 +426,8 @@ impl MockProjectLoader { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })), }, @@ -446,6 +448,8 @@ impl MockProjectLoader { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })), }, diff --git a/crates/icp/src/manifest/network.rs b/crates/icp/src/manifest/network.rs index cfa2446f..8dbe535b 100644 --- a/crates/icp/src/manifest/network.rs +++ b/crates/icp/src/manifest/network.rs @@ -56,6 +56,8 @@ pub enum ManagedMode { status_dir: Option, /// Bind mounts to add to the container in the format relative_host_path:container_path[:options] mounts: Option>, + /// Extra hosts entries for Docker networking (e.g. "host.docker.internal:host-gateway") + extra_hosts: Option>, }, Launcher { /// HTTP gateway configuration @@ -68,6 +70,10 @@ pub enum ManagedMode { nns: Option, /// Configure the list of subnets (one application subnet by default) subnets: Option>, + /// Bitcoin P2P node addresses to connect to (e.g. "127.0.0.1:18444") + bitcoind_addr: Option>, + /// Dogecoin P2P node addresses to connect to + dogecoind_addr: Option>, /// The version of icp-cli-network-launcher to use. Defaults to the latest released version. Launcher versions correspond to published PocketIC or IC-OS releases. version: Option, }, @@ -81,6 +87,8 @@ impl Default for ManagedMode { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, } } @@ -264,6 +272,8 @@ mod tests { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, }) }) @@ -293,6 +303,8 @@ mod tests { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, }) }) @@ -323,6 +335,97 @@ mod tests { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, + version: None, + }) + }) + }, + ); + } + + #[test] + fn managed_network_with_dogecoind_addr() { + assert_eq!( + validate_network_yaml(indoc! {r#" + name: my-network + mode: managed + dogecoind-addr: + - "127.0.0.1:22556" + "#}), + NetworkManifest { + name: "my-network".to_string(), + configuration: Mode::Managed(Managed { + mode: Box::new(ManagedMode::Launcher { + gateway: None, + artificial_delay_ms: None, + ii: None, + nns: None, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: Some(vec!["127.0.0.1:22556".to_string()]), + version: None, + }) + }) + }, + ); + } + + #[test] + fn managed_docker_network_with_extra_hosts() { + assert_eq!( + validate_network_yaml(indoc! {r#" + name: my-network + mode: managed + image: ghcr.io/dfinity/icp-cli-network-launcher + port-mapping: + - "8000:4943" + extra-hosts: + - "host.docker.internal:host-gateway" + "#}), + NetworkManifest { + name: "my-network".to_string(), + configuration: Mode::Managed(Managed { + mode: Box::new(ManagedMode::Image { + image: "ghcr.io/dfinity/icp-cli-network-launcher".to_string(), + port_mapping: vec!["8000:4943".to_string()], + rm_on_exit: None, + args: None, + entrypoint: None, + environment: None, + volumes: None, + platform: None, + user: None, + shm_size: None, + status_dir: None, + mounts: None, + extra_hosts: Some(vec!["host.docker.internal:host-gateway".to_string()]), + }) + }) + }, + ); + } + + #[test] + fn managed_network_with_bitcoind_addr() { + assert_eq!( + validate_network_yaml(indoc! {r#" + name: my-network + mode: managed + bitcoind-addr: + - "127.0.0.1:18444" + "#}), + NetworkManifest { + name: "my-network".to_string(), + configuration: Mode::Managed(Managed { + mode: Box::new(ManagedMode::Launcher { + gateway: None, + artificial_delay_ms: None, + ii: None, + nns: None, + subnets: None, + bitcoind_addr: Some(vec!["127.0.0.1:18444".to_string()]), + dogecoind_addr: None, version: None, }) }) diff --git a/crates/icp/src/manifest/project.rs b/crates/icp/src/manifest/project.rs index c6d8de76..7559f28e 100644 --- a/crates/icp/src/manifest/project.rs +++ b/crates/icp/src/manifest/project.rs @@ -291,6 +291,8 @@ mod tests { ii: None, nns: None, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, }), }), diff --git a/crates/icp/src/network/managed/docker.rs b/crates/icp/src/network/managed/docker.rs index a73685cc..1af70486 100644 --- a/crates/icp/src/network/managed/docker.rs +++ b/crates/icp/src/network/managed/docker.rs @@ -52,6 +52,8 @@ pub struct ManagedImageOptions { pub status_dir: String, /// Parsed mounts (excluding the status directory mount, which is added at runtime). pub mounts: Vec, + /// Extra hosts entries for Docker networking (e.g. "host.docker.internal:host-gateway"). + pub extra_hosts: Vec, } impl ManagedImageOptions { @@ -160,6 +162,7 @@ impl TryFrom<&ManagedImageConfig> for ManagedImageOptions { shm_size: config.shm_size, status_dir: config.status_dir.clone(), mounts, + extra_hosts: config.extra_hosts.clone(), }) } } @@ -187,6 +190,52 @@ pub enum ManagedImageConversionError { WslPathConvert { source: WslPathConversionError }, } +/// Translates a host:port address for use inside a Docker container. +/// Replaces `127.0.0.1`, `0.0.0.0`, `localhost`, and `::1` with `host.docker.internal` +/// so the container can reach services running on the host machine. +pub(super) fn translate_addr_for_docker(addr: &str) -> String { + if let Some((host, port)) = addr.rsplit_once(':') { + let translated = match host { + "127.0.0.1" | "0.0.0.0" | "localhost" | "::1" => "host.docker.internal", + _ => host, + }; + format!("{translated}:{port}") + } else { + addr.to_string() + } +} + +/// Returns extra_hosts entries needed for Docker to resolve `host.docker.internal`. +/// On Linux, Docker Engine does not provide `host.docker.internal` by default, +/// so we add `host.docker.internal:host-gateway` when any addresses reference localhost. +pub(super) fn docker_extra_hosts_for_addrs(addrs: &[String]) -> Vec { + let needs_host_gateway = addrs.iter().any(|addr| { + addr.rsplit_once(':') + .map(|(host, _)| matches!(host, "127.0.0.1" | "0.0.0.0" | "localhost" | "::1")) + .unwrap_or(false) + }); + if needs_host_gateway { + vec!["host.docker.internal:host-gateway".to_string()] + } else { + vec![] + } +} + +/// Translates localhost addresses in `--bitcoind-addr=` and `--dogecoind-addr=` flags +/// for use inside a Docker container. +pub(super) fn translate_launcher_args_for_docker(args: Vec) -> Vec { + args.into_iter() + .map(|arg| { + for prefix in ["--bitcoind-addr=", "--dogecoind-addr="] { + if let Some(addr) = arg.strip_prefix(prefix) { + return format!("{prefix}{}", translate_addr_for_docker(addr)); + } + } + arg + }) + .collect() +} + pub async fn spawn_docker_launcher( options: &ManagedImageOptions, ) -> Result< @@ -211,6 +260,7 @@ pub async fn spawn_docker_launcher( shm_size, status_dir, mounts, + extra_hosts, } = options; // Create status tmpdir and convert path for WSL2 if needed @@ -320,6 +370,11 @@ pub async fn spawn_docker_launcher( mounts: Some(all_mounts), binds: Some(volumes.clone()), shm_size: *shm_size, + extra_hosts: if extra_hosts.is_empty() { + None + } else { + Some(extra_hosts.clone()) + }, ..<_>::default() }), ..<_>::default() diff --git a/crates/icp/src/network/managed/launcher.rs b/crates/icp/src/network/managed/launcher.rs index b1eed4ed..2146c7c2 100644 --- a/crates/icp/src/network/managed/launcher.rs +++ b/crates/icp/src/network/managed/launcher.rs @@ -175,6 +175,8 @@ pub fn launcher_settings_flags(config: &ManagedLauncherConfig) -> Vec { ii, nns, subnets, + bitcoind_addr, + dogecoind_addr, } = config; let mut flags = vec![]; if *ii { @@ -191,6 +193,16 @@ pub fn launcher_settings_flags(config: &ManagedLauncherConfig) -> Vec { flags.push(format!("--subnet={subnet}")); } } + if let Some(addrs) = &bitcoind_addr { + for addr in addrs { + flags.push(format!("--bitcoind-addr={addr}")); + } + } + if let Some(addrs) = &dogecoind_addr { + for addr in addrs { + flags.push(format!("--dogecoind-addr={addr}")); + } + } for domain in &gateway.domains { flags.push(format!("--domain={domain}")); } diff --git a/crates/icp/src/network/managed/run.rs b/crates/icp/src/network/managed/run.rs index 2923d668..e5169642 100644 --- a/crates/icp/src/network/managed/run.rs +++ b/crates/icp/src/network/managed/run.rs @@ -289,11 +289,23 @@ fn transform_native_launcher_to_container(config: &ManagedLauncherConfig) -> Man use bollard::secret::PortBinding; use std::collections::HashMap; + use super::docker::{docker_extra_hosts_for_addrs, translate_launcher_args_for_docker}; + let port = match config.gateway.port { Port::Fixed(port) => port, Port::Random => 0, }; let args = launcher_settings_flags(config); + let args = translate_launcher_args_for_docker(args); + + let all_addrs: Vec = config + .bitcoind_addr + .iter() + .chain(config.dogecoind_addr.iter()) + .flatten() + .cloned() + .collect(); + let extra_hosts = docker_extra_hosts_for_addrs(&all_addrs); let platform = if cfg!(target_arch = "aarch64") { "linux/arm64".to_string() @@ -324,6 +336,7 @@ fn transform_native_launcher_to_container(config: &ManagedLauncherConfig) -> Man shm_size: None, status_dir: "/app/status".to_string(), mounts: vec![], + extra_hosts, } } @@ -851,3 +864,161 @@ async fn install_proxy( Ok(canister_id) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::network::{Gateway, ManagedLauncherConfig, Port}; + + #[test] + fn transform_native_launcher_default_config() { + let config = ManagedLauncherConfig { + gateway: Gateway { + host: "localhost".to_string(), + port: Port::Fixed(8000), + domains: vec![], + }, + artificial_delay_ms: None, + ii: false, + nns: false, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, + version: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert_eq!( + opts.image, + "ghcr.io/dfinity/icp-cli-network-launcher:latest" + ); + assert!(opts.args.is_empty()); + assert!(opts.extra_hosts.is_empty()); + assert!(opts.rm_on_exit); + assert_eq!(opts.status_dir, "/app/status"); + let binding = opts + .port_bindings + .get("4943/tcp") + .unwrap() + .as_ref() + .unwrap(); + assert_eq!(binding[0].host_port.as_deref(), Some("8000")); + } + + #[test] + fn transform_native_launcher_random_port() { + let config = ManagedLauncherConfig { + gateway: Gateway { + host: "localhost".to_string(), + port: Port::Random, + domains: vec![], + }, + artificial_delay_ms: None, + ii: false, + nns: false, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, + version: None, + }; + let opts = transform_native_launcher_to_container(&config); + let binding = opts + .port_bindings + .get("4943/tcp") + .unwrap() + .as_ref() + .unwrap(); + assert_eq!(binding[0].host_port.as_deref(), Some("0")); + } + + #[test] + fn transform_native_launcher_with_bitcoind_addr() { + let config = ManagedLauncherConfig { + gateway: Gateway::default(), + artificial_delay_ms: None, + ii: true, + nns: false, + subnets: None, + bitcoind_addr: Some(vec!["127.0.0.1:18444".to_string()]), + dogecoind_addr: None, + version: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert!(opts.args.contains(&"--ii".to_string())); + assert!( + opts.args + .contains(&"--bitcoind-addr=host.docker.internal:18444".to_string()) + ); + assert_eq!( + opts.extra_hosts, + vec!["host.docker.internal:host-gateway".to_string()] + ); + } + + #[test] + fn transform_native_launcher_with_dogecoind_addr() { + let config = ManagedLauncherConfig { + gateway: Gateway::default(), + artificial_delay_ms: Some(50), + ii: false, + nns: true, + subnets: None, + bitcoind_addr: None, + dogecoind_addr: Some(vec!["localhost:22556".to_string()]), + version: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert!(opts.args.contains(&"--nns".to_string())); + assert!(opts.args.contains(&"--artificial-delay-ms=50".to_string())); + assert!( + opts.args + .contains(&"--dogecoind-addr=host.docker.internal:22556".to_string()) + ); + assert_eq!( + opts.extra_hosts, + vec!["host.docker.internal:host-gateway".to_string()] + ); + } + + #[test] + fn transform_native_launcher_external_addr_no_extra_hosts() { + let config = ManagedLauncherConfig { + gateway: Gateway::default(), + artificial_delay_ms: None, + ii: false, + nns: false, + subnets: None, + bitcoind_addr: Some(vec!["192.168.1.5:18444".to_string()]), + dogecoind_addr: None, + version: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert!( + opts.args + .contains(&"--bitcoind-addr=192.168.1.5:18444".to_string()) + ); + assert!(opts.extra_hosts.is_empty()); + } + + #[test] + fn transform_native_launcher_with_all_zeros_addr() { + let config = ManagedLauncherConfig { + gateway: Gateway::default(), + artificial_delay_ms: None, + ii: false, + nns: false, + subnets: None, + bitcoind_addr: Some(vec!["0.0.0.0:18444".to_string()]), + dogecoind_addr: None, + version: None, + }; + let opts = transform_native_launcher_to_container(&config); + assert!( + opts.args + .contains(&"--bitcoind-addr=host.docker.internal:18444".to_string()) + ); + assert_eq!( + opts.extra_hosts, + vec!["host.docker.internal:host-gateway".to_string()] + ); + } +} diff --git a/crates/icp/src/network/mod.rs b/crates/icp/src/network/mod.rs index f146b8e0..9394fb40 100644 --- a/crates/icp/src/network/mod.rs +++ b/crates/icp/src/network/mod.rs @@ -95,6 +95,8 @@ pub struct ManagedLauncherConfig { pub ii: bool, pub nns: bool, pub subnets: Option>, + pub bitcoind_addr: Option>, + pub dogecoind_addr: Option>, pub version: Option, } @@ -135,6 +137,8 @@ impl ManagedMode { ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })) } @@ -154,6 +158,7 @@ pub struct ManagedImageConfig { pub shm_size: Option, pub status_dir: String, pub mounts: Vec, + pub extra_hosts: Vec, } #[derive(Clone, Debug, PartialEq, JsonSchema, Deserialize, Serialize)] @@ -252,6 +257,8 @@ impl From for Configuration { ii, nns, subnets, + bitcoind_addr, + dogecoind_addr, version, } => { let gateway: Gateway = match gateway { @@ -276,6 +283,8 @@ impl From for Configuration { ii: ii.unwrap_or(false), nns: nns.unwrap_or(false), subnets, + bitcoind_addr, + dogecoind_addr, version, })), }, @@ -294,6 +303,7 @@ impl From for Configuration { shm_size, status_dir, mounts: mount, + extra_hosts, } => Configuration::Managed { managed: Managed { mode: ManagedMode::Image(Box::new(ManagedImageConfig { @@ -309,6 +319,7 @@ impl From for Configuration { shm_size, status_dir: status_dir.unwrap_or_else(|| "/app/status".to_string()), mounts: mount.unwrap_or_default(), + extra_hosts: extra_hosts.unwrap_or_default(), })), }, }, @@ -426,3 +437,51 @@ impl Access for MockNetworkAccessor { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::manifest::network::{ + Gateway as ManifestGateway, Managed as ManifestManaged, ManagedMode as ManifestManagedMode, + Mode, + }; + + #[test] + fn from_mode_launcher_with_bitcoind_addr() { + let mode = Mode::Managed(ManifestManaged { + mode: Box::new(ManifestManagedMode::Launcher { + gateway: Some(ManifestGateway { + host: None, + port: Some(8000), + domains: None, + }), + artificial_delay_ms: None, + ii: None, + nns: None, + subnets: None, + bitcoind_addr: Some(vec!["127.0.0.1:18444".to_string()]), + dogecoind_addr: None, + version: None, + }), + }); + + let config: Configuration = mode.into(); + match config { + Configuration::Managed { + managed: + Managed { + mode: ManagedMode::Launcher(launcher_config), + }, + } => { + assert_eq!( + launcher_config.bitcoind_addr, + Some(vec!["127.0.0.1:18444".to_string()]) + ); + assert_eq!(launcher_config.dogecoind_addr, None); + assert!(!launcher_config.ii); + assert!(!launcher_config.nns); + } + _ => panic!("expected ManagedMode::Launcher"), + } + } +} diff --git a/crates/icp/src/project.rs b/crates/icp/src/project.rs index d218c153..fd47905f 100644 --- a/crates/icp/src/project.rs +++ b/crates/icp/src/project.rs @@ -390,6 +390,8 @@ pub async fn consolidate_manifest( ii: false, nns: false, subnets: None, + bitcoind_addr: None, + dogecoind_addr: None, version: None, })), }, diff --git a/docs/guides/containerized-networks.md b/docs/guides/containerized-networks.md index cacf23a2..93e21cb8 100644 --- a/docs/guides/containerized-networks.md +++ b/docs/guides/containerized-networks.md @@ -158,6 +158,32 @@ networks: - POCKET_IC_MUTE_SERVER=false ``` +### Passing Arguments to the Container + +Use the `args` field to pass command-line arguments to the container's entrypoint. This is how you configure image-specific behavior such as enabling Internet Identity, NNS, Bitcoin integration, or other flags supported by the image: + +```yaml +networks: + - name: docker-local + mode: managed + image: ghcr.io/dfinity/icp-cli-network-launcher + port-mapping: + - "8000:4943" + args: + - "--ii" +``` + +The `args` field passes values directly to the container entrypoint with no processing. The Docker image determines what arguments it accepts — see the image's documentation for available options. + +**Comparison with native launcher mode:** When using native managed networks (without `image`), settings like `bitcoind-addr`, `ii`, `nns`, and `subnets` are configured as top-level YAML fields. In Docker image mode, these are passed via `args` instead, since the image could be any Docker image — not necessarily the official network launcher. + +> **Docker networking note:** When referencing services running on the host machine from inside a container (e.g., a local Bitcoin node), use `host.docker.internal` instead of `127.0.0.1` or `localhost`. Inside a container, `127.0.0.1` refers to the container's own loopback, not the host. For example: `--bitcoind-addr=host.docker.internal:18444`. Docker Desktop (macOS/Windows) resolves `host.docker.internal` automatically. On Linux Docker Engine, add the `extra-hosts` option to ensure it resolves: +> +> ```yaml +> extra-hosts: +> - "host.docker.internal:host-gateway" +> ``` + ### Remove Container on Exit Automatically delete the container when stopped: @@ -325,6 +351,7 @@ All available configuration options for containerized networks: | `user` | string | No | User to run as in `user[:group]` format (group is optional) | | `shm-size` | number | No | Size of `/dev/shm` in bytes | | `status-dir` | string | No | Status directory path (default: `/app/status`) | +| `extra-hosts` | string[] | No | Extra hosts entries (e.g., `host.docker.internal:host-gateway`) | Example with multiple options: diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 999ffc49..9ed56ed2 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -185,8 +185,15 @@ type: https://example.com/recipe.hb.yaml ## Networks +Networks define where canisters are deployed. There are two modes: + +- **Managed** (`mode: managed`): A local test network launched and controlled by icp-cli. Can run [natively](#managed-network) or in a [Docker container](#docker-network). +- **Connected** (`mode: connected`): A remote network accessed by URL. + ### Managed Network +A managed network runs the [network launcher](https://github.com/dfinity/icp-cli-network-launcher) natively on your machine. To run it in a Docker container instead, see [Docker Network](#docker-network). + ```yaml networks: - name: local-dev @@ -202,14 +209,24 @@ networks: | `mode` | string | Yes | `managed` | | `gateway.host` | string | No | Host address (default: localhost) | | `gateway.port` | integer | No | Port number (default: 8000, use 0 for random) | -| `artificial_delay_ms` | integer | No | Artificial delay to add to every update call (ms) | -| `ii` | boolean | No | Set up Internet Identity canister (default: false) | -| `nns` | boolean | No | Set up NNS canisters (default: false) | -| `subnets` | array | No | Configure subnet types (default: one application subnet) | +| `artificial-delay-ms` | integer | No | Artificial delay for update calls (ms) | +| `ii` | boolean | No | Install Internet Identity canister (default: false). Also implicitly enabled by `nns`, `bitcoind-addr`, and `dogecoind-addr`. | +| `nns` | boolean | No | Install NNS and SNS canisters (default: false). Implies `ii` and adds an SNS subnet. | +| `subnets` | array | No | Configure subnet types. See [Subnet Configuration](#subnet-configuration). | +| `bitcoind-addr` | array | No | Bitcoin P2P node addresses (e.g. `127.0.0.1:18444`). Adds a bitcoin and II subnet. | +| `dogecoind-addr` | array | No | Dogecoin P2P node addresses. Adds a bitcoin and II subnet. | + +For full details on how these settings interact, see the [network launcher CLI reference](https://github.com/dfinity/icp-cli-network-launcher#cli-reference). + +> **Note:** These settings apply to native managed networks only. For [Docker image mode](#docker-network), pass equivalent flags via the `args` field instead. #### Subnet Configuration -Configure the local network's subnet layout. By default, a single application subnet is created. Use multiple subnets to test cross-subnet (Xnet) calls: +Configure the local network's subnet layout: + +- **Default** (no `subnets` field): one application subnet is created. +- **With `subnets`**: only the listed subnets are created — the default application subnet is **replaced**, not extended. Add `application` explicitly if you still need it. +- An **NNS subnet** is always created regardless of configuration (required for system operations). ```yaml networks: @@ -223,7 +240,32 @@ networks: Available subnet types: `application`, `system`, `verified-application`, `bitcoin`, `fiduciary`, `nns`, `sns` -**Note:** Subnet type support depends on the network launcher version. The `application` type is commonly used for testing. +#### Bitcoin and Dogecoin Integration + +Connect the local network to a Bitcoin or Dogecoin node for testing chain integration: + +```yaml +networks: + - name: local + mode: managed + bitcoind-addr: + - "127.0.0.1:18444" +``` + +The `bitcoind-addr` field specifies the P2P address (not RPC) of the Bitcoin node. Multiple addresses can be specified. Dogecoin integration works the same way via `dogecoind-addr`. Both can be configured simultaneously. + +**Implicit effects:** When `bitcoind-addr` or `dogecoind-addr` is configured, the network launcher automatically adds a **bitcoin** subnet and an **II** subnet (provides threshold signing keys required for chain operations). If you also explicitly specify `subnets`, you must include `application` to keep the default application subnet: + +```yaml +networks: + - name: local + mode: managed + bitcoind-addr: + - "127.0.0.1:18444" + subnets: + - application + - system +``` ### Connected Network @@ -244,6 +286,8 @@ networks: ### Docker Network +A managed network can also run inside a Docker container. Adding the `image` field switches from native to Docker mode: + ```yaml networks: - name: docker-local @@ -253,7 +297,24 @@ networks: - "0:4943" ``` -See [Containerized Networks](../guides/containerized-networks.md) for full options. +To configure image-specific behavior (e.g., enabling Internet Identity, NNS, or Bitcoin integration), use the `args` field to pass command-line arguments to the container entrypoint: + +```yaml +networks: + - name: docker-local + mode: managed + image: ghcr.io/dfinity/icp-cli-network-launcher + port-mapping: + - "8000:4943" + args: + - "--ii" +``` + +The available arguments depend on the Docker image — see the image's documentation for details. + +> **Docker networking note:** When referencing services running on the host machine from inside a container (e.g., a local Bitcoin node), use `host.docker.internal` instead of `127.0.0.1` or `localhost`. Inside a container, `127.0.0.1` refers to the container's own loopback, not the host. For example: `--bitcoind-addr=host.docker.internal:18444`. Docker Desktop (macOS/Windows) resolves `host.docker.internal` automatically. On Linux Docker Engine, you may need to pass `--add-host=host.docker.internal:host-gateway` or equivalent to ensure it resolves. + +See [Containerized Networks](../guides/containerized-networks.md) for full configuration options. ## Environments diff --git a/docs/schemas/icp-yaml-schema.json b/docs/schemas/icp-yaml-schema.json index 192724e1..2b6756c5 100644 --- a/docs/schemas/icp-yaml-schema.json +++ b/docs/schemas/icp-yaml-schema.json @@ -493,6 +493,16 @@ "null" ] }, + "extra-hosts": { + "description": "Extra hosts entries for Docker networking (e.g. \"host.docker.internal:host-gateway\")", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "image": { "description": "The docker image to use for the network", "type": "string" @@ -578,6 +588,26 @@ "null" ] }, + "bitcoind-addr": { + "description": "Bitcoin P2P node addresses to connect to (e.g. \"127.0.0.1:18444\")", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "dogecoind-addr": { + "description": "Dogecoin P2P node addresses to connect to", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "gateway": { "anyOf": [ { diff --git a/docs/schemas/network-yaml-schema.json b/docs/schemas/network-yaml-schema.json index 04613bd7..81d8da26 100644 --- a/docs/schemas/network-yaml-schema.json +++ b/docs/schemas/network-yaml-schema.json @@ -115,6 +115,16 @@ "null" ] }, + "extra-hosts": { + "description": "Extra hosts entries for Docker networking (e.g. \"host.docker.internal:host-gateway\")", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "image": { "description": "The docker image to use for the network", "type": "string" @@ -200,6 +210,26 @@ "null" ] }, + "bitcoind-addr": { + "description": "Bitcoin P2P node addresses to connect to (e.g. \"127.0.0.1:18444\")", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, + "dogecoind-addr": { + "description": "Dogecoin P2P node addresses to connect to", + "items": { + "type": "string" + }, + "type": [ + "array", + "null" + ] + }, "gateway": { "anyOf": [ {