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:
parent
d289a6ff78
commit
0fd76e53b8
10
config.go
10
config.go
@ -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) {},
|
||||||
|
@ -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,
|
||||||
|
@ -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,6 +3121,8 @@ 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,
|
||||||
|
MaxAnchorsCommitFeeRate: chainfee.SatPerKVByte(
|
||||||
|
s.cfg.MaxCommitFeeRateAnchors * 1000).FeePerKWeight(),
|
||||||
Quit: s.quit,
|
Quit: s.quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user