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