Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/blockchaincmd/add_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ func addValidator(cmd *cobra.Command, args []string) error {
}

if len(args) == 0 {
sc, err = importL1(blockchainIDStr, addValidatorFlags.RPC, network)
sc, _, err = importBlockchain(network, addValidatorFlags.RPC, true, ids.Empty, ux.Logger.PrintToUser)
if err != nil {
return err
}
Expand Down
6 changes: 3 additions & 3 deletions cmd/blockchaincmd/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ func TestExportImportSubnet(t *testing.T) {
err = os.Remove(sidecarFile)
require.NoError(err)

err = importBlockchain(nil, []string{"this-does-also-not-exist-import-should-fail"})
err = importFile(nil, []string{"this-does-also-not-exist-import-should-fail"})
require.ErrorIs(err, os.ErrNotExist)
err = importBlockchain(nil, []string{exportOutput})
err = importFile(nil, []string{exportOutput})
require.ErrorContains(err, "blockchain already exists")
genFile := filepath.Join(app.GetBaseDir(), constants.SubnetDir, testSubnet, constants.GenesisFileName)
err = os.Remove(genFile)
require.NoError(err)
err = importBlockchain(nil, []string{exportOutput})
err = importFile(nil, []string{exportOutput})
require.NoError(err)
}
4 changes: 2 additions & 2 deletions cmd/blockchaincmd/import_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func newImportFileCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "file [blockchainPath]",
Short: "Import an existing blockchain config",
RunE: importBlockchain,
RunE: importFile,
Args: cobrautils.MaximumNArgs(1),
Long: `The blockchain import command will import a blockchain configuration from a file or a git repository.

Expand Down Expand Up @@ -70,7 +70,7 @@ flag.`,
return cmd
}

