multi: cap anchors feerate at configurable maximum

This commit caps the update fee the initiator will send when the anchors
channel type is used. We do not limit anything on the receiver side.

10 sat/vbyte is the current default max fee rate we use. This should be
enough to ensure propagation before anchoring down the commitment
transaction.
This commit is contained in:
Johan T. Halseth 2020-12-10 14:16:53 +01:00
parent d289a6ff78
commit 0fd76e53b8
No known key found for this signature in database
GPG Key ID: 15BAADA29DA20D26
9 changed files with 106 additions and 27 deletions

@ -33,6 +33,7 @@ import (
"github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/tor"
)
@ -289,6 +290,8 @@ type Config struct {
MaxChannelFeeAllocation float64 `long:"max-channel-fee-allocation" description:"The maximum percentage of total funds that can be allocated to a channel's commitment fee. This only applies for the initiator of the channel. Valid values are within [0.1, 1]."`
MaxCommitFeeRateAnchors uint64 `long:"max-commit-fee-rate-anchors" description:"The maximum fee rate in sat/vbyte that will be used for commitments of channels of the anchors type. Must be large enough to ensure transaction propagation"`
DryRunMigration bool `long:"dry-run-migration" description:"If true, lnd will abort committing a migration if it would otherwise have been successful. This leaves the database unmodified, and still compatible with the previously active version of lnd."`
net tor.Net
@ -475,6 +478,7 @@ func DefaultConfig() Config {
},
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
LogWriter: build.NewRotatingLogWriter(),
DB: lncfg.DefaultDB(),
registeredChains: chainreg.NewChainRegistry(),
@ -735,6 +739,12 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) {
cfg.MaxChannelFeeAllocation)
}
if cfg.MaxCommitFeeRateAnchors < 1 {
return nil, fmt.Errorf("invalid max commit fee rate anchors: "+
"%v, must be at least 1 sat/vbyte",
cfg.MaxCommitFeeRateAnchors)
}
// Validate the Tor config parameters.
socks, err := lncfg.ParseAddressString(
cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),

@ -366,6 +366,10 @@ type fundingConfig struct {
// RegisteredChains keeps track of all chains that have been registered
// with the daemon.
RegisteredChains *chainreg.ChainRegistry
// MaxAnchorsCommitFeeRate is the max commitment fee rate we'll use as
// the initiator for channels of the anchor type.
MaxAnchorsCommitFeeRate chainfee.SatPerKWeight
}
// fundingManager acts as an orchestrator/bridge between the wallet's
@ -3122,16 +3126,6 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
msg.pushAmt, msg.chainHash, peerKey.SerializeCompressed(),
ourDustLimit, msg.minConfs)
// First, we'll query the fee estimator for a fee that should get the
// commitment transaction confirmed by the next few blocks (conf target
// of 3). We target the near blocks here to ensure that we'll be able
// to execute a timely unilateral channel closure if needed.
commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3)
if err != nil {
msg.err <- err
return
}
// We set the channel flags to indicate whether we want this channel to
// be announced to the network.
var channelFlags lnwire.FundingFlag
@ -3190,6 +3184,25 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
commitType := commitmentType(
msg.peer.LocalFeatures(), msg.peer.RemoteFeatures(),
)
// First, we'll query the fee estimator for a fee that should get the
// commitment transaction confirmed by the next few blocks (conf target
// of 3). We target the near blocks here to ensure that we'll be able
// to execute a timely unilateral channel closure if needed.
commitFeePerKw, err := f.cfg.FeeEstimator.EstimateFeePerKW(3)
if err != nil {
msg.err <- err
return
}
// For anchor channels cap the initial commit fee rate at our defined
// maximum.
if commitType == lnwallet.CommitmentTypeAnchorsZeroFeeHtlcTx &&
commitFeePerKw > f.cfg.MaxAnchorsCommitFeeRate {
commitFeePerKw = f.cfg.MaxAnchorsCommitFeeRate
}
req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.chainHash,
PendingChanID: chanID,

