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

View File

@ -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),

View File

@ -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,

View File

@ -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)),
)

View File

@ -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) {},

View File

@ -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 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 {
feePerKw = chainfee.SatPerKVByte(
lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte * 1000,
).FeePerKWeight()
commitWeight = input.AnchorCommitWeight
anchors = 2 * anchorSize
}

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -307,6 +307,11 @@
; values are within [0.1, 1]. (default: 0.5)
; 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
; successful. This leaves the database unmodified, and still compatible with the
; previously active version of lnd.

View File

@ -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())