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 }