@@ -3727,8 +3727,8 @@ func (lc *LightningChannel) applyCommitFee(
3727
3727
// or nil otherwise.
3728
3728
func (lc * LightningChannel ) validateCommitmentSanity (theirLogCounter ,
3729
3729
ourLogCounter uint64 , whoseCommitChain lntypes.ChannelParty ,
3730
- buffer BufferType , predictOurAdd , predictTheirAdd * PaymentDescriptor ,
3731
- ) error {
3730
+ buffer BufferType , allowBelowReserve bool , predictOurAdd ,
3731
+ predictTheirAdd * PaymentDescriptor ) error {
3732
3732
3733
3733
// First fetch the initial balance before applying any updates.
3734
3734
commitChain := lc .commitChains .Local
@@ -3826,30 +3826,82 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
3826
3826
commitFee := feePerKw .FeeForWeight (commitWeight )
3827
3827
commitFeeMsat := lnwire .NewMSatFromSatoshis (commitFee )
3828
3828
3829
+ // We only check the reserve requirements if the new balance decreased.
3830
+ // This is sufficient because there are cases where one side can be
3831
+ // below its reserve if we are already below the reserve and nothing
3832
+ // changes on the corresponding balance we allow this contraint
3833
+ // violation. For example the initial channel opening when the peer
3834
+ // has zero balance on his side.
3835
+ localBelowReserve := ourBalance < ourInitialBalance &&
3836
+ ourBalance < ourReserve
3837
+
3838
+ remoteBelowReserve := theirBalance < theirInitialBalance &&
3839
+ theirBalance < theirReserve
3840
+
3841
+ // There are special cases where we allow one side of the channel to
3842
+ // dip below its reserve because there is no disadvantage in doing so
3843
+ // or use cases like splicing need this exception to work properly.
3844
+ // In gerneral we only allow the channel opener who pays the channel
3845
+ // fees to dip below its reserve the counterpart is not allowed to.
3829
3846
switch {
3830
- // TODO(ziggie): Allow the peer dip us below the channel reserve when
3831
- // our local balance would increase during this commitment dance or
3832
- // allow us to dip the peer below its reserve then their balance would
3833
- // increase during this commitment dance. This is needed for splicing
3834
- // when e.g. a new channel (bigger capacity) has a higher required
3835
- // reserve and the peer would need to add an additional htlc to push the
3836
- // missing amount to our side and viceversa.
3837
- // See: https://github.com/lightningnetwork/lnd/issues/8249
3838
- case ourBalance < ourInitialBalance && ourBalance < ourReserve :
3839
- lc .log .Debugf ("Funds below chan reserve: ourBalance=%v, " +
3840
- "ourReserve=%v, commitFee=%v, feeBuffer=%v " +
3847
+ // Disallow the remote dip into its reserve if the remote peer is not
3848
+ // the channel opener.
3849
+ case lc .channelState .IsInitiator && remoteBelowReserve :
3850
+ lc .log .Errorf ("Funds below chan reserve: theirBalance=%v, " +
3851
+ "theirReserve=%v, chan_initiator=%v" , theirBalance ,
3852
+ theirReserve , lc .channelState .IsInitiator )
3853
+
3854
+ return fmt .Errorf ("%w: their balance below chan reserve" ,
3855
+ ErrBelowChanReserve )
3856
+
3857
+ // Disallow the local dip into its reserve if the local peer is not
3858
+ // the channel opener.
3859
+ case ! lc .channelState .IsInitiator && localBelowReserve :
3860
+ lc .log .Errorf ("Funds below chan reserve: ourBalance=%v, " +
3861
+ "ourReserve=%v, commitFee=%v, feeBuffer=%v, " +
3841
3862
"chan_initiator=%v" , ourBalance , ourReserve ,
3842
3863
commitFeeMsat , bufferAmt , lc .channelState .IsInitiator )
3843
3864
3844
3865
return fmt .Errorf ("%w: our balance below chan reserve" ,
3845
3866
ErrBelowChanReserve )
3867
+ }
3868
+
3869
+ // Special cases when we allow the channel opener to dip into its
3870
+ // reserve. This special case is signaled via `allowBelowReserve`
3871
+ // and depends on which update is added to the channel state.
3872
+ switch {
3873
+ case lc .channelState .IsInitiator && localBelowReserve :
3874
+ if ! allowBelowReserve {
3875
+ lc .log .Errorf ("Funds below chan reserve: " +
3876
+ "ourBalance=%v, ourReserve=%v, commitFee=%v, " +
3877
+ "feeBuffer=%v, chan_initiator=%v" , ourBalance ,
3878
+ ourReserve , commitFeeMsat , bufferAmt ,
3879
+ lc .channelState .IsInitiator )
3846
3880
3847
- case theirBalance < theirInitialBalance && theirBalance < theirReserve :
3848
- lc . log . Debugf ( "Funds below chan reserve: theirBalance=%v, " +
3849
- "theirReserve=%v" , theirBalance , theirReserve )
3881
+ return fmt . Errorf ( "%w: their balance below chan " +
3882
+ " reserve" , ErrBelowChanReserve )
3883
+ }
3850
3884
3851
- return fmt .Errorf ("%w: their balance below chan reserve" ,
3852
- ErrBelowChanReserve )
3885
+ lc .log .Debugf ("Funds temporary below chan reserve: " +
3886
+ "ourBalance=%v, ourReserve=%v, commitFee=%v, " +
3887
+ "feeBuffer=%v, chan_initiator=%v" , ourBalance ,
3888
+ ourReserve , commitFeeMsat , bufferAmt ,
3889
+ lc .channelState .IsInitiator )
3890
+
3891
+ case ! lc .channelState .IsInitiator && remoteBelowReserve :
3892
+ if ! allowBelowReserve {
3893
+ lc .log .Errorf ("Funds below chan reserve: " +
3894
+ "theirBalance=%v, theirReserve=%v, " +
3895
+ "chan_initiator=%v" , theirBalance , theirReserve ,
3896
+ lc .channelState .IsInitiator )
3897
+
3898
+ return fmt .Errorf ("%w: their balance below chan " +
3899
+ "reserve" , ErrBelowChanReserve )
3900
+ }
3901
+
3902
+ lc .log .Debugf ("Funds temporary below chan reserve: " +
3903
+ "theirBalance=%v, theirReserve=%v, chan_initiator=%v" ,
3904
+ theirBalance , theirReserve , lc .channelState .IsInitiator )
3853
3905
}
3854
3906
3855
3907
// validateUpdates take a set of updates, and validates them against
@@ -4001,9 +4053,17 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
4001
4053
// We do not enforce the FeeBuffer here because when we reach this
4002
4054
// point all updates will have to get locked-in so we enforce the
4003
4055
// minimum requirement.
4056
+ // We allow the peer who initiated the channel to dip into its reserve
4057
+ // here because the current protocol allows for concurrent state updates
4058
+ // and we already safeguard the reserve check every time we receive or
4059
+ // add a new channel update (feeupdate or htlc update). We basically
4060
+ // ignore the reserve check here because we cannot easily tell which
4061
+ // peer dipped the opener into the reserve.
4062
+ //
4063
+ // TODO(ziggie): Get rid of this commitment sanity check all together?
4004
4064
err := lc .validateCommitmentSanity (
4005
4065
remoteACKedIndex , lc .updateLogs .Local .logIndex , lntypes .Remote ,
4006
- NoBuffer , nil , nil ,
4066
+ NoBuffer , true , nil , nil ,
4007
4067
)
4008
4068
if err != nil {
4009
4069
return nil , err
@@ -5002,9 +5062,17 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
5002
5062
// point all updates will have to get locked-in (we already received
5003
5063
// the UpdateAddHTLC msg from our peer prior to receiving the
5004
5064
// commit-sig).
5065
+ // We allow the peer who initiated the channel to dip into its reserve
5066
+ // here because the current protocol allows for concurrent state updates
5067
+ // and we already safeguard the reserve check every time we receive or
5068
+ // add a new channel update (fee update and htlc update). We basically
5069
+ // ignore the reserve check here because we cannot easily tell which
5070
+ // peer dipped the opener into the reserve.
5071
+ //
5072
+ // TODO(ziggie): Get rid of this commitment sanity check all together?
5005
5073
err := lc .validateCommitmentSanity (
5006
5074
lc .updateLogs .Remote .logIndex , localACKedIndex , lntypes .Local ,
5007
- NoBuffer , nil , nil ,
5075
+ NoBuffer , true , nil , nil ,
5008
5076
)
5009
5077
if err != nil {
5010
5078
return err
@@ -5952,11 +6020,15 @@ func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor,
5952
6020
// must keep on the commitment transactions.
5953
6021
remoteACKedIndex := lc .commitChains .Local .tail ().messageIndices .Remote
5954
6022
6023
+ // We allow the remote dip into its reserve in case the remote is the
6024
+ // channel opener hence pays the fees for the commitment transaction.
6025
+ allowRemoteBelowReserve := ! lc .channelState .IsInitiator
6026
+
5955
6027
// First we'll check whether this HTLC can be added to the remote
5956
6028
// commitment transaction without violation any of the constraints.
5957
6029
err := lc .validateCommitmentSanity (
5958
6030
remoteACKedIndex , lc .updateLogs .Local .logIndex , lntypes .Remote ,
5959
- buffer , pd , nil ,
6031
+ buffer , allowRemoteBelowReserve , pd , nil ,
5960
6032
)
5961
6033
if err != nil {
5962
6034
return err
@@ -5969,7 +6041,7 @@ func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor,
5969
6041
// possible for us to add the HTLC.
5970
6042
err = lc .validateCommitmentSanity (
5971
6043
lc .updateLogs .Remote .logIndex , lc .updateLogs .Local .logIndex ,
5972
- lntypes .Local , buffer , pd , nil ,
6044
+ lntypes .Local , buffer , allowRemoteBelowReserve , pd , nil ,
5973
6045
)
5974
6046
if err != nil {
5975
6047
return err
@@ -6014,9 +6086,12 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
6014
6086
// was introduced is to protect against asynchronous sending of htlcs so
6015
6087
// we use it here. The current lightning protocol does not allow to
6016
6088
// reject ADDs already sent by the peer.
6089
+ // We allow the local peer dip into its reserve in case local is the
6090
+ // channel opener hence pays the fees for the commitment transaction.
6091
+ allowLocalDipBelowReserve := lc .channelState .IsInitiator
6017
6092
err := lc .validateCommitmentSanity (
6018
6093
lc .updateLogs .Remote .logIndex , localACKedIndex , lntypes .Local ,
6019
- NoBuffer , nil , pd ,
6094
+ NoBuffer , allowLocalDipBelowReserve , nil , pd ,
6020
6095
)
6021
6096
if err != nil {
6022
6097
return 0 , err
@@ -8381,6 +8456,7 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error {
8381
8456
}
8382
8457
8383
8458
// Ensure that the passed fee rate meets our current requirements.
8459
+ // TODO(ziggie): Call `validateCommitmentSanity` instead ?
8384
8460
if err := lc .validateFeeRate (feePerKw ); err != nil {
8385
8461
return err
8386
8462
}
@@ -8463,6 +8539,24 @@ func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) er
8463
8539
EntryType : FeeUpdate ,
8464
8540
}
8465
8541
8542
+ // Determine the last update on the local log that has been locked in.
8543
+ localACKedIndex := lc .commitChains .Remote .tail ().messageIndices .Local
8544
+
8545
+ // We make sure the new fee update does not violate any constraints of
8546
+ // the commitment. We do NOT allow the peer to dip into its reserve here
8547
+ // because we evaluate the contraints against all signed local updates.
8548
+ // There is the possiblity that the peer dips below the reserve when
8549
+ // taking all unsigned outgoing HTLCs into account, this however will
8550
+ // be evaluated when signing the next state or receiving the next commit
8551
+ // sig.
8552
+ err := lc .validateCommitmentSanity (
8553
+ lc .updateLogs .Remote .logIndex , localACKedIndex , lntypes .Local ,
8554
+ NoBuffer , false , nil , pd ,
8555
+ )
8556
+ if err != nil {
8557
+ return err
8558
+ }
8559
+
8466
8560
lc .updateLogs .Remote .appendUpdate (pd )
8467
8561
8468
8562
return nil
0 commit comments