@ -269,6 +269,10 @@ type ChannelLinkConfig struct {
// initiator of the channel.
MaxFeeAllocation float64
// MaxAnchorsCommitFeeRate is the max commitment fee rate we'll use as
// the initiator for channels of the anchor type.
MaxAnchorsCommitFeeRate chainfee.SatPerKWeight
// NotifyActiveLink allows the link to tell the ChannelNotifier when a
// link is first started.
NotifyActiveLink func(wire.OutPoint)
@ -1090,7 +1094,10 @@ func (l *channelLink) htlcManager() {
// based on our current set fee rate. We'll cap the new
// fee rate to our max fee allocation.
commitFee := l.channel.CommitFeeRate()
maxFee := l.channel.MaxFeeRate(l.cfg.MaxFeeAllocation)
maxFee := l.channel.MaxFeeRate(
l.cfg.MaxFeeAllocation,
l.cfg.MaxAnchorsCommitFeeRate,
)
newCommitFee := chainfee.SatPerKWeight(
math.Min(float64(netFee), float64(maxFee)),
)

@ -1185,6 +1185,7 @@ func (h *hopNetwork) createChannelLink(server, peer *mockServer,
OutgoingCltvRejectDelta: 3,
MaxOutgoingCltvExpiry: DefaultMaxOutgoingCltvExpiry,
MaxFeeAllocation: DefaultMaxLinkFeeAllocation,
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(10 * 1000).FeePerKWeight(),
NotifyActiveLink: func(wire.OutPoint) {},
NotifyActiveChannel: func(wire.OutPoint) {},
NotifyInactiveChannel: func(wire.OutPoint) {},

@ -6809,11 +6809,14 @@ func (lc *LightningChannel) CalcFee(feeRate chainfee.SatPerKWeight) btcutil.Amou
// MaxFeeRate returns the maximum fee rate given an allocation of the channel
// initiator's spendable balance. This can be useful to determine when we should
// stop proposing fee updates that exceed our maximum allocation.
// stop proposing fee updates that exceed our maximum allocation. We also take
// a fee rate cap that should be used for anchor type channels.
//
// NOTE: This should only be used for channels in which the local commitment is
// the initiator.
func (lc *LightningChannel) MaxFeeRate(maxAllocation float64) chainfee.SatPerKWeight {
func (lc *LightningChannel) MaxFeeRate(maxAllocation float64,
maxAnchorFeeRate chainfee.SatPerKWeight) chainfee.SatPerKWeight {
lc.RLock()
defer lc.RUnlock()
@ -6828,9 +6831,16 @@ func (lc *LightningChannel) MaxFeeRate(maxAllocation float64) chainfee.SatPerKWe
// Ensure the fee rate doesn't dip below the fee floor.
_, weight := lc.availableBalance()
maxFeeRate := maxFee / (float64(weight) / 1000)
return chainfee.SatPerKWeight(
feeRate := chainfee.SatPerKWeight(
math.Max(maxFeeRate, float64(chainfee.FeePerKwFloor)),
)
// Cap anchor fee rates.
if lc.channelState.ChanType.HasAnchors() && feeRate > maxAnchorFeeRate {
return maxAnchorFeeRate
}
return feeRate
}
// RemoteNextRevocation returns the channelState's RemoteNextRevocation.

@ -7996,6 +7996,19 @@ func TestForceCloseBorkedState(t *testing.T) {
func TestChannelMaxFeeRate(t *testing.T) {
t.Parallel()
assertMaxFeeRate := func(c *LightningChannel,
maxAlloc float64, anchorMax, expFeeRate chainfee.SatPerKWeight) {
t.Helper()
maxFeeRate := c.MaxFeeRate(maxAlloc, anchorMax)
if maxFeeRate != expFeeRate {
t.Fatalf("expected max fee rate of %v with max "+
"allocation of %v, got %v", expFeeRate,
maxAlloc, maxFeeRate)
}
}
aliceChannel, _, cleanUp, err := CreateTestChannels(
channeldb.SingleFunderTweaklessBit,
)
@ -8004,21 +8017,32 @@ func TestChannelMaxFeeRate(t *testing.T) {
}
defer cleanUp()
assertMaxFeeRate := func(maxAlloc float64,
expFeeRate chainfee.SatPerKWeight) {
assertMaxFeeRate(aliceChannel, 1.0, 0, 690607734)
assertMaxFeeRate(aliceChannel, 0.001, 0, 690607)
assertMaxFeeRate(aliceChannel, 0.000001, 0, 690)
assertMaxFeeRate(aliceChannel, 0.0000001, 0, chainfee.FeePerKwFloor)
maxFeeRate := aliceChannel.MaxFeeRate(maxAlloc)
if maxFeeRate != expFeeRate {
t.Fatalf("expected max fee rate of %v with max "+
"allocation of %v, got %v", expFeeRate,
maxAlloc, maxFeeRate)
}
// Check that anchor channels are capped at their max fee rate.
anchorChannel, _, cleanUp, err := CreateTestChannels(
channeldb.SingleFunderTweaklessBit | channeldb.AnchorOutputsBit,
)
if err != nil {
t.Fatalf("unable to create test channels: %v", err)
}
defer cleanUp()
assertMaxFeeRate(1.0, 690607734)
assertMaxFeeRate(0.001, 690607)
assertMaxFeeRate(0.000001, 690)
assertMaxFeeRate(0.0000001, chainfee.FeePerKwFloor)
// Anchor commitments are heavier, hence will the same allocation lead
// to slightly lower fee rates.
assertMaxFeeRate(
anchorChannel, 1.0, chainfee.FeePerKwFloor,
chainfee.FeePerKwFloor,
)
assertMaxFeeRate(anchorChannel, 0.001, 1000000, 444839)
assertMaxFeeRate(anchorChannel, 0.001, 300000, 300000)
assertMaxFeeRate(anchorChannel, 0.000001, 700, 444)
assertMaxFeeRate(
anchorChannel, 0.0000001, 1000000, chainfee.FeePerKwFloor,
)
}
// TestChannelFeeRateFloor asserts that valid commitments can be proposed and

@ -17,6 +17,11 @@ import (
// anchorSize is the constant anchor output size.
const anchorSize = btcutil.Amount(330)
// DefaultAnchorsCommitMaxFeeRateSatPerVByte is the default max fee rate in
// sat/vbyte the initiator will use for anchor channels. This should be enough
// to ensure propagation before anchoring down the commitment transaction.
const DefaultAnchorsCommitMaxFeeRateSatPerVByte = 10
// CommitmentKeyRing holds all derived keys needed to construct commitment and
// HTLC transactions. The keys are derived differently depending whether the
// commitment transaction is ours or the remote peer's. Private keys associated

@ -294,6 +294,10 @@ type Config struct {
// commitment fee. This only applies for the initiator of the channel.
MaxChannelFeeAllocation float64
// MaxAnchorsCommitFeeRate is the maximum fee rate we'll use as an
// initiator for anchor channel commitments.
MaxAnchorsCommitFeeRate chainfee.SatPerKWeight
// ServerPubKey is the serialized, compressed public key of our lnd node.
// It is used to determine which policy (channel edge) to pass to the
// ChannelLink.
@ -815,6 +819,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
TowerClient: towerClient,
MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry,
MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation,
MaxAnchorsCommitFeeRate: p.cfg.MaxAnchorsCommitFeeRate,
NotifyActiveLink: p.cfg.ChannelNotifier.NotifyActiveLinkEvent,
NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent,
NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent,

@ -1185,6 +1185,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
NotifyPendingOpenChannelEvent: s.channelNotifier.NotifyPendingOpenChannelEvent,
EnableUpfrontShutdown: cfg.EnableUpfrontShutdown,
RegisteredChains: cfg.registeredChains,
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
})
if err != nil {
return nil, err
@ -3119,7 +3121,9 @@ func (s *server) peerConnected(conn net.Conn, connReq *connmgr.ConnReq,
UnsafeReplay: s.cfg.UnsafeReplay,
MaxOutgoingCltvExpiry: s.cfg.MaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: s.cfg.MaxChannelFeeAllocation,
Quit: s.quit,
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
Quit: s.quit,
}
copy(pCfg.PubKeyBytes[:], peerAddr.IdentityKey.SerializeCompressed())