Files

870 lines
22 KiB
Go

// Copyright (C) 2022, Lux Industries Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package utils
import (
"context"
"crypto/sha256"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"strings"
"time"
"github.com/luxfi/cli/v2/pkg/subnet"
"github.com/luxfi/cli/v2/pkg/binutils"
"github.com/luxfi/cli/v2/pkg/constants"
"github.com/luxfi/cli/v2/pkg/key"
"github.com/luxfi/cli/v2/pkg/models"
"github.com/luxfi/netrunner/client"
"github.com/luxfi/node/v2/v2/api/info"
"github.com/luxfi/ids"
lux_constants "github.com/luxfi/node/v2/v2/utils/constants"
"github.com/luxfi/node/v2/v2/utils/crypto/keychain"
ledger "github.com/luxfi/node/v2/v2/utils/crypto/ledger"
luxlog "github.com/luxfi/log"
"github.com/luxfi/node/v2/v2/vms/components/lux"
"github.com/luxfi/node/v2/v2/vms/platformvm"
"github.com/luxfi/node/v2/v2/vms/secp256k1fx"
"github.com/luxfi/node/v2/v2/wallet/subnet/primary"
"github.com/luxfi/evm/v2/ethclient"
)
const (
expectedRPCComponentsLen = 7
blockchainIDPos = 5
subnetEVMName = "evm"
)
var defaultLocalNetworkNodeIDs = []string{
"NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg", "NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ",
"NodeID-NFBbbJ4qCmNaCzeW7sxErhvWqvEQMnYcN", "NodeID-GWPcbFJZFfZreETSoWjPimr846mXEKCtu", "NodeID-P7oB2McjBGgW2NXXWVYjV8JEDFoW9xDE5",
}
func GetBaseDir() string {
usr, err := user.Current()
if err != nil {
panic(err)
}
return path.Join(usr.HomeDir, baseDir)
}
func GetLPMDir() string {
usr, err := user.Current()
if err != nil {
panic(err)
}
return path.Join(usr.HomeDir, LPMDir)
}
func ChainConfigExists(subnetName string) (bool, error) {
cfgPath := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.ChainConfigFileName)
cfgExists := true
if _, err := os.Stat(cfgPath); errors.Is(err, os.ErrNotExist) {
// does *not* exist
cfgExists = false
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return cfgExists, nil
}
func PerNodeChainConfigExists(subnetName string) (bool, error) {
cfgPath := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.PerNodeChainConfigFileName)
cfgExists := true
if _, err := os.Stat(cfgPath); errors.Is(err, os.ErrNotExist) {
// does *not* exist
cfgExists = false
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return cfgExists, nil
}
func genesisExists(subnetName string) (bool, error) {
genesis := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.GenesisFileName)
genesisExists := true
if _, err := os.Stat(genesis); errors.Is(err, os.ErrNotExist) {
// does *not* exist
genesisExists = false
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return genesisExists, nil
}
func sidecarExists(subnetName string) (bool, error) {
sidecar := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.SidecarFileName)
sidecarExists := true
if _, err := os.Stat(sidecar); errors.Is(err, os.ErrNotExist) {
// does *not* exist
sidecarExists = false
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return sidecarExists, nil
}
func ElasticSubnetConfigExists(subnetName string) (bool, error) {
elasticSubnetConfig := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.ElasticSubnetConfigFileName)
elasticSubnetConfigExists := true
if _, err := os.Stat(elasticSubnetConfig); errors.Is(err, os.ErrNotExist) {
// does *not* exist
elasticSubnetConfigExists = false
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return elasticSubnetConfigExists, nil
}
func PermissionlessValidatorExistsInSidecar(subnetName string, nodeID string, network string) (bool, error) {
sc, err := getSideCar(subnetName)
if err != nil {
return false, err
}
elasticSubnetValidators := sc.ElasticSubnet[network].Validators
_, ok := elasticSubnetValidators[nodeID]
return ok, nil
}
func SubnetConfigExists(subnetName string) (bool, error) {
gen, err := genesisExists(subnetName)
if err != nil {
return false, err
}
sc, err := sidecarExists(subnetName)
if err != nil {
return false, err
}
// do an xor
if (gen || sc) && !(gen && sc) {
return false, errors.New("config half exists")
}
return gen && sc, nil
}
func AddSubnetIDToSidecar(subnetName string, network models.Network, subnetID string) error {
exists, err := sidecarExists(subnetName)
if err != nil {
return fmt.Errorf("failed to access sidecar for %s: %w", subnetName, err)
}
if !exists {
return fmt.Errorf("failed to access sidecar for %s: not found", subnetName)
}
sidecar := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.SidecarFileName)
jsonBytes, err := os.ReadFile(sidecar)
if err != nil {
return err
}
var sc models.Sidecar
err = json.Unmarshal(jsonBytes, &sc)
if err != nil {
return err
}
subnetIDstr, err := ids.FromString(subnetID)
if err != nil {
return err
}
sc.Networks[network.String()] = models.NetworkData{
SubnetID: subnetIDstr,
}
fileBytes, err := json.Marshal(&sc)
if err != nil {
return err
}
return os.WriteFile(sidecar, fileBytes, constants.DefaultPerms755)
}
func LPMConfigExists(subnetName string) (bool, error) {
return sidecarExists(subnetName)
}
func SubnetCustomVMExists(subnetName string) (bool, error) {
vm := path.Join(GetBaseDir(), constants.CustomVMDir, subnetName)
vmExists := true
if _, err := os.Stat(vm); errors.Is(err, os.ErrNotExist) {
// does *not* exist
vmExists = false
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return vmExists, nil
}
func SubnetLPMVMExists(subnetName string) (bool, error) {
sidecarPath := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.SidecarFileName)
jsonBytes, err := os.ReadFile(sidecarPath)
if err != nil {
return false, err
}
var sc models.Sidecar
err = json.Unmarshal(jsonBytes, &sc)
if err != nil {
return false, err
}
vmid := sc.ImportedVMID
vm := path.Join(GetBaseDir(), LPMPluginDir, vmid)
vmExists := true
if _, err := os.Stat(vm); errors.Is(err, os.ErrNotExist) {
// does *not* exist
vmExists = false
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return vmExists, nil
}
func KeyExists(keyName string) (bool, error) {
keyPath := path.Join(GetBaseDir(), constants.KeyDir, keyName+constants.KeySuffix)
if _, err := os.Stat(keyPath); errors.Is(err, os.ErrNotExist) {
// does *not* exist
return false, nil
} else if err != nil {
// Schrodinger: file may or may not exist. See err for details.
return false, err
}
return true, nil
}
func DeleteConfigs(subnetName string) error {
subnetDir := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName)
if _, err := os.Stat(subnetDir); err != nil && !errors.Is(err, os.ErrNotExist) {
// Schrodinger: file may or may not exist. See err for details.
return err
}
// ignore error, file may not exist
_ = os.RemoveAll(subnetDir)
return nil
}
func RemoveLPMRepo() {
_ = os.RemoveAll(GetLPMDir())
}
func DeleteKey(keyName string) error {
keyPath := path.Join(GetBaseDir(), constants.KeyDir, keyName+constants.KeySuffix)
if _, err := os.Stat(keyPath); err != nil && !errors.Is(err, os.ErrNotExist) {
// Schrodinger: file may or may not exist. See err for details.
return err
}
// ignore error, file may not exist
_ = os.Remove(keyPath)
return nil
}
func DeleteBins() error {
luxPath := path.Join(GetBaseDir(), constants.LuxCliBinDir, constants.LuxInstallDir)
if _, err := os.Stat(luxPath); err != nil && !errors.Is(err, os.ErrNotExist) {
// Schrodinger: file may or may not exist. See err for details.
return err
}
// ignore error, file may not exist
_ = os.RemoveAll(luxPath)
subevmPath := path.Join(GetBaseDir(), constants.LuxCliBinDir, constants.EVMInstallDir)
if _, err := os.Stat(subevmPath); err != nil && !errors.Is(err, os.ErrNotExist) {
// Schrodinger: file may or may not exist. See err for details.
return err
}
// ignore error, file may not exist
_ = os.RemoveAll(subevmPath)
return nil
}
func DeleteCustomBinary(vmName string) {
vmPath := path.Join(GetBaseDir(), constants.VMDir, vmName)
// ignore error, file may not exist
_ = os.RemoveAll(vmPath)
}
func DeleteLPMBin(vmid string) {
vmPath := path.Join(GetBaseDir(), constants.LuxCliBinDir, LPMPluginDir, vmid)
// ignore error, file may not exist
_ = os.RemoveAll(vmPath)
}
func stdoutParser(output string, queue string, capture string) (string, error) {
// split output by newline
lines := strings.Split(output, "\n")
for _, line := range lines {
if strings.Contains(line, queue) {
index := strings.Index(line, capture)
if index == -1 {
return "", errors.New("capture string not available at queue")
}
return line[index:], nil
}
}
return "", errors.New("no queue string found")
}
func ParseRPCsFromOutput(output string) ([]string, error) {
rpcs := []string{}
blockchainIDs := map[string]struct{}{}
// split output by newline
lines := strings.Split(output, "\n")
for _, line := range lines {
if !strings.Contains(line, "rpc") {
continue
}
startIndex := strings.Index(line, "http")
if startIndex == -1 {
return nil, fmt.Errorf("no url in RPC URL line: %s", line)
}
endIndex := strings.Index(line, "rpc")
rpc := line[startIndex : endIndex+3]
rpcComponents := strings.Split(rpc, "/")
if len(rpcComponents) != expectedRPCComponentsLen {
return nil, fmt.Errorf("unexpected number of components in url %q: expected %d got %d",
rpc,
expectedRPCComponentsLen,
len(rpcComponents),
)
}
blockchainID := rpcComponents[blockchainIDPos]
_, ok := blockchainIDs[blockchainID]
if !ok {
blockchainIDs[blockchainID] = struct{}{}
rpcs = append(rpcs, rpc)
}
}
if len(rpcs) == 0 {
return nil, errors.New("no RPCs where found")
}
return rpcs, nil
}
type greeterAddr struct {
Greeter string
}
func ParseGreeterAddress(output string) error {
addr, err := stdoutParser(output, "Greeter deployed to:", "0x")
if err != nil {
return err
}
greeter := greeterAddr{addr}
file, err := json.MarshalIndent(greeter, "", " ")
if err != nil {
return err
}
return os.WriteFile(greeterFile, file, 0o600)
}
type confFile struct {
RPC string `json:"rpc"`
ChainID string `json:"chainID"`
}
func SetHardhatRPC(rpc string) error {
client, err := ethclient.Dial(rpc)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), constants.E2ERequestTimeout)
chainIDBig, err := client.ChainID(ctx)
cancel()
if err != nil {
return err
}
confFileData := confFile{
RPC: rpc,
ChainID: chainIDBig.String(),
}
file, err := json.MarshalIndent(confFileData, "", " ")
if err != nil {
return err
}
return os.WriteFile(confFilePath, file, 0o600)
}
func RunHardhatTests(test string) error {
cmd := exec.Command("npx", "hardhat", "test", test, "--network", "subnet")
cmd.Dir = hardhatDir
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Println(string(output))
fmt.Println(err)
}
return err
}
func RunHardhatScript(script string) (string, string, error) {
cmd := exec.Command("npx", "hardhat", "run", script, "--network", "subnet")
cmd.Dir = hardhatDir
output, err := cmd.CombinedOutput()
var (
exitErr *exec.ExitError
stderr string
)
if errors.As(err, &exitErr) {
stderr = string(exitErr.Stderr)
}
if err != nil {
fmt.Println(string(output))
fmt.Println(err)
}
return string(output), stderr, err
}
func PrintStdErr(err error) {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
fmt.Println(string(exitErr.Stderr))
}
}
func CheckKeyEquality(keyPath1, keyPath2 string) (bool, error) {
key1, err := os.ReadFile(keyPath1)
if err != nil {
return false, err
}
key2, err := os.ReadFile(keyPath2)
if err != nil {
return false, err
}
return string(key1) == string(key2), nil
}
func CheckEVMExists(version string) bool {
subevmPath := path.Join(GetBaseDir(), constants.LuxCliBinDir, constants.EVMInstallDir, "evm-"+version)
_, err := os.Stat(subevmPath)
return err == nil
}
func CheckLuxExists(version string) bool {
luxPath := path.Join(GetBaseDir(), constants.LuxCliBinDir, constants.LuxInstallDir, "node-"+version)
_, err := os.Stat(luxPath)
return err == nil
}
// Currently downloads evm, but that suffices to test the custom vm functionality
func DownloadCustomVMBin(subnetEVMversion string) (string, error) {
targetDir := os.TempDir()
subnetEVMDir, err := binutils.DownloadReleaseVersion(luxlog.NewNoOpLogger(), subnetEVMName, subnetEVMversion, targetDir)
if err != nil {
return "", err
}
subnetEVMBin := path.Join(subnetEVMDir, subnetEVMName)
if _, err := os.Stat(subnetEVMBin); errors.Is(err, os.ErrNotExist) {
return "", errors.New("subnet evm bin file was not created")
} else if err != nil {
return "", err
}
return subnetEVMBin, nil
}
func ParsePublicDeployOutput(output string) (string, error) {
lines := strings.Split(output, "\n")
var subnetID string
for _, line := range lines {
if !strings.Contains(line, "Subnet ID") && !strings.Contains(line, "RPC URL") {
continue
}
words := strings.Split(line, "|")
if len(words) != 4 {
return "", errors.New("error parsing output: invalid number of words in line")
}
if strings.Contains(line, "Subnet ID") {
subnetID = strings.TrimSpace(words[2])
}
}
if subnetID == "" {
return "", errors.New("information not found in output")
}
return subnetID, nil
}
func RestartNodesWithWhitelistedSubnets(whitelistedSubnets string) error {
cli, err := binutils.NewGRPCClient()
if err != nil {
return err
}
rootCtx := context.Background()
ctx, cancel := context.WithTimeout(rootCtx, constants.E2ERequestTimeout)
resp, err := cli.Status(ctx)
cancel()
if err != nil {
return err
}
for _, nodeName := range resp.ClusterInfo.NodeNames {
ctx, cancel := context.WithTimeout(rootCtx, constants.E2ERequestTimeout)
_, err := cli.RestartNode(ctx, nodeName, client.WithWhitelistedSubnets(whitelistedSubnets))
cancel()
if err != nil {
return err
}
}
ctx, cancel = context.WithTimeout(rootCtx, constants.E2ERequestTimeout)
_, err = cli.Health(ctx)
cancel()
if err != nil {
return err
}
return nil
}
type NodeInfo struct {
ID string
PluginDir string
ConfigFile string
URI string
LogDir string
}
func GetNodeVMVersion(nodeURI string, vmid string) (string, error) {
rootCtx := context.Background()
ctx, cancel := context.WithTimeout(rootCtx, constants.E2ERequestTimeout)
client := info.NewClient(nodeURI)
versionInfo, err := client.GetNodeVersion(ctx)
cancel()
if err != nil {
return "", err
}
for vm, version := range versionInfo.VMVersions {
if vm == vmid {
return version, nil
}
}
return "", errors.New("vmid not found")
}
func GetNodesInfo() (map[string]NodeInfo, error) {
cli, err := binutils.NewGRPCClient()
if err != nil {
return nil, err
}
rootCtx := context.Background()
ctx, cancel := context.WithTimeout(rootCtx, constants.E2ERequestTimeout)
resp, err := cli.Status(ctx)
cancel()
if err != nil {
return nil, err
}
nodesInfo := map[string]NodeInfo{}
for nodeName, nodeInfo := range resp.ClusterInfo.NodeInfos {
nodesInfo[nodeName] = NodeInfo{
ID: nodeInfo.Id,
PluginDir: nodeInfo.PluginDir,
ConfigFile: path.Join(path.Dir(nodeInfo.LogDir), "config.json"),
URI: nodeInfo.Uri,
LogDir: nodeInfo.LogDir,
}
}
return nodesInfo, nil
}
func GetWhitelistedSubnetsFromConfigFile(configFile string) (string, error) {
fileBytes, err := os.ReadFile(configFile)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return "", fmt.Errorf("failed to load node config file %s: %w", configFile, err)
}
var luxConfig map[string]interface{}
if err := json.Unmarshal(fileBytes, &luxConfig); err != nil {
return "", fmt.Errorf("failed to unpack the config file %s to JSON: %w", configFile, err)
}
whitelistedSubnetsIntf := luxConfig["track-subnets"]
whitelistedSubnets, ok := whitelistedSubnetsIntf.(string)
if !ok {
return "", fmt.Errorf("expected a string value, but got %T", whitelistedSubnetsIntf)
}
return whitelistedSubnets, nil
}
func WaitSubnetValidators(subnetIDStr string, nodeInfos map[string]NodeInfo) error {
var uri string
for _, nodeInfo := range nodeInfos {
uri = nodeInfo.URI
break
}
pClient := platformvm.NewClient(uri)
subnetID, err := ids.FromString(subnetIDStr)
if err != nil {
return err
}
mainCtx, mainCtxCancel := context.WithTimeout(context.Background(), time.Second*30)
defer mainCtxCancel()
for {
ready := true
ctx, ctxCancel := context.WithTimeout(context.Background(), constants.E2ERequestTimeout)
vs, err := pClient.GetCurrentValidators(ctx, subnetID, nil)
ctxCancel()
if err != nil {
return err
}
subnetValidators := map[string]struct{}{}
for _, v := range vs {
subnetValidators[v.NodeID.String()] = struct{}{}
}
for _, nodeInfo := range nodeInfos {
if _, isValidator := subnetValidators[nodeInfo.ID]; !isValidator {
ready = false
}
}
if ready {
return nil
}
select {
case <-mainCtx.Done():
return mainCtx.Err()
case <-time.After(time.Second * 1):
}
}
}
func GetFileHash(filename string) (string, error) {
f, err := os.Open(filename)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return fmt.Sprintf("%x", h.Sum(nil)), nil
}
func FundLedgerAddress() error {
// get ledger
ledgerDev, err := ledger.New()
if err != nil {
return err
}
// get ledger addr
ledgerAddrs, err := ledgerDev.Addresses([]uint32{0})
if err != nil {
return err
}
if len(ledgerAddrs) != 1 {
return fmt.Errorf("no ledger addresses available")
}
ledgerAddr := ledgerAddrs[0]
// get genesis funded wallet
sk, err := key.LoadSoft(constants.LocalNetworkID, EwoqKeyPath)
if err != nil {
return err
}
var kc keychain.Keychain
kc = sk.KeyChain()
// Create wallet without ETH keychain (use nil)
wallet, err := primary.MakeWallet(context.Background(), constants.LocalAPIEndpoint, kc, nil, primary.WalletConfig{})
if err != nil {
return err
}
// export X-Chain genesis addr to P-Chain ledger addr
to := secp256k1fx.OutputOwners{
Threshold: 1,
Addrs: []ids.ShortID{ledgerAddr},
}
output := &lux.TransferableOutput{
Asset: lux.Asset{ID: wallet.X().Builder().Context().LUXAssetID},
Out: &secp256k1fx.TransferOutput{
Amt: 1000000000,
OutputOwners: to,
},
}
outputs := []*lux.TransferableOutput{output}
if _, err := wallet.X().IssueExportTx(lux_constants.PlatformChainID, outputs); err != nil {
return err
}
// get ledger funded wallet
kc, err = keychain.NewLedgerKeychain(ledgerDev, 1)
if err != nil {
return err
}
wallet, err = primary.MakeWallet(context.Background(), constants.LocalAPIEndpoint, kc, nil, primary.WalletConfig{})
if err != nil {
return err
}
// import X-Chain genesis addr to P-Chain ledger addr
fmt.Println("*** Please sign import hash on the ledger device *** ")
if _, err = wallet.P().IssueImportTx(wallet.X().Builder().Context().BlockchainID, &to); err != nil {
return err
}
if err := ledgerDev.Disconnect(); err != nil {
return err
}
return nil
}
func GetPluginBinaries() ([]string, error) {
// load plugin files from the plugin directory
pluginDir := path.Join(GetBaseDir(), PluginDirExt)
files, err := os.ReadDir(pluginDir)
if err != nil {
return nil, err
}
pluginFiles := []string{}
for _, file := range files {
if file.IsDir() {
continue
}
pluginFiles = append(pluginFiles, filepath.Join(pluginDir, file.Name()))
}
return pluginFiles, nil
}
func getSideCar(subnetName string) (models.Sidecar, error) {
exists, err := sidecarExists(subnetName)
if err != nil {
return models.Sidecar{}, fmt.Errorf("failed to access sidecar for %s: %w", subnetName, err)
}
if !exists {
return models.Sidecar{}, fmt.Errorf("failed to access sidecar for %s: not found", subnetName)
}
sidecar := filepath.Join(GetBaseDir(), constants.SubnetDir, subnetName, constants.SidecarFileName)
jsonBytes, err := os.ReadFile(sidecar)
if err != nil {
return models.Sidecar{}, err
}
var sc models.Sidecar
err = json.Unmarshal(jsonBytes, &sc)
if err != nil {
return models.Sidecar{}, err
}
return sc, nil
}
func GetValidators(subnetName string) ([]string, error) {
sc, err := getSideCar(subnetName)
if err != nil {
return nil, err
}
subnetID := sc.Networks[models.Local.String()].SubnetID
if subnetID == ids.Empty {
return nil, errors.New("no subnet id")
}
// Get NodeIDs of all validators on the subnet
validators, err := subnet.GetSubnetValidators(subnetID)
if err != nil {
return nil, err
}
nodeIDsList := []string{}
for _, validator := range validators {
nodeIDsList = append(nodeIDsList, validator.NodeID.String())
}
return nodeIDsList, nil
}
func GetCurrentSupply(subnetName string) error {
sc, err := getSideCar(subnetName)
if err != nil {
return err
}
subnetID := sc.Networks[models.Local.String()].SubnetID
return subnet.GetCurrentSupply(subnetID)
}
func IsNodeInPendingValidator(subnetName string, nodeID string) (bool, error) {
sc, err := getSideCar(subnetName)
if err != nil {
return false, err
}
subnetID := sc.Networks[models.Local.String()].SubnetID
return subnet.CheckNodeIsInSubnetPendingValidators(subnetID, nodeID)
}
func CheckAllNodesAreCurrentValidators(subnetName string) (bool, error) {
sc, err := getSideCar(subnetName)
if err != nil {
return false, err
}
subnetID := sc.Networks[models.Local.String()].SubnetID
api := constants.LocalAPIEndpoint
pClient := platformvm.NewClient(api)
ctx, cancel := context.WithTimeout(context.Background(), constants.E2ERequestTimeout)
defer cancel()
validators, err := pClient.GetCurrentValidators(ctx, subnetID, nil)
if err != nil {
return false, err
}
for _, nodeIDstr := range defaultLocalNetworkNodeIDs {
currentValidator := false
for _, validator := range validators {
if validator.NodeID.String() == nodeIDstr {
currentValidator = true
}
}
if !currentValidator {
return false, fmt.Errorf("%s is still not a current validator of the elastic subnet", nodeIDstr)
}
}
return true, nil
}
func AllPermissionlessValidatorExistsInSidecar(subnetName string, network string) (bool, error) {
sc, err := getSideCar(subnetName)
if err != nil {
return false, err
}
elasticSubnetValidators := sc.ElasticSubnet[network].Validators
for _, nodeIDstr := range defaultLocalNetworkNodeIDs {
_, ok := elasticSubnetValidators[nodeIDstr]
if !ok {
return false, err
}
}
return true, nil
}