diff --git a/cmd/blockchaincmd/deploy.go b/cmd/blockchaincmd/deploy.go index 9ec481220..91a7206af 100644 --- a/cmd/blockchaincmd/deploy.go +++ b/cmd/blockchaincmd/deploy.go @@ -12,9 +12,10 @@ import ( "strings" "time" - "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanche-cli/pkg/node" + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" "github.com/ethereum/go-ethereum/common" @@ -74,6 +75,7 @@ var ( subnetOnly bool icmSpec subnet.ICMSpec generateNodeID bool + useLocalMachine bool bootstrapValidatorsJSONFilePath string privateKeyFlags contract.PrivateKeyFlags bootstrapEndpoints []string @@ -132,6 +134,7 @@ so you can take your locally tested Subnet and deploy it on Fuji or Mainnet.`, cmd.Flags().StringVar(&bootstrapValidatorsJSONFilePath, "bootstrap-filepath", "", "JSON file path that provides details about bootstrap validators, leave Node-ID and BLS values empty if using --generate-node-id=true") cmd.Flags().BoolVar(&generateNodeID, "generate-node-id", false, "whether to create new node id for bootstrap validators (Node-ID and BLS values in bootstrap JSON file will be overridden if --bootstrap-filepath flag is used)") cmd.Flags().StringSliceVar(&bootstrapEndpoints, "bootstrap-endpoints", nil, "take validator node info from the given endpoints") + cmd.Flags().BoolVar(&useLocalMachine, "use-local-machine", false, "use local machine as a blockchain validator") return cmd } @@ -466,6 +469,19 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { return PrintSubnetInfo(blockchainName, true) } + if !useLocalMachine { + ux.Logger.PrintToUser("You can use your local machine as a bootstrap validator on the blockchain") + ux.Logger.PrintToUser("This means that you don't have to to set up a remote server on a cloud service (e.g. AWS / GCP) to be a validator on the blockchain.") + + useLocalMachine, err = app.Prompt.CaptureYesNo("Do you want to use your local machine as a bootstrap validator?") + if err != nil { + return err + } + if useLocalMachine { + bootstrapEndpoints = []string{"http://127.0.0.1:9650"} + } + } + if len(bootstrapEndpoints) > 0 { var changeAddr string for _, endpoint := range bootstrapEndpoints { @@ -727,71 +743,87 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { return err } - if !generateNodeID { - clusterName, err := node.GetClusterNameFromList(app) - if err != nil { - return err - } + clusterName, err := node.GetClusterNameFromList(app) + if err != nil { + return err + } + if !useLocalMachine { if err = node.SyncSubnet(app, clusterName, blockchainName, true, nil); err != nil { return err } - if err := node.WaitForHealthyCluster(app, clusterName, node.HealthCheckTimeout, node.HealthCheckPoolTime); err != nil { return err } - - chainSpec := contract.ChainSpec{ - BlockchainName: blockchainName, - } - _, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( - app, - network, - chainSpec, - ) - if err != nil { + } else { + if err := node.TrackSubnetWithLocalMachine(app, clusterName, blockchainName); err != nil { return err } - rpcURL, _, err := contract.GetBlockchainEndpoints( - app, - network, - chainSpec, - true, - false, + } + + chainSpec := contract.ChainSpec{ + BlockchainName: blockchainName, + } + genesisAddress, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( + app, + network, + chainSpec, + ) + if err != nil { + return err + } + privateKey, err := privateKeyFlags.GetPrivateKey(app, genesisPrivateKey) + if err != nil { + return err + } + if privateKey == "" { + privateKey, err = prompts.PromptPrivateKey( + app.Prompt, + "Which key to you want to use to pay for initializing Validator Manager contract? (Uses Blockchain gas token)", + app.GetKeyDir(), + app.GetKey, + genesisAddress, + genesisPrivateKey, ) if err != nil { return err } - aggregatorExtraPeerEndpoints, err := GetAggregatorExtraPeerEndpoints(network) - if err != nil { - return err - } - if err := validatormanager.SetupPoA( - app, - network, - rpcURL, - contract.ChainSpec{ - BlockchainName: blockchainName, - }, - genesisPrivateKey, - common.HexToAddress(sidecar.PoAValidatorManagerOwner), - avaGoBootstrapValidators, - aggregatorExtraPeerEndpoints, - ); err != nil { - return err - } - ux.Logger.GreenCheckmarkToUser("L1 is successfully converted to sovereign blockchain") - } else { - ux.Logger.GreenCheckmarkToUser("Generated Node ID and BLS info for bootstrap validator(s)") - ux.Logger.PrintToUser("To convert L1 to sovereign blockchain, create the corresponding Avalanche node(s) with the provided Node ID and BLS Info") - ux.Logger.PrintToUser("Created Node ID and BLS Info can be found at %s", app.GetSidecarPath(blockchainName)) - ux.Logger.PrintToUser("Once the Avalanche Node(s) are created and are tracking the blockchain, call `avalanche contract initPoaManager %s` to finish converting L1 to sovereign blockchain", blockchainName) } + rpcURL, _, err := contract.GetBlockchainEndpoints( + app, + network, + chainSpec, + true, + false, + ) + if err != nil { + return err + } + aggregatorExtraPeerEndpoints, err := GetAggregatorExtraPeerEndpoints(network) + if err != nil { + return err + } + if err := validatormanager.SetupPoA( + app, + network, + rpcURL, + contract.ChainSpec{ + BlockchainName: blockchainName, + }, + privateKey, + common.HexToAddress(sidecar.PoAValidatorManagerOwner), + avaGoBootstrapValidators, + aggregatorExtraPeerEndpoints, + ); err != nil { + return err + } + ux.Logger.GreenCheckmarkToUser("Subnet is successfully converted into Subnet Only Validator") } else { if err := app.UpdateSidecarNetworks(&sidecar, network, subnetID, blockchainID, "", "", nil); err != nil { return err } } + flags := make(map[string]string) flags[constants.MetricsNetwork] = network.Name() metrics.HandleTracking(cmd, constants.MetricsSubnetDeployCommand, app, flags) diff --git a/cmd/nodecmd/local.go b/cmd/nodecmd/local.go index 39803f5da..5c165961f 100644 --- a/cmd/nodecmd/local.go +++ b/cmd/nodecmd/local.go @@ -7,6 +7,8 @@ import ( "os" "path/filepath" + "github.com/ava-labs/avalanche-cli/pkg/node" + "github.com/ava-labs/avalanche-cli/pkg/binutils" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/constants" @@ -17,10 +19,7 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" "github.com/ava-labs/avalanche-network-runner/client" - anrutils "github.com/ava-labs/avalanche-network-runner/utils" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/set" "github.com/spf13/cobra" ) @@ -205,6 +204,14 @@ func localStartNode(_ *cobra.Command, args []string) error { } } serverLogPath := filepath.Join(rootDir, "server.log") + // make sure rootDir exists + if err := os.MkdirAll(rootDir, 0o700); err != nil { + return fmt.Errorf("could not create root directory %s: %w", rootDir, err) + } + // make sure pluginDir exists + if err := os.MkdirAll(pluginDir, 0o700); err != nil { + return fmt.Errorf("could not create plugin directory %s: %w", pluginDir, err) + } sd := subnet.NewLocalDeployer(app, avalancheGoVersion, avalanchegoBinaryPath, "") if err := sd.StartServer( constants.ServerRunFileLocalClusterPrefix, @@ -421,7 +428,7 @@ func localDestroyNode(_ *cobra.Command, args []string) error { return err } - if ok, err := checkClusterIsLocal(clusterName); err != nil || !ok { + if ok, err := node.CheckClusterIsLocal(app, clusterName); err != nil || !ok { return fmt.Errorf("local cluster %q not found", clusterName) } @@ -451,118 +458,13 @@ func addLocalClusterConfig(network models.Network) error { return app.WriteClustersConfigFile(&clustersConfig) } -func checkClusterIsLocal(clusterName string) (bool, error) { - clustersConfig, err := app.GetClustersConfig() - if err != nil { - return false, err - } - clusterConf, ok := clustersConfig.Clusters[clusterName] - return ok && clusterConf.Local, nil -} - func localTrack(_ *cobra.Command, args []string) error { clusterName := args[0] blockchainName := args[1] - if ok, err := checkClusterIsLocal(clusterName); err != nil || !ok { - return fmt.Errorf("local node %q is not found", clusterName) - } - sc, err := app.LoadSidecar(blockchainName) - if err != nil { - return err - } - clustersConfig, err := app.LoadClustersConfig() - if err != nil { - return err - } - clusterConfig := clustersConfig.Clusters[clusterName] - network := clusterConfig.Network - if sc.Networks[network.Name()].BlockchainID == ids.Empty { - return fmt.Errorf("blockchain %s has not been deployed to %s", blockchainName, network.Name()) - } - subnetID := sc.Networks[network.Name()].SubnetID - blockchainID := sc.Networks[network.Name()].BlockchainID - vmID, err := anrutils.VMID(blockchainName) - if err != nil { - return fmt.Errorf("failed to create VM ID from %s: %w", blockchainName, err) - } - var vmBin string - switch sc.VM { - case models.SubnetEvm: - _, vmBin, err = binutils.SetupSubnetEVM(app, sc.VMVersion) - if err != nil { - return fmt.Errorf("failed to install subnet-evm: %w", err) - } - case models.CustomVM: - vmBin = binutils.SetupCustomBin(app, blockchainName) - default: - return fmt.Errorf("unknown vm: %s", sc.VM) - } - rootDir := app.GetLocalDir(clusterName) - pluginPath := filepath.Join(rootDir, "node1", "plugins", vmID.String()) - if err := utils.FileCopy(vmBin, pluginPath); err != nil { - return err - } - if err := os.Chmod(pluginPath, constants.DefaultPerms755); err != nil { - return err - } - if app.ChainConfigExists(blockchainName) { - inputChainConfigPath := app.GetChainConfigPath(blockchainName) - outputChainConfigPath := filepath.Join(rootDir, "node1", "configs", "chains", blockchainID.String(), "config.json") - if err := os.MkdirAll(filepath.Dir(outputChainConfigPath), 0o700); err != nil { - return fmt.Errorf("could not create chain conf directory %s: %w", filepath.Dir(outputChainConfigPath), err) - } - if err := utils.FileCopy(inputChainConfigPath, outputChainConfigPath); err != nil { - return err - } - } - - cli, err := binutils.NewGRPCClientWithEndpoint( - binutils.LocalClusterGRPCServerEndpoint, - binutils.WithAvoidRPCVersionCheck(true), - binutils.WithDialTimeout(constants.FastGRPCDialTimeout), - ) - if err != nil { - return err - } - ctx, cancel := utils.GetANRContext() - defer cancel() - status, err := cli.Status(ctx) - if err != nil { - return err - } - publicEndpoints := []string{} - for _, nodeInfo := range status.ClusterInfo.NodeInfos { - if _, err := cli.RestartNode(ctx, nodeInfo.Name, client.WithWhitelistedSubnets(subnetID.String())); err != nil { - return err - } - publicEndpoints = append(publicEndpoints, nodeInfo.Uri) - } - networkInfo := sc.Networks[network.Name()] - rpcEndpoints := set.Of(networkInfo.RPCEndpoints...) - wsEndpoints := set.Of(networkInfo.WSEndpoints...) - for _, publicEndpoint := range publicEndpoints { - rpcEndpoints.Add(getRPCEndpoint(publicEndpoint, networkInfo.BlockchainID.String())) - wsEndpoints.Add(getWSEndpoint(publicEndpoint, networkInfo.BlockchainID.String())) - } - networkInfo.RPCEndpoints = rpcEndpoints.List() - networkInfo.WSEndpoints = wsEndpoints.List() - sc.Networks[clusterConfig.Network.Name()] = networkInfo - if err := app.UpdateSidecar(&sc); err != nil { - return err - } - ux.Logger.GreenCheckmarkToUser("%s successfully tracking %s", clusterName, blockchainName) - return nil + return node.TrackSubnetWithLocalMachine(app, clusterName, blockchainName) } func notImplementedForLocal(what string) error { ux.Logger.PrintToUser("Unsupported cmd: %s is not supported by local clusters", logging.LightBlue.Wrap(what)) return nil } - -func getRPCEndpoint(endpoint string, blockchainID string) string { - return models.NewDevnetNetwork(endpoint, 0).BlockchainEndpoint(blockchainID) -} - -func getWSEndpoint(endpoint string, blockchainID string) string { - return models.NewDevnetNetwork(endpoint, 0).BlockchainWSEndpoint(blockchainID) -} diff --git a/pkg/node/sync.go b/pkg/node/sync.go index d30f1c38f..eb3125f39 100644 --- a/pkg/node/sync.go +++ b/pkg/node/sync.go @@ -8,6 +8,12 @@ import ( "fmt" "sync" + "github.com/ava-labs/avalanche-cli/pkg/binutils" + "github.com/ava-labs/avalanche-cli/pkg/constants" + "github.com/ava-labs/avalanche-network-runner/client" + anrutils "github.com/ava-labs/avalanche-network-runner/utils" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/application" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -234,3 +240,92 @@ func parseBootstrappedOutput(byteValue []byte) (bool, error) { } return false, errors.New("unable to parse node bootstrap status") } + +func TrackSubnetWithLocalMachine(app *application.Avalanche, clusterName, blockchainName string) error { + if ok, err := CheckClusterIsLocal(app, clusterName); err != nil || !ok { + return fmt.Errorf("local node %q is not found", clusterName) + } + sc, err := app.LoadSidecar(blockchainName) + if err != nil { + return err + } + clustersConfig, err := app.LoadClustersConfig() + if err != nil { + return err + } + clusterConfig := clustersConfig.Clusters[clusterName] + network := clusterConfig.Network + if sc.Networks[network.Name()].BlockchainID == ids.Empty { + return fmt.Errorf("blockchain %s has not been deployed to %s", blockchainName, network.Name()) + } + subnetID := sc.Networks[network.Name()].SubnetID + chainVMID, err := anrutils.VMID(blockchainName) + if err != nil { + return fmt.Errorf("failed to create VM ID from %s: %w", blockchainName, err) + } + var vmBin string + switch sc.VM { + case models.SubnetEvm: + _, vmBin, err = binutils.SetupSubnetEVM(app, sc.VMVersion) + if err != nil { + return fmt.Errorf("failed to install subnet-evm: %w", err) + } + case models.CustomVM: + vmBin = binutils.SetupCustomBin(app, blockchainName) + default: + return fmt.Errorf("unknown vm: %s", sc.VM) + } + binaryDownloader := binutils.NewPluginBinaryDownloader(app) + if err := binaryDownloader.InstallVM(chainVMID.String(), vmBin); err != nil { + return err + } + cli, err := binutils.NewGRPCClientWithEndpoint( + binutils.LocalClusterGRPCServerEndpoint, + binutils.WithAvoidRPCVersionCheck(true), + binutils.WithDialTimeout(constants.FastGRPCDialTimeout), + ) + if err != nil { + return err + } + ctx, cancel := utils.GetANRContext() + defer cancel() + status, err := cli.Status(ctx) + if err != nil { + return err + } + publicEndpoints := []string{} + for _, nodeInfo := range status.ClusterInfo.NodeInfos { + if _, err := cli.RestartNode(ctx, nodeInfo.Name, client.WithWhitelistedSubnets(subnetID.String())); err != nil { + return err + } + publicEndpoints = append(publicEndpoints, nodeInfo.Uri) + } + _, err = cli.WaitForHealthy(ctx) + if err != nil { + return err + } + networkInfo := sc.Networks[network.Name()] + rpcEndpoints := set.Of(networkInfo.RPCEndpoints...) + wsEndpoints := set.Of(networkInfo.WSEndpoints...) + for _, publicEndpoint := range publicEndpoints { + rpcEndpoints.Add(getRPCEndpoint(publicEndpoint, networkInfo.BlockchainID.String())) + wsEndpoints.Add(getWSEndpoint(publicEndpoint, networkInfo.BlockchainID.String())) + } + networkInfo.RPCEndpoints = rpcEndpoints.List() + networkInfo.WSEndpoints = wsEndpoints.List() + sc.Networks[clusterConfig.Network.Name()] = networkInfo + if err := app.UpdateSidecar(&sc); err != nil { + return err + } + ux.Logger.GreenCheckmarkToUser("%s successfully tracking %s", clusterName, blockchainName) + return nil +} + +func CheckClusterIsLocal(app *application.Avalanche, clusterName string) (bool, error) { + clustersConfig, err := app.GetClustersConfig() + if err != nil { + return false, err + } + clusterConf, ok := clustersConfig.Clusters[clusterName] + return ok && clusterConf.Local, nil +} diff --git a/pkg/subnet/helpers.go b/pkg/subnet/helpers.go index 332602b68..4011a0537 100644 --- a/pkg/subnet/helpers.go +++ b/pkg/subnet/helpers.go @@ -9,7 +9,6 @@ import ( "os" "path/filepath" "unicode" - "github.com/ava-labs/avalanche-cli/pkg/application" "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/key"