open more channels

This commit is contained in:
boufni95 2024-07-02 19:25:22 +02:00
parent 20d2afa464
commit e7c2511fc8
4 changed files with 67 additions and 64 deletions

View file

@ -290,7 +290,7 @@ export default class {
throw new Error("lnd node is currently out of sync") throw new Error("lnd node is currently out of sync")
} }
await this.Health() 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) { if (useProvider) {
const res = await this.liquidProvider.PayInvoice(invoice) const res = await this.liquidProvider.PayInvoice(invoice)
const providerDst = this.liquidProvider.GetProviderDestination() const providerDst = this.liquidProvider.GetProviderDestination()
@ -314,6 +314,9 @@ export default class {
case Payment_PaymentStatus.SUCCEEDED: case Payment_PaymentStatus.SUCCEEDED:
this.log("invoice payment succeded", Number(payment.valueSat)) this.log("invoice payment succeded", Number(payment.valueSat))
res({ feeSat: Math.ceil(Number(payment.feeMsat) / 1000), valueSat: Number(payment.valueSat), paymentPreimage: payment.paymentPreimage }) 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])
} }
}) })
}) })

View file

@ -61,29 +61,6 @@ class LSP {
this.log = getLogger({ component: serviceName }) 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) => { addPeer = async (pubKey: string, host: string) => {
const { peers } = await this.lnd.ListPeers() const { peers } = await this.lnd.ListPeers()
if (!peers.find(p => p.pubKey === pubKey)) { if (!peers.find(p => p.pubKey === pubKey)) {
@ -98,18 +75,14 @@ export class FlashsatsLSP extends LSP {
super("FlashsatsLSP", settings, lnd, liquidityProvider) super("FlashsatsLSP", settings, lnd, liquidityProvider)
} }
openChannelIfReady = async (): Promise<OrderResponse | null> => { requestChannel = async (maxSpendable: number): Promise<OrderResponse | null> => {
const shouldOpen = await this.shouldOpenChannel()
if (!shouldOpen.shouldOpen) {
return null
}
if (!this.settings.flashsatsServiceUrl) { if (!this.settings.flashsatsServiceUrl) {
this.log("no flashsats service url provided") this.log("no flashsats service url provided")
return null return null
} }
const serviceInfo = await this.getInfo() const serviceInfo = await this.getInfo()
if (+serviceInfo.options.min_initial_client_balance_sat > shouldOpen.maxSpendable) { if (+serviceInfo.options.min_initial_client_balance_sat > maxSpendable) {
this.log("balance of", shouldOpen.maxSpendable, "is lower than service minimum of", serviceInfo.options.min_initial_client_balance_sat) this.log("balance of", maxSpendable, "is lower than service minimum of", serviceInfo.options.min_initial_client_balance_sat)
return null return null
} }
const lndInfo = await this.lnd.GetInfo() 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") this.log("no uri found for this node,uri is required to use flashsats")
return null 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 chanExpiryBlocks = serviceInfo.options.max_channel_expiry_blocks
const order = await this.createOrder({ nodeUri: myUri, lspBalance, clientBalance: "0", chanExpiryBlocks }) const order = await this.createOrder({ nodeUri: myUri, lspBalance, clientBalance: "0", chanExpiryBlocks })
if (order.payment.state !== 'EXPECT_PAYMENT') { 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) this.log("invoice of amount", decoded.numSatoshis, "does not match order total of", order.payment.order_total_sat)
return null return null
} }
if (decoded.numSatoshis > shouldOpen.maxSpendable) { if (decoded.numSatoshis > maxSpendable) {
this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", shouldOpen.maxSpendable) this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", maxSpendable)
return null 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) { if (relativeFee > this.settings.maxRelativeFee) {
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee) this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
return null return null
@ -175,18 +149,14 @@ export class OlympusLSP extends LSP {
super("OlympusLSP", settings, lnd, liquidityProvider) super("OlympusLSP", settings, lnd, liquidityProvider)
} }
openChannelIfReady = async (): Promise<OrderResponse | null> => { requestChannel = async (maxSpendable: number): Promise<OrderResponse | null> => {
const shouldOpen = await this.shouldOpenChannel()
if (!shouldOpen.shouldOpen) {
return null
}
if (!this.settings.olympusServiceUrl) { if (!this.settings.olympusServiceUrl) {
this.log("no olympus service url provided") this.log("no olympus service url provided")
return null return null
} }
const serviceInfo = await this.getInfo() const serviceInfo = await this.getInfo()
if (+serviceInfo.min_initial_client_balance_sat > shouldOpen.maxSpendable) { if (+serviceInfo.min_initial_client_balance_sat > maxSpendable) {
this.log("balance of", shouldOpen.maxSpendable, "is lower than service minimum of", serviceInfo.min_initial_client_balance_sat) this.log("balance of", maxSpendable, "is lower than service minimum of", serviceInfo.min_initial_client_balance_sat)
return null return null
} }
const [servicePub, host] = serviceInfo.uris[0].split('@') const [servicePub, host] = serviceInfo.uris[0].split('@')
@ -194,7 +164,8 @@ export class OlympusLSP extends LSP {
const lndInfo = await this.lnd.GetInfo() const lndInfo = await this.lnd.GetInfo()
const myPub = lndInfo.identityPubkey const myPub = lndInfo.identityPubkey
const refundAddr = await this.lnd.NewAddress(AddressType.WITNESS_PUBKEY_HASH) 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 chanExpiryBlocks = serviceInfo.max_channel_expiry_blocks
const order = await this.createOrder({ pubKey: myPub, refundAddr: refundAddr.address, lspBalance, clientBalance: "0", chanExpiryBlocks }) const order = await this.createOrder({ pubKey: myPub, refundAddr: refundAddr.address, lspBalance, clientBalance: "0", chanExpiryBlocks })
if (order.payment.bolt11.state !== 'EXPECT_PAYMENT') { 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) this.log("invoice of amount", decoded.numSatoshis, "does not match order total of", order.payment.bolt11.order_total_sat)
return null return null
} }
if (decoded.numSatoshis > shouldOpen.maxSpendable) { if (decoded.numSatoshis > maxSpendable) {
this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", shouldOpen.maxSpendable) this.log("invoice of amount", decoded.numSatoshis, "exceeds user balance of", maxSpendable)
return null 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) { if (relativeFee > this.settings.maxRelativeFee) {
this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee) this.log("invoice relative fee of", relativeFee, "exceeds max relative fee of", this.settings.maxRelativeFee)
return null return null
@ -277,12 +248,7 @@ export class VoltageLSP extends LSP {
return json return json
} }
openChannelIfReady = async (): Promise<OrderResponse | null> => { requestChannel = async (maxSpendable: number): Promise<OrderResponse | null> => {
const shouldOpen = await this.shouldOpenChannel()
if (!shouldOpen.shouldOpen) {
return null
}
if (!this.settings.voltageServiceUrl) { if (!this.settings.voltageServiceUrl) {
this.log("no voltage service url provided") this.log("no voltage service url provided")
return null return null
@ -290,10 +256,11 @@ export class VoltageLSP extends LSP {
const lndInfo = await this.lnd.GetInfo() const lndInfo = await this.lnd.GetInfo()
const myPub = lndInfo.identityPubkey 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 fee = await this.getFees(amtMsats, myPub)
const feeSats = fee.fee_amount_msat / 1000 const feeSats = fee.fee_amount_msat / 1000
const relativeFee = feeSats / this.settings.channelThreshold const relativeFee = feeSats / (amtSats * 1.1)
if (relativeFee > this.settings.maxRelativeFee) { if (relativeFee > this.settings.maxRelativeFee) {
this.log("relative fee of", relativeFee, "exceeds max relative fee of", 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}`) 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) const proposalRes = await this.proposal(invoice.payRequest, fee.id)
this.log("proposal res", proposalRes, fee.id) this.log("proposal res", proposalRes, fee.id)
const decoded = await this.lnd.DecodeInvoice(proposalRes.jit_bolt11) const decoded = await this.lnd.DecodeInvoice(proposalRes.jit_bolt11)
if (decoded.numSatoshis !== this.settings.channelThreshold + feeSats) { if (decoded.numSatoshis !== amtSats + feeSats) {
this.log("invoice of amount", decoded.numSatoshis, "does not match expected amount of", this.settings.channelThreshold + 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 return null
} }
const res = await this.liquidityProvider.PayInvoice(proposalRes.jit_bolt11) const res = await this.liquidityProvider.PayInvoice(proposalRes.jit_bolt11)
const fees = feeSats + res.network_fee + res.service_fee const fees = feeSats + res.network_fee + res.service_fee
this.log("paid", res.amount_paid, "to open channel, and a fee of", fees) this.log("paid", res.amount_paid, "to open channel, and a fee of", fees)

View file

@ -4,7 +4,7 @@ import { SendPaymentRequest } from "../../../proto/lnd/router";
export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number): SendPaymentRequest => ({ export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number): SendPaymentRequest => ({
amt: BigInt(amount), amt: BigInt(amount),
feeLimitSat: BigInt(feeLimit), feeLimitSat: BigInt(feeLimit),
noInflightUpdates: true, noInflightUpdates: false,
paymentRequest: invoice, paymentRequest: invoice,
maxParts: 3, maxParts: 3,
timeoutSeconds: 50, timeoutSeconds: 50,
@ -25,6 +25,5 @@ export const PayInvoiceReq = (invoice: string, amount: number, feeLimit: number)
paymentHash: Buffer.alloc(0), paymentHash: Buffer.alloc(0),
routeHints: [], routeHints: [],
timePref: 0, timePref: 0,
outgoingChanId: '0' outgoingChanId: '0'
}) })

View file

@ -71,6 +71,7 @@ export class LiquidityManager {
this.log("channel does not have enough balance for invoice,suggesting provider") this.log("channel does not have enough balance for invoice,suggesting provider")
return 'provider' return 'provider'
} }
afterInInvoicePaid = async () => { afterInInvoicePaid = async () => {
try { try {
await this.orderChannelIfNeeded() await this.orderChannelIfNeeded()
@ -78,9 +79,39 @@ export class LiquidityManager {
this.log("error ordering channel", e) 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 () => { orderChannelIfNeeded = async () => {
const existingOrder = await this.storage.liquidityStorage.GetLatestLspOrder() 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 return
} }
if (this.channelRequested || this.channelRequesting) { if (this.channelRequested || this.channelRequesting) {
@ -88,7 +119,7 @@ export class LiquidityManager {
} }
this.channelRequesting = true this.channelRequesting = true
this.log("checking if channel should be requested") this.log("checking if channel should be requested")
const olympusOk = await this.olympusLSP.openChannelIfReady() const olympusOk = await this.olympusLSP.requestChannel(shouldOpen.maxSpendable)
if (olympusOk) { if (olympusOk) {
this.log("requested channel from olympus") this.log("requested channel from olympus")
this.channelRequested = true 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 }) await this.storage.liquidityStorage.SaveLspOrder({ service_name: 'olympus', invoice: olympusOk.invoice, total_paid: olympusOk.totalSats, order_id: olympusOk.orderId, fees: olympusOk.fees })
return return
} }
const voltageOk = await this.voltageLSP.openChannelIfReady() const voltageOk = await this.voltageLSP.requestChannel(shouldOpen.maxSpendable)
if (voltageOk) { if (voltageOk) {
this.log("requested channel from voltage") this.log("requested channel from voltage")
this.channelRequested = true this.channelRequested = true
@ -107,7 +138,7 @@ export class LiquidityManager {
return return
} }
const flashsatsOk = await this.flashsatsLSP.openChannelIfReady() const flashsatsOk = await this.flashsatsLSP.requestChannel(shouldOpen.maxSpendable)
if (flashsatsOk) { if (flashsatsOk) {
this.log("requested channel from flashsats") this.log("requested channel from flashsats")
this.channelRequested = true this.channelRequested = true