Skip to content

Commit 5769cfd

Browse files
committed
lnwallet: allow opener to dip into its reserve.
1 parent e8c5e7d commit 5769cfd

File tree

1 file changed

+117
-23
lines changed

1 file changed

+117
-23
lines changed

lnwallet/channel.go

Lines changed: 117 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3727,8 +3727,8 @@ func (lc *LightningChannel) applyCommitFee(
37273727
// or nil otherwise.
37283728
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
37293729
ourLogCounter uint64, whoseCommitChain lntypes.ChannelParty,
3730-
buffer BufferType, predictOurAdd, predictTheirAdd *PaymentDescriptor,
3731-
) error {
3730+
buffer BufferType, allowBelowReserve bool, predictOurAdd,
3731+
predictTheirAdd *PaymentDescriptor) error {
37323732

37333733
// First fetch the initial balance before applying any updates.
37343734
commitChain := lc.commitChains.Local
@@ -3826,30 +3826,82 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
38263826
commitFee := feePerKw.FeeForWeight(commitWeight)
38273827
commitFeeMsat := lnwire.NewMSatFromSatoshis(commitFee)
38283828

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.
38293846
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, "+
38413862
"chan_initiator=%v", ourBalance, ourReserve,
38423863
commitFeeMsat, bufferAmt, lc.channelState.IsInitiator)
38433864

38443865
return fmt.Errorf("%w: our balance below chan reserve",
38453866
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)
38463880

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+
}
38503884

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)
38533905
}
38543906

38553907
// validateUpdates take a set of updates, and validates them against
@@ -4001,9 +4053,17 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
40014053
// We do not enforce the FeeBuffer here because when we reach this
40024054
// point all updates will have to get locked-in so we enforce the
40034055
// 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?
40044064
err := lc.validateCommitmentSanity(
40054065
remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote,
4006-
NoBuffer, nil, nil,
4066+
NoBuffer, true, nil, nil,
40074067
)
40084068
if err != nil {
40094069
return nil, err
@@ -5002,9 +5062,17 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
50025062
// point all updates will have to get locked-in (we already received
50035063
// the UpdateAddHTLC msg from our peer prior to receiving the
50045064
// 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?
50055073
err := lc.validateCommitmentSanity(
50065074
lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local,
5007-
NoBuffer, nil, nil,
5075+
NoBuffer, true, nil, nil,
50085076
)
50095077
if err != nil {
50105078
return err
@@ -5952,11 +6020,15 @@ func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor,
59526020
// must keep on the commitment transactions.
59536021
remoteACKedIndex := lc.commitChains.Local.tail().messageIndices.Remote
59546022

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+
59556027
// First we'll check whether this HTLC can be added to the remote
59566028
// commitment transaction without violation any of the constraints.
59576029
err := lc.validateCommitmentSanity(
59586030
remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote,
5959-
buffer, pd, nil,
6031+
buffer, allowRemoteBelowReserve, pd, nil,
59606032
)
59616033
if err != nil {
59626034
return err
@@ -5969,7 +6041,7 @@ func (lc *LightningChannel) validateAddHtlc(pd *PaymentDescriptor,
59696041
// possible for us to add the HTLC.
59706042
err = lc.validateCommitmentSanity(
59716043
lc.updateLogs.Remote.logIndex, lc.updateLogs.Local.logIndex,
5972-
lntypes.Local, buffer, pd, nil,
6044+
lntypes.Local, buffer, allowRemoteBelowReserve, pd, nil,
59736045
)
59746046
if err != nil {
59756047
return err
@@ -6014,9 +6086,12 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
60146086
// was introduced is to protect against asynchronous sending of htlcs so
60156087
// we use it here. The current lightning protocol does not allow to
60166088
// 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
60176092
err := lc.validateCommitmentSanity(
60186093
lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local,
6019-
NoBuffer, nil, pd,
6094+
NoBuffer, allowLocalDipBelowReserve, nil, pd,
60206095
)
60216096
if err != nil {
60226097
return 0, err
@@ -8381,6 +8456,7 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error {
83818456
}
83828457

83838458
// Ensure that the passed fee rate meets our current requirements.
8459+
// TODO(ziggie): Call `validateCommitmentSanity` instead ?
83848460
if err := lc.validateFeeRate(feePerKw); err != nil {
83858461
return err
83868462
}
@@ -8463,6 +8539,24 @@ func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) er
84638539
EntryType: FeeUpdate,
84648540
}
84658541

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+
84668560
lc.updateLogs.Remote.appendUpdate(pd)
84678561

84688562
return nil

0 commit comments

Comments
 (0)