Skip to content

fix: fallback to outgoing payments in Phoenixd LookupInvoice#2447

Open
G12c4 wants to merge 1 commit into
getAlby:masterfrom
G12c4:fix/phoenixd-lookup-outgoing-payments
Open

fix: fallback to outgoing payments in Phoenixd LookupInvoice#2447
G12c4 wants to merge 1 commit into
getAlby:masterfrom
G12c4:fix/phoenixd-lookup-outgoing-payments

Conversation

@G12c4

@G12c4 G12c4 commented Jun 12, 2026

Copy link
Copy Markdown

Problem

Outgoing Lightning payments via Phoenixd backend are permanently displayed as PENDING in Alby Hub, even after Phoenixd confirms they are settled (isPaid=true).

Closes #2442

Root Cause

LookupInvoice only queries /payments/incoming/{paymentHash}. For outgoing payments this returns 404, and the reconciliation loop in transactions_service.go:718 skips the payment, leaving it as PENDING forever.

Fix

When /payments/incoming/{hash} fails, fall back to /payments/outgoingbyhash/{hash} — a dedicated Phoenixd endpoint that looks up outgoing payments directly by payment hash (added in Phoenixd v0.x). This is an O(1) lookup, not a list+scan.

The LNClient.LookupInvoice interface accepts only a paymentHash with no transaction type hint. Ideally it would accept a type so we could skip the incoming probe for known-outgoing payments, but changing the LNClient interface touches every backend (LND, LDK, CLN, Bark, Cashu) and is out of scope for this bugfix. The incoming→outgoing fallback is safe, minimal, and preserves all existing behavior.

Changes

  • LookupInvoice — tries incoming first (preserving existing behavior), then falls back to outgoing
  • lookupOutgoingPayment — uses /payments/outgoingbyhash/{hash} for direct O(1) lookup
  • outgoingPaymentToTransaction — converts OutgoingPaymentResponse to lnclient.Transaction (mirrors existing phoenixInvoiceToTransaction)
  • Removed noisy Error() log on incoming 404 — the error is still returned and logged by the caller in checkUnsettledTransaction

Testing

  • Compiles cleanly (go build ./lnclient/phoenixd/)
  • No existing tests for the Phoenixd connector — only the LND wrapper has tests
  • Verified the 404 pattern in production logs (v1.22.2 + Phoenixd v0.6.0)
  • All 20 outgoing payments confirmed isPaid=true at Phoenixd level while Alby Hub showed them as PENDING
  • Used the /payments/outgoingbyhash endpoint from Phoenixd source (Api.kt) — returns the best match (succeeded > pending > failed)

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced invoice lookup reliability with improved fallback mechanism that now retrieves outgoing payment data when incoming payment lookup fails, ensuring more successful transaction lookups.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@G12c4, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 2 minutes and 31 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3b41ff8b-58d5-4be2-9b09-218acd83375d

📥 Commits

Reviewing files that changed from the base of the PR and between f3f6ad8 and 5e9191a.

📒 Files selected for processing (1)
  • lnclient/phoenixd/phoenixd.go
📝 Walkthrough

Walkthrough

LookupInvoice now attempts an incoming-payment lookup first and, on any error, falls back to an outgoing-payment lookup; outgoing Phoenixd responses are converted into lnclient.Transaction via a new helper.

Changes

Phoenixd outgoing payment lookup

Layer / File(s) Summary
LookupInvoice with outgoing payment fallback
lnclient/phoenixd/phoenixd.go
LookupInvoice now calls lookupIncomingPayment first and, on error, falls back to lookupOutgoingPayment. The incoming path returns a converted lnclient.Transaction on success.
Outgoing payment lookup implementation
lnclient/phoenixd/phoenixd.go
Added lookupOutgoingPayment which GETs /payments/outgoingbyhash/{paymentHash}, unmarshals OutgoingPaymentResponse, converts via outgoingPaymentToTransaction, and returns errors for non-OK/unmarshal/convert failures.
Outgoing payment to transaction conversion
lnclient/phoenixd/phoenixd.go
New outgoingPaymentToTransaction helper converts OutgoingPaymentResponse to lnclient.Transaction by decoding Bolt11, mapping AmountMsat/FeesPaidMsat, invoice/preimage/hash, created/settled timestamps, ExpiresAt, Type: "outgoing", and description fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • frnandu
  • im-adithya

Poem

🐰 In code I hop through Phoenix's flame,
I trace the hash and find its name,
A fallback path, a gentle stitch,
Outgoing payments found—no glitch.
Hooray, transactions match again! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: adding a fallback to outgoing payments in the LookupInvoice function for the Phoenixd backend.
Linked Issues check ✅ Passed The PR implementation fully addresses all coding requirements from issue #2442: implements fallback from incoming to outgoing payments lookup, uses direct hash-based lookup via /payments/outgoingbyhash endpoint, and includes proper transaction conversion.
Out of Scope Changes check ✅ Passed All changes in the PR are directly scoped to fixing the LookupInvoice function to handle outgoing payments; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
lnclient/phoenixd/phoenixd.go (1)

