github.com/MetalBlockchain/metalgo@v1.11.9/api/info/service.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package info 5 6 import ( 7 "errors" 8 "fmt" 9 "net/http" 10 "net/netip" 11 12 "github.com/gorilla/rpc/v2" 13 "go.uber.org/zap" 14 15 "github.com/MetalBlockchain/metalgo/chains" 16 "github.com/MetalBlockchain/metalgo/ids" 17 "github.com/MetalBlockchain/metalgo/network" 18 "github.com/MetalBlockchain/metalgo/network/peer" 19 "github.com/MetalBlockchain/metalgo/snow/networking/benchlist" 20 "github.com/MetalBlockchain/metalgo/snow/validators" 21 "github.com/MetalBlockchain/metalgo/utils" 22 "github.com/MetalBlockchain/metalgo/utils/constants" 23 "github.com/MetalBlockchain/metalgo/utils/json" 24 "github.com/MetalBlockchain/metalgo/utils/logging" 25 "github.com/MetalBlockchain/metalgo/utils/set" 26 "github.com/MetalBlockchain/metalgo/version" 27 "github.com/MetalBlockchain/metalgo/vms" 28 "github.com/MetalBlockchain/metalgo/vms/nftfx" 29 "github.com/MetalBlockchain/metalgo/vms/platformvm/signer" 30 "github.com/MetalBlockchain/metalgo/vms/propertyfx" 31 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 32 ) 33 34 var errNoChainProvided = errors.New("argument 'chain' not given") 35 36 // Info is the API service for unprivileged info on a node 37 type Info struct { 38 Parameters 39 log logging.Logger 40 validators validators.Manager 41 myIP *utils.Atomic[netip.AddrPort] 42 networking network.Network 43 chainManager chains.Manager 44 vmManager vms.Manager 45 benchlist benchlist.Manager 46 } 47 48 type Parameters struct { 49 Version *version.Application 50 NodeID ids.NodeID 51 NodePOP *signer.ProofOfPossession 52 NetworkID uint32 53 TxFee uint64 54 CreateAssetTxFee uint64 55 CreateSubnetTxFee uint64 56 TransformSubnetTxFee uint64 57 CreateBlockchainTxFee uint64 58 AddPrimaryNetworkValidatorFee uint64 59 AddPrimaryNetworkDelegatorFee uint64 60 AddSubnetValidatorFee uint64 61 AddSubnetDelegatorFee uint64 62 VMManager vms.Manager 63 } 64 65 func NewService( 66 parameters Parameters, 67 log logging.Logger, 68 validators validators.Manager, 69 chainManager chains.Manager, 70 vmManager vms.Manager, 71 myIP *utils.Atomic[netip.AddrPort], 72 network network.Network, 73 benchlist benchlist.Manager, 74 ) (http.Handler, error) { 75 server := rpc.NewServer() 76 codec := json.NewCodec() 77 server.RegisterCodec(codec, "application/json") 78 server.RegisterCodec(codec, "application/json;charset=UTF-8") 79 return server, server.RegisterService( 80 &Info{ 81 Parameters: parameters, 82 log: log, 83 validators: validators, 84 chainManager: chainManager, 85 vmManager: vmManager, 86 myIP: myIP, 87 networking: network, 88 benchlist: benchlist, 89 }, 90 "info", 91 ) 92 } 93 94 // GetNodeVersionReply are the results from calling GetNodeVersion 95 type GetNodeVersionReply struct { 96 Version string `json:"version"` 97 DatabaseVersion string `json:"databaseVersion"` 98 RPCProtocolVersion json.Uint32 `json:"rpcProtocolVersion"` 99 GitCommit string `json:"gitCommit"` 100 VMVersions map[string]string `json:"vmVersions"` 101 } 102 103 // GetNodeVersion returns the version this node is running 104 func (i *Info) GetNodeVersion(_ *http.Request, _ *struct{}, reply *GetNodeVersionReply) error { 105 i.log.Debug("API called", 106 zap.String("service", "info"), 107 zap.String("method", "getNodeVersion"), 108 ) 109 110 vmVersions, err := i.vmManager.Versions() 111 if err != nil { 112 return err 113 } 114 115 reply.Version = i.Version.String() 116 reply.DatabaseVersion = version.CurrentDatabase.String() 117 reply.RPCProtocolVersion = json.Uint32(version.RPCChainVMProtocol) 118 reply.GitCommit = version.GitCommit 119 reply.VMVersions = vmVersions 120 return nil 121 } 122 123 // GetNodeIDReply are the results from calling GetNodeID 124 type GetNodeIDReply struct { 125 NodeID ids.NodeID `json:"nodeID"` 126 NodePOP *signer.ProofOfPossession `json:"nodePOP"` 127 } 128 129 // GetNodeID returns the node ID of this node 130 func (i *Info) GetNodeID(_ *http.Request, _ *struct{}, reply *GetNodeIDReply) error { 131 i.log.Debug("API called", 132 zap.String("service", "info"), 133 zap.String("method", "getNodeID"), 134 ) 135 136 reply.NodeID = i.NodeID 137 reply.NodePOP = i.NodePOP 138 return nil 139 } 140 141 // GetNetworkIDReply are the results from calling GetNetworkID 142 type GetNetworkIDReply struct { 143 NetworkID json.Uint32 `json:"networkID"` 144 } 145 146 // GetNodeIPReply are the results from calling GetNodeIP 147 type GetNodeIPReply struct { 148 IP netip.AddrPort `json:"ip"` 149 } 150 151 // GetNodeIP returns the IP of this node 152 func (i *Info) GetNodeIP(_ *http.Request, _ *struct{}, reply *GetNodeIPReply) error { 153 i.log.Debug("API called", 154 zap.String("service", "info"), 155 zap.String("method", "getNodeIP"), 156 ) 157 158 reply.IP = i.myIP.Get() 159 return nil 160 } 161 162 // GetNetworkID returns the network ID this node is running on 163 func (i *Info) GetNetworkID(_ *http.Request, _ *struct{}, reply *GetNetworkIDReply) error { 164 i.log.Debug("API called", 165 zap.String("service", "info"), 166 zap.String("method", "getNetworkID"), 167 ) 168 169 reply.NetworkID = json.Uint32(i.NetworkID) 170 return nil 171 } 172 173 // GetNetworkNameReply is the result from calling GetNetworkName 174 type GetNetworkNameReply struct { 175 NetworkName string `json:"networkName"` 176 } 177 178 // GetNetworkName returns the network name this node is running on 179 func (i *Info) GetNetworkName(_ *http.Request, _ *struct{}, reply *GetNetworkNameReply) error { 180 i.log.Debug("API called", 181 zap.String("service", "info"), 182 zap.String("method", "getNetworkName"), 183 ) 184 185 reply.NetworkName = constants.NetworkName(i.NetworkID) 186 return nil 187 } 188 189 // GetBlockchainIDArgs are the arguments for calling GetBlockchainID 190 type GetBlockchainIDArgs struct { 191 Alias string `json:"alias"` 192 } 193 194 // GetBlockchainIDReply are the results from calling GetBlockchainID 195 type GetBlockchainIDReply struct { 196 BlockchainID ids.ID `json:"blockchainID"` 197 } 198 199 // GetBlockchainID returns the blockchain ID that resolves the alias that was supplied 200 func (i *Info) GetBlockchainID(_ *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error { 201 i.log.Debug("API called", 202 zap.String("service", "info"), 203 zap.String("method", "getBlockchainID"), 204 ) 205 206 bID, err := i.chainManager.Lookup(args.Alias) 207 reply.BlockchainID = bID 208 return err 209 } 210 211 // PeersArgs are the arguments for calling Peers 212 type PeersArgs struct { 213 NodeIDs []ids.NodeID `json:"nodeIDs"` 214 } 215 216 type Peer struct { 217 peer.Info 218 219 Benched []string `json:"benched"` 220 } 221 222 // PeersReply are the results from calling Peers 223 type PeersReply struct { 224 // Number of elements in [Peers] 225 NumPeers json.Uint64 `json:"numPeers"` 226 // Each element is a peer 227 Peers []Peer `json:"peers"` 228 } 229 230 // Peers returns the list of current validators 231 func (i *Info) Peers(_ *http.Request, args *PeersArgs, reply *PeersReply) error { 232 i.log.Debug("API called", 233 zap.String("service", "info"), 234 zap.String("method", "peers"), 235 ) 236 237 peers := i.networking.PeerInfo(args.NodeIDs) 238 peerInfo := make([]Peer, len(peers)) 239 for index, peer := range peers { 240 benchedIDs := i.benchlist.GetBenched(peer.ID) 241 benchedAliases := make([]string, len(benchedIDs)) 242 for idx, id := range benchedIDs { 243 alias, err := i.chainManager.PrimaryAlias(id) 244 if err != nil { 245 return fmt.Errorf("failed to get primary alias for chain ID %s: %w", id, err) 246 } 247 benchedAliases[idx] = alias 248 } 249 peerInfo[index] = Peer{ 250 Info: peer, 251 Benched: benchedAliases, 252 } 253 } 254 255 reply.Peers = peerInfo 256 reply.NumPeers = json.Uint64(len(reply.Peers)) 257 return nil 258 } 259 260 // IsBootstrappedArgs are the arguments for calling IsBootstrapped 261 type IsBootstrappedArgs struct { 262 // Alias of the chain 263 // Can also be the string representation of the chain's ID 264 Chain string `json:"chain"` 265 } 266 267 // IsBootstrappedResponse are the results from calling IsBootstrapped 268 type IsBootstrappedResponse struct { 269 // True iff the chain exists and is done bootstrapping 270 IsBootstrapped bool `json:"isBootstrapped"` 271 } 272 273 // IsBootstrapped returns nil and sets [reply.IsBootstrapped] == true iff [args.Chain] exists and is done bootstrapping 274 // Returns an error if the chain doesn't exist 275 func (i *Info) IsBootstrapped(_ *http.Request, args *IsBootstrappedArgs, reply *IsBootstrappedResponse) error { 276 i.log.Debug("API called", 277 zap.String("service", "info"), 278 zap.String("method", "isBootstrapped"), 279 logging.UserString("chain", args.Chain), 280 ) 281 282 if args.Chain == "" { 283 return errNoChainProvided 284 } 285 chainID, err := i.chainManager.Lookup(args.Chain) 286 if err != nil { 287 return fmt.Errorf("there is no chain with alias/ID '%s'", args.Chain) 288 } 289 reply.IsBootstrapped = i.chainManager.IsBootstrapped(chainID) 290 return nil 291 } 292 293 // UptimeResponse are the results from calling Uptime 294 type UptimeResponse struct { 295 // RewardingStakePercentage shows what percent of network stake thinks we're 296 // above the uptime requirement. 297 RewardingStakePercentage json.Float64 `json:"rewardingStakePercentage"` 298 299 // WeightedAveragePercentage is the average perceived uptime of this node, 300 // weighted by stake. 301 // Note that this is different from RewardingStakePercentage, which shows 302 // the percent of the network stake that thinks this node is above the 303 // uptime requirement. WeightedAveragePercentage is weighted by uptime. 304 // i.e If uptime requirement is 85 and a peer reports 40 percent it will be 305 // counted (40*weight) in WeightedAveragePercentage but not in 306 // RewardingStakePercentage since 40 < 85 307 WeightedAveragePercentage json.Float64 `json:"weightedAveragePercentage"` 308 } 309 310 type UptimeRequest struct { 311 // if omitted, defaults to primary network 312 SubnetID ids.ID `json:"subnetID"` 313 } 314 315 func (i *Info) Uptime(_ *http.Request, args *UptimeRequest, reply *UptimeResponse) error { 316 i.log.Debug("API called", 317 zap.String("service", "info"), 318 zap.String("method", "uptime"), 319 ) 320 321 result, err := i.networking.NodeUptime(args.SubnetID) 322 if err != nil { 323 return fmt.Errorf("couldn't get node uptime: %w", err) 324 } 325 reply.WeightedAveragePercentage = json.Float64(result.WeightedAveragePercentage) 326 reply.RewardingStakePercentage = json.Float64(result.RewardingStakePercentage) 327 return nil 328 } 329 330 type ACP struct { 331 SupportWeight json.Uint64 `json:"supportWeight"` 332 Supporters set.Set[ids.NodeID] `json:"supporters"` 333 ObjectWeight json.Uint64 `json:"objectWeight"` 334 Objectors set.Set[ids.NodeID] `json:"objectors"` 335 AbstainWeight json.Uint64 `json:"abstainWeight"` 336 } 337 338 type ACPsReply struct { 339 ACPs map[uint32]*ACP `json:"acps"` 340 } 341 342 func (a *ACPsReply) getACP(acpNum uint32) *ACP { 343 acp, ok := a.ACPs[acpNum] 344 if !ok { 345 acp = &ACP{} 346 a.ACPs[acpNum] = acp 347 } 348 return acp 349 } 350 351 func (i *Info) Acps(_ *http.Request, _ *struct{}, reply *ACPsReply) error { 352 i.log.Debug("API called", 353 zap.String("service", "info"), 354 zap.String("method", "acps"), 355 ) 356 357 reply.ACPs = make(map[uint32]*ACP, constants.CurrentACPs.Len()) 358 peers := i.networking.PeerInfo(nil) 359 for _, peer := range peers { 360 weight := json.Uint64(i.validators.GetWeight(constants.PrimaryNetworkID, peer.ID)) 361 if weight == 0 { 362 continue 363 } 364 365 for acpNum := range peer.SupportedACPs { 366 acp := reply.getACP(acpNum) 367 acp.Supporters.Add(peer.ID) 368 acp.SupportWeight += weight 369 } 370 for acpNum := range peer.ObjectedACPs { 371 acp := reply.getACP(acpNum) 372 acp.Objectors.Add(peer.ID) 373 acp.ObjectWeight += weight 374 } 375 } 376 377 totalWeight, err := i.validators.TotalWeight(constants.PrimaryNetworkID) 378 if err != nil { 379 return err 380 } 381 for acpNum := range constants.CurrentACPs { 382 acp := reply.getACP(acpNum) 383 acp.AbstainWeight = json.Uint64(totalWeight) - acp.SupportWeight - acp.ObjectWeight 384 } 385 return nil 386 } 387 388 type GetTxFeeResponse struct { 389 TxFee json.Uint64 `json:"txFee"` 390 CreateAssetTxFee json.Uint64 `json:"createAssetTxFee"` 391 CreateSubnetTxFee json.Uint64 `json:"createSubnetTxFee"` 392 TransformSubnetTxFee json.Uint64 `json:"transformSubnetTxFee"` 393 CreateBlockchainTxFee json.Uint64 `json:"createBlockchainTxFee"` 394 AddPrimaryNetworkValidatorFee json.Uint64 `json:"addPrimaryNetworkValidatorFee"` 395 AddPrimaryNetworkDelegatorFee json.Uint64 `json:"addPrimaryNetworkDelegatorFee"` 396 AddSubnetValidatorFee json.Uint64 `json:"addSubnetValidatorFee"` 397 AddSubnetDelegatorFee json.Uint64 `json:"addSubnetDelegatorFee"` 398 } 399 400 // GetTxFee returns the transaction fee in nAVAX. 401 func (i *Info) GetTxFee(_ *http.Request, _ *struct{}, reply *GetTxFeeResponse) error { 402 i.log.Debug("API called", 403 zap.String("service", "info"), 404 zap.String("method", "getTxFee"), 405 ) 406 407 reply.TxFee = json.Uint64(i.TxFee) 408 reply.CreateAssetTxFee = json.Uint64(i.CreateAssetTxFee) 409 reply.CreateSubnetTxFee = json.Uint64(i.CreateSubnetTxFee) 410 reply.TransformSubnetTxFee = json.Uint64(i.TransformSubnetTxFee) 411 reply.CreateBlockchainTxFee = json.Uint64(i.CreateBlockchainTxFee) 412 reply.AddPrimaryNetworkValidatorFee = json.Uint64(i.AddPrimaryNetworkValidatorFee) 413 reply.AddPrimaryNetworkDelegatorFee = json.Uint64(i.AddPrimaryNetworkDelegatorFee) 414 reply.AddSubnetValidatorFee = json.Uint64(i.AddSubnetValidatorFee) 415 reply.AddSubnetDelegatorFee = json.Uint64(i.AddSubnetDelegatorFee) 416 return nil 417 } 418 419 // GetVMsReply contains the response metadata for GetVMs 420 type GetVMsReply struct { 421 VMs map[ids.ID][]string `json:"vms"` 422 Fxs map[ids.ID]string `json:"fxs"` 423 } 424 425 // GetVMs lists the virtual machines installed on the node 426 func (i *Info) GetVMs(_ *http.Request, _ *struct{}, reply *GetVMsReply) error { 427 i.log.Debug("API called", 428 zap.String("service", "info"), 429 zap.String("method", "getVMs"), 430 ) 431 432 // Fetch the VMs registered on this node. 433 vmIDs, err := i.VMManager.ListFactories() 434 if err != nil { 435 return err 436 } 437 438 reply.VMs, err = ids.GetRelevantAliases(i.VMManager, vmIDs) 439 reply.Fxs = map[ids.ID]string{ 440 secp256k1fx.ID: secp256k1fx.Name, 441 nftfx.ID: nftfx.Name, 442 propertyfx.ID: propertyfx.Name, 443 } 444 return err 445 }