Skip to content

Commit 969560e

Browse files
committed
Abort splice attempts when balance is too low
If our balance, combined with our fee credit, doesn't allow us to pay the liquidity fees with the payment type we chose, we immediately abort the splice.
1 parent dd5f736 commit 969560e

File tree

5 files changed

+87
-45
lines changed

5 files changed

+87
-45
lines changed

src/commonMain/kotlin/fr/acinq/lightning/channel/ChannelCommand.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,15 @@ sealed class ChannelCommand {
8787
data class UpdateFee(val feerate: FeeratePerKw, val commit: Boolean = false) : Commitment(), ForbiddenDuringSplice, ForbiddenDuringQuiescence
8888
data object CheckHtlcTimeout : Commitment()
8989
sealed class Splice : Commitment() {
90-
data class Request(val replyTo: CompletableDeferred<Response>, val spliceIn: SpliceIn?, val spliceOut: SpliceOut?, val requestRemoteFunding: LiquidityAds.RequestFunding?, val feerate: FeeratePerKw, val origins: List<Origin>) : Splice() {
90+
data class Request(
91+
val replyTo: CompletableDeferred<Response>,
92+
val spliceIn: SpliceIn?,
93+
val spliceOut: SpliceOut?,
94+
val requestRemoteFunding: LiquidityAds.RequestFunding?,
95+
val currentFeeCredit: MilliSatoshi,
96+
val feerate: FeeratePerKw,
97+
val origins: List<Origin>
98+
) : Splice() {
9199
val pushAmount: MilliSatoshi = spliceIn?.pushAmount ?: 0.msat
92100
val spliceOutputs: List<TxOut> = spliceOut?.let { listOf(TxOut(it.amount, it.scriptPubKey)) } ?: emptyList()
93101

@@ -110,7 +118,7 @@ sealed class ChannelCommand {
110118
) : Response()
111119

112120
sealed class Failure : Response() {
113-
data object InsufficientFunds : Failure()
121+
data class InsufficientFunds(val balanceAfterFees: MilliSatoshi, val liquidityFees: MilliSatoshi, val currentFeeCredit: MilliSatoshi) : Failure()
114122
data object InvalidSpliceOutPubKeyScript : Failure()
115123
data object SpliceAlreadyInProgress : Failure()
116124
data object ConcurrentRemoteSplice : Failure()

src/commonMain/kotlin/fr/acinq/lightning/channel/states/Normal.kt

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -389,22 +389,29 @@ data class Normal(
389389
paysCommitTxFees -> Transactions.commitTxFee(commitments.params.remoteParams.dustLimit, parentCommitment.remoteCommit.spec)
390390
else -> 0.sat
391391
}
392-
if (parentCommitment.localCommit.spec.toLocal + fundingContribution.toMilliSatoshi() < parentCommitment.localChannelReserve(commitments.params).max(commitTxFees)) {
393-
logger.warning { "cannot do splice: insufficient funds" }
394-
spliceStatus.command.replyTo.complete(ChannelCommand.Commitment.Splice.Response.Failure.InsufficientFunds)
395-
val actions = buildList {
396-
add(ChannelAction.Message.Send(Warning(channelId, InvalidSpliceRequest(channelId).message)))
397-
add(ChannelAction.Disconnect)
392+
val liquidityFees = when (val requestRemoteFunding = spliceStatus.command.requestRemoteFunding) {
393+
null -> 0.msat
394+
else -> when (requestRemoteFunding.paymentDetails.paymentType) {
395+
LiquidityAds.PaymentType.FromChannelBalance -> requestRemoteFunding.fees(spliceStatus.command.feerate, isChannelCreation = false).total.toMilliSatoshi()
396+
LiquidityAds.PaymentType.FromChannelBalanceForFutureHtlc -> requestRemoteFunding.fees(spliceStatus.command.feerate, isChannelCreation = false).total.toMilliSatoshi()
397+
// Liquidity fees will be deducted from future HTLCs instead of being paid immediately.
398+
LiquidityAds.PaymentType.FromFutureHtlc -> 0.msat
399+
LiquidityAds.PaymentType.FromFutureHtlcWithPreimage -> 0.msat
400+
is LiquidityAds.PaymentType.Unknown -> 0.msat
398401
}
399-
Pair(this@Normal.copy(spliceStatus = SpliceStatus.None), actions)
402+
}
403+
val liquidityFeesOwed = (liquidityFees - spliceStatus.command.currentFeeCredit).max(0.msat)
404+
val balanceAfterFees = parentCommitment.localCommit.spec.toLocal + fundingContribution.toMilliSatoshi() - liquidityFeesOwed
405+
if (balanceAfterFees < parentCommitment.localChannelReserve(commitments.params).max(commitTxFees)) {
406+
logger.warning { "cannot do splice: insufficient funds (balanceAfterFees=$balanceAfterFees, liquidityFees=$liquidityFees, feeCredit=${spliceStatus.command.currentFeeCredit})" }
407+
spliceStatus.command.replyTo.complete(ChannelCommand.Commitment.Splice.Response.Failure.InsufficientFunds(balanceAfterFees, liquidityFees, spliceStatus.command.currentFeeCredit))
408+
val action = listOf(ChannelAction.Message.Send(TxAbort(channelId, InvalidSpliceRequest(channelId).message)))
409+
Pair(this@Normal.copy(spliceStatus = SpliceStatus.Aborted), action)
400410
} else if (spliceStatus.command.spliceOut?.scriptPubKey?.let { Helpers.Closing.isValidFinalScriptPubkey(it, allowAnySegwit = true) } == false) {
401411
logger.warning { "cannot do splice: invalid splice-out script" }
402412
spliceStatus.command.replyTo.complete(ChannelCommand.Commitment.Splice.Response.Failure.InvalidSpliceOutPubKeyScript)
403-
val actions = buildList {
404-
add(ChannelAction.Message.Send(Warning(channelId, InvalidSpliceRequest(channelId).message)))
405-
add(ChannelAction.Disconnect)
406-
}
407-
Pair(this@Normal.copy(spliceStatus = SpliceStatus.None), actions)
413+
val action = listOf(ChannelAction.Message.Send(TxAbort(channelId, InvalidSpliceRequest(channelId).message)))
414+
Pair(this@Normal.copy(spliceStatus = SpliceStatus.Aborted), action)
408415
} else {
409416
val spliceInit = SpliceInit(
410417
channelId,

src/commonMain/kotlin/fr/acinq/lightning/io/Peer.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -643,6 +643,7 @@ class Peer(
643643
spliceIn = null,
644644
spliceOut = ChannelCommand.Commitment.Splice.Request.SpliceOut(amount, scriptPubKey),
645645
requestRemoteFunding = null,
646+
currentFeeCredit = feeCreditFlow.value,
646647
feerate = feerate,
647648
origins = listOf(),
648649
)
@@ -662,6 +663,7 @@ class Peer(
662663
spliceIn = null,
663664
spliceOut = null,
664665
requestRemoteFunding = null,
666+
currentFeeCredit = feeCreditFlow.value,
665667
feerate = feerate,
666668
origins = listOf(),
667669
)
@@ -680,6 +682,7 @@ class Peer(
680682
spliceIn = null,
681683
spliceOut = null,
682684
requestRemoteFunding = LiquidityAds.RequestFunding(amount, fundingRate, LiquidityAds.PaymentDetails.FromChannelBalance),
685+
currentFeeCredit = feeCreditFlow.value,
683686
feerate = feerate,
684687
origins = listOf(),
685688
)
@@ -1285,6 +1288,7 @@ class Peer(
12851288
spliceIn = ChannelCommand.Commitment.Splice.Request.SpliceIn(cmd.walletInputs),
12861289
spliceOut = null,
12871290
requestRemoteFunding = null,
1291+
currentFeeCredit = feeCreditFlow.value,
12881292
feerate = feerate,
12891293
origins = listOf(Origin.OnChainWallet(cmd.walletInputs.map { it.outPoint }.toSet(), cmd.totalAmount.toMilliSatoshi(), ChannelManagementFees(fee, 0.sat)))
12901294
)
@@ -1425,6 +1429,7 @@ class Peer(
14251429
spliceIn = null,
14261430
spliceOut = null,
14271431
requestRemoteFunding = LiquidityAds.RequestFunding(cmd.requestedAmount, cmd.fundingRate, paymentDetails),
1432+
currentFeeCredit = currentFeeCredit,
14281433
feerate = targetFeerate,
14291434
origins = listOf(Origin.OffChainPayment(cmd.preimage, cmd.paymentAmount, totalFees))
14301435
)

src/commonTest/kotlin/fr/acinq/lightning/channel/states/QuiescenceTestsCommon.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,7 @@ class QuiescenceTestsCommon : LightningTestSuite() {
520520
spliceOut = spliceOut?.let { ChannelCommand.Commitment.Splice.Request.SpliceOut(it, Script.write(Script.pay2wpkh(Lightning.randomKey().publicKey())).byteVector()) },
521521
feerate = FeeratePerKw(253.sat),
522522
requestRemoteFunding = null,
523+
currentFeeCredit = 0.msat,
523524
origins = listOf(),
524525
)
525526
}

0 commit comments

Comments
 (0)