273-299: ⚡ Quick win

Wrap the new helper errors with endpoint-specific context.

The new lookup/conversion helpers return several bare err values, so reconciliation logs lose which Phoenixd call or decode step actually failed. Please wrap the new error paths with contextual fmt.Errorf(...: %w) messages.

💡 Suggested fix
 req, err := http.NewRequestWithContext(ctx, http.MethodGet, svc.Address+"/payments/outgoing", nil)
 if err != nil {
-	return nil, err
+	return nil, fmt.Errorf("build phoenixd outgoing payments request: %w", err)
 }
 ...
 resp, err := client.Do(req)
 if err != nil {
-	return nil, err
+	return nil, fmt.Errorf("call phoenixd outgoing payments endpoint: %w", err)
 }
 ...
 if err := json.Unmarshal(body, &outgoingPayments); err != nil {
-	return nil, err
+	return nil, fmt.Errorf("decode phoenixd outgoing payments response: %w", err)
 }
 ...
 paymentRequest, err := decodepay.Decodepay(payment.Invoice)
 if err != nil {
 	logger.Logger.WithFields(logrus.Fields{
 		"bolt11": payment.Invoice,
 	}).Errorf("Failed to decode bolt11 invoice: %v", err)
-	return nil, err
+	return nil, fmt.Errorf("decode phoenixd outgoing payment bolt11: %w", err)
 }

As per coding guidelines, "Wrap errors with fmt.Errorf("context: %w", err) for debugging".

Also applies to: 305-327, 537-542

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lnclient/phoenixd/phoenixd.go` around lines 273 - 299, In
lookupIncomingPayment (method PhoenixService.lookupIncomingPayment) wrap each
returned error with endpoint-specific context using fmt.Errorf("phoenixd lookup
incoming %s: %w", ...) or similar — e.g., wrap errors from
http.NewRequestWithContext, client.Do, io.ReadAll, json.Unmarshal and the call
to phoenixInvoiceToTransaction so logs show which HTTP call or decode step
failed; apply the same pattern to the other PhoenixService lookup/convert
helpers referenced in the review (the similar functions around the other diffs)
so every bare err return becomes fmt.Errorf("phoenixd <operation> <identifier>:
%w", err).

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lnclient/phoenixd/phoenixd.go`:
- Around line 263-270: The current fallback to lookupOutgoingPayment is
unconditional on any error from lookupIncomingPayment; change this so the
outgoing fallback is invoked only when lookupIncomingPayment returns a clear
“not found” / 404-style error. In the LookupInvoice flow, update the block
around svc.lookupIncomingPayment(ctx, paymentHash) to detect the specific
not-found sentinel (or HTTP 404 / lnrpc not-found error / custom ErrNotFound
used elsewhere) and only call svc.lookupOutgoingPayment(ctx, paymentHash) in
that case; for all other errors (request failures, 5xxs, decode errors,
phoenixInvoiceToTransaction failures) return the original error instead of
falling back. Ensure you reference and use the same error type/variable used by
lookupIncomingPayment for accurate detection.

---

Nitpick comments:
In `@lnclient/phoenixd/phoenixd.go`:
- Around line 273-299: In lookupIncomingPayment (method
PhoenixService.lookupIncomingPayment) wrap each returned error with
endpoint-specific context using fmt.Errorf("phoenixd lookup incoming %s: %w",
...) or similar — e.g., wrap errors from http.NewRequestWithContext, client.Do,
io.ReadAll, json.Unmarshal and the call to phoenixInvoiceToTransaction so logs
show which HTTP call or decode step failed; apply the same pattern to the other
PhoenixService lookup/convert helpers referenced in the review (the similar
functions around the other diffs) so every bare err return becomes
fmt.Errorf("phoenixd <operation> <identifier>: %w", err).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f680c9a5-162a-4413-a54b-03d0206d1117

📥 Commits

Reviewing files that changed from the base of the PR and between bf9c346 and 05e7570.

📒 Files selected for processing (1)
  • lnclient/phoenixd/phoenixd.go

Comment thread lnclient/phoenixd/phoenixd.go Outdated
@G12c4 G12c4 force-pushed the fix/phoenixd-lookup-outgoing-payments branch from 05e7570 to f3f6ad8 Compare June 12, 2026 08:30
LookupInvoice only queried /payments/incoming/{hash}, returning 404 for
outgoing payments. This caused all outgoing Lightning payments to remain
permanently stuck as PENDING in Alby Hub.

The fix tries incoming first (preserving existing behavior), then falls
back to listing outgoing payments and matching by paymentHash.

Fixes getAlby#2442
@G12c4 G12c4 force-pushed the fix/phoenixd-lookup-outgoing-payments branch from f3f6ad8 to 5e9191a Compare June 12, 2026 08:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Phoenixd: outgoing payments permanently stuck as PENDING (LookupInvoice only queries /payments/incoming)

1 participant