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 }