Merge serializeInvoiceable with serialize without bug (#1051)

* Merge serializeInvoiceable with serialize

* Rename to verifyPayment

We already have a function named checkInvoice in the worker which can be confusing.

Also, we don't need to export this function.

* Use crypto.timingSafeEqual

* Fix missing unwrap for item creation and update
This commit is contained in:
ekzyis 2024-04-10 02:49:20 +02:00 committed by GitHub
parent c8480a4996
commit f3c1ebefcf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 109 additions and 91 deletions

View File

@ -1,6 +1,6 @@
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { ensureProtocol, removeTracking, stripTrailingSlash } from '@/lib/url' import { ensureProtocol, removeTracking, stripTrailingSlash } from '@/lib/url'
import serialize, { serializeInvoicable } from './serial' import serialize from './serial'
import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor' import { decodeCursor, LIMIT, nextCursorEncoded } from '@/lib/cursor'
import { getMetadata, metadataRuleSets } from 'page-metadata-parser' import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
import { ruleSet as publicationDateRuleSet } from '@/lib/timedate-scraper' import { ruleSet as publicationDateRuleSet } from '@/lib/timedate-scraper'
@ -849,9 +849,9 @@ export default {
throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } }) throw new GraphQLError('you must be logged in', { extensions: { code: 'FORBIDDEN' } })
} }
await serializeInvoicable( await serialize(
models.$queryRawUnsafe(`${SELECT} FROM poll_vote($1::INTEGER, $2::INTEGER) AS "Item"`, Number(id), Number(me.id)), models.$queryRawUnsafe(`${SELECT} FROM poll_vote($1::INTEGER, $2::INTEGER) AS "Item"`, Number(id), Number(me.id)),
{ me, models, lnd, hash, hmac } { models, lnd, me, hash, hmac }
) )
return id return id
@ -883,7 +883,6 @@ export default {
if (idempotent) { if (idempotent) {
await serialize( await serialize(
models,
models.$queryRaw` models.$queryRaw`
SELECT SELECT
item_act(${Number(id)}::INTEGER, ${me.id}::INTEGER, ${act}::"ItemActType", item_act(${Number(id)}::INTEGER, ${me.id}::INTEGER, ${act}::"ItemActType",
@ -891,15 +890,16 @@ export default {
FROM "ItemAct" FROM "ItemAct"
WHERE act IN ('TIP', 'FEE') WHERE act IN ('TIP', 'FEE')
AND "itemId" = ${Number(id)}::INTEGER AND "itemId" = ${Number(id)}::INTEGER
AND "userId" = ${me.id}::INTEGER)::INTEGER)` AND "userId" = ${me.id}::INTEGER)::INTEGER)`,
{ models }
) )
} else { } else {
await serializeInvoicable( await serialize(
models.$queryRaw` models.$queryRaw`
SELECT SELECT
item_act(${Number(id)}::INTEGER, item_act(${Number(id)}::INTEGER,
${me?.id || ANON_USER_ID}::INTEGER, ${act}::"ItemActType", ${Number(sats)}::INTEGER)`, ${me?.id || ANON_USER_ID}::INTEGER, ${act}::"ItemActType", ${Number(sats)}::INTEGER)`,
{ me, models, lnd, hash, hmac, enforceFee: sats } { models, lnd, me, hash, hmac, fee: sats }
) )
} }
@ -1282,13 +1282,13 @@ export const updateItem = async (parent, { sub: subName, forward, options, ...it
const fwdUsers = await getForwardUsers(models, forward) const fwdUsers = await getForwardUsers(models, forward)
const uploadIds = uploadIdsFromText(item.text, { models }) const uploadIds = uploadIdsFromText(item.text, { models })
const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me }) const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me });
item = await serializeInvoicable( ([item] = await serialize(
models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB, $4::INTEGER[]) AS "Item"`, models.$queryRawUnsafe(`${SELECT} FROM update_item($1::JSONB, $2::JSONB, $3::JSONB, $4::INTEGER[]) AS "Item"`,
JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds), JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds),
{ models, lnd, hash, hmac, me, enforceFee: imgFees } { models, lnd, me, hash, hmac, fee: imgFees }
) ))
await createMentions(item, models) await createMentions(item, models)
@ -1320,23 +1320,23 @@ export const createItem = async (parent, { forward, options, ...item }, { me, mo
const uploadIds = uploadIdsFromText(item.text, { models }) const uploadIds = uploadIdsFromText(item.text, { models })
const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me }) const { totalFees: imgFees } = await imageFeesInfo(uploadIds, { models, me })
let enforceFee = 0 let fee = 0
if (!me) { if (!me) {
if (item.parentId) { if (item.parentId) {
enforceFee = ANON_FEE_MULTIPLIER fee = ANON_FEE_MULTIPLIER
} else { } else {
const sub = await models.sub.findUnique({ where: { name: item.subName } }) const sub = await models.sub.findUnique({ where: { name: item.subName } })
enforceFee = sub.baseCost * ANON_FEE_MULTIPLIER + (item.boost || 0) fee = sub.baseCost * ANON_FEE_MULTIPLIER + (item.boost || 0)
} }
} }
enforceFee += imgFees fee += imgFees;
item = await serializeInvoicable( ([item] = await serialize(
models.$queryRawUnsafe( models.$queryRawUnsafe(
`${SELECT} FROM create_item($1::JSONB, $2::JSONB, $3::JSONB, '${spamInterval}'::INTERVAL, $4::INTEGER[]) AS "Item"`, `${SELECT} FROM create_item($1::JSONB, $2::JSONB, $3::JSONB, '${spamInterval}'::INTERVAL, $4::INTEGER[]) AS "Item"`,
JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds), JSON.stringify(item), JSON.stringify(fwdUsers), JSON.stringify(options), uploadIds),
{ models, lnd, hash, hmac, me, enforceFee } { models, lnd, me, hash, hmac, fee }
) ))
await createMentions(item, models) await createMentions(item, models)

View File

@ -1,6 +1,6 @@
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { amountSchema, ssValidate } from '@/lib/validate' import { amountSchema, ssValidate } from '@/lib/validate'
import { serializeInvoicable } from './serial' import serialize from './serial'
import { ANON_USER_ID } from '@/lib/constants' import { ANON_USER_ID } from '@/lib/constants'
import { getItem } from './item' import { getItem } from './item'
import { topUsers } from './user' import { topUsers } from './user'
@ -168,9 +168,9 @@ export default {
donateToRewards: async (parent, { sats, hash, hmac }, { me, models, lnd }) => { donateToRewards: async (parent, { sats, hash, hmac }, { me, models, lnd }) => {
await ssValidate(amountSchema, { amount: sats }) await ssValidate(amountSchema, { amount: sats })
await serializeInvoicable( await serialize(
models.$queryRaw`SELECT donate(${sats}::INTEGER, ${me?.id || ANON_USER_ID}::INTEGER)`, models.$queryRaw`SELECT donate(${sats}::INTEGER, ${me?.id || ANON_USER_ID}::INTEGER)`,
{ models, lnd, hash, hmac, me, enforceFee: sats } { models, lnd, me, hash, hmac, fee: sats }
) )
return sats return sats

View File

@ -1,4 +1,5 @@
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { timingSafeEqual } from 'crypto'
import retry from 'async-retry' import retry from 'async-retry'
import Prisma from '@prisma/client' import Prisma from '@prisma/client'
import { settleHodlInvoice } from 'ln-service' import { settleHodlInvoice } from 'ln-service'
@ -6,13 +7,30 @@ import { createHmac } from './wallet'
import { msatsToSats, numWithUnits } from '@/lib/format' import { msatsToSats, numWithUnits } from '@/lib/format'
import { BALANCE_LIMIT_MSATS } from '@/lib/constants' import { BALANCE_LIMIT_MSATS } from '@/lib/constants'
export default async function serialize (models, ...calls) { export default async function serialize (trx, { models, lnd, me, hash, hmac, fee }) {
return await retry(async bail => { // wrap first argument in array if not array already
const isArray = Array.isArray(trx)
if (!isArray) trx = [trx]
// conditional queries can be added inline using && syntax
// we filter any falsy value out here
trx = trx.filter(q => !!q)
let invoice
if (hash) {
invoice = await verifyPayment(models, hash, hmac, fee)
trx = [
models.$executeRaw`SELECT confirm_invoice(${hash}, ${invoice.msatsReceived})`,
...trx
]
}
let results = await retry(async bail => {
try { try {
const [, ...result] = await models.$transaction( const [, ...results] = await models.$transaction(
[models.$executeRaw`SELECT ASSERT_SERIALIZED()`, ...calls], [models.$executeRaw`SELECT ASSERT_SERIALIZED()`, ...trx],
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable }) { isolationLevel: Prisma.TransactionIsolationLevel.Serializable })
return calls.length > 1 ? result : result[0] return results
} catch (error) { } catch (error) {
console.log(error) console.log(error)
// two cases where we get insufficient funds: // two cases where we get insufficient funds:
@ -64,38 +82,20 @@ export default async function serialize (models, ...calls) {
maxTimeout: 100, maxTimeout: 100,
retries: 10 retries: 10
}) })
}
export async function serializeInvoicable (query, { models, lnd, hash, hmac, me, enforceFee }) {
if (!me && !hash) {
throw new Error('you must be logged in or pay')
}
let trx = Array.isArray(query) ? query : [query]
let invoice
if (hash) {
invoice = await checkInvoice(models, hash, hmac, enforceFee)
trx = [
models.$executeRaw`SELECT confirm_invoice(${hash}, ${invoice.msatsReceived})`,
...trx
]
}
let results = await serialize(models, ...trx)
if (hash) { if (hash) {
if (invoice?.isHeld) { await settleHodlInvoice({ secret: invoice.preimage, lnd }) } if (invoice?.isHeld) {
await settleHodlInvoice({ secret: invoice.preimage, lnd })
}
// remove first element since that is the confirmed invoice // remove first element since that is the confirmed invoice
[, ...results] = results results = results.slice(1)
} }
// if there is only one result, return it directly, else the array // if first argument was not an array, unwrap the result
results = results.flat(2) return isArray ? results : results[0]
return results.length > 1 ? results : results[0]
} }
export async function checkInvoice (models, hash, hmac, fee) { async function verifyPayment (models, hash, hmac, fee) {
if (!hash) { if (!hash) {
throw new GraphQLError('hash required', { extensions: { code: 'BAD_INPUT' } }) throw new GraphQLError('hash required', { extensions: { code: 'BAD_INPUT' } })
} }
@ -103,7 +103,7 @@ export async function checkInvoice (models, hash, hmac, fee) {
throw new GraphQLError('hmac required', { extensions: { code: 'BAD_INPUT' } }) throw new GraphQLError('hmac required', { extensions: { code: 'BAD_INPUT' } })
} }
const hmac2 = createHmac(hash) const hmac2 = createHmac(hash)
if (hmac !== hmac2) { if (!timingSafeEqual(Buffer.from(hmac), Buffer.from(hmac2))) {
throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } }) throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } })
} }

View File

@ -1,5 +1,5 @@
import { GraphQLError } from 'graphql' import { GraphQLError } from 'graphql'
import { serializeInvoicable } from './serial' import serialize from './serial'
import { TERRITORY_COST_MONTHLY, TERRITORY_COST_ONCE, TERRITORY_COST_YEARLY, TERRITORY_PERIOD_COST } from '@/lib/constants' import { TERRITORY_COST_MONTHLY, TERRITORY_COST_ONCE, TERRITORY_COST_YEARLY, TERRITORY_PERIOD_COST } from '@/lib/constants'
import { datePivot, whenRange } from '@/lib/time' import { datePivot, whenRange } from '@/lib/time'
import { ssValidate, territorySchema } from '@/lib/validate' import { ssValidate, territorySchema } from '@/lib/validate'
@ -246,9 +246,9 @@ export default {
return sub return sub
} }
const results = await serializeInvoicable( const results = await serialize(
queries, queries,
{ models, lnd, hash, hmac, me, enforceFee: sub.billingCost }) { models, lnd, me, hash, hmac, fee: sub.billingCost })
return results[1] return results[1]
}, },
toggleMuteSub: async (parent, { name }, { me, models }) => { toggleMuteSub: async (parent, { name }, { me, models }) => {
@ -344,8 +344,9 @@ export default {
const billPaidUntil = nextBilling(new Date(), data.billingType) const billPaidUntil = nextBilling(new Date(), data.billingType)
const cost = BigInt(1000) * BigInt(billingCost) const cost = BigInt(1000) * BigInt(billingCost)
const newSub = { ...data, billPaidUntil, billingCost, userId: me.id, status: 'ACTIVE' } const newSub = { ...data, billPaidUntil, billingCost, userId: me.id, status: 'ACTIVE' }
const isTransfer = oldSub.userId !== me.id
await serializeInvoicable([ await serialize([
models.user.update({ models.user.update({
where: { where: {
id: me.id id: me.id
@ -365,11 +366,11 @@ export default {
} }
}), }),
models.sub.update({ where: { name }, data: newSub }), models.sub.update({ where: { name }, data: newSub }),
oldSub.userId !== me.id && models.territoryTransfer.create({ data: { subName: name, oldUserId: oldSub.userId, newUserId: me.id } }) isTransfer && models.territoryTransfer.create({ data: { subName: name, oldUserId: oldSub.userId, newUserId: me.id } })
].filter(q => !!q), ],
{ models, lnd, hash, hmac, me, enforceFee: billingCost }) { models, lnd, hash, me, hmac, fee: billingCost })
if (oldSub.userId !== me.id) notifyTerritoryTransfer({ models, sub: newSub, to: me }) if (isTransfer) notifyTerritoryTransfer({ models, sub: newSub, to: me })
} }
}, },
Sub: { Sub: {
@ -417,7 +418,7 @@ async function createSub (parent, data, { me, models, lnd, hash, hmac }) {
const cost = BigInt(1000) * BigInt(billingCost) const cost = BigInt(1000) * BigInt(billingCost)
try { try {
const results = await serializeInvoicable([ const results = await serialize([
// bill 'em // bill 'em
models.user.update({ models.user.update({
where: { where: {
@ -456,7 +457,7 @@ async function createSub (parent, data, { me, models, lnd, hash, hmac }) {
subName: data.name subName: data.name
} }
}) })
], { models, lnd, hash, hmac, me, enforceFee: billingCost }) ], { models, lnd, me, hash, hmac, fee: billingCost })
return results[1] return results[1]
} catch (error) { } catch (error) {
@ -511,7 +512,7 @@ async function updateSub (parent, { oldName, ...data }, { me, models, lnd, hash,
const proratedCost = proratedBillingCost(oldSub, data.billingType) const proratedCost = proratedBillingCost(oldSub, data.billingType)
if (proratedCost > 0) { if (proratedCost > 0) {
const cost = BigInt(1000) * BigInt(proratedCost) const cost = BigInt(1000) * BigInt(proratedCost)
const results = await serializeInvoicable([ const results = await serialize([
models.user.update({ models.user.update({
where: { where: {
id: me.id id: me.id
@ -537,7 +538,7 @@ async function updateSub (parent, { oldName, ...data }, { me, models, lnd, hash,
userId: me.id userId: me.id
} }
}) })
], { models, lnd, hash, hmac, me, enforceFee: proratedCost }) ], { models, lnd, me, hash, hmac, fee: proratedCost })
return results[2] return results[2]
} }
} }

View File

@ -354,10 +354,12 @@ export default {
expires_at: expiresAt expires_at: expiresAt
}) })
const [inv] = await serialize(models, const [inv] = await serialize(
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${hodlInvoice ? invoice.secret : null}::TEXT, ${invoice.request}, models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${hodlInvoice ? invoice.secret : null}::TEXT, ${invoice.request},
${expiresAt}::timestamp, ${amount * 1000}, ${user.id}::INTEGER, ${description}, NULL, NULL, ${expiresAt}::timestamp, ${amount * 1000}, ${user.id}::INTEGER, ${description}, NULL, NULL,
${invLimit}::INTEGER, ${balanceLimit})`) ${invLimit}::INTEGER, ${balanceLimit})`,
{ models }
)
// the HMAC is only returned during invoice creation // the HMAC is only returned during invoice creation
// this makes sure that only the person who created this invoice // this makes sure that only the person who created this invoice
@ -378,7 +380,7 @@ export default {
throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } }) throw new GraphQLError('bad hmac', { extensions: { code: 'FORBIDDEN' } })
} }
await cancelHodlInvoice({ id: hash, lnd }) await cancelHodlInvoice({ id: hash, lnd })
const inv = await serialize(models, const inv = await serialize(
models.invoice.update({ models.invoice.update({
where: { where: {
hash hash
@ -386,7 +388,9 @@ export default {
data: { data: {
cancelled: true cancelled: true
} }
})) }),
{ models }
)
return inv return inv
}, },
dropBolt11: async (parent, { id }, { me, models, lnd }) => { dropBolt11: async (parent, { id }, { me, models, lnd }) => {
@ -660,9 +664,11 @@ export async function createWithdrawal (parent, { invoice, maxFee }, { me, model
const user = await models.user.findUnique({ where: { id: me.id } }) const user = await models.user.findUnique({ where: { id: me.id } })
// create withdrawl transactionally (id, bolt11, amount, fee) // create withdrawl transactionally (id, bolt11, amount, fee)
const [withdrawl] = await serialize(models, const [withdrawl] = await serialize(
models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice}, models.$queryRaw`SELECT * FROM create_withdrawl(${decoded.id}, ${invoice},
${Number(decoded.mtokens)}, ${msatsFee}, ${user.name}, ${autoWithdraw})`) ${Number(decoded.mtokens)}, ${msatsFee}, ${user.name}, ${autoWithdraw})`,
{ models }
)
payViaPaymentRequest({ payViaPaymentRequest({
lnd, lnd,

View File

@ -80,11 +80,13 @@ export default async ({ query: { username, amount, nostr, comment, payerdata: pa
expires_at: expiresAt expires_at: expiresAt
}) })
await serialize(models, await serialize(
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, NULL, ${invoice.request}, models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, NULL, ${invoice.request},
${expiresAt}::timestamp, ${Number(amount)}, ${user.id}::INTEGER, ${noteStr || description}, ${expiresAt}::timestamp, ${Number(amount)}, ${user.id}::INTEGER, ${noteStr || description},
${comment || null}, ${parsedPayerData || null}::JSONB, ${INV_PENDING_LIMIT}::INTEGER, ${comment || null}, ${parsedPayerData || null}::JSONB, ${INV_PENDING_LIMIT}::INTEGER,
${USER_IDS_BALANCE_NO_LIMIT.includes(Number(user.id)) ? 0 : BALANCE_LIMIT_MSATS})`) ${USER_IDS_BALANCE_NO_LIMIT.includes(Number(user.id)) ? 0 : BALANCE_LIMIT_MSATS})`,
{ models }
)
return res.status(200).json({ return res.status(200).json({
pr: invoice.request, pr: invoice.request,

View File

@ -36,8 +36,10 @@ export async function getServerSideProps ({ req, res, query: { id, error = null
try { try {
// attempt to send gift // attempt to send gift
// catch any errors and just ignore them for now // catch any errors and just ignore them for now
await serialize(models, await serialize(
models.$queryRawUnsafe('SELECT invite_drain($1::INTEGER, $2::TEXT)', session.user.id, id)) models.$queryRawUnsafe('SELECT invite_drain($1::INTEGER, $2::TEXT)', session.user.id, id),
{ models }
)
const invite = await models.invite.findUnique({ where: { id } }) const invite = await models.invite.findUnique({ where: { id } })
notifyInvite(invite.userId) notifyInvite(invite.userId)
} catch (e) { } catch (e) {

View File

@ -17,7 +17,6 @@ export async function auction ({ models }) {
// for each item, run serialized auction function // for each item, run serialized auction function
items.forEach(async item => { items.forEach(async item => {
await serialize(models, await serialize(models.$executeRaw`SELECT run_auction(${item.id}::INTEGER)`, { models })
models.$executeRaw`SELECT run_auction(${item.id}::INTEGER)`)
}) })
} }

View File

@ -79,9 +79,11 @@ export async function earn ({ name }) {
console.log('stacker', earner.userId, 'earned', earnings, 'proportion', earner.proportion, 'rank', earner.rank, 'type', earner.type) console.log('stacker', earner.userId, 'earned', earnings, 'proportion', earner.proportion, 'rank', earner.rank, 'type', earner.type)
if (earnings > 0) { if (earnings > 0) {
await serialize(models, await serialize(
models.$executeRaw`SELECT earn(${earner.userId}::INTEGER, ${earnings}, models.$executeRaw`SELECT earn(${earner.userId}::INTEGER, ${earnings},
${now}::timestamp without time zone, ${earner.type}::"EarnType", ${earner.id}::INTEGER, ${earner.rank}::INTEGER)`) ${now}::timestamp without time zone, ${earner.type}::"EarnType", ${earner.id}::INTEGER, ${earner.rank}::INTEGER)`,
{ models }
)
const userN = notifications[earner.userId] || {} const userN = notifications[earner.userId] || {}

View File

@ -34,7 +34,7 @@ export async function territoryBilling ({ data: { subName }, boss, models }) {
try { try {
const queries = paySubQueries(sub, models) const queries = paySubQueries(sub, models)
await serialize(models, ...queries) await serialize(queries, { models })
} catch (e) { } catch (e) {
console.error(e) console.error(e)
await territoryStatusUpdate() await territoryStatusUpdate()
@ -42,7 +42,7 @@ export async function territoryBilling ({ data: { subName }, boss, models }) {
} }
export async function territoryRevenue ({ models }) { export async function territoryRevenue ({ models }) {
await serialize(models, await serialize(
models.$executeRaw` models.$executeRaw`
WITH revenue AS ( WITH revenue AS (
SELECT coalesce(sum(msats), 0) as revenue, "subName", "userId" SELECT coalesce(sum(msats), 0) as revenue, "subName", "userId"
@ -69,6 +69,7 @@ export async function territoryRevenue ({ models }) {
) )
UPDATE users SET msats = users.msats + "SubActResult".msats UPDATE users SET msats = users.msats + "SubActResult".msats
FROM "SubActResult" FROM "SubActResult"
WHERE users.id = "SubActResult"."userId"` WHERE users.id = "SubActResult"."userId"`,
{ models }
) )
} }

View File

@ -121,10 +121,10 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
// ALSO: is_confirmed and is_held are mutually exclusive // ALSO: is_confirmed and is_held are mutually exclusive
// that is, a hold invoice will first be is_held but not is_confirmed // that is, a hold invoice will first be is_held but not is_confirmed
// and once it's settled it will be is_confirmed but not is_held // and once it's settled it will be is_confirmed but not is_held
const [[{ confirm_invoice: code }]] = await serialize(models, const [[{ confirm_invoice: code }]] = await serialize([
models.$queryRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`, models.$queryRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`,
models.invoice.update({ where: { hash }, data: { confirmedIndex: inv.confirmed_index } }) models.invoice.update({ where: { hash }, data: { confirmedIndex: inv.confirmed_index } })
) ], { models })
// don't send notifications for JIT invoices // don't send notifications for JIT invoices
if (dbInv.preimage) return if (dbInv.preimage) return
@ -143,7 +143,7 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
// and without setting the user balance // and without setting the user balance
// those will be set when the invoice is settled by user action // those will be set when the invoice is settled by user action
const expiresAt = new Date(Math.min(dbInv.expiresAt, datePivot(new Date(), { seconds: 60 }))) const expiresAt = new Date(Math.min(dbInv.expiresAt, datePivot(new Date(), { seconds: 60 })))
return await serialize(models, return await serialize([
models.$queryRaw` models.$queryRaw`
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter) INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter)
VALUES ('finalizeHodlInvoice', jsonb_build_object('hash', ${hash}), 21, true, ${expiresAt})`, VALUES ('finalizeHodlInvoice', jsonb_build_object('hash', ${hash}), 21, true, ${expiresAt})`,
@ -154,11 +154,12 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
expiresAt, expiresAt,
isHeld: true isHeld: true
} }
})) })
], { models })
} }
if (inv.is_canceled) { if (inv.is_canceled) {
return await serialize(models, return await serialize(
models.invoice.update({ models.invoice.update({
where: { where: {
hash: inv.id hash: inv.id
@ -166,7 +167,8 @@ async function checkInvoice ({ data: { hash }, boss, models, lnd }) {
data: { data: {
cancelled: true cancelled: true
} }
})) }), { models }
)
} }
} }
@ -228,8 +230,10 @@ async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
if (wdrwl?.is_confirmed) { if (wdrwl?.is_confirmed) {
const fee = Number(wdrwl.payment.fee_mtokens) const fee = Number(wdrwl.payment.fee_mtokens)
const paid = Number(wdrwl.payment.mtokens) - fee const paid = Number(wdrwl.payment.mtokens) - fee
const [{ confirm_withdrawl: code }] = await serialize(models, models.$queryRaw` const [{ confirm_withdrawl: code }] = await serialize(
SELECT confirm_withdrawl(${dbWdrwl.id}::INTEGER, ${paid}, ${fee})`) models.$queryRaw`SELECT confirm_withdrawl(${dbWdrwl.id}::INTEGER, ${paid}, ${fee})`,
{ models }
)
if (code === 0) { if (code === 0) {
notifyWithdrawal(dbWdrwl.userId, wdrwl) notifyWithdrawal(dbWdrwl.userId, wdrwl)
} }
@ -245,9 +249,10 @@ async function checkWithdrawal ({ data: { hash }, boss, models, lnd }) {
status = 'ROUTE_NOT_FOUND' status = 'ROUTE_NOT_FOUND'
} }
await serialize(models, await serialize(
models.$executeRaw` models.$executeRaw`
SELECT reverse_withdrawl(${dbWdrwl.id}::INTEGER, ${status}::"WithdrawlStatus")` SELECT reverse_withdrawl(${dbWdrwl.id}::INTEGER, ${status}::"WithdrawlStatus")`,
{ models }
) )
} }
} }