From b863ae22ad8d40d1aef724f5e2d388aa34406d2e Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Wed, 14 May 2025 14:58:04 +0800 Subject: [PATCH 01/12] Use the pocket-ic running in the docker container. --- src/dfx/src/actors/pocketic.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index f3f7bd80f6..38fa4c62cf 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -145,7 +145,7 @@ impl PocketIc { fn send_ready_signal(&self, port: u16) { for sub in &self.ready_subscribers { sub.do_send(PortReadySignal { - url: format!("http://localhost:{port}/instances/0/"), + url: format!("http://0.0.0.0:{port}/instances/0/"), }); } } @@ -185,7 +185,7 @@ impl Handler for PocketIc { // If we have a port, send that we're already ready! Yeah! if let Some(port) = self.port { msg.0.do_send(PortReadySignal { - url: format!("http://localhost:{port}/instances/0/"), + url: format!("http://0.0.0.0:{port}/instances/0/"), }); } @@ -267,7 +267,7 @@ fn pocketic_start_thread( ); } - let port = match PocketIc::wait_for_ready(&config.port_file, receiver.clone()) { + let _port = match PocketIc::wait_for_ready(&config.port_file, receiver.clone()) { Ok(p) => p, Err(e) => { let _ = child.kill(); @@ -281,6 +281,8 @@ fn pocketic_start_thread( } } }; + + let port : u16 = 8081; let instance = match initialize_pocketic( port, &config.effective_config_path, @@ -375,7 +377,7 @@ async fn initialize_pocketic( } } let resp = init_client - .post(format!("http://localhost:{port}/instances")) + .post(format!("http://0.0.0.0:{port}/instances")) .json(&InstanceConfig { subnet_config_set, state_dir: Some(replica_config.state_manager.state_root.clone()), @@ -409,7 +411,7 @@ async fn initialize_pocketic( }; init_client .post(format!( - "http://localhost:{port}/instances/{instance}/update/set_time" + "http://0.0.0.0:{port}/instances/{instance}/update/set_time" )) .json(&RawTime { nanos_since_epoch: OffsetDateTime::now_utc() @@ -422,7 +424,7 @@ async fn initialize_pocketic( .error_for_status()?; init_client .post(format!( - "http://localhost:{port}/instances/{instance}/auto_progress" + "http://0.0.0.0:{port}/instances/{instance}/auto_progress" )) .json(&AutoProgressConfig { artificial_delay_ms: Some(replica_config.artificial_delay as u64), @@ -431,7 +433,7 @@ async fn initialize_pocketic( .await? .error_for_status()?; - let agent_url = format!("http://localhost:{port}/instances/{instance}/"); + let agent_url = format!("http://0.0.0.0:{port}/instances/{instance}/"); debug!(logger, "Waiting for replica to report healthy status"); crate::lib::replica::status::ping_and_wait(&agent_url).await?; @@ -464,7 +466,7 @@ async fn shutdown_pocketic(port: u16, instance: usize, logger: Logger) -> DfxRes let shutdown_client = Client::new(); debug!(logger, "Sending shutdown request to PocketIC server"); shutdown_client - .delete(format!("http://localhost:{port}/instances/{instance}")) + .delete(format!("http://0.0.0.0:{port}/instances/{instance}")) .send() .await? .error_for_status()?; From 3048741c4cef71575685441c19a08708b29654cc Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Wed, 4 Jun 2025 00:35:13 +0800 Subject: [PATCH 02/12] Start the pocket-ic docker container and retrieve the port from it. --- src/dfx/src/actors/mod.rs | 2 + src/dfx/src/actors/pocketic.rs | 111 +++++++++++++++++---------- src/dfx/src/actors/pocketic_proxy.rs | 4 +- src/dfx/src/commands/start.rs | 6 ++ 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/src/dfx/src/actors/mod.rs b/src/dfx/src/actors/mod.rs index c9a418b424..cc3ecf34dd 100644 --- a/src/dfx/src/actors/mod.rs +++ b/src/dfx/src/actors/mod.rs @@ -57,6 +57,7 @@ pub fn start_pocketic_actor( local_server_descriptor: &LocalServerDescriptor, shutdown_controller: Addr, pocketic_port_path: PathBuf, + docker: bool, ) -> DfxResult> { let pocketic_path = env.get_cache().get_binary_command_path(env, "pocket-ic")?; @@ -80,6 +81,7 @@ pub fn start_pocketic_actor( }; let actor_config = pocketic::Config { pocketic_path, + docker, effective_config_path: local_server_descriptor.effective_config_path(), replica_config, bitcoind_addr: local_server_descriptor.bitcoin.nodes.clone(), diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index 38fa4c62cf..a904b63bd1 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -48,6 +48,7 @@ pub mod signals { #[derive(Clone)] pub struct Config { pub pocketic_path: PathBuf, + pub docker: bool, pub effective_config_path: PathBuf, pub replica_config: ReplicaConfig, pub bitcoind_addr: Option>, @@ -106,16 +107,33 @@ impl PocketIc { fn wait_for_ready( port_file_path: &Path, shutdown_signal: Receiver<()>, + docker: bool, ) -> Result> { let mut retries = 0; loop { - if let Ok(content) = std::fs::read_to_string(port_file_path) { - if content.ends_with('\n') { - if let Ok(port) = content.trim().parse::() { + if docker { + let output = std::process::Command::new("docker") + .args(["exec", "pocket-ic", "cat", "pocket-ic-port"]) + .output(); + if let Ok(output) = output { + if let Ok(port) = String::from_utf8(output.stdout) + .unwrap() + .trim() + .parse::() + { return Ok(port); } } + } else { + if let Ok(content) = std::fs::read_to_string(port_file_path) { + if content.ends_with('\n') { + if let Ok(port) = content.trim().parse::() { + return Ok(port); + } + } + } } + if shutdown_signal.try_recv().is_ok() { return Err(Break(())); } @@ -230,44 +248,59 @@ fn pocketic_start_thread( ) -> DfxResult> { let thread_handler = move || { loop { - // Start the process, then wait for the file. - let pocketic_path = config.pocketic_path.as_os_str(); + let last_start: Instant; + let mut child = if config.docker { + let mut cmd = std::process::Command::new("docker"); + cmd.args(["start", "pocket-ic"]); + last_start = std::time::Instant::now(); + cmd.spawn().expect("Could not start PocketIC.") + } else { + // Start the process, then wait for the file. + let pocketic_path = config.pocketic_path.as_os_str(); + + // form the pocket-ic command here similar to the ic-starter command + let mut cmd = std::process::Command::new(pocketic_path); + if let Some(port) = config.port { + cmd.args(["--port", &port.to_string()]); + }; + cmd.args([ + "--port-file", + &config.port_file.to_string_lossy(), + "--ttl", + "2592000", + ]); + cmd.args([ + "--log-levels", + &config.replica_config.log_level.to_pocketic_string(), + ]); + cmd.stdout(std::process::Stdio::inherit()); + cmd.stderr(std::process::Stdio::inherit()); + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + cmd.process_group(0); + } + let _ = std::fs::remove_file(&config.port_file); + last_start = std::time::Instant::now(); + debug!(logger, "Starting PocketIC..."); + let child = cmd.spawn().expect("Could not start PocketIC."); + + if let Err(e) = std::fs::write(&config.pid_file, child.id().to_string()) { + warn!( + logger, + "Failed to write PocketIC PID to {}: {e}", + config.pid_file.display() + ); + } - // form the pocket-ic command here similar to the ic-starter command - let mut cmd = std::process::Command::new(pocketic_path); - if let Some(port) = config.port { - cmd.args(["--port", &port.to_string()]); + child }; - cmd.args([ - "--port-file", - &config.port_file.to_string_lossy(), - "--ttl", - "2592000", - ]); - cmd.args([ - "--log-levels", - &config.replica_config.log_level.to_pocketic_string(), - ]); - cmd.stdout(std::process::Stdio::inherit()); - cmd.stderr(std::process::Stdio::inherit()); - #[cfg(unix)] - { - use std::os::unix::process::CommandExt; - cmd.process_group(0); - } - let _ = std::fs::remove_file(&config.port_file); - let last_start = std::time::Instant::now(); - debug!(logger, "Starting PocketIC..."); - let mut child = cmd.spawn().expect("Could not start PocketIC."); - if let Err(e) = std::fs::write(&config.pid_file, child.id().to_string()) { - warn!( - logger, - "Failed to write PocketIC PID to {}: {e}", - config.pid_file.display() - ); - } - let _port = match PocketIc::wait_for_ready(&config.port_file, receiver.clone()) { + let port = match PocketIc::wait_for_ready( + &config.port_file, + receiver.clone(), + config.docker, + ) { Ok(p) => p, Err(e) => { let _ = child.kill(); @@ -281,8 +314,8 @@ fn pocketic_start_thread( } } }; + println!("port: {}", port); - let port : u16 = 8081; let instance = match initialize_pocketic( port, &config.effective_config_path, diff --git a/src/dfx/src/actors/pocketic_proxy.rs b/src/dfx/src/actors/pocketic_proxy.rs index b571643ea4..09e69eaf0a 100644 --- a/src/dfx/src/actors/pocketic_proxy.rs +++ b/src/dfx/src/actors/pocketic_proxy.rs @@ -271,7 +271,7 @@ fn pocketic_proxy_start_thread( .expect("Could not write to pocketic-proxy-pid file."); std::fs::write(&pocketic_proxy_pid_path, child.id().to_string()) .expect("Could not write to pocketic-proxy-pid file."); - let port = + let _port = match PocketIcProxy::wait_for_ready(&pocketic_proxy_port_path, receiver.clone()) { Ok(p) => p, Err(e) => { @@ -286,6 +286,8 @@ fn pocketic_proxy_start_thread( } } }; + + let port: u16 = 8082; let instance = match initialize_gateway( format!("http://localhost:{port}").parse().unwrap(), replica_url.clone(), diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index b37e36aa44..17fe78a6f9 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -86,6 +86,10 @@ pub struct StartOpts { #[clap(long, hide = true)] replica: bool, + + /// Runs PocketIC in docker container. + #[arg(long)] + docker: bool, } // The frontend webserver is brought up by the bg process; thus, the fg process @@ -154,6 +158,7 @@ pub fn exec( domain, pocketic: _, replica, + docker, }: StartOpts, ) -> DfxResult { ensure!(!replica, "The 'native' replica (--replica) is no longer supported. See the 0.27.0 migration guide for more information. @@ -321,6 +326,7 @@ https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-g local_server_descriptor, shutdown_controller.clone(), pocketic_port_path, + docker, )?; server.recipient() }; From 679b976db353db0e67b0d55eeb6f21e15c071857 Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Wed, 4 Jun 2025 16:54:02 +0800 Subject: [PATCH 03/12] Run docker logs to keep a running child process, and use localhost instead of 0.0.0.0 for pocket-ic server. --- src/dfx/src/actors/pocketic.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index a904b63bd1..b1e32f8e09 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -163,7 +163,7 @@ impl PocketIc { fn send_ready_signal(&self, port: u16) { for sub in &self.ready_subscribers { sub.do_send(PortReadySignal { - url: format!("http://0.0.0.0:{port}/instances/0/"), + url: format!("http://localhost:{port}/instances/0/"), }); } } @@ -203,7 +203,7 @@ impl Handler for PocketIc { // If we have a port, send that we're already ready! Yeah! if let Some(port) = self.port { msg.0.do_send(PortReadySignal { - url: format!("http://0.0.0.0:{port}/instances/0/"), + url: format!("http://localhost:{port}/instances/0/"), }); } @@ -253,7 +253,13 @@ fn pocketic_start_thread( let mut cmd = std::process::Command::new("docker"); cmd.args(["start", "pocket-ic"]); last_start = std::time::Instant::now(); - cmd.spawn().expect("Could not start PocketIC.") + cmd.spawn().expect("Could not start PocketIC."); + + let mut cmd = std::process::Command::new("docker"); + cmd.args(["logs", "-f", "pocket-ic"]); + cmd.stdout(std::process::Stdio::inherit()); + let child = cmd.spawn().expect("Could not stream logs."); + child } else { // Start the process, then wait for the file. let pocketic_path = config.pocketic_path.as_os_str(); @@ -410,7 +416,7 @@ async fn initialize_pocketic( } } let resp = init_client - .post(format!("http://0.0.0.0:{port}/instances")) + .post(format!("http://localhost:{port}/instances")) .json(&InstanceConfig { subnet_config_set, state_dir: Some(replica_config.state_manager.state_root.clone()), @@ -444,7 +450,7 @@ async fn initialize_pocketic( }; init_client .post(format!( - "http://0.0.0.0:{port}/instances/{instance}/update/set_time" + "http://localhost:{port}/instances/{instance}/update/set_time" )) .json(&RawTime { nanos_since_epoch: OffsetDateTime::now_utc() @@ -457,7 +463,7 @@ async fn initialize_pocketic( .error_for_status()?; init_client .post(format!( - "http://0.0.0.0:{port}/instances/{instance}/auto_progress" + "http://localhost:{port}/instances/{instance}/auto_progress" )) .json(&AutoProgressConfig { artificial_delay_ms: Some(replica_config.artificial_delay as u64), @@ -466,7 +472,7 @@ async fn initialize_pocketic( .await? .error_for_status()?; - let agent_url = format!("http://0.0.0.0:{port}/instances/{instance}/"); + let agent_url = format!("http://localhost:{port}/instances/{instance}/"); debug!(logger, "Waiting for replica to report healthy status"); crate::lib::replica::status::ping_and_wait(&agent_url).await?; @@ -499,7 +505,7 @@ async fn shutdown_pocketic(port: u16, instance: usize, logger: Logger) -> DfxRes let shutdown_client = Client::new(); debug!(logger, "Sending shutdown request to PocketIC server"); shutdown_client - .delete(format!("http://0.0.0.0:{port}/instances/{instance}")) + .delete(format!("http://localhost:{port}/instances/{instance}")) .send() .await? .error_for_status()?; From c8c8619dceebe08ec70bf7ffe55a7147da9a31cb Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Wed, 4 Jun 2025 17:10:41 +0800 Subject: [PATCH 04/12] Add comments. --- src/dfx/src/actors/pocketic.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index b1e32f8e09..8d257dca8f 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -250,11 +250,14 @@ fn pocketic_start_thread( loop { let last_start: Instant; let mut child = if config.docker { + // Start the container. + // TODO: Should we always run a clean new container from the image as the container will keep the status for previous runs? let mut cmd = std::process::Command::new("docker"); cmd.args(["start", "pocket-ic"]); last_start = std::time::Instant::now(); cmd.spawn().expect("Could not start PocketIC."); + // Stream the logs to the console. let mut cmd = std::process::Command::new("docker"); cmd.args(["logs", "-f", "pocket-ic"]); cmd.stdout(std::process::Stdio::inherit()); From b3f3840b31db88f37812956e491e879deda11c55 Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Thu, 5 Jun 2025 16:25:16 +0800 Subject: [PATCH 05/12] Temporarily disable to allow 8080 to be used in the docker. --- src/dfx/src/commands/start.rs | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 17fe78a6f9..d828c11c1c 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -10,7 +10,7 @@ use crate::lib::info::replica_rev; use crate::lib::integrations::status::wait_for_integrations_initialized; use crate::lib::network::id::write_network_id; use crate::lib::replica::status::ping_and_wait; -use crate::util::get_reusable_socket_addr; +//use crate::util::get_reusable_socket_addr; use actix::Recipient; use anyhow::{anyhow, bail, ensure, Context, Error}; use clap::{ArgAction, Parser}; @@ -499,17 +499,18 @@ fn send_background() -> DfxResult<()> { #[context("Failed to get frontend address.")] fn frontend_address( local_server_descriptor: &LocalServerDescriptor, - background: bool, + _background: bool, ) -> DfxResult<(String, SocketAddr)> { - let mut address_and_port = local_server_descriptor.bind_address; - - if !background { - // Since the user may have provided port "0", we need to grab a dynamically - // allocated port and construct a resuable SocketAddr which the actix - // HttpServer will bind to - address_and_port = - get_reusable_socket_addr(address_and_port.ip(), address_and_port.port())?; - } + let address_and_port = local_server_descriptor.bind_address; + + // TODO: Temporarily disabled to allow for port 8080 to be used. + // if !background { + // // Since the user may have provided port "0", we need to grab a dynamically + // // allocated port and construct a resuable SocketAddr which the actix + // // HttpServer will bind to + // address_and_port = + // get_reusable_socket_addr(address_and_port.ip(), address_and_port.port())?; + // } let ip = if address_and_port.is_ipv6() { format!("[{}]", address_and_port.ip()) } else { From 23765b0e56201bda43105135eb3bca7aed343bc3 Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Mon, 9 Jun 2025 16:06:08 +0800 Subject: [PATCH 06/12] Run a clean container instead of starting an exising one, and remove the temporary frontend address solution. --- src/dfx/src/actors/mod.rs | 3 +++ src/dfx/src/actors/pocketic.rs | 28 ++++++++++++++++++------- src/dfx/src/commands/start.rs | 38 ++++++++++++++++++++++------------ 3 files changed, 49 insertions(+), 20 deletions(-) diff --git a/src/dfx/src/actors/mod.rs b/src/dfx/src/actors/mod.rs index cc3ecf34dd..3ef2f69349 100644 --- a/src/dfx/src/actors/mod.rs +++ b/src/dfx/src/actors/mod.rs @@ -12,6 +12,7 @@ use pocketic::BitcoinIntegrationConfig; use pocketic_proxy::signals::PortReadySubscribe; use pocketic_proxy::{PocketIcProxy, PocketIcProxyConfig}; use post_start::PostStart; +use std::net::SocketAddr; use std::path::PathBuf; pub mod pocketic; @@ -58,6 +59,7 @@ pub fn start_pocketic_actor( shutdown_controller: Addr, pocketic_port_path: PathBuf, docker: bool, + address: SocketAddr, ) -> DfxResult> { let pocketic_path = env.get_cache().get_binary_command_path(env, "pocket-ic")?; @@ -89,6 +91,7 @@ pub fn start_pocketic_actor( port: local_server_descriptor.replica.port, port_file: pocketic_port_path, pid_file: local_server_descriptor.pocketic_pid_path(), + address, shutdown_controller, logger: Some(env.get_logger().clone()), }; diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index 8d257dca8f..ff62a76719 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -56,6 +56,7 @@ pub struct Config { pub port: Option, pub port_file: PathBuf, pub pid_file: PathBuf, + pub address: SocketAddr, pub shutdown_controller: Addr, pub logger: Option, } @@ -116,8 +117,7 @@ impl PocketIc { .args(["exec", "pocket-ic", "cat", "pocket-ic-port"]) .output(); if let Ok(output) = output { - if let Ok(port) = String::from_utf8(output.stdout) - .unwrap() + if let Ok(port) = String::from_utf8_lossy(&output.stdout) .trim() .parse::() { @@ -250,14 +250,28 @@ fn pocketic_start_thread( loop { let last_start: Instant; let mut child = if config.docker { - // Start the container. - // TODO: Should we always run a clean new container from the image as the container will keep the status for previous runs? + // Run the container. let mut cmd = std::process::Command::new("docker"); - cmd.args(["start", "pocket-ic"]); + cmd.args(["run"]); + cmd.args([ + "-p", + format!("{}:{}", config.address.port(), config.address.port()).as_str(), + ]); + cmd.args(["-p", "8081:8081", "-p", "8082:8082"]); + cmd.args(["-d", "--name", "pocket-ic", "pocket-ic"]); + cmd.stdout(std::process::Stdio::piped()); + println!("cmd: {:?}", cmd.get_args()); + last_start = std::time::Instant::now(); - cmd.spawn().expect("Could not start PocketIC."); + let child = cmd.spawn().expect("Could not start PocketIC."); + + // Retrieve the container id. + // TODO: The container id will be used to remove the container when the process stops. + let output = child.wait_with_output().unwrap(); + let container_id = String::from_utf8_lossy(&output.stdout).trim().to_string(); + println!("Container id: {}", container_id); - // Stream the logs to the console. + // Stream the logs. let mut cmd = std::process::Command::new("docker"); cmd.args(["logs", "-f", "pocket-ic"]); cmd.stdout(std::process::Stdio::inherit()); diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index d828c11c1c..218061f7a8 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -10,7 +10,7 @@ use crate::lib::info::replica_rev; use crate::lib::integrations::status::wait_for_integrations_initialized; use crate::lib::network::id::write_network_id; use crate::lib::replica::status::ping_and_wait; -//use crate::util::get_reusable_socket_addr; +use crate::util::get_reusable_socket_addr; use actix::Recipient; use anyhow::{anyhow, bail, ensure, Context, Error}; use clap::{ArgAction, Parser}; @@ -209,7 +209,8 @@ https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-g clean_state(local_server_descriptor, env.get_project_temp_dir()?)?; } - let (frontend_url, address_and_port) = frontend_address(local_server_descriptor, background)?; + let (frontend_url, address_and_port) = + frontend_address(local_server_descriptor, background, docker)?; fs::create_dir_all(&local_server_descriptor.data_dir_by_settings_digest())?; @@ -327,6 +328,7 @@ https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-g shutdown_controller.clone(), pocketic_port_path, docker, + address_and_port, )?; server.recipient() }; @@ -499,18 +501,28 @@ fn send_background() -> DfxResult<()> { #[context("Failed to get frontend address.")] fn frontend_address( local_server_descriptor: &LocalServerDescriptor, - _background: bool, + background: bool, + docker: bool, ) -> DfxResult<(String, SocketAddr)> { - let address_and_port = local_server_descriptor.bind_address; - - // TODO: Temporarily disabled to allow for port 8080 to be used. - // if !background { - // // Since the user may have provided port "0", we need to grab a dynamically - // // allocated port and construct a resuable SocketAddr which the actix - // // HttpServer will bind to - // address_and_port = - // get_reusable_socket_addr(address_and_port.ip(), address_and_port.port())?; - // } + let mut address_and_port = local_server_descriptor.bind_address; + + if docker { + // Update address_and_port to use 0.0.0.0 for IPv4 and :: for IPv6 + if address_and_port.is_ipv6() { + address_and_port.set_ip(std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED)); + } else { + address_and_port.set_ip(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)); + }; + } + + if !background { + // Since the user may have provided port "0", we need to grab a dynamically + // allocated port and construct a resuable SocketAddr which the actix + // HttpServer will bind to + address_and_port = + get_reusable_socket_addr(address_and_port.ip(), address_and_port.port())?; + } + let ip = if address_and_port.is_ipv6() { format!("[{}]", address_and_port.ip()) } else { From 5ee1bc635673622b9d76ca192699f1fe0ad0f7bc Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Thu, 12 Jun 2025 17:54:52 +0800 Subject: [PATCH 07/12] Add docker image argument and polish the code a little bit. --- src/dfx/src/actors/mod.rs | 7 ++---- src/dfx/src/actors/pocketic.rs | 44 +++++++++++++++++++++------------- src/dfx/src/commands/start.rs | 18 +++----------- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/dfx/src/actors/mod.rs b/src/dfx/src/actors/mod.rs index 9602fc6f33..2a79158933 100644 --- a/src/dfx/src/actors/mod.rs +++ b/src/dfx/src/actors/mod.rs @@ -10,7 +10,6 @@ use dfx_core::config::model::replica_config::ReplicaConfig; use fn_error_context::context; use pocketic::BitcoinIntegrationConfig; use post_start::PostStart; -use std::net::SocketAddr; use std::path::PathBuf; pub mod pocketic; @@ -34,8 +33,7 @@ pub fn start_pocketic_actor( shutdown_controller: Addr, pocketic_port_path: PathBuf, pocketic_proxy_config: pocketic::PocketIcProxyConfig, - docker: bool, - address: SocketAddr, + docker: Option, ) -> DfxResult> { let pocketic_path = env.get_cache().get_binary_command_path(env, "pocket-ic")?; @@ -59,7 +57,6 @@ pub fn start_pocketic_actor( }; let actor_config = pocketic::Config { pocketic_path, - docker, effective_config_path: local_server_descriptor.effective_config_path(), replica_config, bitcoind_addr: local_server_descriptor.bitcoin.nodes.clone(), @@ -67,10 +64,10 @@ pub fn start_pocketic_actor( port: local_server_descriptor.replica.port, port_file: pocketic_port_path, pid_file: local_server_descriptor.pocketic_pid_path(), - address, shutdown_controller, logger: Some(env.get_logger().clone()), pocketic_proxy_config, + docker, }; Ok(pocketic::PocketIc::new(actor_config).start()) } diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index 8921f15a45..edfc38b847 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -56,7 +56,6 @@ pub struct PocketIcProxyConfig { #[derive(Clone)] pub struct Config { pub pocketic_path: PathBuf, - pub docker: bool, pub effective_config_path: PathBuf, pub replica_config: ReplicaConfig, pub bitcoind_addr: Option>, @@ -64,10 +63,10 @@ pub struct Config { pub port: Option, pub port_file: PathBuf, pub pid_file: PathBuf, - pub address: SocketAddr, pub shutdown_controller: Addr, pub logger: Option, pub pocketic_proxy_config: PocketIcProxyConfig, + pub docker: Option, } #[derive(Clone)] @@ -117,13 +116,13 @@ impl PocketIc { fn wait_for_ready( port_file_path: &Path, shutdown_signal: Receiver<()>, - docker: bool, + docker_container_id: Option, ) -> Result> { let mut retries = 0; loop { - if docker { + if let Some(container_id) = docker_container_id.as_ref() { let output = std::process::Command::new("docker") - .args(["exec", "pocket-ic", "cat", "pocket-ic-port"]) + .args(["exec", container_id.as_str(), "cat", "pocket-ic-port"]) .output(); if let Ok(output) = output { if let Ok(port) = String::from_utf8_lossy(&output.stdout) @@ -256,35 +255,36 @@ fn pocketic_start_thread( ) -> DfxResult> { let thread_handler = move || { loop { - let last_start: Instant; - let mut child = if config.docker { + let mut bind_address = config.pocketic_proxy_config.bind; + let (mut child, docker_container_id, last_start) = if let Some(docker_image) = config.docker.as_ref() { // Run the container. let mut cmd = std::process::Command::new("docker"); cmd.args(["run"]); + bind_address = convert_to_docker_bind_address(&bind_address); cmd.args([ "-p", - format!("{}:{}", config.address.port(), config.address.port()).as_str(), + format!("{}:{}", bind_address.port(), bind_address.port()).as_str(), ]); cmd.args(["-p", "8081:8081", "-p", "8082:8082"]); - cmd.args(["-d", "--name", "pocket-ic", "pocket-ic"]); + cmd.args(["-d", docker_image.as_str()]); cmd.stdout(std::process::Stdio::piped()); println!("cmd: {:?}", cmd.get_args()); - last_start = std::time::Instant::now(); + let last_start = std::time::Instant::now(); let child = cmd.spawn().expect("Could not start PocketIC."); // Retrieve the container id. // TODO: The container id will be used to remove the container when the process stops. let output = child.wait_with_output().unwrap(); let container_id = String::from_utf8_lossy(&output.stdout).trim().to_string(); - println!("Container id: {}", container_id); // Stream the logs. let mut cmd = std::process::Command::new("docker"); - cmd.args(["logs", "-f", "pocket-ic"]); + cmd.args(["logs", "-f", container_id.as_ref()]); cmd.stdout(std::process::Stdio::inherit()); let child = cmd.spawn().expect("Could not stream logs."); - child + + (child, Some(container_id), last_start) } else { // Start the process, then wait for the file. let pocketic_path = config.pocketic_path.as_os_str(); @@ -309,7 +309,7 @@ fn pocketic_start_thread( cmd.process_group(0); } let _ = std::fs::remove_file(&config.port_file); - last_start = std::time::Instant::now(); + let last_start = std::time::Instant::now(); debug!(logger, "Starting PocketIC..."); let child = cmd.spawn().expect("Could not start PocketIC."); @@ -321,13 +321,13 @@ fn pocketic_start_thread( ); } - child + (child, None, last_start) }; let port = match PocketIc::wait_for_ready( &config.port_file, receiver.clone(), - config.docker, + docker_container_id, ) { Ok(p) => p, Err(e) => { @@ -371,7 +371,7 @@ fn pocketic_start_thread( format!("http://localhost:{port}").parse().unwrap(), server_instance, config.pocketic_proxy_config.domains.clone(), - config.pocketic_proxy_config.bind, + bind_address, logger.clone(), ) { Err(e) => { @@ -425,6 +425,16 @@ fn pocketic_start_thread( .map_err(DfxError::from) } +fn convert_to_docker_bind_address(address: &SocketAddr) -> SocketAddr { + let mut bind_address = address.clone(); + if bind_address.is_ipv6() { + bind_address.set_ip(std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED)); + } else { + bind_address.set_ip(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)); + } + bind_address +} + #[cfg(unix)] #[tokio::main(flavor = "current_thread")] async fn initialize_pocketic( diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 54a5969425..a0b21fa440 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -83,9 +83,9 @@ pub struct StartOpts { #[clap(long, hide = true)] replica: bool, - /// Runs PocketIC in docker container. + /// Runs the given docker image which runs PocketIC process. #[arg(long)] - docker: bool, + docker: Option, } // The frontend webserver is brought up by the bg process; thus, the fg process @@ -205,8 +205,7 @@ https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-g clean_state(local_server_descriptor, env.get_project_temp_dir()?)?; } - let (frontend_url, address_and_port) = - frontend_address(local_server_descriptor, background, docker)?; + let (frontend_url, address_and_port) = frontend_address(local_server_descriptor, background)?; fs::create_dir_all(&local_server_descriptor.data_dir_by_settings_digest())?; @@ -325,7 +324,6 @@ https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-g pocketic_port_path, pocketic_proxy_config, docker, - address_and_port, )?; let post_start = start_post_start_actor(env, running_in_background, Some(server), spinner)?; @@ -482,19 +480,9 @@ fn send_background() -> DfxResult<()> { fn frontend_address( local_server_descriptor: &LocalServerDescriptor, background: bool, - docker: bool, ) -> DfxResult<(String, SocketAddr)> { let mut address_and_port = local_server_descriptor.bind_address; - if docker { - // Update address_and_port to use 0.0.0.0 for IPv4 and :: for IPv6 - if address_and_port.is_ipv6() { - address_and_port.set_ip(std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED)); - } else { - address_and_port.set_ip(std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED)); - }; - } - if !background { // Since the user may have provided port "0", we need to grab a dynamically // allocated port and construct a resuable SocketAddr which the actix From f0e26b1ea1c4e8c7ed0aaf474f63bc9023300c73 Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Thu, 12 Jun 2025 18:30:53 +0800 Subject: [PATCH 08/12] Fix format and lint. --- src/dfx/src/actors/pocketic.rs | 147 ++++++++++++++++----------------- 1 file changed, 73 insertions(+), 74 deletions(-) diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index edfc38b847..0918d7f3d8 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -132,12 +132,10 @@ impl PocketIc { return Ok(port); } } - } else { - if let Ok(content) = std::fs::read_to_string(port_file_path) { - if content.ends_with('\n') { - if let Ok(port) = content.trim().parse::() { - return Ok(port); - } + } else if let Ok(content) = std::fs::read_to_string(port_file_path) { + if content.ends_with('\n') { + if let Ok(port) = content.trim().parse::() { + return Ok(port); } } } @@ -256,73 +254,74 @@ fn pocketic_start_thread( let thread_handler = move || { loop { let mut bind_address = config.pocketic_proxy_config.bind; - let (mut child, docker_container_id, last_start) = if let Some(docker_image) = config.docker.as_ref() { - // Run the container. - let mut cmd = std::process::Command::new("docker"); - cmd.args(["run"]); - bind_address = convert_to_docker_bind_address(&bind_address); - cmd.args([ - "-p", - format!("{}:{}", bind_address.port(), bind_address.port()).as_str(), - ]); - cmd.args(["-p", "8081:8081", "-p", "8082:8082"]); - cmd.args(["-d", docker_image.as_str()]); - cmd.stdout(std::process::Stdio::piped()); - println!("cmd: {:?}", cmd.get_args()); - - let last_start = std::time::Instant::now(); - let child = cmd.spawn().expect("Could not start PocketIC."); - - // Retrieve the container id. - // TODO: The container id will be used to remove the container when the process stops. - let output = child.wait_with_output().unwrap(); - let container_id = String::from_utf8_lossy(&output.stdout).trim().to_string(); - - // Stream the logs. - let mut cmd = std::process::Command::new("docker"); - cmd.args(["logs", "-f", container_id.as_ref()]); - cmd.stdout(std::process::Stdio::inherit()); - let child = cmd.spawn().expect("Could not stream logs."); - - (child, Some(container_id), last_start) - } else { - // Start the process, then wait for the file. - let pocketic_path = config.pocketic_path.as_os_str(); - - // form the pocket-ic command here similar to the ic-starter command - let mut cmd = std::process::Command::new(pocketic_path); - if let Some(port) = config.port { - cmd.args(["--port", &port.to_string()]); - }; - cmd.args([ - "--port-file", - &config.port_file.to_string_lossy(), - "--ttl", - "2592000", - ]); - cmd.args(["--log-levels", "error"]); - cmd.stdout(std::process::Stdio::inherit()); - cmd.stderr(std::process::Stdio::inherit()); - #[cfg(unix)] - { - use std::os::unix::process::CommandExt; - cmd.process_group(0); - } - let _ = std::fs::remove_file(&config.port_file); - let last_start = std::time::Instant::now(); - debug!(logger, "Starting PocketIC..."); - let child = cmd.spawn().expect("Could not start PocketIC."); - - if let Err(e) = std::fs::write(&config.pid_file, child.id().to_string()) { - warn!( - logger, - "Failed to write PocketIC PID to {}: {e}", - config.pid_file.display() - ); - } + let (mut child, docker_container_id, last_start) = + if let Some(docker_image) = config.docker.as_ref() { + // Run the container. + let mut cmd = std::process::Command::new("docker"); + cmd.args(["run"]); + bind_address = convert_to_docker_bind_address(bind_address); + cmd.args([ + "-p", + format!("{}:{}", bind_address.port(), bind_address.port()).as_str(), + ]); + cmd.args(["-p", "8081:8081", "-p", "8082:8082"]); + cmd.args(["-d", docker_image.as_str()]); + cmd.stdout(std::process::Stdio::piped()); + println!("cmd: {:?}", cmd.get_args()); + + let last_start = std::time::Instant::now(); + let child = cmd.spawn().expect("Could not start PocketIC."); + + // Retrieve the container id. + // TODO: The container id will be used to remove the container when the process stops. + let output = child.wait_with_output().unwrap(); + let container_id = String::from_utf8_lossy(&output.stdout).trim().to_string(); + + // Stream the logs. + let mut cmd = std::process::Command::new("docker"); + cmd.args(["logs", "-f", container_id.as_ref()]); + cmd.stdout(std::process::Stdio::inherit()); + let child = cmd.spawn().expect("Could not stream logs."); + + (child, Some(container_id), last_start) + } else { + // Start the process, then wait for the file. + let pocketic_path = config.pocketic_path.as_os_str(); + + // form the pocket-ic command here similar to the ic-starter command + let mut cmd = std::process::Command::new(pocketic_path); + if let Some(port) = config.port { + cmd.args(["--port", &port.to_string()]); + }; + cmd.args([ + "--port-file", + &config.port_file.to_string_lossy(), + "--ttl", + "2592000", + ]); + cmd.args(["--log-levels", "error"]); + cmd.stdout(std::process::Stdio::inherit()); + cmd.stderr(std::process::Stdio::inherit()); + #[cfg(unix)] + { + use std::os::unix::process::CommandExt; + cmd.process_group(0); + } + let _ = std::fs::remove_file(&config.port_file); + let last_start = std::time::Instant::now(); + debug!(logger, "Starting PocketIC..."); + let child = cmd.spawn().expect("Could not start PocketIC."); - (child, None, last_start) - }; + if let Err(e) = std::fs::write(&config.pid_file, child.id().to_string()) { + warn!( + logger, + "Failed to write PocketIC PID to {}: {e}", + config.pid_file.display() + ); + } + + (child, None, last_start) + }; let port = match PocketIc::wait_for_ready( &config.port_file, @@ -425,8 +424,8 @@ fn pocketic_start_thread( .map_err(DfxError::from) } -fn convert_to_docker_bind_address(address: &SocketAddr) -> SocketAddr { - let mut bind_address = address.clone(); +fn convert_to_docker_bind_address(address: SocketAddr) -> SocketAddr { + let mut bind_address = address; if bind_address.is_ipv6() { bind_address.set_ip(std::net::IpAddr::V6(std::net::Ipv6Addr::UNSPECIFIED)); } else { From 340016e5539323798dec7402acd41813179f16c9 Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Thu, 12 Jun 2025 23:55:26 +0800 Subject: [PATCH 09/12] Only map the 8081 port. --- src/dfx/src/actors/pocketic.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index 0918d7f3d8..31ff5ba44c 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -257,17 +257,18 @@ fn pocketic_start_thread( let (mut child, docker_container_id, last_start) = if let Some(docker_image) = config.docker.as_ref() { // Run the container. + // TODO: Check if docker command is available. let mut cmd = std::process::Command::new("docker"); cmd.args(["run"]); bind_address = convert_to_docker_bind_address(bind_address); cmd.args([ "-p", format!("{}:{}", bind_address.port(), bind_address.port()).as_str(), + "-p", + "8081:8081", ]); - cmd.args(["-p", "8081:8081", "-p", "8082:8082"]); cmd.args(["-d", docker_image.as_str()]); cmd.stdout(std::process::Stdio::piped()); - println!("cmd: {:?}", cmd.get_args()); let last_start = std::time::Instant::now(); let child = cmd.spawn().expect("Could not start PocketIC."); From 63bfb30fe2083312ef7a169f1c66b51bcd82c60f Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Tue, 17 Jun 2025 19:25:23 +0800 Subject: [PATCH 10/12] Remove the container when stopping dfx. --- src/dfx/src/actors/pocketic.rs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index 31ff5ba44c..8d016570da 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -327,7 +327,7 @@ fn pocketic_start_thread( let port = match PocketIc::wait_for_ready( &config.port_file, receiver.clone(), - docker_container_id, + docker_container_id.clone(), ) { Ok(p) => p, Err(e) => { @@ -394,9 +394,13 @@ fn pocketic_start_thread( match wait_for_child_or_receiver(&mut child, &receiver) { ChildOrReceiver::Receiver => { debug!(logger, "Got signal to stop. Killing PocketIC process..."); - if let Err(e) = - shutdown_pocketic(port, server_instance, gateway_instance, logger.clone()) - { + if let Err(e) = shutdown_pocketic( + port, + server_instance, + gateway_instance, + docker_container_id, + logger.clone(), + ) { error!(logger, "Error shutting down PocketIC gracefully: {e}"); } let _ = child.kill(); @@ -612,6 +616,7 @@ async fn shutdown_pocketic( port: u16, server_instance: usize, gateway_instance: usize, + docker_container_id: Option, logger: Logger, ) -> DfxResult { use reqwest::Client; @@ -631,6 +636,14 @@ async fn shutdown_pocketic( .send() .await? .error_for_status()?; + if let Some(docker_container_id) = docker_container_id { + let output = std::process::Command::new("docker") + .args(["rm", "-f", docker_container_id.as_str()]) + .output(); + if let Err(e) = output { + error!(logger, "Failed to remove docker container: {e}"); + } + } Ok(()) } From 494c18867245d53e5ad589a2c13eb6fd4d9f1197 Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Tue, 17 Jun 2025 19:37:28 +0800 Subject: [PATCH 11/12] Check the 'docker' command if '--docker' is used. --- src/dfx/src/commands/start.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index a0b21fa440..38834a6a55 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -159,6 +159,9 @@ pub fn exec( ) -> DfxResult { ensure!(!replica, "The 'native' replica (--replica) is no longer supported. See the 0.27.0 migration guide for more information. https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-guide.md"); + if let Some(_) = docker { + check_docker_command_available()?; + } if !background { info!( env.get_logger(), @@ -532,3 +535,16 @@ pub fn empty_writable_path(path: PathBuf) -> DfxResult { .with_context(|| format!("Unable to write to {}", path.to_string_lossy()))?; Ok(path) } + +fn check_docker_command_available() -> DfxResult<()> { + if std::process::Command::new("docker") + .args(["--version"]) + .output() + .is_err() + { + bail!( + "The 'docker' command is not available. Please install Docker or remove the --docker option." + ); + } + Ok(()) +} From ee23529c850a247572591ad289eeb24ca475426c Mon Sep 17 00:00:00 2001 From: Vincent Zhang Date: Tue, 17 Jun 2025 19:50:24 +0800 Subject: [PATCH 12/12] Fixed lint. --- src/dfx/src/actors/pocketic.rs | 2 +- src/dfx/src/commands/start.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dfx/src/actors/pocketic.rs b/src/dfx/src/actors/pocketic.rs index 8d016570da..b58d33b783 100644 --- a/src/dfx/src/actors/pocketic.rs +++ b/src/dfx/src/actors/pocketic.rs @@ -648,6 +648,6 @@ async fn shutdown_pocketic( } #[cfg(not(unix))] -fn shutdown_pocketic(_: u16, _: usize, _: usize, _: Logger) -> DfxResult { +fn shutdown_pocketic(_: u16, _: usize, _: usize, _: Option, _: Logger) -> DfxResult { bail!("PocketIC not supported on this platform") } diff --git a/src/dfx/src/commands/start.rs b/src/dfx/src/commands/start.rs index 38834a6a55..6e8a34f0eb 100644 --- a/src/dfx/src/commands/start.rs +++ b/src/dfx/src/commands/start.rs @@ -159,9 +159,6 @@ pub fn exec( ) -> DfxResult { ensure!(!replica, "The 'native' replica (--replica) is no longer supported. See the 0.27.0 migration guide for more information. https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-guide.md"); - if let Some(_) = docker { - check_docker_command_available()?; - } if !background { info!( env.get_logger(), @@ -169,6 +166,9 @@ https://github.com/dfinity/sdk/blob/0.27.0/docs/migration/dfx-0.27.0-migration-g dfx_version_str() ); } + if docker.is_some() { + check_docker_command_available()?; + } let project_config = env.get_config()?; let network_descriptor_logger = if background {