Merge pull request #4855 from halseth/anchors-clamp-max-feerate

multi: cap anchors feerate at configurable maximum
This commit is contained in:
Olaoluwa Osuntokun 2020-12-15 14:25:15 -08:00 committed by GitHub
commit 63055eecd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 116 additions and 28 deletions

@ -33,6 +33,7 @@ import (
"github.com/lightningnetwork/lnd/lncfg" "github.com/lightningnetwork/lnd/lncfg"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/signrpc" "github.com/lightningnetwork/lnd/lnrpc/signrpc"
"github.com/lightningnetwork/lnd/lnwallet"
"github.com/lightningnetwork/lnd/routing" "github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/tor" "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]."` 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."` 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 net tor.Net
@ -475,6 +478,7 @@ func DefaultConfig() Config {
}, },
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry, MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation, MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,
LogWriter: build.NewRotatingLogWriter(), LogWriter: build.NewRotatingLogWriter(),
DB: lncfg.DefaultDB(), DB: lncfg.DefaultDB(),
registeredChains: chainreg.NewChainRegistry(), registeredChains: chainreg.NewChainRegistry(),
@ -735,6 +739,12 @@ func ValidateConfig(cfg Config, usageMessage string) (*Config, error) {
cfg.MaxChannelFeeAllocation) 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. // Validate the Tor config parameters.
socks, err := lncfg.ParseAddressString( socks, err := lncfg.ParseAddressString(
cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort), cfg.Tor.SOCKS, strconv.Itoa(defaultTorSOCKSPort),

@ -366,6 +366,10 @@ type fundingConfig struct {
// RegisteredChains keeps track of all chains that have been registered // RegisteredChains keeps track of all chains that have been registered
// with the daemon. // with the daemon.
RegisteredChains *chainreg.ChainRegistry 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 // 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(), msg.pushAmt, msg.chainHash, peerKey.SerializeCompressed(),
ourDustLimit, msg.minConfs) 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 // We set the channel flags to indicate whether we want this channel to
// be announced to the network. // be announced to the network.
var channelFlags lnwire.FundingFlag var channelFlags lnwire.FundingFlag
@ -3190,6 +3184,25 @@ func (f *fundingManager) handleInitFundingMsg(msg *initFundingMsg) {
commitType := commitmentType( commitType := commitmentType(
msg.peer.LocalFeatures(), msg.peer.RemoteFeatures(), 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{ req := &lnwallet.InitFundingReserveMsg{
ChainHash: &msg.chainHash, ChainHash: &msg.chainHash,
PendingChanID: chanID, PendingChanID: chanID,

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

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

@ -1166,8 +1166,12 @@ func (c commitType) calcStaticFee(numHTLCs int) btcutil.Amount {
// The anchor commitment type is slightly heavier, and we must also add // The anchor commitment type is slightly heavier, and we must also add
// the value of the two anchors to the resulting fee the initiator // the value of the two anchors to the resulting fee the initiator
// pays. // pays. In addition the fee rate is capped at 10 sat/vbyte for anchor
// channels.
if c == commitTypeAnchors { if c == commitTypeAnchors {
feePerKw = chainfee.SatPerKVByte(
lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000,
).FeePerKWeight()
commitWeight = input.AnchorCommitWeight commitWeight = input.AnchorCommitWeight
anchors = 2 * anchorSize anchors = 2 * anchorSize
} }

@ -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 // 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 // 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 // NOTE: This should only be used for channels in which the local commitment is
// the initiator. // the initiator.
func (lc *LightningChannel) MaxFeeRate(maxAllocation float64) chainfee.SatPerKWeight { func (lc *LightningChannel) MaxFeeRate(maxAllocation float64,
maxAnchorFeeRate chainfee.SatPerKWeight) chainfee.SatPerKWeight {
lc.RLock() lc.RLock()
defer lc.RUnlock() 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. // Ensure the fee rate doesn't dip below the fee floor.
_, weight := lc.availableBalance() _, weight := lc.availableBalance()
maxFeeRate := maxFee / (float64(weight) / 1000) maxFeeRate := maxFee / (float64(weight) / 1000)
return chainfee.SatPerKWeight( feeRate := chainfee.SatPerKWeight(
math.Max(maxFeeRate, float64(chainfee.FeePerKwFloor)), 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. // RemoteNextRevocation returns the channelState's RemoteNextRevocation.

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

@ -17,6 +17,11 @@ import (
// anchorSize is the constant anchor output size. // anchorSize is the constant anchor output size.
const anchorSize = btcutil.Amount(330) 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 // CommitmentKeyRing holds all derived keys needed to construct commitment and
// HTLC transactions. The keys are derived differently depending whether the // HTLC transactions. The keys are derived differently depending whether the
// commitment transaction is ours or the remote peer's. Private keys associated // 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. // commitment fee. This only applies for the initiator of the channel.
MaxChannelFeeAllocation float64 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. // ServerPubKey is the serialized, compressed public key of our lnd node.
// It is used to determine which policy (channel edge) to pass to the // It is used to determine which policy (channel edge) to pass to the
// ChannelLink. // ChannelLink.
@ -815,6 +819,7 @@ func (p *Brontide) addLink(chanPoint *wire.OutPoint,
TowerClient: towerClient, TowerClient: towerClient,
MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry, MaxOutgoingCltvExpiry: p.cfg.MaxOutgoingCltvExpiry,
MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation, MaxFeeAllocation: p.cfg.MaxChannelFeeAllocation,
MaxAnchorsCommitFeeRate: p.cfg.MaxAnchorsCommitFeeRate,
NotifyActiveLink: p.cfg.ChannelNotifier.NotifyActiveLinkEvent, NotifyActiveLink: p.cfg.ChannelNotifier.NotifyActiveLinkEvent,
NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent, NotifyActiveChannel: p.cfg.ChannelNotifier.NotifyActiveChannelEvent,
NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent, NotifyInactiveChannel: p.cfg.ChannelNotifier.NotifyInactiveChannelEvent,

@ -307,6 +307,11 @@
; values are within [0.1, 1]. (default: 0.5) ; values are within [0.1, 1]. (default: 0.5)
; max-channel-fee-allocation=0.9 ; max-channel-fee-allocation=0.9
; 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 (default: 10)
; max-commit-fee-rate-anchors=5
; If true, lnd will abort committing a migration if it would otherwise have been ; 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 ; successful. This leaves the database unmodified, and still compatible with the
; previously active version of lnd. ; previously active version of lnd.

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