Merge pull request #10103 from yyforyongyu/rate-limit-peer
Rate limit outgoing gossip bandwidth by peer
This commit is contained in:
11 files changed
+181
-67
No files matched your search
@@ -721,6 +721,7 @@ func DefaultConfig() Config {
|
|||||||
MsgBurstBytes: discovery.DefaultMsgBytesBurst,
|
MsgBurstBytes: discovery.DefaultMsgBytesBurst,
|
||||||
FilterConcurrency: discovery.DefaultFilterConcurrency,
|
FilterConcurrency: discovery.DefaultFilterConcurrency,
|
||||||
BanThreshold: discovery.DefaultBanThreshold,
|
BanThreshold: discovery.DefaultBanThreshold,
|
||||||
|
PeerMsgRateBytes: discovery.DefaultPeerMsgBytesPerSecond,
|
||||||
},
|
},
|
||||||
Invoices: &lncfg.Invoices{
|
Invoices: &lncfg.Invoices{
|
||||||
HoldExpiryDelta: lncfg.DefaultHoldInvoiceExpiryDelta,
|
HoldExpiryDelta: lncfg.DefaultHoldInvoiceExpiryDelta,
|
||||||
|
|||||||
@@ -407,6 +407,10 @@ type Config struct {
|
|||||||
// BanThreshold is the score used to decide whether a given peer is
|
// BanThreshold is the score used to decide whether a given peer is
|
||||||
// banned or not.
|
// banned or not.
|
||||||
BanThreshold uint64
|
BanThreshold uint64
|
||||||
|
|
||||||
|
// PeerMsgRateBytes is the rate limit for the number of bytes per second
|
||||||
|
// that we'll allocate to outbound gossip messages for a single peer.
|
||||||
|
PeerMsgRateBytes uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
|
// processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
|
||||||
@@ -609,6 +613,7 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper
|
|||||||
AllotedMsgBytesPerSecond: cfg.MsgRateBytes,
|
AllotedMsgBytesPerSecond: cfg.MsgRateBytes,
|
||||||
AllotedMsgBytesBurst: cfg.MsgBurstBytes,
|
AllotedMsgBytesBurst: cfg.MsgBurstBytes,
|
||||||
FilterConcurrency: cfg.FilterConcurrency,
|
FilterConcurrency: cfg.FilterConcurrency,
|
||||||
|
PeerMsgBytesPerSecond: cfg.PeerMsgRateBytes,
|
||||||
})
|
})
|
||||||
|
|
||||||
gossiper.reliableSender = newReliableSender(&reliableSenderCfg{
|
gossiper.reliableSender = newReliableSender(&reliableSenderCfg{
|
||||||
|
|||||||
+29
-19
@@ -40,6 +40,11 @@ const (
|
|||||||
// they'll be refilled at this rate.
|
// they'll be refilled at this rate.
|
||||||
DefaultMsgBytesPerSecond = 1000 * 1_024
|
DefaultMsgBytesPerSecond = 1000 * 1_024
|
||||||
|
|
||||||
|
// DefaultPeerMsgBytesPerSecond is the max bytes/s we'll permit for
|
||||||
|
// outgoing messages for a single peer. Once tokens (bytes) have been
|
||||||
|
// taken from the bucket, they'll be refilled at this rate.
|
||||||
|
DefaultPeerMsgBytesPerSecond = 50 * 1_024
|
||||||
|
|
||||||
// assumedMsgSize is the assumed size of a message if we can't compute
|
// assumedMsgSize is the assumed size of a message if we can't compute
|
||||||
// its serialized size. This comes out to 1 KB.
|
// its serialized size. This comes out to 1 KB.
|
||||||
assumedMsgSize = 1_024
|
assumedMsgSize = 1_024
|
||||||
@@ -141,6 +146,11 @@ type SyncManagerCfg struct {
|
|||||||
// FilterConcurrency is the maximum number of concurrent gossip filter
|
// FilterConcurrency is the maximum number of concurrent gossip filter
|
||||||
// applications that can be processed. If not set, defaults to 5.
|
// applications that can be processed. If not set, defaults to 5.
|
||||||
FilterConcurrency int
|
FilterConcurrency int
|
||||||
|
|
||||||
|
// PeerMsgBytesPerSecond is the allotted bandwidth rate, expressed in
|
||||||
|
// bytes/second that a single gossip syncer can consume. Once we exceed
|
||||||
|
// this rate, message sending will block until we're below the rate.
|
||||||
|
PeerMsgBytesPerSecond uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// SyncManager is a subsystem of the gossiper that manages the gossip syncers
|
// SyncManager is a subsystem of the gossiper that manages the gossip syncers
|
||||||
@@ -200,7 +210,7 @@ type SyncManager struct {
|
|||||||
gossipFilterSema chan struct{}
|
gossipFilterSema chan struct{}
|
||||||
|
|
||||||
// rateLimiter dictates the frequency with which we will reply to gossip
|
// rateLimiter dictates the frequency with which we will reply to gossip
|
||||||
// queries from a peer. This is used to delay responses to peers to
|
// queries from all peers. This is used to delay responses to peers to
|
||||||
// prevent DOS vulnerabilities if they are spamming with an unreasonable
|
// prevent DOS vulnerabilities if they are spamming with an unreasonable
|
||||||
// number of queries.
|
// number of queries.
|
||||||
rateLimiter *rate.Limiter
|
rateLimiter *rate.Limiter
|
||||||
@@ -554,8 +564,8 @@ func (m *SyncManager) isPinnedSyncer(s *GossipSyncer) bool {
|
|||||||
|
|
||||||
// deriveRateLimitReservation will take the current message and derive a
|
// deriveRateLimitReservation will take the current message and derive a
|
||||||
// reservation that can be used to wait on the rate limiter.
|
// reservation that can be used to wait on the rate limiter.
|
||||||
func (m *SyncManager) deriveRateLimitReservation(msg lnwire.Message,
|
func deriveRateLimitReservation(rl *rate.Limiter,
|
||||||
) (*rate.Reservation, error) {
|
msg lnwire.Message) (*rate.Reservation, error) {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
msgSize uint32
|
msgSize uint32
|
||||||
@@ -575,12 +585,12 @@ func (m *SyncManager) deriveRateLimitReservation(msg lnwire.Message,
|
|||||||
msgSize = assumedMsgSize
|
msgSize = assumedMsgSize
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.rateLimiter.ReserveN(time.Now(), int(msgSize)), nil
|
return rl.ReserveN(time.Now(), int(msgSize)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitMsgDelay takes a delay, and waits until it has finished.
|
// waitMsgDelay takes a delay, and waits until it has finished.
|
||||||
func (m *SyncManager) waitMsgDelay(ctx context.Context, peerPub [33]byte,
|
func waitMsgDelay(ctx context.Context, peerPub [33]byte,
|
||||||
limitReservation *rate.Reservation) error {
|
limitReservation *rate.Reservation, quit <-chan struct{}) error {
|
||||||
|
|
||||||
// If we've already replied a handful of times, we will start to delay
|
// If we've already replied a handful of times, we will start to delay
|
||||||
// responses back to the remote peer. This can help prevent DOS attacks
|
// responses back to the remote peer. This can help prevent DOS attacks
|
||||||
@@ -602,7 +612,7 @@ func (m *SyncManager) waitMsgDelay(ctx context.Context, peerPub [33]byte,
|
|||||||
|
|
||||||
return ErrGossipSyncerExiting
|
return ErrGossipSyncerExiting
|
||||||
|
|
||||||
case <-m.quit:
|
case <-quit:
|
||||||
limitReservation.Cancel()
|
limitReservation.Cancel()
|
||||||
|
|
||||||
return ErrGossipSyncerExiting
|
return ErrGossipSyncerExiting
|
||||||
@@ -614,15 +624,15 @@ func (m *SyncManager) waitMsgDelay(ctx context.Context, peerPub [33]byte,
|
|||||||
|
|
||||||
// maybeRateLimitMsg takes a message, and may wait a period of time to rate
|
// maybeRateLimitMsg takes a message, and may wait a period of time to rate
|
||||||
// limit the msg.
|
// limit the msg.
|
||||||
func (m *SyncManager) maybeRateLimitMsg(ctx context.Context, peerPub [33]byte,
|
func maybeRateLimitMsg(ctx context.Context, rl *rate.Limiter, peerPub [33]byte,
|
||||||
msg lnwire.Message) error {
|
msg lnwire.Message, quit <-chan struct{}) error {
|
||||||
|
|
||||||
delay, err := m.deriveRateLimitReservation(msg)
|
delay, err := deriveRateLimitReservation(rl, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.waitMsgDelay(ctx, peerPub, delay)
|
return waitMsgDelay(ctx, peerPub, delay, quit)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendMessages sends a set of messages to the remote peer.
|
// sendMessages sends a set of messages to the remote peer.
|
||||||
@@ -630,9 +640,13 @@ func (m *SyncManager) sendMessages(ctx context.Context, sync bool,
|
|||||||
peer lnpeer.Peer, nodeID route.Vertex, msgs ...lnwire.Message) error {
|
peer lnpeer.Peer, nodeID route.Vertex, msgs ...lnwire.Message) error {
|
||||||
|
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
if err := m.maybeRateLimitMsg(ctx, nodeID, msg); err != nil {
|
err := maybeRateLimitMsg(
|
||||||
|
ctx, m.rateLimiter, nodeID, msg, m.quit,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := peer.SendMessageLazy(sync, msg); err != nil {
|
if err := peer.SendMessageLazy(sync, msg); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -654,15 +668,10 @@ func (m *SyncManager) createGossipSyncer(peer lnpeer.Peer) *GossipSyncer {
|
|||||||
encodingType: encoding,
|
encodingType: encoding,
|
||||||
chunkSize: encodingTypeToChunkSize[encoding],
|
chunkSize: encodingTypeToChunkSize[encoding],
|
||||||
batchSize: requestBatchSize,
|
batchSize: requestBatchSize,
|
||||||
sendToPeer: func(ctx context.Context,
|
sendMsg: func(ctx context.Context, sync bool,
|
||||||
msgs ...lnwire.Message) error {
|
msgs ...lnwire.Message) error {
|
||||||
|
|
||||||
return m.sendMessages(ctx, false, peer, nodeID, msgs...)
|
return m.sendMessages(ctx, sync, peer, nodeID, msgs...)
|
||||||
},
|
|
||||||
sendToPeerSync: func(ctx context.Context,
|
|
||||||
msgs ...lnwire.Message) error {
|
|
||||||
|
|
||||||
return m.sendMessages(ctx, true, peer, nodeID, msgs...)
|
|
||||||
},
|
},
|
||||||
ignoreHistoricalFilters: m.cfg.IgnoreHistoricalFilters,
|
ignoreHistoricalFilters: m.cfg.IgnoreHistoricalFilters,
|
||||||
bestHeight: m.cfg.BestHeight,
|
bestHeight: m.cfg.BestHeight,
|
||||||
@@ -670,6 +679,7 @@ func (m *SyncManager) createGossipSyncer(peer lnpeer.Peer) *GossipSyncer {
|
|||||||
maxQueryChanRangeReplies: maxQueryChanRangeReplies,
|
maxQueryChanRangeReplies: maxQueryChanRangeReplies,
|
||||||
noTimestampQueryOption: m.cfg.NoTimestampQueries,
|
noTimestampQueryOption: m.cfg.NoTimestampQueries,
|
||||||
isStillZombieChannel: m.cfg.IsStillZombieChannel,
|
isStillZombieChannel: m.cfg.IsStillZombieChannel,
|
||||||
|
msgBytesPerSecond: m.cfg.PeerMsgBytesPerSecond,
|
||||||
}, m.gossipFilterSema)
|
}, m.gossipFilterSema)
|
||||||
|
|
||||||
// Gossip syncers are initialized by default in a PassiveSync type
|
// Gossip syncers are initialized by default in a PassiveSync type
|
||||||
|
|||||||
@@ -748,7 +748,7 @@ func TestDeriveRateLimitReservation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// First message should have no delay as it fits within burst.
|
// First message should have no delay as it fits within burst.
|
||||||
delay1, err := sm.deriveRateLimitReservation(msg)
|
delay1, err := deriveRateLimitReservation(sm.rateLimiter, msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t, time.Duration(0), delay1.Delay(), "first message "+
|
t, time.Duration(0), delay1.Delay(), "first message "+
|
||||||
@@ -757,7 +757,7 @@ func TestDeriveRateLimitReservation(t *testing.T) {
|
|||||||
|
|
||||||
// Second message should have a non-zero delay as the token
|
// Second message should have a non-zero delay as the token
|
||||||
// bucket is now depleted.
|
// bucket is now depleted.
|
||||||
delay2, err := sm.deriveRateLimitReservation(msg)
|
delay2, err := deriveRateLimitReservation(sm.rateLimiter, msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(
|
require.True(
|
||||||
t, delay2.Delay() > 0, "second message should have "+
|
t, delay2.Delay() > 0, "second message should have "+
|
||||||
@@ -766,7 +766,7 @@ func TestDeriveRateLimitReservation(t *testing.T) {
|
|||||||
|
|
||||||
// Third message should have an even longer delay since the
|
// Third message should have an even longer delay since the
|
||||||
// token bucket is still refilling at a constant rate.
|
// token bucket is still refilling at a constant rate.
|
||||||
delay3, err := sm.deriveRateLimitReservation(msg)
|
delay3, err := deriveRateLimitReservation(sm.rateLimiter, msg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.True(t, delay3.Delay() > delay2.Delay(), "third "+
|
require.True(t, delay3.Delay() > delay2.Delay(), "third "+
|
||||||
"message should have longer delay than second: %s > %s",
|
"message should have longer delay than second: %s > %s",
|
||||||
@@ -798,7 +798,7 @@ func TestDeriveRateLimitReservation(t *testing.T) {
|
|||||||
|
|
||||||
// The error should propagate through
|
// The error should propagate through
|
||||||
// deriveRateLimitReservation.
|
// deriveRateLimitReservation.
|
||||||
_, err := sm.deriveRateLimitReservation(msg)
|
_, err := deriveRateLimitReservation(sm.rateLimiter, msg)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(
|
require.Equal(
|
||||||
t, errMsg, err, "Error should be propagated unchanged",
|
t, errMsg, err, "Error should be propagated unchanged",
|
||||||
@@ -815,7 +815,7 @@ func TestDeriveRateLimitReservation(t *testing.T) {
|
|||||||
initialMsg := &TestSizeableMessage{
|
initialMsg := &TestSizeableMessage{
|
||||||
size: uint32(bytesBurst),
|
size: uint32(bytesBurst),
|
||||||
}
|
}
|
||||||
_, err := sm.deriveRateLimitReservation(initialMsg)
|
_, err := deriveRateLimitReservation(sm.rateLimiter, initialMsg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Now send two messages of different sizes and compare their
|
// Now send two messages of different sizes and compare their
|
||||||
@@ -828,18 +828,22 @@ func TestDeriveRateLimitReservation(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send the small message first.
|
// Send the small message first.
|
||||||
smallDelay, err := sm.deriveRateLimitReservation(smallMsg)
|
smallDelay, err := deriveRateLimitReservation(
|
||||||
|
sm.rateLimiter, smallMsg,
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Reset the limiter to the same state, then empty the bucket.
|
// Reset the limiter to the same state, then empty the bucket.
|
||||||
sm.rateLimiter = rate.NewLimiter(
|
sm.rateLimiter = rate.NewLimiter(
|
||||||
rate.Limit(bytesPerSec), int(bytesBurst),
|
rate.Limit(bytesPerSec), int(bytesBurst),
|
||||||
)
|
)
|
||||||
_, err = sm.deriveRateLimitReservation(initialMsg)
|
_, err = deriveRateLimitReservation(sm.rateLimiter, initialMsg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Now send the large message.
|
// Now send the large message.
|
||||||
largeDelay, err := sm.deriveRateLimitReservation(largeMsg)
|
largeDelay, err := deriveRateLimitReservation(
|
||||||
|
sm.rateLimiter, largeMsg,
|
||||||
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// The large message should have a longer delay than the small
|
// The large message should have a longer delay than the small
|
||||||
|
|||||||
+79
-22
@@ -17,6 +17,7 @@ import (
|
|||||||
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
graphdb "github.com/lightningnetwork/lnd/graph/db"
|
||||||
"github.com/lightningnetwork/lnd/lnpeer"
|
"github.com/lightningnetwork/lnd/lnpeer"
|
||||||
"github.com/lightningnetwork/lnd/lnwire"
|
"github.com/lightningnetwork/lnd/lnwire"
|
||||||
|
"golang.org/x/time/rate"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SyncerType encapsulates the different types of syncing mechanisms for a
|
// SyncerType encapsulates the different types of syncing mechanisms for a
|
||||||
@@ -245,15 +246,10 @@ type gossipSyncerCfg struct {
|
|||||||
// the remote node in a single QueryShortChanIDs request.
|
// the remote node in a single QueryShortChanIDs request.
|
||||||
batchSize int32
|
batchSize int32
|
||||||
|
|
||||||
// sendToPeer sends a variadic number of messages to the remote peer.
|
// sendMsg sends a variadic number of messages to the remote peer.
|
||||||
// This method should not block while waiting for sends to be written
|
// The boolean indicates whether this method should be blocked or not
|
||||||
// to the wire.
|
// while waiting for sends to be written to the wire.
|
||||||
sendToPeer func(context.Context, ...lnwire.Message) error
|
sendMsg func(context.Context, bool, ...lnwire.Message) error
|
||||||
|
|
||||||
// sendToPeerSync sends a variadic number of messages to the remote
|
|
||||||
// peer, blocking until all messages have been sent successfully or a
|
|
||||||
// write error is encountered.
|
|
||||||
sendToPeerSync func(context.Context, ...lnwire.Message) error
|
|
||||||
|
|
||||||
// noSyncChannels will prevent the GossipSyncer from spawning a
|
// noSyncChannels will prevent the GossipSyncer from spawning a
|
||||||
// channelGraphSyncer, meaning we will not try to reconcile unknown
|
// channelGraphSyncer, meaning we will not try to reconcile unknown
|
||||||
@@ -295,6 +291,11 @@ type gossipSyncerCfg struct {
|
|||||||
// timestampQueueSize is the size of the timestamp range queue. If not
|
// timestampQueueSize is the size of the timestamp range queue. If not
|
||||||
// set, defaults to the global timestampQueueSize constant.
|
// set, defaults to the global timestampQueueSize constant.
|
||||||
timestampQueueSize int
|
timestampQueueSize int
|
||||||
|
|
||||||
|
// msgBytesPerSecond is the allotted bandwidth rate, expressed in
|
||||||
|
// bytes/second that this gossip syncer can consume. Once we exceed this
|
||||||
|
// rate, message sending will block until we're below the rate.
|
||||||
|
msgBytesPerSecond uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// GossipSyncer is a struct that handles synchronizing the channel graph state
|
// GossipSyncer is a struct that handles synchronizing the channel graph state
|
||||||
@@ -407,6 +408,10 @@ type GossipSyncer struct {
|
|||||||
// allows contexts that either block or cancel on those depending on
|
// allows contexts that either block or cancel on those depending on
|
||||||
// the use case.
|
// the use case.
|
||||||
cg *fn.ContextGuard
|
cg *fn.ContextGuard
|
||||||
|
|
||||||
|
// rateLimiter dictates the frequency with which we will reply to gossip
|
||||||
|
// queries to this peer.
|
||||||
|
rateLimiter *rate.Limiter
|
||||||
}
|
}
|
||||||
|
|
||||||
// newGossipSyncer returns a new instance of the GossipSyncer populated using
|
// newGossipSyncer returns a new instance of the GossipSyncer populated using
|
||||||
@@ -418,6 +423,17 @@ func newGossipSyncer(cfg gossipSyncerCfg, sema chan struct{}) *GossipSyncer {
|
|||||||
queueSize = defaultTimestampQueueSize
|
queueSize = defaultTimestampQueueSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bytesPerSecond := cfg.msgBytesPerSecond
|
||||||
|
if bytesPerSecond == 0 {
|
||||||
|
bytesPerSecond = DefaultPeerMsgBytesPerSecond
|
||||||
|
}
|
||||||
|
bytesBurst := 2 * bytesPerSecond
|
||||||
|
|
||||||
|
// We'll use this rate limiter to limit this single peer.
|
||||||
|
rateLimiter := rate.NewLimiter(
|
||||||
|
rate.Limit(bytesPerSecond), int(bytesBurst),
|
||||||
|
)
|
||||||
|
|
||||||
return &GossipSyncer{
|
return &GossipSyncer{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
syncTransitionReqs: make(chan *syncTransitionReq),
|
syncTransitionReqs: make(chan *syncTransitionReq),
|
||||||
@@ -427,8 +443,9 @@ func newGossipSyncer(cfg gossipSyncerCfg, sema chan struct{}) *GossipSyncer {
|
|||||||
timestampRangeQueue: make(
|
timestampRangeQueue: make(
|
||||||
chan *lnwire.GossipTimestampRange, queueSize,
|
chan *lnwire.GossipTimestampRange, queueSize,
|
||||||
),
|
),
|
||||||
syncerSema: sema,
|
syncerSema: sema,
|
||||||
cg: fn.NewContextGuard(),
|
cg: fn.NewContextGuard(),
|
||||||
|
rateLimiter: rateLimiter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +513,7 @@ func (g *GossipSyncer) handleSyncingChans(ctx context.Context) {
|
|||||||
|
|
||||||
// Send the msg to the remote peer, which is non-blocking as
|
// Send the msg to the remote peer, which is non-blocking as
|
||||||
// `sendToPeer` only queues the msg in Brontide.
|
// `sendToPeer` only queues the msg in Brontide.
|
||||||
err = g.cfg.sendToPeer(ctx, queryRangeMsg)
|
err = g.sendToPeer(ctx, queryRangeMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to send chan range query: %v", err)
|
log.Errorf("Unable to send chan range query: %v", err)
|
||||||
return
|
return
|
||||||
@@ -783,11 +800,11 @@ func (g *GossipSyncer) sendGossipTimestampRange(ctx context.Context,
|
|||||||
TimestampRange: timestampRange,
|
TimestampRange: timestampRange,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.cfg.sendToPeer(ctx, localUpdateHorizon); err != nil {
|
if err := g.sendToPeer(ctx, localUpdateHorizon); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if firstTimestamp == zeroTimestamp && timestampRange == 0 {
|
if firstTimestamp.Equal(zeroTimestamp) && timestampRange == 0 {
|
||||||
g.localUpdateHorizon = nil
|
g.localUpdateHorizon = nil
|
||||||
} else {
|
} else {
|
||||||
g.localUpdateHorizon = localUpdateHorizon
|
g.localUpdateHorizon = localUpdateHorizon
|
||||||
@@ -839,7 +856,7 @@ func (g *GossipSyncer) synchronizeChanIDs(ctx context.Context) bool {
|
|||||||
|
|
||||||
// With our chunk obtained, we'll send over our next query, then return
|
// With our chunk obtained, we'll send over our next query, then return
|
||||||
// false indicating that we're net yet fully synced.
|
// false indicating that we're net yet fully synced.
|
||||||
err := g.cfg.sendToPeer(ctx, &lnwire.QueryShortChanIDs{
|
err := g.sendToPeer(ctx, &lnwire.QueryShortChanIDs{
|
||||||
ChainHash: g.cfg.chainHash,
|
ChainHash: g.cfg.chainHash,
|
||||||
EncodingType: lnwire.EncodingSortedPlain,
|
EncodingType: lnwire.EncodingSortedPlain,
|
||||||
ShortChanIDs: queryChunk,
|
ShortChanIDs: queryChunk,
|
||||||
@@ -1154,7 +1171,7 @@ func (g *GossipSyncer) replyChanRangeQuery(ctx context.Context,
|
|||||||
"chain=%v, we're on chain=%v", query.ChainHash,
|
"chain=%v, we're on chain=%v", query.ChainHash,
|
||||||
g.cfg.chainHash)
|
g.cfg.chainHash)
|
||||||
|
|
||||||
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
|
return g.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
|
||||||
ChainHash: query.ChainHash,
|
ChainHash: query.ChainHash,
|
||||||
FirstBlockHeight: query.FirstBlockHeight,
|
FirstBlockHeight: query.FirstBlockHeight,
|
||||||
NumBlocks: query.NumBlocks,
|
NumBlocks: query.NumBlocks,
|
||||||
@@ -1227,7 +1244,7 @@ func (g *GossipSyncer) replyChanRangeQuery(ctx context.Context,
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
|
return g.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
|
||||||
ChainHash: query.ChainHash,
|
ChainHash: query.ChainHash,
|
||||||
NumBlocks: numBlocks,
|
NumBlocks: numBlocks,
|
||||||
FirstBlockHeight: firstHeight,
|
FirstBlockHeight: firstHeight,
|
||||||
@@ -1333,7 +1350,7 @@ func (g *GossipSyncer) replyShortChanIDs(ctx context.Context,
|
|||||||
"chain=%v, we're on chain=%v", query.ChainHash,
|
"chain=%v, we're on chain=%v", query.ChainHash,
|
||||||
g.cfg.chainHash)
|
g.cfg.chainHash)
|
||||||
|
|
||||||
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
|
return g.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
|
||||||
ChainHash: query.ChainHash,
|
ChainHash: query.ChainHash,
|
||||||
Complete: 0,
|
Complete: 0,
|
||||||
})
|
})
|
||||||
@@ -1364,7 +1381,7 @@ func (g *GossipSyncer) replyShortChanIDs(ctx context.Context,
|
|||||||
// each one individually and synchronously to throttle the sends and
|
// each one individually and synchronously to throttle the sends and
|
||||||
// perform buffering of responses in the syncer as opposed to the peer.
|
// perform buffering of responses in the syncer as opposed to the peer.
|
||||||
for _, msg := range replyMsgs {
|
for _, msg := range replyMsgs {
|
||||||
err := g.cfg.sendToPeerSync(ctx, msg)
|
err := g.sendToPeerSync(ctx, msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -1372,7 +1389,7 @@ func (g *GossipSyncer) replyShortChanIDs(ctx context.Context,
|
|||||||
|
|
||||||
// Regardless of whether we had any messages to reply with, send over
|
// Regardless of whether we had any messages to reply with, send over
|
||||||
// the sentinel message to signal that the stream has terminated.
|
// the sentinel message to signal that the stream has terminated.
|
||||||
return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
|
return g.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
|
||||||
ChainHash: query.ChainHash,
|
ChainHash: query.ChainHash,
|
||||||
Complete: 1,
|
Complete: 1,
|
||||||
})
|
})
|
||||||
@@ -1462,7 +1479,7 @@ func (g *GossipSyncer) ApplyGossipFilter(ctx context.Context,
|
|||||||
defer g.isSendingBacklog.Store(false)
|
defer g.isSendingBacklog.Store(false)
|
||||||
|
|
||||||
for _, msg := range newUpdatestoSend {
|
for _, msg := range newUpdatestoSend {
|
||||||
err := g.cfg.sendToPeerSync(ctx, msg)
|
err := g.sendToPeerSync(ctx, msg)
|
||||||
switch {
|
switch {
|
||||||
case err == ErrGossipSyncerExiting:
|
case err == ErrGossipSyncerExiting:
|
||||||
return
|
return
|
||||||
@@ -1608,7 +1625,7 @@ func (g *GossipSyncer) FilterGossipMsgs(ctx context.Context,
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = g.cfg.sendToPeer(ctx, msgsToSend...); err != nil {
|
if err = g.sendToPeer(ctx, msgsToSend...); err != nil {
|
||||||
log.Errorf("unable to send gossip msgs: %v", err)
|
log.Errorf("unable to send gossip msgs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1802,3 +1819,43 @@ func (g *GossipSyncer) handleHistoricalSync(req *historicalSyncReq) {
|
|||||||
g.setSyncState(syncingChans)
|
g.setSyncState(syncingChans)
|
||||||
close(req.doneChan)
|
close(req.doneChan)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendToPeer sends a variadic number of messages to the remote peer. This
|
||||||
|
// method should not block while waiting for sends to be written to the wire.
|
||||||
|
func (g *GossipSyncer) sendToPeer(ctx context.Context,
|
||||||
|
msgs ...lnwire.Message) error {
|
||||||
|
|
||||||
|
return g.sendMsgRateLimited(ctx, false, msgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendToPeerSync sends a variadic number of messages to the remote peer,
|
||||||
|
// blocking until all messages have been sent successfully or a write error is
|
||||||
|
// encountered.
|
||||||
|
func (g *GossipSyncer) sendToPeerSync(ctx context.Context,
|
||||||
|
msgs ...lnwire.Message) error {
|
||||||
|
|
||||||
|
return g.sendMsgRateLimited(ctx, true, msgs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendMsgRateLimited sends a variadic number of messages to the remote peer,
|
||||||
|
// applying our per-peer rate limit before each send. The sync boolean
|
||||||
|
// determines if the send is blocking or not.
|
||||||
|
func (g *GossipSyncer) sendMsgRateLimited(ctx context.Context, sync bool,
|
||||||
|
msgs ...lnwire.Message) error {
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
err := maybeRateLimitMsg(
|
||||||
|
ctx, g.rateLimiter, g.cfg.peerPub, msg, g.cg.Done(),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = g.cfg.sendMsg(ctx, sync, msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -26,9 +26,14 @@ func TestGossipSyncerSingleBacklogSend(t *testing.T) {
|
|||||||
// Create a blocking sendToPeerSync function. We'll use this to simulate
|
// Create a blocking sendToPeerSync function. We'll use this to simulate
|
||||||
// sending a large backlog.
|
// sending a large backlog.
|
||||||
blockingSendChan := make(chan struct{})
|
blockingSendChan := make(chan struct{})
|
||||||
sendToPeerSync := func(_ context.Context,
|
mockSendMsg := func(_ context.Context, sync bool,
|
||||||
msgs ...lnwire.Message) error {
|
msgs ...lnwire.Message) error {
|
||||||
|
|
||||||
|
// Sync is only true when calling `sendToPeerSync`.
|
||||||
|
if !sync {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Track that we're in a send goroutine.
|
// Track that we're in a send goroutine.
|
||||||
count := activeGoroutines.Add(1)
|
count := activeGoroutines.Add(1)
|
||||||
totalGoroutinesLaunched.Add(1)
|
totalGoroutinesLaunched.Add(1)
|
||||||
@@ -55,8 +60,8 @@ func TestGossipSyncerSingleBacklogSend(t *testing.T) {
|
|||||||
defaultChunkSize, true, true, true,
|
defaultChunkSize, true, true, true,
|
||||||
)
|
)
|
||||||
|
|
||||||
// Override the sendToPeerSync to use our blocking version.
|
// Override the sendMsg to use our blocking version.
|
||||||
syncer.cfg.sendToPeerSync = sendToPeerSync
|
syncer.cfg.sendMsg = mockSendMsg
|
||||||
syncer.cfg.ignoreHistoricalFilters = false
|
syncer.cfg.ignoreHistoricalFilters = false
|
||||||
|
|
||||||
syncer.Start()
|
syncer.Start()
|
||||||
|
|||||||
+14
-15
@@ -200,13 +200,7 @@ func newTestSyncer(hID lnwire.ShortChannelID,
|
|||||||
noSyncChannels: !syncChannels,
|
noSyncChannels: !syncChannels,
|
||||||
noReplyQueries: !replyQueries,
|
noReplyQueries: !replyQueries,
|
||||||
noTimestampQueryOption: !timestamps,
|
noTimestampQueryOption: !timestamps,
|
||||||
sendToPeer: func(_ context.Context,
|
sendMsg: func(_ context.Context, _ bool,
|
||||||
msgs ...lnwire.Message) error {
|
|
||||||
|
|
||||||
msgChan <- msgs
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
sendToPeerSync: func(_ context.Context,
|
|
||||||
msgs ...lnwire.Message) error {
|
msgs ...lnwire.Message) error {
|
||||||
|
|
||||||
msgChan <- msgs
|
msgChan <- msgs
|
||||||
@@ -392,16 +386,21 @@ func TestGossipSyncerFilterGossipMsgsAllInMemory(t *testing.T) {
|
|||||||
// We'll then instruct the gossiper to filter this set of messages.
|
// We'll then instruct the gossiper to filter this set of messages.
|
||||||
syncer.FilterGossipMsgs(ctx, msgs...)
|
syncer.FilterGossipMsgs(ctx, msgs...)
|
||||||
|
|
||||||
// Out of all the messages we sent in, we should only get 2 of them
|
// Out of all the messages we sent in, we should only get 3 of them
|
||||||
// back.
|
// back.
|
||||||
select {
|
msgReceived := make([]lnwire.Message, 0, 3)
|
||||||
case <-time.After(time.Second * 15):
|
for {
|
||||||
t.Fatalf("no msgs received")
|
select {
|
||||||
|
case <-time.After(time.Second * 1):
|
||||||
|
t.Fatalf("timeout receiving msg, want 3 msgs, got %v "+
|
||||||
|
"messages: %v", len(msgReceived), msgReceived)
|
||||||
|
|
||||||
case msgs := <-msgChan:
|
case msgs := <-msgChan:
|
||||||
if len(msgs) != 3 {
|
msgReceived = append(msgReceived, msgs...)
|
||||||
t.Fatalf("expected 3 messages instead got %v "+
|
}
|
||||||
"messages: %v", len(msgs), spew.Sdump(msgs))
|
|
||||||
|
if len(msgReceived) == 3 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,10 @@ circuit. The indices are only available for forwarding events saved after v0.20.
|
|||||||
finish under this timeout value. Consider using a larger timeout value if you
|
finish under this timeout value. Consider using a larger timeout value if you
|
||||||
have a slow network.
|
have a slow network.
|
||||||
|
|
||||||
|
* The default value for `gossip.msg-rate-bytes` has been
|
||||||
|
[increased](https://github.com/lightningnetwork/lnd/pull/10096) from 100KB to
|
||||||
|
1MB, and `gossip.msg-burst-bytes` has been increased from 200KB to 2MB.
|
||||||
|
|
||||||
* Added [`deletecanceledinvoices`](
|
* Added [`deletecanceledinvoices`](
|
||||||
https://github.com/lightningnetwork/lnd/pull/9625) RPC to allow the removal of
|
https://github.com/lightningnetwork/lnd/pull/9625) RPC to allow the removal of
|
||||||
a canceled invoice. Supports deleting a canceled invoice by providing its
|
a canceled invoice. Supports deleting a canceled invoice by providing its
|
||||||
@@ -105,6 +109,14 @@ circuit. The indices are only available for forwarding events saved after v0.20.
|
|||||||
disconnected and banned. Setting the value to 0 effectively disables banning
|
disconnected and banned. Setting the value to 0 effectively disables banning
|
||||||
by setting the threshold to the maximum possible value.
|
by setting the threshold to the maximum possible value.
|
||||||
|
|
||||||
|
* A [new config](https://github.com/lightningnetwork/lnd/pull/10103) value
|
||||||
|
`gossip.peer-msg-rate-bytes=102400` is introduced to allow limiting the
|
||||||
|
outgoing bandwidth used by each peer when processing gossip-related messages.
|
||||||
|
Note this is different from `gossip.msg-rate-bytes`, as this new config
|
||||||
|
controls the bandwidth per peer, while `msg-rate-bytes` controls the gossip as
|
||||||
|
a whole. This new config prevents a single misbehaving peer from using up all
|
||||||
|
the bandwidth.
|
||||||
|
|
||||||
## lncli Additions
|
## lncli Additions
|
||||||
|
|
||||||
* [`lncli sendpayment` and `lncli queryroutes` now support the
|
* [`lncli sendpayment` and `lncli queryroutes` now support the
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ type Gossip struct {
|
|||||||
FilterConcurrency int `long:"filter-concurrency" description:"The maximum number of concurrent gossip filter applications that can be processed. If not set, defaults to 5."`
|
FilterConcurrency int `long:"filter-concurrency" description:"The maximum number of concurrent gossip filter applications that can be processed. If not set, defaults to 5."`
|
||||||
|
|
||||||
BanThreshold uint64 `long:"ban-threshold" description:"The score at which a peer is banned. A peer's ban score is incremented for each invalid gossip message. Invalid messages include those with bad signatures, stale timestamps, excessive updates, or invalid chain data. Once the score reaches this threshold, the peer is banned. Set to 0 to disable banning."`
|
BanThreshold uint64 `long:"ban-threshold" description:"The score at which a peer is banned. A peer's ban score is incremented for each invalid gossip message. Invalid messages include those with bad signatures, stale timestamps, excessive updates, or invalid chain data. Once the score reaches this threshold, the peer is banned. Set to 0 to disable banning."`
|
||||||
|
|
||||||
|
PeerMsgRateBytes uint64 `long:"peer-msg-rate-bytes" description:"The peer-specific rate of outbound gossip messages, expressed in bytes per second. This setting controls the long-term average speed of gossip traffic sent from your node. The rate limit is applied to each peer. If the rate of outgoing messages exceeds this value, lnd will start to queue and delay messages sending to that peer to stay within the limit."`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the pubkeys for the pinned syncers.
|
// Parse the pubkeys for the pinned syncers.
|
||||||
@@ -72,6 +74,17 @@ func (g *Gossip) Validate() error {
|
|||||||
g.MsgBurstBytes, lnwire.MaxSliceLength)
|
g.MsgBurstBytes, lnwire.MaxSliceLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if g.MsgBurstBytes <= g.MsgRateBytes {
|
||||||
|
return fmt.Errorf("msg-burst-bytes=%v must be greater than "+
|
||||||
|
"msg-rate-bytes=%v", g.MsgBurstBytes, g.MsgRateBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.MsgRateBytes <= g.PeerMsgRateBytes {
|
||||||
|
return fmt.Errorf("msg-rate-bytes=%v must be greater than "+
|
||||||
|
"peer-msg-rate-bytes=%v", g.MsgRateBytes,
|
||||||
|
g.PeerMsgRateBytes)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1814,6 +1814,13 @@
|
|||||||
;
|
;
|
||||||
; gossip.ban-threshold=100
|
; gossip.ban-threshold=100
|
||||||
|
|
||||||
|
; The peer-specific rate of outbound gossip messages, expressed in bytes per
|
||||||
|
; second. This setting controls the long-term average speed of gossip traffic
|
||||||
|
; sent from your node. The rate limit is applied to each peer. If the rate of
|
||||||
|
; outgoing messages exceeds this value, lnd will start to queue and delay
|
||||||
|
; messages sending to that peer to stay within the limit.
|
||||||
|
; gossip.peer-msg-rate-bytes=51200
|
||||||
|
|
||||||
[invoices]
|
[invoices]
|
||||||
|
|
||||||
; If a hold invoice has accepted htlcs that reach their expiry height and are
|
; If a hold invoice has accepted htlcs that reach their expiry height and are
|
||||||
|
|||||||
@@ -1103,6 +1103,7 @@ func newServer(ctx context.Context, cfg *Config, listenAddrs []net.Addr,
|
|||||||
MsgBurstBytes: cfg.Gossip.MsgBurstBytes,
|
MsgBurstBytes: cfg.Gossip.MsgBurstBytes,
|
||||||
FilterConcurrency: cfg.Gossip.FilterConcurrency,
|
FilterConcurrency: cfg.Gossip.FilterConcurrency,
|
||||||
BanThreshold: cfg.Gossip.BanThreshold,
|
BanThreshold: cfg.Gossip.BanThreshold,
|
||||||
|
PeerMsgRateBytes: cfg.Gossip.PeerMsgRateBytes,
|
||||||
}, nodeKeyDesc)
|
}, nodeKeyDesc)
|
||||||
|
|
||||||
accessCfg := &accessManConfig{
|
accessCfg := &accessManConfig{
|
||||||
|
|||||||
Reference in new issue
Block a user