mirror of
https://github.com/luxfi/cli.git
synced 2026-10-30 17:01:20 +00:00
276 lines
7.1 KiB
Go
276 lines
7.1 KiB
Go
// Copyright (C) 2022, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
package subnetcmd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/luxfi/cli/v2/pkg/constants"
|
|
"github.com/luxfi/cli/v2/pkg/models"
|
|
"github.com/luxfi/cli/v2/pkg/ux"
|
|
"github.com/luxfi/cli/v2/pkg/vm"
|
|
"github.com/luxfi/node/v2/v2/api/info"
|
|
"github.com/luxfi/ids"
|
|
"github.com/luxfi/node/v2/v2/utils/rpc"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm"
|
|
"github.com/luxfi/node/v2/v2/vms/platformvm/txs"
|
|
"github.com/luxfi/evm/v2/core"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var (
|
|
genesisFilePath string
|
|
blockchainIDstr string
|
|
nodeURL string
|
|
)
|
|
|
|
// lux subnet import
|
|
func newImportFromNetworkCmd() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "public [subnetPath]",
|
|
Short: "Import an existing subnet config from running subnets on a public network",
|
|
RunE: importRunningSubnet,
|
|
SilenceUsage: true,
|
|
Args: cobra.MaximumNArgs(1),
|
|
Long: `The subnet import public command imports a Subnet configuration from a running network.
|
|
|
|
The genesis file should be available from the disk for this to work. By default, an imported Subnet
|
|
doesn't overwrite an existing Subnet with the same name. To allow overwrites, provide the --force
|
|
flag.`,
|
|
}
|
|
|
|
cmd.Flags().StringVar(&nodeURL, "node-url", "", "[optional] URL of an already running subnet validator")
|
|
|
|
cmd.Flags().BoolVar(&deployTestnet, "testnet", false, "import from `testnet` (alias for `testnet`)")
|
|
cmd.Flags().BoolVar(&deployMainnet, "mainnet", false, "import from `mainnet`")
|
|
cmd.Flags().BoolVar(&useSubnetEvm, "evm", false, "import a evm")
|
|
cmd.Flags().BoolVar(&useCustom, "custom", false, "use a custom VM template")
|
|
cmd.Flags().BoolVarP(
|
|
&overwriteImport,
|
|
"force",
|
|
"f",
|
|
false,
|
|
"overwrite the existing configuration if one exists",
|
|
)
|
|
cmd.Flags().StringVar(
|
|
&genesisFilePath,
|
|
"genesis-file-path",
|
|
"",
|
|
"path to the genesis file",
|
|
)
|
|
cmd.Flags().StringVar(
|
|
&blockchainIDstr,
|
|
"blockchain-id",
|
|
"",
|
|
"the blockchain ID",
|
|
)
|
|
return cmd
|
|
}
|
|
|
|
func importRunningSubnet(*cobra.Command, []string) error {
|
|
var err error
|
|
|
|
var network models.Network
|
|
switch {
|
|
case deployTestnet:
|
|
network = models.Testnet
|
|
case deployMainnet:
|
|
network = models.Mainnet
|
|
}
|
|
|
|
if network == models.Undefined {
|
|
networkStr, err := app.Prompt.CaptureList(
|
|
"Choose a network to import from",
|
|
[]string{models.Testnet.String(), models.Mainnet.String()},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
network = models.NetworkFromString(networkStr)
|
|
}
|
|
|
|
if genesisFilePath == "" {
|
|
genesisFilePath, err = app.Prompt.CaptureExistingFilepath("Provide the path to the genesis file")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var reply *info.GetNodeVersionReply
|
|
|
|
if nodeURL == "" {
|
|
yes, err := app.Prompt.CaptureYesNo("Have nodes already been deployed to this subnet?")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if yes {
|
|
nodeURL, err = app.Prompt.CaptureString(
|
|
"Please provide an API URL of such a node so we can query its VM version (e.g. http://111.22.33.44:5555)")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout)
|
|
defer cancel()
|
|
infoAPI := info.NewClient(nodeURL)
|
|
options := []rpc.Option{}
|
|
reply, err = infoAPI.GetNodeVersion(ctx, options...)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to query node - is it running and reachable? %w", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
var blockchainID ids.ID
|
|
if blockchainIDstr == "" {
|
|
blockchainID, err = app.Prompt.CaptureID("What is the ID of the blockchain?")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
blockchainID, err = ids.FromString(blockchainIDstr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var pubAPI string
|
|
switch network {
|
|
case models.Testnet:
|
|
pubAPI = constants.TestnetAPIEndpoint
|
|
case models.Mainnet:
|
|
pubAPI = constants.MainnetAPIEndpoint
|
|
}
|
|
client := platformvm.NewClient(pubAPI)
|
|
ctx, cancel := context.WithTimeout(context.Background(), constants.RequestTimeout)
|
|
defer cancel()
|
|
options := []rpc.Option{}
|
|
|
|
ux.Logger.PrintToUser("Getting information from the %s network...", network.String())
|
|
|
|
txBytes, err := client.GetTx(ctx, blockchainID, options...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var (
|
|
vmID, subnetID ids.ID
|
|
tx txs.Tx
|
|
subnetName string
|
|
)
|
|
|
|
_, err = txs.Codec.Unmarshal(txBytes, &tx)
|
|
if err != nil {
|
|
return fmt.Errorf("failed unmarshaling the createChainTx: %w", err)
|
|
}
|
|
|
|
createChainTx, ok := tx.Unsigned.(*txs.CreateChainTx)
|
|
if !ok {
|
|
return fmt.Errorf("expected a CreateChainTx, got %T", createChainTx)
|
|
}
|
|
|
|
vmID = createChainTx.VMID
|
|
subnetID = createChainTx.SubnetID
|
|
subnetName = createChainTx.ChainName
|
|
|
|
ux.Logger.PrintToUser("Retrieved information. BlockchainID: %s, SubnetID: %s, Name: %s, VMID: %s",
|
|
blockchainID.String(),
|
|
subnetID.String(),
|
|
subnetName,
|
|
vmID.String(),
|
|
)
|
|
// TODO: it's probably possible to deploy VMs with the same name on a public network
|
|
// In this case, an import could clash because the tool supports unique names only
|
|
|
|
genBytes, err := os.ReadFile(genesisFilePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = app.WriteGenesisFile(subnetName, genBytes); err != nil {
|
|
return err
|
|
}
|
|
|
|
vmType := getVMFromFlag()
|
|
if vmType == "" {
|
|
subnetTypeStr, err := app.Prompt.CaptureList(
|
|
"What's this VM's type?",
|
|
[]string{models.EVM, models.CustomVM},
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
vmType = models.VMTypeFromString(subnetTypeStr)
|
|
}
|
|
|
|
vmIDstr := vmID.String()
|
|
|
|
sc := &models.Sidecar{
|
|
Name: subnetName,
|
|
VM: vmType,
|
|
Networks: map[string]models.NetworkData{
|
|
network.String(): {
|
|
SubnetID: subnetID,
|
|
BlockchainID: blockchainID,
|
|
},
|
|
},
|
|
Subnet: subnetName,
|
|
Version: constants.SidecarVersion,
|
|
TokenName: constants.DefaultTokenName,
|
|
ImportedVMID: vmIDstr,
|
|
// signals that the VMID wasn't derived from the subnet name but through import
|
|
ImportedFromLPM: true,
|
|
}
|
|
|
|
var versions []string
|
|
|
|
if reply != nil {
|
|
// a node was queried
|
|
for _, v := range reply.VMVersions {
|
|
if v == vmIDstr {
|
|
sc.VMVersion = v
|
|
break
|
|
}
|
|
}
|
|
sc.RPCVersion = int(reply.RPCProtocolVersion)
|
|
} else {
|
|
// no node was queried, ask the user
|
|
switch vmType {
|
|
case models.EVM:
|
|
versions, err = app.Downloader.GetAllReleasesForRepo(constants.LuxOrg, constants.EVMRepoName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sc.VMVersion, err = app.Prompt.CaptureList("Pick the version for this VM", versions)
|
|
case models.CustomVM:
|
|
return fmt.Errorf("importing custom VMs is not yet implemented, but will be available soon")
|
|
default:
|
|
return fmt.Errorf("unexpected VM type: %v", vmType)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sc.RPCVersion, err = vm.GetRPCProtocolVersion(app, vmType, sc.VMVersion)
|
|
if err != nil {
|
|
return fmt.Errorf("failed getting RPCVersion for VM type %s with version %s", vmType, sc.VMVersion)
|
|
}
|
|
}
|
|
if vmType == models.EVM {
|
|
var genesis core.Genesis
|
|
if err := json.Unmarshal(genBytes, &genesis); err != nil {
|
|
return err
|
|
}
|
|
sc.ChainID = genesis.Config.ChainID.String()
|
|
}
|
|
|
|
if err := app.CreateSidecar(sc); err != nil {
|
|
return fmt.Errorf("failed creating the sidecar for import: %w", err)
|
|
}
|
|
|
|
ux.Logger.PrintToUser("Subnet %s imported successfully", sc.Name)
|
|
|
|
return nil
|
|
}
|