diff --git a/core/archipelago/src/api/rpc/package/dependencies.rs b/core/archipelago/src/api/rpc/package/dependencies.rs index f6f1a24f..fdb80014 100644 --- a/core/archipelago/src/api/rpc/package/dependencies.rs +++ b/core/archipelago/src/api/rpc/package/dependencies.rs @@ -206,19 +206,24 @@ pub(super) fn check_install_deps(package_id: &str, deps: &RunningDeps) -> Result "BTCPay Server requires a running Bitcoin node (Bitcoin Knots). \ Please install and start Bitcoin Knots first." )), - "mempool" | "mempool-web" if !deps.has_bitcoin || !deps.has_electrumx => { - let mut missing = vec![]; - if !deps.has_bitcoin { - missing.push("Bitcoin Knots"); - } - if !deps.has_electrumx { - missing.push("ElectrumX"); - } - Err(anyhow::anyhow!( - "Mempool requires {} to be running. Please install and start {} first.", - missing.join(" and "), - missing.join(" and ") - )) + "mempool" | "mempool-web" if !deps.has_bitcoin => Err(anyhow::anyhow!( + "Mempool requires Bitcoin Knots to be running. \ + Please install and start Bitcoin Knots first." + )), + // ElectrumX is deliberately NOT a hard gate for mempool: mempool-api + // reconnects to electrum on its own (verified live — it retries + // ECONNREFUSED in a loop and comes good when electrum starts + // listening), and an ElectrumX mid-resync can be legitimately down + // for DAYS. The hard gate here blocked repeated mempool installs on + // .116 (2026-07-22) while electrumx was compacting. Block/fee views + // work from bitcoind immediately; address lookups light up when + // ElectrumX finishes. + "mempool" | "mempool-web" if !deps.has_electrumx => { + info!( + "Mempool installing while ElectrumX is not running — \ + mempool-api reconnects automatically once ElectrumX is up" + ); + Ok(()) } "fedimint" if !deps.has_bitcoin => { info!("Fedimint installing without local Bitcoin node — configure remote Bitcoin RPC in Fedimint guardian setup"); @@ -298,9 +303,10 @@ fn missing_install_deps(package_id: &str, deps: &RunningDeps) -> Vec if !deps.has_bitcoin { missing.push(BITCOIN); } - if !deps.has_electrumx { - missing.push(ELECTRUM); - } + // ElectrumX intentionally not required — see check_install_deps: + // mempool-api reconnects when electrum comes up, and a resyncing + // ElectrumX can be down for days (the 3-minute bounded wait here + // would still fail the install for no benefit). } // fedimint deliberately absent: check_install_deps allows it without // a local Bitcoin node (remote RPC configured in guardian setup). @@ -1111,7 +1117,11 @@ mod tests { } #[tokio::test] - async fn mempool_waits_on_both_bitcoin_and_electrumx() { + async fn mempool_waits_on_bitcoin_only() { + // ElectrumX is deliberately NOT gated (2026-07-22): a resyncing + // ElectrumX can be down for days and mempool-api reconnects on + // its own, so the wait only covers Bitcoin — even when electrumx + // is installed and stopped. let calls = Arc::new(AtomicU32::new(0)); let (labels, sink) = label_sink(); let probe_calls = Arc::clone(&calls); @@ -1119,8 +1129,8 @@ mod tests { "mempool", move || { let n = probe_calls.fetch_add(1, Ordering::SeqCst); - // Bitcoin comes up on probe 2, electrumx on probe 3. - async move { Ok(probe(n >= 1, n >= 2, &["bitcoin-knots", "electrumx"])) } + // Bitcoin comes up on probe 2; electrumx never does. + async move { Ok(probe(n >= 1, false, &["bitcoin-knots", "electrumx"])) } }, sink, 36, @@ -1131,22 +1141,19 @@ mod tests { let labels = labels.lock().unwrap(); assert_eq!( labels.as_slice(), - &[ - "Waiting for Bitcoin and ElectrumX to start…".to_string(), - "Waiting for ElectrumX to start…".to_string(), - ] + &["Waiting for Bitcoin to start…".to_string()] ); } #[tokio::test] - async fn mempool_fails_fast_when_one_dep_is_not_installed() { - // Bitcoin is installed (waiting could help) but ElectrumX is not - // installed at all — waiting can never satisfy the gate, so fail - // fast with the canonical message. + async fn mempool_fails_fast_when_bitcoin_is_not_installed() { + // Bitcoin isn't installed at all — waiting can never satisfy the + // gate, so fail fast with the canonical message. (A missing + // ElectrumX no longer blocks anything.) let (labels, sink) = label_sink(); let err = wait_for_install_deps( "mempool", - || async { Ok(probe(false, false, &["bitcoin-knots"])) }, + || async { Ok(probe(false, false, &["electrumx"])) }, sink, 36, Duration::ZERO, diff --git a/core/archipelago/src/api/rpc/package/stacks.rs b/core/archipelago/src/api/rpc/package/stacks.rs index 14995069..8e0a7ed9 100644 --- a/core/archipelago/src/api/rpc/package/stacks.rs +++ b/core/archipelago/src/api/rpc/package/stacks.rs @@ -12,7 +12,11 @@ use tracing::info; use super::install::{install_log, patch_indeedhub_nostr_provider}; -const PODMAN_STACK_PROBE_TIMEOUT: Duration = Duration::from_secs(10); +// 45s, not 10s: under heavy disk load (e.g. an ElectrumX resync/compaction +// hammering the same SSD) a plain `podman ps` was observed taking >10s on +// .116 (2026-07-22), failing a mempool install at the probe step for no real +// reason. Podman being slow is not podman being dead. +const PODMAN_STACK_PROBE_TIMEOUT: Duration = Duration::from_secs(45); const PODMAN_STACK_LOG_TIMEOUT: Duration = Duration::from_secs(15); const PODMAN_STACK_PULL_TIMEOUT: Duration = Duration::from_secs(600); const PODMAN_STACK_REMOVE_TIMEOUT: Duration = Duration::from_secs(90);