fix(mempool): resolve CORE_RPC_HOST to the actual bitcoin node (Knots/Core) (B12)
CORE_RPC_HOST was hardcoded to bitcoin-knots in three env-render paths, so on a
bitcoin-core node (container named bitcoin-core) mempool-api could not reach
Bitcoin RPC. Both node variants are reachable on archy-net by container name —
only the name differs.
- Legacy direct-podman (stacks.rs) and config.rs::get_app_config now use a new
dependencies::detect_bitcoin_rpc_host() (pure, unit-tested pick_bitcoin_host).
- Quadlet/manifest path (the modern fleet default): add a {{BITCOIN_HOST}}
derived-env placeholder — HostFacts.bitcoin_host + resolve_derived_env render
it; prod_orchestrator detects Knots/Core via podman ps, resolved on demand
only for manifests that use the placeholder. mempool-api manifest moves
CORE_RPC_HOST from static env to derived_env: {{BITCOIN_HOST}}.
Tests: pick_bitcoin_host (5 cases incl. substring safety), container-crate
resolve_derived_env, and orchestrator mempool_core_rpc_host_follows_bitcoin_node
(core->bitcoin-core, knots->bitcoin-knots). No-regression confirmed: picker
returns bitcoin-knots live on .198. Live bitcoin-core validation pending (no
core node available). Sibling hardcodes (lnd/btcpay/electrumx/fedimint) tracked
as B12b.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
987a961f4a
commit
bf24bbc15a
@@ -772,6 +772,8 @@ pub struct ProdContainerOrchestrator {
|
||||
use_quadlet_backends: bool,
|
||||
#[cfg(test)]
|
||||
test_disk_gb: Option<u64>,
|
||||
#[cfg(test)]
|
||||
test_bitcoin_host: Option<String>,
|
||||
}
|
||||
|
||||
struct FileSecretsProvider {
|
||||
@@ -832,6 +834,8 @@ impl ProdContainerOrchestrator {
|
||||
use_quadlet_backends: config.use_quadlet_backends,
|
||||
#[cfg(test)]
|
||||
test_disk_gb: None,
|
||||
#[cfg(test)]
|
||||
test_bitcoin_host: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -850,6 +854,7 @@ impl ProdContainerOrchestrator {
|
||||
secrets_dir: PathBuf::from("/var/lib/archipelago/secrets"),
|
||||
use_quadlet_backends: false,
|
||||
test_disk_gb: None,
|
||||
test_bitcoin_host: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2314,9 +2319,45 @@ impl ProdContainerOrchestrator {
|
||||
host_ip,
|
||||
host_mdns,
|
||||
disk_gb,
|
||||
// Cheap default; resolve_dynamic_env fills the real node name on
|
||||
// demand (it costs a podman call) only for manifests that use
|
||||
// {{BITCOIN_HOST}}, rather than every app on every reconcile.
|
||||
bitcoin_host: "bitcoin-knots".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Container name of the running Bitcoin node (`bitcoin-knots` or
|
||||
/// `bitcoin-core`) for the `{{BITCOIN_HOST}}` derived-env placeholder.
|
||||
/// Synchronous `podman ps` to match the surrounding host-fact detection;
|
||||
/// defaults to `bitcoin-knots` when none is running (B12).
|
||||
fn bitcoin_host(&self) -> String {
|
||||
#[cfg(test)]
|
||||
if let Some(host) = &self.test_bitcoin_host {
|
||||
return host.clone();
|
||||
}
|
||||
// Mirrors api::rpc::package::dependencies (the legacy install path);
|
||||
// both Bitcoin node variants are reachable on archy-net by name.
|
||||
const BITCOIN_NAMES: &[&str] = &["bitcoin-knots", "bitcoin-core", "bitcoin"];
|
||||
let names = Command::new("podman")
|
||||
.args(["ps", "--format", "{{.Names}}"])
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|o| o.status.success())
|
||||
.map(|o| String::from_utf8_lossy(&o.stdout).into_owned())
|
||||
.unwrap_or_default();
|
||||
names
|
||||
.lines()
|
||||
.map(|l| l.trim())
|
||||
.find(|name| BITCOIN_NAMES.contains(name))
|
||||
.map(|name| name.to_string())
|
||||
.unwrap_or_else(|| "bitcoin-knots".to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn set_bitcoin_host_for_test(&mut self, host: &str) {
|
||||
self.test_bitcoin_host = Some(host.to_string());
|
||||
}
|
||||
|
||||
fn detect_host_ip() -> Option<String> {
|
||||
let output = Command::new("hostname").arg("-I").output().ok()?;
|
||||
if !output.status.success() {
|
||||
@@ -2400,7 +2441,18 @@ impl ProdContainerOrchestrator {
|
||||
}
|
||||
|
||||
fn resolve_dynamic_env(&self, manifest: &mut AppManifest) -> Result<()> {
|
||||
let facts = self.detect_host_facts();
|
||||
let mut facts = self.detect_host_facts();
|
||||
// Only pay the podman cost to detect Knots-vs-Core when this manifest
|
||||
// actually templates the Bitcoin node into its env (mempool — B12).
|
||||
if manifest
|
||||
.app
|
||||
.container
|
||||
.derived_env
|
||||
.iter()
|
||||
.any(|e| e.template.contains("{{BITCOIN_HOST}}"))
|
||||
{
|
||||
facts.bitcoin_host = self.bitcoin_host();
|
||||
}
|
||||
let mut env = manifest.app.environment.clone();
|
||||
env.extend(manifest.app.container.resolve_derived_env(&facts));
|
||||
|
||||
@@ -3489,6 +3541,35 @@ app:
|
||||
assert!(!calls.iter().any(|c| c.starts_with("build_image:")));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mempool_core_rpc_host_follows_bitcoin_node() {
|
||||
// B12: mempool's CORE_RPC_HOST must resolve to whichever Bitcoin node
|
||||
// container is running (Knots OR Core), not a hardcoded value.
|
||||
let yaml = "app:\n id: mempool-api\n name: mempool-api\n version: 1.0.0\n container:\n image: x:1\n derived_env:\n - key: CORE_RPC_HOST\n template: \"{{BITCOIN_HOST}}\"\n";
|
||||
|
||||
for (node, expected) in [
|
||||
("bitcoin-core", "bitcoin-core"),
|
||||
("bitcoin-knots", "bitcoin-knots"),
|
||||
] {
|
||||
let rt = Arc::new(MockRuntime::default());
|
||||
let mut orch = orch_with(rt).await;
|
||||
orch.set_bitcoin_host_for_test(node);
|
||||
|
||||
let mut manifest = AppManifest::parse(yaml).unwrap();
|
||||
orch.resolve_dynamic_env(&mut manifest).unwrap();
|
||||
|
||||
assert!(
|
||||
manifest
|
||||
.app
|
||||
.environment
|
||||
.iter()
|
||||
.any(|e| e == &format!("CORE_RPC_HOST={expected}")),
|
||||
"node={node}: expected CORE_RPC_HOST={expected}, got {:?}",
|
||||
manifest.app.environment
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn install_fresh_build_when_image_absent() {
|
||||
let rt = Arc::new(MockRuntime::default());
|
||||
|
||||
Reference in New Issue
Block a user