From 69f3a355c74e92ef9182491985b018abba5ed7ec Mon Sep 17 00:00:00 2001 From: ssmithx Date: Thu, 10 Sep 2026 15:28:17 +0000 Subject: [PATCH 1/2] fix(tor): recognize bitcoin-core in Tor auto-enrollment tables apps/bitcoin-core/manifest.yml uses id "bitcoin-core", but known_service_port/is_protocol_service (tor/mod.rs) and tor_service_name (docker_packages.rs) only matched "bitcoin" and "bitcoin-knots", so the app silently never got auto-enrolled for a P2P (8333) hidden service at install time, and the UI's Tor address lookup for it always returned None. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WxfWiFfnBkdSxwKUuV2tNy --- core/archipelago/src/api/rpc/tor/mod.rs | 4 ++-- core/archipelago/src/container/docker_packages.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/archipelago/src/api/rpc/tor/mod.rs b/core/archipelago/src/api/rpc/tor/mod.rs index 0ec6a611..e0e6efef 100644 --- a/core/archipelago/src/api/rpc/tor/mod.rs +++ b/core/archipelago/src/api/rpc/tor/mod.rs @@ -594,7 +594,7 @@ fn is_valid_v3_onion(s: &str) -> bool { pub(in crate::api::rpc) fn known_service_port(name: &str) -> u16 { match name { "archipelago" => 80, - "bitcoin" | "bitcoin-knots" => 8333, + "bitcoin" | "bitcoin-core" | "bitcoin-knots" => 8333, "electrs" | "electrumx" => 50001, "lnd" => 8080, "btcpay" | "btcpay-server" | "btcpayserver" => 23000, @@ -619,7 +619,7 @@ pub(in crate::api::rpc) fn known_service_port(name: &str) -> u16 { pub(in crate::api::rpc) fn is_protocol_service(name: &str) -> bool { matches!( name, - "bitcoin" | "bitcoin-knots" | "electrs" | "electrumx" | "lnd" + "bitcoin" | "bitcoin-core" | "bitcoin-knots" | "electrs" | "electrumx" | "lnd" ) } diff --git a/core/archipelago/src/container/docker_packages.rs b/core/archipelago/src/container/docker_packages.rs index 557e0e60..76935b22 100644 --- a/core/archipelago/src/container/docker_packages.rs +++ b/core/archipelago/src/container/docker_packages.rs @@ -660,7 +660,7 @@ fn apply_dynamic_metadata(app_id: &str, meta: &mut AppMetadata) { fn tor_service_name(app_id: &str) -> Option<&'static str> { match app_id { "archipelago" => Some("archipelago"), - "bitcoin" | "bitcoin-knots" | "bitcoind" => Some("bitcoin"), + "bitcoin" | "bitcoin-core" | "bitcoin-knots" | "bitcoind" => Some("bitcoin"), "electrumx" | "electrs" | "electrum" => Some("electrumx"), "lnd" | "lnd-ui" => Some("lnd"), "btcpay" | "btcpay-server" | "btcpayserver" => Some("btcpay"), From dc7b5985585264bda9ecad4da21ccf64ad8e8899 Mon Sep 17 00:00:00 2001 From: ssmithx Date: Thu, 10 Sep 2026 16:26:59 +0000 Subject: [PATCH 2/2] fix(tor): un-alias bitcoin-core's hidden-service name; add regression tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit read_tor_address("bitcoin-core") was resolving through tor_service_name to the shared "bitcoin" alias, but enrollment (install.rs auto-enroll and the tor.create-service RPC) always names HiddenServiceDir/tor-hostnames entries using the raw package_id verbatim — never canonicalized. On a real node that's hidden_service_bitcoin-core, which the aliased lookup never found, so the per-app UI Tor badge stayed empty even after the previous commit made bitcoin-core auto-enrollable. Give bitcoin-core its own identity-mapped arm instead of folding it into the legacy bitcoin/bitcoin-knots/bitcoind alias, and pin all three lookup tables (known_service_port, is_protocol_service, tor_service_name) with regression tests so this alias-drift class of bug can't recur silently. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01WxfWiFfnBkdSxwKUuV2tNy --- core/archipelago/src/api/rpc/tor/mod.rs | 17 ++++++++++ .../src/container/docker_packages.rs | 34 ++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/core/archipelago/src/api/rpc/tor/mod.rs b/core/archipelago/src/api/rpc/tor/mod.rs index e0e6efef..1634a82f 100644 --- a/core/archipelago/src/api/rpc/tor/mod.rs +++ b/core/archipelago/src/api/rpc/tor/mod.rs @@ -377,6 +377,23 @@ async fn write_staged_torrc(content: &str, staging: &str) -> Result<()> { Ok(()) } +#[cfg(test)] +mod known_service_tests { + use super::{is_protocol_service, known_service_port}; + + #[test] + fn bitcoin_core_is_a_protocol_service_on_the_p2p_port() { + // Regression: apps/bitcoin-core/manifest.yml uses id "bitcoin-core", + // distinct from the legacy "bitcoin"/"bitcoin-knots" ids. Missing + // here means auto-enrollment silently skips it (known_service_port + // returns 0) and, separately, regenerate_torrc falls back to the + // web-app HiddenServicePort-80 default instead of forwarding 8333 + // straight through. + assert_eq!(known_service_port("bitcoin-core"), 8333); + assert!(is_protocol_service("bitcoin-core")); + } +} + #[cfg(test)] mod torrc_tests { use super::app_hidden_service_port_line; diff --git a/core/archipelago/src/container/docker_packages.rs b/core/archipelago/src/container/docker_packages.rs index 76935b22..82095c63 100644 --- a/core/archipelago/src/container/docker_packages.rs +++ b/core/archipelago/src/container/docker_packages.rs @@ -657,10 +657,20 @@ fn apply_dynamic_metadata(app_id: &str, meta: &mut AppMetadata) { /// Map app_id to Tor hidden service directory name. /// "archipelago" is the main web UI (nginx port 80). /// Supports container names from deploy (archy-*, btcpay-server, etc.). +/// +/// This must match what enrollment actually names the hidden service dir +/// with — both the install-time auto-enroll (`install.rs`) and the manual +/// `tor.create-service` RPC write `HiddenServiceDir` using the raw +/// `package_id`/`name` verbatim, with no canonicalization. So `bitcoin-core` +/// gets its own identity arm rather than folding into the "bitcoin" alias: +/// aliasing it here without also canonicalizing the write side would point +/// this lookup at `hidden_service_bitcoin`, which never gets created — the +/// on-disk dir is always `hidden_service_bitcoin-core` for this app id. fn tor_service_name(app_id: &str) -> Option<&'static str> { match app_id { "archipelago" => Some("archipelago"), - "bitcoin" | "bitcoin-core" | "bitcoin-knots" | "bitcoind" => Some("bitcoin"), + "bitcoin-core" => Some("bitcoin-core"), + "bitcoin" | "bitcoin-knots" | "bitcoind" => Some("bitcoin"), "electrumx" | "electrs" | "electrum" => Some("electrumx"), "lnd" | "lnd-ui" => Some("lnd"), "btcpay" | "btcpay-server" | "btcpayserver" => Some("btcpay"), @@ -906,6 +916,28 @@ mod launch_url_port_tests { } } +#[cfg(test)] +mod tor_service_name_tests { + use super::tor_service_name; + + #[test] + fn bitcoin_core_resolves_to_its_own_hidden_service_dir() { + // Regression: enrollment (install.rs, tor.create-service) writes + // HiddenServiceDir/tor-hostnames entries using the raw package_id + // verbatim, never canonicalized. Aliasing "bitcoin-core" to the + // shared "bitcoin" name here would point reads at a directory + // enrollment never creates. + assert_eq!(tor_service_name("bitcoin-core"), Some("bitcoin-core")); + } + + #[test] + fn legacy_bitcoin_ids_share_the_bitcoin_alias() { + assert_eq!(tor_service_name("bitcoin"), Some("bitcoin")); + assert_eq!(tor_service_name("bitcoin-knots"), Some("bitcoin")); + assert_eq!(tor_service_name("bitcoind"), Some("bitcoin")); + } +} + #[cfg(test)] mod extract_lan_address_tests { use super::extract_lan_address;