From 61353a71e27d3bf07b2636a9f59ba8bcb643d12c Mon Sep 17 00:00:00 2001 From: Overtorment Date: Thu, 31 Dec 2020 18:51:23 +0000 Subject: [PATCH] FIX: Debit correct amount in case of overpaid userinvoice (closes #138) --- class/Invo.js | 8 ++++---- class/User.js | 25 ++++++++++++++++++------- controllers/api.js | 8 ++++---- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/class/Invo.js b/class/Invo.js index 8c77044..e428775 100644 --- a/class/Invo.js +++ b/class/Invo.js @@ -38,7 +38,7 @@ export class Invo { } } if (!paymentHash) throw new Error('Could not find payment hash in invoice tags'); - return await this._setIsPaymentHashPaidInDatabase(paymentHash, true); + return await this._setIsPaymentHashPaidInDatabase(paymentHash, decoded.satoshis); } async markAsUnpaidInDatabase() { @@ -54,9 +54,9 @@ export class Invo { return await this._setIsPaymentHashPaidInDatabase(paymentHash, false); } - async _setIsPaymentHashPaidInDatabase(paymentHash, isPaid) { - if (isPaid) { - return await this._redis.set('ispaid_' + paymentHash, 1); + async _setIsPaymentHashPaidInDatabase(paymentHash, settleAmountSat) { + if (settleAmountSat) { + return await this._redis.set('ispaid_' + paymentHash, settleAmountSat); } else { return await this._redis.del('ispaid_' + paymentHash); } diff --git a/class/User.js b/class/User.js index 6a787bb..fc6e724 100644 --- a/class/User.js +++ b/class/User.js @@ -116,7 +116,7 @@ export class User { let self = this; return new Promise(function(resolve, reject) { self._lightning.newAddress({ type: 0 }, async function(err, response) { - if (err) return reject('LND failure'); + if (err) return reject('LND failure when trying to generate new address'); await self.addAddress(response.address); self._bitcoindrpc.request('importaddress', [response.address, response.address, false]); resolve(); @@ -226,8 +226,8 @@ export class User { * @see Invo._setIsPaymentHashPaidInDatabase * @see Invo.markAsPaidInDatabase */ - async setPaymentHashPaid(payment_hash) { - return await this._redis.set('ispaid_' + payment_hash, 1); + async setPaymentHashPaid(payment_hash, settleAmountSat) { + return await this._redis.set('ispaid_' + payment_hash, settleAmountSat); } async lookupInvoice(payment_hash) { @@ -254,7 +254,7 @@ export class User { const ispaid = invoice.settled; // TODO: start using `state` instead as its future proof, and this one might get deprecated if (ispaid) { // so invoice was paid after all - await this.setPaymentHashPaid(payment_hash); + await this.setPaymentHashPaid(payment_hash, invoice.amt_paid_msat ? Math.floor(invoice.amt_paid_msat / 1000) : invoice.amt_paid_sat); await this.clearBalanceCache(); } return ispaid; @@ -283,17 +283,28 @@ export class User { } } - invoice.ispaid = _invoice_ispaid_cache[invoice.payment_hash] || !!(await this.getPaymentHashPaid(invoice.payment_hash)); + let paymentHashPaidAmountSat = 0; + if (_invoice_ispaid_cache[invoice.payment_hash]) { + // static cache hit + invoice.ispaid = true; + paymentHashPaidAmountSat = _invoice_ispaid_cache[invoice.payment_hash]; + } else { + // static cache miss, asking redis cache + paymentHashPaidAmountSat = await this.getPaymentHashPaid(invoice.payment_hash); + if (paymentHashPaidAmountSat) invoice.ispaid = true; + } + if (!invoice.ispaid) { if (decoded && decoded.timestamp > +new Date() / 1000 - 3600 * 24 * 5) { // if invoice is not too old we query lnd to find out if its paid invoice.ispaid = await this.syncInvoicePaid(invoice.payment_hash); + paymentHashPaidAmountSat = await this.getPaymentHashPaid(invoice.payment_hash); // since we have just saved it } } else { - _invoice_ispaid_cache[invoice.payment_hash] = true; + _invoice_ispaid_cache[invoice.payment_hash] = paymentHashPaidAmountSat; } - invoice.amt = decoded.satoshis; + invoice.amt = (paymentHashPaidAmountSat && parseInt(paymentHashPaidAmountSat) > decoded.satoshis) ? parseInt(paymentHashPaidAmountSat) : decoded.satoshis; invoice.expire_time = 3600 * 24; // ^^^default; will keep for now. if we want to un-hardcode it - it should be among tags (`expire_time`) invoice.timestamp = decoded.timestamp; diff --git a/controllers/api.js b/controllers/api.js index 8ae1cea..ac6c8b4 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -60,7 +60,7 @@ const subscribeInvoicesCallCallback = async function (response) { memo: response.memo, preimage: response.r_preimage.toString('hex'), hash: response.r_hash.toString('hex'), - amt_paid_sat: response.value_msat ? Math.floor(response.value_msat / 1000) : response.value, + amt_paid_sat: response.amt_paid_msat ? Math.floor(response.amt_paid_msat / 1000) : response.amt_paid_sat, }; // obtaining a lock, to make sure we push to groundcontrol only once // since this web server can have several instances running, and each will get the same callback from LND @@ -70,7 +70,7 @@ const subscribeInvoicesCallCallback = async function (response) { return; } let invoice = new Invo(redis, bitcoinclient, lightning); - await invoice._setIsPaymentHashPaidInDatabase(LightningInvoiceSettledNotification.hash, true); + await invoice._setIsPaymentHashPaidInDatabase(LightningInvoiceSettledNotification.hash, LightningInvoiceSettledNotification.amt_paid_sat || 1); const user = new User(redis, bitcoinclient, lightning); user._userid = await user.getUseridByPaymentHash(LightningInvoiceSettledNotification.hash); await user.clearBalanceCache(); @@ -253,7 +253,7 @@ router.post('/payinvoice', async function(req, res) { memo: info.description, r_preimage: Buffer.from(preimage, 'hex'), r_hash: Buffer.from(info.payment_hash, 'hex'), - value: +info.num_satoshis, + amt_paid_sat: +info.num_satoshis, }); } await lock.releaseLock(); @@ -355,7 +355,7 @@ router.get('/balance', postLimiter, async function(req, res) { if (balance < 0) balance = 0; res.send({ BTC: { AvailableBalance: balance } }); } catch (Error) { - logger.log('', [req.id, 'error getting balance:', Error.message, 'userid:', u.getUserId()]); + logger.log('', [req.id, 'error getting balance:', Error, 'userid:', u.getUserId()]); return errorGeneralServerError(res); } });