Skip to content

Commit

Permalink
Poll failed invoices with visibility timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
ekzyis committed Jan 4, 2025
1 parent 3fc1291 commit 7e0082e
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 4 deletions.
31 changes: 31 additions & 0 deletions api/resolvers/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,37 @@ const resolvers = {
cursor: nextCursor,
entries: logs
}
},
failedInvoices: async (parent, args, { me, models }) => {
if (!me) {
throw new GqlAuthenticationError()
}
// make sure each invoice is only returned once via visibility timeouts and SKIP LOCKED
return await models.$queryRaw`
WITH failed AS (
UPDATE "Invoice"
SET "lockedAt" = now()
WHERE id IN (
SELECT id FROM "Invoice"
WHERE "userId" = ${me.id}
AND "actionState" = 'FAILED'
AND "userCancel" = false
AND "lockedAt" IS NULL
ORDER BY id DESC
FOR UPDATE SKIP LOCKED
)
RETURNING *
),
_ AS (
INSERT INTO pgboss.job (name, data, startafter, expirein)
SELECT
'unlockInvoice',
jsonb_build_object('id', id),
now() + interval '10 minutes',
interval '15 minutes'
FROM failed
)
SELECT * FROM failed`
}
},
Wallet: {
Expand Down
1 change: 1 addition & 0 deletions api/typeDefs/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const typeDefs = `
wallet(id: ID!): Wallet
walletByType(type: String!): Wallet
walletLogs(type: String, from: String, to: String, cursor: String): WalletLog!
failedInvoices: [Invoice!]!
}
extend type Mutation {
Expand Down
9 changes: 9 additions & 0 deletions fragments/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,12 @@ export const CANCEL_INVOICE = gql`
}
}
`

export const FAILED_INVOICES = gql`
${INVOICE_FIELDS}
query FailedInvoices {
failedInvoices {
...InvoiceFields
}
}
`
6 changes: 6 additions & 0 deletions lib/apollo.js
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,12 @@ function getClient (uri) {
facts: [...(existing?.facts || []), ...incoming.facts]
}
}
},
failedInvoices: {
keyArgs: [],
merge (existing, incoming) {
return incoming
}
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Invoice" ADD COLUMN "lockedAt" TIMESTAMP(3);
1 change: 1 addition & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -924,6 +924,7 @@ model Invoice {
cancelled Boolean @default(false)
cancelledAt DateTime?
userCancel Boolean?
lockedAt DateTime?
msatsRequested BigInt
msatsReceived BigInt?
desc String?
Expand Down
32 changes: 29 additions & 3 deletions wallets/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useMe } from '@/components/me'
import { SET_WALLET_PRIORITY, WALLETS } from '@/fragments/wallet'
import { SSR } from '@/lib/constants'
import { FAILED_INVOICES, SET_WALLET_PRIORITY, WALLETS } from '@/fragments/wallet'
import { FAST_POLL_INTERVAL, SSR } from '@/lib/constants'
import { useApolloClient, useMutation, useQuery } from '@apollo/client'
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { getStorageKey, getWalletByType, walletPrioritySort, canSend, isConfigured, upsertWalletVariables, siftConfig, saveWalletLocally } from './common'
Expand Down Expand Up @@ -204,7 +204,9 @@ export function WalletsProvider ({ children }) {
removeLocalWallets
}}
>
{children}
<RetryHandler>
{children}
</RetryHandler>
</WalletsContext.Provider>
)
}
Expand All @@ -225,3 +227,27 @@ export function useSendWallets () {
.filter(w => !w.def.isAvailable || w.def.isAvailable())
.filter(w => w.config?.enabled && canSend(w))
}

function RetryHandler ({ children }) {
const failedInvoices = useFailedInvoices()

useEffect(() => {
// TODO: retry
}, [failedInvoices])

return children
}

function useFailedInvoices () {
const wallets = useSendWallets()

// TODO: use longer poll interval in prod?
const { data } = useQuery(FAILED_INVOICES, {
pollInterval: FAST_POLL_INTERVAL,
fetchPolicy: 'no-cache',
nextFetchPolicy: 'no-cache',
skip: wallets.length === 0
})

return data?.failedInvoices ?? []
}
4 changes: 3 additions & 1 deletion worker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import createPrisma from '@/lib/create-prisma'
import {
checkInvoice, checkPendingDeposits, checkPendingWithdrawals,
checkWithdrawal,
finalizeHodlInvoice, subscribeToWallet
finalizeHodlInvoice, subscribeToWallet,
unlockInvoice
} from './wallet'
import { repin } from './repin'
import { trust } from './trust'
Expand Down Expand Up @@ -102,6 +103,7 @@ async function work () {
await boss.work('autoWithdraw', jobWrapper(autoWithdraw))
await boss.work('checkInvoice', jobWrapper(checkInvoice))
await boss.work('checkWithdrawal', jobWrapper(checkWithdrawal))
await boss.work('unlockInvoice', jobWrapper(unlockInvoice))
// paidAction jobs
await boss.work('paidActionForwarding', jobWrapper(paidActionForwarding))
await boss.work('paidActionForwarded', jobWrapper(paidActionForwarded))
Expand Down
4 changes: 4 additions & 0 deletions worker/wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -284,3 +284,7 @@ export async function checkPendingWithdrawals (args) {
}
}
}

export async function unlockInvoice ({ data: { id }, models }) {
await models.invoice.update({ where: { id }, data: { lockedAt: null } })
}

0 comments on commit 7e0082e

Please sign in to comment.