mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
325 lines
9.4 KiB
Go
325 lines
9.4 KiB
Go
// Copyright (C) 2022-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package node
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/luxfi/cli/pkg/dependencies"
|
|
|
|
apiinfo "github.com/luxfi/api/info"
|
|
"github.com/luxfi/cli/pkg/application"
|
|
"github.com/luxfi/cli/pkg/localnet"
|
|
"github.com/luxfi/cli/pkg/utils"
|
|
"github.com/luxfi/cli/pkg/ux"
|
|
"github.com/luxfi/config"
|
|
"github.com/luxfi/constants"
|
|
"github.com/luxfi/ids"
|
|
luxlog "github.com/luxfi/log"
|
|
sdkinfo "github.com/luxfi/sdk/info"
|
|
"github.com/luxfi/sdk/models"
|
|
"github.com/luxfi/sdk/platformvm"
|
|
)
|
|
|
|
func setupLuxd(
|
|
app *application.Lux,
|
|
luxdBinaryPath string,
|
|
luxdVersionSetting dependencies.LuxdVersionSettings,
|
|
network models.Network,
|
|
printFunc func(msg string, args ...interface{}),
|
|
) (string, error) {
|
|
var err error
|
|
luxdVersion := ""
|
|
if luxdBinaryPath == "" {
|
|
luxdVersion, err = dependencies.GetLuxdVersion(app, luxdVersionSetting, network)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
printFunc("Using Luxd version: %s", luxdVersion)
|
|
}
|
|
luxdBinaryPath, err = localnet.SetupLuxdBinary(app, luxdVersion, luxdBinaryPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
printFunc("Luxd path: %s\n", luxdBinaryPath)
|
|
return luxdBinaryPath, err
|
|
}
|
|
|
|
func StartLocalNode(
|
|
app *application.Lux,
|
|
clusterName string,
|
|
luxdBinaryPath string,
|
|
numNodes uint32,
|
|
defaultFlags map[string]interface{},
|
|
connectionSettings localnet.ConnectionSettings,
|
|
nodeSettings []localnet.NodeSetting,
|
|
luxdVersionSetting dependencies.LuxdVersionSettings,
|
|
network models.Network,
|
|
) error {
|
|
// initializes directories
|
|
networkDir := localnet.GetLocalClusterDir(app, clusterName)
|
|
pluginDir := filepath.Join(networkDir, "plugins")
|
|
if err := os.MkdirAll(networkDir, constants.DefaultPerms755); err != nil {
|
|
return fmt.Errorf("could not create network directory %s: %w", networkDir, err)
|
|
}
|
|
if err := os.MkdirAll(pluginDir, constants.DefaultPerms755); err != nil {
|
|
return fmt.Errorf("could not create plugin directory %s: %w", pluginDir, err)
|
|
}
|
|
|
|
if localnet.LocalClusterExists(app, clusterName) {
|
|
ux.Logger.GreenCheckmarkToUser("Local cluster %s found. Booting up...", clusterName)
|
|
if err := localnet.LoadLocalCluster(app, clusterName, luxdBinaryPath); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
var err error
|
|
luxdBinaryPath, err = setupLuxd(
|
|
app,
|
|
luxdBinaryPath,
|
|
luxdVersionSetting,
|
|
network,
|
|
ux.Logger.PrintToUser,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
ux.Logger.GreenCheckmarkToUser("Local cluster %s not found. Creating...", clusterName)
|
|
// network.ClusterName is not settable as it's a method
|
|
|
|
switch {
|
|
case network.Kind() == models.Testnet:
|
|
ux.Logger.PrintToUser("%s", luxlog.Yellow.Wrap("Warning: Testnet Bootstrapping can take several minutes"))
|
|
connectionSettings.NetworkID = network.ID()
|
|
case network.Kind() == models.Mainnet:
|
|
ux.Logger.PrintToUser("%s", luxlog.Yellow.Wrap("Warning: Mainnet Bootstrapping can take 6-24 hours"))
|
|
connectionSettings.NetworkID = network.ID()
|
|
case network.Kind() == models.Local:
|
|
connectionSettings, err = localnet.GetLocalNetworkConnectionInfo(app)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if defaultFlags == nil {
|
|
defaultFlags = map[string]interface{}{}
|
|
}
|
|
defaultFlags[config.NetworkAllowPrivateIPsKey] = true
|
|
defaultFlags[config.IndexEnabledKey] = false
|
|
defaultFlags[config.IndexAllowIncompleteKey] = true
|
|
|
|
ux.Logger.PrintToUser("Starting local luxd node using root: %s ...", networkDir)
|
|
spinSession := ux.NewUserSpinner()
|
|
spinner := spinSession.SpinToUser("Booting Network. Wait until healthy...")
|
|
|
|
// Convert validators to []interface{}
|
|
validators := make([]interface{}, 0)
|
|
|
|
// Get node version from settings - use "latest" if not specified
|
|
nodeVer := "latest"
|
|
if luxdVersionSetting.UseCustomLuxgoVersion != "" {
|
|
nodeVer = luxdVersionSetting.UseCustomLuxgoVersion
|
|
}
|
|
|
|
_, _, err = localnet.CreateLocalCluster(
|
|
app,
|
|
ux.Logger.PrintToUser,
|
|
nodeVer,
|
|
luxdBinaryPath,
|
|
clusterName,
|
|
defaultFlags,
|
|
connectionSettings,
|
|
numNodes,
|
|
nodeSettings,
|
|
validators,
|
|
network,
|
|
true, // enableMonitoring
|
|
false, // disableGrpcGateway
|
|
)
|
|
if err != nil {
|
|
ux.SpinFailWithError(spinner, "", err)
|
|
return fmt.Errorf("failed to start local luxd: %w", err)
|
|
}
|
|
|
|
ux.SpinComplete(spinner)
|
|
spinSession.Stop()
|
|
}
|
|
|
|
ux.Logger.GreenCheckmarkToUser("Luxgo started and ready to use from %s", networkDir)
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Node logs directory: %s/<NodeID>/logs", networkDir)
|
|
ux.Logger.PrintToUser("")
|
|
ux.Logger.PrintToUser("Network ready to use.")
|
|
ux.Logger.PrintToUser("")
|
|
|
|
clusterData, err := localnet.GetLocalCluster(app, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Type assert to access Nodes
|
|
if cluster, ok := clusterData.(*localnet.LocalCluster); ok && cluster != nil {
|
|
for _, nodeData := range cluster.Nodes {
|
|
if node, ok := nodeData.(map[string]interface{}); ok {
|
|
if uri, ok := node["URI"].(string); ok {
|
|
ux.Logger.PrintToUser("URI: %s", uri)
|
|
}
|
|
if nodeID, ok := node["NodeID"].(string); ok {
|
|
ux.Logger.PrintToUser("NodeID: %s", nodeID)
|
|
}
|
|
ux.Logger.PrintToUser("")
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func LocalStatus(
|
|
app *application.Lux,
|
|
clusterName string,
|
|
blockchainName string,
|
|
) error {
|
|
var localClusters []string
|
|
if clusterName != "" {
|
|
if !localnet.LocalClusterExists(app, clusterName) {
|
|
return fmt.Errorf("local node %q is not found", clusterName)
|
|
}
|
|
localClusters = []string{clusterName}
|
|
} else {
|
|
var err error
|
|
localClusters, err = localnet.GetLocalClusters(app)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to list local clusters: %w", err)
|
|
}
|
|
}
|
|
if clusterName != "" {
|
|
ux.Logger.PrintToUser("%s %s", luxlog.Blue.Wrap("Local cluster:"), luxlog.Green.Wrap(clusterName))
|
|
} else if len(localClusters) > 0 {
|
|
ux.Logger.PrintToUser("%s", luxlog.Blue.Wrap("Local clusters:"))
|
|
}
|
|
for _, clusterName := range localClusters {
|
|
currenlyRunning := ""
|
|
healthStatus := ""
|
|
luxdURIOuput := ""
|
|
|
|
network, err := localnet.GetLocalClusterNetworkModel(app, clusterName)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get cluster network: %w", err)
|
|
}
|
|
networkKind := fmt.Sprintf(" [%s]", luxlog.Orange.Wrap(network.Name()))
|
|
|
|
// load sidecar and cluster config for the cluster if blockchainName is not empty
|
|
blockchainID := ids.Empty
|
|
if blockchainName != "" {
|
|
sc, err := app.LoadSidecar(blockchainName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
blockchainID = sc.Networks[network.Name()].BlockchainID
|
|
}
|
|
isRunning, err := localnet.LocalClusterIsRunning(app, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if isRunning {
|
|
pChainHealth, l1Health, err := localnet.LocalClusterHealth(app, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
currenlyRunning = fmt.Sprintf(" [%s]", luxlog.Blue.Wrap("Running"))
|
|
if pChainHealth && l1Health {
|
|
healthStatus = fmt.Sprintf(" [%s]", luxlog.Green.Wrap("Healthy"))
|
|
} else {
|
|
healthStatus = fmt.Sprintf(" [%s]", luxlog.Red.Wrap("Unhealthy"))
|
|
}
|
|
runningLuxdURIs, err := localnet.GetLocalClusterURIs(app, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, luxdURI := range runningLuxdURIs {
|
|
nodeID, nodePOP, isBoot, err := getInfo(luxdURI, blockchainID.String())
|
|
if err != nil {
|
|
ux.Logger.RedXToUser("failed to get node %s info: %v", luxdURI, err)
|
|
continue
|
|
}
|
|
nodePOPPubKey := formatPOPField(nodePOP.PublicKey)
|
|
nodePOPProof := formatPOPField(nodePOP.ProofOfPossession)
|
|
|
|
isBootStr := "Primary:" + luxlog.Red.Wrap("Not Bootstrapped")
|
|
if isBoot {
|
|
isBootStr = "Primary:" + luxlog.Green.Wrap("Bootstrapped")
|
|
}
|
|
|
|
blockchainStatus := ""
|
|
if blockchainID != ids.Empty {
|
|
blockchainStatus, _ = getBlockchainStatus(luxdURI, blockchainID.String()) // silence errors
|
|
}
|
|
|
|
luxdURIOuput += fmt.Sprintf(" - %s [%s] [%s]\n publicKey: %s \n proofOfPossession: %s \n",
|
|
luxlog.LightBlue.Wrap(luxdURI),
|
|
nodeID,
|
|
strings.TrimRight(strings.Join([]string{isBootStr, "L1:" + luxlog.Orange.Wrap(blockchainStatus)}, " "), " "),
|
|
nodePOPPubKey,
|
|
nodePOPProof,
|
|
)
|
|
}
|
|
} else {
|
|
currenlyRunning = fmt.Sprintf(" [%s]", luxlog.LightGray.Wrap("Stopped"))
|
|
}
|
|
networkDir := localnet.GetLocalClusterDir(app, clusterName)
|
|
ux.Logger.PrintToUser("- %s: %s %s %s %s", clusterName, networkDir, networkKind, currenlyRunning, healthStatus)
|
|
ux.Logger.PrintToUser("%s", luxdURIOuput)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getInfo(uri string, blockchainID string) (
|
|
ids.NodeID, // nodeID
|
|
*apiinfo.ProofOfPossession, // nodePOP
|
|
bool, // isBootstrapped
|
|
error, // error
|
|
) {
|
|
client := sdkinfo.NewClient(uri)
|
|
ctx, cancel := utils.GetAPILargeContext()
|
|
defer cancel()
|
|
nodeID, nodePOP, err := client.GetNodeID(ctx)
|
|
if err != nil {
|
|
return ids.EmptyNodeID, &apiinfo.ProofOfPossession{}, false, err
|
|
}
|
|
isBootstrapped, err := client.IsBootstrapped(ctx, blockchainID)
|
|
if err != nil {
|
|
return nodeID, nodePOP, isBootstrapped, err
|
|
}
|
|
return nodeID, nodePOP, isBootstrapped, err
|
|
}
|
|
|
|
func formatPOPField(field string) string {
|
|
if strings.HasPrefix(field, "0x") || strings.HasPrefix(field, "0X") {
|
|
return field
|
|
}
|
|
return "0x" + field
|
|
}
|
|
|
|
func getBlockchainStatus(uri string, blockchainID string) (
|
|
string, // status
|
|
error, // error
|
|
) {
|
|
client := platformvm.NewClient(uri)
|
|
ctx, cancel := utils.GetAPILargeContext()
|
|
defer cancel()
|
|
status, err := client.GetBlockchainStatus(ctx, blockchainID)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if status.String() == "" {
|
|
return "Not Syncing", nil
|
|
}
|
|
return status.String(), nil
|
|
}
|