fix(lnd): pay through Router.SendPaymentV2 — LND 0.21 removed the old route
LND 0.21.2 removed the deprecated Lightning.SendPaymentSync REST route (/v1/channels/transactions). The backend still called it, so every Lightning send answered literal HTTP 404 and the wallet UI reported 'Payment failed: Not Found' fleet-wide right after the pin bump — receive worked, which made it look intermittent. Pay through the supported Router.SendPaymentV2 route (/v2/router/send) instead, keeping the existing contract with the UI: - single-record responses (no_inflight_updates) unwrapped from the grpc-gateway result envelope, transport errors from the nested error - a slow multi-hop payment still resolves as pending + payment hash (only LND may declare failure), never a false 'Payment failed' - LND's failure_reason codes translated to the same plain-language advice, invoice-expiry still says 'ask for a fresh invoice' Guard it at the gate: tests/lifecycle/bats/lnd-api-compat.bats POSTs a deliberately-invalid invoice to /v2/router/send on the RUNNING LND and fails if the route answers 404 — the image/backend skew that shipped silently last time because no test ever spoke the payment endpoint. Also bumps the stale lnd image expectation in remote-lifecycle.sh.
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bats
|
||||
# tests/lifecycle/bats/lnd-api-compat.bats
|
||||
#
|
||||
# Regression guard for the 2026-09-01 fleet breakage: LND 0.21 REMOVED the
|
||||
# deprecated Lightning.SendPaymentSync REST route (`/v1/channels/transactions`)
|
||||
# that the backend paid through — every Lightning send answered the literal
|
||||
# HTTP 404 "Not Found" and the wallet UI showed "Payment failed: Not Found".
|
||||
# The backend now pays via Router.SendPaymentV2 (`/v2/router/send`).
|
||||
#
|
||||
# This test does not send sats. It POSTs a deliberately-invalid invoice to the
|
||||
# v2 route on the RUNNING LND and asserts the route itself answers: a
|
||||
# 400/500 "cannot parse" proves the endpoint exists; a 404 means the pinned
|
||||
# image no longer serves the route the backend calls — the exact image/backend
|
||||
# skew that shipped silently last time because no gate test ever spoke the
|
||||
# payment endpoint.
|
||||
#
|
||||
# Tiers: read-only (invalid payment request; nothing is sent).
|
||||
#
|
||||
# Runs on the archy host (sudo for the macaroon, curl to localhost).
|
||||
|
||||
LND_MAINNET_DIR="/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet"
|
||||
|
||||
_lnd_rest_host_port() {
|
||||
local mf
|
||||
for mf in \
|
||||
"${ARCHIPELAGO_APPS_DIR:-/opt/archipelago/apps}/lnd/manifest.yml" \
|
||||
"${ARCHIPELAGO_APPS_DIR:-/opt/archipelago/apps}/lnd/manifest.yaml" \
|
||||
"$BATS_TEST_DIRNAME/../../../apps/lnd/manifest.yml"; do
|
||||
[[ -r "$mf" ]] || continue
|
||||
awk '
|
||||
/- host:/ { host=$3 }
|
||||
/container:/ { if ($2 == 8080 && host != "") { print host; exit } }
|
||||
' "$mf"
|
||||
return 0
|
||||
done
|
||||
}
|
||||
|
||||
@test "running LND serves /v2/router/send (the route the backend pays through)" {
|
||||
if ! podman ps --format '{{.Names}}' 2>/dev/null | grep -qx lnd; then
|
||||
skip "lnd not running"
|
||||
fi
|
||||
local port
|
||||
port=$(_lnd_rest_host_port)
|
||||
[[ -n "$port" ]] || skip "could not resolve LND REST host port from manifest"
|
||||
|
||||
local mac
|
||||
mac=$(sudo cat "$LND_MAINNET_DIR/admin.macaroon" 2>/dev/null | od -An -tx1 -v | tr -d " \n")
|
||||
[[ -n "$mac" ]] || skip "LND admin macaroon not readable (LND installed but wallet not initialized?)"
|
||||
|
||||
local code body
|
||||
body=$(mktemp)
|
||||
code=$(curl -sk -o "$body" -w '%{http_code}' --max-time 10 -X POST \
|
||||
-H "Grpc-Metadata-macaroon: $mac" \
|
||||
-H 'Content-Type: application/json' \
|
||||
--data '{"payment_request":"lnbc1notarealinvoice","timeout_seconds":5,"no_inflight_updates":true}' \
|
||||
"https://127.0.0.1:${port}/v2/router/send" || echo 000)
|
||||
rm -f "$body"
|
||||
|
||||
# 000 = LND REST unreachable at all — that is port-drift's failure class
|
||||
# (port-drift.bats), but it also breaks payments, so fail loudly here too.
|
||||
if [[ "$code" == "000" ]]; then
|
||||
fail "LND REST not reachable on ${port} — payments cannot be sent at all"
|
||||
fi
|
||||
if [[ "$code" == "404" ]]; then
|
||||
fail "running LND does not serve /v2/router/send (HTTP 404) — the backend's payment route is gone; every Lightning send fails 'Not Found'"
|
||||
fi
|
||||
}
|
||||
|
||||
@test "backend no longer references the removed /v1/channels/transactions route" {
|
||||
# Source-level guard: the removed route must not creep back into the
|
||||
# payment path (the runtime fix is in api/rpc/lnd/payments.rs).
|
||||
local src="$BATS_TEST_DIRNAME/../../../core/archipelago/src/api/rpc/lnd/payments.rs"
|
||||
[[ -r "$src" ]] || skip "source tree not present"
|
||||
if grep -q 'v1/channels/transactions' "$src"; then
|
||||
fail "payments.rs references /v1/channels/transactions — removed in LND 0.21, answers 404"
|
||||
fi
|
||||
}
|
||||
@@ -136,7 +136,7 @@ image_for() {
|
||||
bitcoin-knots) echo "source.archipelago-foundation.org/lfg2025/bitcoin-knots:latest" ;;
|
||||
bitcoin-core) echo "docker.io/bitcoin/bitcoin:28.4" ;;
|
||||
btcpay-server) echo "docker.io/btcpayserver/btcpayserver:2.4.2" ;;
|
||||
lnd) echo "source.archipelago-foundation.org/lfg2025/lnd:v0.18.4-beta" ;;
|
||||
lnd) echo "source.archipelago-foundation.org/lfg2025/lnd:v0.21.2-beta" ;;
|
||||
mempool) echo "source.archipelago-foundation.org/lfg2025/mempool-frontend:v3.0.0" ;;
|
||||
homeassistant) echo "source.archipelago-foundation.org/lfg2025/home-assistant:2024.1" ;;
|
||||
grafana) echo "source.archipelago-foundation.org/lfg2025/grafana:10.2.0" ;;
|
||||
|
||||
Reference in New Issue
Block a user