mod diagnostics;
#[cfg(test)]
mod tests;
use self::diagnostics::{
print_bootstrap_failure_diagnostics, print_bootstrap_status, print_current_registry_roles,
print_root_diagnostics,
};
use crate::{
canister_ready::query_canister_ready,
icp::{IcpCli, LocalReplicaTarget, decode_json_response},
release_set::icp_query_on_network,
replica_query,
};
use std::{path::Path, thread, time::Duration};
use canic_core::{dto::state::BootstrapStatusResponse, protocol};
pub(super) fn wait_for_root_ready(
icp_root: &Path,
network: &str,
root_canister: &str,
timeout_seconds: u64,
local_replica: Option<&LocalReplicaTarget>,
) -> Result<(), Box<dyn std::error::Error>> {
let start = std::time::Instant::now();
let mut next_report = 0_u64;
println!("Waiting for {root_canister} to report canic_ready (timeout {timeout_seconds}s)");
loop {
if root_ready(icp_root, network, root_canister, local_replica)? {
println!(
"{root_canister} reported canic_ready after {}s",
start.elapsed().as_secs()
);
return Ok(());
}
let status = root_bootstrap_status(icp_root, network, root_canister, local_replica)?;
if let Some(last_error) = status.last_error.as_deref() {
print_bootstrap_failure_diagnostics(
icp_root,
network,
root_canister,
local_replica,
&status,
last_error,
);
return Err(format!(
"root bootstrap failed during phase '{}' : {}",
status.phase, last_error
)
.into());
}
let elapsed = start.elapsed().as_secs();
if elapsed >= timeout_seconds {
eprintln!("root did not report canic_ready within {timeout_seconds}s");
print_root_diagnostics(icp_root, network, root_canister, local_replica);
return Err("root did not become ready".into());
}
if elapsed >= next_report {
println!("Still waiting for {root_canister} canic_ready ({elapsed}s elapsed)");
print_current_bootstrap_status(icp_root, network, root_canister, local_replica)?;
print_current_registry_roles(icp_root, network, root_canister, local_replica);
next_report = elapsed + 5;
}
thread::sleep(Duration::from_secs(1));
}
}
fn root_ready(
icp_root: &Path,
network: &str,
root_canister: &str,
local_replica: Option<&LocalReplicaTarget>,
) -> Result<bool, Box<dyn std::error::Error>> {
let icp = IcpCli::new("icp", Some(network.to_string()), None)
.with_cwd(icp_root)
.with_local_replica(local_replica.cloned());
query_canister_ready(&icp, root_canister, network, Some(icp_root), None).map_err(Into::into)
}
fn root_bootstrap_status(
icp_root: &Path,
network: &str,
root_canister: &str,
local_replica: Option<&LocalReplicaTarget>,
) -> Result<BootstrapStatusResponse, Box<dyn std::error::Error>> {
if replica_query::should_use_local_replica_query(Some(network)) {
return replica_query::query_bootstrap_status_from_root(
Some(network),
root_canister,
icp_root,
)
.map_err(Into::into);
}
let output = icp_query_on_network(
icp_root,
network,
local_replica,
root_canister,
protocol::CANIC_BOOTSTRAP_STATUS,
None,
Some("json"),
)?;
decode_json_response(&output).map_err(Into::into)
}
fn print_current_bootstrap_status(
icp_root: &Path,
network: &str,
root_canister: &str,
local_replica: Option<&LocalReplicaTarget>,
) -> Result<(), Box<dyn std::error::Error>> {
let status = root_bootstrap_status(icp_root, network, root_canister, local_replica)?;
print_bootstrap_status(&status);
Ok(())
}