github.com/Finschia/ostracon@v1.1.5/p2p/node_info.go (about)

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