Skip to content

Commit

Permalink
Only attempt payment 3 times
Browse files Browse the repository at this point in the history
  • Loading branch information
ekzyis committed Jan 8, 2025
1 parent ebd1349 commit 09cb758
Show file tree
Hide file tree
Showing 8 changed files with 32 additions and 10 deletions.
7 changes: 5 additions & 2 deletions api/paidAction/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,9 @@ export async function retryPaidAction (actionType, args, incomingContext) {
me: await models.user.findUnique({ where: { id: parseInt(me.id) } }),
cost: BigInt(msatsRequested),
actionId,
predecessorId: failedInvoice.id
predecessorId: failedInvoice.id,
// a locked invoice means we're retrying a payment from the beginning with all sender and receiver wallets
retry: failedInvoice.lockedAt ? failedInvoice.retry + 1 : failedInvoice.retry
}

let invoiceArgs
Expand Down Expand Up @@ -419,7 +421,7 @@ async function createSNInvoice (actionType, args, context) {
}

async function createDbInvoice (actionType, args, context) {
const { me, models, tx, cost, optimistic, actionId, invoiceArgs, predecessorId } = context
const { me, models, tx, cost, optimistic, actionId, invoiceArgs, retry, predecessorId } = context
const { bolt11, wrappedBolt11, preimage, wallet, maxFee } = invoiceArgs

const db = tx ?? models
Expand All @@ -445,6 +447,7 @@ async function createDbInvoice (actionType, args, context) {
actionArgs: args,
expiresAt,
actionId,
retry,
predecessorId
}

Expand Down
2 changes: 2 additions & 0 deletions api/resolvers/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { pushSubscriptionSchema, validateSchema } from '@/lib/validate'
import { replyToSubscription } from '@/lib/webPush'
import { getSub } from './sub'
import { GqlAuthenticationError, GqlInputError } from '@/lib/error'
import { WALLET_MAX_RETRIES } from '@/lib/constants'

export default {
Query: {
Expand Down Expand Up @@ -350,6 +351,7 @@ export default {
WHERE "Invoice"."userId" = $1
AND "Invoice"."updated_at" < $2
AND "Invoice"."actionState" = 'FAILED'
AND "Invoice"."retry" >= ${WALLET_MAX_RETRIES}
AND (
"Invoice"."actionType" = 'ITEM_CREATE' OR
"Invoice"."actionType" = 'ZAP' OR
Expand Down
6 changes: 5 additions & 1 deletion api/resolvers/paidAction.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { retryPaidAction } from '../paidAction'
import { USER_ID } from '@/lib/constants'
import { USER_ID, WALLET_MAX_RETRIES } from '@/lib/constants'

function paidActionType (actionType) {
switch (actionType) {
Expand Down Expand Up @@ -67,6 +67,10 @@ export default {
throw new Error(`Invoice is not in failed state: ${invoice.actionState}`)
}

if (invoice.retry >= WALLET_MAX_RETRIES) {
throw new Error('Payment has been retried too many times')
}

const result = await retryPaidAction(invoice.actionType, { invoice }, { models, me, lnd })

return {
Expand Down
7 changes: 5 additions & 2 deletions api/resolvers/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
PAID_ACTION_PAYMENT_METHODS,
WALLET_CREATE_INVOICE_TIMEOUT_MS,
WALLET_RETRY_AFTER_MS,
WALLET_RETRY_BEFORE_MS
WALLET_RETRY_BEFORE_MS,
WALLET_MAX_RETRIES
} from '@/lib/constants'
import { amountSchema, validateSchema, withdrawlSchema, lnAddrSchema } from '@/lib/validate'
import assertGofacYourself from './ofac'
Expand Down Expand Up @@ -480,6 +481,7 @@ const resolvers = {
"cancelledAt" + $3::interval
)
AND "lockedAt" IS NULL
AND "retry" < $4
ORDER BY id DESC
FOR UPDATE SKIP LOCKED
)
Expand All @@ -497,7 +499,8 @@ const resolvers = {
SELECT * FROM failed`,
me.id,
`${WALLET_RETRY_AFTER_MS} milliseconds`,
`${WALLET_RETRY_BEFORE_MS} milliseconds`)
`${WALLET_RETRY_BEFORE_MS} milliseconds`,
WALLET_MAX_RETRIES)
}
},
Wallet: {
Expand Down
2 changes: 2 additions & 0 deletions lib/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,5 @@ export const WALLET_CREATE_INVOICE_TIMEOUT_MS = 45_000
// by the client due to sender or receiver fallbacks are not returned to the client.
export const WALLET_RETRY_AFTER_MS = 60_000
export const WALLET_RETRY_BEFORE_MS = 86_400_000 // 24 hours
// we want to attempt a payment three times so we retry two times
export const WALLET_MAX_RETRIES = 2
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Invoice" ADD COLUMN "retry" INTEGER NOT NULL DEFAULT 0;
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -925,6 +925,7 @@ model Invoice {
cancelledAt DateTime?
userCancel Boolean?
lockedAt DateTime?
retry Int @default(0)
msatsRequested BigInt
msatsReceived BigInt?
desc String?
Expand Down
15 changes: 10 additions & 5 deletions wallets/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,13 @@ export default [lnd, cln, lnAddr, lnbits, nwc, phoenixd, blink, lnc, webln]

const MAX_PENDING_INVOICES_PER_WALLET = 25

export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360 }, { predecessorId, models }) {
export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360 }, { retry, predecessorId, models }) {
// get the wallets in order of priority
const wallets = await getInvoiceableWallets(userId, { predecessorId, models })
const wallets = await getInvoiceableWallets(userId, {
retry,
predecessorId,
models
})

msats = toPositiveNumber(msats)

Expand Down Expand Up @@ -81,7 +85,7 @@ export async function createInvoice (userId, { msats, description, descriptionHa

export async function createWrappedInvoice (userId,
{ msats, feePercent, description, descriptionHash, expiry = 360 },
{ predecessorId, models, me, lnd }) {
{ retry, predecessorId, models, me, lnd }) {
let logger, bolt11
try {
const { invoice, wallet } = await createInvoice(userId, {
Expand All @@ -90,7 +94,7 @@ export async function createWrappedInvoice (userId,
description,
descriptionHash,
expiry
}, { predecessorId, models })
}, { retry, predecessorId, models })

logger = walletLogger({ wallet, models })
bolt11 = invoice
Expand All @@ -110,7 +114,7 @@ export async function createWrappedInvoice (userId,
}
}

export async function getInvoiceableWallets (userId, { predecessorId, models }) {
export async function getInvoiceableWallets (userId, { retry, predecessorId, models }) {
// filter out all wallets that have already been tried by recursively following the retry chain of predecessor invoices.
// the current predecessor invoice is in state 'FAILED' and not in state 'RETRYING' because we are currently retrying it
// so it has not been updated yet.
Expand Down Expand Up @@ -143,6 +147,7 @@ export async function getInvoiceableWallets (userId, { predecessorId, models })
FROM "Invoice"
JOIN "Retries" ON "Invoice"."id" = "Retries"."predecessorId"
WHERE "Invoice"."actionState" = 'RETRYING'
AND "Invoice"."retry" = ${retry}
)
SELECT
"InvoiceForward"."walletId"
Expand Down

0 comments on commit 09cb758

Please sign in to comment.