Skip to content

Commit 9c4aad0

Browse files
authored
Dip into remote initiator reserve only for splices (#2797)
#2761 introduced the ability for the HTLC sender to let a remote initiator dip into its reserve to unblock channels after a large splice. However, we relaxed that condition for all channels, even those that don't use splice. This creates compatibility issues with other implementations that are stricter than what the specification requires, and will force-close in those situations.
1 parent be4ed3c commit 9c4aad0

File tree

2 files changed

+30
-1
lines changed

2 files changed

+30
-1
lines changed

eclair-core/src/main/scala/fr/acinq/eclair/channel/Commitments.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ case class Commitment(fundingTxIndex: Long,
458458
} else if (missingForReceiver < 0.msat) {
459459
if (params.localParams.isInitiator) {
460460
// receiver is not the channel initiator; it is ok if it can't maintain its channel_reserve for now, as long as its balance is increasing, which is the case if it is receiving a payment
461-
} else if (reduced.toLocal > fees && reduced.htlcs.size < 5) {
461+
} else if (reduced.toLocal > fees && reduced.htlcs.size < 5 && fundingTxIndex > 0) {
462462
// Receiver is the channel initiator; we usually don't want to let them dip into their channel reserve, because
463463
// that may give them a commitment transaction where they have nothing at stake, which would create an incentive
464464
// for them to force-close using that commitment after it has been revoked.

eclair-core/src/test/scala/fr/acinq/eclair/channel/states/e/NormalStateSpec.scala

+29
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,35 @@ class NormalStateSpec extends TestKitBaseClass with FixtureAnyFunSuiteLike with
273273
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.remoteChanges.proposed.size == proposedChanges + 1)
274274
}
275275

276+
test("recv CMD_ADD_HTLC (HTLC dips into remote funder channel reserve)", Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight)) { f =>
277+
import f._
278+
val sender = TestProbe()
279+
addHtlc(758_640_000 msat, alice, bob, alice2bob, bob2alice)
280+
crossSign(alice, bob, alice2bob, bob2alice)
281+
assert(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.availableBalanceForSend == 0.msat)
282+
// We increase the feerate to get Alice's balance closer to her channel reserve.
283+
bob.underlyingActor.nodeParams.setFeerates(FeeratesPerKw.single(FeeratePerKw(17_500 sat)))
284+
updateFee(FeeratePerKw(17_500 sat), alice, bob, alice2bob, bob2alice)
285+
286+
// At this point alice has the minimal amount to sustain a channel.
287+
// Alice maintains an extra reserve to accommodate for a one more HTLCs, so the first few HTLCs should be allowed.
288+
bob ! CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, localOrigin(sender.ref))
289+
sender.expectMsgType[RES_SUCCESS[CMD_ADD_HTLC]]
290+
val add = bob2alice.expectMsgType[UpdateAddHtlc]
291+
bob2alice.forward(alice, add)
292+
293+
// But this one will dip alice below her reserve: we must wait for the previous HTLCs to settle before sending any more.
294+
val failedAdd = CMD_ADD_HTLC(sender.ref, 25_000_000 msat, randomBytes32(), CltvExpiry(400144), TestConstants.emptyOnionPacket, None, localOrigin(sender.ref))
295+
bob ! failedAdd
296+
val error = RemoteCannotAffordFeesForNewHtlc(channelId(bob), failedAdd.amount, missing = 340 sat, 20_000 sat, 21_700 sat)
297+
sender.expectMsg(RES_ADD_FAILED(failedAdd, error, Some(bob.stateData.asInstanceOf[DATA_NORMAL].channelUpdate)))
298+
299+
// If Bob had sent this HTLC, Alice would have accepted dipping into her reserve.
300+
val proposedChanges = alice.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.remoteChanges.proposed.size
301+
alice ! add.copy(id = add.id + 1)
302+
awaitCond(alice.stateData.asInstanceOf[DATA_NORMAL].commitments.changes.remoteChanges.proposed.size == proposedChanges + 1)
303+
}
304+
276305
test("recv CMD_ADD_HTLC (insufficient funds w/ pending htlcs and 0 balance)", Tag(ChannelStateTestsTags.NoMaxHtlcValueInFlight)) { f =>
277306
import f._
278307
val sender = TestProbe()

0 commit comments

Comments
 (0)