From e7c2511fc814ab7b79cee0b1ceca8fa9d821ea45 Mon Sep 17 00:00:00 2001 From: boufni95 Date: Tue, 2 Jul 2024 19:25:22 +0200 Subject: [PATCH] open more channels --- src/services/lnd/lnd.ts | 5 +- src/services/lnd/lsp.ts | 84 +++++++++------------------ src/services/lnd/payInvoiceReq.ts | 3 +- src/services/main/liquidityManager.ts | 39 +++++++++++-- 4 files changed, 67 insertions(+), 64 deletions(-) diff --git a/src/services/lnd/lnd.ts b/src/services/lnd/lnd.ts index b779950d..ef924f99 100644 --- a/src/services/lnd/lnd.ts +++ b/src/services/lnd/lnd.ts @@ -290,7 +290,7 @@ export default class { throw new Error("lnd node is currently out of sync") } await this.Health() - this.log("paying invoice", invoice, "for", amount, "sats") + this.log("paying invoice", invoice, "for", amount, "sats with", useProvider ? 'provider' : 'lnd') if (useProvider) { const res = await this.liquidProvider.PayInvoice(invoice) const providerDst = this.liquidProvider.GetProviderDestination() @@ -314,6 +314,9 @@ export default class { case Payment_PaymentStatus.SUCCEEDED: this.log("invoice payment succeded", Number(payment.valueSat)) res({ feeSat: Math.ceil(Number(payment.feeMsat) / 1000), valueSat: Number(payment.valueSat), paymentPreimage: payment.paymentPreimage }) + return + default: + this.log("inflight payment update index", Number(payment.paymentIndex), Payment_PaymentStatus[payment.status]) } }) }) diff --git a/src/services/lnd/lsp.ts b/src/services/lnd/lsp.ts index 82a1ac32..db50fe36 100644 --- a/src/services/lnd/lsp.ts +++ b/src/services/lnd/lsp.ts @@ -61,29 +61,6 @@ class LSP { this.log = getLogger({ component: serviceName }) } - shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => { - if (this.settings.channelThreshold === 0) { - this.log("channel threshold is 0") - return { shouldOpen: false } - } - const channels = await this.lnd.ListChannels() - if (channels.channels.length > 0) { - this.log("this node already has open channels") - return { shouldOpen: false } - } - const pendingChannels = await this.lnd.ListPendingChannels() - if (pendingChannels.pendingOpenChannels.length > 0) { - this.log("this node already has pending channels") - return { shouldOpen: false } - } - const userState = await this.liquidityProvider.CheckUserState() - if (!userState || userState.max_withdrawable < this.settings.channelThreshold) { - this.log("balance of", userState?.max_withdrawable || 0, "is lower than channel threshold of", this.settings.channelThreshold) - return { shouldOpen: false } - } - return { shouldOpen: true, maxSpendable: userState.max_withdrawable } - } - addPeer = async (pubKey: string, host: string) => { const { peers } = await this.lnd.ListPeers() if (!peers.find(p => p.pubKey === pubKey)) { @@ -98,18 +75,14 @@ export class FlashsatsLSP extends LSP { super("FlashsatsLSP", settings, lnd, liquidityProvider) } - openChannelIfReady = async (): Promise => { - const shouldOpen = await this.shouldOpenChannel() - if (!shouldOpen.shouldOpen) { - return null - } + requestChannel = async (maxSpendable: number): Promise => { if (!this.settings.flashsatsServiceUrl) { this.log("no flashsats service url provided") return null } const serviceInfo = await this.getInfo() - if (+serviceInfo.options.min_initial_client_balance_sat > shouldOpen.maxSpendable) { - this.log("balance of", shouldOpen.maxSpendable, "is lower than service minimum of", serviceInfo.options.min_initial_client_balance_sat) + if (+serviceInfo.options.min_initial_client_balance_sat > maxSpendable) { + this.log("balance of", maxSpendable, "is lower than service minimum of", serviceInfo.options.min_initial_client_balance_sat) return null } const lndInfo = await this.lnd.GetInfo() @@ -118,7 +91,8 @@ export class FlashsatsLSP extends LSP { this.log("no uri found for this node,uri is required to use flashsats") return null } - const lspBalance = (this.settings.channelThreshold * 2).toString() + const channelSize = Math.floor(maxSpendable * (1 - this.settings.maxRelativeFee)) * 2 + const lspBalance = channelSize.toString() const chanExpiryBlocks = serviceInfo.options.max_channel_expiry_blocks const order = await this.createOrder({ nodeUri: myUri, lspBalance, clientBalance: "0", chanExpiryBlocks }) if (order.payment.state !== 'EXPECT_PAYMENT') { @@ -130,11 +104,11 @@ export class FlashsatsLSP extends LSP { this.log("invoice of amount", decoded.numSatoshis, "does not match order total of", order.payment.order_total_sat) return null } - if (decoded.numSatoshis > shouldOpen.maxSpendable) { - this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", shouldOpen.maxSpendable) + if (decoded.numSatoshis > maxSpendable) { + this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", maxSpendable) return null } - const relativeFee = +order.payment.fee_total_sat / this.settings.channelThreshold + const relativeFee = +order.payment.fee_total_sat / channelSize if (relativeFee > this.settings.maxRelativeFee) { this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee) return null @@ -175,18 +149,14 @@ export class OlympusLSP extends LSP { super("OlympusLSP", settings, lnd, liquidityProvider) } - openChannelIfReady = async (): Promise => { - const shouldOpen = await this.shouldOpenChannel() - if (!shouldOpen.shouldOpen) { - return null - } + requestChannel = async (maxSpendable: number): Promise => { if (!this.settings.olympusServiceUrl) { this.log("no olympus service url provided") return null } const serviceInfo = await this.getInfo() - if (+serviceInfo.min_initial_client_balance_sat > shouldOpen.maxSpendable) { - this.log("balance of", shouldOpen.maxSpendable, "is lower than service minimum of", serviceInfo.min_initial_client_balance_sat) + if (+serviceInfo.min_initial_client_balance_sat > maxSpendable) { + this.log("balance of", maxSpendable, "is lower than service minimum of", serviceInfo.min_initial_client_balance_sat) return null } const [servicePub, host] = serviceInfo.uris[0].split('@') @@ -194,7 +164,8 @@ export class OlympusLSP extends LSP { const lndInfo = await this.lnd.GetInfo() const myPub = lndInfo.identityPubkey const refundAddr = await this.lnd.NewAddress(AddressType.WITNESS_PUBKEY_HASH) - const lspBalance = (this.settings.channelThreshold * 2).toString() + const channelSize = Math.floor(maxSpendable * (1 - this.settings.maxRelativeFee)) * 2 + const lspBalance = channelSize.toString() const chanExpiryBlocks = serviceInfo.max_channel_expiry_blocks const order = await this.createOrder({ pubKey: myPub, refundAddr: refundAddr.address, lspBalance, clientBalance: "0", chanExpiryBlocks }) if (order.payment.bolt11.state !== 'EXPECT_PAYMENT') { @@ -206,11 +177,11 @@ export class OlympusLSP extends LSP { this.log("invoice of amount", decoded.numSatoshis, "does not match order total of", order.payment.bolt11.order_total_sat) return null } - if (decoded.numSatoshis > shouldOpen.maxSpendable) { - this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", shouldOpen.maxSpendable) + if (decoded.numSatoshis > maxSpendable) { + this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", maxSpendable) return null } - const relativeFee = +order.payment.bolt11.fee_total_sat / this.settings.channelThreshold + const relativeFee = +order.payment.bolt11.fee_total_sat / channelSize if (relativeFee > this.settings.maxRelativeFee) { this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee) return null @@ -277,12 +248,7 @@ export class VoltageLSP extends LSP { return json } - openChannelIfReady = async (): Promise => { - const shouldOpen = await this.shouldOpenChannel() - if (!shouldOpen.shouldOpen) { - return null - } - + requestChannel = async (maxSpendable: number): Promise => { if (!this.settings.voltageServiceUrl) { this.log("no voltage service url provided") return null @@ -290,10 +256,11 @@ export class VoltageLSP extends LSP { const lndInfo = await this.lnd.GetInfo() const myPub = lndInfo.identityPubkey - const amtMsats = this.settings.channelThreshold * 1000 + const amtSats = Math.floor(maxSpendable * 0.9) + const amtMsats = Math.floor(maxSpendable * 0.9) * 1000 const fee = await this.getFees(amtMsats, myPub) const feeSats = fee.fee_amount_msat / 1000 - const relativeFee = feeSats / this.settings.channelThreshold + const relativeFee = feeSats / (amtSats * 1.1) if (relativeFee > this.settings.maxRelativeFee) { this.log("relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee) @@ -308,15 +275,18 @@ export class VoltageLSP extends LSP { } await this.addPeer(info.pubkey, `${ipv4.address}:${ipv4.port}`) - const invoice = await this.lnd.NewInvoice(this.settings.channelThreshold, "open channel", 60 * 60) + const invoice = await this.lnd.NewInvoice(amtSats, "open channel", 60 * 60) const proposalRes = await this.proposal(invoice.payRequest, fee.id) this.log("proposal res", proposalRes, fee.id) const decoded = await this.lnd.DecodeInvoice(proposalRes.jit_bolt11) - if (decoded.numSatoshis !== this.settings.channelThreshold + feeSats) { - this.log("invoice of amount", decoded.numSatoshis, "does not match expected amount of", this.settings.channelThreshold + feeSats) + if (decoded.numSatoshis !== amtSats + feeSats) { + this.log("invoice of amount", decoded.numSatoshis, "does not match expected amount of", amtSats + feeSats) + return null + } + if (decoded.numSatoshis > maxSpendable) { + this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", maxSpendable) return null } - const res = await this.liquidityProvider.PayInvoice(proposalRes.jit_bolt11) const fees = feeSats + res.network_fee + res.service_fee this.log("paid", res.amount_paid, "to open channel, and a fee of", fees) diff --git a/src/services/lnd/payInvoiceReq.ts b/src/services/lnd/payInvoiceReq.ts index dd45dad1..323448e0 100644 --- a/src/services/lnd/payInvoiceReq.ts +++ b/src/services/lnd/payInvoiceReq.ts @@ -4,7 +4,7 @@ import { SendPaymentRequest } from "../../../proto/lnd/router"; export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number): SendPaymentRequest => ({ amt: BigInt(amount), feeLimitSat: BigInt(feeLimit), - noInflightUpdates: true, + noInflightUpdates: false, paymentRequest: invoice, maxParts: 3, timeoutSeconds: 50, @@ -25,6 +25,5 @@ export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number) paymentHash: Buffer.alloc(0), routeHints: [], timePref: 0, - outgoingChanId: '0' }) \ No newline at end of file diff --git a/src/services/main/liquidityManager.ts b/src/services/main/liquidityManager.ts index 6d81cf4b..adb5bdcc 100644 --- a/src/services/main/liquidityManager.ts +++ b/src/services/main/liquidityManager.ts @@ -71,6 +71,7 @@ export class LiquidityManager { this.log("channel does not have enough balance for invoice,suggesting provider") return 'provider' } + afterInInvoicePaid = async () => { try { await this.orderChannelIfNeeded() @@ -78,9 +79,39 @@ export class LiquidityManager { this.log("error ordering channel", e) } } + + shouldOpenChannel = async (): Promise<{ shouldOpen: false } | { shouldOpen: true, maxSpendable: number }> => { + const threshold = this.settings.lspSettings.channelThreshold + if (threshold === 0) { + this.log("channel threshold is 0") + return { shouldOpen: false } + } + const { remote } = await this.lnd.ChannelBalance() + if (remote > threshold) { + this.log("remote channel balance is already more than threshold") + return { shouldOpen: false } + } + const pendingChannels = await this.lnd.ListPendingChannels() + if (pendingChannels.pendingOpenChannels.length > 0) { + this.log("pending open channels detected, liquidiity might be on the way") + return { shouldOpen: false } + } + const userState = await this.liquidityProvider.CheckUserState() + if (!userState || userState.max_withdrawable < threshold) { + this.log("balance of", userState?.max_withdrawable || 0, "is lower than channel threshold of", threshold) + return { shouldOpen: false } + } + return { shouldOpen: true, maxSpendable: userState.max_withdrawable } + } + orderChannelIfNeeded = async () => { const existingOrder = await this.storage.liquidityStorage.GetLatestLspOrder() - if (existingOrder) { + if (existingOrder && existingOrder.created_at > new Date(Date.now() - 20 * 60 * 1000)) { + this.log("most recent lsp order is less than 20 minutes old") + return + } + const shouldOpen = await this.shouldOpenChannel() + if (!shouldOpen.shouldOpen) { return } if (this.channelRequested || this.channelRequesting) { @@ -88,7 +119,7 @@ export class LiquidityManager { } this.channelRequesting = true this.log("checking if channel should be requested") - const olympusOk = await this.olympusLSP.openChannelIfReady() + const olympusOk = await this.olympusLSP.requestChannel(shouldOpen.maxSpendable) if (olympusOk) { this.log("requested channel from olympus") this.channelRequested = true @@ -97,7 +128,7 @@ export class LiquidityManager { await this.storage.liquidityStorage.SaveLspOrder({ service_name: 'olympus', invoice: olympusOk.invoice, total_paid: olympusOk.totalSats, order_id: olympusOk.orderId, fees: olympusOk.fees }) return } - const voltageOk = await this.voltageLSP.openChannelIfReady() + const voltageOk = await this.voltageLSP.requestChannel(shouldOpen.maxSpendable) if (voltageOk) { this.log("requested channel from voltage") this.channelRequested = true @@ -107,7 +138,7 @@ export class LiquidityManager { return } - const flashsatsOk = await this.flashsatsLSP.openChannelIfReady() + const flashsatsOk = await this.flashsatsLSP.requestChannel(shouldOpen.maxSpendable) if (flashsatsOk) { this.log("requested channel from flashsats") this.channelRequested = true