github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/p2p/node_info.go (about)

     1  package p2p
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/gnolang/gno/tm2/pkg/bft/state/eventstore"
     7  	"github.com/gnolang/gno/tm2/pkg/strings"
     8  	"github.com/gnolang/gno/tm2/pkg/versionset"
     9  )
    10  
    11  const (
    12  	maxNodeInfoSize = 10240 // 10KB
    13  	maxNumChannels  = 16    // plenty of room for upgrades, for now
    14  )
    15  
    16  // Max size of the NodeInfo struct
    17  func MaxNodeInfoSize() int {
    18  	return maxNodeInfoSize
    19  }
    20  
    21  // -------------------------------------------------------------
    22  
    23  // NodeInfo is the basic node information exchanged
    24  // between two peers during the Tendermint P2P handshake.
    25  type NodeInfo struct {
    26  	// Set of protocol versions
    27  	VersionSet versionset.VersionSet `json:"version_set"`
    28  
    29  	// Authenticate
    30  	NetAddress *NetAddress `json:"net_address"`
    31  
    32  	// Check compatibility.
    33  	// Channels are HexBytes so easier to read as JSON
    34  	Network  string `json:"network"`  // network/chain ID
    35  	Software string `json:"software"` // name of immediate software
    36  	Version  string `json:"version"`  // software major.minor.revision
    37  	Channels []byte `json:"channels"` // channels this node knows about
    38  
    39  	// ASCIIText fields
    40  	Moniker string        `json:"moniker"` // arbitrary moniker
    41  	Other   NodeInfoOther `json:"other"`   // other application specific data
    42  }
    43  
    44  // NodeInfoOther is the misc. application specific data
    45  type NodeInfoOther struct {
    46  	TxIndex    string `json:"tx_index"`
    47  	RPCAddress string `json:"rpc_address"`
    48  }
    49  
    50  // Validate checks the self-reported NodeInfo is safe.
    51  // It returns an error if there
    52  // are too many Channels, if there are any duplicate Channels,
    53  // if the ListenAddr is malformed, or if the ListenAddr is a host name
    54  // that can not be resolved to some IP.
    55  // TODO: constraints for Moniker/Other? Or is that for the UI ?
    56  // JAE: It needs to be done on the client, but to prevent ambiguous
    57  // unicode characters, maybe it's worth sanitizing it here.
    58  // In the future we might want to validate these, once we have a
    59  // name-resolution system up.
    60  // International clients could then use punycode (or we could use
    61  // url-encoding), and we just need to be careful with how we handle that in our
    62  // clients. (e.g. off by default).
    63  func (info NodeInfo) Validate() error {
    64  	// ID is already validated. TODO validate
    65  
    66  	// Validate ListenAddr.
    67  	if info.NetAddress == nil {
    68  		return fmt.Errorf("info.NetAddress cannot be nil")
    69  	}
    70  	if err := info.NetAddress.ValidateLocal(); err != nil {
    71  		return err
    72  	}
    73  
    74  	// Network is validated in CompatibleWith.
    75  
    76  	// Validate Version
    77  	if len(info.Version) > 0 &&
    78  		(!strings.IsASCIIText(info.Version) || strings.ASCIITrim(info.Version) == "") {
    79  		return fmt.Errorf("info.Version must be valid ASCII text without tabs, but got %v", info.Version)
    80  	}
    81  
    82  	// Validate Channels - ensure max and check for duplicates.
    83  	if len(info.Channels) > maxNumChannels {
    84  		return fmt.Errorf("info.Channels is too long (%v). Max is %v", len(info.Channels), maxNumChannels)
    85  	}
    86  	channels := make(map[byte]struct{})
    87  	for _, ch := range info.Channels {
    88  		_, ok := channels[ch]
    89  		if ok {
    90  			return fmt.Errorf("info.Channels contains duplicate channel id %v", ch)
    91  		}
    92  		channels[ch] = struct{}{}
    93  	}
    94  
    95  	// Validate Moniker.
    96  	if !strings.IsASCIIText(info.Moniker) || strings.ASCIITrim(info.Moniker) == "" {
    97  		return fmt.Errorf("info.Moniker must be valid non-empty ASCII text without tabs, but got %v", info.Moniker)
    98  	}
    99  
   100  	// Validate Other.
   101  	other := info.Other
   102  	txIndex := other.TxIndex
   103  	switch txIndex {
   104  	case "", eventstore.StatusOn, eventstore.StatusOff:
   105  	default:
   106  		return fmt.Errorf("info.Other.TxIndex should be either 'on', 'off', or empty string, got '%v'", txIndex)
   107  	}
   108  	// XXX: Should we be more strict about address formats?
   109  	rpcAddr := other.RPCAddress
   110  	if len(rpcAddr) > 0 && (!strings.IsASCIIText(rpcAddr) || strings.ASCIITrim(rpcAddr) == "") {
   111  		return fmt.Errorf("info.Other.RPCAddress=%v must be valid ASCII text without tabs", rpcAddr)
   112  	}
   113  
   114  	return nil
   115  }
   116  
   117  func (info NodeInfo) ID() ID {
   118  	return info.NetAddress.ID
   119  }
   120  
   121  // CompatibleWith checks if two NodeInfo are compatible with eachother.
   122  // CONTRACT: two nodes are compatible if the Block version and network match
   123  // and they have at least one channel in common.
   124  func (info NodeInfo) CompatibleWith(other NodeInfo) error {
   125  	// check protocol versions
   126  	_, err := info.VersionSet.CompatibleWith(other.VersionSet)
   127  	if err != nil {
   128  		return err
   129  	}
   130  
   131  	// nodes must be on the same network
   132  	if info.Network != other.Network {
   133  		return fmt.Errorf("Peer is on a different network. Got %v, expected %v", other.Network, info.Network)
   134  	}
   135  
   136  	// if we have no channels, we're just testing
   137  	if len(info.Channels) == 0 {
   138  		return nil
   139  	}
   140  
   141  	// for each of our channels, check if they have it
   142  	found := false
   143  OUTER_LOOP:
   144  	for _, ch1 := range info.Channels {
   145  		for _, ch2 := range other.Channels {
   146  			if ch1 == ch2 {
   147  				found = true
   148  				break OUTER_LOOP // only need one
   149  			}
   150  		}
   151  	}
   152  	if !found {
   153  		return fmt.Errorf("Peer has no common channels. Our channels: %v ; Peer channels: %v", info.Channels, other.Channels)
   154  	}
   155  	return nil
   156  }