Skip to content

Commit

Permalink
refactor(core): clean pending invoice 'markAsPaid' signals (#4028)
Browse files Browse the repository at this point in the history
* refactor(core): pass pendingInvoiceLogger forward to 'processPendingInvoice'

* refactor(core): refactor ProcessPendingInvoiceResult object

* refactor(core): rename 'processed' events to be clearer

* fix(core): remove marking 'markProcessedAsCanceledOrExpired' as paid

* refactor(core): enforce that 'processedPaid' and 'processCanceledOrExpired' are mutually exclusive

* chore(core): add comments

* refactor(core): rename to walletInvoiceBeforeProcessing

* fix(core): mark non-paid errors in pending invoices flow

* chore(core): remove redundant 'recordExceptionInCurrentSpan' calls

* refactor(core): consume 'ProcessPendingInvoiceResult' in exhaustive switch

* fix(core): change 'paid' checks to 'processingCompleted'

* chore(core): add mitigation message for ledger-record-failure event

* refactor(core): decline invoice if amount is invalid

* chore(core): cleanup redundant level property
  • Loading branch information
vindard authored Feb 23, 2024
1 parent 4281426 commit 79ba51e
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 122 deletions.
55 changes: 34 additions & 21 deletions core/api/src/app/wallets/decline-single-pending-invoice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {
ProcessPendingInvoiceResult,
ProcessPendingInvoiceResultType,
ProcessedReason,
} from "./process-pending-invoice-result"

Expand All @@ -13,6 +14,10 @@ import { LndService } from "@/services/lnd"

import { elapsedSinceTimestamp } from "@/utils"

const assertUnreachable = (x: never): never => {
throw new Error(`This should never compile with ${x}`)
}

export const declineHeldInvoice = wrapAsyncToRunInSpan({
namespace: "app.invoices",
fnName: "declineHeldInvoice",
Expand All @@ -38,22 +43,28 @@ export const declineHeldInvoice = wrapAsyncToRunInSpan({
logger: pendingInvoiceLogger,
})

if (result.isProcessed) {
const processingCompletedInvoice =
await WalletInvoicesRepository().markAsProcessingCompleted(paymentHash)
if (processingCompletedInvoice instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
}
}
const walletInvoices = WalletInvoicesRepository()
let marked: WalletInvoiceWithOptionalLnInvoice | RepositoryError
switch (result.type) {
case ProcessPendingInvoiceResultType.MarkProcessedAsCanceledOrExpired:
marked = await walletInvoices.markAsProcessingCompleted(paymentHash)
if (marked instanceof Error) {
pendingInvoiceLogger.error("Unable to mark invoice as processingCompleted")
return marked
}
return true

case ProcessPendingInvoiceResultType.Error:
return result.error

const error = "error" in result && result.error
return !(result.isPaid || result.isProcessed)
? false
: result.isProcessed
? false
: error
? error
: result.isPaid
case ProcessPendingInvoiceResultType.MarkProcessedAsPaid:
case ProcessPendingInvoiceResultType.MarkProcessedAsPaidWithError:
case ProcessPendingInvoiceResultType.ReasonInvoiceNotPaidYet:
return true

default:
return assertUnreachable(result)
}
},
})

