Files
zeekay 8f56009cc7 fix: deep audit - remove stubs, fix imports, clean dead code
Deep audit changes:
- Replace stub signature aggregator with real SDK warp implementation
- Remove dead pkg/lpm package (SDK lpmintegration used instead)
- Fix fs imports: use local utils.FileExists, DirExists, ExpandHome
- Fix secp256k1fx imports: use node/vms/secp256k1fx
- Fix platformvm imports: use node/vms/platformvm
- Fix e2e test TODOs: uncomment BootstrapValidators assertions
- Improve localnet stub implementations with real logic
- Fix node.go fs.FileExists reference

Note: Build requires GOWORK=off due to workspace dependency issues
2026-01-10 11:17:00 -08:00

254 lines
7.5 KiB
Go

// Copyright (C) 2022-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package node
import (
"encoding/json"
"errors"
"fmt"
"sync"
"github.com/luxfi/cli/pkg/ansible"
"github.com/luxfi/cli/pkg/application"
"github.com/luxfi/cli/pkg/chain"
"github.com/luxfi/cli/pkg/ssh"
"github.com/luxfi/cli/pkg/utils"
"github.com/luxfi/cli/pkg/ux"
"github.com/luxfi/math/set"
"github.com/luxfi/sdk/models"
)
func SyncChain(app *application.Lux, clusterName, blockchainName string, avoidChecks bool, chainAliases []string) error {
if err := CheckCluster(app, clusterName); err != nil {
return err
}
clusterConfig, err := app.GetClusterConfig(clusterName)
if err != nil {
return err
}
if err := chain.ValidateChainNameAndGetChains(blockchainName); err != nil {
return err
}
hosts, err := ansible.GetInventoryFromAnsibleInventoryFile(app.GetAnsibleInventoryDirPath(clusterName))
if err != nil {
return err
}
defer DisconnectHosts(hosts)
if !avoidChecks {
if err := CheckHostsAreBootstrapped(hosts); err != nil {
return err
}
if err := CheckHostsAreHealthy(hosts); err != nil {
return err
}
if err := CheckHostsAreRPCCompatible(app, hosts, blockchainName); err != nil {
return err
}
}
if err := prepareChainPlugin(app, hosts, blockchainName); err != nil {
return err
}
// Type assertion for network field
networkStr, _ := clusterConfig["network"].(string)
network := models.NetworkFromString(networkStr)
if err := trackChain(app, hosts, clusterName, network, blockchainName, chainAliases); err != nil {
return err
}
ux.Logger.PrintToUser("Node(s) successfully started syncing with blockchain!")
ux.Logger.PrintToUser("%s", fmt.Sprintf("Check node blockchain syncing status with lux node status %s --blockchain %s", clusterName, blockchainName))
return nil
}
// prepareChainPlugin creates chain plugin to all nodes in the cluster
func prepareChainPlugin(app *application.Lux, hosts []*models.Host, blockchainName string) error {
sc, err := app.LoadSidecar(blockchainName)
if err != nil {
return err
}
wg := sync.WaitGroup{}
wgResults := models.NodeResults{}
for _, host := range hosts {
wg.Add(1)
go func(nodeResults *models.NodeResults, host *models.Host) {
defer wg.Done()
if err := ssh.RunSSHCreatePlugin(host, sc); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
}
}(&wgResults, host)
}
wg.Wait()
if wgResults.HasErrors() {
return fmt.Errorf("failed to upload plugin to node(s) %s", wgResults.GetErrorHostMap())
}
return nil
}
func trackChain(
app *application.Lux,
hosts []*models.Host,
clusterName string,
network models.Network,
blockchainName string,
chainAliases []string,
) error {
// load cluster config
clusterConfig, err := app.GetClusterConfig(clusterName)
if err != nil {
return err
}
// and get list of chains
chains, _ := clusterConfig["chains"].([]string)
allChains := utils.Unique(append(chains, blockchainName))
// load sidecar to get chain blockchain ID
sc, err := app.LoadSidecar(blockchainName)
if err != nil {
return err
}
blockchainID := sc.Networks[network.Name()].BlockchainID
wg := sync.WaitGroup{}
wgResults := models.NodeResults{}
for _, host := range hosts {
wg.Add(1)
go func(nodeResults *models.NodeResults, host *models.Host) {
defer wg.Done()
if err := ssh.RunSSHStopNode(host); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
}
if err := ssh.RunSSHRenderLuxdAliasConfigFile(
host,
blockchainID.String(),
chainAliases,
); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
}
// Check if this host is an API host
apiNodes, _ := clusterConfig["apiNodes"].([]string)
isAPIHost := false
for _, apiNode := range apiNodes {
if apiNode == host.GetCloudID() {
isAPIHost = true
break
}
}
if err := ssh.RunSSHRenderLuxNodeConfig(
app,
host,
network,
allChains,
isAPIHost,
); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
}
if err := ssh.RunSSHSyncChainData(app, host, network, blockchainName); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
}
if err := ssh.RunSSHStartNode(host); err != nil {
nodeResults.AddResult(host.NodeID, nil, err)
return
}
}(&wgResults, host)
}
wg.Wait()
if wgResults.HasErrors() {
return fmt.Errorf("failed to track network for node(s) %s", wgResults.GetErrorHostMap())
}
// update slice of chains synced by the cluster
clusterConfig["chains"] = allChains
// Save the updated cluster configuration
if err := app.SetClusterConfig(clusterName, clusterConfig); err != nil {
return fmt.Errorf("failed to save cluster config: %w", err)
}
// update slice of blockchain endpoints with the cluster ones
// Type assertion for network field
networkStr, _ := clusterConfig["network"].(string)
network = models.NetworkFromString(networkStr)
networkInfo := sc.Networks[network.Name()]
rpcEndpoints := set.Of(networkInfo.RPCEndpoints...)
wsEndpoints := set.Of(networkInfo.WSEndpoints...)
publicEndpoints, err := getPublicEndpoints(app, clusterName, hosts)
if err != nil {
return err
}
for _, publicEndpoint := range publicEndpoints {
rpcEndpoints.Add(models.GetRPCEndpoint(publicEndpoint, networkInfo.BlockchainID.String()))
wsEndpoints.Add(models.GetWSEndpoint(publicEndpoint, networkInfo.BlockchainID.String()))
}
networkInfo.RPCEndpoints = rpcEndpoints.List()
networkInfo.WSEndpoints = wsEndpoints.List()
sc.Networks[network.Name()] = networkInfo
return app.UpdateSidecar(&sc)
}
func CheckHostsAreBootstrapped(hosts []*models.Host) error {
notBootstrappedNodes, err := GetNotBootstrappedNodes(hosts)
if err != nil {
return err
}
if len(notBootstrappedNodes) > 0 {
return fmt.Errorf("node(s) %s are not bootstrapped yet, please try again later", notBootstrappedNodes)
}
return nil
}
func CheckHostsAreHealthy(hosts []*models.Host) error {
ux.Logger.PrintToUser("Checking if node(s) are healthy...")
unhealthyNodes, err := GetUnhealthyNodes(hosts)
if err != nil {
return err
}
if len(unhealthyNodes) > 0 {
return fmt.Errorf("node(s) %s are not healthy, please check the issue and try again later", unhealthyNodes)
}
return nil
}
func GetNotBootstrappedNodes(hosts []*models.Host) ([]string, error) {
wg := sync.WaitGroup{}
wgResults := models.NodeResults{}
for _, host := range hosts {
wg.Add(1)
go func(nodeResults *models.NodeResults, host *models.Host) {
defer wg.Done()
if resp, err := ssh.RunSSHCheckBootstrapped(host); err != nil {
nodeResults.AddResult(host.GetCloudID(), nil, err)
return
} else {
if isBootstrapped, err := parseBootstrappedOutput(resp); err != nil {
nodeResults.AddResult(host.GetCloudID(), nil, err)
} else {
nodeResults.AddResult(host.GetCloudID(), isBootstrapped, err)
}
}
}(&wgResults, host)
}
wg.Wait()
if wgResults.HasErrors() {
return nil, fmt.Errorf("failed to get luxd bootstrap status for node(s) %s", wgResults.GetErrorHostMap())
}
return utils.Filter(wgResults.GetNodeList(), func(nodeID string) bool {
return !wgResults.GetResultMap()[nodeID].(bool)
}), nil
}
func parseBootstrappedOutput(byteValue []byte) (bool, error) {
var result map[string]interface{}
if err := json.Unmarshal(byteValue, &result); err != nil {
return false, err
}
isBootstrappedInterface, ok := result["result"].(map[string]interface{})
if ok {
isBootstrapped, ok := isBootstrappedInterface["isBootstrapped"].(bool)
if ok {
return isBootstrapped, nil
}
}
return false, errors.New("unable to parse node bootstrap status")
}