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 }