From 7f9628a5407804a0b4a37db5613dc3e2956b1714 Mon Sep 17 00:00:00 2001 From: michael1011 Date: Wed, 28 Jun 2023 16:23:59 +0200 Subject: [PATCH] fix: Lightning payment reliability (#358) --- lib/db/Database.ts | 2 + lib/lightning/LndClient.ts | 35 +++++++++----- lib/swap/PaymentHandler.ts | 14 ++++-- test/integration/Nodes.ts | 2 +- test/integration/lightning/LndClient.spec.ts | 51 ++++++++++---------- 5 files changed, 61 insertions(+), 43 deletions(-) diff --git a/lib/db/Database.ts b/lib/db/Database.ts index 8b4af239..45207f6c 100644 --- a/lib/db/Database.ts +++ b/lib/db/Database.ts @@ -13,6 +13,8 @@ import ChannelCreation from './models/ChannelCreation'; import PendingEthereumTransaction from './models/PendingEthereumTransaction'; class Database { + public static readonly memoryDatabase = ':memory:'; + public static sequelize: Sequelize.Sequelize; private migration: Migration; diff --git a/lib/lightning/LndClient.ts b/lib/lightning/LndClient.ts index 8119ee4c..8f527789 100644 --- a/lib/lightning/LndClient.ts +++ b/lib/lightning/LndClient.ts @@ -71,15 +71,15 @@ interface LndClient { class LndClient extends BaseClient implements LndClient { public static readonly serviceName = 'LND'; - public static readonly paymentMaxParts = 5; - private static readonly grpcOptions = { // 200 MB which is the same value lncli uses: https://github.com/lightningnetwork/lnd/commit/7470f696aebc51b4ab354324e6536f54446538e1 'grpc.max_receive_message_length': 1024 * 1024 * 200, }; - private static readonly minPaymentFee = 21; + public static readonly paymentMaxParts = 5; + private static readonly paymentMinFee = 121; private static readonly paymentTimeout = 300; + private static readonly paymentTimePreference = 0.9; private readonly uri!: string; private readonly maxPaymentFeeRatio!: number; @@ -108,7 +108,7 @@ class LndClient extends BaseClient implements LndClient { const { host, port, certpath, macaroonpath, maxPaymentFeeRatio } = config; this.maxPaymentFeeRatio = - maxPaymentFeeRatio > 0 ? maxPaymentFeeRatio : 0.03; + maxPaymentFeeRatio > 0 ? maxPaymentFeeRatio : 0.01; if (fs.existsSync(certpath)) { this.uri = `${host}:${port}`; @@ -291,6 +291,13 @@ class LndClient extends BaseClient implements LndClient { return this.unaryCall(this.invoices, methodName, params, true); }; + private unaryRouterCall = ( + methodName: keyof RouterClient, + params: T, + ): Promise => { + return this.unaryCall(this.router, methodName, params, true); + }; + private unaryLightningCall = ( methodName: keyof LightningClient, params: T, @@ -437,6 +444,7 @@ class LndClient extends BaseClient implements LndClient { request.setMaxParts(LndClient.paymentMaxParts); request.setTimeoutSeconds(LndClient.paymentTimeout); + request.setTimePref(LndClient.paymentTimePreference); request.setFeeLimitSat(this.calculatePaymentFee(invoice)); request.setPaymentRequest(invoice); @@ -476,9 +484,6 @@ class LndClient extends BaseClient implements LndClient { }); }; - /** - * - */ public static formatPaymentFailureReason = ( reason: lndrpc.PaymentFailureReason, ): string => { @@ -496,6 +501,13 @@ class LndClient extends BaseClient implements LndClient { } }; + public resetMissionControl = () => { + return this.unaryRouterCall< + routerrpc.ResetMissionControlRequest, + routerrpc.ResetMissionControlResponse.AsObject + >('resetMissionControl', new routerrpc.ResetMissionControlRequest()); + }; + /** * Cancel a hold invoice */ @@ -579,7 +591,8 @@ class LndClient extends BaseClient implements LndClient { }; /** - * Returns the latest advertised, aggregated, and authenticated channel information for the specified node identified by its public key + * Returns the latest advertised, aggregated, and authenticated channel information + * for the specified node identified by its public key */ public getNodeInfo = ( publicKey: string, @@ -922,10 +935,8 @@ class LndClient extends BaseClient implements LndClient { private calculatePaymentFee = (invoice: string): number => { const invoiceAmt = bolt11.decode(invoice).satoshis || 0; - - return Math.max( - Math.ceil(invoiceAmt * this.maxPaymentFeeRatio), - LndClient.minPaymentFee, + return Math.ceil( + Math.max(invoiceAmt * this.maxPaymentFeeRatio, LndClient.paymentMinFee), ); }; } diff --git a/lib/swap/PaymentHandler.ts b/lib/swap/PaymentHandler.ts index 9af78dac..b9f54810 100644 --- a/lib/swap/PaymentHandler.ts +++ b/lib/swap/PaymentHandler.ts @@ -13,10 +13,10 @@ import { Payment, PaymentFailureReason } from '../proto/lnd/rpc_pb'; import { ChannelCreationStatus, SwapUpdateEvent } from '../consts/Enums'; import { formatError, - getChainCurrency, + splitPairId, getHexBuffer, + getChainCurrency, getLightningCurrency, - splitPairId, } from '../Utils'; class PaymentHandler { @@ -177,8 +177,13 @@ class PaymentHandler { ), ); - // If the invoice could not be paid but the Swap has a Channel Creation attached to it, a channel will be opened - } else if ( + return undefined; + } + + await lightningCurrency.lndClient!.resetMissionControl(); + + // If the invoice could not be paid but the Swap has a Channel Creation attached to it, a channel will be opened + if ( typeof error === 'number' && channelCreation && channelCreation.status !== ChannelCreationStatus.Created @@ -187,7 +192,6 @@ class PaymentHandler { case PaymentFailureReason.FAILURE_REASON_TIMEOUT: case PaymentFailureReason.FAILURE_REASON_NO_ROUTE: case PaymentFailureReason.FAILURE_REASON_INSUFFICIENT_BALANCE: - // TODO: !formattedError.startsWith('unable to route payment to destination: UnknownNextPeer') await this.channelNursery.openChannel( lightningCurrency, swap, diff --git a/test/integration/Nodes.ts b/test/integration/Nodes.ts index a5f21b20..69f6ac13 100644 --- a/test/integration/Nodes.ts +++ b/test/integration/Nodes.ts @@ -39,5 +39,5 @@ export const bitcoinLndClient = new LndClient(Logger.disabledLogger, 'BTC', { port: 10009, certpath: `${lndDataPath}/certificates/tls.cert`, macaroonpath: `${lndDataPath}/macaroons/admin.macaroon`, - maxPaymentFeeRatio: 0.03, + maxPaymentFeeRatio: 0.01, }); diff --git a/test/integration/lightning/LndClient.spec.ts b/test/integration/lightning/LndClient.spec.ts index 71d0719c..c8d13344 100644 --- a/test/integration/lightning/LndClient.spec.ts +++ b/test/integration/lightning/LndClient.spec.ts @@ -2,8 +2,9 @@ import * as grpc from '@grpc/grpc-js'; import { readFileSync } from 'fs'; import { getPort } from '../../Utils'; import Logger from '../../../lib/Logger'; -import { bitcoinLndClient, lndDataPath } from '../Nodes'; +import Database from '../../../lib/db/Database'; import LndClient from '../../../lib/lightning/LndClient'; +import { bitcoinClient, bitcoinLndClient, lndDataPath } from '../Nodes'; import { LightningClient, LightningService, @@ -15,37 +16,37 @@ import { } from '../../../lib/proto/lnd/rpc_pb'; describe('LndClient', () => { + const db = new Database(Logger.disabledLogger, Database.memoryDatabase); + beforeAll(async () => { - await bitcoinLndClient.connect(); + await db.init(); + await bitcoinClient.connect(); + await Promise.all([bitcoinLndClient.connect(), bitcoinClient.generate(1)]); }); afterAll(async () => { + await db.close(); + + bitcoinClient.disconnect(); bitcoinLndClient.disconnect(); }); - test('should calculate payment fees', async () => { - const calculatePaymentFee = bitcoinLndClient['calculatePaymentFee']; - - const bigInvoiceAmount = 8754398; - const maxPaymentFeeRatio = 0.03; - let invoice = await bitcoinLndClient.addInvoice(bigInvoiceAmount); - - // Should use the payment fee ratio for big payments - expect(calculatePaymentFee(invoice.paymentRequest)).toEqual( - Math.ceil(bigInvoiceAmount * maxPaymentFeeRatio), - ); - - // Should use the minimal payment fee for small payments - invoice = await bitcoinLndClient.addInvoice(1); - expect(calculatePaymentFee(invoice.paymentRequest)).toEqual( - LndClient['minPaymentFee'], - ); - - invoice = await bitcoinLndClient.addInvoice(0); - expect(calculatePaymentFee(invoice.paymentRequest)).toEqual( - LndClient['minPaymentFee'], - ); - }); + test.each` + fee | amount + ${10000} | ${1000000} + ${87544} | ${8754398} + ${LndClient['paymentMinFee']} | ${0} + ${LndClient['paymentMinFee']} | ${1} + `( + 'should calculate payment fee $fee for invoice amount $amount', + async ({ fee, amount }) => { + expect( + bitcoinLndClient['calculatePaymentFee']( + (await bitcoinLndClient.addInvoice(amount)).paymentRequest, + ), + ).toEqual(fee); + }, + ); test('should handle messages longer than the default gRPC limit', async () => { // 4 MB is the default gRPC limit