Skip to content

Commit 92c5ac9

Browse files
committed
lnwallet: allow opener to dip into its reserve.
1 parent 750770e commit 92c5ac9

File tree

1 file changed

+122
-23
lines changed

1 file changed

+122
-23
lines changed

lnwallet/channel.go

+122-23
Original file line numberDiff line numberDiff line change
@@ -3539,8 +3539,8 @@ func (lc *LightningChannel) applyCommitFee(
35393539
// or nil otherwise.
35403540
func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
35413541
ourLogCounter uint64, whoseCommitChain lntypes.ChannelParty,
3542-
buffer BufferType, predictOurAdd, predictTheirAdd *paymentDescriptor,
3543-
) error {
3542+
buffer BufferType, skipOpenerReserveCHeck bool, predictOurAdd,
3543+
predictTheirAdd *paymentDescriptor) error {
35443544

35453545
// First fetch the initial balance before applying any updates.
35463546
commitChain := lc.commitChains.Local
@@ -3638,30 +3638,82 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
36383638
commitFee := feePerKw.FeeForWeight(commitWeight)
36393639
commitFeeMsat := lnwire.NewMSatFromSatoshis(commitFee)
36403640

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.
36413658
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, "+
36533674
"chan_initiator=%v", ourBalance, ourReserve,
36543675
commitFeeMsat, bufferAmt, lc.channelState.IsInitiator)
36553676

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

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

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)
36653717
}
36663718

36673719
// validateUpdates take a set of updates, and validates them against
@@ -3813,9 +3865,17 @@ func (lc *LightningChannel) SignNextCommitment() (*NewCommitState, error) {
38133865
// We do not enforce the FeeBuffer here because when we reach this
38143866
// point all updates will have to get locked-in so we enforce the
38153867
// 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?
38163876
err := lc.validateCommitmentSanity(
38173877
remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote,
3818-
NoBuffer, nil, nil,
3878+
NoBuffer, true, nil, nil,
38193879
)
38203880
if err != nil {
38213881
return nil, err
@@ -4842,9 +4902,17 @@ func (lc *LightningChannel) ReceiveNewCommitment(commitSigs *CommitSigs) error {
48424902
// point all updates will have to get locked-in (we already received
48434903
// the UpdateAddHTLC msg from our peer prior to receiving the
48444904
// 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?
48454913
err := lc.validateCommitmentSanity(
48464914
lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local,
4847-
NoBuffer, nil, nil,
4915+
NoBuffer, true, nil, nil,
48484916
)
48494917
if err != nil {
48504918
return err
@@ -5748,9 +5816,16 @@ func (lc *LightningChannel) validateAddHtlc(pd *paymentDescriptor,
57485816

57495817
// First we'll check whether this HTLC can be added to the remote
57505818
// 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.
57515826
err := lc.validateCommitmentSanity(
57525827
remoteACKedIndex, lc.updateLogs.Local.logIndex, lntypes.Remote,
5753-
buffer, pd, nil,
5828+
buffer, false, pd, nil,
57545829
)
57555830
if err != nil {
57565831
return err
@@ -5761,9 +5836,11 @@ func (lc *LightningChannel) validateAddHtlc(pd *paymentDescriptor,
57615836
// totally bullet proof, as the remote might be adding updates
57625837
// concurrently, but if we fail this check there is for sure not
57635838
// possible for us to add the HTLC.
5839+
// We also make sure the reserve requirments for the channel opener are
5840+
// met.
57645841
err = lc.validateCommitmentSanity(
57655842
lc.updateLogs.Remote.logIndex, lc.updateLogs.Local.logIndex,
5766-
lntypes.Local, buffer, pd, nil,
5843+
lntypes.Local, buffer, false, pd, nil,
57675844
)
57685845
if err != nil {
57695846
return err
@@ -5807,9 +5884,12 @@ func (lc *LightningChannel) ReceiveHTLC(htlc *lnwire.UpdateAddHTLC) (uint64,
58075884
// was introduced is to protect against asynchronous sending of htlcs so
58085885
// we use it here. The current lightning protocol does not allow to
58095886
// 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
58105890
err := lc.validateCommitmentSanity(
58115891
lc.updateLogs.Remote.logIndex, localACKedIndex, lntypes.Local,
5812-
NoBuffer, nil, pd,
5892+
NoBuffer, allowLocalDipBelowReserve, nil, pd,
58135893
)
58145894
if err != nil {
58155895
return 0, err
@@ -8185,6 +8265,7 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error {
81858265
}
81868266

81878267
// Ensure that the passed fee rate meets our current requirements.
8268+
// TODO(ziggie): Call `validateCommitmentSanity` instead ?
81888269
if err := lc.validateFeeRate(feePerKw); err != nil {
81898270
return err
81908271
}
@@ -8269,6 +8350,24 @@ func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) er
82698350
EntryType: FeeUpdate,
82708351
}
82718352

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

82748373
return nil

0 commit comments

Comments
 (0)