paid action limits (#1323)
This commit is contained in:
parent
67d71ef0c8
commit
df62cfb28c
@ -1,6 +1,6 @@
|
|||||||
import { createHodlInvoice, createInvoice, parsePaymentRequest } from 'ln-service'
|
import { createHodlInvoice, createInvoice, parsePaymentRequest } from 'ln-service'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
import { USER_ID } from '@/lib/constants'
|
import { PAID_ACTION_TERMINAL_STATES, USER_ID } from '@/lib/constants'
|
||||||
import { createHmac } from '../resolvers/wallet'
|
import { createHmac } from '../resolvers/wallet'
|
||||||
import { Prisma } from '@prisma/client'
|
import { Prisma } from '@prisma/client'
|
||||||
import * as ITEM_CREATE from './itemCreate'
|
import * as ITEM_CREATE from './itemCreate'
|
||||||
@ -215,13 +215,30 @@ export async function retryPaidAction (actionType, args, context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const INVOICE_EXPIRE_SECS = 600
|
const INVOICE_EXPIRE_SECS = 600
|
||||||
|
const MAX_PENDING_PAID_ACTIONS_PER_USER = 100
|
||||||
|
|
||||||
export async function createLightningInvoice (actionType, args, context) {
|
export async function createLightningInvoice (actionType, args, context) {
|
||||||
// if the action has an invoiceable peer, we'll create a peer invoice
|
// if the action has an invoiceable peer, we'll create a peer invoice
|
||||||
// wrap it, and return the wrapped invoice
|
// wrap it, and return the wrapped invoice
|
||||||
const { cost, models, lnd } = context
|
const { cost, models, lnd, me } = context
|
||||||
const userId = await paidActions[actionType]?.invoiceablePeer?.(args, context)
|
const userId = await paidActions[actionType]?.invoiceablePeer?.(args, context)
|
||||||
|
|
||||||
|
// count pending invoices and bail if we're over the limit
|
||||||
|
const pendingInvoices = await models.invoice.count({
|
||||||
|
where: {
|
||||||
|
userId: me?.id ?? USER_ID.anon,
|
||||||
|
actionState: {
|
||||||
|
// not in a terminal state. Note: null isn't counted by prisma
|
||||||
|
notIn: PAID_ACTION_TERMINAL_STATES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('pending paid actions', pendingInvoices)
|
||||||
|
if (pendingInvoices >= MAX_PENDING_PAID_ACTIONS_PER_USER) {
|
||||||
|
throw new Error('You have too many pending paid actions, cancel some or wait for them to expire')
|
||||||
|
}
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
try {
|
try {
|
||||||
const description = await paidActions[actionType].describe(args, context)
|
const description = await paidActions[actionType].describe(args, context)
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
export const DEFAULT_SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs']
|
export const DEFAULT_SUBS = ['bitcoin', 'nostr', 'tech', 'meta', 'jobs']
|
||||||
export const DEFAULT_SUBS_NO_JOBS = DEFAULT_SUBS.filter(s => s !== 'jobs')
|
export const DEFAULT_SUBS_NO_JOBS = DEFAULT_SUBS.filter(s => s !== 'jobs')
|
||||||
|
|
||||||
|
export const PAID_ACTION_TERMINAL_STATES = ['FAILED', 'PAID', 'RETRYING']
|
||||||
export const NOFOLLOW_LIMIT = 1000
|
export const NOFOLLOW_LIMIT = 1000
|
||||||
export const UNKNOWN_LINK_REL = 'noreferrer nofollow noopener'
|
export const UNKNOWN_LINK_REL = 'noreferrer nofollow noopener'
|
||||||
export const BOOST_MULT = 5000
|
export const BOOST_MULT = 5000
|
||||||
|
@ -7,9 +7,11 @@ import { addWalletLog } from '@/api/resolvers/wallet'
|
|||||||
import walletDefs from 'wallets/server'
|
import walletDefs from 'wallets/server'
|
||||||
import { parsePaymentRequest } from 'ln-service'
|
import { parsePaymentRequest } from 'ln-service'
|
||||||
import { toPositiveNumber } from '@/lib/validate'
|
import { toPositiveNumber } from '@/lib/validate'
|
||||||
|
import { PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
|
||||||
export default [lnd, cln, lnAddr, lnbits, nwc]
|
export default [lnd, cln, lnAddr, lnbits, nwc]
|
||||||
|
|
||||||
|
const MAX_PENDING_INVOICES_PER_WALLET = 25
|
||||||
|
|
||||||
export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360 }, { models }) {
|
export async function createInvoice (userId, { msats, description, descriptionHash, expiry = 360 }, { models }) {
|
||||||
// get the wallets in order of priority
|
// get the wallets in order of priority
|
||||||
const wallets = await models.wallet.findMany({
|
const wallets = await models.wallet.findMany({
|
||||||
@ -45,6 +47,31 @@ export async function createInvoice (userId, { msats, description, descriptionHa
|
|||||||
throw new Error(`no ${walletType} wallet found`)
|
throw new Error(`no ${walletType} wallet found`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check for pending withdrawals
|
||||||
|
const pendingWithdrawals = await models.withdrawl.count({
|
||||||
|
where: {
|
||||||
|
walletId: walletFull.id,
|
||||||
|
status: null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// and pending forwards
|
||||||
|
const pendingForwards = await models.invoiceForward.count({
|
||||||
|
where: {
|
||||||
|
walletId: walletFull.id,
|
||||||
|
invoice: {
|
||||||
|
actionState: {
|
||||||
|
notIn: PAID_ACTION_TERMINAL_STATES
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('pending invoices', pendingWithdrawals + pendingForwards)
|
||||||
|
if (pendingWithdrawals + pendingForwards >= MAX_PENDING_INVOICES_PER_WALLET) {
|
||||||
|
throw new Error('wallet has too many pending invoices')
|
||||||
|
}
|
||||||
|
|
||||||
const invoice = await createInvoice({
|
const invoice = await createInvoice({
|
||||||
msats,
|
msats,
|
||||||
description: wallet.user.hideInvoiceDesc ? undefined : description,
|
description: wallet.user.hideInvoiceDesc ? undefined : description,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { getPaymentFailureStatus, hodlInvoiceCltvDetails } from '@/api/lnd'
|
import { getPaymentFailureStatus, hodlInvoiceCltvDetails } from '@/api/lnd'
|
||||||
import { paidActions } from '@/api/paidAction'
|
import { paidActions } from '@/api/paidAction'
|
||||||
import { LND_PATHFINDING_TIMEOUT_MS } from '@/lib/constants'
|
import { LND_PATHFINDING_TIMEOUT_MS, PAID_ACTION_TERMINAL_STATES } from '@/lib/constants'
|
||||||
import { datePivot } from '@/lib/time'
|
import { datePivot } from '@/lib/time'
|
||||||
import { toPositiveNumber } from '@/lib/validate'
|
import { toPositiveNumber } from '@/lib/validate'
|
||||||
import { Prisma } from '@prisma/client'
|
import { Prisma } from '@prisma/client'
|
||||||
@ -21,7 +21,7 @@ async function transitionInvoice (jobName, { invoiceId, fromState, toState, tran
|
|||||||
const currentDbInvoice = await models.invoice.findUnique({ where: { id: invoiceId } })
|
const currentDbInvoice = await models.invoice.findUnique({ where: { id: invoiceId } })
|
||||||
console.log('invoice is in state', currentDbInvoice.actionState)
|
console.log('invoice is in state', currentDbInvoice.actionState)
|
||||||
|
|
||||||
if (['FAILED', 'PAID', 'RETRYING'].includes(currentDbInvoice.actionState)) {
|
if (PAID_ACTION_TERMINAL_STATES.includes(currentDbInvoice.actionState)) {
|
||||||
console.log('invoice is already in a terminal state, skipping transition')
|
console.log('invoice is already in a terminal state, skipping transition')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user