@@ -3539,8 +3539,8 @@ func (lc *LightningChannel) applyCommitFee(
3539
3539
// or nil otherwise.
3540
3540
func (lc * LightningChannel ) validateCommitmentSanity (theirLogCounter ,
3541
3541
ourLogCounter uint64 , whoseCommitChain lntypes.ChannelParty ,
3542
- buffer BufferType , predictOurAdd , predictTheirAdd * paymentDescriptor ,
3543
- ) error {
3542
+ buffer BufferType , skipOpenerReserveCHeck bool , predictOurAdd ,
3543
+ predictTheirAdd * paymentDescriptor ) error {
3544
3544
3545
3545
// First fetch the initial balance before applying any updates.
3546
3546
commitChain := lc .commitChains .Local
@@ -3638,30 +3638,82 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
3638
3638
commitFee := feePerKw .FeeForWeight (commitWeight )
3639
3639
commitFeeMsat := lnwire .NewMSatFromSatoshis (commitFee )
3640
3640
3641
+ // We only check the reserve requirements if the new balance decreased.
3642
+ // This is sufficient because there are cases where one side can be
3643
+ // below its reserve if we are already below the reserve and nothing
3644
+ // changes on the corresponding balance we allow this contraint
3645
+ // violation. For example the initial channel opening when the peer
3646
+ // has zero balance on his side.
3647
+ localBelowReserve := ourBalance < ourInitialBalance &&
3648
+ ourBalance < ourReserve
3649
+
3650
+ remoteBelowReserve := theirBalance < theirInitialBalance &&
3651
+ theirBalance < theirReserve
3652
+
3653
+ // There are special cases where we allow one side of the channel to
3654
+ // dip below its reserve because there is no disadvantage in doing so
3655
+ // or use cases like splicing need this exception to work properly.
3656
+ // In gerneral we only allow the channel opener who pays the channel
3657
+ // fees to dip below its reserve the counterpart is not allowed to.
3641
3658
switch {
3642
- // TODO(ziggie): Allow the peer dip us below the channel reserve when
3643
- // our local balance would increase during this commitment dance or
3644
- // allow us to dip the peer below its reserve then their balance would
3645
- // increase during this commitment dance. This is needed for splicing
3646
- // when e.g. a new channel (bigger capacity) has a higher required
3647
- // reserve and the peer would need to add an additional htlc to push the
3648
- // missing amount to our side and viceversa.
3649
- // See: https://github.com/lightningnetwork/lnd/issues/8249
3650
- case ourBalance < ourInitialBalance && ourBalance < ourReserve :
3651
- lc .log .Debugf ("Funds below chan reserve: ourBalance=%v, " +
3652
- "ourReserve=%v, commitFee=%v, feeBuffer=%v " +
3659
+ // Disallow the remote dip into its reserve if the remote peer is not
3660
+ // the channel opener.
3661
+ case lc .channelState .IsInitiator && remoteBelowReserve :
3662
+ lc .log .Errorf ("Funds below chan reserve: theirBalance=%v, " +
3663
+ "theirReserve=%v, chan_initiator=%v" , theirBalance ,
3664
+ theirReserve , lc .channelState .IsInitiator )
3665
+
3666
+ return fmt .Errorf ("%w: their balance below chan reserve" ,
3667
+ ErrBelowChanReserve )
3668
+
3669
+ // Disallow the local dip into its reserve if the local peer is not
3670
+ // the channel opener.
3671
+ case ! lc .channelState .IsInitiator && localBelowReserve :
3672
+ lc .log .Errorf ("Funds below chan reserve: ourBalance=%v, " +
3673
+ "ourReserve=%v, commitFee=%v, feeBuffer=%v, " +
3653
3674
"chan_initiator=%v" , ourBalance , ourReserve ,
3654
3675
commitFeeMsat , bufferAmt , lc .channelState .IsInitiator )
3655
3676
3656
3677
return fmt .Errorf ("%w: our balance below chan reserve" ,
3657
3678
ErrBelowChanReserve )
3679
+ }
3680
+
3681
+ // Special cases when we allow the channel opener to dip into its
3682
+ // reserve. This special case is signaled via `allowBelowReserve`
3683
+ // and depends on which update is added to the channel state.
3684
+ switch {
3685
+ case lc .channelState .IsInitiator && localBelowReserve :
3686
+ if ! skipOpenerReserveCHeck {
3687
+ lc .log .Errorf ("Funds below chan reserve: " +
3688
+ "ourBalance=%v, ourReserve=%v, commitFee=%v, " +
3689
+ "feeBuffer=%v, chan_initiator=%v" , ourBalance ,
3690
+ ourReserve , commitFeeMsat , bufferAmt ,
3691
+ lc .channelState .IsInitiator )
3658
3692
3659
- case theirBalance < theirInitialBalance && theirBalance < theirReserve :
3660
- lc . log . Debugf ( "Funds below chan reserve: theirBalance=%v, " +
3661
- "theirReserve=%v" , theirBalance , theirReserve )
3693
+ return fmt . Errorf ( "%w: their balance below chan " +
3694
+ " reserve" , ErrBelowChanReserve )
3695
+ }
3662
3696
3663
- return fmt .Errorf ("%w: their balance below chan reserve" ,
3664
- ErrBelowChanReserve )
3697
+ lc .log .Debugf ("Funds temporary below chan reserve: " +
3698
+ "ourBalance=%v, ourReserve=%v, commitFee=%v, " +
3699
+ "feeBuffer=%v, chan_initiator=%v" , ourBalance ,
3700
+ ourReserve , commitFeeMsat , bufferAmt ,
3701
+ lc .channelState .IsInitiator )
3702
+
3703
+ case ! lc .channelState .IsInitiator && remoteBelowReserve :
3704
+ if ! skipOpenerReserveCHeck {
3705
+ lc .log .Errorf ("Funds below chan reserve: " +
3706
+ "theirBalance=%v, theirReserve=%v, " +
3707
+ "chan_initiator=%v" , theirBalance , theirReserve ,
3708
+ lc .channelState .IsInitiator )
3709
+
3710
+ return fmt .Errorf ("%w: their balance below chan " +
3711
+ "reserve" , ErrBelowChanReserve )
3712
+ }
3713
+
3714
+ lc .log .Debugf ("Funds temporary below chan reserve: " +
3715
+ "theirBalance=%v, theirReserve=%v, chan_initiator=%v" ,
3716
+ theirBalance , theirReserve , lc .channelState .IsInitiator )
3665
3717
}
3666
3718
3667
3719
// validateUpdates take a set of updates, and validates them against
@@ -3813,9 +3865,17 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
3813
3865
// We do not enforce the FeeBuffer here because when we reach this
3814
3866
// point all updates will have to get locked-in so we enforce the
3815
3867
// minimum requirement.
3868
+ // We allow the peer who initiated the channel to dip into its reserve
3869
+ // here because the current protocol allows for concurrent state updates
3870
+ // and we already safeguard the reserve check every time we receive or
3871
+ // add a new channel update (feeupdate or htlc update). We basically
3872
+ // ignore the reserve check here because we cannot easily tell which
3873
+ // peer dipped the opener into the reserve.
3874
+ //
3875
+ // TODO(ziggie): Get rid of this commitment sanity check all together?
3816
3876
err := lc .validateCommitmentSanity (
3817
3877
remoteACKedIndex , lc .updateLogs .Local .logIndex , lntypes .Remote ,
3818
- NoBuffer , nil , nil ,
3878
+ NoBuffer , true , nil , nil ,
3819
3879
)
3820
3880
if err != nil {
3821
3881
return nil , err
@@ -4842,9 +4902,17 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
4842
4902
// point all updates will have to get locked-in (we already received
4843
4903
// the UpdateAddHTLC msg from our peer prior to receiving the
4844
4904
// commit-sig).
4905
+ // We allow the peer who initiated the channel to dip into its reserve
4906
+ // here because the current protocol allows for concurrent state updates
4907
+ // and we already safeguard the reserve check every time we receive or
4908
+ // add a new channel update (fee update and htlc update). We basically
4909
+ // ignore the reserve check here because we cannot easily tell which
4910
+ // peer dipped the opener into the reserve.
4911
+ //
4912
+ // TODO(ziggie): Get rid of this commitment sanity check all together?
4845
4913
err := lc .validateCommitmentSanity (
4846
4914
lc .updateLogs .Remote .logIndex , localACKedIndex , lntypes .Local ,
4847
- NoBuffer , nil , nil ,
4915
+ NoBuffer , true , nil , nil ,
4848
4916
)
4849
4917
if err != nil {
4850
4918
return err
@@ -5748,9 +5816,16 @@ func (lc *LightningChannel) validateAddHtlc(pd *paymentDescriptor,
5748
5816
5749
5817
// First we'll check whether this HTLC can be added to the remote
5750
5818
// commitment transaction without violation any of the constraints.
5819
+ //
5820
+ // NOTE: The specification would allow us to dip the channel opener into
5821
+ // its reserve when he has not enough balance to pay the fees. However
5822
+ // we don't allow this and always make sure the reserve contraints are
5823
+ // met. This might be change when "splicing" is introduced which has
5824
+ // some edge cases where we might push the remote into its reserve when
5825
+ // he is the opener of the channel hence pays the fees.
5751
5826
err := lc .validateCommitmentSanity (
5752
5827
remoteACKedIndex , lc .updateLogs .Local .logIndex , lntypes .Remote ,
5753
- buffer , pd , nil ,
5828
+ buffer , false , pd , nil ,
5754
5829
)
5755
5830
if err != nil {
5756
5831
return err
@@ -5761,9 +5836,11 @@ func (lc *LightningChannel) validateAddHtlc(pd *paymentDescriptor,
5761
5836
// totally bullet proof, as the remote might be adding updates
5762
5837
// concurrently, but if we fail this check there is for sure not
5763
5838
// possible for us to add the HTLC.
5839
+ // We also make sure the reserve requirments for the channel opener are
5840
+ // met.
5764
5841
err = lc .validateCommitmentSanity (
5765
5842
lc .updateLogs .Remote .logIndex , lc .updateLogs .Local .logIndex ,
5766
- lntypes .Local , buffer , pd , nil ,
5843
+ lntypes .Local , buffer , false , pd , nil ,
5767
5844
)
5768
5845
if err != nil {
5769
5846
return err
@@ -5807,9 +5884,12 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
5807
5884
// was introduced is to protect against asynchronous sending of htlcs so
5808
5885
// we use it here. The current lightning protocol does not allow to
5809
5886
// reject ADDs already sent by the peer.
5887
+ // We allow the local peer dip into its reserve in case local is the
5888
+ // channel opener hence pays the fees for the commitment transaction.
5889
+ allowLocalDipBelowReserve := lc .channelState .IsInitiator
5810
5890
err := lc .validateCommitmentSanity (
5811
5891
lc .updateLogs .Remote .logIndex , localACKedIndex , lntypes .Local ,
5812
- NoBuffer , nil , pd ,
5892
+ NoBuffer , allowLocalDipBelowReserve , nil , pd ,
5813
5893
)
5814
5894
if err != nil {
5815
5895
return 0 , err
@@ -8185,6 +8265,7 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error {
8185
8265
}
8186
8266
8187
8267
// Ensure that the passed fee rate meets our current requirements.
8268
+ // TODO(ziggie): Call `validateCommitmentSanity` instead ?
8188
8269
if err := lc .validateFeeRate (feePerKw ); err != nil {
8189
8270
return err
8190
8271
}
@@ -8269,6 +8350,24 @@ func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) er
8269
8350
EntryType : FeeUpdate ,
8270
8351
}
8271
8352
8353
+ // Determine the last update on the local log that has been locked in.
8354
+ localACKedIndex := lc .commitChains .Remote .tail ().messageIndices .Local
8355
+
8356
+ // We make sure the new fee update does not violate any constraints of
8357
+ // the commitment. We do NOT allow the peer to dip into its reserve here
8358
+ // because we evaluate the contraints against all signed local updates.
8359
+ // There is the possiblity that the peer dips below the reserve when
8360
+ // taking all unsigned outgoing HTLCs into account, this however will
8361
+ // be evaluated when signing the next state or receiving the next commit
8362
+ // sig.
8363
+ err := lc .validateCommitmentSanity (
8364
+ lc .updateLogs .Remote .logIndex , localACKedIndex , lntypes .Local ,
8365
+ NoBuffer , false , nil , pd ,
8366
+ )
8367
+ if err != nil {
8368
+ return err
8369
+ }
8370
+
8272
8371
lc .updateLogs .Remote .appendUpdate (pd )
8273
8372
8274
8373
return nil
0 commit comments