github.com/ethereum-optimism/optimism@v1.7.2/op-node/p2p/host.go (about) 1 package p2p 2 3 import ( 4 "context" 5 "fmt" 6 "net" 7 "sync" 8 "time" 9 10 //nolint:all 11 "github.com/libp2p/go-libp2p/p2p/host/peerstore/pstoreds" 12 13 libp2p "github.com/libp2p/go-libp2p" 14 mplex "github.com/libp2p/go-libp2p-mplex" 15 lconf "github.com/libp2p/go-libp2p/config" 16 "github.com/libp2p/go-libp2p/core/connmgr" 17 "github.com/libp2p/go-libp2p/core/host" 18 "github.com/libp2p/go-libp2p/core/metrics" 19 "github.com/libp2p/go-libp2p/core/network" 20 "github.com/libp2p/go-libp2p/core/peer" 21 "github.com/libp2p/go-libp2p/core/sec/insecure" 22 basichost "github.com/libp2p/go-libp2p/p2p/host/basic" 23 "github.com/libp2p/go-libp2p/p2p/muxer/yamux" 24 "github.com/libp2p/go-libp2p/p2p/protocol/ping" 25 "github.com/libp2p/go-libp2p/p2p/security/noise" 26 tls "github.com/libp2p/go-libp2p/p2p/security/tls" 27 "github.com/libp2p/go-libp2p/p2p/transport/tcp" 28 ma "github.com/multiformats/go-multiaddr" 29 madns "github.com/multiformats/go-multiaddr-dns" 30 31 "github.com/ethereum/go-ethereum/log" 32 33 "github.com/ethereum-optimism/optimism/op-node/p2p/gating" 34 "github.com/ethereum-optimism/optimism/op-node/p2p/store" 35 "github.com/ethereum-optimism/optimism/op-service/clock" 36 ) 37 38 const ( 39 staticPeerTag = "static" 40 ) 41 42 type ExtraHostFeatures interface { 43 host.Host 44 ConnectionGater() gating.BlockingConnectionGater 45 ConnectionManager() connmgr.ConnManager 46 } 47 48 type extraHost struct { 49 host.Host 50 gater gating.BlockingConnectionGater 51 connMgr connmgr.ConnManager 52 log log.Logger 53 54 staticPeers []*peer.AddrInfo 55 56 pinging *PingService 57 58 quitC chan struct{} 59 } 60 61 func (e *extraHost) ConnectionGater() gating.BlockingConnectionGater { 62 return e.gater 63 } 64 65 func (e *extraHost) ConnectionManager() connmgr.ConnManager { 66 return e.connMgr 67 } 68 69 func (e *extraHost) Close() error { 70 close(e.quitC) 71 if e.pinging != nil { 72 e.pinging.Close() 73 } 74 return e.Host.Close() 75 } 76 77 func (e *extraHost) initStaticPeers() { 78 for _, addr := range e.staticPeers { 79 e.Peerstore().AddAddrs(addr.ID, addr.Addrs, time.Hour*24*7) 80 // We protect the peer, so the connection manager doesn't decide to prune it. 81 // We tag it with "static" so other protects/unprotects with different tags don't affect this protection. 82 e.connMgr.Protect(addr.ID, staticPeerTag) 83 // Try to dial the node in the background 84 go func(addr *peer.AddrInfo) { 85 ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) 86 defer cancel() 87 if err := e.dialStaticPeer(ctx, addr); err != nil { 88 e.log.Warn("error dialing static peer", "peer", addr.ID, "err", err) 89 } 90 }(addr) 91 } 92 } 93 94 func (e *extraHost) dialStaticPeer(ctx context.Context, addr *peer.AddrInfo) error { 95 e.log.Info("dialing static peer", "peer", addr.ID, "addrs", addr.Addrs) 96 if _, err := e.Network().DialPeer(ctx, addr.ID); err != nil { 97 return err 98 } 99 return nil 100 } 101 102 func (e *extraHost) monitorStaticPeers() { 103 tick := time.NewTicker(time.Minute) 104 defer tick.Stop() 105 106 for { 107 select { 108 case <-tick.C: 109 ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) 110 var wg sync.WaitGroup 111 112 e.log.Debug("polling static peers", "peers", len(e.staticPeers)) 113 for _, addr := range e.staticPeers { 114 connectedness := e.Network().Connectedness(addr.ID) 115 e.log.Trace("static peer connectedness", "peer", addr.ID, "connectedness", connectedness) 116 117 if connectedness == network.Connected { 118 continue 119 } 120 121 wg.Add(1) 122 go func(addr *peer.AddrInfo) { 123 e.log.Warn("static peer disconnected, reconnecting", "peer", addr.ID) 124 if err := e.dialStaticPeer(ctx, addr); err != nil { 125 e.log.Warn("error reconnecting to static peer", "peer", addr.ID, "err", err) 126 } 127 wg.Done() 128 }(addr) 129 } 130 131 wg.Wait() 132 cancel() 133 case <-e.quitC: 134 return 135 } 136 } 137 } 138 139 var _ ExtraHostFeatures = (*extraHost)(nil) 140 141 func (conf *Config) Host(log log.Logger, reporter metrics.Reporter, metrics HostMetrics) (host.Host, error) { 142 if conf.DisableP2P { 143 return nil, nil 144 } 145 pub := conf.Priv.GetPublic() 146 pid, err := peer.IDFromPublicKey(pub) 147 if err != nil { 148 return nil, fmt.Errorf("failed to derive pubkey from network priv key: %w", err) 149 } 150 151 basePs, err := pstoreds.NewPeerstore(context.Background(), conf.Store, pstoreds.DefaultOpts()) 152 if err != nil { 153 return nil, fmt.Errorf("failed to open peerstore: %w", err) 154 } 155 156 peerScoreParams := conf.PeerScoringParams() 157 var scoreRetention time.Duration 158 if peerScoreParams != nil { 159 // Use the same retention period as gossip will if available 160 scoreRetention = peerScoreParams.PeerScoring.RetainScore 161 } else { 162 // Disable score GC if peer scoring is disabled 163 scoreRetention = 0 164 } 165 ps, err := store.NewExtendedPeerstore(context.Background(), log, clock.SystemClock, basePs, conf.Store, scoreRetention) 166 if err != nil { 167 return nil, fmt.Errorf("failed to open extended peerstore: %w", err) 168 } 169 170 if err := ps.AddPrivKey(pid, conf.Priv); err != nil { 171 return nil, fmt.Errorf("failed to set up peerstore with priv key: %w", err) 172 } 173 if err := ps.AddPubKey(pid, pub); err != nil { 174 return nil, fmt.Errorf("failed to set up peerstore with pub key: %w", err) 175 } 176 177 var connGtr gating.BlockingConnectionGater 178 connGtr, err = gating.NewBlockingConnectionGater(conf.Store) 179 if err != nil { 180 return nil, fmt.Errorf("failed to open connection gater: %w", err) 181 } 182 connGtr = gating.AddBanExpiry(connGtr, ps, log, clock.SystemClock, metrics) 183 connGtr = gating.AddMetering(connGtr, metrics) 184 185 connMngr, err := DefaultConnManager(conf) 186 if err != nil { 187 return nil, fmt.Errorf("failed to open connection manager: %w", err) 188 } 189 190 listenAddr, err := addrFromIPAndPort(conf.ListenIP, conf.ListenTCPPort) 191 if err != nil { 192 return nil, fmt.Errorf("failed to make listen addr: %w", err) 193 } 194 tcpTransport := libp2p.Transport( 195 tcp.NewTCPTransport, 196 tcp.WithConnectionTimeout(time.Minute*60)) // break unused connections 197 // TODO: technically we can also run the node on websocket and QUIC transports. Maybe in the future? 198 199 var nat lconf.NATManagerC // disabled if nil 200 if conf.NAT { 201 nat = basichost.NewNATManager 202 } 203 204 opts := []libp2p.Option{ 205 libp2p.Identity(conf.Priv), 206 // Explicitly set the user-agent, so we can differentiate from other Go libp2p users. 207 libp2p.UserAgent(conf.UserAgent), 208 tcpTransport, 209 libp2p.WithDialTimeout(conf.TimeoutDial), 210 // No relay services, direct connections between peers only. 211 libp2p.DisableRelay(), 212 // host will start and listen to network directly after construction from config. 213 libp2p.ListenAddrs(listenAddr), 214 libp2p.ConnectionGater(connGtr), 215 libp2p.ConnectionManager(connMngr), 216 //libp2p.ResourceManager(nil), // TODO use resource manager interface to manage resources per peer better. 217 libp2p.NATManager(nat), 218 libp2p.Peerstore(ps), 219 libp2p.BandwidthReporter(reporter), // may be nil if disabled 220 libp2p.MultiaddrResolver(madns.DefaultResolver), 221 // Ping is a small built-in libp2p protocol that helps us check/debug latency between peers. 222 libp2p.Ping(true), 223 // Help peers with their NAT reachability status, but throttle to avoid too much work. 224 libp2p.EnableNATService(), 225 libp2p.AutoNATServiceRateLimit(10, 5, time.Second*60), 226 } 227 opts = append(opts, conf.HostMux...) 228 if conf.NoTransportSecurity { 229 opts = append(opts, libp2p.Security(insecure.ID, insecure.NewWithIdentity)) 230 } else { 231 opts = append(opts, conf.HostSecurity...) 232 } 233 h, err := libp2p.New(opts...) 234 if err != nil { 235 return nil, err 236 } 237 238 staticPeers := make([]*peer.AddrInfo, 0, len(conf.StaticPeers)) 239 for _, peerAddr := range conf.StaticPeers { 240 addr, err := peer.AddrInfoFromP2pAddr(peerAddr) 241 if err != nil { 242 return nil, fmt.Errorf("bad peer address: %w", err) 243 } 244 if addr.ID == h.ID() { 245 log.Info("Static-peer list contains address of local peer, ignoring the address.", "peer_id", addr.ID, "addrs", addr.Addrs) 246 continue 247 } 248 staticPeers = append(staticPeers, addr) 249 } 250 251 out := &extraHost{ 252 Host: h, 253 connMgr: connMngr, 254 log: log, 255 staticPeers: staticPeers, 256 quitC: make(chan struct{}), 257 } 258 259 if conf.EnablePingService { 260 out.pinging = NewPingService(log, 261 func(ctx context.Context, peerID peer.ID) <-chan ping.Result { 262 return ping.Ping(ctx, h, peerID) 263 }, h.Network().Peers, clock.SystemClock) 264 } 265 266 out.initStaticPeers() 267 if len(conf.StaticPeers) > 0 { 268 go out.monitorStaticPeers() 269 } 270 271 out.gater = connGtr 272 return out, nil 273 } 274 275 // Creates a multi-addr to bind to. Does not contain a PeerID component (required for usage by external peers) 276 func addrFromIPAndPort(ip net.IP, port uint16) (ma.Multiaddr, error) { 277 ipScheme := "ip4" 278 if ip4 := ip.To4(); ip4 == nil { 279 ipScheme = "ip6" 280 } else { 281 ip = ip4 282 } 283 return ma.NewMultiaddr(fmt.Sprintf("/%s/%s/tcp/%d", ipScheme, ip.String(), port)) 284 } 285 286 func YamuxC() libp2p.Option { 287 return libp2p.Muxer("/yamux/1.0.0", yamux.DefaultTransport) 288 } 289 290 func MplexC() libp2p.Option { 291 return libp2p.Muxer("/mplex/6.7.0", mplex.DefaultTransport) 292 } 293 294 func NoiseC() libp2p.Option { 295 return libp2p.Security(noise.ID, noise.New) 296 } 297 298 func TlsC() libp2p.Option { 299 return libp2p.Security(tls.ID, tls.New) 300 }