Repository navigation
Stop LUD-21 verify polling on a terminal verify error - #13
Conversation
On the self-custodial (Spark) path, a genuine verify {"status":"ERROR"}
(invoice not found/rejected) means the invoice will never settle. Previously
pollVerifyStatus treated it like a transient failure: it backed off 5s and
kept re-polling until the invoice expiry deadline (minutes of pointless
polling of lnurl.blink.sv/verify/* per not-found invoice).
verifyLnurlPayment now tags a status:ERROR throw as terminal
(err.lnurlVerifyTerminal = true), and pollVerifyStatus stops immediately on a
terminal error instead of rescheduling. Transient network/HTTP errors are
unchanged: they still back off and retry until settle or expiry.
The change is applied to BOTH the canonical js/blink-lnurl.js module and the
widget's inline copy (getLnurl()), kept in parity. Tests:
- canonical: status:ERROR is terminal, non-ok HTTP is not (blink-lnurl.spec)
- inline parity for both cases, asserted against the canonical module
(closes a prior gap where the inline ERROR path was untested) (widget-spark.spec)
- pollVerifyStatus stops on terminal, keeps backing off on transient
(widget-polling.spec)
No public API / DOM changes. Version 1.5.0 -> 1.5.1 (+ analytics widget_version).
All 143 tests pass; eslint clean.
blink-dev-bot
left a comment
There was a problem hiding this comment.
HIGH
- js/blink-lnurl.js:285 / js/blink-pay-button.js:1642,1752 — Do not classify every LUD-21 status: ERROR response as terminal. Blink’s current verify route also returns status: ERROR when its GraphQL query throws, with the reason “We could not verify the invoice. Please try again later.” A temporary backend failure would therefore stop this widget’s only settlement detector permanently; if the invoice is paid afterward, the donor UI never observes it. LUD-21 defines ERROR as an error envelope but does not define all ERROR responses as terminal. Please either classify only an explicit stable terminal condition (for example, expired), introduce a machine-readable terminal error contract server-side, or keep retrying ambiguous ERROR responses. Add a polling test for the “Please try again later” response proving it retries.
MEDIUM
- None.
LOW
- None.
Validation: 143 tests passed; ESLint completed with 4 existing warnings and 0 errors; git diff --check passed.
🤖 Review by GPT-5 Codex via Blink-Claw-Bot
Blink's verify route returns status:ERROR for BOTH a terminal 'Not found'
(invoice never existed) AND transient backend failures ('Internal server
error', or a GraphQL throw => 'We could not verify the invoice. Please try
again later.'). Classifying every ERROR as terminal would permanently stop the
widget's only settlement detector on a temporary blip, and a payment landing
afterward would go unobserved.
Narrow the terminal classification to an explicit not-found reason
(isTerminalVerifyReason => /not\s*found/i). Every other status:ERROR stays
untagged and is retried (5s backoff) until settle or invoice expiry. Fails safe:
an unrecognised reason is treated as transient, never as a missed payment.
Applied to both the canonical module and the widget's inline copy (parity).
Tests: not-found is terminal; 'try again later' and 'Internal server error' are
NOT terminal (canonical + inline parity); pollVerifyStatus keeps retrying on a
transient 'try again later' status:ERROR driven end-to-end through the real
inline verifyLnurlPayment. 147 tests pass; eslint clean.
|
Good catch — fixed in the latest push. You're right that Blink's verify route returns Change: the terminal tag is now gated on a stable not-found reason only:
Tests added (incl. the one you requested):
147 tests pass; ESLint clean (0 errors). |
blink-dev-bot
left a comment
There was a problem hiding this comment.
HIGH
- None. The previous blocker is addressed: only an explicit not-found verify reason is treated as terminal, while ambiguous/transient
status: ERRORresponses continue backing off and retrying.
MEDIUM
- None.
LOW
- None.
Validation: GitHub CI is green. Locally, 147 tests passed, ESLint completed with 4 existing warnings and 0 errors, and git diff --check origin/main...HEAD passed.
🤖 Review by GPT-5 Codex via Blink-Claw-Bot
Summary
On the self-custodial (Spark) path, a genuine LUD-21 verify
{"status":"ERROR"}(invoice not found/rejected) means the invoice will never settle. PreviouslypollVerifyStatustreated it like any transient failure — backing off 5s and re-pollinglnurl.blink.sv/verify/*until the invoice expiry deadline. That's minutes of pointless polling per not-found invoice.This makes a terminal verify error stop polling immediately, while leaving transient network/HTTP errors to back off and retry as before.
Context
This came out of a review comparing the widget's verify-polling against the runaway polling found in the BTCPay Blink plugin (blinkbitcoin/blink-wip#1123). The widget is already well-behaved — polling is bounded by the invoice expiry and cancellation is race-safe via generation tokens, so it does not have the plugin's runaway-forever or fan-out bugs. This PR closes the one remaining gap: a genuine
status:ERRORshould terminate polling early rather than run out the expiry clock.Change
verifyLnurlPayment(canonicaljs/blink-lnurl.jsand the widget's inlinegetLnurl()copy) now tags astatus:ERRORthrow as terminal:err.lnurlVerifyTerminal = true. Non-ok HTTP throws stay untagged (transient).pollVerifyStatusstops (no reschedule) on a terminal error; transient errors keep the existing 5s backoff-and-retry.Tests
tests/blink-lnurl.spec.js:status:ERRORis terminal; non-ok HTTP is not.tests/widget-spark.spec.js: inline-copy parity for both cases, asserted against the canonical module — closes a prior gap where the inline ERROR/non-ok path was untested.tests/widget-polling.spec.js:pollVerifyStatusstops on terminal; keeps backing off/retrying on transient.Compatibility
No public API / DOM / config changes. Behavior only differs for a not-found/rejected invoice (it now stops sooner). Version
1.5.0 → 1.5.1(+ analyticswidget_version).Note: this widget redeploys to all live embeds on merge to
main; the change is additive and low-risk (a strictly-earlier stop on an invoice that can never be paid).