Files

322 lines
8.9 KiB
Go

// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package localnet
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/luxfi/cli/v2/pkg/application"
sdkutils "github.com/luxfi/cli/v2/sdk/utils"
"github.com/luxfi/node/v2/v2/api/info"
"github.com/luxfi/ids"
"github.com/luxfi/node/v2/v2/tests/fixture/tmpnet"
"golang.org/x/exp/maps"
)
var ErrNetworkNotRunning = errors.New("network is not running")
// Indicates if all, some or none of the local network nodes are running
func LocalNetworkRunningStatus(app *application.Lux) (RunningStatus, error) {
if LocalNetworkMetaExists(app) {
meta, err := GetLocalNetworkMeta(app)
if err != nil {
return UndefinedRunningStatus, err
}
if sdkutils.DirExists(meta.NetworkDir) {
status, err := GetTmpNetRunningStatus(meta.NetworkDir)
if err != nil {
return status, err
}
if status == NotRunning {
if err := RemoveLocalNetworkMeta(app); err != nil {
return NotRunning, err
}
}
return status, nil
}
}
return NotRunning, nil
}
// Returns true if all local network nodes are running
func IsLocalNetworkRunning(app *application.Lux) (bool, error) {
status, err := LocalNetworkRunningStatus(app)
if err != nil {
return false, err
}
return status == Running, nil
}
// Returns the tmpnet directory associated to the local network
// If the network is not alive it errors
func GetLocalNetworkDir(app *application.Lux) (string, error) {
isRunning, err := IsLocalNetworkRunning(app)
if err != nil {
return "", err
}
if !isRunning {
return "", ErrNetworkNotRunning
}
meta, err := GetLocalNetworkMeta(app)
if err != nil {
return "", err
}
return meta.NetworkDir, nil
}
// Returns the tmpnet associated to the local network
// If the network is not alive it errors
func GetLocalNetwork(app *application.Lux) (*tmpnet.Network, error) {
networkDir, err := GetLocalNetworkDir(app)
if err != nil {
return nil, err
}
return GetTmpNetNetwork(networkDir)
}
// Returns the endpoint associated to the local network
// If the network is not alive it errors
func GetLocalNetworkEndpoint(app *application.Lux) (string, error) {
network, err := GetLocalNetwork(app)
if err != nil {
return "", err
}
return GetTmpNetEndpoint(network)
}
// Returns blockchain info for all non standard blockchains deployed into the local network
func GetLocalNetworkBlockchainsInfo(app *application.Lux) ([]BlockchainInfo, error) {
endpoint, err := GetLocalNetworkEndpoint(app)
if err != nil {
return nil, err
}
return GetBlockchainsInfo(endpoint)
}
// Returns luxd version and RPC version for the local network
func GetLocalNetworkLuxdVersion(app *application.Lux) (bool, string, int, error) {
// not actually an error, network just not running
if isRunning, err := IsLocalNetworkRunning(app); err != nil {
return true, "", 0, err
} else if !isRunning {
return false, "", 0, nil
}
endpoint, err := GetLocalNetworkEndpoint(app)
if err != nil {
return true, "", 0, err
}
ctx, cancel := sdkutils.GetAPIContext()
defer cancel()
infoClient := info.NewClient(endpoint)
versionResponse, err := infoClient.GetNodeVersion(ctx)
if err != nil {
return true, "", 0, err
}
// version is in format lux/x.y.z, need to turn to semantic
splitVersion := strings.Split(versionResponse.Version, "/")
if len(splitVersion) != 2 {
return true, "", 0, fmt.Errorf("unable to parse luxd version " + versionResponse.Version)
}
// index 0 should be lux, index 1 will be version
parsedVersion := "v" + splitVersion[1]
return true, parsedVersion, int(versionResponse.RPCProtocolVersion), nil
}
// Stops the local network
func LocalNetworkStop(app *application.Lux) error {
networkDir, err := GetLocalNetworkDir(app)
if err != nil {
return err
}
if err := TmpNetStop(networkDir); err != nil {
return err
}
return RemoveLocalNetworkMeta(app)
}
// Returns a context large enough to support all local network operations
func GetLocalNetworkDefaultContext() (context.Context, context.CancelFunc) {
return sdkutils.GetTimedContext(2 * time.Minute)
}
// Indicates if the local network tracks a subnet at all
func IsLocalNetworkTrackingSubnet(
app *application.Lux,
subnetID ids.ID,
) (bool, error) {
network, err := GetLocalNetwork(app)
if err != nil {
return false, err
}
return IsTmpNetNodeTrackingSubnet(network.Nodes, subnetID)
}
// Indicates if a blockchain is bootstrapped on the local network
// If the network has no validators for the blockchain, it fails
func IsLocalNetworkBlockchainBootstrapped(
app *application.Lux,
blockchainID string,
subnetID ids.ID,
) (bool, error) {
network, err := GetLocalNetwork(app)
if err != nil {
return false, err
}
ctx, cancel := sdkutils.GetAPIContext()
defer cancel()
return IsTmpNetBlockchainBootstrapped(ctx, network, blockchainID, subnetID)
}
// Indicates if P-Chain is bootstrapped on the local network, and also if
// all blockchains that have validators on the local network, or in clusters
// connected to the local network, are bootstrapped
func LocalNetworkHealth(
app *application.Lux,
) (bool, bool, error) {
pChainBootstrapped, err := IsLocalNetworkBlockchainBootstrapped(app, "P", ids.Empty)
if err != nil {
return false, false, err
}
blockchains, err := GetLocalNetworkBlockchainsInfo(app)
if err != nil {
return pChainBootstrapped, false, err
}
clusters, err := GetRunningLocalClustersConnectedToLocalNetwork(app)
if err != nil {
return pChainBootstrapped, false, err
}
for _, blockchain := range blockchains {
isTracking, err := IsLocalNetworkTrackingSubnet(app, blockchain.SubnetID)
if err != nil {
return pChainBootstrapped, false, err
}
if !isTracking {
blockchainBootstrappedOnSomeCluster := false
for _, clusterName := range clusters {
if isTracking, err := IsLocalClusterTrackingSubnet(app, clusterName, blockchain.SubnetID); err != nil {
return pChainBootstrapped, false, err
} else if !isTracking {
continue
}
blockchainBootstrappedOnSomeCluster, err = IsLocalClusterBlockchainBootstrapped(app, clusterName, blockchain.ID.String(), blockchain.SubnetID)
if err != nil {
return pChainBootstrapped, false, err
}
if blockchainBootstrappedOnSomeCluster {
break
}
}
if !blockchainBootstrappedOnSomeCluster {
return pChainBootstrapped, false, nil
}
} else {
blockchainBootstrapped, err := IsLocalNetworkBlockchainBootstrapped(app, blockchain.ID.String(), blockchain.SubnetID)
if err != nil {
return pChainBootstrapped, false, err
}
if !blockchainBootstrapped {
return pChainBootstrapped, false, nil
}
}
}
return pChainBootstrapped, true, nil
}
// Create a local network of [numNodes] nodes at [networkDir] using luxd binary at [luxdBinPath]
// Make local network meta to point to it
func CreateLocalNetwork(
app *application.Lux,
networkDir string,
numNodes uint32,
pluginDir string,
luxdBinPath string,
) error {
// get default network conf for NumNodes
networkID, unparsedGenesis, upgradeBytes, defaultFlags, nodes, err := GetDefaultNetworkConf(numNodes)
if err != nil {
return err
}
// add node flags on CLI config info default network flags
nodeConfigStr, err := app.Conf.LoadNodeConfig()
if err != nil {
return err
}
var nodeConfig map[string]interface{}
if err := json.Unmarshal([]byte(nodeConfigStr), &nodeConfig); err != nil {
return fmt.Errorf("invalid common node config JSON: %w", err)
}
maps.Copy(defaultFlags, nodeConfig)
// create network
ctx, cancel := GetLocalNetworkDefaultContext()
defer cancel()
if _, err := TmpNetCreate(
ctx,
app.Log,
networkDir,
luxdBinPath,
pluginDir,
networkID,
nil,
nil,
unparsedGenesis,
upgradeBytes,
defaultFlags,
nodes,
true,
); err != nil {
_ = TmpNetStop(networkDir)
return err
}
// save network directory
return SaveLocalNetworkMeta(app, networkDir)
}
// Load a local network at [networkDir] using luxd binary at [luxdBinPath]
// Make local network meta to point to it
func LoadLocalNetwork(
app *application.Lux,
networkDir string,
luxdBinPath string,
) error {
// add node flags on CLI config info flags
nodeConfigStr, err := app.Conf.LoadNodeConfig()
if err != nil {
return err
}
var nodeConfig map[string]interface{}
if err := json.Unmarshal([]byte(nodeConfigStr), &nodeConfig); err != nil {
return fmt.Errorf("invalid common node config JSON: %w", err)
}
network, err := GetTmpNetNetwork(networkDir)
if err != nil {
return err
}
for i := range network.Nodes {
for k, v := range nodeConfig {
network.Nodes[i].Flags[k] = v
}
}
if err := network.Write(); err != nil {
return err
}
// local network
ctx, cancel := GetLocalNetworkDefaultContext()
defer cancel()
if _, err := TmpNetLoad(ctx, app.Log, networkDir, luxdBinPath); err != nil {
_ = TmpNetStop(networkDir)
return err
}
// set aliases
if err := TmpNetSetDefaultAliases(ctx, networkDir); err != nil {
return err
}
// save network directory
return SaveLocalNetworkMeta(app, networkDir)
}