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:
Eugene Siegel committed 2024-08-27 14:11:06 -04:00
1 parent 9380292a5a
commit 013452cff0
7 files changed
+480 -60

No files matched your search

+165 -3
View File
@@ -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
}