github.com/pure-x-eth/consensus_tm@v0.0.0-20230502163723-e3c2ff987250/p2p/node_info.go (about)

     1  package p2p
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"reflect"
     8  
     9  	tmbytes "github.com/pure-x-eth/consensus_tm/libs/bytes"
    10  	tmstrings "github.com/pure-x-eth/consensus_tm/libs/strings"
    11  	tmp2p "github.com/pure-x-eth/consensus_tm/proto/tendermint/p2p"
    12  	"github.com/pure-x-eth/consensus_tm/version"
    13  )
    14  
    15  const (
    16  	maxNodeInfoSize = 10240 // 10KB
    17  	maxNumChannels  = 16    // plenty of room for upgrades, for now
    18  )
    19  
    20  // Max size of the NodeInfo struct
    21  func MaxNodeInfoSize() int {
    22  	return maxNodeInfoSize
    23  }
    24  
    25  //-------------------------------------------------------------
    26  
    27  // NodeInfo exposes basic info of a node
    28  // and determines if we're compatible.
    29  type NodeInfo interface {
    30  	ID() ID
    31  	nodeInfoAddress
    32  	nodeInfoTransport
    33  }
    34  
    35  type nodeInfoAddress interface {
    36  	NetAddress() (*NetAddress, error)
    37  }
    38  
    39  // nodeInfoTransport validates a nodeInfo and checks
    40  // our compatibility with it. It's for use in the handshake.
    41  type nodeInfoTransport interface {
    42  	Validate() error
    43  	CompatibleWith(other NodeInfo) error
    44  }
    45  
    46  //-------------------------------------------------------------
    47  
    48  // ProtocolVersion contains the protocol versions for the software.
    49  type ProtocolVersion struct {
    50  	P2P   uint64 `json:"p2p"`
    51  	Block uint64 `json:"block"`
    52  	App   uint64 `json:"app"`
    53  }
    54  
    55  // defaultProtocolVersion populates the Block and P2P versions using
    56  // the global values, but not the App.
    57  var defaultProtocolVersion = NewProtocolVersion(
    58  	version.P2PProtocol,
    59  	version.BlockProtocol,
    60  	0,
    61  )
    62  
    63  // NewProtocolVersion returns a fully populated ProtocolVersion.
    64  func NewProtocolVersion(p2p, block, app uint64) ProtocolVersion {
    65  	return ProtocolVersion{
    66  		P2P:   p2p,
    67  		Block: block,
    68  		App:   app,
    69  	}
    70  }
    71  
    72  //-------------------------------------------------------------
    73  
    74  // Assert DefaultNodeInfo satisfies NodeInfo
    75  var _ NodeInfo = DefaultNodeInfo{}
    76  
    77  // DefaultNodeInfo is the basic node information exchanged
    78  // between two peers during the Tendermint P2P handshake.
    79  type DefaultNodeInfo struct {
    80  	ProtocolVersion ProtocolVersion `json:"protocol_version"`
    81  
    82  	// Authenticate
    83  	// TODO: replace with NetAddress
    84  	DefaultNodeID ID     `json:"id"`          // authenticated identifier
    85  	ListenAddr    string `json:"listen_addr"` // accepting incoming
    86  
    87  	// Check compatibility.
    88  	// Channels are HexBytes so easier to read as JSON
    89  	Network  string           `json:"network"`  // network/chain ID
    90  	Version  string           `json:"version"`  // major.minor.revision
    91  	Channels tmbytes.HexBytes `json:"channels"` // channels this node knows about
    92  
    93  	// ASCIIText fields
    94  	Moniker string               `json:"moniker"` // arbitrary moniker
    95  	Other   DefaultNodeInfoOther `json:"other"`   // other application specific data
    96  }
    97  
    98  // DefaultNodeInfoOther is the misc. applcation specific data
    99  type DefaultNodeInfoOther struct {
   100  	TxIndex    string `json:"tx_index"`
   101  	RPCAddress string `json:"rpc_address"`
   102  }
   103  
   104  // ID returns the node's peer ID.
   105  func (info DefaultNodeInfo) ID() ID {
   106  	return info.DefaultNodeID
   107  }
   108  
   109  // Validate checks the self-reported DefaultNodeInfo is safe.
   110  // It returns an error if there
   111  // are too many Channels, if there are any duplicate Channels,
   112  // if the ListenAddr is malformed, or if the ListenAddr is a host name
   113  // that can not be resolved to some IP.
   114  // TODO: constraints for Moniker/Other? Or is that for the UI ?
   115  // JAE: It needs to be done on the client, but to prevent ambiguous
   116  // unicode characters, maybe it's worth sanitizing it here.
   117  // In the future we might want to validate these, once we have a
   118  // name-resolution system up.
   119  // International clients could then use punycode (or we could use
   120  // url-encoding), and we just need to be careful with how we handle that in our
   121  // clients. (e.g. off by default).
   122  func (info DefaultNodeInfo) Validate() error {
   123  
   124  	// ID is already validated.
   125  
   126  	// Validate ListenAddr.
   127  	_, err := NewNetAddressString(IDAddressString(info.ID(), info.ListenAddr))
   128  	if err != nil {
   129  		return err
   130  	}
   131  
   132  	// Network is validated in CompatibleWith.
   133  
   134  	// Validate Version
   135  	if len(info.Version) > 0 &&
   136  		(!tmstrings.IsASCIIText(info.Version) || tmstrings.ASCIITrim(info.Version) == "") {
   137  
   138  		return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %v", info.Version)
   139  	}
   140  
   141  	// Validate Channels - ensure max and check for duplicates.
   142  	if len(info.Channels) > maxNumChannels {
   143  		return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels)
   144  	}
   145  	channels := make(map[byte]struct{})
   146  	for _, ch := range info.Channels {
   147  		_, ok := channels[ch]
   148  		if ok {
   149  			return fmt.Errorf("info.Channels contains duplicate channel id %v", ch)
   150  		}
   151  		channels[ch] = struct{}{}
   152  	}
   153  
   154  	// Validate Moniker.
   155  	if !tmstrings.IsASCIIText(info.Moniker) || tmstrings.ASCIITrim(info.Moniker) == "" {
   156  		return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker)
   157  	}
   158  
   159  	// Validate Other.
   160  	other := info.Other
   161  	txIndex := other.TxIndex
   162  	switch txIndex {
   163  	case "", "on", "off":
   164  	default:
   165  		return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex)
   166  	}
   167  	// XXX: Should we be more strict about address formats?
   168  	rpcAddr := other.RPCAddress
   169  	if len(rpcAddr) > 0 && (!tmstrings.IsASCIIText(rpcAddr) || tmstrings.ASCIITrim(rpcAddr) == "") {
   170  		return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr)
   171  	}
   172  
   173  	return nil
   174  }
   175  
   176  // CompatibleWith checks if two DefaultNodeInfo are compatible with eachother.
   177  // CONTRACT: two nodes are compatible if the Block version and network match
   178  // and they have at least one channel in common.
   179  func (info DefaultNodeInfo) CompatibleWith(otherInfo NodeInfo) error {
   180  	other, ok := otherInfo.(DefaultNodeInfo)
   181  	if !ok {
   182  		return fmt.Errorf("wrong NodeInfo type. Expected DefaultNodeInfo, got %v", reflect.TypeOf(otherInfo))
   183  	}
   184  
   185  	if info.ProtocolVersion.Block != other.ProtocolVersion.Block {
   186  		return fmt.Errorf("peer is on a different Block version. Got %v, expected %v",
   187  			other.ProtocolVersion.Block, info.ProtocolVersion.Block)
   188  	}
   189  
   190  	// nodes must be on the same network
   191  	if info.Network != other.Network {
   192  		return fmt.Errorf("peer is on a different network. Got %v, expected %v", other.Network, info.Network)
   193  	}
   194  
   195  	// if we have no channels, we're just testing
   196  	if len(info.Channels) == 0 {
   197  		return nil
   198  	}
   199  
   200  	// for each of our channels, check if they have it
   201  	found := false
   202  OUTER_LOOP:
   203  	for _, ch1 := range info.Channels {
   204  		for _, ch2 := range other.Channels {
   205  			if ch1 == ch2 {
   206  				found = true
   207  				break OUTER_LOOP // only need one
   208  			}
   209  		}
   210  	}
   211  	if !found {
   212  		return fmt.Errorf("peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels)
   213  	}
   214  	return nil
   215  }
   216  
   217  // NetAddress returns a NetAddress derived from the DefaultNodeInfo -
   218  // it includes the authenticated peer ID and the self-reported
   219  // ListenAddr. Note that the ListenAddr is not authenticated and
   220  // may not match that address actually dialed if its an outbound peer.
   221  func (info DefaultNodeInfo) NetAddress() (*NetAddress, error) {
   222  	idAddr := IDAddressString(info.ID(), info.ListenAddr)
   223  	return NewNetAddressString(idAddr)
   224  }
   225  
   226  func (info DefaultNodeInfo) HasChannel(chID byte) bool {
   227  	return bytes.Contains(info.Channels, []byte{chID})
   228  }
   229  
   230  func (info DefaultNodeInfo) ToProto() *tmp2p.DefaultNodeInfo {
   231  
   232  	dni := new(tmp2p.DefaultNodeInfo)
   233  	dni.ProtocolVersion = tmp2p.ProtocolVersion{
   234  		P2P:   info.ProtocolVersion.P2P,
   235  		Block: info.ProtocolVersion.Block,
   236  		App:   info.ProtocolVersion.App,
   237  	}
   238  
   239  	dni.DefaultNodeID = string(info.DefaultNodeID)
   240  	dni.ListenAddr = info.ListenAddr
   241  	dni.Network = info.Network
   242  	dni.Version = info.Version
   243  	dni.Channels = info.Channels
   244  	dni.Moniker = info.Moniker
   245  	dni.Other = tmp2p.DefaultNodeInfoOther{
   246  		TxIndex:    info.Other.TxIndex,
   247  		RPCAddress: info.Other.RPCAddress,
   248  	}
   249  
   250  	return dni
   251  }
   252  
   253  func DefaultNodeInfoFromToProto(pb *tmp2p.DefaultNodeInfo) (DefaultNodeInfo, error) {
   254  	if pb == nil {
   255  		return DefaultNodeInfo{}, errors.New("nil node info")
   256  	}
   257  	dni := DefaultNodeInfo{
   258  		ProtocolVersion: ProtocolVersion{
   259  			P2P:   pb.ProtocolVersion.P2P,
   260  			Block: pb.ProtocolVersion.Block,
   261  			App:   pb.ProtocolVersion.App,
   262  		},
   263  		DefaultNodeID: ID(pb.DefaultNodeID),
   264  		ListenAddr:    pb.ListenAddr,
   265  		Network:       pb.Network,
   266  		Version:       pb.Version,
   267  		Channels:      pb.Channels,
   268  		Moniker:       pb.Moniker,
   269  		Other: DefaultNodeInfoOther{
   270  			TxIndex:    pb.Other.TxIndex,
   271  			RPCAddress: pb.Other.RPCAddress,
   272  		},
   273  	}
   274  
   275  	return dni, nil
   276  }