mirror of
https://github.com/luxfi/api.git
synced 2026-09-24 00:24:53 +00:00
A ZAP field is an offset. Eight of these replies held a map, which has no order to give one, so the reply could not cross between two processes at all. Two more held values whose fields are every one unexported — netip.AddrPort, time.Time — and those are worse: they are not refused, they derive an EMPTY layout, so the reply crossed, reported no error and carried nothing. GetNodeIPReply has one field, so its whole answer was blank. And a health check says what it likes, so its details were an `any`, which has no shape until it holds something. Each map is now a list of a stated element type, ordered by the key it was an object of. An address is the text netip already wrote for it, an instant is three numbers, and a check details are the bytes of their own JSON — the one shape an object, a string and a list all share. The JSON does not move. Every list marshals to the object it replaced and reads one back, and wire_test.go holds that against the old shape spelled out as a literal rather than against itself. The order is new where there was none: a map answered the same question two ways. APIReply also states the status it carries, so the 503 a probe reads and the healthy a person reads are one fact computed once.
355 lines
11 KiB
Go
355 lines
11 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// What a node will tell anyone who asks.
|
|
//
|
|
// SEVERAL SHAPES HERE ARE AN OBJECT ON THE JSON WIRE AND A LIST IN GO, and the
|
|
// reason is the same every time: a ZAP field is an offset, a map has no order to
|
|
// give one, so a reply carrying a map cannot cross between two processes at all.
|
|
// The list is ordered by the key it was an object of, which is an order a map
|
|
// never had — so the answer is now the same twice running as well.
|
|
//
|
|
// The JSON is unchanged. Each list marshals to the object it replaced, and reads
|
|
// one back.
|
|
package info
|
|
|
|
import (
|
|
"encoding/json"
|
|
"slices"
|
|
|
|
"github.com/luxfi/api/types"
|
|
"github.com/luxfi/ids"
|
|
)
|
|
|
|
// 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 — JSON consumers see no
|
|
// difference.
|
|
type PeerInfo struct {
|
|
// IP is the address this node reaches the peer at.
|
|
IP types.Addr `json:"ip"`
|
|
// PublicIP is the address the peer says it is reachable at.
|
|
PublicIP types.Addr `json:"publicIP,omitempty"`
|
|
// ID is the peer's node id.
|
|
ID ids.NodeID `json:"nodeID"`
|
|
// Version is the node software the peer is running.
|
|
Version string `json:"version"`
|
|
// LastSent is when this node last sent the peer a message.
|
|
LastSent types.Time `json:"lastSent"`
|
|
// LastReceived is when this node last heard from the peer.
|
|
LastReceived types.Time `json:"lastReceived"`
|
|
// ObservedUptime is the percentage of time this node has seen the peer up.
|
|
ObservedUptime types.Uint32 `json:"observedUptime"`
|
|
// TrackedChains are the chains the peer serves, ordered by id.
|
|
TrackedChains []ids.ID `json:"trackedChains"`
|
|
// SupportedLPs are the LPs the peer votes for, ascending.
|
|
SupportedLPs []uint32 `json:"supportedLPs"`
|
|
// ObjectedLPs are the LPs the peer votes against, ascending.
|
|
ObjectedLPs []uint32 `json:"objectedLPs"`
|
|
}
|
|
|
|
// ProofOfPossession is a JSON-friendly representation of a BLS PoP.
|
|
type ProofOfPossession struct {
|
|
PublicKey string `json:"publicKey"`
|
|
ProofOfPossession string `json:"proofOfPossession"`
|
|
}
|
|
|
|
// GetNodeVersionReply are the results from calling GetNodeVersion.
|
|
type GetNodeVersionReply struct {
|
|
Version string `json:"version"`
|
|
DatabaseVersion string `json:"databaseVersion"`
|
|
RPCProtocolVersion types.Uint32 `json:"rpcProtocolVersion"`
|
|
GitCommit string `json:"gitCommit"`
|
|
VMVersions VMVersions `json:"vmVersions"`
|
|
// Consensus describes the active consensus configuration. Populated
|
|
// from the in-memory consensus engine state at request time. Omitted
|
|
// for legacy clients that don't wire it through.
|
|
Consensus *ConsensusInfo `json:"consensus,omitempty"`
|
|
}
|
|
|
|
// VMVersion is one VM and the version of it a node runs.
|
|
type VMVersion struct {
|
|
// VM is the VM's alias.
|
|
VM string `json:"vm"`
|
|
// Version is the release of it this node runs.
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// VMVersions is what a node runs of each VM: an object keyed by VM alias on the
|
|
// JSON wire, a list ordered by that alias here.
|
|
type VMVersions []VMVersion
|
|
|
|
func (v VMVersions) MarshalJSON() ([]byte, error) {
|
|
m := make(map[string]string, len(v))
|
|
for _, e := range v {
|
|
m[e.VM] = e.Version
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
func (v *VMVersions) UnmarshalJSON(b []byte) error {
|
|
var m map[string]string
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return err
|
|
}
|
|
*v = make(VMVersions, 0, len(m))
|
|
for vm, version := range m {
|
|
*v = append(*v, VMVersion{VM: vm, Version: version})
|
|
}
|
|
slices.SortFunc(*v, func(x, y VMVersion) int { return cmpString(x.VM, y.VM) })
|
|
return nil
|
|
}
|
|
|
|
// ConsensusInfo summarises the consensus configuration of a running node so
|
|
// callers don't have to scrape the boot logs to learn whether Quasar is in
|
|
// triple/dual/classical mode.
|
|
type ConsensusInfo struct {
|
|
// Mode is one of "triple" (BLS + Corona + ML-DSA), "dual" (BLS +
|
|
// Corona), or "classical" (BLS only). Free-form so future modes
|
|
// don't bump the API version.
|
|
Mode string `json:"mode"`
|
|
// BLS is always true for production Quasar nodes.
|
|
BLS bool `json:"bls"`
|
|
// Corona is true when the post-quantum lattice threshold path is wired.
|
|
Corona bool `json:"corona"`
|
|
// MLDSA is true when ML-DSA-65 (FIPS 204) signature verification is wired.
|
|
MLDSA bool `json:"mlDSA"`
|
|
// PlatformVM is true when the production PlatformVM wiring is in use,
|
|
// false when running with the dev/stub PlatformVM.
|
|
PlatformVM bool `json:"platformVM"`
|
|
}
|
|
|
|
// GetNodeIDReply are the results from calling GetNodeID.
|
|
type GetNodeIDReply struct {
|
|
NodeID ids.NodeID `json:"nodeID"`
|
|
NodePOP *ProofOfPossession `json:"nodePOP"`
|
|
}
|
|
|
|
// GetNetworkIDReply are the results from calling GetNetworkID.
|
|
type GetNetworkIDReply struct {
|
|
NetworkID types.Uint32 `json:"networkID"`
|
|
}
|
|
|
|
// GetNodeIPReply are the results from calling GetNodeIP.
|
|
type GetNodeIPReply struct {
|
|
// IP is the address this node tells peers to reach it at.
|
|
IP types.Addr `json:"ip"`
|
|
}
|
|
|
|
// GetNetworkNameReply is the result from calling GetNetworkName.
|
|
type GetNetworkNameReply struct {
|
|
NetworkName string `json:"networkName"`
|
|
}
|
|
|
|
// GetBlockchainIDArgs are the arguments for calling GetBlockchainID.
|
|
type GetBlockchainIDArgs struct {
|
|
// Alias is the name the chain answers to, such as "X".
|
|
Alias string `json:"alias"`
|
|
}
|
|
|
|
// GetBlockchainIDReply are the results from calling GetBlockchainID.
|
|
type GetBlockchainIDReply struct {
|
|
BlockchainID ids.ID `json:"blockchainID"`
|
|
}
|
|
|
|
// PeersArgs are the arguments for calling Peers.
|
|
type PeersArgs struct {
|
|
// NodeIDs narrows the answer to these peers. Empty asks for all of them.
|
|
NodeIDs []ids.NodeID `json:"nodeIDs"`
|
|
}
|
|
|
|
// Peer is information about a peer in the network.
|
|
type Peer struct {
|
|
PeerInfo
|
|
|
|
Benched []string `json:"benched"`
|
|
}
|
|
|
|
// PeersReply are the results from calling Peers.
|
|
type PeersReply struct {
|
|
NumPeers types.Uint64 `json:"numPeers"`
|
|
Peers []Peer `json:"peers"`
|
|
}
|
|
|
|
// IsBootstrappedArgs are the arguments for calling IsBootstrapped.
|
|
type IsBootstrappedArgs struct {
|
|
// Chain is the alias or id of the chain to ask about.
|
|
Chain string `json:"chain"`
|
|
}
|
|
|
|
// IsBootstrappedResponse are the results from calling IsBootstrapped.
|
|
type IsBootstrappedResponse struct {
|
|
IsBootstrapped bool `json:"isBootstrapped"`
|
|
}
|
|
|
|
// UptimeResponse are the results from calling Uptime.
|
|
type UptimeResponse struct {
|
|
RewardingStakePercentage types.Float64 `json:"rewardingStakePercentage"`
|
|
WeightedAveragePercentage types.Float64 `json:"weightedAveragePercentage"`
|
|
}
|
|
|
|
// LP is information about an LP proposal.
|
|
type LP struct {
|
|
SupportWeight types.Uint64 `json:"supportWeight"`
|
|
// Supporters are the peers voting for it, ordered by node id.
|
|
Supporters []ids.NodeID `json:"supporters"`
|
|
ObjectWeight types.Uint64 `json:"objectWeight"`
|
|
// Objectors are the peers voting against it, ordered by node id.
|
|
Objectors []ids.NodeID `json:"objectors"`
|
|
AbstainWeight types.Uint64 `json:"abstainWeight"`
|
|
}
|
|
|
|
// LPStatus is one LP proposal and where the network's stake stands on it.
|
|
type LPStatus struct {
|
|
// Number is the LP's number, the key it appears under on the JSON wire.
|
|
Number uint32 `json:"number"`
|
|
// LP is the stake for, against and abstaining.
|
|
LP LP `json:"lp"`
|
|
}
|
|
|
|
// LPs is where the network stands on every LP: an object keyed by LP number on
|
|
// the JSON wire, a list in ascending number here.
|
|
type LPs []LPStatus
|
|
|
|
func (l LPs) MarshalJSON() ([]byte, error) {
|
|
m := make(map[uint32]LP, len(l))
|
|
for _, e := range l {
|
|
m[e.Number] = e.LP
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
func (l *LPs) UnmarshalJSON(b []byte) error {
|
|
var m map[uint32]LP
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return err
|
|
}
|
|
*l = make(LPs, 0, len(m))
|
|
for number, lp := range m {
|
|
*l = append(*l, LPStatus{Number: number, LP: lp})
|
|
}
|
|
slices.SortFunc(*l, func(x, y LPStatus) int { return cmpUint32(x.Number, y.Number) })
|
|
return nil
|
|
}
|
|
|
|
// LPsReply are the results from calling LPs.
|
|
type LPsReply struct {
|
|
LPs LPs `json:"lps"`
|
|
}
|
|
|
|
// Find is the status of one LP, and whether the reply carries it.
|
|
func (l LPs) Find(number uint32) (*LPStatus, bool) {
|
|
for i := range l {
|
|
if l[i].Number == number {
|
|
return &l[i], true
|
|
}
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// GetTxFeeResponse are the results from calling GetTxFee.
|
|
type GetTxFeeResponse struct {
|
|
TxFee types.Uint64 `json:"txFee"`
|
|
CreateAssetTxFee types.Uint64 `json:"createAssetTxFee"`
|
|
CreateNetworkTxFee types.Uint64 `json:"createNetworkTxFee"`
|
|
TransformChainTxFee types.Uint64 `json:"transformChainTxFee"`
|
|
CreateChainTxFee types.Uint64 `json:"createChainTxFee"`
|
|
AddNetworkValidatorFee types.Uint64 `json:"addNetworkValidatorFee"`
|
|
AddNetworkDelegatorFee types.Uint64 `json:"addNetworkDelegatorFee"`
|
|
}
|
|
|
|
// VMAlias is one VM and the names it answers to.
|
|
type VMAlias struct {
|
|
// VM is the VM's id.
|
|
VM ids.ID `json:"vm"`
|
|
// Aliases are the names it also answers to.
|
|
Aliases []string `json:"aliases"`
|
|
}
|
|
|
|
// VMAliases are the VMs installed on a node: an object keyed by VM id on the
|
|
// JSON wire, a list ordered by that id here.
|
|
type VMAliases []VMAlias
|
|
|
|
func (v VMAliases) MarshalJSON() ([]byte, error) {
|
|
m := make(map[ids.ID][]string, len(v))
|
|
for _, e := range v {
|
|
m[e.VM] = e.Aliases
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
func (v *VMAliases) UnmarshalJSON(b []byte) error {
|
|
var m map[ids.ID][]string
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return err
|
|
}
|
|
*v = make(VMAliases, 0, len(m))
|
|
for vm, aliases := range m {
|
|
*v = append(*v, VMAlias{VM: vm, Aliases: aliases})
|
|
}
|
|
slices.SortFunc(*v, func(x, y VMAlias) int { return x.VM.Compare(y.VM) })
|
|
return nil
|
|
}
|
|
|
|
// FxName is one feature extension and what it is called.
|
|
type FxName struct {
|
|
// Fx is the extension's id.
|
|
Fx ids.ID `json:"fx"`
|
|
// Name is what it is called.
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// FxNames are the feature extensions a node knows: an object keyed by id on the
|
|
// JSON wire, a list ordered by that id here.
|
|
type FxNames []FxName
|
|
|
|
func (f FxNames) MarshalJSON() ([]byte, error) {
|
|
m := make(map[ids.ID]string, len(f))
|
|
for _, e := range f {
|
|
m[e.Fx] = e.Name
|
|
}
|
|
return json.Marshal(m)
|
|
}
|
|
|
|
func (f *FxNames) UnmarshalJSON(b []byte) error {
|
|
var m map[ids.ID]string
|
|
if err := json.Unmarshal(b, &m); err != nil {
|
|
return err
|
|
}
|
|
*f = make(FxNames, 0, len(m))
|
|
for fx, name := range m {
|
|
*f = append(*f, FxName{Fx: fx, Name: name})
|
|
}
|
|
slices.SortFunc(*f, func(x, y FxName) int { return x.Fx.Compare(y.Fx) })
|
|
return nil
|
|
}
|
|
|
|
// GetVMsReply contains the response metadata for GetVMs.
|
|
type GetVMsReply struct {
|
|
VMs VMAliases `json:"vms"`
|
|
Fxs FxNames `json:"fxs"`
|
|
}
|
|
|
|
// cmpString and cmpUint32 order the lists above. Written out rather than reached
|
|
// for from cmp so the ordering a reply is in is stated where the reply is.
|
|
func cmpString(x, y string) int {
|
|
switch {
|
|
case x < y:
|
|
return -1
|
|
case x > y:
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func cmpUint32(x, y uint32) int {
|
|
switch {
|
|
case x < y:
|
|
return -1
|
|
case x > y:
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|