Merge pull request #5068 from wpaulino/gossip-rate-limit-config

lncfg: expose channel update rate limiting options
This commit is contained in:
Conner Fromknecht 2021-03-17 09:58:53 -07:00 committed by GitHub
commit 482ca0ddc4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 8 deletions

@ -488,7 +488,10 @@ func DefaultConfig() Config {
Backoff: defaultTLSBackoff, Backoff: defaultTLSBackoff,
}, },
}, },
Gossip: &lncfg.Gossip{}, Gossip: &lncfg.Gossip{
MaxChannelUpdateBurst: discovery.DefaultMaxChannelUpdateBurst,
ChannelUpdateInterval: discovery.DefaultChannelUpdateInterval,
},
MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry, MaxOutgoingCltvExpiry: htlcswitch.DefaultMaxOutgoingCltvExpiry,
MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation, MaxChannelFeeAllocation: htlcswitch.DefaultMaxLinkFeeAllocation,
MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte, MaxCommitFeeRateAnchors: lnwallet.DefaultAnchorsCommitMaxFeeRateSatPerVByte,

@ -1,6 +1,8 @@
package lncfg package lncfg
import ( import (
"time"
"github.com/lightningnetwork/lnd/discovery" "github.com/lightningnetwork/lnd/discovery"
"github.com/lightningnetwork/lnd/routing/route" "github.com/lightningnetwork/lnd/routing/route"
) )
@ -9,6 +11,10 @@ type Gossip struct {
PinnedSyncersRaw []string `long:"pinned-syncers" description:"A set of peers that should always remain in an active sync state, which can be used to closely synchronize the routing tables of two nodes. The value should be comma separated list of hex-encoded pubkeys. Connected peers matching this pubkey will remain active for the duration of the connection and not count towards the NumActiveSyncer count."` PinnedSyncersRaw []string `long:"pinned-syncers" description:"A set of peers that should always remain in an active sync state, which can be used to closely synchronize the routing tables of two nodes. The value should be comma separated list of hex-encoded pubkeys. Connected peers matching this pubkey will remain active for the duration of the connection and not count towards the NumActiveSyncer count."`
PinnedSyncers discovery.PinnedSyncers PinnedSyncers discovery.PinnedSyncers
MaxChannelUpdateBurst int `long:"max-channel-update-burst" description:"The maximum number of updates for a specific channel and direction that lnd will accept over the channel update interval."`
ChannelUpdateInterval time.Duration `long:"channel-update-interval" description:"The interval used to determine how often lnd should allow a burst of new updates for a specific channel and direction."`
} }
// Parse the pubkeys for the pinned syncers. // Parse the pubkeys for the pinned syncers.

