Skip to content

Commit 59dc85f

Browse files
committed
fix retry fallbacks that truncate msats
1 parent 94c83b2 commit 59dc85f

File tree

2 files changed

+51
-27
lines changed

2 files changed

+51
-27
lines changed

api/payIn/index.js

Lines changed: 7 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ import { msatsToSats } from '@/lib/format'
77
import { payInBolt11Prospect, payInBolt11WrapProspect } from './lib/payInBolt11'
88
import { isPessimistic, isProxyPayment, isWithdrawal } from './lib/is'
99
import { PAY_IN_INCLUDE, payInCreate } from './lib/payInCreate'
10-
import { NoReceiveWalletError, payOutBolt11Replacement } from './lib/payOutBolt11'
1110
import { payInClone } from './lib/payInPrisma'
1211
import { createHmac } from '../resolvers/wallet'
13-
import { payOutCustodialTokenFromBolt11 } from './lib/payOutCustodialTokens'
1412

1513
// grab a greedy connection for the payIn system on any server
1614
// if we have lock contention of payIns, we don't want to block other queries
1715
import createPrisma from '@/lib/create-prisma'
1816
import { PayInFailureReasonError } from './errors'
17+
import { payInReplacePayOuts } from './lib/payInFailed'
1918
const models = createPrisma({ connectionParams: { connection_limit: 2 } })
2019

2120
export default async function pay (payInType, payInArgs, { me, custodialOnly }) {
@@ -371,43 +370,24 @@ export async function retry (payInId, { me }) {
371370
}
372371
const where = { id: payInId, userId: me.id, payInState: 'FAILED', successorId: null, benefactorId: null }
373372

374-
const payInFailed = await models.payIn.findFirst({
373+
const payInFailedInitial = await models.payIn.findFirst({
375374
where,
376375
include: { ...include, beneficiaries: { include } }
377376
})
378-
if (!payInFailed) {
377+
if (!payInFailedInitial) {
379378
throw new Error('PayIn with id ' + payInId + ' not found')
380379
}
381-
if (isWithdrawal(payInFailed)) {
380+
if (isWithdrawal(payInFailedInitial)) {
382381
throw new Error('Withdrawal payIns cannot be retried')
383382
}
384-
if (isPessimistic(payInFailed, { me })) {
383+
if (isPessimistic(payInFailedInitial, { me })) {
385384
throw new Error('Pessimistic payIns cannot be retried')
386385
}
387386

388-
let payOutBolt11
389-
if (payInFailed.payOutBolt11) {
390-
try {
391-
payOutBolt11 = await payOutBolt11Replacement(models, payInFailed.genesisId ?? payInFailed.id, payInFailed.payOutBolt11)
392-
} catch (e) {
393-
console.error('payOutBolt11Replacement failed', e)
394-
if (!(e instanceof NoReceiveWalletError)) {
395-
throw e
396-
}
397-
// if we can no longer produce a payOutBolt11, we fallback to custodial tokens
398-
payInFailed.payOutCustodialTokens.push(payOutCustodialTokenFromBolt11(payInFailed.payOutBolt11))
399-
// convert the routing fee to another rewards pool output
400-
const routingFee = payInFailed.payOutCustodialTokens.find(t => t.payOutType === 'ROUTING_FEE')
401-
if (routingFee) {
402-
routingFee.payOutType = 'REWARDS_POOL'
403-
routingFee.userId = USER_ID.rewards
404-
}
405-
payInFailed.payOutBolt11 = null
406-
}
407-
}
387+
const payInFailed = await payInReplacePayOuts(models, payInFailedInitial)
408388

409389
const { payIn, result, mCostRemaining } = await models.$transaction(async tx => {
410-
const payInInitial = { ...payInClone({ ...payInFailed, payOutBolt11 }), retryCount: payInFailed.retryCount + 1 }
390+
const payInInitial = { ...payInClone(payInFailed), retryCount: payInFailed.retryCount + 1 }
411391
await obtainRowLevelLocks(tx, payInInitial)
412392
const { payIn, mCostRemaining } = await payInCreate(tx, payInInitial, undefined, { me })
413393

api/payIn/lib/payInFailed.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { USER_ID } from '@/lib/constants'
2+
import { NoReceiveWalletError, payOutBolt11Replacement } from './payOutBolt11'
3+
import { payOutCustodialTokenFromBolt11 } from './payOutCustodialTokens'
4+
5+
export async function payInReplacePayOuts (models, payInFailedInitial) {
6+
if (!payInFailedInitial.payOutBolt11) {
7+
return payInFailedInitial
8+
}
9+
10+
const payInFailed = { ...payInFailedInitial }
11+
try {
12+
payInFailed.payOutBolt11 = await payOutBolt11Replacement(models, payInFailed.genesisId ?? payInFailed.id, payInFailed.payOutBolt11)
13+
// it's possible that the replacement payOutBolt11 is less than the original
14+
// due to msats truncation ... so we need to add the difference to the rewards pool
15+
if (payInFailed.payOutBolt11.msats !== payInFailedInitial.payOutBolt11.msats) {
16+
if (payInFailed.payOutBolt11.msats < payInFailedInitial.payOutBolt11.msats) {
17+
const excessMsats = payInFailedInitial.payOutBolt11.msats - payInFailed.payOutBolt11.msats
18+
payInFailed.payOutCustodialTokens.push({
19+
payOutType: 'REWARDS_POOL',
20+
userId: USER_ID.rewards,
21+
custodialTokenType: 'SATS',
22+
mtokens: excessMsats
23+
})
24+
} else {
25+
throw new NoReceiveWalletError('payOutBolt11Replacement returned more msats than the original')
26+
}
27+
}
28+
} catch (e) {
29+
console.error('payOutBolt11Replacement failed', e)
30+
if (!(e instanceof NoReceiveWalletError)) {
31+
throw e
32+
}
33+
// if we can no longer produce a payOutBolt11, we fallback to custodial tokens
34+
payInFailed.payOutCustodialTokens.push(payOutCustodialTokenFromBolt11(payInFailed.payOutBolt11))
35+
// convert the routing fee to another rewards pool output
36+
const routingFee = payInFailed.payOutCustodialTokens.find(t => t.payOutType === 'ROUTING_FEE')
37+
if (routingFee) {
38+
routingFee.payOutType = 'REWARDS_POOL'
39+
routingFee.userId = USER_ID.rewards
40+
}
41+
payInFailed.payOutBolt11 = null
42+
}
43+
return payInFailed
44+
}

0 commit comments

Comments
 (0)