github.com/ethereum-optimism/optimism@v1.7.2/op-node/p2p/monitor/peer_monitor.go (about)

     1  package monitor
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"sync"
     7  	"time"
     8  
     9  	"github.com/ethereum-optimism/optimism/op-service/clock"
    10  	"github.com/ethereum/go-ethereum/log"
    11  	"github.com/libp2p/go-libp2p/core/peer"
    12  )
    13  
    14  const (
    15  	// Time delay between checking the score of each peer to avoid activity spikes
    16  	checkInterval = 1 * time.Second
    17  )
    18  
    19  //go:generate mockery --name PeerManager --output mocks/ --with-expecter=true
    20  type PeerManager interface {
    21  	Peers() []peer.ID
    22  	GetPeerScore(id peer.ID) (float64, error)
    23  	IsStatic(peer.ID) bool
    24  	// BanPeer bans the peer until the specified time and disconnects any existing connections.
    25  	BanPeer(peer.ID, time.Time) error
    26  }
    27  
    28  // PeerMonitor runs a background process to periodically check for peers with scores below a minimum.
    29  // When it finds bad peers, it disconnects and bans them.
    30  // A delay is introduced between each peer being checked to avoid spikes in system load.
    31  type PeerMonitor struct {
    32  	ctx         context.Context
    33  	cancelFn    context.CancelFunc
    34  	l           log.Logger
    35  	clock       clock.Clock
    36  	manager     PeerManager
    37  	minScore    float64
    38  	banDuration time.Duration
    39  
    40  	bgTasks sync.WaitGroup
    41  
    42  	// Used by checkNextPeer and must only be accessed from the background thread
    43  	peerList    []peer.ID
    44  	nextPeerIdx int
    45  }
    46  
    47  func NewPeerMonitor(ctx context.Context, l log.Logger, clock clock.Clock, manager PeerManager, minScore float64, banDuration time.Duration) *PeerMonitor {
    48  	ctx, cancelFn := context.WithCancel(ctx)
    49  	return &PeerMonitor{
    50  		ctx:         ctx,
    51  		cancelFn:    cancelFn,
    52  		l:           l,
    53  		clock:       clock,
    54  		manager:     manager,
    55  		minScore:    minScore,
    56  		banDuration: banDuration,
    57  	}
    58  }
    59  func (p *PeerMonitor) Start() {
    60  	p.bgTasks.Add(1)
    61  	go p.background(p.checkNextPeer)
    62  }
    63  
    64  func (p *PeerMonitor) Stop() {
    65  	p.cancelFn()
    66  	p.bgTasks.Wait()
    67  }
    68  
    69  // checkNextPeer checks the next peer and disconnects and bans it if its score is too low and its not protected.
    70  // The first call gets the list of current peers and checks the first one, then each subsequent call checks the next
    71  // peer in the list.  When the end of the list is reached, an updated list of connected peers is retrieved and the process
    72  // starts again.
    73  func (p *PeerMonitor) checkNextPeer() error {
    74  	// Get a new list of peers to check if we've checked all peers in the previous list
    75  	if p.nextPeerIdx >= len(p.peerList) {
    76  		p.peerList = p.manager.Peers()
    77  		p.nextPeerIdx = 0
    78  	}
    79  	if len(p.peerList) == 0 {
    80  		// No peers to check
    81  		return nil
    82  	}
    83  	id := p.peerList[p.nextPeerIdx]
    84  	p.nextPeerIdx++
    85  	score, err := p.manager.GetPeerScore(id)
    86  	if err != nil {
    87  		return fmt.Errorf("retrieve score for peer %v: %w", id, err)
    88  	}
    89  	if score >= p.minScore {
    90  		return nil
    91  	}
    92  	if p.manager.IsStatic(id) {
    93  		return nil
    94  	}
    95  	if err := p.manager.BanPeer(id, p.clock.Now().Add(p.banDuration)); err != nil {
    96  		return fmt.Errorf("banning peer %v: %w", id, err)
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // background is intended to run as a separate go routine. It will call the supplied action function every checkInterval
   103  // until the context is done.
   104  func (p *PeerMonitor) background(action func() error) {
   105  	defer p.bgTasks.Done()
   106  	ticker := p.clock.NewTicker(checkInterval)
   107  	defer ticker.Stop()
   108  	for {
   109  		select {
   110  		case <-p.ctx.Done():
   111  			return
   112  		case <-ticker.Ch():
   113  			if err := action(); err != nil {
   114  				p.l.Warn("Error while checking connected peer score", "err", err)
   115  			}
   116  		}
   117  	}
   118  }