Expand All @@ -70,21 +81,23 @@ export const processPendingInvoiceForDecline = async ({
// Fetch invoice from lnd service
const lndService = LndService()
if (lndService instanceof Error) {
return ProcessPendingInvoiceResult.paidWithError(lndService)
return ProcessPendingInvoiceResult.err(lndService)
}

const lnInvoiceLookup = await lndService.lookupInvoice({ pubkey, paymentHash })
if (lnInvoiceLookup instanceof InvoiceNotFoundError) {
return ProcessPendingInvoiceResult.processedOnly(ProcessedReason.InvoiceNotFound)
return ProcessPendingInvoiceResult.processAsCanceledOrExpired(
ProcessedReason.InvoiceNotFound,
)
}
if (lnInvoiceLookup instanceof Error) {
return ProcessPendingInvoiceResult.paidWithError(lnInvoiceLookup)
return ProcessPendingInvoiceResult.err(lnInvoiceLookup)
}

// Check status on invoice fetched from lnd
const { isSettled, isHeld } = lnInvoiceLookup
if (isSettled) {
return ProcessPendingInvoiceResult.paidWithError(
return ProcessPendingInvoiceResult.processAsPaidWithError(
new InvalidNonHodlInvoiceError(JSON.stringify({ paymentHash })),
)
}
Expand All @@ -102,8 +115,8 @@ export const processPendingInvoiceForDecline = async ({
)
const invoiceSettled = await lndService.cancelInvoice({ pubkey, paymentHash })
if (invoiceSettled instanceof Error) {
return ProcessPendingInvoiceResult.paidWithError(invoiceSettled)
return ProcessPendingInvoiceResult.err(invoiceSettled)
}

return ProcessPendingInvoiceResult.ok()
return ProcessPendingInvoiceResult.processAsPaid()
}
22 changes: 12 additions & 10 deletions core/api/src/app/wallets/index.types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,23 +135,25 @@ type LnurlPaymentSendArgs = {
type ProcessedReason =
(typeof import("./process-pending-invoice-result").ProcessedReason)[keyof typeof import("./process-pending-invoice-result").ProcessedReason]

type ProcessPendingInvoiceResultType =
(typeof import("./process-pending-invoice-result").ProcessPendingInvoiceResultType)[keyof typeof import("./process-pending-invoice-result").ProcessPendingInvoiceResultType]

type ProcessPendingInvoiceResult =
| {
isProcessed: true
isPaid: true
type: "markProcessedAsPaid"
}
| {
isProcessed: false
isPaid: true
error?: ApplicationError
type: "markProcessedAsPaidWithError"
error: ApplicationError
}
| {
isProcessed: true
isPaid: false
type: "markProcessedAsCanceledOrExpired"
reason: ProcessedReason
}
| {
isProcessed: false
isPaid: false
error?: ApplicationError
type: "reasonInvoiceNotPaidYet"
}
| {
type: "error"
error: ApplicationError
}
34 changes: 17 additions & 17 deletions core/api/src/app/wallets/process-pending-invoice-result.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
export const ProcessedReason = {
InvoiceNotFound: "InvoiceNotFound",
InvoiceCanceled: "InvoiceCanceled",
InvoiceNotFoundOrCanceled: "InvoiceNotFoundOrCanceled",
} as const

export const ProcessPendingInvoiceResultType = {
MarkProcessedAsPaidWithError: "markProcessedAsPaidWithError",
MarkProcessedAsPaid: "markProcessedAsPaid",
MarkProcessedAsCanceledOrExpired: "markProcessedAsCanceledOrExpired",
ReasonInvoiceNotPaidYet: "reasonInvoiceNotPaidYet",
Error: "error",
} as const

export const ProcessPendingInvoiceResult = {
ok: (): ProcessPendingInvoiceResult => ({
isProcessed: true,
isPaid: true,
}),
paidOnly: (): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: true,
processAsPaid: (): ProcessPendingInvoiceResult => ({
type: ProcessPendingInvoiceResultType.MarkProcessedAsPaid,
}),
paidWithError: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: true,
processAsPaidWithError: (error: ApplicationError): ProcessPendingInvoiceResult => ({
type: ProcessPendingInvoiceResultType.MarkProcessedAsPaidWithError,
error,
}),
processedOnly: (reason: ProcessedReason): ProcessPendingInvoiceResult => ({
isProcessed: true,
isPaid: false,
processAsCanceledOrExpired: (reason: ProcessedReason): ProcessPendingInvoiceResult => ({
type: ProcessPendingInvoiceResultType.MarkProcessedAsCanceledOrExpired,
reason,
}),
notPaid: (): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: false,
type: ProcessPendingInvoiceResultType.ReasonInvoiceNotPaidYet,
}),
err: (error: ApplicationError): ProcessPendingInvoiceResult => ({
isProcessed: false,
isPaid: false,
type: ProcessPendingInvoiceResultType.Error,
error,
}),
}
Loading

0 comments on commit 79ba51e

Please sign in to comment.