From a96044c53a43834d5b1fc2dcc0ebc864bab29add Mon Sep 17 00:00:00 2001 From: amackillop Date: Mon, 30 Mar 2026 12:07:21 -0700 Subject: [PATCH 1/2] Wire up VSS storage and standalone test harness Switch from local filesystem KVStore to VSS for all node state. The builder now calls build_with_vss_store_and_fixed_headers instead of build(). store_id is SHA256(mnemonic), which doesn't depend on the network and doesn't leak the node pubkey. NetworkInfra/LspInfra get a vss_url field with hardcoded endpoints for mainnet and signet. Regtest reads MDK_VSS_URL from the environment. The integration test recipe now spins up its own ephemeral PostgreSQL + VSS server, so tests don't need an external VSS process running. VSS output goes to a log file and only gets dumped on test failure. Tests were all sharing the same mnemonic, which meant the same VSS store_id. Parallel runs would clobber each other. Each test now generates a random mnemonic; only the deterministic-node-id test keeps the fixed one. Nix flake pulls in vss-server from lightningdevkit/vss-server, builds it with noop_authorizer (no JWT config needed), and exposes VSS_EXE in the dev shell. This is not the same VSS version used currently by the LSP so this was also tested against the staging environment to verify. --- flake.lock | 19 +++++++++++- flake.nix | 37 +++++++++++++++++++++++ justfile | 72 +++++++++++++++++++++++++++++++++++++++++--- src/config.rs | 18 +++++++++-- src/main.rs | 35 ++++++++++++++++++--- tests/common/mod.rs | 15 +++++++++ tests/integration.rs | 65 +++++++++++++++++++++------------------ 7 files changed, 218 insertions(+), 43 deletions(-) diff --git a/flake.lock b/flake.lock index 0630569..fae784c 100644 --- a/flake.lock +++ b/flake.lock @@ -92,7 +92,8 @@ "fenix": "fenix", "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", - "nixpkgs-unstable": "nixpkgs-unstable" + "nixpkgs-unstable": "nixpkgs-unstable", + "vss-server": "vss-server" } }, "rust-analyzer-src": { @@ -126,6 +127,22 @@ "repo": "default", "type": "github" } + }, + "vss-server": { + "flake": false, + "locked": { + "lastModified": 1773971508, + "narHash": "sha256-auQU9Odzk5GKH2NF0fJCzWFHdYa4wGThuLWS4QBQpG8=", + "owner": "lightningdevkit", + "repo": "vss-server", + "rev": "3a57b1b39fbd4ffb4e5f2ecdbde3e0f6f3584ca7", + "type": "github" + }, + "original": { + "owner": "lightningdevkit", + "repo": "vss-server", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 027d745..c713ea0 100644 --- a/flake.nix +++ b/flake.nix @@ -10,6 +10,10 @@ }; crane.url = "github:ipetkov/crane"; nixpkgs-unstable.url = "github:nixos/nixpkgs/e6f23dc08d3624daab7094b701aa3954923c6bbb"; + vss-server = { + url = "github:lightningdevkit/vss-server"; + flake = false; + }; }; outputs = @@ -20,6 +24,7 @@ fenix, crane, nixpkgs-unstable, + vss-server, }: flake-utils.lib.eachDefaultSystem ( system: @@ -107,6 +112,35 @@ Entrypoint = [ "/bin/mdk-server" ]; }; }; + + # VSS server (lightningdevkit/vss-server) for integration tests. + # Built with noop_authorizer so no JWT/sig config is needed. + vssSrc = craneLib.cleanCargoSource "${vss-server}/rust"; + vssArgs = { + src = vssSrc; + pname = "vss-server"; + version = "0.1.0"; + strictDeps = true; + nativeBuildInputs = [ + pkgs.protobuf + pkgs.pkg-config + pkgs.autoPatchelfHook + ]; + buildInputs = [ + pkgs.openssl + pkgs.stdenv.cc.cc.lib + ]; + cargoExtraArgs = "--no-default-features"; + CARGO_BUILD_RUSTFLAGS = "--cfg noop_authorizer"; + }; + vssCargoArtifacts = craneLib.buildDepsOnly vssArgs; + vss = craneLib.buildPackage ( + vssArgs + // { + cargoArtifacts = vssCargoArtifacts; + doCheck = false; + } + ); in { packages = { @@ -124,6 +158,7 @@ cargoNextestExtraArgs = "--test integration"; } ); + inherit vss; } // lib.optionalAttrs isLinux { static = staticBin; @@ -161,10 +196,12 @@ jq unixtools.xxd microsocks + postgresql_16 ]; env = { BITCOIND_EXE = "${pkgsUnstable.bitcoind}/bin/bitcoind"; + VSS_EXE = "${vss}/bin/vss-server"; NIX_SYSTEM = system; }; diff --git a/justfile b/justfile index 70f76d4..db88622 100644 --- a/justfile +++ b/justfile @@ -23,17 +23,80 @@ clippy: unit-test: nix build .#checks.{{system}}.test -# Run integration tests +# Run integration tests (starts ephemeral PostgreSQL + VSS, tears down after) integration-test *args: - cargo nextest run --test integration {{args}} + #!/usr/bin/env bash + set -euo pipefail + + : "${VSS_EXE:?VSS_EXE not set (use nix develop)}" + + # -- Ephemeral PostgreSQL ----------------------------------------------- + pg_dir=$(mktemp -d) + pg_port=$(shuf -i 10000-60000 -n 1) + pg_log="$pg_dir/pg.log" + + cleanup() { + echo "==> Tearing down..." + [ -n "${VSS_PID:-}" ] && kill "$VSS_PID" 2>/dev/null && wait "$VSS_PID" 2>/dev/null || true + pg_ctl -D "$pg_dir/data" -m immediate stop 2>/dev/null || true + rm -rf "$pg_dir" + } + trap cleanup EXIT + + echo "==> Starting ephemeral PostgreSQL on port $pg_port..." + initdb -D "$pg_dir/data" --no-locale --encoding=UTF8 -A trust >/dev/null + pg_ctl -D "$pg_dir/data" -l "$pg_log" -o "-p $pg_port -k $pg_dir -h 127.0.0.1" start >/dev/null + for i in $(seq 1 30); do + pg_isready -h 127.0.0.1 -p "$pg_port" -q && break + sleep 0.1 + done + + # -- Start VSS ---------------------------------------------------------- + vss_port=$(shuf -i 10000-60000 -n 1) + vss_config="$pg_dir/vss-config.toml" + cat > "$vss_config" << TOML + [server_config] + bind_address = "127.0.0.1:$vss_port" + + [postgresql_config] + username = "$USER" + password = "" + address = "127.0.0.1:$pg_port" + default_database = "postgres" + vss_database = "vss_test" + TOML + + vss_log="$pg_dir/vss.log" + echo "==> Starting VSS on port $vss_port..." + "$VSS_EXE" "$vss_config" >"$vss_log" 2>&1 & + VSS_PID=$! + + for i in $(seq 1 30); do + # VSS has no health endpoint; probe putObjects (returns 400 on empty body = alive) + code=$(curl -sf -o /dev/null -w '%{http_code}' -X POST "http://127.0.0.1:$vss_port/vss/putObjects" 2>/dev/null || true) + [ "$code" = "400" ] && break + sleep 0.2 + done + echo "==> VSS ready at http://127.0.0.1:$vss_port/vss" + + # -- Run tests ---------------------------------------------------------- + if MDK_VSS_URL="http://127.0.0.1:$vss_port/vss" \ + cargo nextest run --test integration {{args}}; then + : + else + echo "" + echo "==> VSS server log (on failure):" + cat "$vss_log" + exit 1 + fi # Auto-fix lint issues fix: cargo clippy --all-targets --fix --allow-dirty --allow-staged -# Run tests (cargo, all) +# Run tests (unit + doc; use `just integration-test` for integration tests) test *args: - cargo nextest run {{args}} + cargo nextest run --bin mdk-server {{args}} # Run the server run *args: @@ -517,6 +580,7 @@ dev-config: MDK_LSP_NODE_ID=${n1_pubkey} MDK_LSP_ADDRESS=127.0.0.1:${n1_p2p} MDK_API_BASE_URL=${MDK_API_BASE_URL:-http://localhost:3900/rpc} + MDK_VSS_URL=${MDK_VSS_URL:-http://localhost:9999/vss} MDK_WEBHOOK_SECRET=${MDK_WEBHOOK_SECRET:-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa} MDK_HTTP_PASSWORD_FULL=${MDK_HTTP_PASSWORD_FULL:-dev_full_password} MDK_HTTP_PASSWORD_READ_ONLY=${MDK_HTTP_PASSWORD_READ_ONLY:-dev_readonly_password} diff --git a/src/config.rs b/src/config.rs index 46b103b..6e56411 100644 --- a/src/config.rs +++ b/src/config.rs @@ -145,6 +145,7 @@ pub struct LspInfra { pub lsp_node_id: &'static str, pub lsp_address: &'static str, pub mdk_api_base_url: &'static str, + pub vss_url: &'static str, } impl LspInfra { @@ -155,12 +156,14 @@ impl LspInfra { lsp_node_id: "02a63339cc6b913b6330bd61b2f469af8785a6011a6305bb102298a8e76697473b", lsp_address: "lsp.moneydevkit.com:9735", mdk_api_base_url: "https://moneydevkit.com/rpc", + vss_url: "https://vss.moneydevkit.com/vss", }), Network::Signet => Some(LspInfra { chain_source: ChainSource::Esplora("https://mutinynet.com/api"), lsp_node_id: "03fd9a377576df94cc7e458471c43c400630655083dee89df66c6ad38d1b7acffd", lsp_address: "lsp.staging.moneydevkit.com:9735", mdk_api_base_url: "https://staging.moneydevkit.com/rpc", + vss_url: "https://vss.staging.moneydevkit.com/vss", }), _ => None, } @@ -184,6 +187,7 @@ pub enum NetworkInfra { lsp_node_id: String, lsp_address: String, mdk_api_base_url: String, + vss_url: String, }, } @@ -209,6 +213,7 @@ impl NetworkInfra { lsp_node_id: env_required("MDK_LSP_NODE_ID")?, lsp_address: env_required("MDK_LSP_ADDRESS")?, mdk_api_base_url: env_required("MDK_API_BASE_URL")?, + vss_url: env_required("MDK_VSS_URL")?, }) } } @@ -223,26 +228,33 @@ impl NetworkInfra { pub fn lsp_node_id(&self) -> &str { match self { - NetworkInfra::Production(i) => i.lsp_node_id, + NetworkInfra::Production(lsp_infra) => lsp_infra.lsp_node_id, NetworkInfra::Regtest { lsp_node_id, .. } => lsp_node_id, } } pub fn lsp_address(&self) -> &str { match self { - NetworkInfra::Production(i) => i.lsp_address, + NetworkInfra::Production(lsp_infra) => lsp_infra.lsp_address, NetworkInfra::Regtest { lsp_address, .. } => lsp_address, } } pub fn mdk_api_base_url(&self) -> &str { match self { - NetworkInfra::Production(i) => i.mdk_api_base_url, + NetworkInfra::Production(lsp_infra) => lsp_infra.mdk_api_base_url, NetworkInfra::Regtest { mdk_api_base_url, .. } => mdk_api_base_url, } } + + pub fn vss_url(&self) -> &str { + match self { + NetworkInfra::Production(lsp_infra) => lsp_infra.vss_url, + NetworkInfra::Regtest { vss_url, .. } => vss_url, + } + } } fn env_required(name: &str) -> io::Result { diff --git a/src/main.rs b/src/main.rs index 8973561..c063c26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod time; mod types; mod webhook; +use std::collections::HashMap; use std::net::ToSocketAddrs; use std::path::PathBuf; use std::str::FromStr; @@ -18,6 +19,8 @@ use std::sync::Arc; use clap::Parser; use hex::FromHex; use ldk_node::bip39::Mnemonic; +use ldk_node::bitcoin::hashes::sha256; +use ldk_node::bitcoin::hashes::Hash; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::config::Config as LdkNodeConfig; use ldk_node::lightning::ln::msgs::SocketAddress; @@ -113,6 +116,13 @@ fn main() { }; let network_dir: PathBuf = storage_dir.join(format!("{}", config_file.network)); + if let Err(e) = std::fs::create_dir_all(&network_dir) { + eprintln!( + "Failed to create data directory {}: {e}", + network_dir.display() + ); + std::process::exit(1); + } logger::init(config_file.log_level); @@ -213,10 +223,20 @@ fn main() { error!("Invalid MDK_MNEMONIC: {e}"); std::process::exit(1); }); - builder.set_entropy_bip39_mnemonic(mnemonic, None); - info!("Wallet seed derived from MDK_MNEMONIC"); - - let node = match builder.build() { + builder.set_entropy_bip39_mnemonic(mnemonic.clone(), None); + + let store_id = derive_vss_identifier(&mnemonic); + info!( + "VSS store: {} (store_id={}...)", + infra.vss_url(), + &store_id[..16] + ); + + let node = match builder.build_with_vss_store_and_fixed_headers( + infra.vss_url().to_string(), + store_id, + HashMap::new(), + ) { Ok(node) => Arc::new(node), Err(e) => { error!("Failed to build LDK Node: {e}"); @@ -224,7 +244,7 @@ fn main() { } }; - let db_path = network_dir.join("ldk_server_data.sqlite"); + let db_path = network_dir.join("mdkd.sqlite"); let metadata_store = match InvoiceMetadataStore::new(&db_path) { Ok(store) => Arc::new(store), Err(e) => { @@ -359,3 +379,8 @@ fn main() { node.stop().expect("Shutdown should always succeed."); info!("Shutdown complete."); } + +fn derive_vss_identifier(mnemonic: &Mnemonic) -> String { + let mnemonic_phrase = mnemonic.to_string(); + sha256::Hash::hash(mnemonic_phrase.as_bytes()).to_string() +} diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 4637325..e316992 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -6,6 +6,7 @@ use std::str::FromStr; use std::sync::{Arc, Mutex}; use std::time::Duration; +use ldk_node::bip39::Mnemonic; use ldk_node::bitcoin::secp256k1::PublicKey; use ldk_node::bitcoin::Network; use ldk_node::config::Config as LdkNodeConfig; @@ -153,6 +154,12 @@ rpc_password = "{rpc_password}" .env("MDK_LSP_NODE_ID", &lsp_node_id) .env("MDK_LSP_ADDRESS", &lsp_address) .env("MDK_API_BASE_URL", &mdk_api_base_url) + .env( + "MDK_VSS_URL", + std::env::var("MDK_VSS_URL").unwrap_or_else(|_| { + panic!("MDK_VSS_URL must be set (point at a running VSS server)") + }), + ) .env("MDK_WEBHOOK_SECRET", &webhook_secret) .stdout(Stdio::piped()) .stderr(Stdio::piped()) @@ -661,3 +668,11 @@ pub fn find_available_port() -> u16 { .unwrap() .port() } + +/// Generate a fresh random BIP39 mnemonic so each test gets a unique node ID +/// and VSS store, preventing parallel tests from clobbering each other. +pub fn random_mnemonic() -> String { + Mnemonic::generate(12) + .expect("12-word mnemonic") + .to_string() +} diff --git a/tests/integration.rs b/tests/integration.rs index 9c3f59d..e9e45b6 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -3,8 +3,8 @@ mod common; use std::time::Duration; use common::{ - fund_lsp, setup_payer_lsp_channel, LspNode, MdkServerHandle, PayerNode, TestBitcoind, - WebhookReceiver, + fund_lsp, random_mnemonic, setup_payer_lsp_channel, LspNode, MdkServerHandle, PayerNode, + TestBitcoind, WebhookReceiver, }; const TEST_MNEMONIC: &str = @@ -32,7 +32,7 @@ async fn test_mnemonic_deterministic_node_id() { #[tokio::test(flavor = "multi_thread")] async fn test_get_info() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp: serde_json::Value = server.get("/getinfo").await.json().await.unwrap(); assert!(!resp["nodeId"].as_str().unwrap().is_empty()); @@ -48,7 +48,7 @@ async fn test_create_and_get_invoice() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let resp = server .post_form( @@ -93,7 +93,7 @@ async fn test_create_and_get_invoice() { #[tokio::test(flavor = "multi_thread")] async fn test_auth_required() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; // Request without auth header. let resp = reqwest::Client::new() @@ -116,7 +116,7 @@ async fn test_auth_required() { #[tokio::test(flavor = "multi_thread")] async fn test_invoice_not_found() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp = server .get("/payments/incoming/0000000000000000000000000000000000000000000000000000000000000000") @@ -130,7 +130,7 @@ async fn test_payment_flow() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -237,8 +237,13 @@ async fn test_webhook_delivery() { fund_lsp(&bitcoind, &lsp).await; let webhook = WebhookReceiver::start().await; - let server = - MdkServerHandle::start(&bitcoind, Some(webhook.port), Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start( + &bitcoind, + Some(webhook.port), + Some(&lsp), + &random_mnemonic(), + ) + .await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -286,7 +291,7 @@ async fn test_webhook_delivery() { #[tokio::test(flavor = "multi_thread")] async fn test_getbalance_empty() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp: serde_json::Value = server.get("/getbalance").await.json().await.unwrap(); assert_eq!(resp["balanceSat"].as_u64().unwrap(), 0); @@ -299,7 +304,7 @@ async fn test_getbalance_after_payment() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -344,7 +349,7 @@ async fn test_getbalance_after_payment() { let resp: serde_json::Value = server.get("/getbalance").await.json().await.unwrap(); assert!( resp["balanceSat"].as_u64().unwrap() == 98_000, // 2% LSP fee - "Expected non-zero balance after receiving payment, got: {resp}" + "Expected 98k sat balance after receiving payment, got: {resp}" ); } @@ -357,7 +362,7 @@ async fn test_getbalance_small_payment() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -409,7 +414,7 @@ async fn test_jit_channel_invoice() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -498,7 +503,7 @@ async fn test_decodeinvoice() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; // Create an invoice to decode. let created: serde_json::Value = server @@ -540,7 +545,7 @@ async fn test_decodeinvoice() { #[tokio::test(flavor = "multi_thread")] async fn test_decodeinvoice_invalid() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp = server .post_form("/decodeinvoice", &[("invoice", "not-a-real-invoice")]) @@ -554,7 +559,7 @@ async fn test_decodeinvoice_invalid() { #[tokio::test(flavor = "multi_thread")] async fn test_decodeinvoice_missing_param() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; // POST with empty form body — axum returns 422 for missing fields. let resp = server.post_form("/decodeinvoice", &[]).await; @@ -571,7 +576,7 @@ async fn test_list_incoming_payments() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -707,7 +712,7 @@ async fn test_list_incoming_payments() { #[tokio::test(flavor = "multi_thread")] async fn test_listchannels_empty() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let channels: Vec = server.get("/listchannels").await.json().await.unwrap(); assert!(channels.is_empty()); @@ -719,7 +724,7 @@ async fn test_listchannels_and_closechannel() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -820,7 +825,7 @@ async fn test_listchannels_and_closechannel() { #[tokio::test(flavor = "multi_thread")] async fn test_closechannel_not_found() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp = server .post_form( @@ -837,7 +842,7 @@ async fn test_closechannel_not_found() { #[tokio::test(flavor = "multi_thread")] async fn test_closechannel_invalid_hex() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp = server .post_form("/closechannel", &[("channelId", "not-hex")]) @@ -851,7 +856,7 @@ async fn test_getbalance_onchain_after_close() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -945,7 +950,7 @@ async fn test_getbalance_onchain_after_close() { #[tokio::test(flavor = "multi_thread")] async fn test_sendtoaddress_invalid_address() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp = server .post_form( @@ -966,7 +971,7 @@ async fn test_sendtoaddress_invalid_address() { #[tokio::test(flavor = "multi_thread")] async fn test_sendtoaddress_missing_params() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; // Missing all required params. let resp = server.post_form("/sendtoaddress", &[]).await; @@ -980,7 +985,7 @@ async fn test_sendtoaddress_missing_params() { #[tokio::test(flavor = "multi_thread")] async fn test_sendtoaddress_insufficient_funds() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; // Valid regtest address but no on-chain funds. let resp = server @@ -1005,7 +1010,7 @@ async fn test_sendtoaddress_success() { let lsp = LspNode::new(&bitcoind); fund_lsp(&bitcoind, &lsp).await; - let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, Some(&lsp), &random_mnemonic()).await; let payer = PayerNode::new(&bitcoind); setup_payer_lsp_channel(&bitcoind, &payer, &lsp, 500_000).await; @@ -1161,7 +1166,7 @@ async fn test_sendtoaddress_success() { #[tokio::test(flavor = "multi_thread")] async fn test_openapi_scalar() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; // /scalar is outside the auth middleware — no credentials needed. let resp = reqwest::Client::new() @@ -1188,7 +1193,7 @@ const BOLT12_OFFER: &str = #[tokio::test(flavor = "multi_thread")] async fn test_decodeoffer() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp = server .post_form("/decodeoffer", &[("offer", BOLT12_OFFER)]) @@ -1211,7 +1216,7 @@ async fn test_decodeoffer() { #[tokio::test(flavor = "multi_thread")] async fn test_decodeoffer_invalid() { let bitcoind = TestBitcoind::new(); - let server = MdkServerHandle::start(&bitcoind, None, None, TEST_MNEMONIC).await; + let server = MdkServerHandle::start(&bitcoind, None, None, &random_mnemonic()).await; let resp = server .post_form("/decodeoffer", &[("offer", "not-a-real-offer")]) From d3239aba8c55b538729eb99e7ac7cdec2a96da95 Mon Sep 17 00:00:00 2001 From: amackillop Date: Mon, 30 Mar 2026 12:53:22 -0700 Subject: [PATCH 2/2] Fix CI integration tests missing VSS setup The integration-test flake output was a cargoNextest derivation that ran tests inside the Nix sandbox with no PostgreSQL or VSS server. Every test panicked on missing MDK_VSS_URL. Replace the derivation with a writeShellApplication wrapper that calls `just integration-test`, which already starts Postgres and VSS, runs the tests, and tears everything down. This avoids duplicating the orchestration in Nix preCheck hooks. CI now uses `nix run` instead of `nix build` since the tests need loopback networking. Also suppress the VSS log dump on test failure for now, as it floods CI output with debug noise. --- .github/workflows/ci.yml | 2 +- flake.nix | 28 +++++++++++++++++++++------- justfile | 2 +- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d8c2ab..ffd42c5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: run: nix flake check --print-build-logs - name: Run integration tests - run: nix build .#integration-test --print-build-logs + run: nix run .#integration-test --print-build-logs build: name: Build Static (${{ matrix.arch }}) diff --git a/flake.nix b/flake.nix index c713ea0..81e075f 100644 --- a/flake.nix +++ b/flake.nix @@ -151,13 +151,27 @@ doCheck = false; # Tests run in checks.test } ); - integration-test = craneLib.cargoNextest ( - commonArgs - // { - inherit cargoArtifacts; - cargoNextestExtraArgs = "--test integration"; - } - ); + # Wrapper script that runs `just integration-test` with all + # dependencies available. Intended for CI (`nix run .#integration-test`). + integration-test = pkgs.writeShellApplication { + name = "integration-test"; + runtimeInputs = [ + buildToolchain + pkgs.cargo-nextest + pkgs.just + pkgs.postgresql_16 + pkgs.curl + pkgs.protobuf + pkgsUnstable.bitcoind + vss + ]; + text = '' + export BITCOIND_EXE="${pkgsUnstable.bitcoind}/bin/bitcoind" + export VSS_EXE="${vss}/bin/vss-server" + export NIX_SYSTEM="${system}" + just integration-test "$@" + ''; + }; inherit vss; } // lib.optionalAttrs isLinux { diff --git a/justfile b/justfile index db88622..6df1a87 100644 --- a/justfile +++ b/justfile @@ -86,7 +86,7 @@ integration-test *args: else echo "" echo "==> VSS server log (on failure):" - cat "$vss_log" + # cat "$vss_log" skip this for now exit 1 fi