func importBlockchain(_ *cobra.Command, args []string) error {
func importFile(_ *cobra.Command, args []string) error {
if len(args) == 1 {
importPath := args[0]
return importFromFile(importPath)
Expand Down
246 changes: 121 additions & 125 deletions cmd/blockchaincmd/import_public.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ import (
var (
blockchainIDStr string
subnetIDstr string
nodeURL string
useSubnetEvm bool
useCustomVM bool
rpcURL string
noRPCAvailable bool
)

// avalanche blockchain import public
Expand All @@ -48,12 +49,12 @@ func newImportPublicCmd() *cobra.Command {

By default, an imported Blockchain
doesn't overwrite an existing Blockchain with the same name. To allow overwrites, provide the --force
flag.`,
plag.`,
}

networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, networkoptions.DefaultSupportedNetworkOptions)

cmd.Flags().StringVar(&nodeURL, "node-url", "", "[optional] URL of an already running validator")
cmd.Flags().StringVar(&nodeEndpoint, "node-endpoint", "", "[optional] URL of an already running validator")

cmd.Flags().BoolVar(&useSubnetEvm, "evm", false, "import a subnet-evm")
cmd.Flags().BoolVar(&useCustomVM, "custom", false, "use a custom VM template")
Expand All @@ -69,6 +70,8 @@ flag.`,
"",
"the blockchain ID",
)
cmd.Flags().StringVar(&rpcURL, "rpc", "", "rpc endpoint for the blockchain")
cmd.Flags().BoolVar(&noRPCAvailable, "no-rpc-available", false, "use this when an RPC if offline and can't be accesed")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems that noRPCAvailable flag needs to be set to true so that importBlockchain can:

if rpcIsAvailable {
			sc.ValidatorManagement, err = validatorManagerSDK.GetValidatorManagerType(rpcURL, common.HexToAddress(validatorManagerAddress))
			if err != nil {
				return models.Sidecar{}, nil, fmt.Errorf("could not obtain validator manager type: %w", err)
			}
			printFunc("  Validation Kind: %s", sc.ValidatorManagement)
			if sc.ValidatorManagement == validatormanagertypes.ProofOfAuthority {
				owner, err := contract.GetContractOwner(rpcURL, common.HexToAddress(validatorManagerAddress))
				if err != nil {
					return models.Sidecar{}, nil, err
				}
				sc.ValidatorManagerOwner = owner.String()
				printFunc("  Validator Manager Owner: %s", sc.ValidatorManagerOwner)
			}
		}

and

if rpcIsAvailable {
			blockchainID, _ = precompiles.WarpPrecompileGetBlockchainID(rpcURL)
		}
		if blockchainID == ids.Empty {
			blockchainID, err = app.Prompt.CaptureID("What is the Blockchain ID?")
			if err != nil {
				return models.Sidecar{}, nil, err
			}
		}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why don't we just remove this flag and:
return error if rpc is unavailable when:

sc.ValidatorManagement, err = validatorManagerSDK.GetValidatorManagerType(rpcURL, common.HexToAddress(validatorManagerAddress)

since we need to know if poa/ pos anyways

and if

blockchainID, _ = precompiles.WarpPrecompileGetBlockchainID(rpcURL)
		}

returns error due to url being unavailable we can just prompt for the blockchainID

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TLDR: I think we should remove this flag

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are cases where a rpc in unavailable because all rpc nodes are down but you anyway want to be
able to partially import the L1, most notably to increase validator balances, or to try out some local tests, or for any
misc debugging reason like be able to easily have access to certain L1 info.
I indeed used this functionality twice in the past month

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eg if all your validator drawn the balance, you need to use increase balance, and a reasonable flow would be to
call first import on the rpc, and then increase the balances

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

instead of using flag why not we just make a call to this RPC, and if this RPC is not returning any response then we know that it is not available (don't return the error) and respond accordingly as if we have the flag set as true

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be the case that there is a temporal network failure and the import from a failed rpc will be assumed to be successful, being not the case, also, the idea of this flag is to even avoid asking for the url instead of requiring the user to provide
a fake url.
not opposing to this but the flag, being kind of a bit more ugly, seems a better option to me.
as you wish

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or we can just accept any URL string at prompt, and tell the user which to use for empty rpc, eg just enter

return cmd
}

Expand All @@ -86,133 +89,89 @@ func importPublic(*cobra.Command, []string) error {
return err
}

var reply *info.GetNodeVersionReply
var blockchainID ids.ID
if blockchainIDStr != "" {
blockchainID, err = ids.FromString(blockchainIDStr)
if err != nil {
return err
}
}

if nodeURL == "" {
sc, genBytes, err := importBlockchain(network, rpcURL, !noRPCAvailable, blockchainID, ux.Logger.PrintToUser)
if err != nil {
return err
}

sc.TokenName = constants.DefaultTokenName
sc.TokenSymbol = constants.DefaultTokenSymbol

sc.VM, err = vm.PromptVMType(app, useSubnetEvm, useCustomVM)
if err != nil {
return err
}

var nodeVersionReply *info.GetNodeVersionReply
if nodeEndpoint == "" {
yes, err := app.Prompt.CaptureNoYes("Have validator nodes already been deployed to this blockchain?")
if err != nil {
return err
}
if yes {
nodeURL, err = app.Prompt.CaptureString(
nodeEndpoint, 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 := utils.GetAPIContext()
defer cancel()
infoAPI := info.NewClient(nodeURL)
infoAPI := info.NewClient(nodeEndpoint)
options := []rpc.Option{}
reply, err = infoAPI.GetNodeVersion(ctx, options...)
nodeVersionReply, 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
}
}

ux.Logger.PrintToUser("Getting information from the %s network...", network.Name())

createChainTx, err := utils.GetBlockchainTx(network.Endpoint, blockchainID)
if err != nil {
return err
}

vmID := createChainTx.VMID
subnetID := createChainTx.SubnetID
blockchainName := createChainTx.ChainName
genBytes := createChainTx.GenesisData

ux.Logger.PrintToUser("Retrieved information. BlockchainID: %s, SubnetID: %s, Name: %s, VMID: %s",
blockchainID.String(),
subnetID.String(),
blockchainName,
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

vmType, err := vm.PromptVMType(app, useSubnetEvm, useCustomVM)
if err != nil {
return err
}

vmIDstr := vmID.String()

sc := &models.Sidecar{
Name: blockchainName,
VM: vmType,
Networks: map[string]models.NetworkData{
network.Name(): {
SubnetID: subnetID,
BlockchainID: blockchainID,
},
},
Subnet: blockchainName,
Version: constants.SidecarVersion,
TokenName: constants.DefaultTokenName,
TokenSymbol: constants.DefaultTokenSymbol,
ImportedVMID: vmIDstr,
}

var versions []string

if reply != nil {
if nodeVersionReply != nil {
// a node was queried
for _, v := range reply.VMVersions {
if v == vmIDstr {
for _, v := range nodeVersionReply.VMVersions {
if v == sc.ImportedVMID {
sc.VMVersion = v
break
}
}
sc.RPCVersion = int(reply.RPCProtocolVersion)
} else {
sc.RPCVersion = int(nodeVersionReply.RPCProtocolVersion)
} else if sc.VM == models.SubnetEvm {
// no node was queried, ask the user
switch vmType {
case models.SubnetEvm:
versions, err = app.Downloader.GetAllReleasesForRepo(constants.AvaLabsOrg, constants.SubnetEVMRepoName, "", application.All)
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)
versions, err = app.Downloader.GetAllReleasesForRepo(constants.AvaLabsOrg, constants.SubnetEVMRepoName, "", application.All)
if err != nil {
return err
}
sc.VMVersion, err = app.Prompt.CaptureList("Pick the version for this VM", versions)
if err != nil {
return err
}
sc.RPCVersion, err = vm.GetRPCProtocolVersion(app, vmType, sc.VMVersion)
sc.RPCVersion, err = vm.GetRPCProtocolVersion(app, sc.VM, sc.VMVersion)
if err != nil {
return fmt.Errorf("failed getting RPCVersion for VM type %s with version %s", vmType, sc.VMVersion)
return fmt.Errorf("failed getting RPCVersion for VM type %s with version %s", sc.VM, sc.VMVersion)
}
}
if vmType == models.SubnetEvm {

if sc.VM == models.SubnetEvm {
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 {
if err := app.CreateSidecar(&sc); err != nil {
return fmt.Errorf("failed creating the sidecar for import: %w", err)
}

if err = app.WriteGenesisFile(blockchainName, genBytes); err != nil {
if err = app.WriteGenesisFile(sc.Name, genBytes); err != nil {
return err
}

Expand All @@ -221,62 +180,99 @@ func importPublic(*cobra.Command, []string) error {
return nil
}

func importL1(blockchainIDStr string, rpcURL string, network models.Network) (models.Sidecar, error) {
var sc models.Sidecar
func importBlockchain(
network models.Network,
rpcURL string,
rpcIsAvailable bool,
blockchainID ids.ID,
printFunc func(msg string, args ...interface{}),
) (models.Sidecar, []byte, error) {
var err error

if rpcIsAvailable && rpcURL == "" {
rpcURL, err = app.Prompt.CaptureURL("What is the RPC endpoint?", false)
if err != nil {
return models.Sidecar{}, nil, err
}
}

blockchainID, err := precompiles.WarpPrecompileGetBlockchainID(rpcURL)
if err != nil {
if blockchainIDStr == "" {
if blockchainID == ids.Empty {
var err error
if rpcIsAvailable {
blockchainID, _ = precompiles.WarpPrecompileGetBlockchainID(rpcURL)
}
if blockchainID == ids.Empty {
blockchainID, err = app.Prompt.CaptureID("What is the Blockchain ID?")
if err != nil {
return models.Sidecar{}, err
}
} else {
blockchainID, err = ids.FromString(blockchainIDStr)
if err != nil {
return models.Sidecar{}, err
return models.Sidecar{}, nil, err
}
}
}
subnetID, err := blockchain.GetSubnetIDFromBlockchainID(blockchainID, network)

createChainTx, err := utils.GetBlockchainTx(network.Endpoint, blockchainID)
if err != nil {
return models.Sidecar{}, err
return models.Sidecar{}, nil, err
}

subnetID := createChainTx.SubnetID
vmID := createChainTx.VMID
blockchainName := createChainTx.ChainName
genBytes := createChainTx.GenesisData

printFunc("Retrieved information:")
printFunc(" Name: %s", blockchainName)
printFunc(" BlockchainID: %s", blockchainID.String())
printFunc(" SubnetID: %s", subnetID.String())
printFunc(" VMID: %s", vmID.String())

subnetInfo, err := blockchain.GetSubnet(subnetID, network)
if err != nil {
return models.Sidecar{}, err
}
if subnetInfo.IsPermissioned {
return models.Sidecar{}, fmt.Errorf("unable to import non sovereign Subnets")
return models.Sidecar{}, nil, err
}
validatorManagerAddress = "0x" + hex.EncodeToString(subnetInfo.ManagerAddress)

// add validator without blockchain arg is only for l1s
sc = models.Sidecar{
Sovereign: true,
sc := models.Sidecar{
Name: blockchainName,
Networks: map[string]models.NetworkData{
network.Name(): {
SubnetID: subnetID,
BlockchainID: blockchainID,
},
},
Subnet: blockchainName,
Version: constants.SidecarVersion,
ImportedVMID: vmID.String(),
ImportedFromAPM: true,
}

sc.ValidatorManagement, err = validatorManagerSDK.GetValidatorManagerType(rpcURL, common.HexToAddress(validatorManagerAddress))
if err != nil {
return models.Sidecar{}, fmt.Errorf("could not obtain validator manager type: %w", err)
if rpcIsAvailable {
e := sc.Networks[network.Name()]
e.RPCEndpoints = []string{rpcURL}
sc.Networks[network.Name()] = e
}

if sc.ValidatorManagement == validatormanagertypes.ProofOfAuthority {
owner, err := contract.GetContractOwner(rpcURL, common.HexToAddress(validatorManagerAddress))
if err != nil {
return models.Sidecar{}, err
if !subnetInfo.IsPermissioned {
sc.Sovereign = true
validatorManagerAddress = "0x" + hex.EncodeToString(subnetInfo.ManagerAddress)
e := sc.Networks[network.Name()]
e.ValidatorManagerAddress = validatorManagerAddress
sc.Networks[network.Name()] = e
printFunc(" Validator Manager Address: %s", validatorManagerAddress)
if rpcIsAvailable {
sc.ValidatorManagement, err = validatorManagerSDK.GetValidatorManagerType(rpcURL, common.HexToAddress(validatorManagerAddress))
if err != nil {
return models.Sidecar{}, nil, fmt.Errorf("could not obtain validator manager type: %w", err)
}
printFunc(" Validation Kind: %s", sc.ValidatorManagement)
if sc.ValidatorManagement == validatormanagertypes.ProofOfAuthority {
owner, err := contract.GetContractOwner(rpcURL, common.HexToAddress(validatorManagerAddress))
if err != nil {
return models.Sidecar{}, nil, err
}
sc.ValidatorManagerOwner = owner.String()
printFunc(" Validator Manager Owner: %s", sc.ValidatorManagerOwner)
}
}
sc.ValidatorManagerOwner = owner.String()
}

sc.Networks = make(map[string]models.NetworkData)

sc.Networks[network.Name()] = models.NetworkData{
SubnetID: subnetID,
BlockchainID: blockchainID,
ValidatorManagerAddress: validatorManagerAddress,
RPCEndpoints: []string{rpcURL},
}
return sc, err
return sc, genBytes, err
}
Loading
Loading