Files
vm/api/info/service.go
zeekay 6f067da160 codec rip: switch api/* and components/index to luxfi/utils
api/* — all seven service/handler files swap codec/jsonrpc for
luxfi/utils/json (Uint32, Uint64, NewCodec, RawMessage). api carries
no codec.Manager use; the api types only quote integers for the
JSON-RPC boundary.

components/index/index.go — codec/wrappers -> luxfi/utils/wrappers.
Used only for wrappers.LongLen byte-slice sizing.

rpc/runtime/subprocess/linux_stopper.go — drop wrappers.Errs in
favor of errors.Join (stdlib since Go 1.20) for the SIGTERM + Wait
fan-in. One fewer dependency on a doomed module.

vm no longer directly imports any codec/* subpackage. codec is still
pulled in indirectly through chains/atomic (Wave 2E, hand-rolled
binary marshal already in but go.sum still pins codec) and through
the proto/p test fixtures.
2026-06-06 02:57:04 -07:00

510 lines
15 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package info
import (
"errors"
"fmt"
"net/http"
"net/netip"
"time"
"github.com/gorilla/rpc/v2"
"github.com/luxfi/ids"
// "github.com/luxfi/consensus/networking/benchlist" // Unused
"github.com/luxfi/atomic"
"github.com/luxfi/constants"
"github.com/luxfi/log"
"github.com/luxfi/math/set"
"github.com/luxfi/proto/p/signer"
"github.com/luxfi/upgrade"
"github.com/luxfi/utils/json"
"github.com/luxfi/utxo/nftfx"
"github.com/luxfi/utxo/propertyfx"
"github.com/luxfi/utxo/secp256k1fx"
validators "github.com/luxfi/validators"
"github.com/luxfi/version"
"github.com/luxfi/vm/manager"
)
// PeerInfo is the JSON wire shape for a peer in the Peers RPC reply. It is
// the local owner of this shape; previously this type came from
// github.com/luxfi/p2p/peer.Info, but that module is being archived per
// LP-201. Field tags are stable across the rename.
type PeerInfo struct {
IP netip.AddrPort `json:"ip"`
PublicIP netip.AddrPort `json:"publicIP,omitempty"`
ID ids.NodeID `json:"nodeID"`
Version string `json:"version"`
LastSent time.Time `json:"lastSent"`
LastReceived time.Time `json:"lastReceived"`
ObservedUptime json.Uint32 `json:"observedUptime"`
TrackedChains set.Set[ids.ID] `json:"trackedChains"`
SupportedLPs set.Set[uint32] `json:"supportedLPs"`
ObjectedLPs set.Set[uint32] `json:"objectedLPs"`
}
var (
errNoChainProvided = errors.New("argument 'chain' not given")
mainnetGetTxFeeResponse = GetTxFeeResponse{
CreateNetworkTxFee: json.Uint64(1 * constants.Lux),
TransformChainTxFee: json.Uint64(10 * constants.Lux),
CreateChainTxFee: json.Uint64(1 * constants.Lux),
AddNetworkValidatorFee: json.Uint64(constants.MilliLux),
AddNetworkDelegatorFee: json.Uint64(constants.MilliLux),
}
testnetGetTxFeeResponse = GetTxFeeResponse{
CreateNetworkTxFee: json.Uint64(100 * constants.MilliLux),
TransformChainTxFee: json.Uint64(1 * constants.Lux),
CreateChainTxFee: json.Uint64(100 * constants.MilliLux),
AddNetworkValidatorFee: json.Uint64(constants.MilliLux),
AddNetworkDelegatorFee: json.Uint64(constants.MilliLux),
}
defaultGetTxFeeResponse = GetTxFeeResponse{
CreateNetworkTxFee: json.Uint64(100 * constants.MilliLux),
TransformChainTxFee: json.Uint64(100 * constants.MilliLux),
CreateChainTxFee: json.Uint64(100 * constants.MilliLux),
AddNetworkValidatorFee: json.Uint64(constants.MilliLux),
AddNetworkDelegatorFee: json.Uint64(constants.MilliLux),
}
)
type Networking interface {
PeerInfo(nodeIDs []ids.NodeID) []PeerInfo
NodeUptime() (UptimeResult, error)
}
type UptimeResult struct {
RewardingStakePercentage float64
WeightedAveragePercentage float64
PotentialRewardingStakePercentage float64
PotentialWeightedAveragePercentage float64
}
// Info is the API service for unprivileged info on a node
type Info struct {
Parameters
log log.Logger
validators validators.Manager
myIP *atomic.Atomic[netip.AddrPort]
networking Networking
chainManager manager.ChainManager
vmManager manager.Manager
// benchlist benchlist.Manager // benchlist package doesn't exist
}
type Parameters struct {
Version *version.Application
NodeID ids.NodeID
NodePOP *signer.ProofOfPossession
NetworkID uint32
VMManager manager.Manager
Upgrades upgrade.Config
TxFee uint64
CreateAssetTxFee uint64
}
func NewService(
parameters Parameters,
log log.Logger,
validators validators.Manager,
chainManager manager.ChainManager,
vmManager manager.Manager,
myIP *atomic.Atomic[netip.AddrPort],
network Networking,
// benchlist benchlist.Manager, // benchlist package doesn't exist
) (http.Handler, error) {
server := rpc.NewServer()
codec := json.NewCodec()
server.RegisterCodec(codec, "application/json")
server.RegisterCodec(codec, "application/json;charset=UTF-8")
return server, server.RegisterService(
&Info{
Parameters: parameters,
log: log,
validators: validators,
chainManager: chainManager,
vmManager: vmManager,
myIP: myIP,
networking: network,
// benchlist: benchlist, // benchlist removed
},
"info",
)
}
// GetNodeVersionReply are the results from calling GetNodeVersion
type GetNodeVersionReply struct {
Version string `json:"version"`
DatabaseVersion string `json:"databaseVersion"`
RPCProtocolVersion json.Uint32 `json:"rpcProtocolVersion"`
GitCommit string `json:"gitCommit"`
VMVersions map[string]string `json:"vmVersions"`
}
// GetNodeVersion returns the version this node is running
func (i *Info) GetNodeVersion(r *http.Request, _ *struct{}, reply *GetNodeVersionReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "getNodeVersion"),
)
vmVersions, err := i.vmManager.Versions(r.Context())
if err != nil {
return err
}
reply.Version = i.Version.String()
reply.DatabaseVersion = version.CurrentDatabase.String()
reply.RPCProtocolVersion = json.Uint32(version.RPCChainVMProtocol)
reply.GitCommit = version.GitCommit
reply.VMVersions = vmVersions
return nil
}
// GetNodeIDReply are the results from calling GetNodeID
type GetNodeIDReply struct {
NodeID ids.NodeID `json:"nodeID"`
NodePOP *signer.ProofOfPossession `json:"nodePOP"`
}
// GetNodeID returns the node ID of this node
func (i *Info) GetNodeID(_ *http.Request, _ *struct{}, reply *GetNodeIDReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "getNodeID"),
)
reply.NodeID = i.NodeID
reply.NodePOP = i.NodePOP
return nil
}
// GetNetworkIDReply are the results from calling GetNetworkID
type GetNetworkIDReply struct {
NetworkID json.Uint32 `json:"networkID"`
}
// GetNodeIPReply are the results from calling GetNodeIP
type GetNodeIPReply struct {
IP netip.AddrPort `json:"ip"`
}
// GetNodeIP returns the IP of this node
func (i *Info) GetNodeIP(_ *http.Request, _ *struct{}, reply *GetNodeIPReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "getNodeIP"),
)
reply.IP = i.myIP.Get()
return nil
}
// GetNetworkID returns the network ID this node is running on
func (i *Info) GetNetworkID(_ *http.Request, _ *struct{}, reply *GetNetworkIDReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "getNetworkID"),
)
reply.NetworkID = json.Uint32(i.NetworkID)
return nil
}
// GetNetworkNameReply is the result from calling GetNetworkName
type GetNetworkNameReply struct {
NetworkName string `json:"networkName"`
}
// GetNetworkName returns the network name this node is running on
func (i *Info) GetNetworkName(_ *http.Request, _ *struct{}, reply *GetNetworkNameReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "getNetworkName"),
)
reply.NetworkName = constants.NetworkName(i.NetworkID)
return nil
}
// GetBlockchainIDArgs are the arguments for calling GetBlockchainID
type GetBlockchainIDArgs struct {
Alias string `json:"alias"`
}
// GetBlockchainIDReply are the results from calling GetBlockchainID
type GetBlockchainIDReply struct {
BlockchainID ids.ID `json:"blockchainID"`
}
// GetBlockchainID returns the blockchain ID that resolves the alias that was supplied
func (i *Info) GetBlockchainID(_ *http.Request, args *GetBlockchainIDArgs, reply *GetBlockchainIDReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "getBlockchainID"),
)
bID, err := i.chainManager.Lookup(args.Alias)
reply.BlockchainID = bID
return err
}
// PeersArgs are the arguments for calling Peers
type PeersArgs struct {
NodeIDs []ids.NodeID `json:"nodeIDs"`
}
type Peer struct {
PeerInfo
Benched []string `json:"benched"`
}
// PeersReply are the results from calling Peers
type PeersReply struct {
// Number of elements in [Peers]
NumPeers json.Uint64 `json:"numPeers"`
// Each element is a peer
Peers []Peer `json:"peers"`
}
// Peers returns the list of current validators
func (i *Info) Peers(_ *http.Request, args *PeersArgs, reply *PeersReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "peers"),
)
peers := i.networking.PeerInfo(args.NodeIDs)
peerInfo := make([]Peer, len(peers))
for index, peer := range peers {
// benchlist removed
// benchedIDs := i.benchlist.GetBenched(peer.ID)
benchedAliases := make([]string, 0) // Empty list since benchlist is removed
// for idx, id := range benchedIDs {
// alias, err := i.chainManager.PrimaryAlias(id)
// if err != nil {
// return fmt.Errorf("failed to get primary alias for chain ID %s: %w", id, err)
// }
// benchedAliases[idx] = alias
// }
peerInfo[index] = Peer{
PeerInfo: peer,
Benched: benchedAliases,
}
}
reply.Peers = peerInfo
reply.NumPeers = json.Uint64(len(reply.Peers))
return nil
}
// IsBootstrappedArgs are the arguments for calling IsBootstrapped
type IsBootstrappedArgs struct {
// Alias of the chain
// Can also be the string representation of the chain's ID
Chain string `json:"chain"`
}
// IsBootstrappedResponse are the results from calling IsBootstrapped
type IsBootstrappedResponse struct {
// True iff the chain exists and is done bootstrapping
IsBootstrapped bool `json:"isBootstrapped"`
}
// IsBootstrapped returns nil and sets [reply.IsBootstrapped] == true iff [args.Chain] exists and is done bootstrapping
// Returns an error if the chain doesn't exist
func (i *Info) IsBootstrapped(_ *http.Request, args *IsBootstrappedArgs, reply *IsBootstrappedResponse) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "isBootstrapped"),
log.String("chain", args.Chain),
)
if args.Chain == "" {
return errNoChainProvided
}
chainID, err := i.chainManager.Lookup(args.Chain)
if err != nil {
return fmt.Errorf("there is no chain with alias/ID '%s'", args.Chain)
}
reply.IsBootstrapped = i.chainManager.IsBootstrapped(chainID)
return nil
}
// Upgrades returns the upgrade schedule this node is running.
func (i *Info) Upgrades(_ *http.Request, _ *struct{}, reply *upgrade.Config) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "upgrades"),
)
*reply = i.Parameters.Upgrades
return nil
}
// UptimeResponse are the results from calling Uptime
type UptimeResponse struct {
// RewardingStakePercentage shows what percent of network stake thinks we're
// above the uptime requirement.
RewardingStakePercentage json.Float64 `json:"rewardingStakePercentage"`
// WeightedAveragePercentage is the average perceived uptime of this node,
// weighted by stake.
// Note that this is different from RewardingStakePercentage, which shows
// the percent of the network stake that thinks this node is above the
// uptime requirement. WeightedAveragePercentage is weighted by uptime.
// i.e If uptime requirement is 85 and a peer reports 40 percent it will be
// counted (40*weight) in WeightedAveragePercentage but not in
// RewardingStakePercentage since 40 < 85
WeightedAveragePercentage json.Float64 `json:"weightedAveragePercentage"`
}
func (i *Info) Uptime(_ *http.Request, _ *struct{}, reply *UptimeResponse) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "uptime"),
)
result, err := i.networking.NodeUptime()
if err != nil {
return fmt.Errorf("couldn't get node uptime: %w", err)
}
reply.WeightedAveragePercentage = json.Float64(result.WeightedAveragePercentage)
reply.RewardingStakePercentage = json.Float64(result.RewardingStakePercentage)
return nil
}
type LP struct {
SupportWeight json.Uint64 `json:"supportWeight"`
Supporters set.Set[ids.NodeID] `json:"supporters"`
ObjectWeight json.Uint64 `json:"objectWeight"`
Objectors set.Set[ids.NodeID] `json:"objectors"`
AbstainWeight json.Uint64 `json:"abstainWeight"`
}
type LPsReply struct {
LPs map[uint32]*LP `json:"lps"`
}
func (a *LPsReply) getLP(lpNum uint32) *LP {
lp, ok := a.LPs[lpNum]
if !ok {
lp = &LP{}
a.LPs[lpNum] = lp
}
return lp
}
func (i *Info) Lps(_ *http.Request, _ *struct{}, reply *LPsReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "lps"),
)
reply.LPs = make(map[uint32]*LP, constants.CurrentLPs.Len())
peers := i.networking.PeerInfo(nil)
for _, peer := range peers {
w := i.validators.GetWeight(constants.PrimaryNetworkID, peer.ID)
weight := json.Uint64(w)
if weight == 0 {
continue
}
// SupportedLPs and ObjectedLPs not available on peer.Info type
// for lpNum := range peer.SupportedLPs {
// lp := reply.getLP(lpNum)
// lp.Supporters.Add(peer.ID)
// lp.SupportWeight += weight
// }
// for lpNum := range peer.ObjectedLPs {
// lp := reply.getLP(lpNum)
// lp.Objectors.Add(peer.ID)
// lp.ObjectWeight += weight
// }
_ = peer // Silence unused variable warning
}
totalWeight, err := i.validators.TotalWeight(constants.PrimaryNetworkID)
if err != nil {
return err
}
for lpNum := range constants.CurrentLPs {
lp := reply.getLP(lpNum)
lp.AbstainWeight = json.Uint64(totalWeight) - lp.SupportWeight - lp.ObjectWeight
}
return nil
}
type GetTxFeeResponse struct {
TxFee json.Uint64 `json:"txFee"`
CreateAssetTxFee json.Uint64 `json:"createAssetTxFee"`
CreateNetworkTxFee json.Uint64 `json:"createNetworkTxFee"`
TransformChainTxFee json.Uint64 `json:"transformChainTxFee"`
CreateChainTxFee json.Uint64 `json:"createChainTxFee"`
AddNetworkValidatorFee json.Uint64 `json:"addNetworkValidatorFee"`
AddNetworkDelegatorFee json.Uint64 `json:"addNetworkDelegatorFee"`
}
// GetTxFee returns the transaction fee in nLUX.
func (i *Info) GetTxFee(_ *http.Request, _ *struct{}, reply *GetTxFeeResponse) error {
i.log.Warn("deprecated API called",
log.String("service", "info"),
log.String("method", "getTxFee"),
)
switch i.NetworkID {
case constants.MainnetID:
*reply = mainnetGetTxFeeResponse
case constants.TestnetID:
*reply = testnetGetTxFeeResponse
default:
*reply = defaultGetTxFeeResponse
}
reply.TxFee = json.Uint64(i.TxFee)
reply.CreateAssetTxFee = json.Uint64(i.CreateAssetTxFee)
return nil
}
// GetVMsReply contains the response metadata for GetVMs
type GetVMsReply struct {
VMs map[ids.ID][]string `json:"vms"`
Fxs map[ids.ID]string `json:"fxs"`
}
// GetVMs lists the virtual machines installed on the node
func (i *Info) GetVMs(r *http.Request, _ *struct{}, reply *GetVMsReply) error {
i.log.Debug("API called",
log.String("service", "info"),
log.String("method", "getVMs"),
)
// Fetch the VMs registered on this node.
vmIDs, err := i.VMManager.ListFactories(r.Context())
if err != nil {
return err
}
vms := make(map[ids.ID][]string, len(vmIDs))
for _, vmID := range vmIDs {
aliases, err := i.VMManager.Aliases(r.Context(), vmID)
if err != nil {
return err
}
vms[vmID] = aliases
}
reply.VMs = vms
reply.Fxs = map[ids.ID]string{
secp256k1fx.ID: secp256k1fx.Name,
nftfx.ID: nftfx.Name,
propertyfx.ID: propertyfx.Name,
}
return err
}