From 8c8e4d7a29b0c23f201fd8e9e5e35f57bb839770 Mon Sep 17 00:00:00 2001 From: archipelago Date: Sun, 14 Jun 2026 10:36:12 -0400 Subject: [PATCH] test: gate that LND wallet is unlocked after restart (catches fleet-wide lock) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A wrong/locked LND wallet password leaves the wallet LOCKED after every restart/OTA, breaking all Bitcoin-receive + Lightning ops fleet-wide — and the harness was blind to it: live-lnd-address-type treats 'wallet locked' as PASS, os-audit treated lnd-unreachable as WARN, and the archipelago lnd.getinfo RPC masks a locked wallet (returns all-zero success). - tests/release/run.sh: new 'live-lnd-unlocked' stage polls LND's unauth /v1/state and FAILs if still LOCKED after a 60s grace window. - tests/lifecycle/os-audit.sh: probe lnd.newaddress (the real receive path, which surfaces LND_WALLET_LOCKED) instead of lnd.getinfo; locked = hard FAIL, not-installed = WARN. Proven on .116 (genuinely locked): os-audit now reports '[FAIL] lnd wallet unlocked (lnd.newaddress) wallet LOCKED'. Co-Authored-By: Claude Opus 4.8 (1M context) --- tests/lifecycle/os-audit.sh | 17 +++++++++++++++-- tests/release/run.sh | 27 +++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/tests/lifecycle/os-audit.sh b/tests/lifecycle/os-audit.sh index 90feb373..72b3cc8b 100755 --- a/tests/lifecycle/os-audit.sh +++ b/tests/lifecycle/os-audit.sh @@ -133,10 +133,23 @@ section_a() { else record WARN "bitcoin RPC reachable" "bitcoin.getinfo/relay-status did not answer (not installed?)" fi + # LND wallet must be UNLOCKED. NB: lnd.getinfo masks a locked wallet (it + # returns an all-zero success, error:null), so it can't detect the lock. Probe + # the actual receive path (lnd.newaddress) instead: a LOCKED wallet returns the + # LND_WALLET_LOCKED reason code — the exact fleet-wide receive breakage. A + # locked wallet is a hard FAIL; "not installed" is a WARN. (newaddress derives + # a fresh address — harmless; LND tolerates address gaps.) if rpc_ok lnd.getinfo; then - record PASS "lnd RPC reachable" "" + local na; na=$(rpc lnd.newaddress) + if grep -qE "LND_WALLET_LOCKED|wallet is locked|WALLET_LOCKED" <<<"$na"; then + record FAIL "lnd wallet unlocked (lnd.newaddress)" "wallet LOCKED — auto-unlock failed (Bitcoin-receive broken)" + elif [[ "$(jq -r '(has("result") and (.result!=null))' <<<"$na" 2>/dev/null)" == "true" ]]; then + record PASS "lnd wallet unlocked (lnd.newaddress)" "" + else + record WARN "lnd wallet unlocked (lnd.newaddress)" "newaddress: $(jq -rc '.error.message // "no address"' <<<"$na" 2>/dev/null | head -c 60)" + fi else - record WARN "lnd RPC reachable" "lnd.getinfo did not answer (not installed / wallet locked?)" + record WARN "lnd RPC reachable" "lnd.getinfo did not answer (not installed?)" fi if rpc_ok system.stats || rpc_ok system.get-metrics; then record PASS "system metrics reachable" "" diff --git a/tests/release/run.sh b/tests/release/run.sh index ccaeea99..cf79e0b6 100755 --- a/tests/release/run.sh +++ b/tests/release/run.sh @@ -128,6 +128,33 @@ if [[ $LIVE -eq 1 ]]; then done echo "SKIP: LND REST not reachable on 18080/8080 — cannot validate address type live"; exit 0 ' + + # Wallet-unlock guard. After a restart/OTA, LND comes up LOCKED and the backend + # must auto-unlock it; if the unlock password is wrong (e.g. a fleet-wide + # constant vs a per-wallet password) the wallet stays LOCKED forever and ALL + # Bitcoin-receive / Lightning ops fail — fleet-wide, silently. Nothing else in + # this harness catches that: live-lnd-address-type explicitly treats "wallet + # locked" as a PASS, and os-audit treats lnd-unreachable as a WARN. This stage + # polls LND's unauthenticated /v1/state and FAILS if it is still LOCKED after a + # grace window. RPC_ACTIVE = unlocked (pass); NON_EXISTING/WAITING = no wallet + # yet (not a regression); unreachable = skip. + stage "live-lnd-unlocked" bash -c ' + deadline=$(( $(date +%s) + 60 )) + while :; do + seen="" + for port in 18080 8080; do + st=$(curl -sk --max-time 6 "https://127.0.0.1:$port/v1/state" 2>/dev/null) + [ -z "$st" ] && continue + seen=1 + echo "LND($port) state: $st" + echo "$st" | grep -q "RPC_ACTIVE" && { echo "OK: LND wallet is unlocked"; exit 0; } + echo "$st" | grep -qE "NON_EXISTING|WAITING_TO_START" && { echo "OK: LND wallet not initialized yet — not a lock regression"; exit 0; } + done + [ -z "$seen" ] && { echo "SKIP: LND /v1/state not reachable on 18080/8080"; exit 0; } + [ "$(date +%s)" -ge "$deadline" ] && { echo "FAIL: LND wallet still LOCKED after 60s — auto-unlock failed; Bitcoin-receive/Lightning are broken"; exit 1; } + sleep 5 + done + ' fi summary 0