@ -1762,6 +1762,13 @@ out:
select { select {
case graphUpdate := <-subscription.updateChan: case graphUpdate := <-subscription.updateChan:
for _, update := range graphUpdate.ChannelUpdates { for _, update := range graphUpdate.ChannelUpdates {
if len(expUpdates) == 0 {
t.Fatalf("received unexpected channel "+
"update from %v for channel %v",
update.AdvertisingNode,
update.ChanId)
}
// For each expected update, check if it matches // For each expected update, check if it matches
// the update we just received. // the update we just received.
for i, exp := range expUpdates { for i, exp := range expUpdates {
@ -1811,6 +1818,9 @@ out:
case err := <-subscription.errChan: case err := <-subscription.errChan:
t.Fatalf("unable to recv graph update: %v", err) t.Fatalf("unable to recv graph update: %v", err)
case <-time.After(defaultTimeout): case <-time.After(defaultTimeout):
if len(expUpdates) == 0 {
return
}
t.Fatalf("did not receive channel update") t.Fatalf("did not receive channel update")
} }
} }
@ -2014,8 +2024,14 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
t.Fatalf("bob didn't report channel: %v", err) t.Fatalf("bob didn't report channel: %v", err)
} }
// Create Carol and a new channel Bob->Carol. // Create Carol with options to rate limit channel updates up to 2 per
carol, err := net.NewNode("Carol", nil) // day, and create a new channel Bob->Carol.
carol, err := net.NewNode(
"Carol", []string{
"--gossip.max-channel-update-burst=2",
"--gossip.channel-update-interval=24h",
},
)
if err != nil { if err != nil {
t.Fatalf("unable to create new nodes: %v", err) t.Fatalf("unable to create new nodes: %v", err)
} }
@ -2062,7 +2078,6 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
MinHtlc: customMinHtlc, MinHtlc: customMinHtlc,
MaxHtlcMsat: defaultMaxHtlc, MaxHtlcMsat: defaultMaxHtlc,
} }
expectedPolicyCarol := &lnrpc.RoutingPolicy{ expectedPolicyCarol := &lnrpc.RoutingPolicy{
FeeBaseMsat: defaultFeeBase, FeeBaseMsat: defaultFeeBase,
FeeRateMilliMsat: defaultFeeRate, FeeRateMilliMsat: defaultFeeRate,
@ -2386,6 +2401,57 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) {
) )
} }
// Now, to test that Carol is properly rate limiting incoming updates,
// we'll send two more update from Alice. Carol should accept the first,
// but not the second, as she only allows two updates per day and a day
// has yet to elapse from the previous update.
const numUpdatesTilRateLimit = 2
for i := 0; i < numUpdatesTilRateLimit; i++ {
prevAlicePolicy := *expectedPolicy
baseFee *= 2
expectedPolicy.FeeBaseMsat = baseFee
req.BaseFeeMsat = baseFee
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()
_, err = net.Alice.UpdateChannelPolicy(ctxt, req)
if err != nil {
t.Fatalf("unable to update alice's channel policy: %v", err)
}
// Wait for all nodes to have seen the policy updates for both
// of Alice's channels. Carol will not see the last update as
// the limit has been reached.
for idx, graphSub := range graphSubs {
expUpdates := []expectedChanUpdate{
{net.Alice.PubKeyStr, expectedPolicy, chanPoint},
{net.Alice.PubKeyStr, expectedPolicy, chanPoint3},
}
// Carol was added last, which is why we check the last
// index.
if i == numUpdatesTilRateLimit-1 && idx == len(graphSubs)-1 {
expUpdates = nil
}
waitForChannelUpdate(t, graphSub, expUpdates)
}
// And finally check that all nodes remembers the policy update
// they received. Since Carol didn't receive the last update,
// she still has Alice's old policy.
for idx, node := range nodes {
policy := expectedPolicy
// Carol was added last, which is why we check the last
// index.
if i == numUpdatesTilRateLimit-1 && idx == len(nodes)-1 {
policy = &prevAlicePolicy
}
assertChannelPolicy(
t, node, net.Alice.PubKeyStr, policy, chanPoint,
chanPoint3,
)
}
}
// Close the channels. // Close the channels.
ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout)
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false)
@ -12831,10 +12897,7 @@ func testSwitchOfflineDeliveryOutgoingOffline(
nodesMinusCarol := []*lntest.HarnessNode{net.Bob, net.Alice, dave} nodesMinusCarol := []*lntest.HarnessNode{net.Bob, net.Alice, dave}
err = wait.Predicate(func() bool { err = wait.Predicate(func() bool {
predErr = assertNumActiveHtlcs(nodesMinusCarol, 0) predErr = assertNumActiveHtlcs(nodesMinusCarol, 0)
if predErr != nil { return predErr == nil
return false
}
return true
}, defaultTimeout) }, defaultTimeout)
if err != nil { if err != nil {
t.Fatalf("htlc mismatch: %v", predErr) t.Fatalf("htlc mismatch: %v", predErr)

@ -1043,3 +1043,8 @@ litecoin.node=ltcd
; peers can be specified by setting multiple flags/fields in the config. ; peers can be specified by setting multiple flags/fields in the config.
; gossip.pinned-syncers=pubkey1 ; gossip.pinned-syncers=pubkey1
; gossip.pinned-syncers=pubkey2 ; gossip.pinned-syncers=pubkey2
; The maximum number of updates for a specific channel and direction that lnd
; will accept over the channel update interval.
; gossip.max-channel-update-burst=10
; gossip.channel-update-interval=1m

@ -823,6 +823,8 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
SubBatchDelay: time.Second * 5, SubBatchDelay: time.Second * 5,
IgnoreHistoricalFilters: cfg.IgnoreHistoricalGossipFilters, IgnoreHistoricalFilters: cfg.IgnoreHistoricalGossipFilters,
PinnedSyncers: cfg.Gossip.PinnedSyncers, PinnedSyncers: cfg.Gossip.PinnedSyncers,
MaxChannelUpdateBurst: cfg.Gossip.MaxChannelUpdateBurst,
ChannelUpdateInterval: cfg.Gossip.ChannelUpdateInterval,
}, },
nodeKeyECDH.PubKey(), nodeKeyECDH.PubKey(),
) )