Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Unreleased

Important: A network launcher more recent than v12.0.0-83c3f95e8c4ce28e02493df83df5f84a166451c0 is
required to use internet identity.

* feat: Many more commands support `--json` and `--quiet`.
* feat: When a local network is started internet identity is available at id.ai.localhost
* fix: Network would fail to start if a stale descriptor was present

# v0.2.1
Expand Down
13 changes: 8 additions & 5 deletions crates/icp-canister-interfaces/src/internet_identity.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
use candid::Principal;

pub const INTERNET_IDENTITY_CID: &str = "rdmx6-jaaaa-aaaaa-aaadq-cai";
pub const INTERNET_IDENTITY_PRINCIPAL: Principal =
Principal::from_slice(&[0, 0, 0, 0, 0, 0, 0, 7, 1, 1]);
pub const INTERNET_IDENTITY_FRONTEND_CID: &str = "uqzsh-gqaaa-aaaaq-qaada-cai";
pub const INTERNET_IDENTITY_FRONTEND_PRINCIPAL: Principal =
Principal::from_slice(&[0, 0, 0, 0, 2, 16, 0, 6, 1, 1]);

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn internet_identity_cid_and_principal_match() {
assert_eq!(INTERNET_IDENTITY_CID, INTERNET_IDENTITY_PRINCIPAL.to_text());
fn internet_identity_frontend_cid_and_principal_match() {
assert_eq!(
INTERNET_IDENTITY_FRONTEND_CID,
INTERNET_IDENTITY_FRONTEND_PRINCIPAL.to_text()
);
}
}
4 changes: 2 additions & 2 deletions crates/icp-cli/tests/network_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use candid::Principal;
use icp_canister_interfaces::{
cycles_ledger::CYCLES_LEDGER_PRINCIPAL,
cycles_minting_canister::CYCLES_MINTING_CANISTER_PRINCIPAL, icp_ledger::ICP_LEDGER_PRINCIPAL,
internet_identity::INTERNET_IDENTITY_PRINCIPAL, registry::REGISTRY_PRINCIPAL,
internet_identity::INTERNET_IDENTITY_FRONTEND_PRINCIPAL, registry::REGISTRY_PRINCIPAL,
};
use indoc::{formatdoc, indoc};
use predicates::{
Expand Down Expand Up @@ -495,7 +495,7 @@ async fn network_starts_with_canisters_preset() {
.unwrap();
// Internet identity
agent
.read_state_canister_module_hash(INTERNET_IDENTITY_PRINCIPAL)
.read_state_canister_module_hash(INTERNET_IDENTITY_FRONTEND_PRINCIPAL)
.await
.unwrap();
}
Expand Down
12 changes: 9 additions & 3 deletions crates/icp/src/context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -548,9 +548,15 @@ impl Context {
env_mappings.insert(env_name.clone(), mapping);
}
}
if let Err(e) =
crate::network::custom_domains::write_custom_domains(status_dir, domain, &env_mappings)
{
let extra: Vec<_> = crate::network::custom_domains::ii_custom_domain_entry(desc.ii, domain)
.into_iter()
.collect();
if let Err(e) = crate::network::custom_domains::write_custom_domains(
status_dir,
domain,
&env_mappings,
&extra,
) {
tracing::warn!("Failed to update custom domains: {e}");
}
}
Expand Down
3 changes: 3 additions & 0 deletions crates/icp/src/network/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ pub struct NetworkDescriptorModel {
pub candid_ui_canister_id: Option<Principal>,
/// Canister ID of the deployed proxy canister, if any.
pub proxy_canister_id: Option<Principal>,
/// Whether the Internet Identity canister is deployed on this network.
#[serde(default)]
pub ii: bool,
/// Path to the status directory shared with the network launcher.
/// Used to write `custom-domains.txt` for friendly domain routing.
#[serde(default)]
Expand Down
55 changes: 53 additions & 2 deletions crates/icp/src/network/custom_domains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,43 @@ use crate::{prelude::*, store_id::IdMapping};
/// Each line has the format `<canister_name>.<env_name>.<domain>:<principal>`.
/// The file is written fresh each time from the full set of current mappings
/// across all environments sharing this network.
///
/// `extra_entries` are raw `(full_domain, canister_id)` pairs appended after the
/// environment-based entries (e.g. system canisters like Internet Identity).
pub fn write_custom_domains(
status_dir: &Path,
domain: &str,
env_mappings: &BTreeMap<String, IdMapping>,
extra_entries: &[(String, String)],
) -> Result<(), WriteCustomDomainsError> {
let file_path = status_dir.join("custom-domains.txt");
let content: String = env_mappings
let mut content: String = env_mappings
.iter()
.flat_map(|(env_name, mappings)| {
mappings
.iter()
.map(move |(name, principal)| format!("{name}.{env_name}.{domain}:{principal}\n"))
})
.collect();
for (full_domain, canister_id) in extra_entries {
content.push_str(&format!("{full_domain}:{canister_id}\n"));
}
crate::fs::write(&file_path, content.as_bytes())?;
Ok(())
}

/// Returns the custom domain entry for the II frontend canister, if II is enabled.
pub fn ii_custom_domain_entry(ii: bool, domain: &str) -> Option<(String, String)> {
if ii {
Some((
format!("id.ai.{domain}"),
icp_canister_interfaces::internet_identity::INTERNET_IDENTITY_FRONTEND_CID.to_string(),
))
} else {
None
}
}

/// Extracts the domain authority from a gateway URL for use in subdomain-based
/// canister routing.
///
Expand Down Expand Up @@ -110,7 +129,7 @@ mod tests {
);
env_mappings.insert("staging".to_string(), staging_mappings);

write_custom_domains(dir.path(), "localhost", &env_mappings).unwrap();
write_custom_domains(dir.path(), "localhost", &env_mappings, &[]).unwrap();

let content = std::fs::read_to_string(dir.path().join("custom-domains.txt")).unwrap();
// BTreeMap is ordered, so local comes before staging
Expand All @@ -122,6 +141,38 @@ mod tests {
);
}

