mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
The node moved every chain route from /v1/bc/<alias> to /v1/chain/<alias> and deleted the old prefix, so every chain call answered 404. The address was spelled out at 112 sites across 34 files and constants.ChainAliasPrefix was used at none of them, which is why nothing failed to compile when the segment moved. route.Chain(uri, alias) composes it now, and route.Alias reads one back, replacing three hand-rolled parsers — one of which took the alias by position in the split path and only worked because the new segment happens to leave the component count unchanged. TestChainAddressBuiltOnlyHere walks every .go file in the module and fails on a chain address in a string literal, so the segment cannot drift back; prose is free to name a route. TestGuardCatchesEachShape plants each forbidden shape and requires the guard to find it. The endpoint box and the eleven-chain table were the same list written twice; there is one list, rendered once, measured rather than hand-padded. The generated CLI reference and the faucet manifest compose through the same function. Co-authored-by: Hanzo Dev <[email protected]>
427 lines
11 KiB
Go
427 lines
11 KiB
Go
// Copyright (C) 2022-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package status
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/luxfi/cli/pkg/route"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// StatusFormatter handles formatting of status output
|
|
type StatusFormatter struct {
|
|
writer io.Writer
|
|
}
|
|
|
|
// NewStatusFormatter creates a new formatter
|
|
func NewStatusFormatter(writer io.Writer) *StatusFormatter {
|
|
return &StatusFormatter{
|
|
writer: writer,
|
|
}
|
|
}
|
|
|
|
// getChainTypeName returns the human-readable name for a chain type
|
|
func getChainTypeName(chainAlias string) string {
|
|
switch chainAlias {
|
|
case "p":
|
|
return "platform"
|
|
case "x":
|
|
return "exchange"
|
|
case "c":
|
|
return "evm" // C-Chain is Lux EVM (EVM-compatible)
|
|
case "a":
|
|
return "ai"
|
|
case "b":
|
|
return "bridge"
|
|
case "d":
|
|
return "dex"
|
|
case "g":
|
|
return "graph"
|
|
case "k":
|
|
return "kms"
|
|
case "q":
|
|
return "quantum"
|
|
case "t":
|
|
return "threshold"
|
|
case "z":
|
|
return "zk"
|
|
case "zoo":
|
|
return "zoo" // Zoo app chain
|
|
case "hanzo":
|
|
return "hanzo" // Hanzo app chain
|
|
case "spc":
|
|
return "spc" // SPC app chain
|
|
default:
|
|
return "custom"
|
|
}
|
|
}
|
|
|
|
// FormatNetworkStatus formats network status in the requested clean format
|
|
func (f *StatusFormatter) FormatNetworkStatus(result *StatusResult) {
|
|
// Format network summary
|
|
for _, network := range result.Networks {
|
|
status := "stopped"
|
|
if network.Metadata.Status == "up" {
|
|
status = "up"
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "status %-8s %-8s grpc=%d nodes=%d vms=%d controller=%s\n",
|
|
network.Name,
|
|
status,
|
|
network.Metadata.GRPCPort,
|
|
network.Metadata.NodesCount,
|
|
network.Metadata.VMsCount,
|
|
network.Metadata.Controller)
|
|
}
|
|
|
|
// Format node details for each network
|
|
for _, network := range result.Networks {
|
|
if len(network.Nodes) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s nodes\n", network.Name)
|
|
fmt.Fprintf(f.writer, "node node_id http version peers uptime gpu ok\n")
|
|
|
|
for _, node := range network.Nodes {
|
|
okStr := "no"
|
|
if node.OK {
|
|
okStr = "✓ yes"
|
|
}
|
|
|
|
nodeID := "-"
|
|
if node.NodeID != "" {
|
|
nodeID = node.NodeID
|
|
}
|
|
|
|
// Create a more descriptive node identifier
|
|
nodeIdentifier := fmt.Sprintf("%s-%s-%s", network.Name, node.ID, (func() string {
|
|
if len(nodeID) > 8 {
|
|
return nodeID[:8]
|
|
}
|
|
return nodeID
|
|
}()))
|
|
|
|
version := strings.TrimPrefix(node.Version, "luxd/")
|
|
|
|
// GPU status
|
|
gpuStatus := "-"
|
|
if node.GPUAccelerated {
|
|
if node.GPUDevice != "" {
|
|
gpuStatus = node.GPUDevice
|
|
if len(gpuStatus) > 10 {
|
|
gpuStatus = gpuStatus[:10]
|
|
}
|
|
} else {
|
|
gpuStatus = "yes"
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-12s %-30s %-32s %-12s %-5d %-8s %-10s %s\n",
|
|
nodeIdentifier,
|
|
nodeID,
|
|
node.HTTPURL,
|
|
version,
|
|
node.PeerCount,
|
|
node.Uptime,
|
|
gpuStatus,
|
|
okStr)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Format node addresses for each network
|
|
for _, network := range result.Networks {
|
|
if len(network.Nodes) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s node addresses\n", network.Name)
|
|
fmt.Fprintf(f.writer, "node x-chain address c-chain address\n")
|
|
|
|
for _, node := range network.Nodes {
|
|
xAddress := "-"
|
|
if node.XChainAddress != "" {
|
|
xAddress = node.XChainAddress
|
|
}
|
|
|
|
cAddress := "-"
|
|
if node.CChainAddress != "" {
|
|
cAddress = node.CChainAddress
|
|
}
|
|
|
|
// Format addresses with P/X-lux prefix if they look like Lux addresses
|
|
displayX := xAddress
|
|
if strings.HasPrefix(xAddress, "X-lux") {
|
|
displayX = strings.Replace(xAddress, "X-lux", "X-lux", 1) // Ensure consistency
|
|
} else if strings.HasPrefix(xAddress, "lux") {
|
|
displayX = "X-" + xAddress
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-5s %-40s %s\n",
|
|
node.ID,
|
|
displayX,
|
|
cAddress)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Format chain status for each network
|
|
for _, network := range result.Networks {
|
|
if len(network.Chains) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s chains (heights)\n", network.Name)
|
|
fmt.Fprintf(f.writer, "chain type height block_time rpc_ok latency chain_id rpc_endpoint\n")
|
|
|
|
for _, chain := range network.Chains {
|
|
rpcOK := "no"
|
|
if chain.RPC_OK {
|
|
rpcOK = "yes"
|
|
}
|
|
|
|
blockTime := "-"
|
|
if chain.BlockTime != nil {
|
|
blockTime = chain.BlockTime.Format("2006-01-02 15:04:05")
|
|
}
|
|
|
|
chainType := getChainTypeName(chain.Alias)
|
|
chainID := "-"
|
|
if chain.ChainID != "" {
|
|
chainID = chain.ChainID
|
|
}
|
|
|
|
// Get RPC endpoint for this chain from actual network nodes
|
|
rpcEndpoint := "-"
|
|
baseURL := "http://127.0.0.1:9650"
|
|
if len(network.Nodes) > 0 {
|
|
baseURL = network.Nodes[0].HTTPURL
|
|
}
|
|
|
|
// P-chain and X-chain don't use /rpc suffix, EVM chains do
|
|
switch chain.Alias {
|
|
case "p":
|
|
rpcEndpoint = route.Chain(baseURL, "P")
|
|
case "x":
|
|
rpcEndpoint = route.Chain(baseURL, "X")
|
|
case "c", "a", "b", "d", "g", "k", "q", "t", "z":
|
|
rpcEndpoint = route.Chain(baseURL, strings.ToUpper(chain.Alias)) + "/rpc"
|
|
default:
|
|
rpcEndpoint = route.Chain(baseURL, chain.Alias) + "/rpc"
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-5s %-10s %-10d %-20s %-6s %dms %-8s %s\n",
|
|
chain.Alias,
|
|
chainType,
|
|
chain.Height,
|
|
blockTime,
|
|
rpcOK,
|
|
chain.LatencyMS,
|
|
chainID,
|
|
rpcEndpoint)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Format endpoints by chain for each network
|
|
for _, network := range result.Networks {
|
|
if len(network.Endpoints) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s endpoints (by chain)\n", network.Name)
|
|
fmt.Fprintf(f.writer, "chain endpoints\n")
|
|
|
|
for _, endpoint := range network.Endpoints {
|
|
fmt.Fprintf(f.writer, "%-5s %s (x%d)\n",
|
|
endpoint.ChainAlias,
|
|
endpoint.URL,
|
|
1) // Placeholder for count
|
|
}
|
|
}
|
|
}
|
|
|
|
// Format L1 EVM chains (Zoo, Hanzo, SPC)
|
|
if len(result.TrackedEVMs) > 0 {
|
|
fmt.Fprintf(f.writer, "\nl1 chains (zoo, hanzo, spc)\n")
|
|
fmt.Fprintf(f.writer, "chain network chain_id height rpc_ok client_version rpc_endpoint\n")
|
|
|
|
for _, evm := range result.TrackedEVMs {
|
|
rpcOK := "no"
|
|
rpcEndpoint := "-"
|
|
if len(evm.Endpoints) > 0 {
|
|
if evm.Endpoints[0].OK {
|
|
rpcOK = "yes"
|
|
}
|
|
rpcEndpoint = evm.Endpoints[0].URL
|
|
}
|
|
|
|
chainID := "-"
|
|
if evm.ChainID > 0 {
|
|
chainID = fmt.Sprintf("%d", evm.ChainID)
|
|
}
|
|
|
|
clientVersion := "-"
|
|
if evm.ClientVersion != "" {
|
|
if len(evm.ClientVersion) > 28 {
|
|
clientVersion = evm.ClientVersion[:28]
|
|
} else {
|
|
clientVersion = evm.ClientVersion
|
|
}
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-8s %-9s %-9s %-10d %-6s %-28s %s\n",
|
|
evm.Name,
|
|
evm.Network,
|
|
chainID,
|
|
evm.Height,
|
|
rpcOK,
|
|
clientVersion,
|
|
rpcEndpoint)
|
|
}
|
|
}
|
|
|
|
// Format validator accounts with balances for each network
|
|
for _, network := range result.Networks {
|
|
if len(network.Validators) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s validators\n", network.Name)
|
|
fmt.Fprintf(f.writer, "# node_id p-chain x-chain c-chain active\n")
|
|
|
|
for _, v := range network.Validators {
|
|
activeStr := " "
|
|
if v.IsActive {
|
|
activeStr = "*"
|
|
}
|
|
|
|
// Truncate node_id for display
|
|
nodeID := v.NodeID
|
|
if len(nodeID) > 40 {
|
|
nodeID = nodeID[:40]
|
|
}
|
|
|
|
// Truncate addresses for display
|
|
pAddr := v.PChainAddress
|
|
if len(pAddr) > 38 {
|
|
pAddr = pAddr[:38]
|
|
}
|
|
xAddr := v.XChainAddress
|
|
if len(xAddr) > 38 {
|
|
xAddr = xAddr[:38]
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-2d %-42s %-40s %-40s %-42s %s\n",
|
|
v.Index,
|
|
nodeID,
|
|
pAddr,
|
|
xAddr,
|
|
v.CChainAddress,
|
|
activeStr)
|
|
}
|
|
}
|
|
|
|
// Show validator balances
|
|
if len(network.Validators) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s validator balances\n", network.Name)
|
|
fmt.Fprintf(f.writer, "# p-chain balance x-chain balance c-chain balance\n")
|
|
|
|
for _, v := range network.Validators {
|
|
pBalance := FormatNLUXToLUX(v.PChainBalance)
|
|
xBalance := FormatNLUXToLUX(v.XChainBalance)
|
|
cBalance := v.CChainBalanceLUX
|
|
if cBalance == "" {
|
|
cBalance = "0 LUX"
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-2d %-20s %-20s %s\n",
|
|
v.Index,
|
|
pBalance,
|
|
xBalance,
|
|
cBalance)
|
|
}
|
|
}
|
|
|
|
// Show active account summary
|
|
if network.ActiveAccount != nil {
|
|
fmt.Fprintf(f.writer, "\n%s active account\n", network.Name)
|
|
fmt.Fprintf(f.writer, " validator #%d\n", network.ActiveAccount.Index)
|
|
fmt.Fprintf(f.writer, " P-Chain: %s\n", network.ActiveAccount.PChainAddress)
|
|
fmt.Fprintf(f.writer, " X-Chain: %s\n", network.ActiveAccount.XChainAddress)
|
|
fmt.Fprintf(f.writer, " C-Chain: %s\n", network.ActiveAccount.CChainAddress)
|
|
}
|
|
}
|
|
}
|
|
|
|
// FormatStatusSummary provides a compact summary format
|
|
func (f *StatusFormatter) FormatStatusSummary(result *StatusResult) {
|
|
for _, network := range result.Networks {
|
|
status := "stopped"
|
|
if network.Metadata.Status == "up" {
|
|
status = "up"
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "status %-8s %-8s grpc=%d nodes=%d vms=%d controller=%s\n",
|
|
network.Name,
|
|
status,
|
|
network.Metadata.GRPCPort,
|
|
network.Metadata.NodesCount,
|
|
network.Metadata.VMsCount,
|
|
network.Metadata.Controller)
|
|
}
|
|
}
|
|
|
|
// FormatChainStatus provides a compact chain status format
|
|
func (f *StatusFormatter) FormatChainStatus(result *StatusResult) {
|
|
for _, network := range result.Networks {
|
|
if len(network.Chains) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s chains\n", network.Name)
|
|
fmt.Fprintf(f.writer, "chain kind height rpc_ok latency\n")
|
|
|
|
for _, chain := range network.Chains {
|
|
rpcOK := "no"
|
|
if chain.RPC_OK {
|
|
rpcOK = "yes"
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-5s %-4s %-6d %-6s %dms\n",
|
|
chain.Alias,
|
|
chain.Kind,
|
|
chain.Height,
|
|
rpcOK,
|
|
chain.LatencyMS)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FormatNodeStatus provides a compact node status format
|
|
func (f *StatusFormatter) FormatNodeStatus(result *StatusResult) {
|
|
for _, network := range result.Networks {
|
|
if len(network.Nodes) > 0 {
|
|
fmt.Fprintf(f.writer, "\n%s nodes\n", network.Name)
|
|
fmt.Fprintf(f.writer, "node http version peers ok\n")
|
|
|
|
for _, node := range network.Nodes {
|
|
okStr := "no"
|
|
if node.OK {
|
|
okStr = "yes"
|
|
}
|
|
|
|
fmt.Fprintf(f.writer, "%-4s %-15s %-7s %-5d %s\n",
|
|
node.ID,
|
|
node.HTTPURL,
|
|
strings.TrimPrefix(node.Version, "luxd/"),
|
|
node.PeerCount,
|
|
okStr)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// FormatJSON outputs the status as JSON
|
|
func (f *StatusFormatter) FormatJSON(result *StatusResult) error {
|
|
encoder := json.NewEncoder(f.writer)
|
|
encoder.SetIndent("", " ")
|
|
return encoder.Encode(result)
|
|
}
|
|
|
|
// FormatYAML outputs the status as YAML
|
|
func (f *StatusFormatter) FormatYAML(result *StatusResult) error {
|
|
encoder := yaml.NewEncoder(f.writer)
|
|
encoder.SetIndent(2)
|
|
return encoder.Encode(result)
|
|
}
|