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

     1  package p2p
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/libp2p/go-libp2p/core/peer"
     9  	"github.com/libp2p/go-libp2p/p2p/protocol/ping"
    10  	"golang.org/x/time/rate"
    11  
    12  	"github.com/ethereum/go-ethereum/log"
    13  
    14  	"github.com/ethereum-optimism/optimism/op-service/clock"
    15  )
    16  
    17  const (
    18  	pingRound                 = 3 * time.Minute
    19  	pingsPerSecond rate.Limit = 1
    20  	pingsBurst                = 10
    21  )
    22  
    23  type PingFn func(ctx context.Context, peerID peer.ID) <-chan ping.Result
    24  
    25  type PeersFn func() []peer.ID
    26  
    27  type PingService struct {
    28  	ping  PingFn
    29  	peers PeersFn
    30  
    31  	clock clock.Clock
    32  
    33  	log log.Logger
    34  
    35  	ctx    context.Context
    36  	cancel context.CancelFunc
    37  
    38  	trace func(work string)
    39  
    40  	// to signal service completion
    41  	wg sync.WaitGroup
    42  }
    43  
    44  func NewPingService(log log.Logger, ping PingFn, peers PeersFn, clock clock.Clock) *PingService {
    45  	ctx, cancel := context.WithCancel(context.Background())
    46  	srv := &PingService{
    47  		ping:   ping,
    48  		peers:  peers,
    49  		log:    log,
    50  		clock:  clock,
    51  		ctx:    ctx,
    52  		cancel: cancel,
    53  	}
    54  	srv.wg.Add(1)
    55  	go srv.pingPeersBackground()
    56  	return srv
    57  }
    58  
    59  func (p *PingService) Close() {
    60  	p.cancel()
    61  	p.wg.Wait()
    62  }
    63  
    64  func (e *PingService) pingPeersBackground() {
    65  	defer e.wg.Done()
    66  
    67  	tick := e.clock.NewTicker(pingRound)
    68  	defer tick.Stop()
    69  
    70  	if e.trace != nil {
    71  		e.trace("started")
    72  	}
    73  
    74  	for {
    75  		select {
    76  		case <-tick.Ch():
    77  			e.pingPeers()
    78  		case <-e.ctx.Done():
    79  			return
    80  		}
    81  	}
    82  }
    83  
    84  func (e *PingService) pingPeers() {
    85  	if e.trace != nil {
    86  		e.trace("pingPeers start")
    87  	}
    88  	ctx, cancel := context.WithTimeout(e.ctx, pingRound)
    89  	defer cancel()
    90  
    91  	// Wait group to wait for all pings to complete
    92  	var wg sync.WaitGroup
    93  	// Rate-limiter to help schedule the ping
    94  	// work without overwhelming ourselves.
    95  	rl := rate.NewLimiter(pingsPerSecond, pingsBurst)
    96  
    97  	// iterate through the connected peers
    98  	for i, peerID := range e.peers() {
    99  		if e.ctx.Err() != nil { // stop if the service is closing or timing out
   100  			return
   101  		}
   102  		if ctx.Err() != nil {
   103  			e.log.Warn("failed to ping all peers", "pinged", i, "err", ctx.Err())
   104  			return
   105  		}
   106  		if err := rl.Wait(ctx); err != nil {
   107  			// host may be getting closed, causing a parent ctx to close.
   108  			return
   109  		}
   110  		wg.Add(1)
   111  		go func(peerID peer.ID) {
   112  			e.pingPeer(ctx, peerID)
   113  			wg.Done()
   114  		}(peerID)
   115  	}
   116  	wg.Wait()
   117  	if e.trace != nil {
   118  		e.trace("pingPeers end")
   119  	}
   120  }
   121  
   122  func (e *PingService) pingPeer(ctx context.Context, peerID peer.ID) {
   123  	results := e.ping(ctx, peerID)
   124  	// the results channel will be closed by the ping.Ping function upon context close / completion
   125  	res, ok := <-results
   126  	if !ok {
   127  		// timed out or completed before Pong
   128  		e.log.Warn("failed to ping peer, context cancelled", "peerID", peerID, "err", ctx.Err())
   129  	} else if res.Error != nil {
   130  		e.log.Warn("failed to ping peer, communication error", "peerID", peerID, "err", res.Error)
   131  	} else {
   132  		e.log.Debug("ping-pong", "peerID", peerID, "rtt", res.RTT)
   133  	}
   134  }