Merge pull request #8824 from Crypt-iQ/precise_dust_limit
multi: include commitment fees in dust calculation
This commit is contained in:
13 files changed
+461
-89
No files matched your search
@@ -441,7 +441,7 @@ type Config struct {
|
|||||||
|
|
||||||
GcCanceledInvoicesOnTheFly bool `long:"gc-canceled-invoices-on-the-fly" description:"If true, we'll delete newly canceled invoices on the fly."`
|
GcCanceledInvoicesOnTheFly bool `long:"gc-canceled-invoices-on-the-fly" description:"If true, we'll delete newly canceled invoices on the fly."`
|
||||||
|
|
||||||
DustThreshold uint64 `long:"dust-threshold" description:"Sets the dust sum threshold in satoshis for a channel after which dust HTLC's will be failed."`
|
MaxFeeExposure uint64 `long:"dust-threshold" description:"Sets the max fee exposure in satoshis for a channel after which HTLC's will be failed."`
|
||||||
|
|
||||||
Fee *lncfg.Fee `group:"fee" namespace:"fee"`
|
Fee *lncfg.Fee `group:"fee" namespace:"fee"`
|
||||||
|
|
||||||
@@ -693,7 +693,7 @@ func DefaultConfig() Config {
|
|||||||
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
|
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
|
||||||
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
|
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
|
||||||
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
|
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
|
||||||
DustThreshold: uint64(htlcswitch.DefaultDustThreshold.ToSatoshis()),
|
MaxFeeExposure: uint64(htlcswitch.DefaultMaxFeeExposure.ToSatoshis()),
|
||||||
LogWriter: build.NewRotatingLogWriter(),
|
LogWriter: build.NewRotatingLogWriter(),
|
||||||
DB: lncfg.DefaultDB(),
|
DB: lncfg.DefaultDB(),
|
||||||
Cluster: lncfg.DefaultCluster(),
|
Cluster: lncfg.DefaultCluster(),
|
||||||
|
|||||||
@@ -84,6 +84,9 @@ commitment when the channel was force closed.
|
|||||||
All units are `sats/kvB`. If the new field `min_relay_feerate` is not set,
|
All units are `sats/kvB`. If the new field `min_relay_feerate` is not set,
|
||||||
the default floor feerate (1012 sats/kvB) will be used.
|
the default floor feerate (1012 sats/kvB) will be used.
|
||||||
|
|
||||||
|
* Commitment fees are now taken into account when [calculating the fee
|
||||||
|
exposure threshold](https://github.com/lightningnetwork/lnd/pull/8824).
|
||||||
|
|
||||||
## RPC Updates
|
## RPC Updates
|
||||||
|
|
||||||
* [`xImportMissionControl`](https://github.com/lightningnetwork/lnd/pull/8779)
|
* [`xImportMissionControl`](https://github.com/lightningnetwork/lnd/pull/8779)
|
||||||
@@ -161,6 +164,7 @@ commitment when the channel was force closed.
|
|||||||
* bitromortac
|
* bitromortac
|
||||||
* Bufo
|
* Bufo
|
||||||
* Elle Mouton
|
* Elle Mouton
|
||||||
|
* Eugene Siegel
|
||||||
* Matheus Degiovani
|
* Matheus Degiovani
|
||||||
* Oliver Gugger
|
* Oliver Gugger
|
||||||
* Slyghtning
|
* Slyghtning
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ package htlcswitch
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/btcutil"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/invoices"
|
"github.com/lightningnetwork/lnd/invoices"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
@@ -59,8 +61,10 @@ type packetHandler interface {
|
|||||||
// whether a link has too much dust exposure.
|
// whether a link has too much dust exposure.
|
||||||
type dustHandler interface {
|
type dustHandler interface {
|
||||||
// getDustSum returns the dust sum on either the local or remote
|
// getDustSum returns the dust sum on either the local or remote
|
||||||
// commitment.
|
// commitment. An optional fee parameter can be passed in which is used
|
||||||
getDustSum(remote bool) lnwire.MilliSatoshi
|
// to calculate the dust sum.
|
||||||
|
getDustSum(remote bool,
|
||||||
|
fee fn.Option[chainfee.SatPerKWeight]) lnwire.MilliSatoshi
|
||||||
|
|
||||||
// getFeeRate returns the current channel feerate.
|
// getFeeRate returns the current channel feerate.
|
||||||
getFeeRate() chainfee.SatPerKWeight
|
getFeeRate() chainfee.SatPerKWeight
|
||||||
@@ -68,6 +72,10 @@ type dustHandler interface {
|
|||||||
// getDustClosure returns a closure that can evaluate whether a passed
|
// getDustClosure returns a closure that can evaluate whether a passed
|
||||||
// HTLC is dust.
|
// HTLC is dust.
|
||||||
getDustClosure() dustClosure
|
getDustClosure() dustClosure
|
||||||
|
|
||||||
|
// getCommitFee returns the commitment fee in satoshis from either the
|
||||||
|
// local or remote commitment. This does not include dust.
|
||||||
|
getCommitFee(remote bool) btcutil.Amount
|
||||||
}
|
}
|
||||||
|
|
||||||
// scidAliasHandler is an interface that the ChannelLink implements so it can
|
// scidAliasHandler is an interface that the ChannelLink implements so it can
|
||||||
|
|||||||
+201
-3
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/fn"
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||||
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/invoices"
|
"github.com/lightningnetwork/lnd/invoices"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -278,6 +279,10 @@ type ChannelLinkConfig struct {
|
|||||||
// by failing back any blinding-related payloads as if they were
|
// by failing back any blinding-related payloads as if they were
|
||||||
// invalid.
|
// invalid.
|
||||||
DisallowRouteBlinding bool
|
DisallowRouteBlinding bool
|
||||||
|
|
||||||
|
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
|
||||||
|
// restrict the flow of HTLCs and fee updates.
|
||||||
|
MaxFeeExposure lnwire.MilliSatoshi
|
||||||
}
|
}
|
||||||
|
|
||||||
// channelLink is the service which drives a channel's commitment update
|
// channelLink is the service which drives a channel's commitment update
|
||||||
@@ -447,6 +452,11 @@ func NewChannelLink(cfg ChannelLinkConfig,
|
|||||||
|
|
||||||
logPrefix := fmt.Sprintf("ChannelLink(%v):", channel.ChannelPoint())
|
logPrefix := fmt.Sprintf("ChannelLink(%v):", channel.ChannelPoint())
|
||||||
|
|
||||||
|
// If the max fee exposure isn't set, use the default.
|
||||||
|
if cfg.MaxFeeExposure == 0 {
|
||||||
|
cfg.MaxFeeExposure = DefaultMaxFeeExposure
|
||||||
|
}
|
||||||
|
|
||||||
return &channelLink{
|
return &channelLink{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -1591,6 +1601,20 @@ func (l *channelLink) handleDownstreamUpdateAdd(pkt *htlcPacket) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if we can add the HTLC here without exceededing the max fee
|
||||||
|
// exposure threshold.
|
||||||
|
if l.isOverexposedWithHtlc(htlc, false) {
|
||||||
|
l.log.Debugf("Unable to handle downstream HTLC - max fee " +
|
||||||
|
"exposure exceeded")
|
||||||
|
|
||||||
|
l.mailBox.FailAdd(pkt)
|
||||||
|
|
||||||
|
return NewDetailedLinkError(
|
||||||
|
lnwire.NewTemporaryChannelFailure(nil),
|
||||||
|
OutgoingFailureDownstreamHtlcAdd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// A new payment has been initiated via the downstream channel,
|
// A new payment has been initiated via the downstream channel,
|
||||||
// so we add the new HTLC to our local log, then update the
|
// so we add the new HTLC to our local log, then update the
|
||||||
// commitment chains.
|
// commitment chains.
|
||||||
@@ -1958,6 +1982,18 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We have to check the limit here rather than later in the
|
||||||
|
// switch because the counterparty can keep sending HTLC's
|
||||||
|
// without sending a revoke. This would mean that the switch
|
||||||
|
// check would only occur later.
|
||||||
|
if l.isOverexposedWithHtlc(msg, true) {
|
||||||
|
l.fail(LinkFailureError{code: ErrInternalError},
|
||||||
|
"peer sent us an HTLC that exceeded our max "+
|
||||||
|
"fee exposure")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// We just received an add request from an upstream peer, so we
|
// We just received an add request from an upstream peer, so we
|
||||||
// add it to our state machine, then add the HTLC to our
|
// add it to our state machine, then add the HTLC to our
|
||||||
// "settle" list in the event that we know the preimage.
|
// "settle" list in the event that we know the preimage.
|
||||||
@@ -2375,9 +2411,32 @@ func (l *channelLink) handleUpstreamMsg(msg lnwire.Message) {
|
|||||||
l.RWMutex.Unlock()
|
l.RWMutex.Unlock()
|
||||||
|
|
||||||
case *lnwire.UpdateFee:
|
case *lnwire.UpdateFee:
|
||||||
|
// Check and see if their proposed fee-rate would make us
|
||||||
|
// exceed the fee threshold.
|
||||||
|
fee := chainfee.SatPerKWeight(msg.FeePerKw)
|
||||||
|
|
||||||
|
isDust, err := l.exceedsFeeExposureLimit(fee)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't typically happen. If it does, it
|
||||||
|
// indicates something is wrong with our channel state.
|
||||||
|
l.log.Errorf("Unable to determine if fee threshold " +
|
||||||
|
"exceeded")
|
||||||
|
l.fail(LinkFailureError{code: ErrInternalError},
|
||||||
|
"error calculating fee exposure: %v", err)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if isDust {
|
||||||
|
// The proposed fee-rate makes us exceed the fee
|
||||||
|
// threshold.
|
||||||
|
l.fail(LinkFailureError{code: ErrInternalError},
|
||||||
|
"fee threshold exceeded: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// We received fee update from peer. If we are the initiator we
|
// We received fee update from peer. If we are the initiator we
|
||||||
// will fail the channel, if not we will apply the update.
|
// will fail the channel, if not we will apply the update.
|
||||||
fee := chainfee.SatPerKWeight(msg.FeePerKw)
|
|
||||||
if err := l.channel.ReceiveUpdateFee(fee); err != nil {
|
if err := l.channel.ReceiveUpdateFee(fee); err != nil {
|
||||||
l.fail(LinkFailureError{code: ErrInvalidUpdate},
|
l.fail(LinkFailureError{code: ErrInvalidUpdate},
|
||||||
"error receiving fee update: %v", err)
|
"error receiving fee update: %v", err)
|
||||||
@@ -2668,8 +2727,10 @@ func (l *channelLink) MayAddOutgoingHtlc(amt lnwire.MilliSatoshi) error {
|
|||||||
// method.
|
// method.
|
||||||
//
|
//
|
||||||
// NOTE: Part of the dustHandler interface.
|
// NOTE: Part of the dustHandler interface.
|
||||||
func (l *channelLink) getDustSum(remote bool) lnwire.MilliSatoshi {
|
func (l *channelLink) getDustSum(remote bool,
|
||||||
return l.channel.GetDustSum(remote)
|
dryRunFee fn.Option[chainfee.SatPerKWeight]) lnwire.MilliSatoshi {
|
||||||
|
|
||||||
|
return l.channel.GetDustSum(remote, dryRunFee)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getFeeRate is a wrapper method that retrieves the underlying channel's
|
// getFeeRate is a wrapper method that retrieves the underlying channel's
|
||||||
@@ -2692,6 +2753,130 @@ func (l *channelLink) getDustClosure() dustClosure {
|
|||||||
return dustHelper(chanType, localDustLimit, remoteDustLimit)
|
return dustHelper(chanType, localDustLimit, remoteDustLimit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getCommitFee returns either the local or remote CommitFee in satoshis. This
|
||||||
|
// is used so that the Switch can have access to the commitment fee without
|
||||||
|
// needing to have a *LightningChannel. This doesn't include dust.
|
||||||
|
//
|
||||||
|
// NOTE: Part of the dustHandler interface.
|
||||||
|
func (l *channelLink) getCommitFee(remote bool) btcutil.Amount {
|
||||||
|
if remote {
|
||||||
|
return l.channel.State().RemoteCommitment.CommitFee
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.channel.State().LocalCommitment.CommitFee
|
||||||
|
}
|
||||||
|
|
||||||
|
// exceedsFeeExposureLimit returns whether or not the new proposed fee-rate
|
||||||
|
// increases the total dust and fees within the channel past the configured
|
||||||
|
// fee threshold. It first calculates the dust sum over every update in the
|
||||||
|
// update log with the proposed fee-rate and taking into account both the local
|
||||||
|
// and remote dust limits. It uses every update in the update log instead of
|
||||||
|
// what is actually on the local and remote commitments because it is assumed
|
||||||
|
// that in a worst-case scenario, every update in the update log could
|
||||||
|
// theoretically be on either commitment transaction and this needs to be
|
||||||
|
// accounted for with this fee-rate. It then calculates the local and remote
|
||||||
|
// commitment fees given the proposed fee-rate. Finally, it tallies the results
|
||||||
|
// and determines if the fee threshold has been exceeded.
|
||||||
|
func (l *channelLink) exceedsFeeExposureLimit(
|
||||||
|
feePerKw chainfee.SatPerKWeight) (bool, error) {
|
||||||
|
|
||||||
|
dryRunFee := fn.Some[chainfee.SatPerKWeight](feePerKw)
|
||||||
|
|
||||||
|
// Get the sum of dust for both the local and remote commitments using
|
||||||
|
// this "dry-run" fee.
|
||||||
|
localDustSum := l.getDustSum(false, dryRunFee)
|
||||||
|
remoteDustSum := l.getDustSum(true, dryRunFee)
|
||||||
|
|
||||||
|
// Calculate the local and remote commitment fees using this dry-run
|
||||||
|
// fee.
|
||||||
|
localFee, remoteFee, err := l.channel.CommitFeeTotalAt(feePerKw)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, check whether the max fee exposure was exceeded on either
|
||||||
|
// future commitment transaction with the fee-rate.
|
||||||
|
totalLocalDust := localDustSum + lnwire.NewMSatFromSatoshis(localFee)
|
||||||
|
if totalLocalDust > l.cfg.MaxFeeExposure {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
totalRemoteDust := remoteDustSum + lnwire.NewMSatFromSatoshis(
|
||||||
|
remoteFee,
|
||||||
|
)
|
||||||
|
|
||||||
|
return totalRemoteDust > l.cfg.MaxFeeExposure, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isOverexposedWithHtlc calculates whether the proposed HTLC will make the
|
||||||
|
// channel exceed the fee threshold. It first fetches the largest fee-rate that
|
||||||
|
// may be on any unrevoked commitment transaction. Then, using this fee-rate,
|
||||||
|
// determines if the to-be-added HTLC is dust. If the HTLC is dust, it adds to
|
||||||
|
// the overall dust sum. If it is not dust, it contributes to weight, which
|
||||||
|
// also adds to the overall dust sum by an increase in fees. If the dust sum on
|
||||||
|
// either commitment exceeds the configured fee threshold, this function
|
||||||
|
// returns true.
|
||||||
|
func (l *channelLink) isOverexposedWithHtlc(htlc *lnwire.UpdateAddHTLC,
|
||||||
|
incoming bool) bool {
|
||||||
|
|
||||||
|
dustClosure := l.getDustClosure()
|
||||||
|
|
||||||
|
feeRate := l.channel.WorstCaseFeeRate()
|
||||||
|
|
||||||
|
amount := htlc.Amount.ToSatoshis()
|
||||||
|
|
||||||
|
// See if this HTLC is dust on both the local and remote commitments.
|
||||||
|
isLocalDust := dustClosure(feeRate, incoming, true, amount)
|
||||||
|
isRemoteDust := dustClosure(feeRate, incoming, false, amount)
|
||||||
|
|
||||||
|
// Calculate the dust sum for the local and remote commitments.
|
||||||
|
localDustSum := l.getDustSum(false, fn.None[chainfee.SatPerKWeight]())
|
||||||
|
remoteDustSum := l.getDustSum(true, fn.None[chainfee.SatPerKWeight]())
|
||||||
|
|
||||||
|
// Grab the larger of the local and remote commitment fees w/o dust.
|
||||||
|
commitFee := l.getCommitFee(false)
|
||||||
|
|
||||||
|
if l.getCommitFee(true) > commitFee {
|
||||||
|
commitFee = l.getCommitFee(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
localDustSum += lnwire.NewMSatFromSatoshis(commitFee)
|
||||||
|
remoteDustSum += lnwire.NewMSatFromSatoshis(commitFee)
|
||||||
|
|
||||||
|
// Calculate the additional fee increase if this is a non-dust HTLC.
|
||||||
|
weight := lntypes.WeightUnit(input.HTLCWeight)
|
||||||
|
additional := lnwire.NewMSatFromSatoshis(
|
||||||
|
feeRate.FeeForWeight(weight),
|
||||||
|
)
|
||||||
|
|
||||||
|
if isLocalDust {
|
||||||
|
// If this is dust, it doesn't contribute to weight but does
|
||||||
|
// contribute to the overall dust sum.
|
||||||
|
localDustSum += lnwire.NewMSatFromSatoshis(amount)
|
||||||
|
} else {
|
||||||
|
// Account for the fee increase that comes with an increase in
|
||||||
|
// weight.
|
||||||
|
localDustSum += additional
|
||||||
|
}
|
||||||
|
|
||||||
|
if localDustSum > l.cfg.MaxFeeExposure {
|
||||||
|
// The max fee exposure was exceeded.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if isRemoteDust {
|
||||||
|
// If this is dust, it doesn't contribute to weight but does
|
||||||
|
// contribute to the overall dust sum.
|
||||||
|
remoteDustSum += lnwire.NewMSatFromSatoshis(amount)
|
||||||
|
} else {
|
||||||
|
// Account for the fee increase that comes with an increase in
|
||||||
|
// weight.
|
||||||
|
remoteDustSum += additional
|
||||||
|
}
|
||||||
|
|
||||||
|
return remoteDustSum > l.cfg.MaxFeeExposure
|
||||||
|
}
|
||||||
|
|
||||||
// dustClosure is a function that evaluates whether an HTLC is dust. It returns
|
// dustClosure is a function that evaluates whether an HTLC is dust. It returns
|
||||||
// true if the HTLC is dust. It takes in a feerate, a boolean denoting whether
|
// true if the HTLC is dust. It takes in a feerate, a boolean denoting whether
|
||||||
// the HTLC is incoming (i.e. one that the remote sent), a boolean denoting
|
// the HTLC is incoming (i.e. one that the remote sent), a boolean denoting
|
||||||
@@ -3060,6 +3245,19 @@ func (l *channelLink) updateChannelFee(feePerKw chainfee.SatPerKWeight) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check and see if our proposed fee-rate would make us exceed the fee
|
||||||
|
// threshold.
|
||||||
|
thresholdExceeded, err := l.exceedsFeeExposureLimit(feePerKw)
|
||||||
|
if err != nil {
|
||||||
|
// This shouldn't typically happen. If it does, it indicates
|
||||||
|
// something is wrong with our channel state.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if thresholdExceeded {
|
||||||
|
return fmt.Errorf("link fee threshold exceeded")
|
||||||
|
}
|
||||||
|
|
||||||
// First, we'll update the local fee on our commitment.
|
// First, we'll update the local fee on our commitment.
|
||||||
if err := l.channel.UpdateFee(feePerKw); err != nil {
|
if err := l.channel.UpdateFee(feePerKw); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
+11
-1
@@ -4471,10 +4471,20 @@ func TestChannelLinkUpdateCommitFee(t *testing.T) {
|
|||||||
|
|
||||||
// Triggering the link to update the fee of the channel with a fee rate
|
// Triggering the link to update the fee of the channel with a fee rate
|
||||||
// that exceeds its maximum fee allocation should result in a fee rate
|
// that exceeds its maximum fee allocation should result in a fee rate
|
||||||
// corresponding to the maximum fee allocation.
|
// corresponding to the maximum fee allocation. Increase the dust
|
||||||
|
// threshold so that we don't trigger that logic.
|
||||||
|
highFeeExposure := lnwire.NewMSatFromSatoshis(
|
||||||
|
2 * btcutil.SatoshiPerBitcoin,
|
||||||
|
)
|
||||||
const maxFeeRate chainfee.SatPerKWeight = 207180182
|
const maxFeeRate chainfee.SatPerKWeight = 207180182
|
||||||
|
n.aliceChannelLink.cfg.MaxFeeExposure = highFeeExposure
|
||||||
|
n.firstBobChannelLink.cfg.MaxFeeExposure = highFeeExposure
|
||||||
triggerFeeUpdate(maxFeeRate+1, minRelayFee, maxFeeRate, true)
|
triggerFeeUpdate(maxFeeRate+1, minRelayFee, maxFeeRate, true)
|
||||||
|
|
||||||
|
// Decrease the max fee exposure back to normal.
|
||||||
|
n.aliceChannelLink.cfg.MaxFeeExposure = DefaultMaxFeeExposure
|
||||||
|
n.firstBobChannelLink.cfg.MaxFeeExposure = DefaultMaxFeeExposure
|
||||||
|
|
||||||
// Triggering the link to update the fee of the channel with a fee rate
|
// Triggering the link to update the fee of the channel with a fee rate
|
||||||
// that is below the current min relay fee rate should result in a fee
|
// that is below the current min relay fee rate should result in a fee
|
||||||
// rate corresponding to the minimum relay fee.
|
// rate corresponding to the minimum relay fee.
|
||||||
|
|||||||
+9
-2
@@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/clock"
|
"github.com/lightningnetwork/lnd/clock"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||||
"github.com/lightningnetwork/lnd/invoices"
|
"github.com/lightningnetwork/lnd/invoices"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
@@ -200,7 +201,7 @@ func initSwitchWithDB(startingHeight uint32, db *channeldb.DB) (*Switch, error)
|
|||||||
HtlcNotifier: &mockHTLCNotifier{},
|
HtlcNotifier: &mockHTLCNotifier{},
|
||||||
Clock: clock.NewDefaultClock(),
|
Clock: clock.NewDefaultClock(),
|
||||||
MailboxDeliveryTimeout: time.Hour,
|
MailboxDeliveryTimeout: time.Hour,
|
||||||
DustThreshold: DefaultDustThreshold,
|
MaxFeeExposure: DefaultMaxFeeExposure,
|
||||||
SignAliasUpdate: signAliasUpdate,
|
SignAliasUpdate: signAliasUpdate,
|
||||||
IsAlias: isAlias,
|
IsAlias: isAlias,
|
||||||
}
|
}
|
||||||
@@ -813,7 +814,9 @@ func (f *mockChannelLink) handleSwitchPacket(pkt *htlcPacket) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *mockChannelLink) getDustSum(remote bool) lnwire.MilliSatoshi {
|
func (f *mockChannelLink) getDustSum(remote bool,
|
||||||
|
dryRunFee fn.Option[chainfee.SatPerKWeight]) lnwire.MilliSatoshi {
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -828,6 +831,10 @@ func (f *mockChannelLink) getDustClosure() dustClosure {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *mockChannelLink) getCommitFee(remote bool) btcutil.Amount {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) {
|
func (f *mockChannelLink) HandleChannelUpdate(lnwire.Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+41
-32
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/clock"
|
"github.com/lightningnetwork/lnd/clock"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||||
"github.com/lightningnetwork/lnd/kvdb"
|
"github.com/lightningnetwork/lnd/kvdb"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -75,14 +76,15 @@ var (
|
|||||||
// failed to be processed.
|
// failed to be processed.
|
||||||
ErrLocalAddFailed = errors.New("local add HTLC failed")
|
ErrLocalAddFailed = errors.New("local add HTLC failed")
|
||||||
|
|
||||||
// errDustThresholdExceeded is only surfaced to callers of SendHTLC and
|
// errFeeExposureExceeded is only surfaced to callers of SendHTLC and
|
||||||
// signals that sending the HTLC would exceed the outgoing link's dust
|
// signals that sending the HTLC would exceed the outgoing link's fee
|
||||||
// threshold.
|
// exposure threshold.
|
||||||
errDustThresholdExceeded = errors.New("dust threshold exceeded")
|
errFeeExposureExceeded = errors.New("fee exposure exceeded")
|
||||||
|
|
||||||
// DefaultDustThreshold is the default threshold after which we'll fail
|
// DefaultMaxFeeExposure is the default threshold after which we'll
|
||||||
// payments if they are dust. This is currently set to 500m msats.
|
// fail payments if they increase our fee exposure. This is currently
|
||||||
DefaultDustThreshold = lnwire.MilliSatoshi(500_000_000)
|
// set to 500m msats.
|
||||||
|
DefaultMaxFeeExposure = lnwire.MilliSatoshi(500_000_000)
|
||||||
)
|
)
|
||||||
|
|
||||||
// plexPacket encapsulates switch packet and adds error channel to receive
|
// plexPacket encapsulates switch packet and adds error channel to receive
|
||||||
@@ -210,9 +212,9 @@ type Config struct {
|
|||||||
// a mailbox via AddPacket.
|
// a mailbox via AddPacket.
|
||||||
MailboxDeliveryTimeout time.Duration
|
MailboxDeliveryTimeout time.Duration
|
||||||
|
|
||||||
// DustThreshold is the threshold in milli-satoshis after which we'll
|
// MaxFeeExposure is the threshold in milli-satoshis after which we'll
|
||||||
// fail incoming or outgoing dust payments for a particular channel.
|
// fail incoming or outgoing payments for a particular channel.
|
||||||
DustThreshold lnwire.MilliSatoshi
|
MaxFeeExposure lnwire.MilliSatoshi
|
||||||
|
|
||||||
// SignAliasUpdate is used when sending FailureMessages backwards for
|
// SignAliasUpdate is used when sending FailureMessages backwards for
|
||||||
// option_scid_alias channels. This avoids a potential privacy leak by
|
// option_scid_alias channels. This avoids a potential privacy leak by
|
||||||
@@ -559,9 +561,9 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, attemptID uint64,
|
|||||||
return linkErr
|
return linkErr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate whether this HTLC would increase our exposure to dust. If
|
// Evaluate whether this HTLC would bypass our fee exposure. If it
|
||||||
// it does, don't send it out and instead return an error.
|
// does, don't send it out and instead return an error.
|
||||||
if s.evaluateDustThreshold(link, htlc.Amount, false) {
|
if s.dustExceedsFeeThreshold(link, htlc.Amount, false) {
|
||||||
// Notify the htlc notifier of a link failure on our outgoing
|
// Notify the htlc notifier of a link failure on our outgoing
|
||||||
// link. We use the FailTemporaryChannelFailure in place of a
|
// link. We use the FailTemporaryChannelFailure in place of a
|
||||||
// more descriptive error message.
|
// more descriptive error message.
|
||||||
@@ -579,7 +581,7 @@ func (s *Switch) SendHTLC(firstHop lnwire.ShortChannelID, attemptID uint64,
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
return errDustThresholdExceeded
|
return errFeeExposureExceeded
|
||||||
}
|
}
|
||||||
|
|
||||||
circuit := newPaymentCircuit(&htlc.PaymentHash, packet)
|
circuit := newPaymentCircuit(&htlc.PaymentHash, packet)
|
||||||
@@ -1247,9 +1249,10 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
|||||||
return s.failAddPacket(packet, linkErr)
|
return s.failAddPacket(packet, linkErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate whether this HTLC would increase our exposure to
|
// Evaluate whether this HTLC would increase our fee exposure
|
||||||
// dust on the incoming link. If it does, fail it backwards.
|
// over the threshold on the incoming link. If it does, fail it
|
||||||
if s.evaluateDustThreshold(
|
// backwards.
|
||||||
|
if s.dustExceedsFeeThreshold(
|
||||||
incomingLink, packet.incomingAmount, true,
|
incomingLink, packet.incomingAmount, true,
|
||||||
) {
|
) {
|
||||||
// The incoming dust exceeds the threshold, so we fail
|
// The incoming dust exceeds the threshold, so we fail
|
||||||
@@ -1261,9 +1264,10 @@ func (s *Switch) handlePacketForward(packet *htlcPacket) error {
|
|||||||
return s.failAddPacket(packet, linkErr)
|
return s.failAddPacket(packet, linkErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also evaluate whether this HTLC would increase our exposure
|
// Also evaluate whether this HTLC would increase our fee
|
||||||
// to dust on the destination link. If it does, fail it back.
|
// exposure over the threshold on the destination link. If it
|
||||||
if s.evaluateDustThreshold(
|
// does, fail it back.
|
||||||
|
if s.dustExceedsFeeThreshold(
|
||||||
destination, packet.amount, false,
|
destination, packet.amount, false,
|
||||||
) {
|
) {
|
||||||
// The outgoing dust exceeds the threshold, so we fail
|
// The outgoing dust exceeds the threshold, so we fail
|
||||||
@@ -2768,14 +2772,15 @@ func (s *Switch) BestHeight() uint32 {
|
|||||||
return atomic.LoadUint32(&s.bestHeight)
|
return atomic.LoadUint32(&s.bestHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
// evaluateDustThreshold takes in a ChannelLink, HTLC amount, and a boolean to
|
// dustExceedsFeeThreshold takes in a ChannelLink, HTLC amount, and a boolean
|
||||||
// determine whether the default dust threshold has been exceeded. This
|
// to determine whether the default fee threshold has been exceeded. This
|
||||||
// heuristic takes into account the trimmed-to-dust mechanism. The sum of the
|
// heuristic takes into account the trimmed-to-dust mechanism. The sum of the
|
||||||
// commitment's dust with the mailbox's dust with the amount is checked against
|
// commitment's dust with the mailbox's dust with the amount is checked against
|
||||||
// the default threshold. If incoming is true, then the amount is not included
|
// the fee exposure threshold. If incoming is true, then the amount is not
|
||||||
// in the sum as it was already included in the commitment's dust. A boolean is
|
// included in the sum as it was already included in the commitment's dust. A
|
||||||
// returned telling the caller whether the HTLC should be failed back.
|
// boolean is returned telling the caller whether the HTLC should be failed
|
||||||
func (s *Switch) evaluateDustThreshold(link ChannelLink,
|
// back.
|
||||||
|
func (s *Switch) dustExceedsFeeThreshold(link ChannelLink,
|
||||||
amount lnwire.MilliSatoshi, incoming bool) bool {
|
amount lnwire.MilliSatoshi, incoming bool) bool {
|
||||||
|
|
||||||
// Retrieve the link's current commitment feerate and dustClosure.
|
// Retrieve the link's current commitment feerate and dustClosure.
|
||||||
@@ -2801,7 +2806,9 @@ func (s *Switch) evaluateDustThreshold(link ChannelLink,
|
|||||||
// If the htlc is dust on the local commitment, we'll obtain the dust
|
// If the htlc is dust on the local commitment, we'll obtain the dust
|
||||||
// sum for it.
|
// sum for it.
|
||||||
if isLocalDust {
|
if isLocalDust {
|
||||||
localSum := link.getDustSum(false)
|
localSum := link.getDustSum(
|
||||||
|
false, fn.None[chainfee.SatPerKWeight](),
|
||||||
|
)
|
||||||
localSum += localMailDust
|
localSum += localMailDust
|
||||||
|
|
||||||
// Optionally include the HTLC amount only for outgoing
|
// Optionally include the HTLC amount only for outgoing
|
||||||
@@ -2810,8 +2817,8 @@ func (s *Switch) evaluateDustThreshold(link ChannelLink,
|
|||||||
localSum += amount
|
localSum += amount
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally check against the defined dust threshold.
|
// Finally check against the defined fee threshold.
|
||||||
if localSum > s.cfg.DustThreshold {
|
if localSum > s.cfg.MaxFeeExposure {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2819,7 +2826,9 @@ func (s *Switch) evaluateDustThreshold(link ChannelLink,
|
|||||||
// Also check if the htlc is dust on the remote commitment, if we've
|
// Also check if the htlc is dust on the remote commitment, if we've
|
||||||
// reached this point.
|
// reached this point.
|
||||||
if isRemoteDust {
|
if isRemoteDust {
|
||||||
remoteSum := link.getDustSum(true)
|
remoteSum := link.getDustSum(
|
||||||
|
true, fn.None[chainfee.SatPerKWeight](),
|
||||||
|
)
|
||||||
remoteSum += remoteMailDust
|
remoteSum += remoteMailDust
|
||||||
|
|
||||||
// Optionally include the HTLC amount only for outgoing
|
// Optionally include the HTLC amount only for outgoing
|
||||||
@@ -2828,8 +2837,8 @@ func (s *Switch) evaluateDustThreshold(link ChannelLink,
|
|||||||
remoteSum += amount
|
remoteSum += amount
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally check against the defined dust threshold.
|
// Finally check against the defined fee threshold.
|
||||||
if remoteSum > s.cfg.DustThreshold {
|
if remoteSum > s.cfg.MaxFeeExposure {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+79
-35
@@ -18,10 +18,12 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
"github.com/lightningnetwork/lnd/contractcourt"
|
"github.com/lightningnetwork/lnd/contractcourt"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
"github.com/lightningnetwork/lnd/htlcswitch/hodl"
|
||||||
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
"github.com/lightningnetwork/lnd/htlcswitch/hop"
|
||||||
"github.com/lightningnetwork/lnd/lntest/mock"
|
"github.com/lightningnetwork/lnd/lntest/mock"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
"github.com/lightningnetwork/lnd/ticker"
|
"github.com/lightningnetwork/lnd/ticker"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -4259,7 +4261,7 @@ func TestInterceptableSwitchWatchDog(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestSwitchDustForwarding tests that the switch properly fails HTLC's which
|
// TestSwitchDustForwarding tests that the switch properly fails HTLC's which
|
||||||
// have incoming or outgoing links that breach their dust thresholds.
|
// have incoming or outgoing links that breach their fee thresholds.
|
||||||
func TestSwitchDustForwarding(t *testing.T) {
|
func TestSwitchDustForwarding(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -4288,14 +4290,15 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
// We'll test that once the default threshold is exceeded on the
|
// We'll test that once the default threshold is exceeded on the
|
||||||
// Alice -> Bob channel, either side's calls to SendHTLC will fail.
|
// Alice -> Bob channel, either side's calls to SendHTLC will fail.
|
||||||
//
|
//
|
||||||
// Alice will send 357 HTLC's of 700sats. Bob will also send 357 HTLC's
|
// Alice will send 354 HTLC's of 700sats. Bob will also send 354 HTLC's
|
||||||
// of 700sats. If either side attempts to send a dust HTLC, it will
|
// of 700sats.
|
||||||
// fail so amounts below 800sats will breach the dust threshold.
|
numHTLCs := 354
|
||||||
|
aliceAttemptID, bobAttemptID := numHTLCs, numHTLCs
|
||||||
amt := lnwire.NewMSatFromSatoshis(700)
|
amt := lnwire.NewMSatFromSatoshis(700)
|
||||||
aliceBobFirstHop := n.aliceChannelLink.ShortChanID()
|
aliceBobFirstHop := n.aliceChannelLink.ShortChanID()
|
||||||
|
|
||||||
sendDustHtlcs(t, n, true, amt, aliceBobFirstHop)
|
sendDustHtlcs(t, n, true, amt, aliceBobFirstHop, numHTLCs)
|
||||||
sendDustHtlcs(t, n, false, amt, aliceBobFirstHop)
|
sendDustHtlcs(t, n, false, amt, aliceBobFirstHop, numHTLCs)
|
||||||
|
|
||||||
// Generate the parameters needed for Bob to send another dust HTLC.
|
// Generate the parameters needed for Bob to send another dust HTLC.
|
||||||
_, timelock, hops := generateHops(
|
_, timelock, hops := generateHops(
|
||||||
@@ -4320,7 +4323,7 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
|
|
||||||
timeout := time.After(15 * time.Second)
|
timeout := time.After(15 * time.Second)
|
||||||
pollInterval := 300 * time.Millisecond
|
pollInterval := 300 * time.Millisecond
|
||||||
expectedDust := 357 * 2 * amt
|
expectedDust := 354 * 2 * amt
|
||||||
|
|
||||||
for {
|
for {
|
||||||
<-time.After(pollInterval)
|
<-time.After(pollInterval)
|
||||||
@@ -4331,7 +4334,9 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
linkDust := link.getDustSum(remote)
|
linkDust := link.getDustSum(
|
||||||
|
remote, fn.None[chainfee.SatPerKWeight](),
|
||||||
|
)
|
||||||
localMailDust, remoteMailDust := mbox.DustPackets()
|
localMailDust, remoteMailDust := mbox.DustPackets()
|
||||||
|
|
||||||
totalDust := linkDust
|
totalDust := linkDust
|
||||||
@@ -4349,18 +4354,34 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait until Bob is almost at the dust threshold.
|
// Wait until Bob is almost at the fee threshold.
|
||||||
bobMbox := n.bobServer.htlcSwitch.mailOrchestrator.GetOrCreateMailBox(
|
bobMbox := n.bobServer.htlcSwitch.mailOrchestrator.GetOrCreateMailBox(
|
||||||
n.firstBobChannelLink.ChanID(),
|
n.firstBobChannelLink.ChanID(),
|
||||||
n.firstBobChannelLink.ShortChanID(),
|
n.firstBobChannelLink.ShortChanID(),
|
||||||
)
|
)
|
||||||
require.True(t, checkAlmostDust(n.firstBobChannelLink, bobMbox, false))
|
require.True(t, checkAlmostDust(n.firstBobChannelLink, bobMbox, false))
|
||||||
|
|
||||||
// Assert that the HTLC is failed due to the dust threshold.
|
// Sending one more HTLC should fail. SendHTLC won't error, but the
|
||||||
|
// HTLC should be failed backwards.
|
||||||
err = n.bobServer.htlcSwitch.SendHTLC(
|
err = n.bobServer.htlcSwitch.SendHTLC(
|
||||||
aliceBobFirstHop, uint64(357), failingHtlc,
|
aliceBobFirstHop, uint64(bobAttemptID), failingHtlc,
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, errDustThresholdExceeded)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
// Use the network result store to ensure the HTLC was failed
|
||||||
|
// backwards.
|
||||||
|
bobResultChan, err := n.bobServer.htlcSwitch.GetAttemptResult(
|
||||||
|
uint64(bobAttemptID), failingHash, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result, ok := <-bobResultChan
|
||||||
|
require.True(t, ok)
|
||||||
|
assertFailureCode(
|
||||||
|
t, result.Error, lnwire.CodeTemporaryChannelFailure,
|
||||||
|
)
|
||||||
|
|
||||||
|
bobAttemptID++
|
||||||
|
|
||||||
// Generate the parameters needed for bob to send a non-dust HTLC.
|
// Generate the parameters needed for bob to send a non-dust HTLC.
|
||||||
nondustAmt := lnwire.NewMSatFromSatoshis(10_000)
|
nondustAmt := lnwire.NewMSatFromSatoshis(10_000)
|
||||||
@@ -4371,8 +4392,9 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
blob, err = generateRoute(hops...)
|
blob, err = generateRoute(hops...)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Now attempt to send an HTLC above Bob's dust limit. It should
|
// Now attempt to send an HTLC above Bob's dust limit. Even though this
|
||||||
// succeed.
|
// is not a dust HTLC, it should fail because the increase in weight
|
||||||
|
// pushes us over the threshold.
|
||||||
nondustPreimage := lntypes.Preimage{0, 0, 4}
|
nondustPreimage := lntypes.Preimage{0, 0, 4}
|
||||||
nondustHash := nondustPreimage.Hash()
|
nondustHash := nondustPreimage.Hash()
|
||||||
nondustHtlc := &lnwire.UpdateAddHTLC{
|
nondustHtlc := &lnwire.UpdateAddHTLC{
|
||||||
@@ -4382,12 +4404,23 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
OnionBlob: blob,
|
OnionBlob: blob,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that SendHTLC succeeds and evaluateDustThreshold returns
|
|
||||||
// false.
|
|
||||||
err = n.bobServer.htlcSwitch.SendHTLC(
|
err = n.bobServer.htlcSwitch.SendHTLC(
|
||||||
aliceBobFirstHop, uint64(358), nondustHtlc,
|
aliceBobFirstHop, uint64(bobAttemptID), nondustHtlc,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.True(t, checkAlmostDust(n.firstBobChannelLink, bobMbox, false))
|
||||||
|
|
||||||
|
// Check that the HTLC failed.
|
||||||
|
bobResultChan, err = n.bobServer.htlcSwitch.GetAttemptResult(
|
||||||
|
uint64(bobAttemptID), nondustHash, newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result, ok = <-bobResultChan
|
||||||
|
require.True(t, ok)
|
||||||
|
assertFailureCode(
|
||||||
|
t, result.Error, lnwire.CodeTemporaryChannelFailure,
|
||||||
|
)
|
||||||
|
|
||||||
// Introduce Carol into the mix and assert that sending a multi-hop
|
// Introduce Carol into the mix and assert that sending a multi-hop
|
||||||
// dust HTLC to Alice will fail. Bob should fail back the HTLC with a
|
// dust HTLC to Alice will fail. Bob should fail back the HTLC with a
|
||||||
@@ -4417,21 +4450,19 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
carolHtlc,
|
carolHtlc,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
carolAttemptID++
|
|
||||||
|
|
||||||
carolResultChan, err := n.carolServer.htlcSwitch.GetAttemptResult(
|
carolResultChan, err := n.carolServer.htlcSwitch.GetAttemptResult(
|
||||||
uint64(carolAttemptID-1), carolHash, newMockDeobfuscator(),
|
uint64(carolAttemptID), carolHash, newMockDeobfuscator(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
result, ok := <-carolResultChan
|
result, ok = <-carolResultChan
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
assertFailureCode(
|
assertFailureCode(
|
||||||
t, result.Error, lnwire.CodeTemporaryChannelFailure,
|
t, result.Error, lnwire.CodeTemporaryChannelFailure,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Send an HTLC from Alice to Carol and assert that it is failed at the
|
// Send an HTLC from Alice to Carol and assert that it gets failed.
|
||||||
// call to SendHTLC.
|
|
||||||
htlcAmt, totalTimelock, aliceHops := generateHops(
|
htlcAmt, totalTimelock, aliceHops := generateHops(
|
||||||
amt, testStartingHeight, n.firstBobChannelLink,
|
amt, testStartingHeight, n.firstBobChannelLink,
|
||||||
n.carolChannelLink,
|
n.carolChannelLink,
|
||||||
@@ -4450,7 +4481,7 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Wait until Alice's expected dust for the remote commitment is just
|
// Wait until Alice's expected dust for the remote commitment is just
|
||||||
// under the dust threshold.
|
// under the fee threshold.
|
||||||
aliceOrch := n.aliceServer.htlcSwitch.mailOrchestrator
|
aliceOrch := n.aliceServer.htlcSwitch.mailOrchestrator
|
||||||
aliceMbox := aliceOrch.GetOrCreateMailBox(
|
aliceMbox := aliceOrch.GetOrCreateMailBox(
|
||||||
n.aliceChannelLink.ChanID(), n.aliceChannelLink.ShortChanID(),
|
n.aliceChannelLink.ChanID(), n.aliceChannelLink.ShortChanID(),
|
||||||
@@ -4458,23 +4489,36 @@ func TestSwitchDustForwarding(t *testing.T) {
|
|||||||
require.True(t, checkAlmostDust(n.aliceChannelLink, aliceMbox, true))
|
require.True(t, checkAlmostDust(n.aliceChannelLink, aliceMbox, true))
|
||||||
|
|
||||||
err = n.aliceServer.htlcSwitch.SendHTLC(
|
err = n.aliceServer.htlcSwitch.SendHTLC(
|
||||||
n.aliceChannelLink.ShortChanID(), uint64(357),
|
n.aliceChannelLink.ShortChanID(), uint64(aliceAttemptID),
|
||||||
aliceMultihopHtlc,
|
aliceMultihopHtlc,
|
||||||
)
|
)
|
||||||
require.ErrorIs(t, err, errDustThresholdExceeded)
|
require.Nil(t, err)
|
||||||
|
|
||||||
|
aliceResultChan, err := n.aliceServer.htlcSwitch.GetAttemptResult(
|
||||||
|
uint64(aliceAttemptID), aliceMultihopHash,
|
||||||
|
newMockDeobfuscator(),
|
||||||
|
)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
result, ok = <-aliceResultChan
|
||||||
|
require.True(t, ok)
|
||||||
|
assertFailureCode(
|
||||||
|
t, result.Error, lnwire.CodeTemporaryChannelFailure,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that there are numHTLCs circuits open for both Alice and Bob.
|
||||||
|
require.Equal(t, numHTLCs, n.aliceServer.htlcSwitch.circuits.NumOpen())
|
||||||
|
require.Equal(t, numHTLCs, n.bobServer.htlcSwitch.circuits.NumOpen())
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendDustHtlcs is a helper function used to send many dust HTLC's to test the
|
// sendDustHtlcs is a helper function used to send many dust HTLC's to test the
|
||||||
// Switch's dust-threshold logic. It takes a boolean denoting whether or not
|
// Switch's dust-threshold logic. It takes a boolean denoting whether or not
|
||||||
// Alice is the sender.
|
// Alice is the sender.
|
||||||
func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
|
func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
|
||||||
amt lnwire.MilliSatoshi, sid lnwire.ShortChannelID) {
|
amt lnwire.MilliSatoshi, sid lnwire.ShortChannelID, numHTLCs int) {
|
||||||
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
// The number of dust HTLC's we'll send for both Alice and Bob.
|
|
||||||
numHTLCs := 357
|
|
||||||
|
|
||||||
// Extract the destination into a variable. If alice is the sender, the
|
// Extract the destination into a variable. If alice is the sender, the
|
||||||
// destination is Bob.
|
// destination is Bob.
|
||||||
destLink := n.aliceChannelLink
|
destLink := n.aliceChannelLink
|
||||||
@@ -4527,8 +4571,8 @@ func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
// It may be the case that the dust threshold is hit
|
// It may be the case that the fee threshold is hit
|
||||||
// before all 357*2 HTLC's are sent due to double
|
// before all numHTLCs*2 HTLC's are sent due to double
|
||||||
// counting. Get around this by continuing to send
|
// counting. Get around this by continuing to send
|
||||||
// until successful.
|
// until successful.
|
||||||
err = sendingSwitch.SendHTLC(sid, attemptID, htlc)
|
err = sendingSwitch.SendHTLC(sid, attemptID, htlc)
|
||||||
@@ -4542,7 +4586,7 @@ func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestSwitchMailboxDust tests that the switch takes into account the mailbox
|
// TestSwitchMailboxDust tests that the switch takes into account the mailbox
|
||||||
// dust when evaluating the dust threshold. The mockChannelLink does not have
|
// dust when evaluating the fee threshold. The mockChannelLink does not have
|
||||||
// channel state, so this only tests the switch-mailbox interaction.
|
// channel state, so this only tests the switch-mailbox interaction.
|
||||||
func TestSwitchMailboxDust(t *testing.T) {
|
func TestSwitchMailboxDust(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -4612,7 +4656,7 @@ func TestSwitchMailboxDust(t *testing.T) {
|
|||||||
var carolHTLCID uint64
|
var carolHTLCID uint64
|
||||||
|
|
||||||
// It will take aliceCount HTLC's of 350sats to fill up Alice's mailbox
|
// It will take aliceCount HTLC's of 350sats to fill up Alice's mailbox
|
||||||
// to the point where another would put Alice over the dust threshold.
|
// to the point where another would put Alice over the fee threshold.
|
||||||
aliceCount := 1428
|
aliceCount := 1428
|
||||||
|
|
||||||
mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID1, aliceChanID)
|
mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID1, aliceChanID)
|
||||||
@@ -4634,10 +4678,10 @@ func TestSwitchMailboxDust(t *testing.T) {
|
|||||||
carolHTLCID++
|
carolHTLCID++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sending one more HTLC to Alice should result in the dust threshold
|
// Sending one more HTLC to Alice should result in the fee threshold
|
||||||
// being breached.
|
// being breached.
|
||||||
err = s.SendHTLC(aliceChanID, 0, addMsg)
|
err = s.SendHTLC(aliceChanID, 0, addMsg)
|
||||||
require.ErrorIs(t, err, errDustThresholdExceeded)
|
require.ErrorIs(t, err, errFeeExposureExceeded)
|
||||||
|
|
||||||
// We'll now call ForwardPackets from Bob to ensure that the mailbox
|
// We'll now call ForwardPackets from Bob to ensure that the mailbox
|
||||||
// sum is also accounted for in the forwarding case.
|
// sum is also accounted for in the forwarding case.
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ func testBidirectionalAsyncPayments(ht *lntest.HarnessTest) {
|
|||||||
args := []string{
|
args := []string{
|
||||||
// Increase the dust threshold to avoid the payments fail due
|
// Increase the dust threshold to avoid the payments fail due
|
||||||
// to threshold limit reached.
|
// to threshold limit reached.
|
||||||
"--dust-threshold=5000000",
|
"--dust-threshold=10000000",
|
||||||
|
|
||||||
// Increase the pending commit interval since there are lots of
|
// Increase the pending commit interval since there are lots of
|
||||||
// commitment dances.
|
// commitment dances.
|
||||||
|
|||||||
+85
-7
@@ -26,6 +26,7 @@ import (
|
|||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/keychain"
|
"github.com/lightningnetwork/lnd/keychain"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
@@ -2989,7 +2990,7 @@ func (lc *LightningChannel) fetchCommitmentView(remoteChain bool,
|
|||||||
// initiator.
|
// initiator.
|
||||||
htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex)
|
htlcView := lc.fetchHTLCView(theirLogIndex, ourLogIndex)
|
||||||
ourBalance, theirBalance, _, filteredHTLCView, err := lc.computeView(
|
ourBalance, theirBalance, _, filteredHTLCView, err := lc.computeView(
|
||||||
htlcView, remoteChain, true,
|
htlcView, remoteChain, true, fn.None[chainfee.SatPerKWeight](),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -3949,7 +3950,7 @@ func (lc *LightningChannel) validateCommitmentSanity(theirLogCounter,
|
|||||||
}
|
}
|
||||||
|
|
||||||
ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView(
|
ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView(
|
||||||
view, remoteChain, false,
|
view, remoteChain, false, fn.None[chainfee.SatPerKWeight](),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -4711,13 +4712,15 @@ func (lc *LightningChannel) ProcessChanSyncMsg(
|
|||||||
// view (settling unsettled HTLCs), commitment weight and feePerKw, after
|
// view (settling unsettled HTLCs), commitment weight and feePerKw, after
|
||||||
// applying the HTLCs to the latest commitment. The returned balances are the
|
// applying the HTLCs to the latest commitment. The returned balances are the
|
||||||
// balances *before* subtracting the commitment fee from the initiator's
|
// balances *before* subtracting the commitment fee from the initiator's
|
||||||
// balance.
|
// balance. It accepts a "dry run" feerate argument to calculate a potential
|
||||||
|
// commitment transaction fee.
|
||||||
//
|
//
|
||||||
// If the updateState boolean is set true, the add and remove heights of the
|
// If the updateState boolean is set true, the add and remove heights of the
|
||||||
// HTLCs will be set to the next commitment height.
|
// HTLCs will be set to the next commitment height.
|
||||||
func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
||||||
updateState bool) (lnwire.MilliSatoshi, lnwire.MilliSatoshi,
|
updateState bool, dryRunFee fn.Option[chainfee.SatPerKWeight]) (
|
||||||
lntypes.WeightUnit, *htlcView, error) {
|
lnwire.MilliSatoshi, lnwire.MilliSatoshi, lntypes.WeightUnit,
|
||||||
|
*htlcView, error) {
|
||||||
|
|
||||||
commitChain := lc.localCommitChain
|
commitChain := lc.localCommitChain
|
||||||
dustLimit := lc.channelState.LocalChanCfg.DustLimit
|
dustLimit := lc.channelState.LocalChanCfg.DustLimit
|
||||||
@@ -4763,6 +4766,12 @@ func (lc *LightningChannel) computeView(view *htlcView, remoteChain bool,
|
|||||||
}
|
}
|
||||||
feePerKw := filteredHTLCView.feePerKw
|
feePerKw := filteredHTLCView.feePerKw
|
||||||
|
|
||||||
|
// Here we override the view's fee-rate if a dry-run fee-rate was
|
||||||
|
// passed in.
|
||||||
|
if !updateState {
|
||||||
|
feePerKw = dryRunFee.UnwrapOr(feePerKw)
|
||||||
|
}
|
||||||
|
|
||||||
// We need to first check ourBalance and theirBalance to be negative
|
// We need to first check ourBalance and theirBalance to be negative
|
||||||
// because MilliSathoshi is a unsigned type and can underflow in
|
// because MilliSathoshi is a unsigned type and can underflow in
|
||||||
// `evaluateHTLCView`. This should never happen for views which do not
|
// `evaluateHTLCView`. This should never happen for views which do not
|
||||||
@@ -5954,7 +5963,9 @@ func (lc *LightningChannel) addHTLC(htlc *lnwire.UpdateAddHTLC,
|
|||||||
// commitment tx.
|
// commitment tx.
|
||||||
//
|
//
|
||||||
// NOTE: This over-estimates the dust exposure.
|
// NOTE: This over-estimates the dust exposure.
|
||||||
func (lc *LightningChannel) GetDustSum(remote bool) lnwire.MilliSatoshi {
|
func (lc *LightningChannel) GetDustSum(remote bool,
|
||||||
|
dryRunFee fn.Option[chainfee.SatPerKWeight]) lnwire.MilliSatoshi {
|
||||||
|
|
||||||
lc.RLock()
|
lc.RLock()
|
||||||
defer lc.RUnlock()
|
defer lc.RUnlock()
|
||||||
|
|
||||||
@@ -5971,6 +5982,9 @@ func (lc *LightningChannel) GetDustSum(remote bool) lnwire.MilliSatoshi {
|
|||||||
chanType := lc.channelState.ChanType
|
chanType := lc.channelState.ChanType
|
||||||
feeRate := chainfee.SatPerKWeight(commit.FeePerKw)
|
feeRate := chainfee.SatPerKWeight(commit.FeePerKw)
|
||||||
|
|
||||||
|
// Optionally use the dry-run fee-rate.
|
||||||
|
feeRate = dryRunFee.UnwrapOr(feeRate)
|
||||||
|
|
||||||
// Grab all of our HTLCs and evaluate against the dust limit.
|
// Grab all of our HTLCs and evaluate against the dust limit.
|
||||||
for e := lc.localUpdateLog.Front(); e != nil; e = e.Next() {
|
for e := lc.localUpdateLog.Front(); e != nil; e = e.Next() {
|
||||||
pd := e.Value.(*PaymentDescriptor)
|
pd := e.Value.(*PaymentDescriptor)
|
||||||
@@ -8256,7 +8270,7 @@ func (lc *LightningChannel) availableCommitmentBalance(
|
|||||||
// into account HTLCs to determine the commit weight, which the
|
// into account HTLCs to determine the commit weight, which the
|
||||||
// initiator must pay the fee for.
|
// initiator must pay the fee for.
|
||||||
ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView(
|
ourBalance, theirBalance, commitWeight, filteredView, err := lc.computeView(
|
||||||
view, remoteChain, false,
|
view, remoteChain, false, fn.None[chainfee.SatPerKWeight](),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lc.log.Errorf("Unable to fetch available balance: %v", err)
|
lc.log.Errorf("Unable to fetch available balance: %v", err)
|
||||||
@@ -8456,6 +8470,54 @@ func (lc *LightningChannel) UpdateFee(feePerKw chainfee.SatPerKWeight) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CommitFeeTotalAt applies a proposed feerate to the channel and returns the
|
||||||
|
// commitment fee with this new feerate. It does not modify the underlying
|
||||||
|
// LightningChannel.
|
||||||
|
func (lc *LightningChannel) CommitFeeTotalAt(
|
||||||
|
feePerKw chainfee.SatPerKWeight) (btcutil.Amount, btcutil.Amount,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
lc.RLock()
|
||||||
|
defer lc.RUnlock()
|
||||||
|
|
||||||
|
dryRunFee := fn.Some[chainfee.SatPerKWeight](feePerKw)
|
||||||
|
|
||||||
|
// We want to grab every update in both update logs to calculate the
|
||||||
|
// commitment fees in the worst-case with this fee-rate.
|
||||||
|
localIdx := lc.localUpdateLog.logIndex
|
||||||
|
remoteIdx := lc.remoteUpdateLog.logIndex
|
||||||
|
|
||||||
|
localHtlcView := lc.fetchHTLCView(remoteIdx, localIdx)
|
||||||
|
|
||||||
|
var localCommitFee, remoteCommitFee btcutil.Amount
|
||||||
|
|
||||||
|
// Compute the local commitment's weight.
|
||||||
|
_, _, localWeight, _, err := lc.computeView(
|
||||||
|
localHtlcView, false, false, dryRunFee,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localCommitFee = feePerKw.FeeForWeight(localWeight)
|
||||||
|
|
||||||
|
// Create another view in case for some reason the prior one was
|
||||||
|
// mutated.
|
||||||
|
remoteHtlcView := lc.fetchHTLCView(remoteIdx, localIdx)
|
||||||
|
|
||||||
|
// Compute the remote commitment's weight.
|
||||||
|
_, _, remoteWeight, _, err := lc.computeView(
|
||||||
|
remoteHtlcView, true, false, dryRunFee,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteCommitFee = feePerKw.FeeForWeight(remoteWeight)
|
||||||
|
|
||||||
|
return localCommitFee, remoteCommitFee, err
|
||||||
|
}
|
||||||
|
|
||||||
// ReceiveUpdateFee handles an updated fee sent from remote. This method will
|
// ReceiveUpdateFee handles an updated fee sent from remote. This method will
|
||||||
// return an error if called as channel initiator.
|
// return an error if called as channel initiator.
|
||||||
func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) error {
|
func (lc *LightningChannel) ReceiveUpdateFee(feePerKw chainfee.SatPerKWeight) error {
|
||||||
@@ -8810,6 +8872,22 @@ func (lc *LightningChannel) CommitFeeRate() chainfee.SatPerKWeight {
|
|||||||
return chainfee.SatPerKWeight(lc.channelState.LocalCommitment.FeePerKw)
|
return chainfee.SatPerKWeight(lc.channelState.LocalCommitment.FeePerKw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WorstCaseFeeRate returns the higher feerate from either the local commitment
|
||||||
|
// or the remote commitment.
|
||||||
|
func (lc *LightningChannel) WorstCaseFeeRate() chainfee.SatPerKWeight {
|
||||||
|
lc.RLock()
|
||||||
|
defer lc.RUnlock()
|
||||||
|
|
||||||
|
localFeeRate := lc.channelState.LocalCommitment.FeePerKw
|
||||||
|
remoteFeeRate := lc.channelState.RemoteCommitment.FeePerKw
|
||||||
|
|
||||||
|
if localFeeRate > remoteFeeRate {
|
||||||
|
return chainfee.SatPerKWeight(localFeeRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chainfee.SatPerKWeight(remoteFeeRate)
|
||||||
|
}
|
||||||
|
|
||||||
// IsPending returns true if the channel's funding transaction has been fully
|
// IsPending returns true if the channel's funding transaction has been fully
|
||||||
// confirmed, and false otherwise.
|
// confirmed, and false otherwise.
|
||||||
func (lc *LightningChannel) IsPending() bool {
|
func (lc *LightningChannel) IsPending() bool {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import (
|
|||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
"github.com/lightningnetwork/lnd/chainntnfs"
|
"github.com/lightningnetwork/lnd/chainntnfs"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
|
"github.com/lightningnetwork/lnd/fn"
|
||||||
"github.com/lightningnetwork/lnd/input"
|
"github.com/lightningnetwork/lnd/input"
|
||||||
"github.com/lightningnetwork/lnd/lntypes"
|
"github.com/lightningnetwork/lnd/lntypes"
|
||||||
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
|
||||||
@@ -9750,9 +9751,13 @@ func testGetDustSum(t *testing.T, chantype channeldb.ChannelType) {
|
|||||||
checkDust := func(c *LightningChannel, expLocal,
|
checkDust := func(c *LightningChannel, expLocal,
|
||||||
expRemote lnwire.MilliSatoshi) {
|
expRemote lnwire.MilliSatoshi) {
|
||||||
|
|
||||||
localDustSum := c.GetDustSum(false)
|
localDustSum := c.GetDustSum(
|
||||||
|
false, fn.None[chainfee.SatPerKWeight](),
|
||||||
|
)
|
||||||
require.Equal(t, expLocal, localDustSum)
|
require.Equal(t, expLocal, localDustSum)
|
||||||
remoteDustSum := c.GetDustSum(true)
|
remoteDustSum := c.GetDustSum(
|
||||||
|
true, fn.None[chainfee.SatPerKWeight](),
|
||||||
|
)
|
||||||
require.Equal(t, expRemote, remoteDustSum)
|
require.Equal(t, expRemote, remoteDustSum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -381,6 +381,10 @@ type Config struct {
|
|||||||
// invalid.
|
// invalid.
|
||||||
DisallowRouteBlinding bool
|
DisallowRouteBlinding bool
|
||||||
|
|
||||||
|
// MaxFeeExposure limits the number of outstanding fees in a channel.
|
||||||
|
// This value will be passed to created links.
|
||||||
|
MaxFeeExposure lnwire.MilliSatoshi
|
||||||
|
|
||||||
// Quit is the server's quit channel. If this is closed, we halt operation.
|
// Quit is the server's quit channel. If this is closed, we halt operation.
|
||||||
Quit chan struct{}
|
Quit chan struct{}
|
||||||
}
|
}
|
||||||
@@ -1194,6 +1198,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
|
|||||||
GetAliases: p.cfg.GetAliases,
|
GetAliases: p.cfg.GetAliases,
|
||||||
PreviouslySentShutdown: shutdownMsg,
|
PreviouslySentShutdown: shutdownMsg,
|
||||||
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
|
DisallowRouteBlinding: p.cfg.DisallowRouteBlinding,
|
||||||
|
MaxFeeExposure: p.cfg.MaxFeeExposure,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Before adding our new link, purge the switch of any pending or live
|
// Before adding our new link, purge the switch of any pending or live
|
||||||
|
|||||||
@@ -636,7 +636,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
|
|
||||||
s.htlcNotifier = htlcswitch.NewHtlcNotifier(time.Now)
|
s.htlcNotifier = htlcswitch.NewHtlcNotifier(time.Now)
|
||||||
|
|
||||||
thresholdSats := btcutil.Amount(cfg.DustThreshold)
|
thresholdSats := btcutil.Amount(cfg.MaxFeeExposure)
|
||||||
thresholdMSats := lnwire.NewMSatFromSatoshis(thresholdSats)
|
thresholdMSats := lnwire.NewMSatFromSatoshis(thresholdSats)
|
||||||
|
|
||||||
s.aliasMgr, err = aliasmgr.NewManager(dbs.ChanStateDB)
|
s.aliasMgr, err = aliasmgr.NewManager(dbs.ChanStateDB)
|
||||||
@@ -676,7 +676,7 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
|
|||||||
RejectHTLC: cfg.RejectHTLC,
|
RejectHTLC: cfg.RejectHTLC,
|
||||||
Clock: clock.NewDefaultClock(),
|
Clock: clock.NewDefaultClock(),
|
||||||
MailboxDeliveryTimeout: cfg.Htlcswitch.MailboxDeliveryTimeout,
|
MailboxDeliveryTimeout: cfg.Htlcswitch.MailboxDeliveryTimeout,
|
||||||
DustThreshold: thresholdMSats,
|
MaxFeeExposure: thresholdMSats,
|
||||||
SignAliasUpdate: s.signAliasUpdate,
|
SignAliasUpdate: s.signAliasUpdate,
|
||||||
IsAlias: aliasmgr.IsAlias,
|
IsAlias: aliasmgr.IsAlias,
|
||||||
}, uint32(currentHeight))
|
}, uint32(currentHeight))
|
||||||
@@ -3861,6 +3861,9 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||||||
towerClient = s.towerClientMgr
|
towerClient = s.towerClientMgr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
thresholdSats := btcutil.Amount(s.cfg.MaxFeeExposure)
|
||||||
|
thresholdMSats := lnwire.NewMSatFromSatoshis(thresholdSats)
|
||||||
|
|
||||||
// Now that we've established a connection, create a peer, and it to the
|
// Now that we've established a connection, create a peer, and it to the
|
||||||
// set of currently active peers. Configure the peer with the incoming
|
// set of currently active peers. Configure the peer with the incoming
|
||||||
// and outgoing broadcast deltas to prevent htlcs from being accepted or
|
// and outgoing broadcast deltas to prevent htlcs from being accepted or
|
||||||
@@ -3930,6 +3933,7 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
|
|||||||
RequestAlias: s.aliasMgr.RequestAlias,
|
RequestAlias: s.aliasMgr.RequestAlias,
|
||||||
AddLocalAlias: s.aliasMgr.AddLocalAlias,
|
AddLocalAlias: s.aliasMgr.AddLocalAlias,
|
||||||
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
|
DisallowRouteBlinding: s.cfg.ProtocolOptions.NoRouteBlinding(),
|
||||||
|
MaxFeeExposure: thresholdMSats,
|
||||||
Quit: s.quit,
|
Quit: s.quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user