mirror of
https://github.com/ACINQ/eclair.git
synced 2025-03-13 11:35:47 +01:00
Use remote funding when setting max_htlc_value_in_flight
When using dual-funding, both peers may contribute to the funding amount and it usually cannot be known ahead of time how much the remote peer will contribute, which usually leads to underestimating the channel capacity and thus using a lower `max_htlc_value_in_flight` than what should be used. However, when we use liquidity ads, we will: - contribute to the funding transaction if we're not the opener - cancel the funding attempt if we're the opener and our peers doesn't contribute at least the amount we requested So in that case, we can use a better estimate of the channel capacity when computing our `max_htlc_value_in_flight`.
This commit is contained in:
parent
8827a04349
commit
65740907f1
3 changed files with 30 additions and 9 deletions
|
@ -22,7 +22,7 @@ import akka.actor.typed.scaladsl.adapter.TypedActorRefOps
|
|||
import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
|
||||
import akka.actor.typed.{ActorRef, Behavior}
|
||||
import fr.acinq.bitcoin.scalacompat.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin.scalacompat.{BtcDouble, ByteVector32, Satoshi, Script}
|
||||
import fr.acinq.bitcoin.scalacompat.{BtcDouble, ByteVector32, Satoshi, SatoshiLong, Script}
|
||||
import fr.acinq.eclair.Features.Wumbo
|
||||
import fr.acinq.eclair.blockchain.OnchainPubkeyCache
|
||||
import fr.acinq.eclair.channel._
|
||||
|
@ -84,8 +84,8 @@ object OpenChannelInterceptor {
|
|||
}
|
||||
}
|
||||
|
||||
def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], walletStaticPaymentBasepoint_opt: Option[PublicKey], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi, unlimitedMaxHtlcValueInFlight: Boolean): LocalParams = {
|
||||
val maxHtlcValueInFlightMsat = if (unlimitedMaxHtlcValueInFlight) {
|
||||
private def computeMaxHtlcValueInFlight(nodeParams: NodeParams, fundingAmount: Satoshi, unlimitedMaxHtlcValueInFlight: Boolean): MilliSatoshi = {
|
||||
if (unlimitedMaxHtlcValueInFlight) {
|
||||
// We don't want to impose limits on the amount in flight, typically to allow fully emptying the channel.
|
||||
21e6.btc.toMilliSatoshi
|
||||
} else {
|
||||
|
@ -94,11 +94,14 @@ object OpenChannelInterceptor {
|
|||
// base it on the amount that we're contributing instead of the total funding amount.
|
||||
nodeParams.channelConf.maxHtlcValueInFlightMsat.min(fundingAmount * nodeParams.channelConf.maxHtlcValueInFlightPercent / 100)
|
||||
}
|
||||
}
|
||||
|
||||
def makeChannelParams(nodeParams: NodeParams, initFeatures: Features[InitFeature], upfrontShutdownScript_opt: Option[ByteVector], walletStaticPaymentBasepoint_opt: Option[PublicKey], isChannelOpener: Boolean, paysCommitTxFees: Boolean, dualFunded: Boolean, fundingAmount: Satoshi, unlimitedMaxHtlcValueInFlight: Boolean): LocalParams = {
|
||||
LocalParams(
|
||||
nodeParams.nodeId,
|
||||
nodeParams.channelKeyManager.newFundingKeyPath(isChannelOpener), // we make sure that opener and non-opener key paths end differently
|
||||
dustLimit = nodeParams.channelConf.dustLimit,
|
||||
maxHtlcValueInFlightMsat = maxHtlcValueInFlightMsat,
|
||||
maxHtlcValueInFlightMsat = computeMaxHtlcValueInFlight(nodeParams, fundingAmount, unlimitedMaxHtlcValueInFlight),
|
||||
initialRequestedChannelReserve_opt = if (dualFunded) None else Some((fundingAmount * nodeParams.channelConf.reserveToFundingRatio).max(nodeParams.channelConf.dustLimit)), // BOLT #2: make sure that our reserve is above our dust limit
|
||||
htlcMinimum = nodeParams.channelConf.htlcMinimum,
|
||||
toSelfDelay = nodeParams.channelConf.toRemoteDelay, // we choose their delay
|
||||
|
@ -142,7 +145,9 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
|
|||
val channelType = request.open.channelType_opt.getOrElse(ChannelTypes.defaultFromFeatures(request.localFeatures, request.remoteFeatures, channelFlags.announceChannel))
|
||||
val dualFunded = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.DualFunding)
|
||||
val upfrontShutdownScript = Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.UpfrontShutdownScript)
|
||||
val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, channelType, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, request.open.fundingAmount, request.open.disableMaxHtlcValueInFlight)
|
||||
// If we're purchasing liquidity, we expect our peer to contribute at least the amount we're purchasing, otherwise we'll cancel the funding attempt.
|
||||
val expectedFundingAmount = request.open.fundingAmount + request.open.requestFunding_opt.map(_.requestedAmount).getOrElse(0 sat)
|
||||
val localParams = createLocalParams(nodeParams, request.localFeatures, upfrontShutdownScript, channelType, isChannelOpener = true, paysCommitTxFees = true, dualFunded = dualFunded, expectedFundingAmount, request.open.disableMaxHtlcValueInFlight)
|
||||
peer ! Peer.SpawnChannelInitiator(request.replyTo, request.open, ChannelConfig.standard, channelType, localParams)
|
||||
waitForRequest()
|
||||
}
|
||||
|
@ -210,7 +215,10 @@ private class OpenChannelInterceptor(peer: ActorRef[Any],
|
|||
request.open.fold(_ => None, _.requestFunding_opt) match {
|
||||
case Some(requestFunding) if Features.canUseFeature(request.localFeatures, request.remoteFeatures, Features.OnTheFlyFunding) && localParams.paysCommitTxFees =>
|
||||
val addFunding = LiquidityAds.AddFunding(requestFunding.requestedAmount, nodeParams.willFundRates_opt)
|
||||
val accept = SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, Some(addFunding), localParams, request.peerConnection.toClassic)
|
||||
// Now that we know how much we'll contribute to the funding transaction, we update the maxHtlcValueInFlight.
|
||||
val maxHtlcValueInFlight = localParams.maxHtlcValueInFlightMsat.max(computeMaxHtlcValueInFlight(nodeParams, request.fundingAmount + addFunding.fundingAmount, unlimitedMaxHtlcValueInFlight = false))
|
||||
val localParams1 = localParams.copy(maxHtlcValueInFlightMsat = maxHtlcValueInFlight)
|
||||
val accept = SpawnChannelNonInitiator(request.open, ChannelConfig.standard, channelType, Some(addFunding), localParams1, request.peerConnection.toClassic)
|
||||
checkNoExistingChannel(request, accept)
|
||||
case _ =>
|
||||
// We don't honor liquidity ads for new channels: node operators should use plugin for that.
|
||||
|
|
|
@ -31,7 +31,7 @@ import fr.acinq.eclair.channel._
|
|||
import fr.acinq.eclair.channel.fsm.Channel
|
||||
import fr.acinq.eclair.channel.states.ChannelStateTestsTags
|
||||
import fr.acinq.eclair.io.OpenChannelInterceptor.{DefaultParams, OpenChannelInitiator, OpenChannelNonInitiator}
|
||||
import fr.acinq.eclair.io.Peer.{OpenChannelResponse, OutgoingMessage, SpawnChannelNonInitiator}
|
||||
import fr.acinq.eclair.io.Peer.{OpenChannelResponse, OutgoingMessage, SpawnChannelInitiator, SpawnChannelNonInitiator}
|
||||
import fr.acinq.eclair.io.PeerSpec.{createOpenChannelMessage, createOpenDualFundedChannelMessage}
|
||||
import fr.acinq.eclair.io.PendingChannelsRateLimiter.AddOrRejectChannel
|
||||
import fr.acinq.eclair.transactions.Transactions.{ClosingTx, InputInfo}
|
||||
|
@ -146,10 +146,23 @@ class OpenChannelInterceptorSpec extends ScalaTestWithActorTestKit(ConfigFactory
|
|||
val result = peer.expectMessageType[SpawnChannelNonInitiator]
|
||||
assert(!result.localParams.isChannelOpener)
|
||||
assert(result.localParams.paysCommitTxFees)
|
||||
assert(result.localParams.maxHtlcValueInFlightMsat == 500_000_000.msat)
|
||||
assert(result.addFunding_opt.map(_.fundingAmount).contains(250_000 sat))
|
||||
assert(result.addFunding_opt.flatMap(_.rates_opt).contains(TestConstants.defaultLiquidityRates))
|
||||
}
|
||||
|
||||
test("expect remote funding contribution in max_htlc_value_in_flight") { f =>
|
||||
import f._
|
||||
|
||||
val probe = TestProbe[Any]()
|
||||
val requestFunding = LiquidityAds.RequestFunding(150_000 sat, LiquidityAds.FundingRate(0 sat, 200_000 sat, 400, 100, 0 sat, 0 sat), LiquidityAds.PaymentDetails.FromChannelBalance)
|
||||
val openChannelInitiator = OpenChannelInitiator(probe.ref, remoteNodeId, Peer.OpenChannel(remoteNodeId, 300_000 sat, None, None, None, None, Some(requestFunding), None, None), defaultFeatures, defaultFeatures)
|
||||
openChannelInterceptor ! openChannelInitiator
|
||||
val result = peer.expectMessageType[SpawnChannelInitiator]
|
||||
assert(result.cmd == openChannelInitiator.open)
|
||||
assert(result.localParams.maxHtlcValueInFlightMsat == 450_000_000.msat)
|
||||
}
|
||||
|
||||
test("continue channel open if no interceptor plugin registered and pending channels rate limiter accepts it") { f =>
|
||||
import f._
|
||||
|
||||
|
|
|
@ -803,11 +803,11 @@ object PeerSpec {
|
|||
}
|
||||
|
||||
def createOpenChannelMessage(openTlv: TlvStream[OpenChannelTlv] = TlvStream.empty): protocol.OpenChannel = {
|
||||
protocol.OpenChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), 25000 sat, 0 msat, 483 sat, UInt64(100), 1000 sat, 1 msat, TestConstants.feeratePerKw, CltvExpiryDelta(144), 10, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false), openTlv)
|
||||
protocol.OpenChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), 250_000 sat, 0 msat, 483 sat, UInt64(100), 1000 sat, 1 msat, TestConstants.feeratePerKw, CltvExpiryDelta(144), 10, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false), openTlv)
|
||||
}
|
||||
|
||||
def createOpenDualFundedChannelMessage(): protocol.OpenDualFundedChannel = {
|
||||
protocol.OpenDualFundedChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), TestConstants.feeratePerKw, TestConstants.anchorOutputsFeeratePerKw, 25000 sat, 483 sat, UInt64(100), 1 msat, CltvExpiryDelta(144), 10, 0, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false))
|
||||
protocol.OpenDualFundedChannel(Block.RegtestGenesisBlock.hash, randomBytes32(), TestConstants.feeratePerKw, TestConstants.anchorOutputsFeeratePerKw, 250_000 sat, 483 sat, UInt64(100), 1 msat, CltvExpiryDelta(144), 10, 0, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, randomKey().publicKey, ChannelFlags(announceChannel = false))
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue