diff --git a/lnwallet/channel.go b/lnwallet/channel.go index f3368218..0cc512d5 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -6049,7 +6049,7 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView) ( // Compute the current balances for this commitment. This will take // into account HTLCs to determine the commit weight, which the // initiator must pay the fee for. - ourBalance, _, commitWeight, filteredView, err := lc.computeView( + ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView( view, false, false, ) if err != nil { @@ -6091,7 +6091,42 @@ func (lc *LightningChannel) availableCommitmentBalance(view *htlcView) ( return 0, commitWeight } - ourBalance -= htlcCommitFee + return ourBalance - htlcCommitFee, commitWeight + } + + // If we're not the initiator, we must check whether the remote has + // enough balance to pay for the fee of our HTLC. We'll start by also + // subtracting our counterparty's reserve from their balance. + theirReserve := lnwire.NewMSatFromSatoshis( + lc.channelState.RemoteChanCfg.ChanReserve, + ) + if theirReserve <= theirBalance { + theirBalance -= theirReserve + } else { + theirBalance = 0 + } + + // We'll use the dustlimit and htlcFee to find the largest HTLC value + // that will be considered dust on the commitment. + dustlimit := lnwire.NewMSatFromSatoshis( + lc.channelState.LocalChanCfg.DustLimit, + ) + + // For an extra HTLC fee to be paid on our commitment, the HTLC must be + // large enough to make a non-dust HTLC timeout transaction. + htlcFee := lnwire.NewMSatFromSatoshis( + htlcTimeoutFee(feePerKw), + ) + + // The HTLC output will be manifested on the commitment if it + // is non-dust after paying the HTLC fee. + nonDustHtlcAmt := dustlimit + htlcFee + + // If they cannot pay the fee if we add another non-dust HTLC, we'll + // report our available balance just below the non-dust amount, to + // avoid attempting HTLCs larger than this size. + if theirBalance < htlcCommitFee && ourBalance >= nonDustHtlcAmt { + ourBalance = nonDustHtlcAmt - 1 } return ourBalance, commitWeight diff --git a/lnwallet/channel_test.go b/lnwallet/channel_test.go index 09697629..be739520 100644 --- a/lnwallet/channel_test.go +++ b/lnwallet/channel_test.go @@ -4735,16 +4735,23 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) { } defer cleanUp() - // Alice starts with half the channel capacity. + // Alice and Bob start with half the channel capacity. aliceBalance := lnwire.NewMSatFromSatoshis(5 * btcutil.SatoshiPerBitcoin) + bobBalance := lnwire.NewMSatFromSatoshis(5 * btcutil.SatoshiPerBitcoin) aliceReserve := lnwire.NewMSatFromSatoshis( aliceChannel.channelState.LocalChanCfg.ChanReserve, ) + bobReserve := lnwire.NewMSatFromSatoshis( + bobChannel.channelState.LocalChanCfg.ChanReserve, + ) aliceDustlimit := lnwire.NewMSatFromSatoshis( aliceChannel.channelState.LocalChanCfg.DustLimit, ) + bobDustlimit := lnwire.NewMSatFromSatoshis( + bobChannel.channelState.LocalChanCfg.DustLimit, + ) feeRate := chainfee.SatPerKWeight( aliceChannel.channelState.LocalCommitment.FeePerKw, ) @@ -4759,12 +4766,20 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) { ) // Helper method to check the current reported balance. - checkBalance := func(t *testing.T, expBalanceAlice lnwire.MilliSatoshi) { + checkBalance := func(t *testing.T, expBalanceAlice, + expBalanceBob lnwire.MilliSatoshi) { + t.Helper() - balance := aliceChannel.AvailableBalance() - if balance != expBalanceAlice { - t.Fatalf("Expected balance %v, got %v", expBalanceAlice, - balance) + aliceBalance := aliceChannel.AvailableBalance() + if aliceBalance != expBalanceAlice { + t.Fatalf("Expected alice balance %v, got %v", + expBalanceAlice, aliceBalance) + } + + bobBalance := bobChannel.AvailableBalance() + if bobBalance != expBalanceBob { + t.Fatalf("Expected bob balance %v, got %v", + expBalanceBob, bobBalance) } } @@ -4803,6 +4818,7 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) { htlcIndex++ aliceBalance -= htlcAmt + bobBalance += htlcAmt } // Balance should start out equal to half the channel capacity minus @@ -4811,12 +4827,17 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) { // reflect that this value must be reserved for any payment above the // dust limit. expAliceBalance := aliceBalance - commitFee - aliceReserve - htlcFee - checkBalance(t, expAliceBalance) + + // Bob is not the initiator, so he will have all his balance available, + // since Alice pays for fees. Bob only need to keep his balance above + // the reserve. + expBobBalance := bobBalance - bobReserve + checkBalance(t, expAliceBalance, expBobBalance) // Find the minumim size of a non-dust HTLC. aliceNonDustHtlc := aliceDustlimit + htlcTimeoutFee - // Send a HTLC leaving the remaining balance just enough to have + // Send a HTLC leaving Alice's remaining balance just enough to have // nonDustHtlc left after paying the commit fee and htlc fee. htlcAmt := aliceBalance - (commitFee + aliceReserve + htlcFee + aliceNonDustHtlc) sendHtlc(htlcAmt) @@ -4826,7 +4847,8 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) { // reported will just be nonDustHtlc, since the rest of the balance is // reserved. expAliceBalance = aliceNonDustHtlc - checkBalance(t, expAliceBalance) + expBobBalance = bobBalance - bobReserve + checkBalance(t, expAliceBalance, expBobBalance) // Send an HTLC using all but one msat of the reported balance. htlcAmt = aliceNonDustHtlc - 1 @@ -4834,7 +4856,12 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) { // 1 msat should be left. expAliceBalance = 1 - checkBalance(t, expAliceBalance) + + // Bob should still have all his balance available, since even though + // Alice cannot afford to add a non-dust HTLC, she can afford to add a + // non-dust HTLC from Bob. + expBobBalance = bobBalance - bobReserve + checkBalance(t, expAliceBalance, expBobBalance) // Sendng the last msat. htlcAmt = 1 @@ -4842,7 +4869,30 @@ func TestChanAvailableBalanceNearHtlcFee(t *testing.T) { // No balance left. expAliceBalance = 0 - checkBalance(t, expAliceBalance) + + // We try to always reserve enough for the non-iniitator to be able to + // add an HTLC, hence Bob should still have all his non-reserved + // balance available. + expBobBalance = bobBalance - bobReserve + checkBalance(t, expAliceBalance, expBobBalance) + + // Even though Alice has a reported balance of 0, this is because we + // try to avoid getting into the position where she cannot pay the fee + // for Bob adding another HTLC. This means she actually _has_ some + // balance left, and we now force the channel into this situation by + // sending yet another HTLC. In practice this can also happen if a fee + // update eats into Alice's balance. + htlcAmt = 1 + sendHtlc(htlcAmt) + + // Now Alice balance is so low that she cannot even afford to add a new + // HTLC from Bob to the commitment transaction. Bob's balance should + // reflect this, by only reporting dust amount being available. Alice + // should still report a zero balance. + bobNonDustHtlc := bobDustlimit + htlcTimeoutFee + expBobBalance = bobNonDustHtlc - 1 + expAliceBalance = 0 + checkBalance(t, expAliceBalance, expBobBalance) } // TestSignCommitmentFailNotLockedIn tests that a channel will not attempt to