Skip to content

Commit

Permalink
Reject splices that cannot pay fees
Browse files Browse the repository at this point in the history
If paying the liquidity fees, or pushing sats from one side to the other
would make the balance of either side go below 0 msat, we can abort
the splice right at the beginning of the interactive-tx session.
  • Loading branch information
t-bast committed Dec 12, 2023
1 parent fa61510 commit b1724ae
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -367,9 +367,11 @@ object InteractiveTxBuilder {
Behaviors.withMdc(Logs.mdc(remoteNodeId_opt = Some(channelParams.remoteParams.nodeId), channelId_opt = Some(fundingParams.channelId))) {
Behaviors.receiveMessagePartial {
case Start(replyTo) =>
// The initiator of the interactive-tx is the liquidity buyer (if liquidity ads is used).
val liquidityFee = liquidityPurchased_opt.map(l => if (fundingParams.isInitiator) l.fees.total else -l.fees.total).getOrElse(0 sat)
// Note that pending HTLCs are ignored: splices only affect the main outputs.
val nextLocalBalance = purpose.previousLocalBalance + fundingParams.localContribution
val nextRemoteBalance = purpose.previousRemoteBalance + fundingParams.remoteContribution
val nextLocalBalance = purpose.previousLocalBalance + fundingParams.localContribution - localPushAmount + remotePushAmount - liquidityFee
val nextRemoteBalance = purpose.previousRemoteBalance + fundingParams.remoteContribution - remotePushAmount + localPushAmount + liquidityFee
if (fundingParams.fundingAmount < fundingParams.dustLimit) {
replyTo ! LocalFailure(FundingAmountTooLow(channelParams.channelId, fundingParams.fundingAmount, fundingParams.dustLimit))
Behaviors.stopped
Expand Down Expand Up @@ -756,12 +758,11 @@ private class InteractiveTxBuilder(replyTo: ActorRef[InteractiveTxBuilder.Respon
val fundingTx = completeTx.buildUnsignedTx()
val fundingOutputIndex = fundingTx.txOut.indexWhere(_.publicKeyScript == fundingPubkeyScript)
// The initiator of the interactive-tx is the liquidity buyer (if liquidity ads is used).
val localLiquidityFee = liquidityPurchased_opt.collect { case lease if fundingParams.isInitiator => lease.fees.total }.getOrElse(0 sat)
val remoteLiquidityFee = liquidityPurchased_opt.collect { case lease if !fundingParams.isInitiator => lease.fees.total }.getOrElse(0 sat)
val liquidityFee = liquidityPurchased_opt.map(l => if (fundingParams.isInitiator) l.fees.total else -l.fees.total).getOrElse(0 sat)
Funding.makeCommitTxs(keyManager, channelParams,
fundingAmount = fundingParams.fundingAmount,
toLocal = completeTx.sharedOutput.localAmount - localPushAmount + remotePushAmount - localLiquidityFee + remoteLiquidityFee,
toRemote = completeTx.sharedOutput.remoteAmount - remotePushAmount + localPushAmount - remoteLiquidityFee + localLiquidityFee,
toLocal = completeTx.sharedOutput.localAmount - localPushAmount + remotePushAmount - liquidityFee,
toRemote = completeTx.sharedOutput.remoteAmount - remotePushAmount + localPushAmount + liquidityFee,
localHtlcs = purpose.localHtlcs,
purpose.commitTxFeerate,
fundingTxIndex = purpose.fundingTxIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,25 @@ class NormalSplicesStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLik
alice2bob.forward(bob)
}

test("recv CMD_SPLICE (splice-in, liquidity ads, cannot pay fees)", Tag(ChannelStateTestsTags.Quiescence), Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight)) { f =>
import f._

val sender = TestProbe()
// Alice requests a lot of funding, but she doesn't have enough balance to pay the corresponding fee.
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.latest.localCommit.spec.toLocal == 800_000_000.msat)
val fundingRequest = LiquidityAds.RequestRemoteFunding(5_000_000 sat, 250_000 sat, alice.nodeParams.currentBlockHeight, TestConstants.defaultLeaseDuration)
val cmd = CMD_SPLICE(sender.ref, None, Some(SpliceOut(750_000 sat, defaultSpliceOutScriptPubKey)), Some(fundingRequest))
alice ! cmd

exchangeStfu(alice, bob, alice2bob, bob2alice)
assert(alice2bob.expectMsgType[SpliceInit].requestFunds_opt.nonEmpty)
alice2bob.forward(bob)
assert(bob2alice.expectMsgType[SpliceAck].willFund_opt.nonEmpty)
bob2alice.forward(alice)
assert(alice2bob.expectMsgType[TxAbort].toAscii.contains("invalid balances"))
assert(bob2alice.expectMsgType[TxAbort].toAscii.contains("invalid balances"))
}

test("recv CMD_SPLICE (splice-in, local and remote commit index mismatch)", Tag(ChannelStateTestsTags.Quiescence)) { f =>
import f._

Expand Down

0 comments on commit b1724ae

Please sign in to comment.