move invoice creation outside of interactive tx
This commit is contained in:
parent
6e8d7ef1b8
commit
0aa5ba4955
|
@ -130,9 +130,11 @@ async function performOptimisticAction (actionType, args, context) {
|
||||||
const { models } = context
|
const { models } = context
|
||||||
const action = paidActions[actionType]
|
const action = paidActions[actionType]
|
||||||
|
|
||||||
|
context.optimistic = true
|
||||||
|
context.lndInvoice = await createLndInvoice(actionType, args, context)
|
||||||
|
|
||||||
return await models.$transaction(async tx => {
|
return await models.$transaction(async tx => {
|
||||||
context.tx = tx
|
context.tx = tx
|
||||||
context.optimistic = true
|
|
||||||
|
|
||||||
const invoice = await createDbInvoice(actionType, args, context)
|
const invoice = await createDbInvoice(actionType, args, context)
|
||||||
|
|
||||||
|
@ -160,15 +162,20 @@ async function performPessimisticAction (actionType, args, context) {
|
||||||
const invoice = await verifyPayment(context)
|
const invoice = await verifyPayment(context)
|
||||||
args.invoiceId = invoice.id
|
args.invoiceId = invoice.id
|
||||||
|
|
||||||
|
// make sure to perform before settling so we don't race with worker to onPaid
|
||||||
|
const result = await action.perform(args, context)
|
||||||
|
|
||||||
|
// XXX this might cause the interactive tx to time out
|
||||||
await settleHodlInvoice({ secret: invoice.preimage, lnd })
|
await settleHodlInvoice({ secret: invoice.preimage, lnd })
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: await action.perform(args, context),
|
result,
|
||||||
paymentMethod: 'PESSIMISTIC'
|
paymentMethod: 'PESSIMISTIC'
|
||||||
}
|
}
|
||||||
}, { isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted })
|
}, { isolationLevel: Prisma.TransactionIsolationLevel.ReadCommitted })
|
||||||
} else {
|
} else {
|
||||||
// just create the invoice and complete action when it's paid
|
// just create the invoice and complete action when it's paid
|
||||||
|
context.lndInvoice = await createLndInvoice(actionType, args, context)
|
||||||
return {
|
return {
|
||||||
invoice: await createDbInvoice(actionType, args, context),
|
invoice: await createDbInvoice(actionType, args, context),
|
||||||
paymentMethod: 'PESSIMISTIC'
|
paymentMethod: 'PESSIMISTIC'
|
||||||
|
@ -201,13 +208,18 @@ export async function retryPaidAction (actionType, args, context) {
|
||||||
throw new Error(`retryPaidAction - missing invoiceId ${actionType}`)
|
throw new Error(`retryPaidAction - missing invoiceId ${actionType}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
context.optimistic = true
|
||||||
context.user = await models.user.findUnique({ where: { id: me.id } })
|
context.user = await models.user.findUnique({ where: { id: me.id } })
|
||||||
|
|
||||||
|
const { msatsRequested } = await models.invoice.findUnique({ where: { id: invoiceId, actionState: 'FAILED' } })
|
||||||
|
context.cost = BigInt(msatsRequested)
|
||||||
|
context.lndInvoice = await createLndInvoice(actionType, args, context)
|
||||||
|
|
||||||
return await models.$transaction(async tx => {
|
return await models.$transaction(async tx => {
|
||||||
context.tx = tx
|
context.tx = tx
|
||||||
context.optimistic = true
|
|
||||||
|
|
||||||
// update the old invoice to RETRYING, so that it's not confused with FAILED
|
// update the old invoice to RETRYING, so that it's not confused with FAILED
|
||||||
const { msatsRequested, actionId } = await tx.invoice.update({
|
const { actionId } = await tx.invoice.update({
|
||||||
where: {
|
where: {
|
||||||
id: invoiceId,
|
id: invoiceId,
|
||||||
actionState: 'FAILED'
|
actionState: 'FAILED'
|
||||||
|
@ -217,7 +229,6 @@ export async function retryPaidAction (actionType, args, context) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
context.cost = BigInt(msatsRequested)
|
|
||||||
context.actionId = actionId
|
context.actionId = actionId
|
||||||
|
|
||||||
// create a new invoice
|
// create a new invoice
|
||||||
|
@ -234,13 +245,14 @@ export async function retryPaidAction (actionType, args, context) {
|
||||||
const OPTIMISTIC_INVOICE_EXPIRE = { minutes: 10 }
|
const OPTIMISTIC_INVOICE_EXPIRE = { minutes: 10 }
|
||||||
const PESSIMISTIC_INVOICE_EXPIRE = { minutes: 10 }
|
const PESSIMISTIC_INVOICE_EXPIRE = { minutes: 10 }
|
||||||
|
|
||||||
async function createDbInvoice (actionType, args, context) {
|
// we seperate the invoice creation into two functions because
|
||||||
const { user, models, tx, lnd, cost, optimistic, actionId } = context
|
// because if lnd is slow, it'll timeout the interactive tx
|
||||||
|
async function createLndInvoice (actionType, args, context) {
|
||||||
|
const { user, lnd, cost, optimistic } = context
|
||||||
const action = paidActions[actionType]
|
const action = paidActions[actionType]
|
||||||
const db = tx ?? models
|
const [createLNDInvoice, expirePivot] = optimistic
|
||||||
const [createLNDInvoice, expirePivot, actionState] = optimistic
|
? [createInvoice, OPTIMISTIC_INVOICE_EXPIRE]
|
||||||
? [createInvoice, OPTIMISTIC_INVOICE_EXPIRE, 'PENDING']
|
: [createHodlInvoice, PESSIMISTIC_INVOICE_EXPIRE]
|
||||||
: [createHodlInvoice, PESSIMISTIC_INVOICE_EXPIRE, 'PENDING_HELD']
|
|
||||||
|
|
||||||
if (cost < 1000n) {
|
if (cost < 1000n) {
|
||||||
// sanity check
|
// sanity check
|
||||||
|
@ -248,19 +260,33 @@ async function createDbInvoice (actionType, args, context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const expiresAt = datePivot(new Date(), expirePivot)
|
const expiresAt = datePivot(new Date(), expirePivot)
|
||||||
const lndInv = await createLNDInvoice({
|
return await createLNDInvoice({
|
||||||
description: user?.hideInvoiceDesc ? undefined : await action.describe(args, context),
|
description: user?.hideInvoiceDesc ? undefined : await action.describe(args, context),
|
||||||
lnd,
|
lnd,
|
||||||
mtokens: String(cost),
|
mtokens: String(cost),
|
||||||
expires_at: expiresAt
|
expires_at: expiresAt
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createDbInvoice (actionType, args, context) {
|
||||||
|
const { user, models, tx, lndInvoice, cost, optimistic, actionId } = context
|
||||||
|
const db = tx ?? models
|
||||||
|
const [expirePivot, actionState] = optimistic
|
||||||
|
? [OPTIMISTIC_INVOICE_EXPIRE, 'PENDING']
|
||||||
|
: [PESSIMISTIC_INVOICE_EXPIRE, 'PENDING_HELD']
|
||||||
|
|
||||||
|
if (cost < 1000n) {
|
||||||
|
// sanity check
|
||||||
|
throw new Error('The cost of the action must be at least 1 sat')
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiresAt = datePivot(new Date(), expirePivot)
|
||||||
const invoice = await db.invoice.create({
|
const invoice = await db.invoice.create({
|
||||||
data: {
|
data: {
|
||||||
hash: lndInv.id,
|
hash: lndInvoice.id,
|
||||||
msatsRequested: cost,
|
msatsRequested: cost,
|
||||||
preimage: optimistic ? undefined : lndInv.secret,
|
preimage: optimistic ? undefined : lndInvoice.secret,
|
||||||
bolt11: lndInv.request,
|
bolt11: lndInvoice.request,
|
||||||
userId: user?.id || USER_ID.anon,
|
userId: user?.id || USER_ID.anon,
|
||||||
actionType,
|
actionType,
|
||||||
actionState,
|
actionState,
|
||||||
|
@ -273,7 +299,7 @@ async function createDbInvoice (actionType, args, context) {
|
||||||
await db.$executeRaw`
|
await db.$executeRaw`
|
||||||
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter, expirein, priority)
|
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter, expirein, priority)
|
||||||
VALUES ('checkInvoice',
|
VALUES ('checkInvoice',
|
||||||
jsonb_build_object('hash', ${lndInv.id}::TEXT), 21, true,
|
jsonb_build_object('hash', ${lndInvoice.id}::TEXT), 21, true,
|
||||||
${expiresAt}::TIMESTAMP WITH TIME ZONE,
|
${expiresAt}::TIMESTAMP WITH TIME ZONE,
|
||||||
${expiresAt}::TIMESTAMP WITH TIME ZONE - now() + interval '10m', 100)`
|
${expiresAt}::TIMESTAMP WITH TIME ZONE - now() + interval '10m', 100)`
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue