github.com/ari-anchor/sei-tendermint@v0.0.0-20230519144642-dc826b7b56bb/types/node_info.go (about)

     1  package types
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"net"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/ari-anchor/sei-tendermint/libs/bytes"
    11  	tmstrings "github.com/ari-anchor/sei-tendermint/libs/strings"
    12  	tmp2p "github.com/ari-anchor/sei-tendermint/proto/tendermint/p2p"
    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  // ProtocolVersion contains the protocol versions for the software.
    26  type ProtocolVersion struct {
    27  	P2P   uint64 `json:"p2p,string"`
    28  	Block uint64 `json:"block,string"`
    29  	App   uint64 `json:"app,string"`
    30  }
    31  
    32  //-------------------------------------------------------------
    33  
    34  // NodeInfo is the basic node information exchanged
    35  // between two peers during the Tendermint P2P handshake.
    36  type NodeInfo struct {
    37  	ProtocolVersion ProtocolVersion `json:"protocol_version"`
    38  
    39  	// Authenticate
    40  	NodeID     NodeID `json:"id"`          // authenticated identifier
    41  	ListenAddr string `json:"listen_addr"` // accepting incoming
    42  
    43  	// Check compatibility.
    44  	// Channels are HexBytes so easier to read as JSON
    45  	Network string `json:"network"` // network/chain ID
    46  	Version string `json:"version"` // major.minor.revision
    47  	// FIXME: This should be changed to uint16 to be consistent with the updated channel type
    48  	Channels bytes.HexBytes `json:"channels"` // channels this node knows about
    49  
    50  	// ASCIIText fields
    51  	Moniker string        `json:"moniker"` // arbitrary moniker
    52  	Other   NodeInfoOther `json:"other"`   // other application specific data
    53  }
    54  
    55  // NodeInfoOther is the misc. applcation specific data
    56  type NodeInfoOther struct {
    57  	TxIndex    string `json:"tx_index"`
    58  	RPCAddress string `json:"rpc_address"`
    59  }
    60  
    61  // ID returns the node's peer ID.
    62  func (info NodeInfo) ID() NodeID {
    63  	return info.NodeID
    64  }
    65  
    66  // Validate checks the self-reported NodeInfo is safe.
    67  // It returns an error if there
    68  // are too many Channels, if there are any duplicate Channels,
    69  // if the ListenAddr is malformed, or if the ListenAddr is a host name
    70  // that can not be resolved to some IP.
    71  // TODO: constraints for Moniker/Other? Or is that for the UI ?
    72  // JAE: It needs to be done on the client, but to prevent ambiguous
    73  // unicode characters, maybe it's worth sanitizing it here.
    74  // In the future we might want to validate these, once we have a
    75  // name-resolution system up.
    76  // International clients could then use punycode (or we could use
    77  // url-encoding), and we just need to be careful with how we handle that in our
    78  // clients. (e.g. off by default).
    79  func (info NodeInfo) Validate() error {
    80  	if _, _, err := ParseAddressString(info.ID().AddressString(info.ListenAddr)); err != nil {
    81  		return err
    82  	}
    83  
    84  	// Validate Version
    85  	if len(info.Version) > 0 {
    86  		if ver, err := tmstrings.ASCIITrim(info.Version); err != nil || ver == "" {
    87  			return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got, %q [%s]", info.Version, ver)
    88  		}
    89  	}
    90  
    91  	// Validate Channels - ensure max and check for duplicates.
    92  	if len(info.Channels) > maxNumChannels {
    93  		return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels)
    94  	}
    95  	channels := make(map[byte]struct{})
    96  	for _, ch := range info.Channels {
    97  		_, ok := channels[ch]
    98  		if ok {
    99  			return fmt.Errorf("info.Channels contains duplicate channel id %v", ch)
   100  		}
   101  		channels[ch] = struct{}{}
   102  	}
   103  
   104  	if m, err := tmstrings.ASCIITrim(info.Moniker); err != nil || m == "" {
   105  		return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker)
   106  	}
   107  
   108  	// Validate Other.
   109  	other := info.Other
   110  	txIndex := other.TxIndex
   111  	switch txIndex {
   112  	case "", "on", "off":
   113  	default:
   114  		return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex)
   115  	}
   116  	// XXX: Should we be more strict about address formats?
   117  	rpcAddr := other.RPCAddress
   118  	if len(rpcAddr) > 0 {
   119  		if a, err := tmstrings.ASCIITrim(rpcAddr); err != nil || a == "" {
   120  			return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr)
   121  		}
   122  	}
   123  
   124  	return nil
   125  }
   126  
   127  // CompatibleWith checks if two NodeInfo are compatible with each other.
   128  // CONTRACT: two nodes are compatible if the Block version and network match
   129  // and they have at least one channel in common.
   130  func (info NodeInfo) CompatibleWith(other NodeInfo) error {
   131  	if info.ProtocolVersion.Block != other.ProtocolVersion.Block {
   132  		return fmt.Errorf("peer is on a different Block version. Got %v, expected %v",
   133  			other.ProtocolVersion.Block, info.ProtocolVersion.Block)
   134  	}
   135  
   136  	// nodes must be on the same network
   137  	if info.Network != other.Network {
   138  		return fmt.Errorf("peer is on a different network. Got %v, expected %v", other.Network, info.Network)
   139  	}
   140  
   141  	// if we have no channels, we're just testing
   142  	if len(info.Channels) == 0 {
   143  		return nil
   144  	}
   145  
   146  	// for each of our channels, check if they have it
   147  	found := false
   148  OUTER_LOOP:
   149  	for _, ch1 := range info.Channels {
   150  		for _, ch2 := range other.Channels {
   151  			if ch1 == ch2 {
   152  				found = true
   153  				break OUTER_LOOP // only need one
   154  			}
   155  		}
   156  	}
   157  	if !found {
   158  		return fmt.Errorf("peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels)
   159  	}
   160  	return nil
   161  }
   162  
   163  // AddChannel is used by the router when a channel is opened to add it to the node info
   164  func (info *NodeInfo) AddChannel(channel uint16) {
   165  	// check that the channel doesn't already exist
   166  	for _, ch := range info.Channels {
   167  		if ch == byte(channel) {
   168  			return
   169  		}
   170  	}
   171  
   172  	info.Channels = append(info.Channels, byte(channel))
   173  }
   174  
   175  func (info NodeInfo) Copy() NodeInfo {
   176  	return NodeInfo{
   177  		ProtocolVersion: info.ProtocolVersion,
   178  		NodeID:          info.NodeID,
   179  		ListenAddr:      info.ListenAddr,
   180  		Network:         info.Network,
   181  		Version:         info.Version,
   182  		Channels:        info.Channels,
   183  		Moniker:         info.Moniker,
   184  		Other:           info.Other,
   185  	}
   186  }
   187  
   188  func (info NodeInfo) ToProto() *tmp2p.NodeInfo {
   189  
   190  	dni := new(tmp2p.NodeInfo)
   191  	dni.ProtocolVersion = tmp2p.ProtocolVersion{
   192  		P2P:   info.ProtocolVersion.P2P,
   193  		Block: info.ProtocolVersion.Block,
   194  		App:   info.ProtocolVersion.App,
   195  	}
   196  
   197  	dni.NodeID = string(info.NodeID)
   198  	dni.ListenAddr = info.ListenAddr
   199  	dni.Network = info.Network
   200  	dni.Version = info.Version
   201  	dni.Channels = info.Channels
   202  	dni.Moniker = info.Moniker
   203  	dni.Other = tmp2p.NodeInfoOther{
   204  		TxIndex:    info.Other.TxIndex,
   205  		RPCAddress: info.Other.RPCAddress,
   206  	}
   207  
   208  	return dni
   209  }
   210  
   211  func NodeInfoFromProto(pb *tmp2p.NodeInfo) (NodeInfo, error) {
   212  	if pb == nil {
   213  		return NodeInfo{}, errors.New("nil node info")
   214  	}
   215  	dni := NodeInfo{
   216  		ProtocolVersion: ProtocolVersion{
   217  			P2P:   pb.ProtocolVersion.P2P,
   218  			Block: pb.ProtocolVersion.Block,
   219  			App:   pb.ProtocolVersion.App,
   220  		},
   221  		NodeID:     NodeID(pb.NodeID),
   222  		ListenAddr: pb.ListenAddr,
   223  		Network:    pb.Network,
   224  		Version:    pb.Version,
   225  		Channels:   pb.Channels,
   226  		Moniker:    pb.Moniker,
   227  		Other: NodeInfoOther{
   228  			TxIndex:    pb.Other.TxIndex,
   229  			RPCAddress: pb.Other.RPCAddress,
   230  		},
   231  	}
   232  
   233  	return dni, nil
   234  }
   235  
   236  // ParseAddressString reads an address string, and returns the IP
   237  // address and port information, returning an error for any validation
   238  // errors.
   239  func ParseAddressString(addr string) (net.IP, uint16, error) {
   240  	addrWithoutProtocol := removeProtocolIfDefined(addr)
   241  	spl := strings.Split(addrWithoutProtocol, "@")
   242  	if len(spl) != 2 {
   243  		return nil, 0, errors.New("invalid address")
   244  	}
   245  
   246  	id, err := NewNodeID(spl[0])
   247  	if err != nil {
   248  		return nil, 0, err
   249  	}
   250  
   251  	if err := id.Validate(); err != nil {
   252  		return nil, 0, err
   253  	}
   254  
   255  	addrWithoutProtocol = spl[1]
   256  
   257  	// get host and port
   258  	host, portStr, err := net.SplitHostPort(addrWithoutProtocol)
   259  	if err != nil {
   260  		return nil, 0, err
   261  	}
   262  	if len(host) == 0 {
   263  		return nil, 0, err
   264  	}
   265  
   266  	ip := net.ParseIP(host)
   267  	if ip == nil {
   268  		ips, err := net.LookupIP(host)
   269  		if err != nil {
   270  			return nil, 0, err
   271  		}
   272  		ip = ips[0]
   273  	}
   274  
   275  	port, err := strconv.ParseUint(portStr, 10, 16)
   276  	if err != nil {
   277  		return nil, 0, err
   278  	}
   279  
   280  	return ip, uint16(port), nil
   281  }
   282  
   283  func removeProtocolIfDefined(addr string) string {
   284  	if strings.Contains(addr, "://") {
   285  		return strings.Split(addr, "://")[1]
   286  	}
   287  	return addr
   288  
   289  }