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  }