Skip to content

Commit

Permalink
discovery: implement ChannelAnnouncement banning
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Crypt-iQ committed Aug 14, 2024
1 parent 80b5d04 commit 29b032d
Show file tree
Hide file tree
Showing 5 changed files with 394 additions and 58 deletions.
137 changes: 137 additions & 0 deletions discovery/gossiper.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ type Config struct {
// here?
AnnSigner lnwallet.MessageSigner

// ScidCloser is an instance of gossipScidCloser that helps the
// gossiper cut down on spam channel announcements.
ScidCloser GossipScidCloser

// NumActiveSyncers is the number of peers for which we should have
// active syncers with. After reaching NumActiveSyncers, any future
// gossip syncers will be passive.
Expand Down Expand Up @@ -434,6 +438,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.
Expand Down Expand Up @@ -512,6 +519,7 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper
maxRejectedUpdates,
),
chanUpdateRateLimiter: make(map[uint64][2]*rate.Limiter),
banman: newBanman(),
}

gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
Expand Down Expand Up @@ -606,6 +614,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()
Expand Down Expand Up @@ -762,6 +772,8 @@ func (d *AuthenticatedGossiper) stop() {

d.syncMgr.Stop()

d.banman.stop()

close(d.quit)
d.wg.Wait()

Expand Down Expand Up @@ -2438,6 +2450,29 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
return nil, false
}

// If this peer is banned and the channel announcement is for a channel
// that we are not a part of, ignore the announcement.
if nMsg.isRemote && d.isBanned(nMsg.peer.PubKey()) {
chanPeer, err := d.isChannelPeer(nMsg.peer.IdentityKey())
if err != nil {
log.Errorf("failed to check if peer %x is a channel "+
"peer: %v", nMsg.peer.PubKey(), err)
nMsg.err <- err
return nil, false
}

if !chanPeer {
nMsg.peer.Disconnect(ErrPeerBanned)
}

err = fmt.Errorf("ignored channel announcement for "+
"channel=%v since peer is banned", scid)
log.Tracef(err.Error())

nMsg.err <- err
return nil, false
}

// If the advertised inclusionary block is beyond our knowledge of the
// chain tip, then we'll ignore it for now.
d.Lock()
Expand Down Expand Up @@ -2488,6 +2523,23 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
}
}

// 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)
nMsg.err <- err
return nil, false
}

// With the proof validated (if necessary), we can now store it within
// the database for our path finding and syncing needs.
var featureBuf bytes.Buffer
Expand Down Expand Up @@ -2570,6 +2622,47 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
nMsg.err <- nil

return anns, true
} else if graph.IsError(err, graph.ErrNoFundingTransaction) ||
graph.IsError(err, 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 accidentally ban ourselves in case of a
// bug.
if nMsg.isRemote {
d.banman.incrementBanScore(nMsg.peer.PubKey())
}
} else if 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.
dbErr := d.cfg.ScidCloser.MarkClosedScid(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())
}
} else {
// Otherwise, this is just a regular rejected edge.
key := newRejectCacheKey(
Expand All @@ -2579,6 +2672,31 @@ func (d *AuthenticatedGossiper) handleChanAnnouncement(nMsg *networkMsg,
_, _ = d.recentRejects.Put(key, &cachedReject{})
}

if !nMsg.isRemote {
nMsg.err <- err
return nil, false
}

// If the peer is banned, check if it's a channel peer. We do
// the isBanned check first to not incur the cost of a db
// lookup.
if d.isBanned(nMsg.peer.PubKey()) {
chanPeer, dbErr := d.isChannelPeer(
nMsg.peer.IdentityKey(),
)
if dbErr != nil {
log.Errorf("failed to check if peer %x is a "+
"channel peer: %v", nMsg.peer.PubKey(),
dbErr)
nMsg.err <- dbErr
return nil, false
}

if !chanPeer {
nMsg.peer.Disconnect(ErrPeerBanned)
}
}

nMsg.err <- err
return nil, false
}
Expand Down Expand Up @@ -3368,3 +3486,22 @@ 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)
}

// isChannelPeer returns true if the peer denoted by pubkey is one we have a
// channel peer.
func (d *AuthenticatedGossiper) isChannelPeer(pubkey *btcec.PublicKey) (bool,
error) {

chanPeer, err := d.cfg.ScidCloser.IsChannelPeer(pubkey)
if err != nil {
return false, err
}

return chanPeer, nil
}
Loading

0 comments on commit 29b032d

Please sign in to comment.