discovery: implement ChannelAnnouncement banning
This commit hooks up the banman to the gossiper: - peers that are banned and don't have a channel with us will get disconnected until they are unbanned. - peers that are banned and have a channel with us won't get disconnected, but we will ignore their channel announcements until they are no longer banned. Note that this only disables gossip of announcements to us and still allows us to open channels to them.
This commit is contained in:
7 files changed
+480
-60
No files matched your search
+165
-3
@@ -256,6 +256,11 @@ type Config struct {
|
||||
// here?
|
||||
AnnSigner lnwallet.MessageSigner
|
||||
|
||||
// ScidCloser is an instance of ClosedChannelTracker that helps the
|
||||
// gossiper cut down on spam channel announcements for already closed
|
||||
// channels.
|
||||
ScidCloser ClosedChannelTracker
|
||||
|
||||
// NumActiveSyncers is the number of peers for which we should have
|
||||
// active syncers with. After reaching NumActiveSyncers, any future
|
||||
// gossip syncers will be passive.
|
||||
@@ -434,6 +439,9 @@ type AuthenticatedGossiper struct {
|
||||
// ChannelAnnouncement for the channel is received.
|
||||
prematureChannelUpdates *lru.Cache[uint64, *cachedNetworkMsg]
|
||||
|
||||
// banman tracks our peer's ban status.
|
||||
banman *banman
|
||||
|
||||
// networkMsgs is a channel that carries new network broadcasted
|
||||
// message from outside the gossiper service to be processed by the
|
||||
// networkHandler.
|
||||
@@ -512,6 +520,7 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper
|
||||
maxRejectedUpdates,
|
||||
),
|
||||
chanUpdateRateLimiter: make(map[uint64][2]*rate.Limiter),
|
||||
banman: newBanman(),
|
||||
}
|
||||
|
||||
gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
|
||||
@@ -606,6 +615,8 @@ func (d *AuthenticatedGossiper) start() error {
|
||||
|
||||
d.syncMgr.Start()
|
||||
|
||||
d.banman.start()
|
||||
|
||||
// Start receiving blocks in its dedicated goroutine.
|
||||
d.wg.Add(2)
|
||||
go d.syncBlockHeight()
|
||||
@@ -762,6 +773,8 @@ func (d *AuthenticatedGossiper) stop() {
|
||||
|
||||
d.syncMgr.Stop()
|
||||
|
||||
d.banman.stop()
|
||||
|
||||
close(d.quit)
|
||||
d.wg.Wait()
|
||||
|
||||
@@ -2459,6 +2472,51 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
return nil, true
|
||||
}
|
||||
|
||||
// Check if the channel is already closed in which case we can ignore
|
||||
// it.
|
||||
closed, err := d.cfg.ScidCloser.IsClosedScid(scid)
|
||||
if err != nil {
|
||||
log.Errorf("failed to check if scid %v is closed: %v", scid,
|
||||
err)
|
||||
nMsg.err <- err
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if closed {
|
||||
err = fmt.Errorf("ignoring closed channel %v", scid)
|
||||
log.Error(err)
|
||||
|
||||
// If this is an announcement from us, we'll just ignore it.
|
||||
if !nMsg.isRemote {
|
||||
nMsg.err <- err
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Increment the peer's ban score if they are sending closed
|
||||
// channel announcements.
|
||||
d.banman.incrementBanScore(nMsg.peer.PubKey())
|
||||
|
||||
// If the peer is banned and not a channel peer, we'll
|
||||
// disconnect them.
|
||||
shouldDc, dcErr := d.ShouldDisconnect(nMsg.peer.IdentityKey())
|
||||
if dcErr != nil {
|
||||
log.Errorf("failed to check if we should disconnect "+
|
||||
"peer: %v", dcErr)
|
||||
nMsg.err <- dcErr
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if shouldDc {
|
||||
nMsg.peer.Disconnect(ErrPeerBanned)
|
||||
}
|
||||
|
||||
nMsg.err <- err
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// If this is a remote channel announcement, then we'll validate all
|
||||
// the signatures within the proof as it should be well formed.
|
||||
var proof *models.ChannelAuthProof
|
||||
@@ -2533,7 +2591,7 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
// database and is now making decisions based on this DB state, before
|
||||
// it writes to the DB.
|
||||
d.channelMtx.Lock(scid.ToUint64())
|
||||
err := d.cfg.Graph.AddEdge(edge, ops...)
|
||||
err = d.cfg.Graph.AddEdge(edge, ops...)
|
||||
if err != nil {
|
||||
log.Debugf("Graph rejected edge for short_chan_id(%v): %v",
|
||||
scid.ToUint64(), err)
|
||||
@@ -2543,7 +2601,8 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
// If the edge was rejected due to already being known, then it
|
||||
// may be the case that this new message has a fresh channel
|
||||
// proof, so we'll check.
|
||||
if graph.IsError(err, graph.ErrIgnored) {
|
||||
switch {
|
||||
case graph.IsError(err, graph.ErrIgnored):
|
||||
// Attempt to process the rejected message to see if we
|
||||
// get any new announcements.
|
||||
anns, rErr := d.processRejectedEdge(ann, proof)
|
||||
@@ -2571,7 +2630,55 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
nMsg.err <- nil
|
||||
|
||||
return anns, true
|
||||
} else {
|
||||
|
||||
case graph.IsError(
|
||||
err, graph.ErrNoFundingTransaction,
|
||||
graph.ErrInvalidFundingOutput,
|
||||
):
|
||||
key := newRejectCacheKey(
|
||||
scid.ToUint64(),
|
||||
sourceToPub(nMsg.source),
|
||||
)
|
||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||
|
||||
// Increment the peer's ban score. We check isRemote
|
||||
// so we don't actually ban the peer in case of a local
|
||||
// bug.
|
||||
if nMsg.isRemote {
|
||||
d.banman.incrementBanScore(nMsg.peer.PubKey())
|
||||
}
|
||||
|
||||
case graph.IsError(err, graph.ErrChannelSpent):
|
||||
key := newRejectCacheKey(
|
||||
scid.ToUint64(),
|
||||
sourceToPub(nMsg.source),
|
||||
)
|
||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||
|
||||
// Since this channel has already been closed, we'll
|
||||
// add it to the graph's closed channel index such that
|
||||
// we won't attempt to do expensive validation checks
|
||||
// on it again.
|
||||
// TODO: Populate the ScidCloser by using closed
|
||||
// channel notifications.
|
||||
dbErr := d.cfg.ScidCloser.PutClosedScid(scid)
|
||||
if dbErr != nil {
|
||||
log.Errorf("failed to mark scid(%v) as "+
|
||||
"closed: %v", scid, dbErr)
|
||||
|
||||
nMsg.err <- dbErr
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// Increment the peer's ban score. We check isRemote
|
||||
// so we don't accidentally ban ourselves in case of a
|
||||
// bug.
|
||||
if nMsg.isRemote {
|
||||
d.banman.incrementBanScore(nMsg.peer.PubKey())
|
||||
}
|
||||
|
||||
default:
|
||||
// Otherwise, this is just a regular rejected edge.
|
||||
key := newRejectCacheKey(
|
||||
scid.ToUint64(),
|
||||
@@ -2580,7 +2687,29 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
|
||||
_, _ = d.recentRejects.Put(key, &cachedReject{})
|
||||
}
|
||||
|
||||
if !nMsg.isRemote {
|
||||
log.Errorf("failed to add edge for local channel: %v",
|
||||
err)
|
||||
nMsg.err <- err
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
shouldDc, dcErr := d.ShouldDisconnect(nMsg.peer.IdentityKey())
|
||||
if dcErr != nil {
|
||||
log.Errorf("failed to check if we should disconnect "+
|
||||
"peer: %v", dcErr)
|
||||
nMsg.err <- dcErr
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
if shouldDc {
|
||||
nMsg.peer.Disconnect(ErrPeerBanned)
|
||||
}
|
||||
|
||||
nMsg.err <- err
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
@@ -3385,3 +3514,36 @@ func (d *AuthenticatedGossiper) handleAnnSig(nMsg *networkMsg,
|
||||
nMsg.err <- nil
|
||||
return announcements, true
|
||||
}
|
||||
|
||||
// isBanned returns true if the peer identified by pubkey is banned for sending
|
||||
// invalid channel announcements.
|
||||
func (d *AuthenticatedGossiper) isBanned(pubkey [33]byte) bool {
|
||||
return d.banman.isBanned(pubkey)
|
||||
}
|
||||
|
||||
// ShouldDisconnect returns true if we should disconnect the peer identified by
|
||||
// pubkey.
|
||||
func (d *AuthenticatedGossiper) ShouldDisconnect(pubkey *btcec.PublicKey) (
|
||||
bool, error) {
|
||||
|
||||
pubkeySer := pubkey.SerializeCompressed()
|
||||
|
||||
var pubkeyBytes [33]byte
|
||||
copy(pubkeyBytes[:], pubkeySer)
|
||||
|
||||
// If the public key is banned, check whether or not this is a channel
|
||||
// peer.
|
||||
if d.isBanned(pubkeyBytes) {
|
||||
isChanPeer, err := d.cfg.ScidCloser.IsChannelPeer(pubkey)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// We should only disconnect non-channel peers.
|
||||
if !isChanPeer {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
Reference in new issue
Block a user