@ -3,9 +3,9 @@ package p2p
import (
"fmt"
"reflect"
"strings"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/version"
)
const (
@ -18,8 +18,10 @@ func MaxNodeInfoSize() int {
return maxNodeInfoSize
}
//-------------------------------------------------------------
// NodeInfo exposes basic info of a node
// and determines if we're compatible
// and determines if we're compatible.
type NodeInfo interface {
nodeInfoAddress
nodeInfoTransport
@ -31,16 +33,38 @@ type nodeInfoAddress interface {
NetAddress ( ) * NetAddress
}
// nodeInfoTransport is validates a nodeInfo and checks
// nodeInfoTransport validates a nodeInfo and checks
// our compatibility with it. It's for use in the handshake.
type nodeInfoTransport interface {
ValidateBasic ( ) error
CompatibleWith ( other NodeInfo ) error
}
//-------------------------------------------------------------
// ProtocolVersion contains the protocol versions for the software.
type ProtocolVersion struct {
P2P version . Protocol ` json:"p2p" `
Block version . Protocol ` json:"block" `
App version . Protocol ` json:"app" `
}
var InitProtocolVersion = ProtocolVersion {
P2P : version . P2PProtocol ,
Block : version . BlockProtocol ,
App : 0 ,
}
//-------------------------------------------------------------
// Assert DefaultNodeInfo satisfies NodeInfo
var _ NodeInfo = DefaultNodeInfo { }
// DefaultNodeInfo is the basic node information exchanged
// between two peers during the Tendermint P2P handshake.
type DefaultNodeInfo struct {
ProtocolVersion ProtocolVersion ` json:"protocol_version" `
// Authenticate
// TODO: replace with NetAddress
ID_ ID ` json:"id" ` // authenticated identifier
@ -59,12 +83,8 @@ type DefaultNodeInfo struct {
// DefaultNodeInfoOther is the misc. applcation specific data
type DefaultNodeInfoOther struct {
AminoVersion string ` json:"amino_version" `
P2PVersion string ` json:"p2p_version" `
ConsensusVersion string ` json:"consensus_version" `
RPCVersion string ` json:"rpc_version" `
TxIndex string ` json:"tx_index" `
RPCAddress string ` json:"rpc_address" `
TxIndex string ` json:"tx_index" `
RPCAddress string ` json:"rpc_address" `
}
// ID returns the node's peer ID.
@ -86,35 +106,28 @@ func (info DefaultNodeInfo) ID() ID {
// url-encoding), and we just need to be careful with how we handle that in our
// clients. (e.g. off by default).
func ( info DefaultNodeInfo ) ValidateBasic ( ) error {
if len ( info . Channels ) > maxNumChannels {
return fmt . Errorf ( "info.Channels is too long (%v). Max is %v" , len ( info . Channels ) , maxNumChannels )
}
// Sanitize ASCII text fields.
if ! cmn . IsASCIIText ( info . Moniker ) || cmn . ASCIITrim ( info . Moniker ) == "" {
return fmt . Errorf ( "info.Moniker must be valid non-empty ASCII text without tabs, but got %v" , info . Moniker )
}
// ID is already validated.
// Sanitize versions
// XXX: Should we be more strict about version and address formats?
other := info . Other
versions := [ ] string {
other . AminoVersion ,
other . P2PVersion ,
other . ConsensusVersion ,
other . RPCVersion }
for i , v := range versions {
if cmn . ASCIITrim ( v ) != "" && ! cmn . IsASCIIText ( v ) {
return fmt . Errorf ( "info.Other[%d]=%v must be valid non-empty ASCII text without tabs" , i , v )
}
}
if cmn . ASCIITrim ( other . TxIndex ) != "" && ( other . TxIndex != "on" && other . TxIndex != "off" ) {
return fmt . Errorf ( "info.Other.TxIndex should be either 'on' or 'off', got '%v'" , other . TxIndex )
// Validate ListenAddr.
_ , err := NewNetAddressString ( IDAddressString ( info . ID ( ) , info . ListenAddr ) )
if err != nil {
return err
}
if cmn . ASCIITrim ( other . RPCAddress ) != "" && ! cmn . IsASCIIText ( other . RPCAddress ) {
return fmt . Errorf ( "info.Other.RPCAddress=%v must be valid non-empty ASCII text without tabs" , other . RPCAddress )
// Network is validated in CompatibleWith.
// Validate Version
if len ( info . Version ) > 0 &&
( ! cmn . IsASCIIText ( info . Version ) || cmn . ASCIITrim ( info . Version ) == "" ) {
return fmt . Errorf ( "info.Version must be valid ASCII text without tabs, but got %v" , info . Version )
}
// Validate Channels - ensure max and check for duplicates.
if len ( info . Channels ) > maxNumChannels {
return fmt . Errorf ( "info.Channels is too long (%v). Max is %v" , len ( info . Channels ) , maxNumChannels )
}
channels := make ( map [ byte ] struct { } )
for _ , ch := range info . Channels {
_ , ok := channels [ ch ]
@ -124,13 +137,30 @@ func (info DefaultNodeInfo) ValidateBasic() error {
channels [ ch ] = struct { } { }
}
// ensure ListenAddr is good
_ , err := NewNetAddressString ( IDAddressString ( info . ID ( ) , info . ListenAddr ) )
return err
// Validate Moniker.
if ! cmn . IsASCIIText ( info . Moniker ) || cmn . ASCIITrim ( info . Moniker ) == "" {
return fmt . Errorf ( "info.Moniker must be valid non-empty ASCII text without tabs, but got %v" , info . Moniker )
}
// Validate Other.
other := info . Other
txIndex := other . TxIndex
switch txIndex {
case "" , "on" , "off" :
default :
return fmt . Errorf ( "info.Other.TxIndex should be either 'on' or 'off', got '%v'" , txIndex )
}
// XXX: Should we be more strict about address formats?
rpcAddr := other . RPCAddress
if len ( rpcAddr ) > 0 && ( ! cmn . IsASCIIText ( rpcAddr ) || cmn . ASCIITrim ( rpcAddr ) == "" ) {
return fmt . Errorf ( "info.Other.RPCAddress=%v must be valid ASCII text without tabs" , rpcAddr )
}
return nil
}
// CompatibleWith checks if two DefaultNodeInfo are compatible with eachother.
// CONTRACT: two nodes are compatible if the major version matches and network match
// CONTRACT: two nodes are compatible if the Block version and network match
// and they have at least one channel in common.
func ( info DefaultNodeInfo ) CompatibleWith ( other_ NodeInfo ) error {
other , ok := other_ . ( DefaultNodeInfo )
@ -138,22 +168,9 @@ func (info DefaultNodeInfo) CompatibleWith(other_ NodeInfo) error {
return fmt . Errorf ( "wrong NodeInfo type. Expected DefaultNodeInfo, got %v" , reflect . TypeOf ( other_ ) )
}
iMajor , _ , _ , iErr := splitVersion ( info . Version )
oMajor , _ , _ , oErr := splitVersion ( other . Version )
// if our own version number is not formatted right, we messed up
if iErr != nil {
return iErr
}
// version number must be formatted correctly ("x.x.x")
if oErr != nil {
return oErr
}
// major version must match
if iMajor != oMajor {
return fmt . Errorf ( "Peer is on a different major version. Got %v, expected %v" , oMajor , iMajor )
if info . ProtocolVersion . Block != other . ProtocolVersion . Block {
return fmt . Errorf ( "Peer is on a different Block version. Got %v, expected %v" ,
other . ProtocolVersion . Block , info . ProtocolVersion . Block )
}
// nodes must be on the same network
@ -201,11 +218,3 @@ func (info DefaultNodeInfo) NetAddress() *NetAddress {
}
return netAddr
}
func splitVersion ( version string ) ( string , string , string , error ) {
spl := strings . Split ( version , "." )
if len ( spl ) != 3 {
return "" , "" , "" , fmt . Errorf ( "Invalid version format %v" , version )
}
return spl [ 0 ] , spl [ 1 ] , spl [ 2 ] , nil
}