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  }