auto canceling bolt11s from lnd when auto dropped from DB (#793)

* auto canceling bolt11s from lnd when auto dropped from DB

* auto canceling bolt11s from lnd when auto dropped from DB

* removed semicolon for lint

* changed cancleHodlInvoic to deletePayment function

* updated code to account for failed LND deletes

* linter fixes

* updated to only remove hashes and bolt11's from model when successfully deleted from LND

* updated to revert unsuccessful deletes from LND and add those values back into the db

* linter fix and renaming for clarity

* updated WITH query

* added if statement to account for invoices not returning from db

* fixed linter

* reverted docker-compose.yml

* made it dry

* made it dry

* added a comment because the query might be confusing

* made query moar dry

* Query formatting

* Fix query returns number of rows instead of rows

* updated  to

* fixed linter

* removed lnbits dir

* removed gitignore and docker-compose.yml from pr

* added deleting from LND in wallet resolver

* linter + added missing import

* fixed merge conflict

* refine invoice deletion

---------

Co-authored-by: ekzyis <ek@stacker.news>
Co-authored-by: keyan <keyan.kousha+huumn@gmail.com>
This commit is contained in:
Dillon 2024-02-14 17:31:25 -06:00 committed by GitHub
parent 1444ff476e
commit 35d212573e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 68 additions and 25 deletions

View File

@ -1,4 +1,4 @@
import { getIdentity, createHodlInvoice, createInvoice, decodePaymentRequest, payViaPaymentRequest, cancelHodlInvoice, getInvoice as getInvoiceFromLnd, getNode, authenticatedLndGrpc } from 'ln-service'
import { getIdentity, createHodlInvoice, createInvoice, decodePaymentRequest, payViaPaymentRequest, cancelHodlInvoice, getInvoice as getInvoiceFromLnd, getNode, authenticatedLndGrpc, deletePayment } from 'ln-service'
import { GraphQLError } from 'graphql'
import crypto from 'crypto'
import serialize from './serial'
@ -8,7 +8,7 @@ import { SELECT } from './item'
import { lnAddrOptions } from '../../lib/lnurl'
import { msatsToSats, msatsToSatsDecimal } from '../../lib/format'
import { LNDAutowithdrawSchema, amountSchema, lnAddrAutowithdrawSchema, lnAddrSchema, ssValidate, withdrawlSchema } from '../../lib/validate'
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '../../lib/constants'
import { ANON_BALANCE_LIMIT_MSATS, ANON_INV_PENDING_LIMIT, ANON_USER_ID, BALANCE_LIMIT_MSATS, INVOICE_RETENTION_DAYS, INV_PENDING_LIMIT, USER_IDS_BALANCE_NO_LIMIT } from '../../lib/constants'
import { datePivot } from '../../lib/time'
import assertGofacYourself from './ofac'
import { HEX_REGEX } from '../../lib/macaroon'
@ -371,21 +371,40 @@ export default {
}))
return inv
},
dropBolt11: async (parent, { id }, { me, models }) => {
dropBolt11: async (parent, { id }, { me, models, lnd }) => {
if (!me) {
throw new GraphQLError('you must be logged in', { extensions: { code: 'UNAUTHENTICATED' } })
}
await models.withdrawl.update({
where: {
userId: me.id,
id: Number(id),
createdAt: {
lte: datePivot(new Date(), { days: -7 })
}
},
data: { bolt11: null, hash: null }
})
const retention = `${INVOICE_RETENTION_DAYS} days`
const [invoice] = await models.$queryRaw`
WITH to_be_updated AS (
SELECT id, hash, bolt11
FROM "Withdrawl"
WHERE "userId" = ${me.id}
AND id = ${Number(id)}
AND now() > created_at + interval '${retention}'
AND hash IS NOT NULL
), updated_rows AS (
UPDATE "Withdrawl"
SET hash = NULL, bolt11 = NULL
FROM to_be_updated
WHERE "Withdrawl".id = to_be_updated.id)
SELECT * FROM to_be_updated;`
if (invoice) {
try {
await deletePayment({ id: invoice.hash, lnd })
} catch (error) {
console.error(error)
await models.withdrawl.update({
where: { id: invoice.id },
data: { hash: invoice.hash, bolt11: invoice.bolt11 }
})
throw new GraphQLError('failed to drop bolt11 from lnd', { extensions: { code: 'BAD_INPUT' } })
}
}
return { id }
},
upsertWalletLND: async (parent, { settings, ...data }, { me, models }) => {

View File

@ -161,11 +161,12 @@ function PrivacyOption ({ wd }) {
try {
await dropBolt11({ variables: { id: wd.id } })
} catch (err) {
console.error(err)
toaster.danger('unable to delete invoice')
toaster.danger('unable to delete invoice: ' + err.message || err.toString?.())
throw err
} finally {
onClose()
}
}
onClose()
}}
/>
)

View File

@ -1,6 +1,6 @@
import serialize from '../api/resolvers/serial.js'
import {
getInvoice, getPayment, cancelHodlInvoice,
getInvoice, getPayment, cancelHodlInvoice, deletePayment,
subscribeToInvoices, subscribeToPayments, subscribeToInvoice
} from 'ln-service'
import { sendUserNotification } from '../api/webPush/index.js'
@ -253,14 +253,37 @@ async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
}
}
export async function autoDropBolt11s ({ models }) {
await serialize(models, models.$executeRaw`
UPDATE "Withdrawl"
SET hash = NULL, bolt11 = NULL
WHERE "userId" IN (SELECT id FROM users WHERE "autoDropBolt11s")
AND now() > created_at + interval '${INVOICE_RETENTION_DAYS} days'
AND hash IS NOT NULL;`
)
export async function autoDropBolt11s ({ models, lnd }) {
const retention = `${INVOICE_RETENTION_DAYS} days`
// This query will update the withdrawls and return what the hash and bol11 values were before the update
const invoices = await models.$queryRaw`
WITH to_be_updated AS (
SELECT id, hash, bolt11
FROM "Withdrawl"
WHERE "userId" IN (SELECT id FROM users WHERE "autoDropBolt11s")
AND now() > created_at + interval '${retention}'
AND hash IS NOT NULL
), updated_rows AS (
UPDATE "Withdrawl"
SET hash = NULL, bolt11 = NULL
FROM to_be_updated
WHERE "Withdrawl".id = to_be_updated.id)
SELECT * FROM to_be_updated;`
if (invoices.length > 0) {
for (const invoice of invoices) {
try {
await deletePayment({ id: invoice.hash, lnd })
} catch (error) {
console.error(`Error removing invoice with hash ${invoice.hash}:`, error)
await models.withdrawl.update({
where: { id: invoice.id },
data: { hash: invoice.hash, bolt11: invoice.bolt11 }
})
}
}
}
}
// The callback subscriptions above will NOT get called for HODL invoices that are already paid.