github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/p2p/peer.go (about) 1 package p2p 2 3 import ( 4 "fmt" 5 "net" 6 "reflect" 7 "time" 8 9 "github.com/badrootd/nibiru-cometbft/libs/cmap" 10 "github.com/badrootd/nibiru-cometbft/libs/log" 11 "github.com/badrootd/nibiru-cometbft/libs/service" 12 "github.com/cosmos/gogoproto/proto" 13 14 cmtconn "github.com/badrootd/nibiru-cometbft/p2p/conn" 15 ) 16 17 //go:generate ../scripts/mockery_generate.sh Peer 18 19 const metricsTickerDuration = 10 * time.Second 20 21 // Peer is an interface representing a peer connected on a reactor. 22 type Peer interface { 23 service.Service 24 FlushStop() 25 26 ID() ID // peer's cryptographic ID 27 RemoteIP() net.IP // remote IP of the connection 28 RemoteAddr() net.Addr // remote address of the connection 29 30 IsOutbound() bool // did we dial the peer 31 IsPersistent() bool // do we redial this peer when we disconnect 32 33 CloseConn() error // close original connection 34 35 NodeInfo() NodeInfo // peer's info 36 Status() cmtconn.ConnectionStatus 37 SocketAddr() *NetAddress // actual address of the socket 38 39 SendEnvelope(Envelope) bool 40 TrySendEnvelope(Envelope) bool 41 42 Set(string, interface{}) 43 Get(string) interface{} 44 45 SetRemovalFailed() 46 GetRemovalFailed() bool 47 } 48 49 //---------------------------------------------------------- 50 51 // peerConn contains the raw connection and its config. 52 type peerConn struct { 53 outbound bool 54 persistent bool 55 conn net.Conn // source connection 56 57 socketAddr *NetAddress 58 59 // cached RemoteIP() 60 ip net.IP 61 } 62 63 func newPeerConn( 64 outbound, persistent bool, 65 conn net.Conn, 66 socketAddr *NetAddress, 67 ) peerConn { 68 69 return peerConn{ 70 outbound: outbound, 71 persistent: persistent, 72 conn: conn, 73 socketAddr: socketAddr, 74 } 75 } 76 77 // ID only exists for SecretConnection. 78 // NOTE: Will panic if conn is not *SecretConnection. 79 func (pc peerConn) ID() ID { 80 return PubKeyToID(pc.conn.(*cmtconn.SecretConnection).RemotePubKey()) 81 } 82 83 // Return the IP from the connection RemoteAddr 84 func (pc peerConn) RemoteIP() net.IP { 85 if pc.ip != nil { 86 return pc.ip 87 } 88 89 host, _, err := net.SplitHostPort(pc.conn.RemoteAddr().String()) 90 if err != nil { 91 panic(err) 92 } 93 94 ips, err := net.LookupIP(host) 95 if err != nil { 96 panic(err) 97 } 98 99 pc.ip = ips[0] 100 101 return pc.ip 102 } 103 104 // peer implements Peer. 105 // 106 // Before using a peer, you will need to perform a handshake on connection. 107 type peer struct { 108 service.BaseService 109 110 // raw peerConn and the multiplex connection 111 peerConn 112 mconn *cmtconn.MConnection 113 114 // peer's node info and the channel it knows about 115 // channels = nodeInfo.Channels 116 // cached to avoid copying nodeInfo in hasChannel 117 nodeInfo NodeInfo 118 channels []byte 119 120 // User data 121 Data *cmap.CMap 122 123 metrics *Metrics 124 metricsTicker *time.Ticker 125 mlc *metricsLabelCache 126 127 // When removal of a peer fails, we set this flag 128 removalAttemptFailed bool 129 } 130 131 type PeerOption func(*peer) 132 133 func newPeer( 134 pc peerConn, 135 mConfig cmtconn.MConnConfig, 136 nodeInfo NodeInfo, 137 reactorsByCh map[byte]Reactor, 138 msgTypeByChID map[byte]proto.Message, 139 chDescs []*cmtconn.ChannelDescriptor, 140 onPeerError func(Peer, interface{}), 141 mlc *metricsLabelCache, 142 options ...PeerOption, 143 ) *peer { 144 p := &peer{ 145 peerConn: pc, 146 nodeInfo: nodeInfo, 147 channels: nodeInfo.(DefaultNodeInfo).Channels, 148 Data: cmap.NewCMap(), 149 metricsTicker: time.NewTicker(metricsTickerDuration), 150 metrics: NopMetrics(), 151 mlc: mlc, 152 } 153 154 p.mconn = createMConnection( 155 pc.conn, 156 p, 157 reactorsByCh, 158 msgTypeByChID, 159 chDescs, 160 onPeerError, 161 mConfig, 162 ) 163 p.BaseService = *service.NewBaseService(nil, "Peer", p) 164 for _, option := range options { 165 option(p) 166 } 167 168 return p 169 } 170 171 // String representation. 172 func (p *peer) String() string { 173 if p.outbound { 174 return fmt.Sprintf("Peer{%v %v out}", p.mconn, p.ID()) 175 } 176 177 return fmt.Sprintf("Peer{%v %v in}", p.mconn, p.ID()) 178 } 179 180 //--------------------------------------------------- 181 // Implements service.Service 182 183 // SetLogger implements BaseService. 184 func (p *peer) SetLogger(l log.Logger) { 185 p.Logger = l 186 p.mconn.SetLogger(l) 187 } 188 189 // OnStart implements BaseService. 190 func (p *peer) OnStart() error { 191 if err := p.BaseService.OnStart(); err != nil { 192 return err 193 } 194 195 if err := p.mconn.Start(); err != nil { 196 return err 197 } 198 199 go p.metricsReporter() 200 return nil 201 } 202 203 // FlushStop mimics OnStop but additionally ensures that all successful 204 // SendEnvelope() calls will get flushed before closing the connection. 205 // NOTE: it is not safe to call this method more than once. 206 func (p *peer) FlushStop() { 207 p.metricsTicker.Stop() 208 p.BaseService.OnStop() 209 p.mconn.FlushStop() // stop everything and close the conn 210 } 211 212 // OnStop implements BaseService. 213 func (p *peer) OnStop() { 214 p.metricsTicker.Stop() 215 p.BaseService.OnStop() 216 if err := p.mconn.Stop(); err != nil { // stop everything and close the conn 217 p.Logger.Debug("Error while stopping peer", "err", err) 218 } 219 } 220 221 //--------------------------------------------------- 222 // Implements Peer 223 224 // ID returns the peer's ID - the hex encoded hash of its pubkey. 225 func (p *peer) ID() ID { 226 return p.nodeInfo.ID() 227 } 228 229 // IsOutbound returns true if the connection is outbound, false otherwise. 230 func (p *peer) IsOutbound() bool { 231 return p.peerConn.outbound 232 } 233 234 // IsPersistent returns true if the peer is persitent, false otherwise. 235 func (p *peer) IsPersistent() bool { 236 return p.peerConn.persistent 237 } 238 239 // NodeInfo returns a copy of the peer's NodeInfo. 240 func (p *peer) NodeInfo() NodeInfo { 241 return p.nodeInfo 242 } 243 244 // SocketAddr returns the address of the socket. 245 // For outbound peers, it's the address dialed (after DNS resolution). 246 // For inbound peers, it's the address returned by the underlying connection 247 // (not what's reported in the peer's NodeInfo). 248 func (p *peer) SocketAddr() *NetAddress { 249 return p.peerConn.socketAddr 250 } 251 252 // Status returns the peer's ConnectionStatus. 253 func (p *peer) Status() cmtconn.ConnectionStatus { 254 return p.mconn.Status() 255 } 256 257 // SendEnvelope sends the message in the envelope on the channel specified by the 258 // envelope. Returns false if the connection times out trying to place the message 259 // onto its internal queue. 260 func (p *peer) SendEnvelope(e Envelope) bool { 261 return p.send(e.ChannelID, e.Message, p.mconn.Send) 262 } 263 264 // TrySendEnvelope attempts to sends the message in the envelope on the channel specified by the 265 // envelope. Returns false immediately if the connection's internal queue is full 266 func (p *peer) TrySendEnvelope(e Envelope) bool { 267 return p.send(e.ChannelID, e.Message, p.mconn.TrySend) 268 } 269 270 func (p *peer) send(chID byte, msg proto.Message, sendFunc func(byte, []byte) bool) bool { 271 if !p.IsRunning() { 272 return false 273 } else if !p.hasChannel(chID) { 274 return false 275 } 276 metricLabelValue := p.mlc.ValueToMetricLabel(msg) 277 if w, ok := msg.(Wrapper); ok { 278 msg = w.Wrap() 279 } 280 msgBytes, err := proto.Marshal(msg) 281 if err != nil { 282 p.Logger.Error("marshaling message to send", "error", err) 283 return false 284 } 285 res := sendFunc(chID, msgBytes) 286 if res { 287 labels := []string{ 288 "peer_id", string(p.ID()), 289 "chID", fmt.Sprintf("%#x", chID), 290 } 291 p.metrics.PeerSendBytesTotal.With(labels...).Add(float64(len(msgBytes))) 292 p.metrics.MessageSendBytesTotal.With("message_type", metricLabelValue).Add(float64(len(msgBytes))) 293 } 294 return res 295 } 296 297 // Get the data for a given key. 298 func (p *peer) Get(key string) interface{} { 299 return p.Data.Get(key) 300 } 301 302 // Set sets the data for the given key. 303 func (p *peer) Set(key string, data interface{}) { 304 p.Data.Set(key, data) 305 } 306 307 // hasChannel returns true if the peer reported 308 // knowing about the given chID. 309 func (p *peer) hasChannel(chID byte) bool { 310 for _, ch := range p.channels { 311 if ch == chID { 312 return true 313 } 314 } 315 // NOTE: probably will want to remove this 316 // but could be helpful while the feature is new 317 p.Logger.Debug( 318 "Unknown channel for peer", 319 "channel", 320 chID, 321 "channels", 322 p.channels, 323 ) 324 return false 325 } 326 327 // CloseConn closes original connection. Used for cleaning up in cases where the peer had not been started at all. 328 func (p *peer) CloseConn() error { 329 return p.peerConn.conn.Close() 330 } 331 332 func (p *peer) SetRemovalFailed() { 333 p.removalAttemptFailed = true 334 } 335 336 func (p *peer) GetRemovalFailed() bool { 337 return p.removalAttemptFailed 338 } 339 340 //--------------------------------------------------- 341 // methods only used for testing 342 // TODO: can we remove these? 343 344 // CloseConn closes the underlying connection 345 func (pc *peerConn) CloseConn() { 346 pc.conn.Close() 347 } 348 349 // RemoteAddr returns peer's remote network address. 350 func (p *peer) RemoteAddr() net.Addr { 351 return p.peerConn.conn.RemoteAddr() 352 } 353 354 // CanSend returns true if the send queue is not full, false otherwise. 355 func (p *peer) CanSend(chID byte) bool { 356 if !p.IsRunning() { 357 return false 358 } 359 return p.mconn.CanSend(chID) 360 } 361 362 //--------------------------------------------------- 363 364 func PeerMetrics(metrics *Metrics) PeerOption { 365 return func(p *peer) { 366 p.metrics = metrics 367 } 368 } 369 370 func (p *peer) metricsReporter() { 371 for { 372 select { 373 case <-p.metricsTicker.C: 374 status := p.mconn.Status() 375 var sendQueueSize float64 376 for _, chStatus := range status.Channels { 377 sendQueueSize += float64(chStatus.SendQueueSize) 378 } 379 380 p.metrics.PeerPendingSendBytes.With("peer_id", string(p.ID())).Set(sendQueueSize) 381 case <-p.Quit(): 382 return 383 } 384 } 385 } 386 387 //------------------------------------------------------------------ 388 // helper funcs 389 390 func createMConnection( 391 conn net.Conn, 392 p *peer, 393 reactorsByCh map[byte]Reactor, 394 msgTypeByChID map[byte]proto.Message, 395 chDescs []*cmtconn.ChannelDescriptor, 396 onPeerError func(Peer, interface{}), 397 config cmtconn.MConnConfig, 398 ) *cmtconn.MConnection { 399 400 onReceive := func(chID byte, msgBytes []byte) { 401 reactor := reactorsByCh[chID] 402 if reactor == nil { 403 // Note that its ok to panic here as it's caught in the conn._recover, 404 // which does onPeerError. 405 panic(fmt.Sprintf("Unknown channel %X", chID)) 406 } 407 mt := msgTypeByChID[chID] 408 msg := proto.Clone(mt) 409 err := proto.Unmarshal(msgBytes, msg) 410 if err != nil { 411 panic(fmt.Errorf("unmarshaling message: %s into type: %s", err, reflect.TypeOf(mt))) 412 } 413 labels := []string{ 414 "peer_id", string(p.ID()), 415 "chID", fmt.Sprintf("%#x", chID), 416 } 417 if w, ok := msg.(Unwrapper); ok { 418 msg, err = w.Unwrap() 419 if err != nil { 420 panic(fmt.Errorf("unwrapping message: %s", err)) 421 } 422 } 423 p.metrics.PeerReceiveBytesTotal.With(labels...).Add(float64(len(msgBytes))) 424 p.metrics.MessageReceiveBytesTotal.With("message_type", p.mlc.ValueToMetricLabel(msg)).Add(float64(len(msgBytes))) 425 reactor.ReceiveEnvelope(Envelope{ 426 ChannelID: chID, 427 Src: p, 428 Message: msg, 429 }) 430 } 431 432 onError := func(r interface{}) { 433 onPeerError(p, r) 434 } 435 436 return cmtconn.NewMConnectionWithConfig( 437 conn, 438 chDescs, 439 onReceive, 440 onError, 441 config, 442 ) 443 }