#[test]
fn write_custom_domains_with_extra_entries() {
let dir = camino_tempfile::Utf8TempDir::new().unwrap();
let env_mappings = BTreeMap::new();
let extra = vec![(
"id.ai.localhost".to_string(),
"uqzsh-gqaaa-aaaaq-qaada-cai".to_string(),
)];

write_custom_domains(dir.path(), "localhost", &env_mappings, &extra).unwrap();

let content = std::fs::read_to_string(dir.path().join("custom-domains.txt")).unwrap();
assert_eq!(content, "id.ai.localhost:uqzsh-gqaaa-aaaaq-qaada-cai\n");
}

#[test]
fn ii_custom_domain_entry_returns_entry_when_enabled() {
let entry = ii_custom_domain_entry(true, "localhost");
assert_eq!(
entry,
Some((
"id.ai.localhost".to_string(),
"uqzsh-gqaaa-aaaaq-qaada-cai".to_string()
))
);
}

#[test]
fn ii_custom_domain_entry_returns_none_when_disabled() {
assert_eq!(ii_custom_domain_entry(false, "localhost"), None);
}

#[test]
fn canister_gateway_url_with_friendly_domain() {
let base: Url = "http://localhost:8000".parse().unwrap();
Expand Down
28 changes: 25 additions & 3 deletions crates/icp/src/network/managed/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,10 +241,12 @@ async fn run_network_launcher(
info!("Network started on port {}", instance.gateway_port);
}

let gateway_url: Url = format!("http://{}:{}", gateway.host, gateway.port)
.parse()
.unwrap();

let (candid_ui_canister_id, proxy_canister_id) = initialize_network(
&format!("http://{}:{}", gateway.host, gateway.port)
.parse()
.unwrap(),
&gateway_url,
&instance.root_key,
all_identities,
default_identity,
Expand All @@ -253,6 +255,8 @@ async fn run_network_launcher(
)
.await?;

let ii = matches!(&config.mode, ManagedMode::Launcher(cfg) if cfg.ii);

network_root
.with_write(async |root| -> Result<_, RunNetworkLauncherError> {
// Acquire locks for all fixed ports
Expand All @@ -274,6 +278,7 @@ async fn run_network_launcher(
pocketic_instance_id: instance.pocketic_instance_id,
candid_ui_canister_id,
proxy_canister_id,
ii,
status_dir: Some(status_dir_path.clone()),
use_friendly_domains: instance.use_friendly_domains,
};
Expand All @@ -284,6 +289,23 @@ async fn run_network_launcher(
Ok(())
})
.await??;

// Write initial custom-domains.txt with system canister entries (e.g. II)
if instance.use_friendly_domains
&& let Some(domain) = crate::network::custom_domains::gateway_domain(&gateway_url)
{
let extra: Vec<_> = crate::network::custom_domains::ii_custom_domain_entry(ii, domain)
.into_iter()
.collect();
if !extra.is_empty() {
let _ = crate::network::custom_domains::write_custom_domains(
&status_dir_path,
domain,
&std::collections::BTreeMap::new(),
&extra,
);
}
}
if background {
info!("To stop the network, run `icp network stop`");
guard.defuse();
Expand Down
Loading