diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..c036ece05 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,3 @@ +# https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax + +* @ava-labs/tooling diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..9b34eaf77 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,29 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '[Bug]: ' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior. + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Logs** +If applicable, please include the relevant logs that indicate a problem. + +**Operating System** +Which OS you used to reveal the bug. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_spec.md b/.github/ISSUE_TEMPLATE/feature_spec.md new file mode 100644 index 000000000..ec68012fb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_spec.md @@ -0,0 +1,18 @@ +--- +name: Feature specification +about: Discussion on design and implementation of new features for the Avalanche CLI. +title: '' +labels: enhancement +assignees: '' + +--- + +**Context and scope** +Include a short description of the context and scope of the suggested feature. +Include goals the change will accomplish if relevant. + +**Discussion and alternatives** +Include a description of the changes to be made to the code along with alternatives that were considered. + +**Open questions** +Questions that are still being discussed. diff --git a/.github/packer/aws-ubuntu-docker.pkr.hcl b/.github/packer/aws-ubuntu-docker.pkr.hcl index c36a1b243..911547560 100644 --- a/.github/packer/aws-ubuntu-docker.pkr.hcl +++ b/.github/packer/aws-ubuntu-docker.pkr.hcl @@ -36,6 +36,12 @@ source "amazon-ebs" "ubuntu_amd64" { ami_description = "Avalanche-CLI Ubuntu 20.04 Docker" instance_type = "t3.xlarge" region = "us-east-1" + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 1 + } + imds_support = "v2.0" source_ami_filter { filters = { name = "ubuntu/images/*ubuntu-focal-20.04-amd64-server-*" @@ -64,6 +70,12 @@ source "amazon-ebs" "ubuntu_arm64" { ami_description = "Avalanche-CLI Ubuntu 20.04 Docker" instance_type = "t4g.xlarge" # Adjust the instance type for arm64 region = "us-east-1" + metadata_options { + http_endpoint = "enabled" + http_tokens = "required" + http_put_response_hop_limit = 1 + } + imds_support = "v2.0" source_ami_filter { filters = { name = "ubuntu/images/*ubuntu-focal-20.04-arm64-server-*" # Filter for arm64 AMIs @@ -99,7 +111,7 @@ build { inline = [ "export DEBIAN_FRONTEND=noninteractive", "sudo add-apt-repository -y ppa:longsleep/golang-backports", - "sudo apt-get -y update && sudo apt-get -y dist-upgrade && sudo apt-get -y install ca-certificates curl gcc git golang-go=2:1.22~3longsleep1", + "sudo apt-get -y update && sudo apt-get -y dist-upgrade && sudo apt-get -y install ca-certificates curl gcc git golang-go", "sudo install -m 0755 -d /etc/apt/keyrings && sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc && sudo chmod a+r /etc/apt/keyrings/docker.asc", "echo \"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null", "sudo apt-get -y update && sudo apt-get -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose", @@ -118,7 +130,8 @@ build { "docker pull prom/node-exporter:v1.7.0", "docker pull grafana/grafana:10.4.1", "docker pull prom/prometheus:v2.51.2", - "docker pull avaplatform/awm-relayer" + "docker pull avaplatform/awm-relayer", + "docker pull golang:1.22.1-bullseye" ] } diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..119cac2eb --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,7 @@ +## Why this should be merged + +## How this works + +## How this was tested + +## How is this documented diff --git a/.github/workflows/lint-and-tests.yml b/.github/workflows/lint-and-tests.yml index b634c2f3c..6e926262b 100644 --- a/.github/workflows/lint-and-tests.yml +++ b/.github/workflows/lint-and-tests.yml @@ -42,7 +42,7 @@ jobs: go-version: ${{ matrix.go }} - run: go mod download - run: scripts/build.sh - - run: go test -v -coverprofile=coverage.out $(go list ./... | grep -v /tests/) + - run: go test -v -coverprofile=coverage.out $(go list ./... | grep -v /tests/ | grep -v '/sdk/') env: CGO_CFLAGS: "-O -D__BLST_PORTABLE__" # Set the CGO flags to use the portable version of BLST - run: go tool cover -func=coverage.out @@ -114,12 +114,13 @@ jobs: run: | npm install -g typescript npm install -g ts-node + npm install -g tsx - name: Install docker on macos - if: ${{ matrix.os == 'macos-13' }} + if: ${{ (matrix.os == 'macos-13') && (matrix.suite == '\\[Public Subnet\\]') }} run: | brew install docker brew install colima - colima start + colima start --vm-type vz sudo ln -s ~/.colima/default/docker.sock /var/run/docker.sock - name: generate ssh token for e2e tests run: | diff --git a/.github/workflows/release-public-ami.yml b/.github/workflows/release-public-ami.yml index ae6c8eabc..ae6089b70 100644 --- a/.github/workflows/release-public-ami.yml +++ b/.github/workflows/release-public-ami.yml @@ -7,8 +7,12 @@ on: tags: - "*" +permissions: + id-token: write # This is required for requesting the JWT + contents: read # This is required for actions/checkout + env: - PACKER_VERSION: "1.10.2" + PACKER_VERSION: "1.11.2" jobs: build-public-ami-and-upload: @@ -32,21 +36,19 @@ jobs: - name: install npx aws-amicleaner run: npm install -g aws-amicleaner - - name: clean up old AMIs - run: npx aws-amicleaner --include-name 'public-avalanchecli-ubuntu-*' --exclude-newest 1 --exclude-days 2 --region="*" --force - env: - AWS_ACCESS_KEY_ID: ${{ secrets.EXPERIMENTAL_AWS_ACCESS_KEY_PACKER }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.EXPERIMENTAL_AWS_SECRET_KEY_PACKER }} - AWS_REGION: us-east-1 + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: - aws-access-key-id: ${{ secrets.EXPERIMENTAL_AWS_ACCESS_KEY_PACKER }} - aws-secret-access-key: ${{ secrets.EXPERIMENTAL_AWS_SECRET_KEY_PACKER }} - #aws-session-token: ${{ secrets.EXPERIMENTAL_AWS_SESSION_TOKEN_PACKER }} + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} aws-region: us-east-1 + - name: clean up old AMIs + run: npx aws-amicleaner --include-name 'public-avalanchecli-ubuntu-*' --exclude-newest 1 --exclude-days 2 --region="*" --force + env: + AWS_REGION: us-east-1 + - name: Configure GCP credentials uses: google-github-actions/auth@v2 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4069c816d..dc427e01c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -38,7 +38,7 @@ jobs: go-version: ${{ matrix.go }} - run: go mod download - run: scripts/build.sh - - run: go test -v -coverprofile=coverage.out ./... + - run: go test -v -coverprofile=coverage.out $(go list ./... | grep -v /tests/ | grep -v '/sdk/') env: CGO_CFLAGS: "-O -D__BLST_PORTABLE__" # Set the CGO flags to use the portable version of BLST - run: go tool cover -func=coverage.out diff --git a/VERSION b/VERSION index f5d2a5858..5849151fd 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.3 \ No newline at end of file +1.7.5 \ No newline at end of file diff --git a/assets/bootstrapSnapshot.PreDurango11.tar.gz b/assets/bootstrapSnapshot.PreDurango11.tar.gz new file mode 100644 index 000000000..e61cd8447 Binary files /dev/null and b/assets/bootstrapSnapshot.PreDurango11.tar.gz differ diff --git a/assets/bootstrapSnapshot.tar.gz b/assets/bootstrapSnapshot.tar.gz index e61cd8447..96828d384 100644 Binary files a/assets/bootstrapSnapshot.tar.gz and b/assets/bootstrapSnapshot.tar.gz differ diff --git a/assets/bootstrapSnapshotSingleNode.PreDurango11.tar.gz b/assets/bootstrapSnapshotSingleNode.PreDurango11.tar.gz new file mode 100644 index 000000000..22434b061 Binary files /dev/null and b/assets/bootstrapSnapshotSingleNode.PreDurango11.tar.gz differ diff --git a/assets/bootstrapSnapshotSingleNode.tar.gz b/assets/bootstrapSnapshotSingleNode.tar.gz index 22434b061..7f1bf263d 100644 Binary files a/assets/bootstrapSnapshotSingleNode.tar.gz and b/assets/bootstrapSnapshotSingleNode.tar.gz differ diff --git a/assets/sha256sum.PreDurango11.txt b/assets/sha256sum.PreDurango11.txt new file mode 100644 index 000000000..c02e44c45 --- /dev/null +++ b/assets/sha256sum.PreDurango11.txt @@ -0,0 +1 @@ +a8827a2c4e32cfd8a05287acae454e86f1cfab90c6f79a3411bed08f98fcea06 assets/bootstrapSnapshot.PreDurango11.tar.gz diff --git a/assets/sha256sum.txt b/assets/sha256sum.txt index a9c86b7dc..803a08b41 100644 --- a/assets/sha256sum.txt +++ b/assets/sha256sum.txt @@ -1 +1 @@ -a8827a2c4e32cfd8a05287acae454e86f1cfab90c6f79a3411bed08f98fcea06 assets/bootstrapSnapshot.tar.gz +811e73be1ad9b56f05a14ca6d239ee50bc3882fa10fdb2f78d41b02fb81ac6fe assets/bootstrapSnapshot.tar.gz diff --git a/assets/sha256sumSingleNode.PreDurango11.txt b/assets/sha256sumSingleNode.PreDurango11.txt new file mode 100644 index 000000000..a857e0608 --- /dev/null +++ b/assets/sha256sumSingleNode.PreDurango11.txt @@ -0,0 +1 @@ +1cb0fc4f458895623eb19e12ae580be5e4f23ef69ba557a71415bac01608983b assets/bootstrapSnapshotSingleNode.PreDurango11.tar.gz diff --git a/assets/sha256sumSingleNode.txt b/assets/sha256sumSingleNode.txt index f22b06dc3..6f15edbed 100644 --- a/assets/sha256sumSingleNode.txt +++ b/assets/sha256sumSingleNode.txt @@ -1 +1 @@ -1cb0fc4f458895623eb19e12ae580be5e4f23ef69ba557a71415bac01608983b assets/bootstrapSnapshotSingleNode.tar.gz +53ef0b4c6ef6bd54e07bd065d58164bd464cd46576d36230731c93d3cc254df9 assets/bootstrapSnapshotSingleNode.tar.gz diff --git a/cmd/backendcmd/spawnServer.go b/cmd/backendcmd/spawn_server.go similarity index 100% rename from cmd/backendcmd/spawnServer.go rename to cmd/backendcmd/spawn_server.go diff --git a/cmd/subnetcmd/addValidator.go b/cmd/blockchaincmd/add_validator.go similarity index 92% rename from cmd/subnetcmd/addValidator.go rename to cmd/blockchaincmd/add_validator.go index 502224778..cca7eb589 100644 --- a/cmd/subnetcmd/addValidator.go +++ b/cmd/blockchaincmd/add_validator.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "errors" @@ -24,7 +24,12 @@ import ( ) var ( - addValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Devnet, networkoptions.Fuji, networkoptions.Mainnet} + addValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + networkoptions.Mainnet, + } nodeIDStr string weight uint64 @@ -43,20 +48,20 @@ var ( ErrNotPermissionedSubnet = errors.New("subnet is not permissioned") ) -// avalanche subnet addValidator +// avalanche blockchain addValidator func newAddValidatorCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "addValidator [subnetName]", - Short: "Allow a validator to validate your subnet", - Long: `The subnet addValidator command whitelists a primary network validator to -validate the provided deployed Subnet. + Use: "addValidator [blockchainName]", + Short: "Allow a validator to validate your blockchain's subnet", + Long: `The blockchain addValidator command whitelists a primary network validator to +validate the subnet of the provided deployed Blockchain. To add the validator to the Subnet's allow list, you first need to provide -the subnetName and the validator's unique NodeID. The command then prompts +the blockchainName and the validator's unique NodeID. The command then prompts for the validation start time, duration, and stake weight. You can bypass these prompts by providing the values with flags. -This command currently only works on Subnets deployed to either the Fuji +This command currently only works on Blockchains deployed to either the Fuji Testnet or Mainnet.`, RunE: addValidator, Args: cobrautils.ExactArgs(1), @@ -85,7 +90,7 @@ Testnet or Mainnet.`, } func addValidator(_ *cobra.Command, args []string) error { - subnetName := args[0] + blockchainName := args[0] network, err := networkoptions.GetNetworkFromCmdLineFlags( app, "", @@ -98,7 +103,7 @@ func addValidator(_ *cobra.Command, args []string) error { if err != nil { return err } - fee := network.GenesisParams().AddSubnetValidatorFee + fee := network.GenesisParams().TxFeeConfig.StaticFeeConfig.AddSubnetValidatorFee kc, err := keychain.GetKeychainFromCmdLineFlags( app, constants.PayTxsFeesMsg, @@ -113,11 +118,11 @@ func addValidator(_ *cobra.Command, args []string) error { return err } network.HandlePublicNetworkSimulation() - if err := UpdateKeychainWithSubnetControlKeys(kc, network, subnetName); err != nil { + if err := UpdateKeychainWithSubnetControlKeys(kc, network, blockchainName); err != nil { return err } deployer := subnet.NewPublicDeployer(app, kc, network) - return CallAddValidator(deployer, network, kc, useLedger, subnetName, nodeIDStr, defaultValidatorParams, waitForTxAcceptance) + return CallAddValidator(deployer, network, kc, useLedger, blockchainName, nodeIDStr, defaultValidatorParams, waitForTxAcceptance) } func CallAddValidator( @@ -125,7 +130,7 @@ func CallAddValidator( network models.Network, kc *keychain.Keychain, useLedgerSetting bool, - subnetName string, + blockchainName string, nodeIDStr string, defaultValidatorParamsSetting bool, waitForTxAcceptanceSetting bool, @@ -162,12 +167,12 @@ func CallAddValidator( } } - _, err = ValidateSubnetNameAndGetChains([]string{subnetName}) + _, err = ValidateSubnetNameAndGetChains([]string{blockchainName}) if err != nil { return err } - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } @@ -176,7 +181,6 @@ func CallAddValidator( if subnetID == ids.Empty { return errNoSubnetID } - transferSubnetOwnershipTxID := sc.Networks[network.Name()].TransferSubnetOwnershipTxID isPermissioned, controlKeys, threshold, err := txutils.GetOwners(network, subnetID) if err != nil { @@ -241,7 +245,6 @@ func CallAddValidator( controlKeys, subnetAuthKeys, subnetID, - transferSubnetOwnershipTxID, nodeID, selectedWeight, start, @@ -254,7 +257,7 @@ func CallAddValidator( if err := SaveNotFullySignedTx( "Add Validator", tx, - subnetName, + blockchainName, subnetAuthKeys, remainingSubnetAuthKeys, outputTxPath, diff --git a/cmd/subnetcmd/subnet.go b/cmd/blockchaincmd/blockchain.go similarity index 52% rename from cmd/subnetcmd/subnet.go rename to cmd/blockchaincmd/blockchain.go index 5c24bc68e..514f92e52 100644 --- a/cmd/subnetcmd/subnet.go +++ b/cmd/blockchaincmd/blockchain.go @@ -1,9 +1,9 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd/upgradecmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd/upgradecmd" "github.com/ava-labs/avalanche-cli/pkg/application" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/spf13/cobra" @@ -11,57 +11,53 @@ import ( var app *application.Avalanche -// avalanche subnet +// avalanche blockchain func NewCmd(injectedApp *application.Avalanche) *cobra.Command { cmd := &cobra.Command{ - Use: "subnet", - Short: "Create and deploy subnets", - Long: `The subnet command suite provides a collection of tools for developing -and deploying Subnets. + Use: "blockchain", + Short: "Create and deploy blockchains", + Long: `The blockchain command suite provides a collection of tools for developing +and deploying Blockchains. -To get started, use the subnet create command wizard to walk through the -configuration of your very first Subnet. Then, go ahead and deploy it -with the subnet deploy command. You can use the rest of the commands to -manage your Subnet configurations and live deployments.`, +To get started, use the blockchain create command wizard to walk through the +configuration of your very first Blockchain. Then, go ahead and deploy it +with the blockchain deploy command. You can use the rest of the commands to +manage your Blockchain configurations and live deployments.`, RunE: cobrautils.CommandSuiteUsage, } app = injectedApp - // subnet create + // blockchain create cmd.AddCommand(newCreateCmd()) - // subnet delete + // blockchain delete cmd.AddCommand(newDeleteCmd()) - // subnet deploy + // blockchain deploy cmd.AddCommand(newDeployCmd()) - // subnet describe + // blockchain describe cmd.AddCommand(newDescribeCmd()) - // subnet list + // blockchain list cmd.AddCommand(newListCmd()) - // subnet join + // blockchain join cmd.AddCommand(newJoinCmd()) - // subnet addValidator + // blockchain addValidator cmd.AddCommand(newAddValidatorCmd()) - // subnet export + // blockchain export cmd.AddCommand(newExportCmd()) - // subnet import + // blockchain import cmd.AddCommand(newImportCmd()) - // subnet publish + // blockchain publish cmd.AddCommand(newPublishCmd()) - // subnet upgrade + // blockchain upgrade cmd.AddCommand(upgradecmd.NewCmd(app)) - // subnet stats + // blockchain stats cmd.AddCommand(newStatsCmd()) - // subnet configure + // blockchain configure cmd.AddCommand(newConfigureCmd()) - // subnet VMID + // blockchain VMID cmd.AddCommand(vmidCmd()) - // subnet removeValidator + // blockchain removeValidator cmd.AddCommand(newRemoveValidatorCmd()) - // subnet elastic - cmd.AddCommand(newElasticCmd()) // subnet validators cmd.AddCommand(newValidatorsCmd()) - // subnet addPermissionlessDelegator - cmd.AddCommand(newAddPermissionlessDelegatorCmd()) // subnet changeOwner cmd.AddCommand(newChangeOwnerCmd()) return cmd diff --git a/cmd/subnetcmd/changeOwner.go b/cmd/blockchaincmd/change_owner.go similarity index 79% rename from cmd/subnetcmd/changeOwner.go rename to cmd/blockchaincmd/change_owner.go index 8789c49be..9ff6c950e 100644 --- a/cmd/subnetcmd/changeOwner.go +++ b/cmd/blockchaincmd/change_owner.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "fmt" @@ -17,18 +17,21 @@ import ( "github.com/spf13/cobra" ) -var changeOwnerSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Devnet, networkoptions.Fuji, networkoptions.Mainnet} +var changeOwnerSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + networkoptions.Mainnet, +} -// avalanche subnet changeOwner +// avalanche blockchain changeOwner func newChangeOwnerCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "changeOwner [subnetName]", - Short: "Change owner of the subnet", - Long: `The subnet changeOwner changes the owner of the deployed Subnet. - -This command currently only works on Subnets deployed to Devnet, Fuji or Mainnet.`, - RunE: changeOwner, - Args: cobrautils.ExactArgs(1), + Use: "changeOwner [blockchainName]", + Short: "Change owner of the blockchain's subnet", + Long: `The blockchain changeOwner changes the owner of the subnet of the deployed Blockchain.`, + RunE: changeOwner, + Args: cobrautils.ExactArgs(1), } networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, true, changeOwnerSupportedNetworkOptions) cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on fuji/devnet)") @@ -44,7 +47,7 @@ This command currently only works on Subnets deployed to Devnet, Fuji or Mainnet } func changeOwner(_ *cobra.Command, args []string) error { - subnetName := args[0] + blockchainName := args[0] network, err := networkoptions.GetNetworkFromCmdLineFlags( app, @@ -59,7 +62,7 @@ func changeOwner(_ *cobra.Command, args []string) error { return err } - fee := network.GenesisParams().TxFee + fee := network.GenesisParams().TxFeeConfig.StaticFeeConfig.TxFee kc, err := keychain.GetKeychainFromCmdLineFlags( app, "pay fees", @@ -82,12 +85,12 @@ func changeOwner(_ *cobra.Command, args []string) error { } } - _, err = ValidateSubnetNameAndGetChains([]string{subnetName}) + _, err = ValidateSubnetNameAndGetChains([]string{blockchainName}) if err != nil { return err } - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } @@ -96,7 +99,6 @@ func changeOwner(_ *cobra.Command, args []string) error { if subnetID == ids.Empty { return errNoSubnetID } - transferSubnetOwnershipTxID := sc.Networks[network.Name()].TransferSubnetOwnershipTxID isPermissioned, currentControlKeys, currentThreshold, err := txutils.GetOwners(network, subnetID) if err != nil { @@ -146,7 +148,6 @@ func changeOwner(_ *cobra.Command, args []string) error { currentControlKeys, subnetAuthKeys, subnetID, - transferSubnetOwnershipTxID, controlKeys, threshold, ) @@ -157,7 +158,7 @@ func changeOwner(_ *cobra.Command, args []string) error { if err := SaveNotFullySignedTx( "Transfer Subnet Ownership", tx, - subnetName, + blockchainName, subnetAuthKeys, remainingSubnetAuthKeys, outputTxPath, @@ -165,14 +166,6 @@ func changeOwner(_ *cobra.Command, args []string) error { ); err != nil { return err } - } else { - networkData := sc.Networks[network.Name()] - networkData.TransferSubnetOwnershipTxID = tx.ID() - sc.Networks[network.Name()] = networkData - if err := app.UpdateSidecar(&sc); err != nil { - return fmt.Errorf("change of subnet owner was successful, but failed to update sidecar: %w", err) - } } - return nil } diff --git a/cmd/subnetcmd/configure.go b/cmd/blockchaincmd/configure.go similarity index 93% rename from cmd/subnetcmd/configure.go rename to cmd/blockchaincmd/configure.go index 49c4e266c..c3192de4a 100644 --- a/cmd/subnetcmd/configure.go +++ b/cmd/blockchaincmd/configure.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "fmt" @@ -22,10 +22,10 @@ var ( perNodeChainConf string ) -// avalanche subnet configure +// avalanche blockchain configure func newConfigureCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "configure [subnetName]", + Use: "configure [blockchainName]", Short: "Adds additional config files for the avalanchego nodes", Long: `AvalancheGo nodes support several different configuration files. Subnets have their own Subnet config which applies to all chains/VMs in the Subnet. Each chain within the Subnet @@ -44,7 +44,7 @@ configuration itself. This command allows you to set all those files.`, func CallConfigure( cmd *cobra.Command, - subnetName string, + blockchainName string, chainConfParam string, subnetConfParam string, nodeConfParam string, @@ -52,7 +52,7 @@ func CallConfigure( chainConf = chainConfParam subnetConf = subnetConfParam nodeConf = nodeConfParam - return configure(cmd, []string{subnetName}) + return configure(cmd, []string{blockchainName}) } func configure(_ *cobra.Command, args []string) error { @@ -60,7 +60,7 @@ func configure(_ *cobra.Command, args []string) error { if err != nil { return err } - subnetName := chains[0] + blockchainName := chains[0] const ( chainLabel = constants.ChainConfigFileName @@ -114,7 +114,7 @@ func configure(_ *cobra.Command, args []string) error { // load each provided file for filename, configPath := range configsToLoad { - if err = updateConf(subnetName, configPath, filename); err != nil { + if err = updateConf(blockchainName, configPath, filename); err != nil { return err } } diff --git a/cmd/subnetcmd/create.go b/cmd/blockchaincmd/create.go similarity index 81% rename from cmd/subnetcmd/create.go rename to cmd/blockchaincmd/create.go index 9c4b184ea..07ee0835d 100644 --- a/cmd/subnetcmd/create.go +++ b/cmd/blockchaincmd/create.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "encoding/json" @@ -37,7 +37,8 @@ type CreateFlags struct { useCustomVM bool chainID uint64 tokenSymbol string - useDefaults bool + useTestDefaults bool + useProductionDefaults bool useWarp bool useTeleporter bool vmVersion string @@ -56,27 +57,27 @@ var ( errIllegalNameCharacter = errors.New( "illegal name character: only letters, no special characters allowed") errMutuallyExlusiveVersionOptions = errors.New("version flags --latest,--pre-release,vm-version are mutually exclusive") - errMutuallyExclusiveVMConfigOptions = errors.New("--genesis flag disables --evm-chain-id,--evm-defaults") + errMutuallyExclusiveVMConfigOptions = errors.New("--genesis flag disables --evm-chain-id,--evm-defaults,--production-defaults,--test-defaults") ) -// avalanche subnet create +// avalanche blockchain create func newCreateCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "create [subnetName]", - Short: "Create a new subnet configuration", - Long: `The subnet create command builds a new genesis file to configure your Subnet. + Use: "create [blockchainName]", + Short: "Create a new blockchain configuration", + Long: `The blockchain create command builds a new genesis file to configure your Blockchain. By default, the command runs an interactive wizard. It walks you through -all the steps you need to create your first Subnet. +all the steps you need to create your first Blockchain. The tool supports deploying Subnet-EVM, and custom VMs. You can create a custom, user-generated genesis with a custom VM by providing the path to your genesis and VM binaries with the --genesis and --vm flags. -By default, running the command with a subnetName that already exists +By default, running the command with a blockchainName that already exists causes the command to fail. If you'd like to overwrite an existing configuration, pass the -f flag.`, Args: cobrautils.ExactArgs(1), - RunE: createSubnetConfig, + RunE: createBlockchainConfig, PersistentPostRun: handlePostRun, } cmd.Flags().StringVar(&genesisFile, "genesis", "", "file path of genesis to use") @@ -87,7 +88,9 @@ configuration, pass the -f flag.`, cmd.Flags().BoolVar(&createFlags.useLatestReleasedVMVersion, latest, false, "use latest Subnet-EVM released version, takes precedence over --vm-version") cmd.Flags().Uint64Var(&createFlags.chainID, "evm-chain-id", 0, "chain ID to use with Subnet-EVM") cmd.Flags().StringVar(&createFlags.tokenSymbol, "evm-token", "", "token symbol to use with Subnet-EVM") - cmd.Flags().BoolVar(&createFlags.useDefaults, "evm-defaults", false, "use default settings for fees/airdrop/precompiles/teleporter with Subnet-EVM") + cmd.Flags().BoolVar(&createFlags.useProductionDefaults, "evm-defaults", false, "deprecation notice: use '--production-defaults'") + cmd.Flags().BoolVar(&createFlags.useProductionDefaults, "production-defaults", false, "use default production settings for your blockchain") + cmd.Flags().BoolVar(&createFlags.useTestDefaults, "test-defaults", false, "use default test settings for your blockchain") cmd.Flags().BoolVarP(&forceCreate, forceFlag, "f", false, "overwrite the existing configuration if one exists") cmd.Flags().StringVar(&vmFile, "vm", "", "file path of custom vm to use. alias to custom-vm-path") cmd.Flags().StringVar(&vmFile, "custom-vm-path", "", "file path of custom vm to use") @@ -103,7 +106,7 @@ configuration, pass the -f flag.`, func CallCreate( cmd *cobra.Command, - subnetName string, + blockchainName string, forceCreateParam bool, genesisFileParam string, useSubnetEvmParam bool, @@ -111,7 +114,8 @@ func CallCreate( vmVersionParam string, evmChainIDParam uint64, tokenSymbolParam string, - useDefaultsParam bool, + useProductionDefaultsParam bool, + useTestDefaultsParam bool, useLatestReleasedVMVersionParam bool, useLatestPreReleasedVMVersionParam bool, customVMRepoURLParam string, @@ -124,28 +128,29 @@ func CallCreate( createFlags.vmVersion = vmVersionParam createFlags.chainID = evmChainIDParam createFlags.tokenSymbol = tokenSymbolParam - createFlags.useDefaults = useDefaultsParam + createFlags.useProductionDefaults = useProductionDefaultsParam + createFlags.useTestDefaults = useTestDefaultsParam createFlags.useLatestReleasedVMVersion = useLatestReleasedVMVersionParam createFlags.useLatestPreReleasedVMVersion = useLatestPreReleasedVMVersionParam createFlags.useCustomVM = useCustomParam customVMRepoURL = customVMRepoURLParam customVMBranch = customVMBranchParam customVMBuildScript = customVMBuildScriptParam - return createSubnetConfig(cmd, []string{subnetName}) + return createBlockchainConfig(cmd, []string{blockchainName}) } // override postrun function from root.go, so that we don't double send metrics for the same command func handlePostRun(_ *cobra.Command, _ []string) {} -func createSubnetConfig(cmd *cobra.Command, args []string) error { - subnetName := args[0] +func createBlockchainConfig(cmd *cobra.Command, args []string) error { + blockchainName := args[0] - if app.GenesisExists(subnetName) && !forceCreate { + if app.GenesisExists(blockchainName) && !forceCreate { return errors.New("configuration already exists. Use --" + forceFlag + " parameter to overwrite") } - if err := checkInvalidSubnetNames(subnetName); err != nil { - return fmt.Errorf("subnet name %q is invalid: %w", subnetName, err) + if err := checkInvalidSubnetNames(blockchainName); err != nil { + return fmt.Errorf("subnet name %q is invalid: %w", blockchainName, err) } // version flags exclusiveness @@ -157,8 +162,16 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { return errMutuallyExlusiveVersionOptions } + defaultsKind := vm.NoDefaults + if createFlags.useTestDefaults { + defaultsKind = vm.TestDefaults + } + if createFlags.useProductionDefaults { + defaultsKind = vm.ProductionDefaults + } + // genesis flags exclusiveness - if genesisFile != "" && (createFlags.chainID != 0 || createFlags.useDefaults) { + if genesisFile != "" && (createFlags.chainID != 0 || defaultsKind != vm.NoDefaults) { return errMutuallyExclusiveVMConfigOptions } @@ -201,7 +214,7 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { if vmType == models.SubnetEvm { if genesisFile == "" { // Default - createFlags.useDefaults, err = vm.PromptDefaults(app, createFlags.useDefaults) + defaultsKind, err = vm.PromptDefaults(app, defaultsKind) if err != nil { return err } @@ -209,7 +222,7 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { // get vm version vmVersion := createFlags.vmVersion - if createFlags.useLatestReleasedVMVersion || createFlags.useDefaults { + if createFlags.useLatestReleasedVMVersion || defaultsKind != vm.NoDefaults { vmVersion = latest } if createFlags.useLatestPreReleasedVMVersion { @@ -235,11 +248,11 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { if err != nil { return err } - deployTeleporter, err = vm.PromptInteropt(app, useTeleporterFlag, createFlags.useDefaults, false) + deployTeleporter, err = vm.PromptInterop(app, useTeleporterFlag, defaultsKind, false) if err != nil { return err } - ux.Logger.PrintToUser("importing genesis for subnet %s", subnetName) + ux.Logger.PrintToUser("importing genesis for blockchain %s", blockchainName) genesisBytes, err = os.ReadFile(genesisFile) if err != nil { return err @@ -251,8 +264,9 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { vmVersion, createFlags.chainID, createFlags.tokenSymbol, + blockchainName, useTeleporterFlag, - createFlags.useDefaults, + defaultsKind, createFlags.useWarp, createFlags.useExternalGasToken, ) @@ -261,9 +275,8 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { } deployTeleporter = params.UseTeleporter useExternalGasToken = params.UseExternalGasToken - genesisBytes, err = vm.CreateEvmGenesis( - app, - subnetName, + genesisBytes, err = vm.CreateEVMGenesis( + blockchainName, params, teleporterInfo, ) @@ -273,7 +286,7 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { } sc, err = vm.CreateEvmSidecar( app, - subnetName, + blockchainName, vmVersion, tokenSymbol, true, @@ -292,14 +305,14 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { if err != nil { return err } - deployTeleporter, err = vm.PromptInteropt(app, useTeleporterFlag, createFlags.useDefaults, false) + deployTeleporter, err = vm.PromptInterop(app, useTeleporterFlag, defaultsKind, false) if err != nil { return err } } sc, err = vm.CreateCustomSidecar( app, - subnetName, + blockchainName, useRepo, customVMRepoURL, customVMBranch, @@ -316,7 +329,7 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { sc.TeleporterReady = true sc.RunRelayer = true // TODO: remove this once deploy asks if deploying relayer sc.ExternalToken = useExternalGasToken - sc.TeleporterKey = constants.TeleporterKeyName + sc.TeleporterKey = constants.ICMKeyName sc.TeleporterVersion = teleporterInfo.Version if genesisFile != "" { if evmCompatibleGenesis, err := utils.FileIsSubnetEVMGenesis(genesisFile); err != nil { @@ -331,7 +344,7 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { } } - if err = app.WriteGenesisFile(subnetName, genesisBytes); err != nil { + if err = app.WriteGenesisFile(blockchainName, genesisBytes); err != nil { return err } @@ -339,12 +352,12 @@ func createSubnetConfig(cmd *cobra.Command, args []string) error { return err } if vmType == models.SubnetEvm { - err = sendMetrics(cmd, vmType.RepoName(), subnetName) + err = sendMetrics(cmd, vmType.RepoName(), blockchainName) if err != nil { return err } } - ux.Logger.GreenCheckmarkToUser("Successfully created subnet configuration") + ux.Logger.GreenCheckmarkToUser("Successfully created blockchain configuration") return nil } @@ -369,10 +382,10 @@ func addSubnetEVMGenesisPrefundedAddress(genesisBytes []byte, address string, ba return json.MarshalIndent(genesisMap, "", " ") } -func sendMetrics(cmd *cobra.Command, repoName, subnetName string) error { +func sendMetrics(cmd *cobra.Command, repoName, blockchainName string) error { flags := make(map[string]string) flags[constants.SubnetType] = repoName - genesis, err := app.LoadEvmGenesis(subnetName) + genesis, err := app.LoadEvmGenesis(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/delete.go b/cmd/blockchaincmd/delete.go similarity index 76% rename from cmd/subnetcmd/delete.go rename to cmd/blockchaincmd/delete.go index 242278fbb..c542abb3c 100644 --- a/cmd/subnetcmd/delete.go +++ b/cmd/blockchaincmd/delete.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "errors" @@ -14,28 +14,28 @@ import ( "github.com/spf13/cobra" ) -// avalanche subnet delete +// avalanche blockchain delete func newDeleteCmd() *cobra.Command { return &cobra.Command{ - Use: "delete [subnetName]", - Short: "Delete a subnet configuration", - Long: "The subnet delete command deletes an existing subnet configuration.", - RunE: deleteSubnet, + Use: "delete [blockchainName]", + Short: "Delete a blockchain configuration", + Long: "The blockchain delete command deletes an existing blockchain configuration.", + RunE: deleteBlockchain, Args: cobrautils.ExactArgs(1), } } -func deleteSubnet(_ *cobra.Command, args []string) error { +func deleteBlockchain(_ *cobra.Command, args []string) error { // TODO sanitize this input - subnetName := args[0] + blockchainName := args[0] - sidecar, err := app.LoadSidecar(subnetName) + sidecar, err := app.LoadSidecar(blockchainName) if err != nil { return err } if sidecar.VM == models.CustomVM { - customVMPath := app.GetCustomVMPath(subnetName) + customVMPath := app.GetCustomVMPath(blockchainName) if _, err := os.Stat(customVMPath); err != nil { if !errors.Is(err, fs.ErrNotExist) { return err @@ -56,7 +56,7 @@ func deleteSubnet(_ *cobra.Command, args []string) error { // but only if no other subnet is using it. // More info: https://github.com/ava-labs/avalanche-cli/issues/246 - subnetDir := filepath.Join(app.GetSubnetDir(), subnetName) + subnetDir := filepath.Join(app.GetSubnetDir(), blockchainName) if _, err := os.Stat(subnetDir); err != nil { if !errors.Is(err, fs.ErrNotExist) { return err @@ -66,7 +66,7 @@ func deleteSubnet(_ *cobra.Command, args []string) error { } // rm airdrop key if exists - airdropKeyName, _, _, err := subnet.GetDefaultSubnetAirdropKeyInfo(app, subnetName) + airdropKeyName, _, _, err := subnet.GetDefaultSubnetAirdropKeyInfo(app, blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/deploy.go b/cmd/blockchaincmd/deploy.go similarity index 84% rename from cmd/subnetcmd/deploy.go rename to cmd/blockchaincmd/deploy.go index 42d2bfaf1..01516cc0a 100644 --- a/cmd/subnetcmd/deploy.go +++ b/cmd/blockchaincmd/deploy.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "encoding/json" @@ -21,7 +21,6 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/prompts" "github.com/ava-labs/avalanche-cli/pkg/subnet" "github.com/ava-labs/avalanche-cli/pkg/txutils" - "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" "github.com/ava-labs/avalanche-cli/pkg/vm" anrutils "github.com/ava-labs/avalanche-network-runner/utils" @@ -34,7 +33,12 @@ import ( "golang.org/x/mod/semver" ) -var deploySupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Devnet, networkoptions.Fuji, networkoptions.Mainnet} +var deploySupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + networkoptions.Mainnet, +} var ( sameControlKey bool @@ -52,7 +56,7 @@ var ( skipCreatePrompt bool avagoBinaryPath string subnetOnly bool - teleporterEsp subnet.TeleporterEsp + icmSpec subnet.ICMSpec errMutuallyExlusiveControlKeys = errors.New("--control-keys and --same-control-key are mutually exclusive") ErrMutuallyExlusiveKeyLedger = errors.New("key source flags --key, --ledger/--ledger-addrs are mutually exclusive") @@ -60,22 +64,22 @@ var ( errMutuallyExlusiveSubnetFlags = errors.New("--subnet-only and --subnet-id are mutually exclusive") ) -// avalanche subnet deploy +// avalanche blockchain deploy func newDeployCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "deploy [subnetName]", - Short: "Deploys a subnet configuration", - Long: `The subnet deploy command deploys your Subnet configuration locally, to Fuji Testnet, or to Mainnet. + Use: "deploy [blockchainName]", + Short: "Deploys a blockchain configuration", + Long: `The blockchain deploy command deploys your Blockchain configuration locally, to Fuji Testnet, or to Mainnet. At the end of the call, the command prints the RPC URL you can use to interact with the Subnet. -Avalanche-CLI only supports deploying an individual Subnet once per network. Subsequent -attempts to deploy the same Subnet to the same network (local, Fuji, Mainnet) aren't -allowed. If you'd like to redeploy a Subnet locally for testing, you must first call +Avalanche-CLI only supports deploying an individual Blockchain once per network. Subsequent +attempts to deploy the same Blockchain to the same network (local, Fuji, Mainnet) aren't +allowed. If you'd like to redeploy a Blockchain locally for testing, you must first call avalanche network clean to reset all deployed chain state. Subsequent local deploys -redeploy the chain with fresh state. You can deploy the same Subnet to multiple networks, +redeploy the chain with fresh state. You can deploy the same Blockchain to multiple networks, so you can take your locally tested Subnet and deploy it on Fuji or Mainnet.`, - RunE: deploySubnet, + RunE: deployBlockchain, PersistentPostRun: handlePostRun, Args: cobrautils.ExactArgs(1), } @@ -94,20 +98,21 @@ so you can take your locally tested Subnet and deploy it on Fuji or Mainnet.`, cmd.Flags().Uint32Var(&mainnetChainID, "mainnet-chain-id", 0, "use different ChainID for mainnet deployment") cmd.Flags().StringVar(&avagoBinaryPath, "avalanchego-path", "", "use this avalanchego binary path") cmd.Flags().BoolVar(&subnetOnly, "subnet-only", false, "only create a subnet") - cmd.Flags().BoolVar(&teleporterEsp.SkipDeploy, "skip-local-teleporter", false, "skip automatic teleporter deploy on local networks [to be deprecated]") - cmd.Flags().BoolVar(&teleporterEsp.SkipDeploy, "skip-teleporter-deploy", false, "skip automatic teleporter deploy") - cmd.Flags().StringVar(&teleporterEsp.Version, "teleporter-version", "latest", "teleporter version to deploy") - cmd.Flags().StringVar(&teleporterEsp.MessengerContractAddressPath, "teleporter-messenger-contract-address-path", "", "path to a teleporter messenger contract address file") - cmd.Flags().StringVar(&teleporterEsp.MessengerDeployerAddressPath, "teleporter-messenger-deployer-address-path", "", "path to a teleporter messenger deployer address file") - cmd.Flags().StringVar(&teleporterEsp.MessengerDeployerTxPath, "teleporter-messenger-deployer-tx-path", "", "path to a teleporter messenger deployer tx file") - cmd.Flags().StringVar(&teleporterEsp.RegistryBydecodePath, "teleporter-registry-bytecode-path", "", "path to a teleporter registry bytecode file") + cmd.Flags().BoolVar(&icmSpec.SkipICMDeploy, "skip-local-teleporter", false, "skip automatic teleporter deploy on local networks [to be deprecated]") + cmd.Flags().BoolVar(&icmSpec.SkipICMDeploy, "skip-teleporter-deploy", false, "skip automatic teleporter deploy") + cmd.Flags().BoolVar(&icmSpec.SkipRelayerDeploy, "skip-relayer", false, "skip relayer deploy") + cmd.Flags().StringVar(&icmSpec.Version, "teleporter-version", "latest", "teleporter version to deploy") + cmd.Flags().StringVar(&icmSpec.MessengerContractAddressPath, "teleporter-messenger-contract-address-path", "", "path to an interchain messenger contract address file") + cmd.Flags().StringVar(&icmSpec.MessengerDeployerAddressPath, "teleporter-messenger-deployer-address-path", "", "path to an interchain messenger deployer address file") + cmd.Flags().StringVar(&icmSpec.MessengerDeployerTxPath, "teleporter-messenger-deployer-tx-path", "", "path to an interchain messenger deployer tx file") + cmd.Flags().StringVar(&icmSpec.RegistryBydecodePath, "teleporter-registry-bytecode-path", "", "path to an interchain messenger registry bytecode file") return cmd } func CallDeploy( cmd *cobra.Command, subnetOnlyParam bool, - subnetName string, + blockchainName string, networkFlags networkoptions.NetworkFlags, keyNameParam string, useLedgerParam bool, @@ -120,10 +125,10 @@ func CallDeploy( keyName = keyNameParam useLedger = useLedgerParam useEwoq = useEwoqParam - return deploySubnet(cmd, []string{subnetName}) + return deployBlockchain(cmd, []string{blockchainName}) } -func getChainsInSubnet(subnetName string) ([]string, error) { +func getChainsInSubnet(blockchainName string) ([]string, error) { subnets, err := os.ReadDir(app.GetSubnetDir()) if err != nil { return nil, fmt.Errorf("failed to read baseDir: %w", err) @@ -148,7 +153,7 @@ func getChainsInSubnet(subnetName string) ([]string, error) { if err != nil { return nil, fmt.Errorf("failed unmarshaling file %s: %w", sidecarFile, err) } - if sc.Subnet == subnetName { + if sc.Subnet == blockchainName { chains = append(chains, sc.Name) } } @@ -175,7 +180,7 @@ func checkSubnetEVMDefaultAddressNotInAlloc(network models.Network, chain string func runDeploy(cmd *cobra.Command, args []string, supportedNetworkOptions []networkoptions.NetworkOption) error { skipCreatePrompt = true deploySupportedNetworkOptions = supportedNetworkOptions - return deploySubnet(cmd, args) + return deployBlockchain(cmd, args) } func updateSubnetEVMGenesisChainID(genesisBytes []byte, newChainID uint) ([]byte, error) { @@ -197,9 +202,9 @@ func updateSubnetEVMGenesisChainID(genesisBytes []byte, newChainID uint) ([]byte // updates sidecar with genesis mainnet id to use // given either by cmdline flag, original genesis id, or id obtained from the user -func getSubnetEVMMainnetChainID(sc *models.Sidecar, subnetName string) error { +func getSubnetEVMMainnetChainID(sc *models.Sidecar, blockchainName string) error { // get original chain id - evmGenesis, err := app.LoadEvmGenesis(subnetName) + evmGenesis, err := app.LoadEvmGenesis(blockchainName) if err != nil { return err } @@ -256,11 +261,11 @@ func getSubnetEVMMainnetChainID(sc *models.Sidecar, subnetName string) error { return app.UpdateSidecar(sc) } -// deploySubnet is the cobra command run for deploying subnets -func deploySubnet(cmd *cobra.Command, args []string) error { - subnetName := args[0] +// deployBlockchain is the cobra command run for deploying subnets +func deployBlockchain(cmd *cobra.Command, args []string) error { + blockchainName := args[0] - if err := CreateSubnetFirst(cmd, subnetName, skipCreatePrompt); err != nil { + if err := CreateBlockchainFirst(cmd, blockchainName, skipCreatePrompt); err != nil { return err } @@ -269,8 +274,8 @@ func deploySubnet(cmd *cobra.Command, args []string) error { return err } - if teleporterEsp.MessengerContractAddressPath != "" || teleporterEsp.MessengerDeployerAddressPath != "" || teleporterEsp.MessengerDeployerTxPath != "" || teleporterEsp.RegistryBydecodePath != "" { - if teleporterEsp.MessengerContractAddressPath == "" || teleporterEsp.MessengerDeployerAddressPath == "" || teleporterEsp.MessengerDeployerTxPath == "" || teleporterEsp.RegistryBydecodePath == "" { + if icmSpec.MessengerContractAddressPath != "" || icmSpec.MessengerDeployerAddressPath != "" || icmSpec.MessengerDeployerTxPath != "" || icmSpec.RegistryBydecodePath != "" { + if icmSpec.MessengerContractAddressPath == "" || icmSpec.MessengerDeployerAddressPath == "" || icmSpec.MessengerDeployerTxPath == "" || icmSpec.RegistryBydecodePath == "" { return fmt.Errorf("if setting any teleporter asset path, you must set all teleporter asset paths") } } @@ -368,7 +373,7 @@ func deploySubnet(cmd *cobra.Command, args []string) error { } deployer := subnet.NewLocalDeployer(app, userProvidedAvagoVersion, avagoBinaryPath, vmBin) - deployInfo, err := deployer.DeployToLocalNetwork(chain, genesisPath, teleporterEsp, subnetIDStr) + deployInfo, err := deployer.DeployToLocalNetwork(chain, genesisPath, icmSpec, subnetIDStr) if err != nil { if deployer.BackendStartedHere() { if innerErr := binutils.KillgRPCServerProcess(app); innerErr != nil { @@ -384,14 +389,13 @@ func deploySubnet(cmd *cobra.Command, args []string) error { &sidecar, network, deployInfo.SubnetID, - ids.Empty, deployInfo.BlockchainID, - deployInfo.TeleporterMessengerAddress, - deployInfo.TeleporterRegistryAddress, + deployInfo.ICMMessengerAddress, + deployInfo.ICMRegistryAddress, ); err != nil { return err } - return PrintSubnetInfo(subnetName, true) + return PrintSubnetInfo(blockchainName, true) } // from here on we are assuming a public deploy @@ -400,7 +404,7 @@ func deploySubnet(cmd *cobra.Command, args []string) error { } createSubnet := true - var subnetID, transferSubnetOwnershipTxID ids.ID + var subnetID ids.ID if subnetIDStr != "" { subnetID, err = ids.FromString(subnetIDStr) if err != nil { @@ -412,7 +416,6 @@ func deploySubnet(cmd *cobra.Command, args []string) error { if ok { if model.SubnetID != ids.Empty && model.BlockchainID == ids.Empty { subnetID = model.SubnetID - transferSubnetOwnershipTxID = model.TransferSubnetOwnershipTxID createSubnet = false } } @@ -420,10 +423,10 @@ func deploySubnet(cmd *cobra.Command, args []string) error { fee := uint64(0) if !subnetOnly { - fee += network.GenesisParams().CreateBlockchainTxFee + fee += network.GenesisParams().TxFeeConfig.StaticFeeConfig.CreateBlockchainTxFee } if createSubnet { - fee += network.GenesisParams().CreateSubnetTxFee + fee += network.GenesisParams().TxFeeConfig.StaticFeeConfig.CreateSubnetTxFee } kc, err := keychain.GetKeychainFromCmdLineFlags( @@ -519,7 +522,6 @@ func deploySubnet(cmd *cobra.Command, args []string) error { controlKeys, subnetAuthKeys, subnetID, - transferSubnetOwnershipTxID, chain, chainGenesis, ) @@ -550,28 +552,13 @@ func deploySubnet(cmd *cobra.Command, args []string) error { } } - if isFullySigned { - if network.ClusterName != "" { - clusterConfig, err := app.GetClusterConfig(network.ClusterName) - if err != nil { - return err - } - if _, err := utils.GetIndexInSlice(clusterConfig.Subnets, subnetName); err != nil { - clusterConfig.Subnets = append(clusterConfig.Subnets, subnetName) - } - if err := app.SetClusterConfig(network.ClusterName, clusterConfig); err != nil { - return err - } - } - } - flags := make(map[string]string) flags[constants.MetricsNetwork] = network.Name() metrics.HandleTracking(cmd, constants.MetricsSubnetDeployCommand, app, flags) // update sidecar // TODO: need to do something for backwards compatibility? - return app.UpdateSidecarNetworks(&sidecar, network, subnetID, transferSubnetOwnershipTxID, blockchainID, "", "") + return app.UpdateSidecarNetworks(&sidecar, network, subnetID, blockchainID, "", "") } func ValidateSubnetNameAndGetChains(args []string) ([]string, error) { diff --git a/cmd/subnetcmd/deploy_test.go b/cmd/blockchaincmd/deploy_test.go similarity index 99% rename from cmd/subnetcmd/deploy_test.go rename to cmd/blockchaincmd/deploy_test.go index 1ed4b693a..053bab2c5 100644 --- a/cmd/subnetcmd/deploy_test.go +++ b/cmd/blockchaincmd/deploy_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "errors" diff --git a/cmd/subnetcmd/describe.go b/cmd/blockchaincmd/describe.go similarity index 93% rename from cmd/subnetcmd/describe.go rename to cmd/blockchaincmd/describe.go index 0e4febe2d..83ac2a5b8 100644 --- a/cmd/subnetcmd/describe.go +++ b/cmd/blockchaincmd/describe.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "encoding/hex" @@ -41,12 +41,12 @@ import ( var printGenesisOnly bool -// avalanche subnet describe +// avalanche blockchain describe func newDescribeCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "describe [subnetName]", - Short: "Print a summary of the subnet’s configuration", - Long: `The subnet describe command prints the details of a Subnet configuration to the console. + Use: "describe [blockchainName]", + Short: "Print a summary of the blockchain’s configuration", + Long: `The blockchain describe command prints the details of a Blockchain configuration to the console. By default, the command prints a summary of the configuration. By providing the --genesis flag, the command instead prints out the raw genesis file.`, RunE: describe, @@ -62,8 +62,8 @@ flag, the command instead prints out the raw genesis file.`, return cmd } -func printGenesis(subnetName string) error { - genesisFile := app.GetGenesisPath(subnetName) +func printGenesis(blockchainName string) error { + genesisFile := app.GetGenesisPath(blockchainName) gen, err := os.ReadFile(genesisFile) if err != nil { return err @@ -73,8 +73,8 @@ func printGenesis(subnetName string) error { return nil } -func PrintSubnetInfo(subnetName string, onlyLocalnetInfo bool) error { - sc, err := app.LoadSidecar(subnetName) +func PrintSubnetInfo(blockchainName string, onlyLocalnetInfo bool) error { + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } @@ -127,9 +127,9 @@ func PrintSubnetInfo(subnetName string, onlyLocalnetInfo bool) error { genesisBytes, err := contract.GetBlockchainGenesis( app, network, - sc.Name, - false, - "", + contract.ChainSpec{ + BlockchainName: sc.Name, + }, ) if err != nil { return err @@ -383,28 +383,28 @@ func addPrecompileAllowListToTable( } func describe(_ *cobra.Command, args []string) error { - subnetName := args[0] - if !app.GenesisExists(subnetName) { - ux.Logger.PrintToUser("The provided subnet name %q does not exist", subnetName) + blockchainName := args[0] + if !app.GenesisExists(blockchainName) { + ux.Logger.PrintToUser("The provided subnet name %q does not exist", blockchainName) return nil } if printGenesisOnly { - return printGenesis(subnetName) + return printGenesis(blockchainName) } - if err := PrintSubnetInfo(subnetName, false); err != nil { + if err := PrintSubnetInfo(blockchainName, false); err != nil { return err } - if isEVM, _, err := app.HasSubnetEVMGenesis(subnetName); err != nil { + if isEVM, _, err := app.HasSubnetEVMGenesis(blockchainName); err != nil { return err } else if !isEVM { - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } app.Log.Warn("Unknown genesis format", zap.Any("vm-type", sc.VM)) ux.Logger.PrintToUser("") ux.Logger.PrintToUser("Printing genesis") - return printGenesis(subnetName) + return printGenesis(blockchainName) } return nil } diff --git a/cmd/subnetcmd/export.go b/cmd/blockchaincmd/export.go similarity index 80% rename from cmd/subnetcmd/export.go rename to cmd/blockchaincmd/export.go index 17f1b1b1a..aa5d789f4 100644 --- a/cmd/subnetcmd/export.go +++ b/cmd/blockchaincmd/export.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "encoding/json" @@ -22,12 +22,12 @@ var ( customVMBuildScript string ) -// avalanche subnet list +// avalanche blockchain export func newExportCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "export [subnetName]", + Use: "export [blockchainName]", Short: "Export deployment details", - Long: `The subnet export command write the details of an existing Subnet deploy to a file. + Long: `The blockchain export command write the details of an existing Blockchain deploy to a file. The command prompts for an output path. You can also provide one with the --output flag.`, @@ -48,9 +48,9 @@ the --output flag.`, return cmd } -func CallExportSubnet(subnetName, exportPath string) error { +func CallExportSubnet(blockchainName, exportPath string) error { exportOutput = exportPath - return exportSubnet(nil, []string{subnetName}) + return exportSubnet(nil, []string{blockchainName}) } func exportSubnet(_ *cobra.Command, args []string) error { @@ -63,13 +63,13 @@ func exportSubnet(_ *cobra.Command, args []string) error { } } - subnetName := args[0] + blockchainName := args[0] - if !app.SidecarExists(subnetName) { - return fmt.Errorf("invalid subnet %q", subnetName) + if !app.SidecarExists(blockchainName) { + return fmt.Errorf("invalid subnet %q", blockchainName) } - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } @@ -125,33 +125,33 @@ func exportSubnet(_ *cobra.Command, args []string) error { } } - gen, err := app.LoadRawGenesis(subnetName) + gen, err := app.LoadRawGenesis(blockchainName) if err != nil { return err } var nodeConfig, chainConfig, subnetConfig, networkUpgrades []byte - if app.AvagoNodeConfigExists(subnetName) { - nodeConfig, err = app.LoadRawAvagoNodeConfig(subnetName) + if app.AvagoNodeConfigExists(blockchainName) { + nodeConfig, err = app.LoadRawAvagoNodeConfig(blockchainName) if err != nil { return err } } - if app.ChainConfigExists(subnetName) { - chainConfig, err = app.LoadRawChainConfig(subnetName) + if app.ChainConfigExists(blockchainName) { + chainConfig, err = app.LoadRawChainConfig(blockchainName) if err != nil { return err } } - if app.AvagoSubnetConfigExists(subnetName) { - subnetConfig, err = app.LoadRawAvagoSubnetConfig(subnetName) + if app.AvagoSubnetConfigExists(blockchainName) { + subnetConfig, err = app.LoadRawAvagoSubnetConfig(blockchainName) if err != nil { return err } } - if app.NetworkUpgradeExists(subnetName) { - networkUpgrades, err = app.LoadRawNetworkUpgrades(subnetName) + if app.NetworkUpgradeExists(blockchainName) { + networkUpgrades, err = app.LoadRawNetworkUpgrades(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/export_test.go b/cmd/blockchaincmd/export_test.go similarity index 93% rename from cmd/subnetcmd/export_test.go rename to cmd/blockchaincmd/export_test.go index 0177c307f..c83e0552e 100644 --- a/cmd/subnetcmd/export_test.go +++ b/cmd/blockchaincmd/export_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "encoding/json" @@ -87,13 +87,13 @@ func TestExportImportSubnet(t *testing.T) { err = os.Remove(sidecarFile) require.NoError(err) - err = importSubnet(nil, []string{"this-does-also-not-exist-import-should-fail"}) + err = importBlockchain(nil, []string{"this-does-also-not-exist-import-should-fail"}) require.ErrorIs(err, os.ErrNotExist) - err = importSubnet(nil, []string{exportOutput}) + err = importBlockchain(nil, []string{exportOutput}) require.ErrorContains(err, "subnet already exists") genFile := filepath.Join(app.GetBaseDir(), constants.SubnetDir, testSubnet, constants.GenesisFileName) err = os.Remove(genFile) require.NoError(err) - err = importSubnet(nil, []string{exportOutput}) + err = importBlockchain(nil, []string{exportOutput}) require.NoError(err) } diff --git a/cmd/subnetcmd/helpers.go b/cmd/blockchaincmd/helpers.go similarity index 54% rename from cmd/subnetcmd/helpers.go rename to cmd/blockchaincmd/helpers.go index 2c068b680..98ada06c5 100644 --- a/cmd/subnetcmd/helpers.go +++ b/cmd/blockchaincmd/helpers.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "fmt" @@ -15,41 +15,41 @@ import ( var globalNetworkFlags networkoptions.NetworkFlags -func CreateSubnetFirst(cmd *cobra.Command, subnetName string, skipPrompt bool) error { - if !app.SubnetConfigExists(subnetName) { +func CreateBlockchainFirst(cmd *cobra.Command, blockchainName string, skipPrompt bool) error { + if !app.BlockchainConfigExists(blockchainName) { if !skipPrompt { - yes, err := app.Prompt.CaptureNoYes(fmt.Sprintf("Subnet %s is not created yet. Do you want to create it first?", subnetName)) + yes, err := app.Prompt.CaptureNoYes(fmt.Sprintf("Blockchain %s is not created yet. Do you want to create it first?", blockchainName)) if err != nil { return err } if !yes { - return fmt.Errorf("subnet not available and not being created first") + return fmt.Errorf("blockchain not available and not being created first") } } - return createSubnetConfig(cmd, []string{subnetName}) + return createBlockchainConfig(cmd, []string{blockchainName}) } return nil } -func DeploySubnetFirst(cmd *cobra.Command, subnetName string, skipPrompt bool, supportedNetworkOptions []networkoptions.NetworkOption) error { +func DeployBlockchainFirst(cmd *cobra.Command, blockchainName string, skipPrompt bool, supportedNetworkOptions []networkoptions.NetworkOption) error { var ( doDeploy bool msg string errIfNoChoosen error ) - if !app.SubnetConfigExists(subnetName) { + if !app.BlockchainConfigExists(blockchainName) { doDeploy = true - msg = fmt.Sprintf("Subnet %s is not created yet. Do you want to create it first?", subnetName) - errIfNoChoosen = fmt.Errorf("subnet not available and not being created first") + msg = fmt.Sprintf("Blockchain %s is not created yet. Do you want to create it first?", blockchainName) + errIfNoChoosen = fmt.Errorf("blockchain not available and not being created first") } else { - filteredSupportedNetworkOptions, _, _, err := networkoptions.GetSupportedNetworkOptionsForSubnet(app, subnetName, supportedNetworkOptions) + filteredSupportedNetworkOptions, _, _, err := networkoptions.GetSupportedNetworkOptionsForSubnet(app, blockchainName, supportedNetworkOptions) if err != nil { return err } if len(filteredSupportedNetworkOptions) == 0 { doDeploy = true - msg = fmt.Sprintf("Subnet %s is not deployed yet to a supported network. Do you want to deploy it first?", subnetName) - errIfNoChoosen = fmt.Errorf("subnet not deployed and not being deployed first") + msg = fmt.Sprintf("Blockchain %s is not deployed yet to a supported network. Do you want to deploy it first?", blockchainName) + errIfNoChoosen = fmt.Errorf("blockchain not deployed and not being deployed first") } } if doDeploy { @@ -62,7 +62,7 @@ func DeploySubnetFirst(cmd *cobra.Command, subnetName string, skipPrompt bool, s return errIfNoChoosen } } - return runDeploy(cmd, []string{subnetName}, supportedNetworkOptions) + return runDeploy(cmd, []string{blockchainName}, supportedNetworkOptions) } return nil } @@ -70,9 +70,9 @@ func DeploySubnetFirst(cmd *cobra.Command, subnetName string, skipPrompt bool, s func UpdateKeychainWithSubnetControlKeys( kc *keychain.Keychain, network models.Network, - subnetName string, + blockchainName string, ) error { - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/import.go b/cmd/blockchaincmd/import.go similarity index 66% rename from cmd/subnetcmd/import.go rename to cmd/blockchaincmd/import.go index e3b8ffd27..9641e8c03 100644 --- a/cmd/subnetcmd/import.go +++ b/cmd/blockchaincmd/import.go @@ -1,27 +1,27 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/spf13/cobra" ) -// avalanche subnet +// avalanche blockchain import func newImportCmd() *cobra.Command { cmd := &cobra.Command{ Use: "import", - Short: "Import subnets into avalanche-cli", - Long: `Import subnet configurations into avalanche-cli. + Short: "Import blockchains into avalanche-cli", + Long: `Import blockchain configurations into avalanche-cli. This command suite supports importing from a file created on another computer, -or importing from subnets running public networks +or importing from blockchains running public networks (e.g. created manually or with the deprecated subnet-cli)`, RunE: cobrautils.CommandSuiteUsage, } - // subnet import file + // blockchain import file cmd.AddCommand(newImportFileCmd()) - // subnet import public + // blockchain import public cmd.AddCommand(newImportPublicCmd()) return cmd } diff --git a/cmd/subnetcmd/import_file.go b/cmd/blockchaincmd/import_file.go similarity index 84% rename from cmd/subnetcmd/import_file.go rename to cmd/blockchaincmd/import_file.go index 0370dfab0..1afc3fefa 100644 --- a/cmd/subnetcmd/import_file.go +++ b/cmd/blockchaincmd/import_file.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "encoding/json" @@ -27,19 +27,19 @@ var ( branch string ) -// avalanche subnet import +// avalanche blockchain import file func newImportFileCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "file [subnetPath]", - Short: "Import an existing subnet config", - RunE: importSubnet, + Use: "file [blockchainPath]", + Short: "Import an existing blockchain config", + RunE: importBlockchain, Args: cobrautils.MaximumNArgs(1), - Long: `The subnet import command will import a subnet configuration from a file or a git repository. + Long: `The blockchain import command will import a blockchain configuration from a file or a git repository. To import from a file, you can optionally provide the path as a command-line argument. Alternatively, running the command without any arguments triggers an interactive wizard. -To import from a repository, go through the wizard. By default, an imported Subnet doesn't -overwrite an existing Subnet with the same name. To allow overwrites, provide the --force +To import from a repository, go through the wizard. By default, an imported Blockchain doesn't +overwrite an existing Blockchain with the same name. To allow overwrites, provide the --force flag.`, } cmd.Flags().BoolVarP( @@ -70,7 +70,7 @@ flag.`, return cmd } -func importSubnet(_ *cobra.Command, args []string) error { +func importBlockchain(_ *cobra.Command, args []string) error { if len(args) == 1 { importPath := args[0] return importFromFile(importPath) @@ -116,12 +116,12 @@ func importFromFile(importPath string) error { return err } - subnetName := importable.Sidecar.Name - if subnetName == "" { + blockchainName := importable.Sidecar.Name + if blockchainName == "" { return errors.New("export data is malformed: missing subnet name") } - if app.GenesisExists(subnetName) && !overwriteImport { + if app.GenesisExists(blockchainName) && !overwriteImport { return errors.New("subnet already exists. Use --" + forceFlag + " parameter to overwrite") } @@ -140,7 +140,7 @@ func importFromFile(importPath string) error { return err } - vmPath := app.GetCustomVMPath(subnetName) + vmPath := app.GetCustomVMPath(blockchainName) rpcVersion, err := vm.GetVMBinaryProtocolVersion(vmPath) if err != nil { return fmt.Errorf("unable to get custom binary RPC version: %w", err) @@ -150,40 +150,40 @@ func importFromFile(importPath string) error { } } - if err := app.WriteGenesisFile(subnetName, importable.Genesis); err != nil { + if err := app.WriteGenesisFile(blockchainName, importable.Genesis); err != nil { return err } if importable.NodeConfig != nil { - if err := app.WriteAvagoNodeConfigFile(subnetName, importable.NodeConfig); err != nil { + if err := app.WriteAvagoNodeConfigFile(blockchainName, importable.NodeConfig); err != nil { return err } } else { - _ = os.RemoveAll(app.GetAvagoNodeConfigPath(subnetName)) + _ = os.RemoveAll(app.GetAvagoNodeConfigPath(blockchainName)) } if importable.ChainConfig != nil { - if err := app.WriteChainConfigFile(subnetName, importable.ChainConfig); err != nil { + if err := app.WriteChainConfigFile(blockchainName, importable.ChainConfig); err != nil { return err } } else { - _ = os.RemoveAll(app.GetChainConfigPath(subnetName)) + _ = os.RemoveAll(app.GetChainConfigPath(blockchainName)) } if importable.SubnetConfig != nil { - if err := app.WriteAvagoSubnetConfigFile(subnetName, importable.SubnetConfig); err != nil { + if err := app.WriteAvagoSubnetConfigFile(blockchainName, importable.SubnetConfig); err != nil { return err } } else { - _ = os.RemoveAll(app.GetAvagoSubnetConfigPath(subnetName)) + _ = os.RemoveAll(app.GetAvagoSubnetConfigPath(blockchainName)) } if importable.NetworkUpgrades != nil { - if err := app.WriteNetworkUpgradesFile(subnetName, importable.NetworkUpgrades); err != nil { + if err := app.WriteNetworkUpgradesFile(blockchainName, importable.NetworkUpgrades); err != nil { return err } } else { - _ = os.RemoveAll(app.GetUpgradeBytesFilepath(subnetName)) + _ = os.RemoveAll(app.GetUpgradeBytesFilepath(blockchainName)) } if err := app.CreateSidecar(&importable.Sidecar); err != nil { diff --git a/cmd/subnetcmd/import_public.go b/cmd/blockchaincmd/import_public.go similarity index 85% rename from cmd/subnetcmd/import_public.go rename to cmd/blockchaincmd/import_public.go index 94c796cd8..6ac1e3e5a 100644 --- a/cmd/subnetcmd/import_public.go +++ b/cmd/blockchaincmd/import_public.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "encoding/json" @@ -21,24 +21,29 @@ import ( ) var ( - importPublicSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Fuji, networkoptions.Mainnet} - blockchainIDstr string - nodeURL string - useSubnetEvm bool - useCustomVM bool + importPublicSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Fuji, + networkoptions.Mainnet, + networkoptions.Devnet, + networkoptions.Local, + } + blockchainIDstr string + nodeURL string + useSubnetEvm bool + useCustomVM bool ) -// avalanche subnet import public +// avalanche blockchain import public func newImportPublicCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "public [subnetPath]", - Short: "Import an existing subnet config from running subnets on a public network", + Use: "public [blockchainPath]", + Short: "Import an existing blockchain config from running blockchains on a public network", RunE: importPublic, Args: cobrautils.MaximumNArgs(1), - Long: `The subnet import public command imports a Subnet configuration from a running network. + Long: `The blockchain import public command imports a Blockchain 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 +By default, an imported Blockchain +doesn't overwrite an existing Blockchain with the same name. To allow overwrites, provide the --force flag.`, } @@ -68,7 +73,7 @@ func importPublic(*cobra.Command, []string) error { app, "", globalNetworkFlags, - false, + true, false, importPublicSupportedNetworkOptions, "", @@ -123,13 +128,13 @@ func importPublic(*cobra.Command, []string) error { vmID := createChainTx.VMID subnetID := createChainTx.SubnetID - subnetName := createChainTx.ChainName + blockchainName := createChainTx.ChainName genBytes := createChainTx.GenesisData ux.Logger.PrintToUser("Retrieved information. BlockchainID: %s, SubnetID: %s, Name: %s, VMID: %s", blockchainID.String(), subnetID.String(), - subnetName, + blockchainName, vmID.String(), ) // TODO: it's probably possible to deploy VMs with the same name on a public network @@ -143,7 +148,7 @@ func importPublic(*cobra.Command, []string) error { vmIDstr := vmID.String() sc := &models.Sidecar{ - Name: subnetName, + Name: blockchainName, VM: vmType, Networks: map[string]models.NetworkData{ network.Name(): { @@ -151,7 +156,7 @@ func importPublic(*cobra.Command, []string) error { BlockchainID: blockchainID, }, }, - Subnet: subnetName, + Subnet: blockchainName, Version: constants.SidecarVersion, TokenName: constants.DefaultTokenName, TokenSymbol: constants.DefaultTokenSymbol, @@ -203,7 +208,7 @@ func importPublic(*cobra.Command, []string) error { return fmt.Errorf("failed creating the sidecar for import: %w", err) } - if err = app.WriteGenesisFile(subnetName, genBytes); err != nil { + if err = app.WriteGenesisFile(blockchainName, genBytes); err != nil { return err } diff --git a/cmd/blockchaincmd/join.go b/cmd/blockchaincmd/join.go new file mode 100644 index 000000000..4ce214aea --- /dev/null +++ b/cmd/blockchaincmd/join.go @@ -0,0 +1,381 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package blockchaincmd + +import ( + "context" + "errors" + "os" + "path/filepath" + + "github.com/ava-labs/avalanche-cli/pkg/application" + "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/constants" + "github.com/ava-labs/avalanche-cli/pkg/models" + "github.com/ava-labs/avalanche-cli/pkg/networkoptions" + "github.com/ava-labs/avalanche-cli/pkg/plugins" + "github.com/ava-labs/avalanche-cli/pkg/utils" + "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/platformvm" + "github.com/spf13/cobra" +) + +var ( + joinSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + networkoptions.Mainnet, + } + // path to avalanchego config file + avagoConfigPath string + // path to avalanchego plugin dir + pluginDir string + // path to avalanchego datadir dir + dataDir string + // if true, print the manual instructions to screen + printManual bool + // if true, doesn't ask for overwriting the config file + forceWrite bool + // for permissionless subnet only: how much subnet native token will be staked in the validator + stakeAmount uint64 +) + +// avalanche blockchain join +func newJoinCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "join [blockchainName]", + Short: "Configure your validator node to begin validating a new blockchain", + Long: `The subnet join command configures your validator node to begin validating a new Blockchain. + +To complete this process, you must have access to the machine running your validator. If the +CLI is running on the same machine as your validator, it can generate or update your node's +config file automatically. Alternatively, the command can print the necessary instructions +to update your node manually. To complete the validation process, the Subnet's admins must add +the NodeID of your validator to the Subnet's allow list by calling addValidator with your +NodeID. + +After you update your validator's config, you need to restart your validator manually. If +you provide the --avalanchego-config flag, this command attempts to edit the config file +at that path. + +This command currently only supports Blockchains deployed on the Fuji Testnet and Mainnet.`, + RunE: joinCmd, + Args: cobrautils.ExactArgs(1), + } + networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, joinSupportedNetworkOptions) + cmd.Flags().StringVar(&avagoConfigPath, "avalanchego-config", "", "file path of the avalanchego config file") + cmd.Flags().StringVar(&pluginDir, "plugin-dir", "", "file path of avalanchego's plugin directory") + cmd.Flags().StringVar(&dataDir, "data-dir", "", "path of avalanchego's data dir directory") + cmd.Flags().BoolVar(&printManual, "print", false, "if true, print the manual config without prompting") + cmd.Flags().StringVar(&nodeIDStr, "nodeID", "", "set the NodeID of the validator to check") + cmd.Flags().BoolVar(&forceWrite, "force-write", false, "if true, skip to prompt to overwrite the config file") + cmd.Flags().Uint64Var(&stakeAmount, "stake-amount", 0, "amount of tokens to stake on validator") + cmd.Flags().StringVar(&startTimeStr, "start-time", "", "start time that validator starts validating") + cmd.Flags().DurationVar(&duration, "staking-period", 0, "how long validator validates for after start time") + cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [fuji only]") + cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on fuji)") + cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses") + return cmd +} + +func joinCmd(_ *cobra.Command, args []string) error { + if printManual && (avagoConfigPath != "" || pluginDir != "") { + return errors.New("--print cannot be used with --avalanchego-config or --plugin-dir") + } + + chains, err := ValidateSubnetNameAndGetChains(args) + if err != nil { + return err + } + + blockchainName := chains[0] + + sc, err := app.LoadSidecar(blockchainName) + if err != nil { + return err + } + + network, err := networkoptions.GetNetworkFromCmdLineFlags( + app, + "", + globalNetworkFlags, + true, + false, + joinSupportedNetworkOptions, + "", + ) + if err != nil { + return err + } + + network.HandlePublicNetworkSimulation() + + subnetID := sc.Networks[network.Name()].SubnetID + if subnetID == ids.Empty { + return errNoSubnetID + } + subnetIDStr := subnetID.String() + + if printManual { + pluginDir = app.GetTmpPluginDir() + vmPath, err := plugins.CreatePlugin(app, sc.Name, pluginDir) + if err != nil { + return err + } + printJoinCmd(subnetIDStr, network, vmPath) + return nil + } + + // if **both** flags were set, nothing special needs to be done + // just check the following blocks + if avagoConfigPath == "" && pluginDir == "" { + // both flags are NOT set + const ( + choiceManual = "Manual" + choiceAutomatic = "Automatic" + ) + choice, err := app.Prompt.CaptureList( + "How would you like to update the avalanchego config?", + []string{choiceAutomatic, choiceManual}, + ) + if err != nil { + return err + } + if choice == choiceManual { + pluginDir = app.GetTmpPluginDir() + vmPath, err := plugins.CreatePlugin(app, sc.Name, pluginDir) + if err != nil { + return err + } + printJoinCmd(subnetIDStr, network, vmPath) + return nil + } + } + + // if choice is automatic, we just pass through this block + // or, pluginDir was set but not avagoConfigPath + // if **both** flags were set, this will be skipped... + if avagoConfigPath == "" { + avagoConfigPath, err = plugins.FindAvagoConfigPath() + if err != nil { + return err + } + if avagoConfigPath != "" { + ux.Logger.PrintToUser(logging.Bold.Wrap(logging.Green.Wrap("Found a config file at %s")), avagoConfigPath) + yes, err := app.Prompt.CaptureYesNo("Is this the file we should update?") + if err != nil { + return err + } + if yes { + ux.Logger.PrintToUser("Will use file at path %s to update the configuration", avagoConfigPath) + } else { + avagoConfigPath = "" + } + } + if avagoConfigPath == "" { + avagoConfigPath, err = app.Prompt.CaptureString("Path to your existing config file (or where it will be generated)") + if err != nil { + return err + } + } + } + + // ...but not this + avagoConfigPath, err := plugins.SanitizePath(avagoConfigPath) + if err != nil { + return err + } + + // avagoConfigPath was set but not pluginDir + // if **both** flags were set, this will be skipped... + if pluginDir == "" { + pluginDir, err = plugins.FindPluginDir() + if err != nil { + return err + } + if pluginDir != "" { + ux.Logger.PrintToUser(logging.Bold.Wrap(logging.Green.Wrap("Found the VM plugin directory at %s")), pluginDir) + yes, err := app.Prompt.CaptureYesNo("Is this where we should install the VM?") + if err != nil { + return err + } + if yes { + ux.Logger.PrintToUser("Will use plugin directory at %s to install the VM", pluginDir) + } else { + pluginDir = "" + } + } + if pluginDir == "" { + pluginDir, err = app.Prompt.CaptureString("Path to your avalanchego plugin dir (likely .avalanchego/plugins)") + if err != nil { + return err + } + } + } + + // ...but not this + pluginDir, err := plugins.SanitizePath(pluginDir) + if err != nil { + return err + } + + vmPath, err := plugins.CreatePlugin(app, sc.Name, pluginDir) + if err != nil { + return err + } + + ux.Logger.PrintToUser("VM binary written to %s", vmPath) + + if forceWrite { + if err := writeAvagoChainConfigFiles(app, dataDir, blockchainName, sc, network); err != nil { + return err + } + } + + subnetAvagoConfigFile := "" + if app.AvagoNodeConfigExists(blockchainName) { + subnetAvagoConfigFile = app.GetAvagoNodeConfigPath(blockchainName) + } + + if err := plugins.EditConfigFile( + app, + subnetIDStr, + network, + avagoConfigPath, + forceWrite, + subnetAvagoConfigFile, + ); err != nil { + return err + } + + return nil +} + +func writeAvagoChainConfigFiles( + app *application.Avalanche, + dataDir string, + blockchainName string, + sc models.Sidecar, + network models.Network, +) error { + if dataDir == "" { + dataDir = utils.UserHomePath(".avalanchego") + } + + subnetID := sc.Networks[network.Name()].SubnetID + if subnetID == ids.Empty { + return errNoSubnetID + } + subnetIDStr := subnetID.String() + blockchainID := sc.Networks[network.Name()].BlockchainID + + configsPath := filepath.Join(dataDir, "configs") + + subnetConfigsPath := filepath.Join(configsPath, "subnets") + subnetConfigPath := filepath.Join(subnetConfigsPath, subnetIDStr+".json") + if app.AvagoSubnetConfigExists(blockchainName) { + if err := os.MkdirAll(subnetConfigsPath, constants.DefaultPerms755); err != nil { + return err + } + subnetConfig, err := app.LoadRawAvagoSubnetConfig(blockchainName) + if err != nil { + return err + } + if err := os.WriteFile(subnetConfigPath, subnetConfig, constants.DefaultPerms755); err != nil { + return err + } + } else { + _ = os.RemoveAll(subnetConfigPath) + } + + if blockchainID != ids.Empty && app.ChainConfigExists(blockchainName) || app.NetworkUpgradeExists(blockchainName) { + chainConfigsPath := filepath.Join(configsPath, "chains", blockchainID.String()) + if err := os.MkdirAll(chainConfigsPath, constants.DefaultPerms755); err != nil { + return err + } + chainConfigPath := filepath.Join(chainConfigsPath, "config.json") + if app.ChainConfigExists(blockchainName) { + chainConfig, err := app.LoadRawChainConfig(blockchainName) + if err != nil { + return err + } + if err := os.WriteFile(chainConfigPath, chainConfig, constants.DefaultPerms755); err != nil { + return err + } + } else { + _ = os.RemoveAll(chainConfigPath) + } + networkUpgradesPath := filepath.Join(chainConfigsPath, "upgrade.json") + if app.NetworkUpgradeExists(blockchainName) { + networkUpgrades, err := app.LoadRawNetworkUpgrades(blockchainName) + if err != nil { + return err + } + if err := os.WriteFile(networkUpgradesPath, networkUpgrades, constants.DefaultPerms755); err != nil { + return err + } + } else { + _ = os.RemoveAll(networkUpgradesPath) + } + } + + return nil +} + +func checkIsValidating(subnetID ids.ID, nodeID ids.NodeID, pClient platformvm.Client) (bool, error) { + // first check if the node is already an accepted validator on the subnet + ctx := context.Background() + nodeIDs := []ids.NodeID{nodeID} + vals, err := pClient.GetCurrentValidators(ctx, subnetID, nodeIDs) + if err != nil { + return false, err + } + for _, v := range vals { + // strictly this is not needed, as we are providing the nodeID as param + // just a double check + if v.NodeID == nodeID { + return true, nil + } + } + return false, nil +} + +func printJoinCmd(subnetID string, network models.Network, vmPath string) { + msg := ` +To setup your node, you must do two things: + +1. Add your VM binary to your node's plugin directory +2. Update your node config to start validating the subnet + +To add the VM to your plugin directory, copy or scp from %s + +If you installed avalanchego with the install script, your plugin directory is likely +~/.avalanchego/plugins. + +If you start your node from the command line WITHOUT a config file (e.g. via command +line or systemd script), add the following flag to your node's startup command: + +--track-subnets=%s +(if the node already has a track-subnets config, append the new value by +comma-separating it). + +For example: +./build/avalanchego --network-id=%s --track-subnets=%s + +If you start the node via a JSON config file, add this to your config file: +track-subnets: %s + +NOTE: The flag --track-subnets is a replacement of the deprecated --whitelisted-subnets. +If the later is present in config, please rename it to track-subnets first. + +TIP: Try this command with the --avalanchego-config flag pointing to your config file, +this tool will try to update the file automatically (make sure it can write to it). + +After you update your config, you will need to restart your node for the changes to +take effect.` + + ux.Logger.PrintToUser(msg, vmPath, subnetID, network.NetworkIDFlagValue(), subnetID, subnetID) +} diff --git a/cmd/subnetcmd/join_test.go b/cmd/blockchaincmd/join_test.go similarity index 98% rename from cmd/subnetcmd/join_test.go rename to cmd/blockchaincmd/join_test.go index 900d00c46..00c9d6364 100644 --- a/cmd/subnetcmd/join_test.go +++ b/cmd/blockchaincmd/join_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "testing" diff --git a/cmd/subnetcmd/list.go b/cmd/blockchaincmd/list.go similarity index 92% rename from cmd/subnetcmd/list.go rename to cmd/blockchaincmd/list.go index 1ab3f1b5f..045a80c5f 100644 --- a/cmd/subnetcmd/list.go +++ b/cmd/blockchaincmd/list.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "os" @@ -21,15 +21,15 @@ import ( var deployed bool -// avalanche subnet list +// avalanche blockchain list func newListCmd() *cobra.Command { cmd := &cobra.Command{ Use: "list", - Short: "List all created Subnet configurations", - Long: `The subnet list command prints the names of all created Subnet configurations. Without any flags, -it prints some general, static information about the Subnet. With the --deployed flag, the command + Short: "List all created Blockchain configurations", + Long: `The blockchain list command prints the names of all created Blockchain configurations. Without any flags, +it prints some general, static information about the Blockchain. With the --deployed flag, the command shows additional information including the VMID, BlockchainID and SubnetID.`, - RunE: listSubnets, + RunE: listBlockchains, } cmd.Flags().BoolVar(&deployed, "deployed", false, "show additional deploy information") return cmd @@ -50,7 +50,7 @@ func (c subnetMatrix) Less(i, j int) bool { return strings.Compare(c[i][0], c[j][0]) == -1 } -func listSubnets(cmd *cobra.Command, args []string) error { +func listBlockchains(cmd *cobra.Command, args []string) error { if deployed { return listDeployInfo(cmd, args) } diff --git a/cmd/subnetcmd/promptOwners.go b/cmd/blockchaincmd/prompt_owners.go similarity index 99% rename from cmd/subnetcmd/promptOwners.go rename to cmd/blockchaincmd/prompt_owners.go index 3502dbb8d..5233890af 100644 --- a/cmd/subnetcmd/promptOwners.go +++ b/cmd/blockchaincmd/prompt_owners.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "fmt" diff --git a/cmd/subnetcmd/publish.go b/cmd/blockchaincmd/publish.go similarity index 93% rename from cmd/subnetcmd/publish.go rename to cmd/blockchaincmd/publish.go index 76f12805d..649fc7534 100644 --- a/cmd/subnetcmd/publish.go +++ b/cmd/blockchaincmd/publish.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "errors" @@ -40,12 +40,12 @@ var ( type newPublisherFunc func(string, string, string) subnet.Publisher -// avalanche subnet publish +// avalanche blockchain publish func newPublishCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "publish [subnetName]", - Short: "Publish the subnet's VM to a repository", - Long: `The subnet publish command publishes the Subnet's VM to a repository.`, + Use: "publish [blockchainName]", + Short: "Publish the blockchain's VM to a repository", + Long: `The blockchain publish command publishes the Blockchain's VM to a repository.`, RunE: publish, Args: cobrautils.ExactArgs(1), } @@ -68,15 +68,15 @@ func publish(_ *cobra.Command, args []string) error { if err != nil { return err } - subnetName := chains[0] - sc, err := app.LoadSidecar(subnetName) + blockchainName := chains[0] + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } if !isReadyToPublish(&sc) { return errSubnetNotDeployed } - return doPublish(&sc, subnetName, subnet.NewPublisher) + return doPublish(&sc, blockchainName, subnet.NewPublisher) } // isReadyToPublish currently means if deployed to fuji and/or main @@ -92,7 +92,7 @@ func isReadyToPublish(sc *models.Sidecar) bool { return false } -func doPublish(sc *models.Sidecar, subnetName string, publisherCreateFunc newPublisherFunc) (err error) { +func doPublish(sc *models.Sidecar, blockchainName string, publisherCreateFunc newPublisherFunc) (err error) { reposDir := app.GetReposDir() // iterate the reposDir to check what repos already exist locally // if nothing is found, prompt the user for an alias for a new repo @@ -111,7 +111,7 @@ func doPublish(sc *models.Sidecar, subnetName string, publisherCreateFunc newPub if !forceWrite && noRepoPath == "" { // if forceWrite is present, we don't need to check if it has been previously published, we just do - published, err := isAlreadyPublished(subnetName) + published, err := isAlreadyPublished(blockchainName) if err != nil { return err } @@ -168,7 +168,7 @@ func doPublish(sc *models.Sidecar, subnetName string, publisherCreateFunc newPub ux.Logger.PrintToUser( "The given --no-repo-path at %s did not exist; created it with permissions %o", noRepoPath, constants.DefaultPerms755) } - subnetFile := filepath.Join(noRepoPath, constants.SubnetDir, subnetName+constants.YAMLSuffix) + subnetFile := filepath.Join(noRepoPath, constants.SubnetDir, blockchainName+constants.YAMLSuffix) vmFile := filepath.Join(noRepoPath, constants.VMDir, vm.Alias+constants.YAMLSuffix) if !forceWrite { // do not automatically overwrite @@ -200,7 +200,7 @@ func doPublish(sc *models.Sidecar, subnetName string, publisherCreateFunc newPub } // TODO: if not published? New commit? Etc... - if err = publisher.Publish(repo, subnetName, vm.Alias, subnetYAML, vmYAML); err != nil { + if err = publisher.Publish(repo, blockchainName, vm.Alias, subnetYAML, vmYAML); err != nil { return err } @@ -209,8 +209,8 @@ func doPublish(sc *models.Sidecar, subnetName string, publisherCreateFunc newPub } // current simplistic approach: -// just search any folder names `subnetName` inside the reposDir's `subnets` folder -func isAlreadyPublished(subnetName string) (bool, error) { +// just search any folder names `blockchainName` inside the reposDir's `subnets` folder +func isAlreadyPublished(blockchainName string) (bool, error) { reposDir := app.GetReposDir() found := false @@ -220,7 +220,7 @@ func isAlreadyPublished(subnetName string) (bool, error) { if filepath.Base(path) == constants.VMDir { return filepath.SkipDir } - if !d.IsDir() && d.Name() == subnetName { + if !d.IsDir() && d.Name() == blockchainName { found = true } } diff --git a/cmd/subnetcmd/publish_test.go b/cmd/blockchaincmd/publish_test.go similarity index 99% rename from cmd/subnetcmd/publish_test.go rename to cmd/blockchaincmd/publish_test.go index 81dec15b9..99079a8e8 100644 --- a/cmd/subnetcmd/publish_test.go +++ b/cmd/blockchaincmd/publish_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "io" diff --git a/cmd/subnetcmd/removeValidator.go b/cmd/blockchaincmd/remove_validator.go similarity index 89% rename from cmd/subnetcmd/removeValidator.go rename to cmd/blockchaincmd/remove_validator.go index 5d89fcbbb..ab5af308e 100644 --- a/cmd/subnetcmd/removeValidator.go +++ b/cmd/blockchaincmd/remove_validator.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "errors" @@ -22,15 +22,20 @@ import ( "github.com/spf13/cobra" ) -var removeValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Fuji, networkoptions.Mainnet} +var removeValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + networkoptions.Mainnet, +} -// avalanche subnet removeValidator +// avalanche blockchain removeValidator func newRemoveValidatorCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "removeValidator [subnetName]", - Short: "Remove a permissioned validator from your subnet", - Long: `The subnet removeValidator command stops a whitelisted, subnet network validator from -validating your deployed Subnet. + Use: "removeValidator [blockchainName]", + Short: "Remove a permissioned validator from your blockchain's subnet", + Long: `The blockchain removeValidator command stops a whitelisted, subnet network validator from +validating your deployed Blockchain. To remove the validator from the Subnet's allow list, provide the validator's unique NodeID. You can bypass these prompts by providing the values with flags.`, @@ -57,7 +62,7 @@ func removeValidator(_ *cobra.Command, args []string) error { app, "", globalNetworkFlags, - false, + true, false, removeValidatorSupportedNetworkOptions, "", @@ -84,11 +89,11 @@ func removeValidator(_ *cobra.Command, args []string) error { if err != nil { return err } - subnetName := chains[0] + blockchainName := chains[0] switch network.Kind { case models.Local: - return removeFromLocal(subnetName) + return removeFromLocal(blockchainName) case models.Fuji: if !useLedger && keyName == "" { useLedger, keyName, err = prompts.GetKeyOrLedger(app.Prompt, constants.PayTxsFeesMsg, app.GetKeyDir(), false) @@ -106,7 +111,7 @@ func removeValidator(_ *cobra.Command, args []string) error { } // get keychain accesor - fee := network.GenesisParams().TxFee + fee := network.GenesisParams().TxFeeConfig.StaticFeeConfig.TxFee kc, err := keychain.GetKeychain(app, false, useLedger, ledgerAddresses, keyName, network, fee) if err != nil { return err @@ -114,7 +119,7 @@ func removeValidator(_ *cobra.Command, args []string) error { network.HandlePublicNetworkSimulation() - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } @@ -123,7 +128,6 @@ func removeValidator(_ *cobra.Command, args []string) error { if subnetID == ids.Empty { return errNoSubnetID } - transferSubnetOwnershipTxID := sc.Networks[network.Name()].TransferSubnetOwnershipTxID isPermissioned, controlKeys, threshold, err := txutils.GetOwners(network, subnetID) if err != nil { @@ -187,7 +191,6 @@ func removeValidator(_ *cobra.Command, args []string) error { controlKeys, subnetAuthKeys, subnetID, - transferSubnetOwnershipTxID, nodeID, ) if err != nil { @@ -197,7 +200,7 @@ func removeValidator(_ *cobra.Command, args []string) error { if err := SaveNotFullySignedTx( "Remove Validator", tx, - subnetName, + blockchainName, subnetAuthKeys, remainingSubnetAuthKeys, outputTxPath, @@ -210,8 +213,8 @@ func removeValidator(_ *cobra.Command, args []string) error { return err } -func removeFromLocal(subnetName string) error { - sc, err := app.LoadSidecar(subnetName) +func removeFromLocal(blockchainName string) error { + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/stats.go b/cmd/blockchaincmd/stats.go similarity index 92% rename from cmd/subnetcmd/stats.go rename to cmd/blockchaincmd/stats.go index 1b8be5774..61012744d 100644 --- a/cmd/subnetcmd/stats.go +++ b/cmd/blockchaincmd/stats.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "context" @@ -22,14 +22,19 @@ import ( "github.com/spf13/cobra" ) -var statsSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Fuji, networkoptions.Mainnet} +var statsSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + networkoptions.Mainnet, +} -// avalanche subnet stats +// avalanche blockchain stats func newStatsCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "stats [subnetName]", - Short: "Show validator statistics for the given subnet", - Long: `The subnet stats command prints validator statistics for the given Subnet.`, + Use: "stats [blockchainName]", + Short: "Show validator statistics for the given blockchain", + Long: `The blockchain stats command prints validator statistics for the given Blockchain.`, Args: cobrautils.ExactArgs(1), RunE: stats, } @@ -42,7 +47,7 @@ func stats(_ *cobra.Command, args []string) error { app, "", globalNetworkFlags, - false, + true, false, statsSupportedNetworkOptions, "", @@ -55,9 +60,9 @@ func stats(_ *cobra.Command, args []string) error { if err != nil { return err } - subnetName := chains[0] + blockchainName := chains[0] - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/stats_test.go b/cmd/blockchaincmd/stats_test.go similarity index 98% rename from cmd/subnetcmd/stats_test.go rename to cmd/blockchaincmd/stats_test.go index 3c0a44209..d012155d6 100644 --- a/cmd/subnetcmd/stats_test.go +++ b/cmd/blockchaincmd/stats_test.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "io" diff --git a/cmd/subnetcmd/upgradecmd/apply.go b/cmd/blockchaincmd/upgradecmd/apply.go similarity index 90% rename from cmd/subnetcmd/upgradecmd/apply.go rename to cmd/blockchaincmd/upgradecmd/apply.go index 3d0512a3a..207e05f9b 100644 --- a/cmd/subnetcmd/upgradecmd/apply.go +++ b/cmd/blockchaincmd/upgradecmd/apply.go @@ -56,12 +56,12 @@ var ( print bool ) -// avalanche subnet upgrade apply +// avalanche blockchain upgrade apply func newUpgradeApplyCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "apply [subnetName]", - Short: "Apply upgrade bytes onto subnet nodes", - Long: `Apply generated upgrade bytes to running Subnet nodes to trigger a network upgrade. + Use: "apply [blockchainName]", + Short: "Apply upgrade bytes onto blockchain nodes", + Long: `Apply generated upgrade bytes to running Blockchain nodes to trigger a network upgrade. For public networks (Fuji Testnet or Mainnet), to complete this process, you must have access to the machine running your validator. @@ -89,13 +89,13 @@ Refer to https://docs.avax.network/nodes/maintain/chain-config-flags#subnet-chai } func applyCmd(_ *cobra.Command, args []string) error { - subnetName := args[0] + blockchainName := args[0] - if !app.SubnetConfigExists(subnetName) { - return errors.New("subnet does not exist") + if !app.BlockchainConfigExists(blockchainName) { + return errors.New("blockchain does not exist") } - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return fmt.Errorf("unable to load sidecar: %w", err) } @@ -108,11 +108,11 @@ func applyCmd(_ *cobra.Command, args []string) error { switch networkToUpgrade { // update a locally running network case localDeployment: - return applyLocalNetworkUpgrade(subnetName, models.Local.String(), &sc) + return applyLocalNetworkUpgrade(blockchainName, models.Local.String(), &sc) case fujiDeployment: - return applyPublicNetworkUpgrade(subnetName, models.Fuji.String(), &sc) + return applyPublicNetworkUpgrade(blockchainName, models.Fuji.String(), &sc) case mainnetDeployment: - return applyPublicNetworkUpgrade(subnetName, models.Mainnet.String(), &sc) + return applyPublicNetworkUpgrade(blockchainName, models.Mainnet.String(), &sc) } return nil @@ -131,11 +131,11 @@ func applyCmd(_ *cobra.Command, args []string) error { // For a already deployed subnet, the supported scheme is to // save a snapshot, and to load the snapshot with the upgrade -func applyLocalNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar) error { +func applyLocalNetworkUpgrade(blockchainName, networkKey string, sc *models.Sidecar) error { if print { ux.Logger.PrintToUser("The --print flag is ignored on local networks. Continuing.") } - precmpUpgrades, strNetUpgrades, err := validateUpgrade(subnetName, networkKey, sc, force) + precmpUpgrades, strNetUpgrades, err := validateUpgrade(blockchainName, networkKey, sc, force) if err != nil { return err } @@ -184,7 +184,7 @@ func applyLocalNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar) defer cancel() // save a temporary snapshot - snapName := subnetName + tmpSnapshotInfix + time.Now().Format(timestampFormat) + snapName := blockchainName + tmpSnapshotInfix + time.Now().Format(timestampFormat) app.Log.Debug("saving temporary snapshot for upgrade bytes", zap.String("snapshot-name", snapName)) _, err = cli.SaveSnapshot(ctx, snapName, false) if err != nil { @@ -224,11 +224,12 @@ func applyLocalNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar) } ux.Logger.PrintToUser("The next upgrade will go into effect %s", time.Unix(nextUpgrade, 0).Local().Format(constants.TimeParseLayout)) ux.Logger.PrintToUser("") - if err := localnet.PrintEndpoints(ux.Logger.PrintToUser, subnetName); err != nil { + if err := localnet.PrintEndpoints(ux.Logger.PrintToUser, blockchainName); err != nil { return err } - return writeLockFile(precmpUpgrades, subnetName) + writeLockFile(precmpUpgrades, blockchainName) + return nil } return errors.New("unexpected network size of zero nodes") @@ -249,11 +250,10 @@ func applyLocalNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar) // // For public networks we therefore limit ourselves to just "apply" the upgrades // This also means we are *ignoring* the lock file here! -func applyPublicNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar) error { +func applyPublicNetworkUpgrade(blockchainName, networkKey string, sc *models.Sidecar) error { if print { blockchainIDstr := "" if sc.Networks != nil && - sc.Networks[networkKey] != (models.NetworkData{}) && sc.Networks[networkKey].BlockchainID != ids.Empty { blockchainIDstr = sc.Networks[networkKey].BlockchainID.String() } @@ -264,7 +264,7 @@ func applyPublicNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar ux.Logger.PrintToUser(" If you are using a different chain config dir for your node, use that one.") ux.Logger.PrintToUser("2. Create a directory with the blockchainID in the configured chain-config-dir (e.g. $HOME/.avalanchego/chains/%s) if doesn't already exist.", blockchainIDstr) ux.Logger.PrintToUser("3. Create an `upgrade.json` file in the blockchain directory with the content of your upgrade file.") - upgr, err := app.ReadUpgradeFile(subnetName) + upgr, err := app.ReadUpgradeFile(blockchainName) if err == nil { var prettyJSON bytes.Buffer if err := json.Indent(&prettyJSON, upgr, "", " "); err == nil { @@ -281,7 +281,7 @@ func applyPublicNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar ux.Logger.PrintToUser(" *************************************************************************************************************") return nil } - _, _, err := validateUpgrade(subnetName, networkKey, sc, force) + _, _, err := validateUpgrade(blockchainName, networkKey, sc, force) if err != nil { return err } @@ -309,24 +309,21 @@ func applyPublicNetworkUpgrade(subnetName, networkKey string, sc *models.Sidecar return fmt.Errorf("failed to create blockchain directory: %w", err) } - if err := binutils.CopyFile(app.GetUpgradeBytesFilePath(subnetName), destPath); err != nil { + if err := binutils.CopyFile(app.GetUpgradeBytesFilePath(blockchainName), destPath); err != nil { return fmt.Errorf("failed to install the upgrades path at the provided destination: %w", err) } ux.Logger.PrintToUser("Successfully installed upgrade file") return nil } -func validateUpgrade(subnetName, networkKey string, sc *models.Sidecar, skipPrompting bool) ([]params.PrecompileUpgrade, string, error) { +func validateUpgrade(blockchainName, networkKey string, sc *models.Sidecar, skipPrompting bool) ([]params.PrecompileUpgrade, string, error) { // if there's no entry in the Sidecar, we assume there hasn't been a deploy yet - if sc.Networks[networkKey] == (models.NetworkData{}) { - return nil, "", subnetNotYetDeployed() - } chainID := sc.Networks[networkKey].BlockchainID if chainID == ids.Empty { return nil, "", errors.New(ErrSubnetNotDeployedOutput) } // let's check update bytes actually exist - netUpgradeBytes, err := app.ReadUpgradeFile(subnetName) + netUpgradeBytes, err := app.ReadUpgradeFile(blockchainName) if err != nil { if err == os.ErrNotExist { ux.Logger.PrintToUser("No file with upgrade specs for the given subnet has been found") @@ -337,7 +334,7 @@ func validateUpgrade(subnetName, networkKey string, sc *models.Sidecar, skipProm } // read the lock file right away - lockUpgradeBytes, err := app.ReadLockUpgradeFile(subnetName) + lockUpgradeBytes, err := app.ReadLockUpgradeFile(blockchainName) if err != nil { // if the file doesn't exist, that's ok if !os.IsNotExist(err) { @@ -375,7 +372,7 @@ func subnetNotYetDeployed() error { return errSubnetNotYetDeployed } -func writeLockFile(precmpUpgrades []params.PrecompileUpgrade, subnetName string) error { +func writeLockFile(precmpUpgrades []params.PrecompileUpgrade, blockchainName string) { // it seems all went well this far, now we try to write/update the lock file // if this fails, we probably don't want to cause an error to the user? // so we are silently failing, just write a log entry @@ -386,11 +383,9 @@ func writeLockFile(precmpUpgrades []params.PrecompileUpgrade, subnetName string) if err != nil { app.Log.Debug("failed to marshaling upgrades lock file content", zap.Error(err)) } - if err := app.WriteLockUpgradeFile(subnetName, jsonBytes); err != nil { + if err := app.WriteLockUpgradeFile(blockchainName, jsonBytes); err != nil { app.Log.Debug("failed to write upgrades lock file", zap.Error(err)) } - - return nil } func validateUpgradeBytes(file, lockFile []byte, skipPrompting bool) ([]params.PrecompileUpgrade, error) { diff --git a/cmd/subnetcmd/upgradecmd/export.go b/cmd/blockchaincmd/upgradecmd/export.go similarity index 89% rename from cmd/subnetcmd/upgradecmd/export.go rename to cmd/blockchaincmd/upgradecmd/export.go index 8a72f29a0..623dcac5a 100644 --- a/cmd/subnetcmd/upgradecmd/export.go +++ b/cmd/blockchaincmd/upgradecmd/export.go @@ -13,10 +13,10 @@ import ( var force bool -// avalanche subnet upgrade import +// avalanche blockchain upgrade import func newUpgradeExportCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "export [subnetName]", + Use: "export [blockchainName]", Short: "Export the upgrade bytes file to a location of choice on disk", Long: `Export the upgrade bytes file to a location of choice on disk`, RunE: upgradeExportCmd, @@ -30,9 +30,9 @@ func newUpgradeExportCmd() *cobra.Command { } func upgradeExportCmd(_ *cobra.Command, args []string) error { - subnetName := args[0] - if !app.GenesisExists(subnetName) { - ux.Logger.PrintToUser("The provided subnet name %q does not exist", subnetName) + blockchainName := args[0] + if !app.GenesisExists(blockchainName) { + ux.Logger.PrintToUser("The provided subnet name %q does not exist", blockchainName) return nil } @@ -59,7 +59,7 @@ func upgradeExportCmd(_ *cobra.Command, args []string) error { } } - fileBytes, err := app.ReadUpgradeFile(subnetName) + fileBytes, err := app.ReadUpgradeFile(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/upgradecmd/generate.go b/cmd/blockchaincmd/upgradecmd/generate.go similarity index 94% rename from cmd/subnetcmd/upgradecmd/generate.go rename to cmd/blockchaincmd/upgradecmd/generate.go index 860715851..b4cd36dbd 100644 --- a/cmd/subnetcmd/upgradecmd/generate.go +++ b/cmd/blockchaincmd/upgradecmd/generate.go @@ -15,6 +15,7 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" "github.com/ava-labs/avalanche-cli/pkg/vm" + avalancheSDK "github.com/ava-labs/avalanche-cli/sdk/vm" "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/coreth/ethclient" @@ -33,14 +34,9 @@ import ( ) const ( - blockTimestampKey = "blockTimestamp" - feeConfigKey = "initialFeeConfig" - initialMintKey = "initialMint" - adminAddressesKey = "adminAddresses" - managerAddressesKey = "managerAddresses" - enabledAddressesKey = "enabledAddresses" - - enabledLabel = "enabled" + feeConfigKey = "initialFeeConfig" + initialMintKey = "initialMint" + managerLabel = "manager" adminLabel = "admin" @@ -51,14 +47,14 @@ const ( RewardManager = "Customize Fees Distribution" ) -var subnetName string +var blockchainName string -// avalanche subnet upgrade generate +// avalanche blockchain upgrade generate func newUpgradeGenerateCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "generate [subnetName]", - Short: "Generate the configuration file to upgrade subnet nodes", - Long: `The subnet upgrade generate command builds a new upgrade.json file to customize your Subnet. It + Use: "generate [blockchainName]", + Short: "Generate the configuration file to upgrade blockchain nodes", + Long: `The blockchain upgrade generate command builds a new upgrade.json file to customize your Blockchain. It guides the user through the process using an interactive wizard.`, RunE: upgradeGenerateCmd, Args: cobrautils.ExactArgs(1), @@ -67,9 +63,9 @@ guides the user through the process using an interactive wizard.`, } func upgradeGenerateCmd(_ *cobra.Command, args []string) error { - subnetName = args[0] - if !app.GenesisExists(subnetName) { - ux.Logger.PrintToUser("The provided subnet name %q does not exist", subnetName) + blockchainName = args[0] + if !app.GenesisExists(blockchainName) { + ux.Logger.PrintToUser("The provided subnet name %q does not exist", blockchainName) return nil } // print some warning/info message @@ -152,7 +148,7 @@ func upgradeGenerateCmd(_ *cobra.Command, args []string) error { return err } - return app.WriteUpgradeFile(subnetName, jsonBytes) + return app.WriteUpgradeFile(blockchainName, jsonBytes) } func queryActivationTimestamp() (time.Time, error) { @@ -194,7 +190,7 @@ func queryActivationTimestamp() (time.Time, error) { } func promptParams(precomp string, precompiles *[]params.PrecompileUpgrade) (bool, error) { - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return false, err } @@ -393,7 +389,7 @@ func GetFeeConfig(config params.ChainConfig, useDefault bool) ( setGasStep = "Set block gas cost step" ) - config.FeeConfig = vm.StarterFeeConfig + config.FeeConfig = avalancheSDK.StarterFeeConfig if useDefault { config.FeeConfig.GasLimit = vm.LowGasLimit @@ -421,13 +417,13 @@ func GetFeeConfig(config params.ChainConfig, useDefault bool) ( switch feeDefault { case lowOption: - vm.SetStandardGas(&config, vm.LowGasLimit, vm.LowTargetGas, useDynamicFees) + vm.SetStandardGas(&config.FeeConfig, vm.LowGasLimit, vm.LowTargetGas, useDynamicFees) return config, nil case mediumOption: - vm.SetStandardGas(&config, vm.MediumGasLimit, vm.MediumTargetGas, useDynamicFees) + vm.SetStandardGas(&config.FeeConfig, vm.MediumGasLimit, vm.MediumTargetGas, useDynamicFees) return config, err case highOption: - vm.SetStandardGas(&config, vm.HighGasLimit, vm.HighTargetGas, useDynamicFees) + vm.SetStandardGas(&config.FeeConfig, vm.HighGasLimit, vm.HighTargetGas, useDynamicFees) return config, err default: ux.Logger.PrintToUser("Customizing fee config") diff --git a/cmd/subnetcmd/upgradecmd/import.go b/cmd/blockchaincmd/upgradecmd/import.go similarity index 86% rename from cmd/subnetcmd/upgradecmd/import.go rename to cmd/blockchaincmd/upgradecmd/import.go index f2f04d4fc..9bcf66d6f 100644 --- a/cmd/subnetcmd/upgradecmd/import.go +++ b/cmd/blockchaincmd/upgradecmd/import.go @@ -15,10 +15,10 @@ var upgradeBytesFilePath string const upgradeBytesFilePathKey = "upgrade-filepath" -// avalanche subnet upgrade import +// avalanche blockchain upgrade import func newUpgradeImportCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "import [subnetName]", + Use: "import [blockchainName]", Short: "Import the upgrade bytes file into the local environment", Long: `Import the upgrade bytes file into the local environment`, RunE: upgradeImportCmd, @@ -31,9 +31,9 @@ func newUpgradeImportCmd() *cobra.Command { } func upgradeImportCmd(_ *cobra.Command, args []string) error { - subnetName := args[0] - if !app.GenesisExists(subnetName) { - ux.Logger.PrintToUser("The provided subnet name %q does not exist", subnetName) + blockchainName := args[0] + if !app.GenesisExists(blockchainName) { + ux.Logger.PrintToUser("The provided subnet name %q does not exist", blockchainName) return nil } @@ -57,5 +57,5 @@ func upgradeImportCmd(_ *cobra.Command, args []string) error { return fmt.Errorf("failed to read the provided upgrade file: %w", err) } - return app.WriteUpgradeFile(subnetName, fileBytes) + return app.WriteUpgradeFile(blockchainName, fileBytes) } diff --git a/cmd/subnetcmd/upgradecmd/print.go b/cmd/blockchaincmd/upgradecmd/print.go similarity index 80% rename from cmd/subnetcmd/upgradecmd/print.go rename to cmd/blockchaincmd/upgradecmd/print.go index d4cc4f4ab..4f8bc5dd4 100644 --- a/cmd/subnetcmd/upgradecmd/print.go +++ b/cmd/blockchaincmd/upgradecmd/print.go @@ -11,10 +11,10 @@ import ( "github.com/spf13/cobra" ) -// avalanche subnet upgrade import +// avalanche blockchain upgrade print func newUpgradePrintCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "print [subnetName]", + Use: "print [blockchainName]", Short: "Print the upgrade.json file content", Long: `Print the upgrade.json file content`, RunE: upgradePrintCmd, @@ -25,13 +25,13 @@ func newUpgradePrintCmd() *cobra.Command { } func upgradePrintCmd(_ *cobra.Command, args []string) error { - subnetName := args[0] - if !app.GenesisExists(subnetName) { - ux.Logger.PrintToUser("The provided subnet name %q does not exist", subnetName) + blockchainName := args[0] + if !app.GenesisExists(blockchainName) { + ux.Logger.PrintToUser("The provided subnet name %q does not exist", blockchainName) return nil } - fileBytes, err := app.ReadUpgradeFile(subnetName) + fileBytes, err := app.ReadUpgradeFile(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/upgradecmd/upgrade.go b/cmd/blockchaincmd/upgradecmd/upgrade.go similarity index 80% rename from cmd/subnetcmd/upgradecmd/upgrade.go rename to cmd/blockchaincmd/upgradecmd/upgrade.go index 002f0337c..05ea7ae4f 100644 --- a/cmd/subnetcmd/upgradecmd/upgrade.go +++ b/cmd/blockchaincmd/upgradecmd/upgrade.go @@ -10,13 +10,13 @@ import ( var app *application.Avalanche -// avalanche subnet vm +// avalanche blockchain upgrade func NewCmd(injectedApp *application.Avalanche) *cobra.Command { cmd := &cobra.Command{ Use: "upgrade", - Short: "Upgrade your Subnets", - Long: `The subnet upgrade command suite provides a collection of tools for -updating your developmental and deployed Subnets.`, + Short: "Upgrade your Blockchains", + Long: `The blockchain upgrade command suite provides a collection of tools for +updating your developmental and deployed Blockchains.`, RunE: cobrautils.CommandSuiteUsage, } app = injectedApp diff --git a/cmd/subnetcmd/upgradecmd/validate_test.go b/cmd/blockchaincmd/upgradecmd/validate_test.go similarity index 100% rename from cmd/subnetcmd/upgradecmd/validate_test.go rename to cmd/blockchaincmd/upgradecmd/validate_test.go diff --git a/cmd/subnetcmd/upgradecmd/vm.go b/cmd/blockchaincmd/upgradecmd/vm.go similarity index 95% rename from cmd/subnetcmd/upgradecmd/vm.go rename to cmd/blockchaincmd/upgradecmd/vm.go index 2cfb5b75c..6d2f3732f 100644 --- a/cmd/subnetcmd/upgradecmd/vm.go +++ b/cmd/blockchaincmd/upgradecmd/vm.go @@ -40,13 +40,13 @@ var ( binaryPathArg string ) -// avalanche subnet update vm +// avalanche blockchain upgrade vm func newUpgradeVMCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "vm [subnetName]", - Short: "Upgrade a subnet's binary", - Long: `The subnet upgrade vm command enables the user to upgrade their Subnet's VM binary. The command -can upgrade both local Subnets and publicly deployed Subnets on Fuji and Mainnet. + Use: "vm [blockchainName]", + Short: "Upgrade a blockchain's binary", + Long: `The blockchain upgrade vm command enables the user to upgrade their Blockchain's VM binary. The command +can upgrade both local Blockchains and publicly deployed Blockchains on Fuji and Mainnet. The command walks the user through an interactive wizard. The user can skip the wizard by providing command line flags.`, @@ -97,13 +97,13 @@ func upgradeVM(_ *cobra.Command, args []string) error { return errors.New("--print and --plugin-dir are mutually exclusive") } - subnetName := args[0] + blockchainName := args[0] - if !app.SubnetConfigExists(subnetName) { - return errors.New("subnet does not exist") + if !app.BlockchainConfigExists(blockchainName) { + return errors.New("blockchain does not exist") } - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return fmt.Errorf("unable to load sidecar: %w", err) } diff --git a/cmd/subnetcmd/upgradecmd/vm_test.go b/cmd/blockchaincmd/upgradecmd/vm_test.go similarity index 97% rename from cmd/subnetcmd/upgradecmd/vm_test.go rename to cmd/blockchaincmd/upgradecmd/vm_test.go index cf14037dd..c7bc12077 100644 --- a/cmd/subnetcmd/upgradecmd/vm_test.go +++ b/cmd/blockchaincmd/upgradecmd/vm_test.go @@ -288,13 +288,13 @@ func TestUpdateToCustomBin(t *testing.T) { assert := require.New(t) testDir := t.TempDir() - subnetName := "testSubnet" + blockchainName := "testSubnet" sc := models.Sidecar{ - Name: subnetName, + Name: blockchainName, VM: models.SubnetEvm, VMVersion: "v3.0.0", RPCVersion: 20, - Subnet: subnetName, + Subnet: blockchainName, } networkToUpgrade := futureDeployment @@ -325,7 +325,7 @@ func TestUpdateToCustomBin(t *testing.T) { assert.NoError(err) // check new binary exists and matches - placedBinaryPath := app.GetCustomVMPath(subnetName) + placedBinaryPath := app.GetCustomVMPath(blockchainName) assert.FileExists(placedBinaryPath) expectedHash, err := utils.GetSHA256FromDisk(binaryPath) assert.NoError(err) @@ -336,7 +336,7 @@ func TestUpdateToCustomBin(t *testing.T) { assert.Equal(expectedHash, actualHash) // check sidecar - diskSC, err := app.LoadSidecar(subnetName) + diskSC, err := app.LoadSidecar(blockchainName) assert.NoError(err) assert.Equal(models.VMTypeFromString(models.CustomVM), diskSC.VM) assert.Empty(diskSC.VMVersion) diff --git a/cmd/subnetcmd/validators.go b/cmd/blockchaincmd/validators.go similarity index 85% rename from cmd/subnetcmd/validators.go rename to cmd/blockchaincmd/validators.go index 0c3815cde..8d18458b5 100644 --- a/cmd/subnetcmd/validators.go +++ b/cmd/blockchaincmd/validators.go @@ -1,7 +1,7 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "errors" @@ -19,15 +19,20 @@ import ( "github.com/spf13/cobra" ) -var validatorsSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Fuji, networkoptions.Mainnet, networkoptions.Cluster, networkoptions.Devnet} +var validatorsSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + networkoptions.Mainnet, +} -// avalanche subnet validators +// avalanche blockchain validators func newValidatorsCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "validators [subnetName]", - Short: "List a subnet's validators", - Long: `The subnet validators command lists the validators of a subnet and provides -severarl statistics about them.`, + Use: "validators [blockchainName]", + Short: "List subnets validators of a blockchain", + Long: `The blockchain validators command lists the validators of a blockchain's subnet and provides +several statistics about them.`, RunE: printValidators, Args: cobrautils.ExactArgs(1), } @@ -36,23 +41,23 @@ severarl statistics about them.`, } func printValidators(_ *cobra.Command, args []string) error { - subnetName := args[0] + blockchainName := args[0] network, err := networkoptions.GetNetworkFromCmdLineFlags( app, "", globalNetworkFlags, - false, + true, false, validatorsSupportedNetworkOptions, - subnetName, + blockchainName, ) if err != nil { return err } // get the subnetID - sc, err := app.LoadSidecar(subnetName) + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } diff --git a/cmd/subnetcmd/vmid.go b/cmd/blockchaincmd/vmid.go similarity index 83% rename from cmd/subnetcmd/vmid.go rename to cmd/blockchaincmd/vmid.go index 0bb16cbe8..4ab0684d6 100644 --- a/cmd/subnetcmd/vmid.go +++ b/cmd/blockchaincmd/vmid.go @@ -1,6 +1,6 @@ // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. -package subnetcmd +package blockchaincmd import ( "fmt" @@ -11,12 +11,12 @@ import ( "github.com/spf13/cobra" ) -// avalanche subnet create +// avalanche blockchain vmid func vmidCmd() *cobra.Command { cmd := &cobra.Command{ Use: "vmid [vmName]", Short: "Prints the VMID of a VM", - Long: `The subnet vmid command prints the virtual machine ID (VMID) for the given Subnet.`, + Long: `The blockchain vmid command prints the virtual machine ID (VMID) for the given Blockchain.`, Args: cobrautils.ExactArgs(1), RunE: printVMID, } diff --git a/cmd/configcmd/singleNode.go b/cmd/configcmd/single_node.go similarity index 100% rename from cmd/configcmd/singleNode.go rename to cmd/configcmd/single_node.go diff --git a/cmd/configcmd/snapshotsAutoSave.go b/cmd/configcmd/snapshots_auto_save.go similarity index 100% rename from cmd/configcmd/snapshotsAutoSave.go rename to cmd/configcmd/snapshots_auto_save.go diff --git a/cmd/contractcmd/deployERC20.go b/cmd/contractcmd/deploy_erc20.go similarity index 74% rename from cmd/contractcmd/deployERC20.go rename to cmd/contractcmd/deploy_erc20.go index f3b7b9242..eacce6040 100644 --- a/cmd/contractcmd/deployERC20.go +++ b/cmd/contractcmd/deploy_erc20.go @@ -3,15 +3,14 @@ package contractcmd import ( - "fmt" "math/big" - cmdflags "github.com/ava-labs/avalanche-cli/cmd/flags" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" "github.com/ava-labs/avalanche-cli/pkg/prompts" "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ethereum/go-ethereum/common" "github.com/spf13/cobra" @@ -20,10 +19,11 @@ import ( type DeployERC20Flags struct { Network networkoptions.NetworkFlags PrivateKeyFlags contract.PrivateKeyFlags - chainFlags contract.ChainFlags + chainFlags contract.ChainSpec symbol string funded string supply uint64 + rpcEndpoint string } var ( @@ -45,17 +45,12 @@ func newDeployERC20Cmd() *cobra.Command { Args: cobrautils.ExactArgs(0), } networkoptions.AddNetworkFlagsToCmd(cmd, &deployERC20Flags.Network, true, deployERC20SupportedNetworkOptions) - contract.AddPrivateKeyFlagsToCmd(cmd, &deployERC20Flags.PrivateKeyFlags, "as contract deployer") - contract.AddChainFlagsToCmd( - cmd, - &deployERC20Flags.chainFlags, - "deploy the ERC20 contract", - "", - "", - ) + deployERC20Flags.PrivateKeyFlags.AddToCmd(cmd, "as contract deployer") + deployERC20Flags.chainFlags.AddToCmd(cmd, "deploy the ERC20 contract", true) cmd.Flags().StringVar(&deployERC20Flags.symbol, "symbol", "", "set the token symbol") cmd.Flags().Uint64Var(&deployERC20Flags.supply, "supply", 0, "set the token supply") cmd.Flags().StringVar(&deployERC20Flags.funded, "funded", "", "set the funded address") + cmd.Flags().StringVar(&deployERC20Flags.rpcEndpoint, "rpc", "", "deploy the contract into the given rpc endpoint") return cmd } @@ -72,51 +67,45 @@ func deployERC20(_ *cobra.Command, _ []string) error { if err != nil { return err } - // flags exclusiveness - if !cmdflags.EnsureMutuallyExclusive([]bool{ - deployERC20Flags.chainFlags.SubnetName != "", - deployERC20Flags.chainFlags.CChain, - }) { - return fmt.Errorf("--subnet and --c-chain are mutually exclusive flags") + if err := deployERC20Flags.chainFlags.CheckMutuallyExclusiveFields(); err != nil { + return err } - if deployERC20Flags.chainFlags.SubnetName == "" && !deployERC20Flags.chainFlags.CChain { - subnetNames, err := app.GetSubnetNamesOnNetwork(network) - if err != nil { - return err - } + if !deployERC20Flags.chainFlags.Defined() { prompt := "Where do you want to Deploy the ERC-20 Token?" - cancel, _, _, cChain, subnetName, err := prompts.PromptChain( - app.Prompt, + if cancel, err := contract.PromptChain( + app, + network, prompt, - subnetNames, + false, + "", true, + &deployERC20Flags.chainFlags, + ); cancel || err != nil { + return err + } + } + if deployERC20Flags.rpcEndpoint == "" { + deployERC20Flags.rpcEndpoint, _, err = contract.GetBlockchainEndpoints( + app, + network, + deployERC20Flags.chainFlags, true, false, - "", ) - if cancel { - return nil - } - if err == nil { - deployERC20Flags.chainFlags.SubnetName = subnetName - deployERC20Flags.chainFlags.CChain = cChain + if err != nil { + return err } + ux.Logger.PrintToUser(logging.Yellow.Wrap("RPC Endpoint: %s"), deployERC20Flags.rpcEndpoint) } genesisAddress, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( app, network, - deployERC20Flags.chainFlags.SubnetName, - deployERC20Flags.chainFlags.CChain, - "", + deployERC20Flags.chainFlags, ) if err != nil { return err } - privateKey, err := contract.GetPrivateKeyFromFlags( - app, - deployERC20Flags.PrivateKeyFlags, - genesisPrivateKey, - ) + privateKey, err := deployERC20Flags.PrivateKeyFlags.GetPrivateKey(app, genesisPrivateKey) if err != nil { return err } @@ -167,17 +156,8 @@ func deployERC20(_ *cobra.Command, _ []string) error { return err } } - rpcURL, err := contract.GetRPCURL( - app, - network, - deployERC20Flags.chainFlags.SubnetName, - deployERC20Flags.chainFlags.CChain, - ) - if err != nil { - return err - } address, err := contract.DeployERC20( - rpcURL, + deployERC20Flags.rpcEndpoint, privateKey, deployERC20Flags.symbol, common.HexToAddress(deployERC20Flags.funded), diff --git a/cmd/interchaincmd/tokentransferrercmd/deploy.go b/cmd/interchaincmd/tokentransferrercmd/deploy.go index eede624dc..5987392ab 100644 --- a/cmd/interchaincmd/tokentransferrercmd/deploy.go +++ b/cmd/interchaincmd/tokentransferrercmd/deploy.go @@ -5,32 +5,48 @@ package tokentransferrercmd import ( _ "embed" "fmt" + "math/big" + "time" cmdflags "github.com/ava-labs/avalanche-cli/cmd/flags" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/ictt" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" + "github.com/ava-labs/avalanche-cli/pkg/precompiles" "github.com/ava-labs/avalanche-cli/pkg/prompts" - "github.com/ava-labs/avalanche-cli/pkg/teleporter" "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/spf13/cobra" ) type HomeFlags struct { - chainFlags contract.ChainFlags - homeAddress string - native bool - erc20Address string + chainFlags contract.ChainSpec + homeAddress string + native bool + erc20Address string + privateKeyFlags contract.PrivateKeyFlags + RPCEndpoint string +} + +type RemoteFlags struct { + chainFlags contract.ChainSpec + native bool + removeMinterAdmin bool + privateKeyFlags contract.PrivateKeyFlags + RPCEndpoint string + Decimals uint8 } type DeployFlags struct { Network networkoptions.NetworkFlags homeFlags HomeFlags - remoteFlags contract.ChainFlags + remoteFlags RemoteFlags version string } @@ -53,24 +69,31 @@ func NewDeployCmd() *cobra.Command { Args: cobrautils.ExactArgs(0), } networkoptions.AddNetworkFlagsToCmd(cmd, &deployFlags.Network, true, deploySupportedNetworkOptions) - contract.AddChainFlagsToCmd( - cmd, - &deployFlags.homeFlags.chainFlags, - "set the Transferrer's Home Chain", - "home-subnet", + deployFlags.homeFlags.chainFlags.SetFlagNames( + "home-blockchain", "c-chain-home", + "", ) - contract.AddChainFlagsToCmd( - cmd, - &deployFlags.remoteFlags, - "set the Transferrer's Remote Chain", - "remote-subnet", + deployFlags.homeFlags.chainFlags.AddToCmd(cmd, "set the Transferrer's Home Chain", false) + deployFlags.remoteFlags.chainFlags.SetFlagNames( + "remote-blockchain", "c-chain-remote", + "", ) + deployFlags.remoteFlags.chainFlags.AddToCmd(cmd, "set the Transferrer's Remote Chain", false) cmd.Flags().BoolVar(&deployFlags.homeFlags.native, "deploy-native-home", false, "deploy a Transferrer Home for the Chain's Native Token") cmd.Flags().StringVar(&deployFlags.homeFlags.erc20Address, "deploy-erc20-home", "", "deploy a Transferrer Home for the given Chain's ERC20 Token") cmd.Flags().StringVar(&deployFlags.homeFlags.homeAddress, "use-home", "", "use the given Transferrer's Home Address") cmd.Flags().StringVar(&deployFlags.version, "version", "", "tag/branch/commit of Avalanche InterChain Token Transfer to be used (defaults to main branch)") + cmd.Flags().BoolVar(&deployFlags.remoteFlags.native, "deploy-native-remote", false, "deploy a Transferrer Remote for the Chain's Native Token") + cmd.Flags().BoolVar(&deployFlags.remoteFlags.removeMinterAdmin, "remove-minter-admin", false, "remove the native minter precompile admin found on remote blockchain genesis") + deployFlags.homeFlags.privateKeyFlags.SetFlagNames("home-private-key", "home-key", "home-genesis-key") + deployFlags.homeFlags.privateKeyFlags.AddToCmd(cmd, "to deploy Transferrer Home") + deployFlags.remoteFlags.privateKeyFlags.SetFlagNames("remote-private-key", "remote-key", "remote-genesis-key") + deployFlags.remoteFlags.privateKeyFlags.AddToCmd(cmd, "to deploy Transferrer Remote") + cmd.Flags().StringVar(&deployFlags.homeFlags.RPCEndpoint, "home-rpc", "", "use the given RPC URL to connect to the home blockchain") + cmd.Flags().StringVar(&deployFlags.remoteFlags.RPCEndpoint, "remote-rpc", "", "use the given RPC URL to connect to the remote blockchain") + cmd.Flags().Uint8Var(&deployFlags.remoteFlags.Decimals, "remote-token-decimals", 0, "use the given number of token decimals for the Transferrer Remote [defaults to token home's decimals (18 for a new wrapped native home token)]") return cmd } @@ -98,8 +121,8 @@ func CallDeploy(_ []string, flags DeployFlags) error { } // flags exclusiveness - if !cmdflags.EnsureMutuallyExclusive([]bool{flags.homeFlags.chainFlags.SubnetName != "", flags.homeFlags.chainFlags.CChain}) { - return fmt.Errorf("--home-subnet and --c-chain-home are mutually exclusive flags") + if err := flags.homeFlags.chainFlags.CheckMutuallyExclusiveFields(); err != nil { + return err } if !cmdflags.EnsureMutuallyExclusive([]bool{ flags.homeFlags.homeAddress != "", @@ -108,23 +131,31 @@ func CallDeploy(_ []string, flags DeployFlags) error { }) { return fmt.Errorf("--deploy-native-home, --deploy-erc20-home, and --use-home are mutually exclusive flags") } - if !cmdflags.EnsureMutuallyExclusive([]bool{flags.remoteFlags.SubnetName != "", flags.remoteFlags.CChain}) { - return fmt.Errorf("--remote-subnet and --c-chain-remote are mutually exclusive flags") + if err := flags.remoteFlags.chainFlags.CheckMutuallyExclusiveFields(); err != nil { + return err } // Home Chain Prompts - if flags.homeFlags.chainFlags.SubnetName == "" && !flags.homeFlags.chainFlags.CChain { + if !flags.homeFlags.chainFlags.Defined() { prompt := "Where is the Token origin?" - if cancel, err := promptChain(prompt, network, false, "", &flags.homeFlags.chainFlags); err != nil { + if cancel, err := contract.PromptChain(app, network, prompt, false, "", false, &flags.homeFlags.chainFlags); err != nil { return err } else if cancel { return nil } } + homeRPCEndpoint := flags.homeFlags.RPCEndpoint + if homeRPCEndpoint == "" { + homeRPCEndpoint, _, err = contract.GetBlockchainEndpoints(app, network, flags.homeFlags.chainFlags, true, false) + if err != nil { + return err + } + ux.Logger.PrintToUser(logging.Yellow.Wrap("Home RPC Endpoint: %s"), homeRPCEndpoint) + } // Home Chain Validations - if flags.homeFlags.chainFlags.SubnetName != "" { - if err := validateSubnet(network, flags.homeFlags.chainFlags.SubnetName); err != nil { + if flags.homeFlags.chainFlags.BlockchainName != "" { + if err := validateSubnet(network, flags.homeFlags.chainFlags.BlockchainName); err != nil { return err } } @@ -132,21 +163,21 @@ func CallDeploy(_ []string, flags DeployFlags) error { // Home Contract Prompts if flags.homeFlags.homeAddress == "" && flags.homeFlags.erc20Address == "" && !flags.homeFlags.native { nativeTokenSymbol, err := getNativeTokenSymbol( - flags.homeFlags.chainFlags.SubnetName, + flags.homeFlags.chainFlags.BlockchainName, flags.homeFlags.chainFlags.CChain, ) if err != nil { return err } prompt := "What kind of token do you want to be able to transfer?" - popularOption := "A popular token (e.g. AVAX, USDC, WAVAX, ...) (recommended)" + popularOption := "A popular token (e.g. WAVAX, USDC, ...) (recommended)" homeDeployedOption := "A token that already has a Home deployed (recommended)" deployNewHomeOption := "Deploy a new Home for the token" explainOption := "Explain the difference" goBackOption := "Go Back" homeChain := "C-Chain" if !flags.homeFlags.chainFlags.CChain { - homeChain = flags.homeFlags.chainFlags.SubnetName + homeChain = flags.homeFlags.chainFlags.BlockchainName } popularTokensInfo, err := GetPopularTokensInfo(network, homeChain) if err != nil { @@ -184,6 +215,11 @@ func CallDeploy(_ []string, flags DeployFlags) error { if option == goBackOption { continue } + p := utils.Find(popularTokensInfo, func(p PopularTokenInfo) bool { return p.Desc() == option }) + if p == nil { + return fmt.Errorf("expected to have found a popular token from option") + } + flags.homeFlags.homeAddress = p.TransferrerHomeAddress case homeDeployedOption: addr, err := app.Prompt.CaptureAddress( "Enter the address of the Home", @@ -218,9 +254,9 @@ func CallDeploy(_ []string, flags DeployFlags) error { ux.Logger.PrintToUser("There already is a Token Home for %s deployed on %s.", p.TokenName, homeChain) ux.Logger.PrintToUser("") ux.Logger.PrintToUser("Home Address: %s", p.TransferrerHomeAddress) - deployANewHupOption := "Yes, use the existing Home (recommended)" - useTheExistingHomeOption := "No, deploy my own Home" - options := []string{deployANewHupOption, useTheExistingHomeOption, explainOption} + useTheExistingHomeOption := "Yes, use the existing Home (recommended)" + deployANewHupOption := "No, deploy my own Home" + options := []string{useTheExistingHomeOption, deployANewHupOption, explainOption} option, err := app.Prompt.CaptureList( "Do you want to use the existing Home?", options, @@ -268,6 +304,43 @@ func CallDeploy(_ []string, flags DeployFlags) error { } } + var ( + homeKey string + homeKeyAddress string + ) + if flags.homeFlags.homeAddress == "" { + genesisAddress, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( + app, + network, + flags.homeFlags.chainFlags, + ) + if err != nil { + return err + } + homeKey, err = flags.homeFlags.privateKeyFlags.GetPrivateKey(app, genesisPrivateKey) + if err != nil { + return err + } + if homeKey == "" { + homeKey, err = prompts.PromptPrivateKey( + app.Prompt, + "pay for home deploy fees", + app.GetKeyDir(), + app.GetKey, + genesisAddress, + genesisPrivateKey, + ) + if err != nil { + return err + } + } + pk, err := crypto.HexToECDSA(homeKey) + if err != nil { + return err + } + homeKeyAddress = crypto.PubkeyToAddress(pk.PublicKey).Hex() + } + // Home Contract Validations if flags.homeFlags.homeAddress != "" { if err := prompts.ValidateAddress(flags.homeFlags.homeAddress); err != nil { @@ -281,31 +354,129 @@ func CallDeploy(_ []string, flags DeployFlags) error { } // Remote Chain Prompts - if !flags.remoteFlags.CChain && flags.remoteFlags.SubnetName == "" { + if !flags.remoteFlags.chainFlags.Defined() { prompt := "Where should the token be available as an ERC-20?" - if cancel, err := promptChain(prompt, network, flags.homeFlags.chainFlags.CChain, flags.homeFlags.chainFlags.SubnetName, &flags.remoteFlags); err != nil { + if flags.remoteFlags.native { + prompt = "Where should the token be available as a Native Token?" + } + if cancel, err := contract.PromptChain( + app, + network, + prompt, + flags.homeFlags.chainFlags.CChain, + flags.homeFlags.chainFlags.BlockchainName, + false, + &flags.remoteFlags.chainFlags, + ); err != nil { return err } else if cancel { return nil } } + remoteRPCEndpoint := flags.remoteFlags.RPCEndpoint + if remoteRPCEndpoint == "" { + remoteRPCEndpoint, _, err = contract.GetBlockchainEndpoints(app, network, flags.remoteFlags.chainFlags, true, false) + if err != nil { + return err + } + ux.Logger.PrintToUser(logging.Yellow.Wrap("Remote RPC Endpoint: %s"), remoteRPCEndpoint) + } + + genesisAddress, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( + app, + network, + flags.remoteFlags.chainFlags, + ) + if err != nil { + return err + } + remoteKey, err := flags.remoteFlags.privateKeyFlags.GetPrivateKey(app, genesisPrivateKey) + if err != nil { + return err + } + if remoteKey == "" { + remoteKey, err = prompts.PromptPrivateKey( + app.Prompt, + "pay for home deploy fees", + app.GetKeyDir(), + app.GetKey, + genesisAddress, + genesisPrivateKey, + ) + if err != nil { + return err + } + } + pk, err := crypto.HexToECDSA(remoteKey) + if err != nil { + return err + } + remoteKeyAddress := crypto.PubkeyToAddress(pk.PublicKey).Hex() // Remote Chain Validations - if flags.remoteFlags.SubnetName != "" { - if err := validateSubnet(network, flags.remoteFlags.SubnetName); err != nil { + if flags.remoteFlags.chainFlags.BlockchainName != "" { + if err := validateSubnet(network, flags.remoteFlags.chainFlags.BlockchainName); err != nil { return err } - if flags.remoteFlags.SubnetName == flags.homeFlags.chainFlags.SubnetName { + if flags.remoteFlags.chainFlags.BlockchainName == flags.homeFlags.chainFlags.BlockchainName { return fmt.Errorf("trying to make an Transferrer were home and remote are on the same subnet") } } - if flags.remoteFlags.CChain && flags.homeFlags.chainFlags.CChain { + if flags.remoteFlags.chainFlags.CChain && flags.homeFlags.chainFlags.CChain { return fmt.Errorf("trying to make an Transferrer were home and remote are on the same subnet") } + // Checkout minter availability for native remote before doing something else + remoteBlockchainDesc, err := contract.GetBlockchainDesc(flags.remoteFlags.chainFlags) + if err != nil { + return err + } + var ( + remoteMinterManagerPrivKey, remoteMinterManagerAddress string + remoteMinterManagerIsAdmin bool + ) + if flags.remoteFlags.native { + var remoteMinterAdminFound, remoteManagedMinterAdmin bool + remoteMinterAdminFound, remoteManagedMinterAdmin, _, remoteMinterManagerAddress, remoteMinterManagerPrivKey, err = contract.GetEVMSubnetGenesisNativeMinterAdmin( + app, + network, + flags.remoteFlags.chainFlags, + ) + if err != nil { + return err + } + if !remoteManagedMinterAdmin { + remoteMinterAdminAddress := remoteMinterManagerAddress + var remoteMinterManagerFound, remoteManagedMinterManager bool + remoteMinterManagerFound, remoteManagedMinterManager, _, remoteMinterManagerAddress, remoteMinterManagerPrivKey, err = contract.GetEVMSubnetGenesisNativeMinterManager( + app, + network, + flags.remoteFlags.chainFlags, + ) + if err != nil { + return err + } + if !remoteMinterManagerFound { + return fmt.Errorf("there is no native minter precompile admin or manager on %s", remoteBlockchainDesc) + } + if !remoteManagedMinterManager { + if remoteMinterAdminFound { + ux.Logger.PrintToUser("no managed key found for native minter admin %s on %s. add a CLI key for it using 'avalanche key create --file'", remoteMinterAdminAddress, remoteBlockchainDesc) + } + return fmt.Errorf("no managed key found for native minter manager %s on %s. add a CLI key for it using 'avalanche key create --file'", remoteMinterManagerAddress, remoteBlockchainDesc) + } + } else { + remoteMinterManagerIsAdmin = true + } + } + // Setup Contracts ux.Logger.PrintToUser("Downloading Avalanche InterChain Token Transfer Contracts") - if err := ictt.DownloadRepo(app, flags.version); err != nil { + version := constants.ICTTVersion + if flags.version != "" { + version = flags.version + } + if err := ictt.DownloadRepo(app, version); err != nil { return err } ux.Logger.PrintToUser("Compiling Avalanche InterChain Token Transfer") @@ -319,79 +490,47 @@ func CallDeploy(_ []string, flags DeployFlags) error { if err != nil { return err } - var ( - homeAddress common.Address - tokenSymbol string - tokenName string - tokenDecimals uint8 - tokenAddress common.Address - ) + var homeAddress common.Address // TODO: need registry address, manager address, private key for the home chain (academy for fuji) - homeEndpoint, _, homeBlockchainID, _, homeRegistryAddress, homeKey, err := teleporter.GetSubnetParams( - app, - network, - flags.homeFlags.chainFlags.SubnetName, - flags.homeFlags.chainFlags.CChain, - ) + homeBlockchainID, err := contract.GetBlockchainID(app, network, flags.homeFlags.chainFlags) + if err != nil { + return err + } + homeRegistryAddress, _, err := contract.GetICMInfo(app, network, flags.homeFlags.chainFlags, true, false, true) if err != nil { return err } if flags.homeFlags.homeAddress != "" { homeAddress = common.HexToAddress(flags.homeFlags.homeAddress) - endpointKind, err := ictt.GetEndpointKind(homeEndpoint, homeAddress) - if err != nil { - return err - } - switch endpointKind { - case ictt.ERC20TokenHome: - tokenAddress, err = ictt.ERC20TokenHomeGetTokenAddress(homeEndpoint, homeAddress) - if err != nil { - return err - } - case ictt.NativeTokenHome: - tokenAddress, err = ictt.NativeTokenHomeGetTokenAddress(homeEndpoint, homeAddress) - if err != nil { - return err - } - default: - return fmt.Errorf("unsupported ictt endpoint kind %d", endpointKind) - } - tokenSymbol, tokenName, tokenDecimals, err = ictt.GetTokenParams( - homeEndpoint, - tokenAddress.Hex(), - ) - if err != nil { - return err - } } if flags.homeFlags.erc20Address != "" { - tokenAddress = common.HexToAddress(flags.homeFlags.erc20Address) - tokenSymbol, tokenName, tokenDecimals, err = ictt.GetTokenParams( - homeEndpoint, - tokenAddress.Hex(), + tokenHomeAddress := common.HexToAddress(flags.homeFlags.erc20Address) + tokenHomeDecimals, err := ictt.GetTokenDecimals( + homeRPCEndpoint, + tokenHomeAddress, ) if err != nil { return err } homeAddress, err = ictt.DeployERC20Home( icttSrcDir, - homeEndpoint, - homeKey.PrivKeyHex(), + homeRPCEndpoint, + homeKey, common.HexToAddress(homeRegistryAddress), - common.HexToAddress(homeKey.C()), - tokenAddress, - tokenDecimals, + common.HexToAddress(homeKeyAddress), + tokenHomeAddress, + tokenHomeDecimals, ) if err != nil { return err } - ux.Logger.PrintToUser("Home Deployed to %s", homeEndpoint) + ux.Logger.PrintToUser("Home Deployed to %s", homeRPCEndpoint) ux.Logger.PrintToUser("Home Address: %s", homeAddress) ux.Logger.PrintToUser("") } if flags.homeFlags.native { nativeTokenSymbol, err := getNativeTokenSymbol( - flags.homeFlags.chainFlags.SubnetName, + flags.homeFlags.chainFlags.BlockchainName, flags.homeFlags.chainFlags.CChain, ) if err != nil { @@ -399,75 +538,254 @@ func CallDeploy(_ []string, flags DeployFlags) error { } wrappedNativeTokenAddress, err := ictt.DeployWrappedNativeToken( icttSrcDir, - homeEndpoint, - homeKey.PrivKeyHex(), + homeRPCEndpoint, + homeKey, nativeTokenSymbol, ) if err != nil { return err } - tokenSymbol, tokenName, tokenDecimals, err = ictt.GetTokenParams( - homeEndpoint, - wrappedNativeTokenAddress.Hex(), - ) - if err != nil { - return err - } - ux.Logger.PrintToUser("Wrapped Native Token Deployed to %s", homeEndpoint) - ux.Logger.PrintToUser("%s Address: %s", tokenSymbol, wrappedNativeTokenAddress) + ux.Logger.PrintToUser("Wrapped Native Token Deployed to %s", homeRPCEndpoint) + ux.Logger.PrintToUser("%s Address: %s", nativeTokenSymbol, wrappedNativeTokenAddress) ux.Logger.PrintToUser("") homeAddress, err = ictt.DeployNativeHome( icttSrcDir, - homeEndpoint, - homeKey.PrivKeyHex(), + homeRPCEndpoint, + homeKey, common.HexToAddress(homeRegistryAddress), - common.HexToAddress(homeKey.C()), + common.HexToAddress(homeKeyAddress), wrappedNativeTokenAddress, ) if err != nil { return err } - ux.Logger.PrintToUser("Home Deployed to %s", homeEndpoint) + ux.Logger.PrintToUser("Home Deployed to %s", homeRPCEndpoint) ux.Logger.PrintToUser("Home Address: %s", homeAddress) ux.Logger.PrintToUser("") } // Remote Deploy - remoteEndpoint, _, _, _, remoteRegistryAddress, remoteKey, err := teleporter.GetSubnetParams( - app, - network, - flags.remoteFlags.SubnetName, - flags.remoteFlags.CChain, - ) + remoteBlockchainID, err := contract.GetBlockchainID(app, network, flags.remoteFlags.chainFlags) + if err != nil { + return err + } + remoteRegistryAddress, _, err := contract.GetICMInfo(app, network, flags.remoteFlags.chainFlags, true, false, true) if err != nil { return err } - remoteAddress, err := ictt.DeployERC20Remote( - icttSrcDir, - remoteEndpoint, - remoteKey.PrivKeyHex(), - common.HexToAddress(remoteRegistryAddress), - common.HexToAddress(remoteKey.C()), - homeBlockchainID, - homeAddress, - tokenName, - tokenSymbol, - tokenDecimals, + var ( + remoteAddress common.Address + remoteSupply *big.Int ) + + // get token home symbol, name, decimals + endpointKind, err := ictt.GetEndpointKind(homeRPCEndpoint, homeAddress) if err != nil { return err } + var tokenHomeAddress common.Address + switch endpointKind { + case ictt.ERC20TokenHome: + tokenHomeAddress, err = ictt.ERC20TokenHomeGetTokenAddress(homeRPCEndpoint, homeAddress) + if err != nil { + return err + } + case ictt.NativeTokenHome: + tokenHomeAddress, err = ictt.NativeTokenHomeGetTokenAddress(homeRPCEndpoint, homeAddress) + if err != nil { + return err + } + default: + return fmt.Errorf("unsupported ictt endpoint kind %d", endpointKind) + } + tokenHomeSymbol, tokenHomeName, _, err := ictt.GetTokenParams( + homeRPCEndpoint, + tokenHomeAddress, + ) + if err != nil { + return err + } + homeDecimals, err := ictt.TokenHomeGetDecimals(homeRPCEndpoint, homeAddress) + if err != nil { + return err + } + + if !flags.remoteFlags.native { + // we default token remote decimals to be the same as token home decimals, + // but allow to be overridden by a user's provided flag + remoteDecimals := homeDecimals + if flags.remoteFlags.Decimals != 0 { + remoteDecimals = flags.remoteFlags.Decimals + } + remoteAddress, err = ictt.DeployERC20Remote( + icttSrcDir, + remoteRPCEndpoint, + remoteKey, + common.HexToAddress(remoteRegistryAddress), + common.HexToAddress(remoteKeyAddress), + homeBlockchainID, + homeAddress, + homeDecimals, + tokenHomeName, + tokenHomeSymbol, + remoteDecimals, + ) + if err != nil { + return err + } + } else { + nativeTokenSymbol, err := getNativeTokenSymbol( + flags.remoteFlags.chainFlags.BlockchainName, + flags.remoteFlags.chainFlags.CChain, + ) + if err != nil { + return err + } + remoteSupply, err = contract.GetEVMSubnetGenesisSupply( + app, + network, + flags.remoteFlags.chainFlags, + ) + if err != nil { + return err + } + remoteAddress, err = ictt.DeployNativeRemote( + icttSrcDir, + remoteRPCEndpoint, + remoteKey, + common.HexToAddress(remoteRegistryAddress), + common.HexToAddress(remoteKeyAddress), + homeBlockchainID, + homeAddress, + homeDecimals, + nativeTokenSymbol, + remoteSupply, + big.NewInt(0), + ) + if err != nil { + return err + } + } if err := ictt.RegisterERC20Remote( - remoteEndpoint, - remoteKey.PrivKeyHex(), + remoteRPCEndpoint, + remoteKey, remoteAddress, ); err != nil { return err } - ux.Logger.PrintToUser("Remote Deployed to %s", remoteEndpoint) + checkInterval := 100 * time.Millisecond + checkTimeout := 10 * time.Second + t0 := time.Now() + for { + registeredRemote, err := ictt.TokenHomeGetRegisteredRemote( + homeRPCEndpoint, + homeAddress, + remoteBlockchainID, + remoteAddress, + ) + if err != nil { + return err + } + if registeredRemote.Registered { + break + } + elapsed := time.Since(t0) + if elapsed > checkTimeout { + return fmt.Errorf("timeout waiting for remote endpoint registration") + } + time.Sleep(checkInterval) + } + + if flags.remoteFlags.native { + err = ictt.TokenHomeAddCollateral( + homeRPCEndpoint, + homeAddress, + homeKey, + remoteBlockchainID, + remoteAddress, + remoteSupply, + ) + if err != nil { + return err + } + + registeredRemote, err := ictt.TokenHomeGetRegisteredRemote( + homeRPCEndpoint, + homeAddress, + remoteBlockchainID, + remoteAddress, + ) + if err != nil { + return err + } + if registeredRemote.CollateralNeeded.Cmp(big.NewInt(0)) != 0 { + return fmt.Errorf("failure setting collateral in home endpoint: remaining collateral=%d", registeredRemote.CollateralNeeded) + } + + if err := precompiles.SetEnabled( + remoteRPCEndpoint, + precompiles.NativeMinterPrecompile, + remoteMinterManagerPrivKey, + remoteAddress, + ); err != nil { + return err + } + + err = ictt.Send( + homeRPCEndpoint, + homeAddress, + homeKey, + remoteBlockchainID, + remoteAddress, + common.HexToAddress(homeKeyAddress), + big.NewInt(1), + ) + if err != nil { + return err + } + + t0 := time.Now() + for { + isCollateralized, err := ictt.TokenRemoteIsCollateralized( + remoteRPCEndpoint, + remoteAddress, + ) + if err != nil { + return err + } + if isCollateralized { + break + } + elapsed := time.Since(t0) + if elapsed > checkTimeout { + return fmt.Errorf("timeout waiting for remote endpoint collateralization") + } + time.Sleep(checkInterval) + } + + if flags.remoteFlags.removeMinterAdmin && remoteMinterManagerIsAdmin { + ux.Logger.PrintToUser("Removing minter admin %s", remoteMinterManagerAddress) + if err := precompiles.SetNone( + remoteRPCEndpoint, + precompiles.NativeMinterPrecompile, + remoteMinterManagerPrivKey, + common.HexToAddress(remoteMinterManagerAddress), + ); err != nil { + return err + } + } else { + minterRole := "admin" + if !remoteMinterManagerIsAdmin { + minterRole = "manager" + } + ux.Logger.PrintToUser("Original minter %s %s is left in place", minterRole, remoteMinterManagerAddress) + } + } + + ux.Logger.PrintToUser("Remote Deployed to %s", remoteRPCEndpoint) ux.Logger.PrintToUser("Remote Address: %s", remoteAddress) return nil diff --git a/cmd/interchaincmd/tokentransferrercmd/helpers.go b/cmd/interchaincmd/tokentransferrercmd/helpers.go index 9efa7536a..85e0acb42 100644 --- a/cmd/interchaincmd/tokentransferrercmd/helpers.go +++ b/cmd/interchaincmd/tokentransferrercmd/helpers.go @@ -6,9 +6,7 @@ import ( _ "embed" "fmt" - "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/prompts" "github.com/ava-labs/avalanchego/ids" ) @@ -23,33 +21,6 @@ func validateSubnet(network models.Network, subnetName string) error { return nil } -func promptChain( - prompt string, - network models.Network, - avoidCChain bool, - avoidSubnet string, - chainFlags *contract.ChainFlags, -) (bool, error) { - subnetNames, err := app.GetSubnetNamesOnNetwork(network) - if err != nil { - return false, err - } - cancel, _, _, cChain, subnetName, err := prompts.PromptChain( - app.Prompt, - prompt, - subnetNames, - true, - true, - avoidCChain, - avoidSubnet, - ) - if err == nil { - chainFlags.SubnetName = subnetName - chainFlags.CChain = cChain - } - return cancel, err -} - func getNativeTokenSymbol(subnetName string, isCChain bool) (string, error) { nativeTokenSymbol := "AVAX" if !isCChain { diff --git a/cmd/interchaincmd/tokentransferrercmd/popularTokensInfo.json b/cmd/interchaincmd/tokentransferrercmd/popularTokensInfo.json index 049f073e2..49643539a 100644 --- a/cmd/interchaincmd/tokentransferrercmd/popularTokensInfo.json +++ b/cmd/interchaincmd/tokentransferrercmd/popularTokensInfo.json @@ -1,20 +1,15 @@ { "Fuji": { "C-Chain": [ - { - "TokenName": "AVAX", - "TokenContractAddress": "", - "TransferrerHomeAddress": "0xd00ae08403B9bbb9124bB305C09058E32C39A48c" - }, { "TokenName": "USDC", "TokenContractAddress": "0x5425890298aed601595a70AB815c96711a31Bc65", - "TransferrerHomeAddress": "0x5425890298aed601595a70AB815c96711a31Bc65" + "TransferrerHomeAddress": "0x546526F786115af1FE7c11aa8Ac5682b8c181E3A" }, { "TokenName": "WAVAX", "TokenContractAddress": "0xd00ae08403B9bbb9124bB305C09058E32C39A48c", - "TransferrerHomeAddress": "0xd00ae08403B9bbb9124bB305C09058E32C39A48c" + "TransferrerHomeAddress": "0xBBeE016016c91302058089E91bcc3be1cb2941Af" } ] } diff --git a/cmd/interchaincmd/tokentransferrercmd/popularTokensInfo.go b/cmd/interchaincmd/tokentransferrercmd/popular_tokens_info.go similarity index 100% rename from cmd/interchaincmd/tokentransferrercmd/popularTokensInfo.go rename to cmd/interchaincmd/tokentransferrercmd/popular_tokens_info.go diff --git a/cmd/interchaincmd/tokentransferrercmd/tokentransferrer.go b/cmd/interchaincmd/tokentransferrercmd/token_transferrer.go similarity index 100% rename from cmd/interchaincmd/tokentransferrercmd/tokentransferrer.go rename to cmd/interchaincmd/tokentransferrercmd/token_transferrer.go diff --git a/cmd/keycmd/list.go b/cmd/keycmd/list.go index 6dc27647b..568878e1b 100644 --- a/cmd/keycmd/list.go +++ b/cmd/keycmd/list.go @@ -7,7 +7,8 @@ import ( "math/big" "os" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" + "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/key" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" @@ -49,6 +50,7 @@ var ( cchain bool xchain bool useNanoAvax bool + useGwei bool ledgerIndices []uint keys []string tokenAddresses []string @@ -100,6 +102,12 @@ keys or for the ledger addresses associated to certain indices.`, false, "use nano Avax for balances", ) + cmd.Flags().BoolVar( + &useGwei, + "use-gwei", + false, + "use gwei for EVM balances", + ) cmd.Flags().UintSliceVarP( &ledgerIndices, ledgerIndicesFlag, @@ -119,6 +127,12 @@ keys or for the ledger addresses associated to certain indices.`, []string{}, "subnets to show information about (p=p-chain, x=x-chain, c=c-chain, and subnet names) (default p,x,c)", ) + cmd.Flags().StringSliceVar( + &subnets, + "blockchains", + []string{}, + "blockchains to show information about (p=p-chain, x=x-chain, c=c-chain, and blockchain names) (default p,x,c)", + ) cmd.Flags().StringSliceVar( &tokenAddresses, "tokens", @@ -169,7 +183,7 @@ func getClients(networks []models.Network, pchain bool, cchain bool, xchain bool } for _, subnetName := range subnets { if subnetName != "p" && subnetName != "x" && subnetName != "c" { - _, err = subnetcmd.ValidateSubnetNameAndGetChains([]string{subnetName}) + _, err = blockchaincmd.ValidateSubnetNameAndGetChains([]string{subnetName}) if err != nil { return nil, err } @@ -183,13 +197,21 @@ func getClients(networks []models.Network, pchain bool, cchain bool, xchain bool return nil, err } subnetToken = sc.TokenSymbol - chainID := sc.Networks[network.Name()].BlockchainID - if chainID != ids.Empty { + endpoint, _, err := contract.GetBlockchainEndpoints( + app, + network, + contract.ChainSpec{ + BlockchainName: subnetName, + }, + true, + false, + ) + if err == nil { _, b := evmClients[network] if !b { evmClients[network] = map[string]ethclient.Client{} } - evmClients[network][subnetName], err = ethclient.Dial(network.BlockchainEndpoint(chainID.String())) + evmClients[network][subnetName], err = ethclient.Dial(endpoint) if err != nil { return nil, err } @@ -198,7 +220,7 @@ func getClients(networks []models.Network, pchain bool, cchain bool, xchain bool if !b { evmGethClients[network] = map[string]*goethereumethclient.Client{} } - evmGethClients[network][subnetName], err = goethereumethclient.Dial(network.BlockchainEndpoint(chainID.String())) + evmGethClients[network][subnetName], err = goethereumethclient.Dial(endpoint) if err != nil { return nil, err } @@ -251,8 +273,8 @@ func listKeys(*cobra.Command, []string) error { network, err := networkoptions.GetNetworkFromCmdLineFlags( app, "", - networkoptions.NetworkFlags{}, - false, + globalNetworkFlags, + true, false, listSupportedNetworkOptions, "", @@ -595,6 +617,9 @@ func getCChainBalanceStr(cClient ethclient.Client, addrStr string) (string, erro } func formatCChainBalance(balance *big.Int) (string, error) { + if useGwei { + return fmt.Sprintf("%d", balance), nil + } // convert to nAvax balance = balance.Div(balance, big.NewInt(int64(units.Avax))) if balance.Cmp(big.NewInt(0)) == 0 { diff --git a/cmd/keycmd/transfer.go b/cmd/keycmd/transfer.go index fc25b9f6e..0f7eeccc7 100644 --- a/cmd/keycmd/transfer.go +++ b/cmd/keycmd/transfer.go @@ -10,6 +10,7 @@ import ( "time" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/ictt" "github.com/ava-labs/avalanche-cli/pkg/key" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" @@ -193,7 +194,7 @@ func transferF(*cobra.Command, []string) error { app, "On what Network do you want to execute the transfer?", globalNetworkFlags, - false, + true, false, transferSupportedNetworkOptions, "", @@ -202,14 +203,14 @@ func transferF(*cobra.Command, []string) error { return err } - subnetNames, err := app.GetSubnetNamesOnNetwork(network) + subnetNames, err := app.GetBlockchainNamesOnNetwork(network) if err != nil { return err } if originSubnet == "" && !PToX && !PToP { prompt := "Where are the funds to transfer?" - cancel, pChainChoosen, _, cChainChoosen, subnetName, err := prompts.PromptChain( + cancel, pChainChoosen, _, cChainChoosen, subnetName, _, err := prompts.PromptChain( app.Prompt, prompt, subnetNames, @@ -217,6 +218,7 @@ func transferF(*cobra.Command, []string) error { true, false, "", + false, ) if err != nil { return err @@ -252,7 +254,7 @@ func transferF(*cobra.Command, []string) error { if originSubnet == cChain { avoidSubnet = "" } - cancel, _, _, cChainChoosen, subnetName, err := prompts.PromptChain( + cancel, _, _, cChainChoosen, subnetName, _, err := prompts.PromptChain( app.Prompt, prompt, subnetNames, @@ -260,6 +262,7 @@ func transferF(*cobra.Command, []string) error { true, originSubnet == cChain, avoidSubnet, + false, ) if err != nil { return err @@ -275,15 +278,18 @@ func transferF(*cobra.Command, []string) error { } originURL := network.CChainEndpoint() if strings.ToLower(originSubnet) != cChain { - sc, err := app.LoadSidecar(originSubnet) + originURL, _, err = contract.GetBlockchainEndpoints( + app, + network, + contract.ChainSpec{ + BlockchainName: originSubnet, + }, + true, + false, + ) if err != nil { return err } - blockchainID := sc.Networks[network.Name()].BlockchainID - if blockchainID == ids.Empty { - return fmt.Errorf("subnet %s is not deployed to %s", originSubnet, network.Name()) - } - originURL = network.BlockchainEndpoint(blockchainID.String()) } var destinationBlockchainID ids.ID if strings.ToLower(destinationSubnet) == cChain { @@ -390,45 +396,15 @@ func transferF(*cobra.Command, []string) error { amount = amount.Mul(amount, new(big.Float).SetFloat64(float64(units.Avax))) amount = amount.Mul(amount, new(big.Float).SetFloat64(float64(units.Avax))) amountInt, _ := amount.Int(nil) - endpointKind, err := ictt.GetEndpointKind( + return ictt.Send( originURL, goethereumcommon.HexToAddress(originTransferrerAddress), + privateKey, + destinationBlockchainID, + goethereumcommon.HexToAddress(destinationTransferrerAddress), + destinationAddr, + amountInt, ) - if err != nil { - return err - } - switch endpointKind { - case ictt.ERC20TokenRemote: - return ictt.ERC20TokenRemoteSend( - originURL, - goethereumcommon.HexToAddress(originTransferrerAddress), - privateKey, - destinationBlockchainID, - goethereumcommon.HexToAddress(destinationTransferrerAddress), - destinationAddr, - amountInt, - ) - case ictt.ERC20TokenHome: - return ictt.ERC20TokenHomeSend( - originURL, - goethereumcommon.HexToAddress(originTransferrerAddress), - privateKey, - destinationBlockchainID, - goethereumcommon.HexToAddress(destinationTransferrerAddress), - destinationAddr, - amountInt, - ) - case ictt.NativeTokenHome: - return ictt.NativeTokenHomeSend( - originURL, - goethereumcommon.HexToAddress(originTransferrerAddress), - privateKey, - destinationBlockchainID, - goethereumcommon.HexToAddress(destinationTransferrerAddress), - destinationAddr, - amountInt, - ) - } } if !send && !receive { @@ -474,7 +450,7 @@ func transferF(*cobra.Command, []string) error { } amount := uint64(amountFlt * float64(units.Avax)) - fee := network.GenesisParams().TxFee + fee := network.GenesisParams().TxFeeConfig.StaticFeeConfig.TxFee var kc keychain.Keychain if keyName != "" { @@ -522,6 +498,8 @@ func transferF(*cobra.Command, []string) error { } } + usingLedger := ledgerIndex != wrongLedgerIndexVal + ux.Logger.PrintToUser("") ux.Logger.PrintToUser("this operation is going to:") if send { @@ -535,6 +513,9 @@ func transferF(*cobra.Command, []string) error { } ux.Logger.PrintToUser("- send %.9f AVAX from %s to destination address %s", float64(amount)/float64(units.Avax), addrStr, destinationAddrStr) totalFee := 4 * fee + if !usingLedger { + totalFee = fee + } if PToX { totalFee = 2 * fee } @@ -573,9 +554,14 @@ func transferF(*cobra.Command, []string) error { if err != nil { return err } - amountPlusFee := amount + fee*3 + amountPlusFee := amount + if PToP { + if usingLedger { + amountPlusFee += fee * 3 + } + } if PToX { - amountPlusFee = amount + fee + amountPlusFee += fee } output := &avax.TransferableOutput{ Asset: avax.Asset{ID: wallet.P().Builder().Context().AVAXAssetID}, @@ -585,17 +571,27 @@ func transferF(*cobra.Command, []string) error { }, } outputs := []*avax.TransferableOutput{output} - ux.Logger.PrintToUser("Issuing ExportTx P -> X") - - if ledgerIndex != wrongLedgerIndexVal { - ux.Logger.PrintToUser("*** Please sign 'Export Tx / P to X Chain' transaction on the ledger device *** ") - } - unsignedTx, err := wallet.P().Builder().NewExportTx( - wallet.X().Builder().Context().BlockchainID, - outputs, - ) - if err != nil { - return fmt.Errorf("error building tx: %w", err) + var unsignedTx txs.UnsignedTx + if PToP && !usingLedger { + ux.Logger.PrintToUser("Issuing BaseTx P -> P") + unsignedTx, err = wallet.P().Builder().NewBaseTx( + outputs, + ) + if err != nil { + return fmt.Errorf("error building tx: %w", err) + } + } else { + ux.Logger.PrintToUser("Issuing ExportTx P -> X") + if usingLedger { + ux.Logger.PrintToUser("*** Please sign 'Export Tx / P to X Chain' transaction on the ledger device *** ") + } + unsignedTx, err = wallet.P().Builder().NewExportTx( + wallet.X().Builder().Context().BlockchainID, + outputs, + ) + if err != nil { + return fmt.Errorf("error building tx: %w", err) + } } tx := txs.Tx{Unsigned: unsignedTx} if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil { @@ -631,7 +627,7 @@ func transferF(*cobra.Command, []string) error { return err } ux.Logger.PrintToUser("Issuing ImportTx P -> X") - if ledgerIndex != wrongLedgerIndexVal { + if usingLedger { ux.Logger.PrintToUser("*** Please sign ImportTx transaction on the ledger device *** ") } unsignedTx, err := wallet.X().Builder().NewImportTx( @@ -687,7 +683,7 @@ func transferF(*cobra.Command, []string) error { ux.Logger.PrintToUser("Issuing ExportTx X -> P") _, err = subnet.IssueXToPExportTx( wallet, - ledgerIndex != wrongLedgerIndexVal, + usingLedger, true, wallet.P().Builder().Context().AVAXAssetID, amount+fee*1, @@ -716,7 +712,7 @@ func transferF(*cobra.Command, []string) error { ux.Logger.PrintToUser("Issuing ImportTx X -> P") _, err = subnet.IssuePFromXImportTx( wallet, - ledgerIndex != wrongLedgerIndexVal, + usingLedger, true, &to, ) diff --git a/cmd/networkcmd/clean.go b/cmd/networkcmd/clean.go index 180acaee4..fbe556b4f 100644 --- a/cmd/networkcmd/clean.go +++ b/cmd/networkcmd/clean.go @@ -10,7 +10,6 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/binutils" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/constants" - "github.com/ava-labs/avalanche-cli/pkg/elasticsubnet" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/subnet" "github.com/ava-labs/avalanche-cli/pkg/teleporter" @@ -59,8 +58,8 @@ func clean(*cobra.Command, []string) error { } if err := teleporter.RelayerCleanup( - app.GetAWMRelayerRunPath(), - app.GetAWMRelayerStorageDir(), + app.GetLocalRelayerRunPath(models.Local), + app.GetLocalRelayerStorageDir(models.Local), ); err != nil { return err } @@ -79,9 +78,6 @@ func clean(*cobra.Command, []string) error { if err := removeLocalDeployInfoFromSidecars(); err != nil { return err } - if err := removeLocalElasticSubnetInfoFromSidecars(); err != nil { - return err - } return nil } @@ -106,38 +102,6 @@ func removeLocalDeployInfoFromSidecars() error { return nil } -func removeLocalElasticSubnetInfoFromSidecars() error { - // Remove all local elastic subnet info from sidecar files - elasticSubnets, err := elasticsubnet.GetLocalElasticSubnetsFromFile(app) - if err != nil { - return err - } - - for _, subnet := range elasticSubnets { - sc, err := app.LoadSidecar(subnet) - if err != nil { - return err - } - - delete(sc.ElasticSubnet, models.Local.String()) - if err = app.UpdateSidecar(&sc); err != nil { - return err - } - if err = deleteElasticSubnetConfigFile(subnet); err != nil { - return err - } - } - return nil -} - -func deleteElasticSubnetConfigFile(subnetName string) error { - elasticSubetConfigPath := app.GetElasticSubnetConfigPath(subnetName) - if err := os.Remove(elasticSubetConfigPath); err != nil { - return err - } - return nil -} - func cleanBins(dir string) { if err := os.RemoveAll(dir); err != nil { ux.Logger.PrintToUser("Removal failed: %s", err) diff --git a/cmd/networkcmd/start.go b/cmd/networkcmd/start.go index a21099c1b..ab19a9160 100644 --- a/cmd/networkcmd/start.go +++ b/cmd/networkcmd/start.go @@ -165,16 +165,17 @@ func StartNetwork(*cobra.Command, []string) error { return err } - if b, relayerConfigPath, err := subnet.GetAWMRelayerConfigPath(); err != nil { + if b, relayerConfigPath, err := subnet.GetLocalNetworkRelayerConfigPath(app); err != nil { return err } else if b { ux.Logger.PrintToUser("") if err := teleporter.DeployRelayer( + "latest", app.GetAWMRelayerBinDir(), relayerConfigPath, - app.GetAWMRelayerLogPath(), - app.GetAWMRelayerRunPath(), - app.GetAWMRelayerStorageDir(), + app.GetLocalRelayerLogPath(models.Local), + app.GetLocalRelayerRunPath(models.Local), + app.GetLocalRelayerStorageDir(models.Local), ); err != nil { return err } diff --git a/cmd/networkcmd/stop.go b/cmd/networkcmd/stop.go index b6e72bee9..69e23f33a 100644 --- a/cmd/networkcmd/stop.go +++ b/cmd/networkcmd/stop.go @@ -9,6 +9,7 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/binutils" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/constants" + "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/teleporter" "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" @@ -58,8 +59,8 @@ func StopNetwork(*cobra.Command, []string) error { } if err := teleporter.RelayerCleanup( - app.GetAWMRelayerRunPath(), - app.GetAWMRelayerStorageDir(), + app.GetLocalRelayerRunPath(models.Local), + app.GetLocalRelayerStorageDir(models.Local), ); err != nil { return err } diff --git a/cmd/nodecmd/addDashboard.go b/cmd/nodecmd/add_dashboard.go similarity index 81% rename from cmd/nodecmd/addDashboard.go rename to cmd/nodecmd/add_dashboard.go index 0fc0783f4..a683e8928 100644 --- a/cmd/nodecmd/addDashboard.go +++ b/cmd/nodecmd/add_dashboard.go @@ -22,27 +22,27 @@ cluster.`, RunE: addDashboard, } cmd.Flags().StringVar(&customGrafanaDashboardPath, "add-grafana-dashboard", "", "path to additional grafana dashboard json file") - cmd.Flags().StringVar(&subnetName, "subnet", "", "subnet that the dasbhoard is intended for (if any)") + cmd.Flags().StringVar(&blockchainName, "subnet", "", "subnet that the dasbhoard is intended for (if any)") return cmd } func addDashboard(_ *cobra.Command, args []string) error { clusterName := args[0] if customGrafanaDashboardPath != "" { - if err := addCustomDashboard(clusterName, subnetName); err != nil { + if err := addCustomDashboard(clusterName, blockchainName); err != nil { return err } } return nil } -func addCustomDashboard(clusterName, subnetName string) error { +func addCustomDashboard(clusterName, blockchainName string) error { monitoringInventoryPath := app.GetMonitoringInventoryDir(clusterName) monitoringHosts, err := ansible.GetInventoryFromAnsibleInventoryFile(monitoringInventoryPath) if err != nil { return err } - _, chainID, err := getDeployedSubnetInfo(clusterName, subnetName) + _, chainID, err := getDeployedSubnetInfo(clusterName, blockchainName) if err != nil { return err } diff --git a/cmd/nodecmd/create.go b/cmd/nodecmd/create.go index b109d3758..5f8d66553 100644 --- a/cmd/nodecmd/create.go +++ b/cmd/nodecmd/create.go @@ -19,8 +19,8 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/metrics" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/cmd/flags" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/binutils" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" @@ -73,8 +73,9 @@ var ( versionComments = map[string]string{ "v1.11.0-fuji": " (recommended for fuji durango)", } - grafanaPkg string - wizSubnet string + grafanaPkg string + wizSubnet string + publicHTTPPortAccess bool ) func newCreateCmd() *cobra.Command { @@ -126,6 +127,7 @@ will apply to all nodes in the cluster`, cmd.Flags().StringVar(&volumeType, "aws-volume-type", "gp3", "AWS volume type") cmd.Flags().IntVar(&volumeSize, "aws-volume-size", constants.CloudServerStorageSize, "AWS volume size in GB") cmd.Flags().BoolVar(&replaceKeyPair, "auto-replace-keypair", false, "automatically replaces key pair to access node if previous key pair is not found") + cmd.Flags().BoolVar(&publicHTTPPortAccess, "public-http-port", false, "allow public access to avalanchego HTTP port") return cmd } @@ -159,10 +161,10 @@ func preCreateChecks(clusterName string) error { if useSSHAgent && !utils.IsSSHAgentAvailable() { return fmt.Errorf("ssh agent is not available") } - if len(numAPINodes) > 0 && !globalNetworkFlags.UseDevnet { - return fmt.Errorf("API nodes can only be created in Devnet") + if len(numAPINodes) > 0 && !(globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji) { + return fmt.Errorf("API nodes can only be created in Devnet/Fuji(Testnet)") } - if globalNetworkFlags.UseDevnet && len(numAPINodes) != len(numValidatorsNodes) { + if (globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji) && len(numAPINodes) > 0 && len(numAPINodes) != len(numValidatorsNodes) { return fmt.Errorf("API nodes and Validator nodes must be deployed to same number of regions") } if len(numAPINodes) > 0 { @@ -255,7 +257,6 @@ func createNodes(cmd *cobra.Command, args []string) error { return err } network = models.NewNetworkFromCluster(network, clusterName) - globalNetworkFlags.UseDevnet = network.Kind == models.Devnet // set globalNetworkFlags.UseDevnet to true if network is devnet for further use avalancheGoVersion, err := getAvalancheGoVersion() if err != nil { @@ -403,14 +404,14 @@ func createNodes(cmd *cobra.Command, args []string) error { if existingMonitoringInstance == "" { monitoringHostRegion = regions[0] } - cloudConfigMap, err = createAWSInstances(ec2SvcMap, nodeType, numNodesMap, regions, ami, false) + cloudConfigMap, err = createAWSInstances(ec2SvcMap, nodeType, numNodesMap, regions, ami, false, publicHTTPPortAccess) if err != nil { return err } monitoringEc2SvcMap := make(map[string]*awsAPI.AwsCloud) if addMonitoring && existingMonitoringInstance == "" { monitoringEc2SvcMap[monitoringHostRegion] = ec2SvcMap[monitoringHostRegion] - monitoringCloudConfig, err := createAWSInstances(monitoringEc2SvcMap, nodeType, map[string]NumNodes{monitoringHostRegion: {1, 0}}, []string{monitoringHostRegion}, ami, true) + monitoringCloudConfig, err := createAWSInstances(monitoringEc2SvcMap, nodeType, map[string]NumNodes{monitoringHostRegion: {1, 0}}, []string{monitoringHostRegion}, ami, true, publicHTTPPortAccess) if err != nil { return err } @@ -712,7 +713,9 @@ func createNodes(cmd *cobra.Command, args []string) error { ux.SpinComplete(spinner) } spinner = spinSession.SpinToUser(utils.ScriptLog(host.NodeID, "Setup AvalancheGo")) - if err := docker.ComposeSSHSetupNode(host, network, avalancheGoVersion, addMonitoring); err != nil { + // check if host is a API host + publicAccessToHTTPPort := slices.Contains(cloudConfigMap.GetAllAPIInstanceIDs(), host.GetCloudID()) || publicHTTPPortAccess + if err := docker.ComposeSSHSetupNode(host, network, avalancheGoVersion, addMonitoring, publicAccessToHTTPPort); err != nil { nodeResults.AddResult(host.NodeID, nil, err) ux.SpinFailWithError(spinner, "", err) return @@ -900,6 +903,7 @@ func addNodeToClustersConfig(network models.Network, nodeID, clusterName string, if network != models.UndefinedNetwork { clusterConfig.Network = network } + clusterConfig.HTTPAccess = constants.HTTPAccess(publicHTTPPortAccess) if clusterConfig.LoadTestInstance == nil { clusterConfig.LoadTestInstance = make(map[string]string) } @@ -987,6 +991,10 @@ func provideStakingCertAndKey(host *models.Host) error { // or if they want to use the newest Avalanche Go Version that is still compatible with Subnet EVM // version of their choice func getAvalancheGoVersion() (string, error) { + // skip this logic if custom-avalanchego-version flag is set + if useCustomAvalanchegoVersion != "" { + return useCustomAvalanchegoVersion, nil + } latestReleaseVersion, err := app.Downloader.GetLatestReleaseVersion(binutils.GetGithubLatestReleaseURL( constants.AvaLabsOrg, constants.AvalancheGoRepoName, @@ -1078,7 +1086,7 @@ func promptAvalancheGoVersionChoice(latestReleaseVersion string, latestPreReleas if err != nil { return err } - _, err = subnetcmd.ValidateSubnetNameAndGetChains([]string{useAvalanchegoVersionFromSubnet}) + _, err = blockchaincmd.ValidateSubnetNameAndGetChains([]string{useAvalanchegoVersionFromSubnet}) if err == nil { break } @@ -1367,7 +1375,7 @@ func getRegionsNodeNum(cloudName string) ( if err != nil { return nil, err } - if globalNetworkFlags.UseDevnet { + if globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji { numAPINodes, err = app.Prompt.CaptureUint32(fmt.Sprintf("How many API nodes (nodes without stake) do you want to set up in %s %s?", userRegion, supportedClouds[cloudName].locationName)) if err != nil { return nil, err @@ -1378,7 +1386,7 @@ func getRegionsNodeNum(cloudName string) ( } nodes[userRegion] = NumNodes{int(numNodes), int(numAPINodes)} var currentInput []string - if globalNetworkFlags.UseDevnet { + if globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji { currentInput = utils.Map(maps.Keys(nodes), func(region string) string { return fmt.Sprintf("[%s]: %d validator(s) %d api(s)", region, nodes[region].numValidators, nodes[region].numAPI) }) diff --git a/cmd/nodecmd/create_aws.go b/cmd/nodecmd/create_aws.go index 2c7f7d3ce..616320cac 100644 --- a/cmd/nodecmd/create_aws.go +++ b/cmd/nodecmd/create_aws.go @@ -103,9 +103,9 @@ func getAWSCloudConfig(awsProfile string, singleNode bool, clusterSgRegions []st switch { case len(numValidatorsNodes) != len(utils.Unique(cmdLineRegion)): return nil, nil, nil, fmt.Errorf("number of nodes and regions should be the same") - case globalNetworkFlags.UseDevnet && len(numAPINodes) != len(utils.Unique(cmdLineRegion)): + case (globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji) && len(numAPINodes) != len(utils.Unique(cmdLineRegion)): return nil, nil, nil, fmt.Errorf("number of api nodes and regions should be the same") - case globalNetworkFlags.UseDevnet && len(numAPINodes) != len(numValidatorsNodes): + case (globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji) && len(numAPINodes) != len(numValidatorsNodes): return nil, nil, nil, fmt.Errorf("number of api nodes and validator nodes should be the same") case len(cmdLineRegion) == 0 && len(numValidatorsNodes) == 0 && len(numAPINodes) == 0: var err error @@ -128,7 +128,7 @@ func getAWSCloudConfig(awsProfile string, singleNode bool, clusterSgRegions []st } default: for i, region := range cmdLineRegion { - if globalNetworkFlags.UseDevnet { + if globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji { finalRegions[region] = NumNodes{numValidatorsNodes[i], numAPINodes[i]} } else { finalRegions[region] = NumNodes{numValidatorsNodes[i], 0} @@ -193,6 +193,7 @@ func createEC2Instances(ec2Svc map[string]*awsAPI.AwsCloud, regions []string, regionConf map[string]models.RegionConfig, forMonitoring bool, + publicHTTPPortAccess bool, ) (map[string][]string, map[string][]string, map[string]string, map[string]string, error) { if !forMonitoring { ux.Logger.PrintToUser("Creating new EC2 instance(s) on AWS...") @@ -299,6 +300,12 @@ func createEC2Instances(ec2Svc map[string]*awsAPI.AwsCloud, } else { sgID = newSGID } + // allow public access to API avalanchego port + if publicHTTPPortAccess { + if err := ec2Svc[region].AddSecurityGroupRule(sgID, "ingress", "tcp", "0.0.0.0/0", constants.AvalanchegoAPIPort); err != nil { + return instanceIDs, elasticIPs, sshCertPath, keyPairName, err + } + } } else { sgID = *sg.GroupId ux.Logger.PrintToUser(fmt.Sprintf("Using existing security group %s in AWS[%s]", securityGroupName, region)) @@ -333,6 +340,15 @@ func createEC2Instances(ec2Svc map[string]*awsAPI.AwsCloud, return instanceIDs, elasticIPs, sshCertPath, keyPairName, err } } + // check for public access to API port if flag is set + if publicHTTPPortAccess { + ipInPublicAPI := awsAPI.CheckIPInSg(&sg, "0.0.0.0/0", constants.AvalanchegoAPIPort) + if !ipInPublicAPI { + if err := ec2Svc[region].AddSecurityGroupRule(sgID, "ingress", "tcp", "0.0.0.0/0", constants.AvalanchegoAPIPort); err != nil { + return instanceIDs, elasticIPs, sshCertPath, keyPairName, err + } + } + } } sshCertPath[region] = privKey if instanceIDs[region], err = ec2Svc[region].CreateEC2Instances( @@ -482,7 +498,8 @@ func createAWSInstances( numNodes map[string]NumNodes, regions []string, ami map[string]string, - forMonitoring bool) ( + forMonitoring bool, + publicHTTPPortAccess bool) ( models.CloudConfig, error, ) { regionConf := map[string]models.RegionConfig{} @@ -501,7 +518,7 @@ func createAWSInstances( } } // Create new EC2 instances - instanceIDs, elasticIPs, certFilePath, keyPairName, err := createEC2Instances(ec2Svc, regions, regionConf, forMonitoring) + instanceIDs, elasticIPs, certFilePath, keyPairName, err := createEC2Instances(ec2Svc, regions, regionConf, forMonitoring, publicHTTPPortAccess) if err != nil { if err.Error() == constants.EIPLimitErr { ux.Logger.PrintToUser("Failed to create AWS cloud server(s), please try creating again in a different region") diff --git a/cmd/nodecmd/create_devnet.go b/cmd/nodecmd/create_devnet.go index 1582867a9..8b592f7aa 100644 --- a/cmd/nodecmd/create_devnet.go +++ b/cmd/nodecmd/create_devnet.go @@ -172,7 +172,7 @@ func setupDevnet(clusterName string, hosts []*models.Host, apiNodeIPMap map[stri } else { endpointIP = ansibleHosts[ansibleHostIDs[0]].IP } - endpoint := fmt.Sprintf("http://%s:%d", endpointIP, constants.AvalanchegoAPIPort) + endpoint := getAvalancheGoEndpoint(endpointIP) network := models.NewDevnetNetwork(endpoint, 0) network = models.NewNetworkFromCluster(network, clusterName) @@ -205,7 +205,7 @@ func setupDevnet(clusterName string, hosts []*models.Host, apiNodeIPMap map[stri return err } // make sure that custom genesis is saved to the subnet dir - if err := os.WriteFile(app.GetGenesisPath(subnetName), genesisBytes, constants.WriteReadReadPerms); err != nil { + if err := os.WriteFile(app.GetGenesisPath(blockchainName), genesisBytes, constants.WriteReadReadPerms); err != nil { return err } diff --git a/cmd/nodecmd/create_gcp.go b/cmd/nodecmd/create_gcp.go index 9679434ef..9bbad62bf 100644 --- a/cmd/nodecmd/create_gcp.go +++ b/cmd/nodecmd/create_gcp.go @@ -113,7 +113,7 @@ func getGCPConfig(singleNode bool) (*gcpAPI.GcpCloud, map[string]NumNodes, strin } } default: - if globalNetworkFlags.UseDevnet { + if globalNetworkFlags.UseDevnet || globalNetworkFlags.UseFuji { for i, region := range cmdLineRegion { finalRegions[region] = NumNodes{numValidatorsNodes[i], numAPINodes[i]} } diff --git a/cmd/nodecmd/deploy.go b/cmd/nodecmd/deploy.go index d3135b92f..1ba839e58 100644 --- a/cmd/nodecmd/deploy.go +++ b/cmd/nodecmd/deploy.go @@ -5,7 +5,7 @@ package nodecmd import ( "fmt" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -15,8 +15,9 @@ import ( ) var ( - subnetOnly bool - avoidChecks bool + subnetOnly bool + avoidChecks bool + subnetAliases []string ) func newDeployCmd() *cobra.Command { @@ -33,6 +34,7 @@ It saves the deploy info both locally and remotely. } cmd.Flags().BoolVar(&subnetOnly, "subnet-only", false, "only create a subnet") cmd.Flags().BoolVar(&avoidChecks, "no-checks", false, "do not check for healthy status or rpc compatibility of nodes against subnet") + cmd.Flags().StringSliceVar(&subnetAliases, "subnet-aliases", nil, "additional subnet aliases to be used for RPC calls in addition to subnet blockchain name") return cmd } @@ -42,7 +44,7 @@ func deploySubnet(cmd *cobra.Command, args []string) error { if err := checkCluster(clusterName); err != nil { return err } - if _, err := subnetcmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { + if _, err := blockchaincmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { return err } clustersConfig, err := app.LoadClustersConfig() @@ -73,7 +75,7 @@ func deploySubnet(cmd *cobra.Command, args []string) error { useEwoqParam := true sameControlKey := true - if err := subnetcmd.CallDeploy( + if err := blockchaincmd.CallDeploy( cmd, subnetOnly, subnetName, diff --git a/cmd/nodecmd/destroy.go b/cmd/nodecmd/destroy.go index fc523ed8a..9153dc0f3 100644 --- a/cmd/nodecmd/destroy.go +++ b/cmd/nodecmd/destroy.go @@ -24,6 +24,7 @@ import ( var ( authorizeRemove bool authorizeAll bool + destroyAll bool ) func newDestroyCmd() *cobra.Command { @@ -35,12 +36,13 @@ func newDestroyCmd() *cobra.Command { The node destroy command terminates all running nodes in cloud server and deletes all storage disks. If there is a static IP address attached, it will be released.`, - Args: cobrautils.ExactArgs(1), + Args: cobrautils.MinimumNArgs(0), RunE: destroyNodes, } cmd.Flags().BoolVar(&authorizeAccess, "authorize-access", false, "authorize CLI to release cloud resources") cmd.Flags().BoolVar(&authorizeRemove, "authorize-remove", false, "authorize CLI to remove all local files related to cloud nodes") cmd.Flags().BoolVarP(&authorizeAll, "authorize-all", "y", false, "authorize all CLI requests") + cmd.Flags().BoolVar(&destroyAll, "all", false, "destroy all existing clusters created by Avalanche CLI") cmd.Flags().StringVar(&awsProfile, "aws-profile", constants.AWSDefaultCredential, "aws profile to use") return cmd @@ -93,7 +95,65 @@ func removeClustersConfigFiles(clusterName string) error { return removeNodeFromClustersConfig(clusterName) } +func CallDestroyNode(clusterName string) error { + authorizeAll = true + return destroyNodes(nil, []string{clusterName}) +} + +// We need to get which cloud service is being used on a cluster +// getFirstAvailableNode gets first node in the cluster that still has its node_config.json +// This is because some nodes might have had their node_config.json file deleted as part of +// deletion process but if an error occurs during deletion process, the node might still exist +// as part of the cluster in cluster_config.json +// If all nodes in the cluster no longer have their node_config.json files, getFirstAvailableNode +// will return false in its second return value +func getFirstAvailableNode(nodesToStop []string) (string, bool) { + firstAvailableNode := nodesToStop[0] + noAvailableNodesFound := false + for index, node := range nodesToStop { + nodeConfigPath := app.GetNodeConfigPath(node) + if !utils.FileExists(nodeConfigPath) { + if index == len(nodesToStop)-1 { + noAvailableNodesFound = true + } + continue + } + firstAvailableNode = node + } + return firstAvailableNode, noAvailableNodesFound +} + +func Cleanup() error { + var err error + clustersConfig := models.ClustersConfig{} + if app.ClustersConfigExists() { + clustersConfig, err = app.LoadClustersConfig() + if err != nil { + return err + } + } + clusterNames := maps.Keys(clustersConfig.Clusters) + for _, clusterName := range clusterNames { + if err = CallDestroyNode(clusterName); err != nil { + // we only return error for invalid cloud credentials + // silence for other errors + // TODO: differentiate between AWS and GCP credentials + if strings.Contains(err.Error(), "invalid cloud credentials") { + return fmt.Errorf("invalid AWS credentials") + } + } + } + ux.Logger.PrintToUser("all existing instances created by Avalanche CLI successfully destroyed") + return nil +} + func destroyNodes(_ *cobra.Command, args []string) error { + if len(args) == 0 { + if !destroyAll { + return fmt.Errorf("to destroy all existing clusters created by Avalanche CLI, call avalanche node destroy --all. To destroy a specified cluster, call avalanche node destroy CLUSTERNAME") + } + return Cleanup() + } clusterName := args[0] if err := checkCluster(clusterName); err != nil { return err @@ -137,7 +197,11 @@ func destroyNodes(_ *cobra.Command, args []string) error { if err != nil { return err } - nodeToStopConfig, err := app.LoadClusterNodeConfig(nodesToStop[0]) + firstAvailableNodes, noAvailableNodesFound := getFirstAvailableNode(nodesToStop) + if noAvailableNodesFound { + return removeClustersConfigFiles(clusterName) + } + nodeToStopConfig, err := app.LoadClusterNodeConfig(firstAvailableNodes) if err != nil { return err } @@ -160,6 +224,11 @@ func destroyNodes(_ *cobra.Command, args []string) error { } for _, node := range nodesToStop { if !isExternalCluster { + // if we can't find node config path, that means node already deleted on console + // but we didn't get to delete the node from cluster config file + if !utils.FileExists(app.GetNodeConfigPath(node)) { + continue + } nodeConfig, err := app.LoadClusterNodeConfig(node) if err != nil { nodeErrors[node] = err @@ -174,7 +243,7 @@ func destroyNodes(_ *cobra.Command, args []string) error { if isExpiredCredentialError(err) { ux.Logger.PrintToUser("") printExpiredCredentialsOutput(awsProfile) - return nil + return fmt.Errorf("invalid cloud credentials") } if !errors.Is(err, awsAPI.ErrNodeNotFoundToBeRunning) { nodeErrors[node] = err @@ -219,13 +288,20 @@ func destroyNodes(_ *cobra.Command, args []string) error { } if len(nodeErrors) > 0 { ux.Logger.PrintToUser("Failed nodes: ") + invalidCloudCredentials := false for node, nodeErr := range nodeErrors { if strings.Contains(nodeErr.Error(), constants.ErrReleasingGCPStaticIP) { ux.Logger.RedXToUser("Node is destroyed, but failed to release static ip address for node %s due to %s", node, nodeErr) } else { + if strings.Contains(nodeErr.Error(), "AuthFailure") { + invalidCloudCredentials = true + } ux.Logger.RedXToUser("Failed to destroy node %s due to %s", node, nodeErr) } } + if invalidCloudCredentials { + return fmt.Errorf("failed to destroy node(s) due to invalid cloud credentials %s", maps.Keys(nodeErrors)) + } return fmt.Errorf("failed to destroy node(s) %s", maps.Keys(nodeErrors)) } else { if isExternalCluster { diff --git a/cmd/nodecmd/helpers.go b/cmd/nodecmd/helpers.go index 8cfffb1da..dc3c683f4 100644 --- a/cmd/nodecmd/helpers.go +++ b/cmd/nodecmd/helpers.go @@ -227,3 +227,35 @@ func checkHostsAreBootstrapped(hosts []*models.Host) error { } return nil } + +func getAvalancheGoEndpoint(ip string) string { + return fmt.Sprintf("http://%s:%d", ip, constants.AvalanchegoAPIPort) +} + +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) +} + +func getPublicEndpoints(clusterName string) ([]string, error) { + endpoints := []string{} + clusterConfig, err := app.GetClusterConfig(clusterName) + if err != nil { + return nil, err + } + publicNodes := clusterConfig.APINodes + if clusterConfig.Network.Kind == models.Devnet { + publicNodes = clusterConfig.Nodes + } + for _, cloudID := range publicNodes { + nodeConfig, err := app.LoadClusterNodeConfig(cloudID) + if err != nil { + return nil, err + } + endpoints = append(endpoints, getAvalancheGoEndpoint(nodeConfig.ElasticIP)) + } + return endpoints, nil +} diff --git a/cmd/nodecmd/loadtest.go b/cmd/nodecmd/load_test_cmd.go similarity index 87% rename from cmd/nodecmd/loadtest.go rename to cmd/nodecmd/load_test_cmd.go index b2bc169b8..a56d840ca 100644 --- a/cmd/nodecmd/loadtest.go +++ b/cmd/nodecmd/load_test_cmd.go @@ -1,4 +1,4 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. // See the file LICENSE for licensing terms. package nodecmd @@ -7,7 +7,7 @@ import ( "github.com/spf13/cobra" ) -func NewLoadTestCmd() *cobra.Command { +func newLoadTestCmd() *cobra.Command { cmd := &cobra.Command{ Use: "loadtest", Short: "(ALPHA Warning) Load test suite for an existing subnet on an existing cloud cluster", diff --git a/cmd/nodecmd/loadtestStart.go b/cmd/nodecmd/load_test_start.go similarity index 97% rename from cmd/nodecmd/loadtestStart.go rename to cmd/nodecmd/load_test_start.go index 9dec5199b..dea2296d1 100644 --- a/cmd/nodecmd/loadtestStart.go +++ b/cmd/nodecmd/load_test_start.go @@ -54,7 +54,7 @@ type nodeInfo struct { func newLoadTestStartCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "start [loadtestName] [clusterName] [subnetName]", + Use: "start [loadtestName] [clusterName] [blockchainName]", Short: "(ALPHA Warning) Start load test for existing devnet cluster", Long: `(ALPHA Warning) This command is currently in experimental mode. @@ -111,9 +111,9 @@ func preLoadTestChecks(clusterName string) error { func startLoadTest(_ *cobra.Command, args []string) error { loadTestName := args[0] clusterName := args[1] - subnetName = args[2] - if !app.SidecarExists(subnetName) { - return fmt.Errorf("subnet %s doesn't exist, please create it first", subnetName) + blockchainName = args[2] + if !app.SidecarExists(blockchainName) { + return fmt.Errorf("subnet %s doesn't exist, please create it first", blockchainName) } if err := preLoadTestChecks(clusterName); err != nil { return err @@ -194,7 +194,7 @@ func startLoadTest(_ *cobra.Command, args []string) error { } separateHostRegion = loadTestHostRegion loadTestEc2SvcMap[separateHostRegion] = ec2SvcMap[separateHostRegion] - loadTestCloudConfig, err = createAWSInstances(loadTestEc2SvcMap, nodeType, map[string]NumNodes{separateHostRegion: {1, 0}}, []string{separateHostRegion}, ami, true) + loadTestCloudConfig, err = createAWSInstances(loadTestEc2SvcMap, nodeType, map[string]NumNodes{separateHostRegion: {1, 0}}, []string{separateHostRegion}, ami, true, false) if err != nil { return err } @@ -338,7 +338,7 @@ func startLoadTest(_ *cobra.Command, args []string) error { } } - subnetID, chainID, err := getDeployedSubnetInfo(clusterName, subnetName) + subnetID, chainID, err := getDeployedSubnetInfo(clusterName, blockchainName) if err != nil { return err } @@ -369,8 +369,8 @@ func startLoadTest(_ *cobra.Command, args []string) error { return nil } -func getDeployedSubnetInfo(clusterName string, subnetName string) (string, string, error) { - sc, err := app.LoadSidecar(subnetName) +func getDeployedSubnetInfo(clusterName string, blockchainName string) (string, string, error) { + sc, err := app.LoadSidecar(blockchainName) if err != nil { return "", "", err } @@ -387,7 +387,7 @@ func getDeployedSubnetInfo(clusterName string, subnetName string) (string, strin } } } - return "", "", fmt.Errorf("unable to find deployed Cluster info, please call avalanche subnet deploy --cluster first") + return "", "", fmt.Errorf("unable to find deployed Cluster info, please call avalanche subnet deploy --cluster first") } func createClusterYAMLFile(clusterName, subnetID, chainID string, separateHost *models.Host) error { diff --git a/cmd/nodecmd/loadtestStop.go b/cmd/nodecmd/load_test_stop.go similarity index 100% rename from cmd/nodecmd/loadtestStop.go rename to cmd/nodecmd/load_test_stop.go diff --git a/cmd/nodecmd/node.go b/cmd/nodecmd/node.go index a44717468..c67100453 100644 --- a/cmd/nodecmd/node.go +++ b/cmd/nodecmd/node.go @@ -49,7 +49,7 @@ rest of the commands to maintain your node and make your node a Subnet Validator // node refresh-ips cmd.AddCommand(newRefreshIPsCmd()) // node loadtest - cmd.AddCommand(NewLoadTestCmd()) + cmd.AddCommand(newLoadTestCmd()) // node resize cmd.AddCommand(newResizeCmd()) // node addDashboard diff --git a/cmd/nodecmd/refreshIPs.go b/cmd/nodecmd/refresh_ips.go similarity index 100% rename from cmd/nodecmd/refreshIPs.go rename to cmd/nodecmd/refresh_ips.go diff --git a/cmd/nodecmd/status.go b/cmd/nodecmd/status.go index feaf32597..dc499cc18 100644 --- a/cmd/nodecmd/status.go +++ b/cmd/nodecmd/status.go @@ -8,7 +8,7 @@ import ( "strings" "sync" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -24,7 +24,7 @@ import ( "golang.org/x/exp/slices" ) -var subnetName string +var blockchainName string func newStatusCmd() *cobra.Command { cmd := &cobra.Command{ @@ -35,11 +35,12 @@ func newStatusCmd() *cobra.Command { The node status command gets the bootstrap status of all nodes in a cluster with the Primary Network. If no cluster is given, defaults to node list behaviour. -To get the bootstrap status of a node with a Subnet, use --subnet flag`, +To get the bootstrap status of a node with a Blockchain, use --blockchain flag`, Args: cobrautils.MinimumNArgs(0), RunE: statusNode, } - cmd.Flags().StringVar(&subnetName, "subnet", "", "specify the subnet the node is syncing with") + cmd.Flags().StringVar(&blockchainName, "subnet", "", "specify the blockchain the node is syncing with") + cmd.Flags().StringVar(&blockchainName, "blockchain", "", "specify the blockchain the node is syncing with") return cmd } @@ -57,8 +58,8 @@ func statusNode(_ *cobra.Command, args []string) error { return err } var blockchainID ids.ID - if subnetName != "" { - sc, err := app.LoadSidecar(subnetName) + if blockchainName != "" { + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } @@ -75,9 +76,9 @@ func statusNode(_ *cobra.Command, args []string) error { if err != nil { return err } - if subnetName != "" { + if blockchainName != "" { // check subnet first - if _, err := subnetcmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { + if _, err := blockchaincmd.ValidateSubnetNameAndGetChains([]string{blockchainName}); err != nil { return err } } @@ -141,7 +142,7 @@ func statusNode(_ *cobra.Command, args []string) error { notSyncedNodes := []string{} subnetSyncedNodes := []string{} subnetValidatingNodes := []string{} - if subnetName != "" { + if blockchainName != "" { hostsToCheckSyncStatus := []string{} for _, hostID := range hostIDs { if slices.Contains(notBootstrappedNodes, hostID) { @@ -211,7 +212,7 @@ func statusNode(_ *cobra.Command, args []string) error { subnetSyncedNodes, subnetValidatingNodes, clusterName, - subnetName, + blockchainName, nodeConfigs, ) return nil @@ -228,22 +229,22 @@ func printOutput( subnetSyncedHosts []string, subnetValidatingHosts []string, clusterName string, - subnetName string, + blockchainName string, nodeConfigs []models.NodeConfig, ) { if clusterConf.External { ux.Logger.PrintToUser("Cluster %s (%s) is EXTERNAL", logging.LightBlue.Wrap(clusterName), clusterConf.Network.Kind.String()) } - if subnetName == "" && len(notBootstrappedHosts) == 0 { + if blockchainName == "" && len(notBootstrappedHosts) == 0 { ux.Logger.PrintToUser("All nodes in cluster %s are bootstrapped to Primary Network!", clusterName) } - if subnetName != "" && len(notSyncedHosts) == 0 { + if blockchainName != "" && len(notSyncedHosts) == 0 { // all nodes are either synced to or validating subnet status := "synced to" if len(subnetSyncedHosts) == 0 { status = "validators of" } - ux.Logger.PrintToUser("All nodes in cluster %s are %s Subnet %s", logging.LightBlue.Wrap(clusterName), status, subnetName) + ux.Logger.PrintToUser("All nodes in cluster %s are %s Subnet %s", logging.LightBlue.Wrap(clusterName), status, blockchainName) } ux.Logger.PrintToUser("") tit := fmt.Sprintf("STATUS FOR CLUSTER: %s", logging.LightBlue.Wrap(clusterName)) @@ -251,8 +252,8 @@ func printOutput( ux.Logger.PrintToUser(strings.Repeat("=", len(removeColors(tit)))) ux.Logger.PrintToUser("") header := []string{"Cloud ID", "Node ID", "IP", "Network", "Role", "Avago Version", "Primary Network", "Healthy"} - if subnetName != "" { - header = append(header, "Subnet "+subnetName) + if blockchainName != "" { + header = append(header, "Subnet "+blockchainName) } table := tablewriter.NewWriter(os.Stdout) table.SetHeader(header) @@ -285,7 +286,7 @@ func printOutput( boostrappedStatus, healthyStatus, } - if subnetName != "" { + if blockchainName != "" { syncedStatus := "" if clusterConf.MonitoringInstance != cloudID { syncedStatus = logging.Red.Wrap("NOT_BOOTSTRAPPED") diff --git a/cmd/nodecmd/sync.go b/cmd/nodecmd/sync.go index a5928ae60..06a699d02 100644 --- a/cmd/nodecmd/sync.go +++ b/cmd/nodecmd/sync.go @@ -6,37 +6,39 @@ import ( "fmt" "sync" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/ssh" "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanchego/utils/set" "github.com/spf13/cobra" ) func newSyncCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "sync [clusterName] [subnetName]", + Use: "sync [clusterName] [blockchainName]", Short: "(ALPHA Warning) Sync nodes in a cluster with a subnet", Long: `(ALPHA Warning) This command is currently in experimental mode. -The node sync command enables all nodes in a cluster to be bootstrapped to a Subnet. -You can check the subnet bootstrap status by calling avalanche node status --subnet `, +The node sync command enables all nodes in a cluster to be bootstrapped to a Blockchain. +You can check the blockchain bootstrap status by calling avalanche node status --blockchain `, Args: cobrautils.ExactArgs(2), RunE: syncSubnet, } cmd.Flags().StringSliceVar(&validators, "validators", []string{}, "sync subnet into given comma separated list of validators. defaults to all cluster nodes") cmd.Flags().BoolVar(&avoidChecks, "no-checks", false, "do not check for bootstrapped/healthy status or rpc compatibility of nodes against subnet") + cmd.Flags().StringSliceVar(&subnetAliases, "subnet-aliases", nil, "subnet alias to be used for RPC calls. defaults to subnet blockchain ID") return cmd } func syncSubnet(_ *cobra.Command, args []string) error { clusterName := args[0] - subnetName := args[1] + blockchainName := args[1] if err := checkCluster(clusterName); err != nil { return err } @@ -44,7 +46,7 @@ func syncSubnet(_ *cobra.Command, args []string) error { if err != nil { return err } - if _, err := subnetcmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { + if _, err := blockchaincmd.ValidateSubnetNameAndGetChains([]string{blockchainName}); err != nil { return err } hosts, err := ansible.GetInventoryFromAnsibleInventoryFile(app.GetAnsibleInventoryDirPath(clusterName)) @@ -65,28 +67,24 @@ func syncSubnet(_ *cobra.Command, args []string) error { if err := checkHostsAreHealthy(hosts); err != nil { return err } - if err := checkHostsAreRPCCompatible(hosts, subnetName); err != nil { + if err := checkHostsAreRPCCompatible(hosts, blockchainName); err != nil { return err } } - if err := prepareSubnetPlugin(hosts, subnetName); err != nil { + if err := prepareSubnetPlugin(hosts, blockchainName); err != nil { return err } - untrackedNodes, err := trackSubnet(hosts, clusterName, clusterConfig.Network, subnetName) - if err != nil { + if err := trackSubnet(hosts, clusterName, clusterConfig.Network, blockchainName); err != nil { return err } - if len(untrackedNodes) > 0 { - return fmt.Errorf("node(s) %s failed to sync with subnet %s", untrackedNodes, subnetName) - } - ux.Logger.PrintToUser("Node(s) successfully started syncing with Subnet!") - ux.Logger.PrintToUser(fmt.Sprintf("Check node subnet syncing status with avalanche node status %s --subnet %s", clusterName, subnetName)) + ux.Logger.PrintToUser("Node(s) successfully started syncing with Blockchain!") + ux.Logger.PrintToUser(fmt.Sprintf("Check node blockchain syncing status with avalanche node status %s --blockchain %s", clusterName, blockchainName)) return nil } // prepareSubnetPlugin creates subnet plugin to all nodes in the cluster -func prepareSubnetPlugin(hosts []*models.Host, subnetName string) error { - sc, err := app.LoadSidecar(subnetName) +func prepareSubnetPlugin(hosts []*models.Host, blockchainName string) error { + sc, err := app.LoadSidecar(blockchainName) if err != nil { return err } @@ -109,23 +107,31 @@ func prepareSubnetPlugin(hosts []*models.Host, subnetName string) error { } // trackSubnet exports deployed subnet in user's local machine to cloud server and calls node to -// start tracking the specified subnet (similar to avalanche subnet join command) +// start tracking the specified subnet (similar to avalanche subnet join command) func trackSubnet( hosts []*models.Host, clusterName string, network models.Network, - subnetName string, -) ([]string, error) { + blockchainName string, +) error { // load cluster config - clusterConf, err := app.GetClusterConfig(clusterName) + clusterConfig, err := app.GetClusterConfig(clusterName) if err != nil { - return nil, err + return err } // and get list of subnets - allSubnets := utils.Unique(append(clusterConf.Subnets, subnetName)) + allSubnets := utils.Unique(append(clusterConfig.Subnets, blockchainName)) + + // load sidecar to get subnet blockchain ID + sc, err := app.LoadSidecar(blockchainName) + if err != nil { + return err + } + blockchainID := sc.Networks[network.Name()].BlockchainID wg := sync.WaitGroup{} wgResults := models.NodeResults{} + subnetAliases := append([]string{blockchainName}, subnetAliases...) for _, host := range hosts { wg.Add(1) go func(nodeResults *models.NodeResults, host *models.Host) { @@ -133,10 +139,24 @@ func trackSubnet( if err := ssh.RunSSHStopNode(host); err != nil { nodeResults.AddResult(host.NodeID, nil, err) } - if err := ssh.RunSSHRenderAvalancheNodeConfig(app, host, network, allSubnets); err != nil { + + if err := ssh.RunSSHRenderAvagoAliasConfigFile( + host, + blockchainID.String(), + subnetAliases, + ); err != nil { + nodeResults.AddResult(host.NodeID, nil, err) + } + if err := ssh.RunSSHRenderAvalancheNodeConfig( + app, + host, + network, + allSubnets, + clusterConfig.IsAPIHost(host.GetCloudID()), + ); err != nil { nodeResults.AddResult(host.NodeID, nil, err) } - if err := ssh.RunSSHSyncSubnetData(app, host, network, subnetName); err != nil { + if err := ssh.RunSSHSyncSubnetData(app, host, network, blockchainName); err != nil { nodeResults.AddResult(host.NodeID, nil, err) } if err := ssh.RunSSHStartNode(host); err != nil { @@ -147,7 +167,30 @@ func trackSubnet( } wg.Wait() if wgResults.HasErrors() { - return nil, fmt.Errorf("failed to track subnet for node(s) %s", wgResults.GetErrorHostMap()) + return fmt.Errorf("failed to track subnet for node(s) %s", wgResults.GetErrorHostMap()) + } + + // update slice of subnets synced by the cluster + clusterConfig.Subnets = allSubnets + err = app.SetClusterConfig(network.ClusterName, clusterConfig) + if err != nil { + return err + } + + // update slice of blockchain endpoints with the cluster ones + networkInfo := sc.Networks[clusterConfig.Network.Name()] + rpcEndpoints := set.Of(networkInfo.RPCEndpoints...) + wsEndpoints := set.Of(networkInfo.WSEndpoints...) + publicEndpoints, err := getPublicEndpoints(clusterName) + if err != nil { + return err + } + for _, publicEndpoint := range publicEndpoints { + rpcEndpoints.Add(getRPCEndpoint(publicEndpoint, networkInfo.BlockchainID.String())) + wsEndpoints.Add(getWSEndpoint(publicEndpoint, networkInfo.BlockchainID.String())) } - return wgResults.GetErrorHosts(), nil + networkInfo.RPCEndpoints = rpcEndpoints.List() + networkInfo.WSEndpoints = wsEndpoints.List() + sc.Networks[clusterConfig.Network.Name()] = networkInfo + return app.UpdateSidecar(&sc) } diff --git a/cmd/nodecmd/updateSubnet.go b/cmd/nodecmd/update_subnet.go similarity index 91% rename from cmd/nodecmd/updateSubnet.go rename to cmd/nodecmd/update_subnet.go index d51f06538..810aaf5c0 100644 --- a/cmd/nodecmd/updateSubnet.go +++ b/cmd/nodecmd/update_subnet.go @@ -6,7 +6,7 @@ import ( "fmt" "sync" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -41,7 +41,7 @@ func updateSubnet(_ *cobra.Command, args []string) error { if err != nil { return err } - if _, err := subnetcmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { + if _, err := blockchaincmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { return err } hosts, err := ansible.GetInventoryFromAnsibleInventoryFile(app.GetAnsibleInventoryDirPath(clusterName)) @@ -95,7 +95,13 @@ func doUpdateSubnet( if err := ssh.RunSSHStopNode(host); err != nil { nodeResults.AddResult(host.NodeID, nil, err) } - if err := ssh.RunSSHRenderAvalancheNodeConfig(app, host, network, allSubnets); err != nil { + if err := ssh.RunSSHRenderAvalancheNodeConfig( + app, + host, + network, + allSubnets, + clusterConf.IsAPIHost(host.GetCloudID()), + ); err != nil { nodeResults.AddResult(host.NodeID, nil, err) } if err := ssh.RunSSHSyncSubnetData(app, host, network, subnetName); err != nil { diff --git a/cmd/nodecmd/upgrade.go b/cmd/nodecmd/upgrade.go index 99cf856a2..0a8d3200a 100644 --- a/cmd/nodecmd/upgrade.go +++ b/cmd/nodecmd/upgrade.go @@ -68,7 +68,9 @@ func upgrade(_ *cobra.Command, args []string) error { for host, upgradeInfo := range toUpgradeNodesMap { if upgradeInfo.AvalancheGoVersion != "" { spinner := spinSession.SpinToUser(utils.ScriptLog(host.NodeID, fmt.Sprintf("Upgrading avalanchego to version %s...", upgradeInfo.AvalancheGoVersion))) - if err := upgradeAvalancheGo(host, network, upgradeInfo.AvalancheGoVersion); err != nil { + // check if host is API host + publicAccessToHTTPPort := clusterConfig.IsAPIHost(host.GetCloudID()) || clusterConfig.HTTPAccess == constants.PublicAccess + if err := upgradeAvalancheGo(host, network, upgradeInfo.AvalancheGoVersion, publicAccessToHTTPPort); err != nil { ux.SpinFailWithError(spinner, "", err) return err } @@ -216,8 +218,9 @@ func upgradeAvalancheGo( host *models.Host, network models.Network, avaGoVersionToUpdateTo string, + publicAccessToHTTPPort bool, ) error { - if err := ssh.RunSSHUpgradeAvalanchego(host, network, avaGoVersionToUpdateTo); err != nil { + if err := ssh.RunSSHUpgradeAvalanchego(host, network, avaGoVersionToUpdateTo, publicAccessToHTTPPort); err != nil { return err } return nil diff --git a/cmd/nodecmd/validatePrimary.go b/cmd/nodecmd/validate_primary.go similarity index 98% rename from cmd/nodecmd/validatePrimary.go rename to cmd/nodecmd/validate_primary.go index 90019520e..44f1f8fec 100644 --- a/cmd/nodecmd/validatePrimary.go +++ b/cmd/nodecmd/validate_primary.go @@ -9,7 +9,7 @@ import ( "strconv" "time" - subnetcmd "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + blockchaincmd "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/constants" @@ -204,7 +204,7 @@ func GetTimeParametersPrimaryNetwork(network models.Network, nodeIndex int, vali } default: useCustomDuration = true - duration, err = subnetcmd.PromptDuration(start, network) + duration, err = blockchaincmd.PromptDuration(start, network) if err != nil { return time.Time{}, 0, err } @@ -304,7 +304,7 @@ func validatePrimaryNetwork(_ *cobra.Command, args []string) error { hosts := clusterConfig.GetValidatorHosts(allHosts) // exlude api nodes defer disconnectHosts(hosts) - fee := network.GenesisParams().AddPrimaryNetworkValidatorFee * uint64(len(hosts)) + fee := network.GenesisParams().TxFeeConfig.StaticFeeConfig.AddPrimaryNetworkValidatorFee * uint64(len(hosts)) kc, err := keychain.GetKeychainFromCmdLineFlags( app, constants.PayTxsFeesMsg, diff --git a/cmd/nodecmd/validateSubnet.go b/cmd/nodecmd/validate_subnet.go similarity index 95% rename from cmd/nodecmd/validateSubnet.go rename to cmd/nodecmd/validate_subnet.go index a42e936ae..ebc952a5e 100644 --- a/cmd/nodecmd/validateSubnet.go +++ b/cmd/nodecmd/validate_subnet.go @@ -8,7 +8,7 @@ import ( "fmt" "time" - subnetcmd "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + blockchaincmd "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/constants" @@ -96,7 +96,7 @@ func addNodeAsSubnetValidator( } ux.Logger.PrintToUser("Adding the node as a Subnet Validator...") defer ux.Logger.PrintLineSeparator() - if err := subnetcmd.CallAddValidator( + if err := blockchaincmd.CallAddValidator( deployer, network, kc, @@ -176,7 +176,7 @@ func validateSubnet(_ *cobra.Command, args []string) error { if err := checkCluster(clusterName); err != nil { return err } - if _, err := subnetcmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { + if _, err := blockchaincmd.ValidateSubnetNameAndGetChains([]string{subnetName}); err != nil { return err } @@ -211,7 +211,7 @@ func validateSubnet(_ *cobra.Command, args []string) error { nonPrimaryValidators++ } } - fee := network.GenesisParams().AddPrimaryNetworkValidatorFee*uint64(nonPrimaryValidators) + network.GenesisParams().AddSubnetValidatorFee*uint64(len(hosts)) + fee := network.GenesisParams().TxFeeConfig.StaticFeeConfig.AddPrimaryNetworkValidatorFee*uint64(nonPrimaryValidators) + network.GenesisParams().TxFeeConfig.StaticFeeConfig.AddSubnetValidatorFee*uint64(len(hosts)) kc, err := keychain.GetKeychainFromCmdLineFlags( app, constants.PayTxsFeesMsg, @@ -225,7 +225,7 @@ func validateSubnet(_ *cobra.Command, args []string) error { if err != nil { return err } - if err := subnetcmd.UpdateKeychainWithSubnetControlKeys(kc, network, subnetName); err != nil { + if err := blockchaincmd.UpdateKeychainWithSubnetControlKeys(kc, network, subnetName); err != nil { return err } diff --git a/cmd/nodecmd/whitelist.go b/cmd/nodecmd/whitelist.go index aebc078ca..d7ec66172 100644 --- a/cmd/nodecmd/whitelist.go +++ b/cmd/nodecmd/whitelist.go @@ -286,6 +286,9 @@ func whitelistSSHPubKey(clusterName string, pubkey string) error { func getCloudSecurityGroupList(clusterNodes []string) ([]regionSecurityGroup, error) { cloudSecurityGroupList := []regionSecurityGroup{} for _, node := range clusterNodes { + if !utils.FileExists(app.GetNodeConfigPath(node)) { + continue + } nodeConfig, err := app.LoadClusterNodeConfig(node) if err != nil { ux.Logger.PrintToUser("Failed to parse node %s due to %s", node, err.Error()) diff --git a/cmd/nodecmd/wiz.go b/cmd/nodecmd/wiz.go index 40747ca65..61cf9e63c 100644 --- a/cmd/nodecmd/wiz.go +++ b/cmd/nodecmd/wiz.go @@ -4,16 +4,20 @@ package nodecmd import ( "fmt" + "os" + "os/signal" + "path/filepath" "strconv" "strings" "sync" + "syscall" "time" + "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/metrics" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/cmd/teleportercmd" - "github.com/ava-labs/avalanche-cli/cmd/teleportercmd/relayercmd" "github.com/ava-labs/avalanche-cli/pkg/ansible" awsAPI "github.com/ava-labs/avalanche-cli/pkg/cloud/aws" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" @@ -52,7 +56,8 @@ var ( evmVersion string evmChainID uint64 evmToken string - evmDefaults bool + evmTestDefaults bool + evmProductionDefaults bool useLatestEvmReleasedVersion bool useLatestEvmPreReleasedVersion bool customVMRepoURL string @@ -108,7 +113,9 @@ The node wiz command creates a devnet and deploys, sync and validate a subnet in cmd.Flags().StringVar(&evmVersion, "evm-version", "", "version of Subnet-EVM to use") cmd.Flags().Uint64Var(&evmChainID, "evm-chain-id", 0, "chain ID to use with Subnet-EVM") cmd.Flags().StringVar(&evmToken, "evm-token", "", "token name to use with Subnet-EVM") - cmd.Flags().BoolVar(&evmDefaults, "evm-defaults", false, "use default settings for fees/airdrop/precompiles with Subnet-EVM") + cmd.Flags().BoolVar(&evmProductionDefaults, "evm-defaults", false, "use default production settings with Subnet-EVM") + cmd.Flags().BoolVar(&evmProductionDefaults, "evm-production-defaults", false, "use default production settings for your blockchain") + cmd.Flags().BoolVar(&evmTestDefaults, "evm-test-defaults", false, "use default test settings for your blockchain") cmd.Flags().BoolVar(&useLatestEvmReleasedVersion, "latest-evm-version", false, "use latest Subnet-EVM released version") cmd.Flags().BoolVar(&useLatestEvmPreReleasedVersion, "latest-pre-released-evm-version", false, "use latest Subnet-EVM pre-released version") cmd.Flags().StringVar(&customVMRepoURL, "custom-vm-repo-url", "", "custom vm repository url") @@ -136,9 +143,11 @@ The node wiz command creates a devnet and deploys, sync and validate a subnet in cmd.Flags().StringVar(&teleporterMessengerDeployerAddressPath, "teleporter-messenger-deployer-address-path", "", "path to a teleporter messenger deployer address file") cmd.Flags().StringVar(&teleporterMessengerDeployerTxPath, "teleporter-messenger-deployer-tx-path", "", "path to a teleporter messenger deployer tx file") cmd.Flags().StringVar(&teleporterRegistryBydecodePath, "teleporter-registry-bytecode-path", "", "path to a teleporter registry bytecode file") - cmd.Flags().BoolVar(&deployTeleporterMessenger, "deploy-teleporter-messenger", true, "deploy Teleporter Messenger") - cmd.Flags().BoolVar(&deployTeleporterRegistry, "deploy-teleporter-registry", true, "deploy Teleporter Registry") + cmd.Flags().BoolVar(&deployTeleporterMessenger, "deploy-teleporter-messenger", true, "deploy Interchain Messenger") + cmd.Flags().BoolVar(&deployTeleporterRegistry, "deploy-teleporter-registry", true, "deploy Interchain Registry") cmd.Flags().BoolVar(&replaceKeyPair, "auto-replace-keypair", false, "automatically replaces key pair to access node if previous key pair is not found") + cmd.Flags().BoolVar(&publicHTTPPortAccess, "public-http-port", false, "allow public access to avalanchego HTTP port") + cmd.Flags().StringSliceVar(&subnetAliases, "subnet-aliases", nil, "additional subnet aliases to be used for RPC calls in addition to subnet blockchain name") return cmd } @@ -148,6 +157,18 @@ func wiz(cmd *cobra.Command, args []string) error { if len(args) > 1 { subnetName = args[1] } + c := make(chan os.Signal, 1) + // Destroy cluster if user calls ctrl ^ c + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + for range c { + if err := CallDestroyNode(clusterName); err != nil { + ux.Logger.RedXToUser("Unable to delete cluster %s due to %s", clusterName, err) + ux.Logger.RedXToUser("Please try again by calling avalanche node destroy %s", clusterName) + } + os.Exit(0) + } + }() clusterAlreadyExists, err := app.ClusterExists(clusterName) if err != nil { return err @@ -164,7 +185,7 @@ func wiz(cmd *cobra.Command, args []string) error { ux.Logger.PrintToUser("") ux.Logger.PrintToUser(logging.Green.Wrap("Creating the subnet")) ux.Logger.PrintToUser("") - if err := subnetcmd.CallCreate( + if err := blockchaincmd.CallCreate( cmd, subnetName, forceSubnetCreate, @@ -174,7 +195,8 @@ func wiz(cmd *cobra.Command, args []string) error { evmVersion, evmChainID, evmToken, - evmDefaults, + evmProductionDefaults, + evmTestDefaults, useLatestEvmReleasedVersion, useLatestEvmPreReleasedVersion, customVMRepoURL, @@ -184,7 +206,7 @@ func wiz(cmd *cobra.Command, args []string) error { return err } if chainConf != "" || subnetConf != "" || nodeConf != "" { - if err := subnetcmd.CallConfigure( + if err := blockchaincmd.CallConfigure( cmd, subnetName, chainConf, @@ -264,6 +286,7 @@ func wiz(cmd *cobra.Command, args []string) error { ux.Logger.PrintToUser(logging.Green.Wrap("Adding nodes as subnet validators")) ux.Logger.PrintToUser("") avoidSubnetValidationChecks = true + useEwoq = true if err := validateSubnet(cmd, []string{clusterName, subnetName}); err != nil { return err } @@ -361,7 +384,12 @@ func wiz(cmd *cobra.Command, args []string) error { ux.Logger.PrintToUser(logging.Green.Wrap("Setting up teleporter on subnet")) ux.Logger.PrintToUser("") flags := teleportercmd.DeployFlags{ - SubnetName: subnetName, + ChainFlags: contract.ChainSpec{ + BlockchainName: subnetName, + }, + PrivateKeyFlags: contract.PrivateKeyFlags{ + KeyName: constants.ICMKeyName, + }, Network: networkoptions.NetworkFlags{ ClusterName: clusterName, }, @@ -382,7 +410,7 @@ func wiz(cmd *cobra.Command, args []string) error { if err := updateAWMRelayerFunds(network, sc, blockchainID); err != nil { return err } - if err := updateAWMRelayerHostConfig(awmRelayerHost, subnetName, clusterName); err != nil { + if err := updateAWMRelayerHostConfig(network, awmRelayerHost, subnetName); err != nil { return err } } @@ -487,15 +515,9 @@ func setAWMRelayerHost(host *models.Host) error { return app.CreateNodeCloudConfigFile(cloudID, &nodeConfig) } -func updateAWMRelayerHostConfig(host *models.Host, subnetName string, clusterName string) error { - ux.Logger.PrintToUser("setting AWM Relayer on host %s to relay subnet %s", host.GetCloudID(), subnetName) - flags := relayercmd.AddSubnetToServiceFlags{ - Network: networkoptions.NetworkFlags{ - ClusterName: clusterName, - }, - CloudNodeID: host.GetCloudID(), - } - if err := relayercmd.CallAddSubnetToService(subnetName, flags); err != nil { +func updateAWMRelayerHostConfig(network models.Network, host *models.Host, blockchainName string) error { + ux.Logger.PrintToUser("setting AWM Relayer on host %s to relay blockchain %s", host.GetCloudID(), blockchainName) + if err := addBlockchainToRelayerConf(network, host.GetCloudID(), blockchainName); err != nil { return err } if err := ssh.RunSSHUploadNodeAWMRelayerConfig(host, app.GetNodeInstanceDirPath(host.GetCloudID())); err != nil { @@ -980,3 +1002,95 @@ func setUpSubnetLogging(clusterName, subnetName string) error { spinSession.Stop() return nil } + +func addBlockchainToRelayerConf(network models.Network, cloudNodeID string, blockchainName string) error { + relayerAddress, relayerPrivateKey, err := teleporter.GetRelayerKeyInfo(app.GetKeyPath(constants.AWMRelayerKeyName)) + if err != nil { + return err + } + + storageBasePath := constants.AWMRelayerDockerDir + configBasePath := app.GetNodeInstanceDirPath(cloudNodeID) + + configPath := app.GetAWMRelayerServiceConfigPath(configBasePath) + if err := os.MkdirAll(filepath.Dir(configPath), constants.DefaultPerms755); err != nil { + return err + } + ux.Logger.PrintToUser("updating configuration file %s", configPath) + + if err := teleporter.CreateBaseRelayerConfigIfMissing( + configPath, + logging.Info.LowerString(), + app.GetAWMRelayerServiceStorageDir(storageBasePath), + constants.RemoteAWMRelayerMetricsPort, + network, + ); err != nil { + return err + } + + chainSpec := contract.ChainSpec{CChain: true} + subnetID, err := contract.GetSubnetID(app, network, chainSpec) + if err != nil { + return err + } + blockchainID, err := contract.GetBlockchainID(app, network, chainSpec) + if err != nil { + return err + } + registryAddress, messengerAddress, err := contract.GetICMInfo(app, network, chainSpec, false, false, false) + if err != nil { + return err + } + rpcEndpoint, wsEndpoint, err := contract.GetBlockchainEndpoints(app, network, chainSpec, false, false) + if err != nil { + return err + } + + if err = teleporter.AddSourceAndDestinationToRelayerConfig( + configPath, + rpcEndpoint, + wsEndpoint, + subnetID.String(), + blockchainID.String(), + registryAddress, + messengerAddress, + relayerAddress, + relayerPrivateKey, + ); err != nil { + return err + } + + chainSpec = contract.ChainSpec{BlockchainName: blockchainName} + subnetID, err = contract.GetSubnetID(app, network, chainSpec) + if err != nil { + return err + } + blockchainID, err = contract.GetBlockchainID(app, network, chainSpec) + if err != nil { + return err + } + registryAddress, messengerAddress, err = contract.GetICMInfo(app, network, chainSpec, false, false, false) + if err != nil { + return err + } + rpcEndpoint, wsEndpoint, err = contract.GetBlockchainEndpoints(app, network, chainSpec, false, false) + if err != nil { + return err + } + + if err = teleporter.AddSourceAndDestinationToRelayerConfig( + configPath, + rpcEndpoint, + wsEndpoint, + subnetID.String(), + blockchainID.String(), + registryAddress, + messengerAddress, + relayerAddress, + relayerPrivateKey, + ); err != nil { + return err + } + + return nil +} diff --git a/cmd/primarycmd/addValidator.go b/cmd/primarycmd/add_validator.go similarity index 89% rename from cmd/primarycmd/addValidator.go rename to cmd/primarycmd/add_validator.go index 80bbfea86..e3a017654 100644 --- a/cmd/primarycmd/addValidator.go +++ b/cmd/primarycmd/add_validator.go @@ -9,8 +9,8 @@ import ( "math" "time" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/cmd/nodecmd" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" "github.com/ava-labs/avalanche-cli/pkg/application" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/constants" @@ -26,19 +26,23 @@ import ( var ( globalNetworkFlags networkoptions.NetworkFlags - addValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Fuji, networkoptions.Mainnet} - keyName string - useLedger bool - ledgerAddresses []string - nodeIDStr string - weight uint64 - delegationFee uint32 - startTimeStr string - duration time.Duration - publicKey string - pop string - ErrMutuallyExlusiveKeyLedger = errors.New("--key and --ledger,--ledger-addrs are mutually exclusive") - ErrStoredKeyOnMainnet = errors.New("--key is not available for mainnet operations") + addValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Fuji, + networkoptions.Mainnet, + networkoptions.Devnet, + } + keyName string + useLedger bool + ledgerAddresses []string + nodeIDStr string + weight uint64 + delegationFee uint32 + startTimeStr string + duration time.Duration + publicKey string + pop string + ErrMutuallyExlusiveKeyLedger = errors.New("--key and --ledger,--ledger-addrs are mutually exclusive") + ErrStoredKeyOnMainnet = errors.New("--key is not available for mainnet operations") ) type jsonProofOfPossession struct { @@ -119,7 +123,7 @@ func addValidator(_ *cobra.Command, _ []string) error { app, "", globalNetworkFlags, - false, + true, false, addValidatorSupportedNetworkOptions, "", @@ -154,7 +158,7 @@ func addValidator(_ *cobra.Command, _ []string) error { } if nodeIDStr == "" { - nodeID, err = subnetcmd.PromptNodeID() + nodeID, err = blockchaincmd.PromptNodeID() if err != nil { return err } @@ -179,7 +183,7 @@ func addValidator(_ *cobra.Command, _ []string) error { return fmt.Errorf("illegal weight, must be greater than or equal to %d: %d", minValStake, weight) } - fee := network.GenesisParams().AddPrimaryNetworkValidatorFee + fee := network.GenesisParams().TxFeeConfig.StaticFeeConfig.AddPrimaryNetworkValidatorFee kc, err := keychain.GetKeychain(app, false, useLedger, ledgerAddresses, keyName, network, fee) if err != nil { return err diff --git a/cmd/root.go b/cmd/root.go index e386c5bea..00ea688a2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -12,14 +12,15 @@ import ( "time" "github.com/ava-labs/avalanche-cli/cmd/backendcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/cmd/configcmd" "github.com/ava-labs/avalanche-cli/cmd/contractcmd" "github.com/ava-labs/avalanche-cli/cmd/interchaincmd" + "github.com/ava-labs/avalanche-cli/cmd/interchaincmd/tokentransferrercmd" "github.com/ava-labs/avalanche-cli/cmd/keycmd" "github.com/ava-labs/avalanche-cli/cmd/networkcmd" "github.com/ava-labs/avalanche-cli/cmd/nodecmd" "github.com/ava-labs/avalanche-cli/cmd/primarycmd" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" "github.com/ava-labs/avalanche-cli/cmd/teleportercmd" "github.com/ava-labs/avalanche-cli/cmd/transactioncmd" "github.com/ava-labs/avalanche-cli/cmd/updatecmd" @@ -75,7 +76,7 @@ in with avalanche subnet create myNewSubnet.`, BoolVar(&skipCheck, constants.SkipUpdateFlag, false, "skip check for new versions") // add sub commands - rootCmd.AddCommand(subnetcmd.NewCmd(app)) + rootCmd.AddCommand(blockchaincmd.NewCmd(app)) rootCmd.AddCommand(primarycmd.NewCmd(app)) rootCmd.AddCommand(networkcmd.NewCmd(app)) rootCmd.AddCommand(keycmd.NewCmd(app)) @@ -101,6 +102,28 @@ in with avalanche subnet create myNewSubnet.`, // add interchain command rootCmd.AddCommand(interchaincmd.NewCmd(app)) + // add ictt command + subcmd := tokentransferrercmd.NewCmd(app) + subcmd.Use = "ictt" + subcmd.Short = "Manage Interchain Token Transferrers (shorthand for `interchain TokenTransferrer`)" + subcmd.Long = "The ictt command suite provides tools to deploy and manage Interchain Token Transferrers." + rootCmd.AddCommand(subcmd) + + // add subnet command + subcmd = blockchaincmd.NewCmd(app) + subcmd.Use = "subnet" + subcmd.Short = "Create and deploy blockchains (deprecation notice: use 'avalanche blockchain')" + subcmd.Long = `The subnet command suite provides a collection of tools for developing +and deploying Blockchains. + +To get started, use the subnet create command wizard to walk through the +configuration of your very first Blockchain. Then, go ahead and deploy it +with the subnet deploy command. You can use the rest of the commands to +manage your Blockchain configurations and live deployments. + +Deprecation notice: use 'avalanche blockchain'` + rootCmd.AddCommand(subcmd) + // add contract command rootCmd.AddCommand(contractcmd.NewCmd(app)) diff --git a/cmd/subnetcmd/addDelegator.go b/cmd/subnetcmd/addDelegator.go deleted file mode 100644 index b484067f6..000000000 --- a/cmd/subnetcmd/addDelegator.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package subnetcmd - -import ( - "errors" - "fmt" - "os" - "time" - - "github.com/ava-labs/avalanche-cli/pkg/cobrautils" - "github.com/ava-labs/avalanche-cli/pkg/constants" - "github.com/ava-labs/avalanche-cli/pkg/keychain" - "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/networkoptions" - "github.com/ava-labs/avalanche-cli/pkg/prompts" - "github.com/ava-labs/avalanche-cli/pkg/subnet" - "github.com/ava-labs/avalanche-cli/pkg/ux" - "github.com/ava-labs/avalanchego/genesis" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" - "github.com/spf13/cobra" -) - -var addPermissionlessDelegatorSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Fuji, networkoptions.Mainnet} - -// avalanche subnet addPermissionlessDelegator -func newAddPermissionlessDelegatorCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "addPermissionlessDelegator [subnetName]", - Short: "Allow a node join an existing subnet validator as a delegator", - Long: `The subnet addDelegator enables a node (the delegator) to stake -AVAX and specify a validator (the delegatee) to validate on their behalf. The -delegatee has an increased probability of being sampled by other validators -(weight) in proportion to the stake delegated to them. - -The delegatee charges a fee to the delegator; the former receives a percentage -of the delegator’s validation reward (if any.) A transaction that delegates -stake has no fee. - -The delegation period must be a subset of the period that the delegatee -validates the Primary Network. - -To add a node as a delegator, you first need to provide -the subnetID and the validator's unique NodeID. The command then prompts -for the validation start time, duration, and stake weight. You can bypass -these prompts by providing the values with flags.`, - RunE: addPermissionlessDelegator, - Args: cobrautils.ExactArgs(1), - } - - networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, addPermissionlessDelegatorSupportedNetworkOptions) - cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [fuji deploy only]") - cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on fuji)") - cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses") - cmd.Flags().StringVar(&nodeIDStr, "nodeID", "", "set the NodeID of the validator to delegate to") - cmd.Flags().Uint64Var(&stakeAmount, "stake-amount", 0, "amount of tokens to stake") - cmd.Flags().StringVar(&startTimeStr, "start-time", "", "start time that delegator starts delegating") - cmd.Flags().DurationVar(&duration, "staking-period", 0, "how long delegator should delegate for after start time") - - return cmd -} - -func addPermissionlessDelegator(_ *cobra.Command, args []string) error { - chains, err := ValidateSubnetNameAndGetChains(args) - if err != nil { - return err - } - subnetName := chains[0] - sc, err := app.LoadSidecar(subnetName) - if err != nil { - return err - } - - network, err := networkoptions.GetNetworkFromCmdLineFlags( - app, - "", - globalNetworkFlags, - true, - false, - addPermissionlessDelegatorSupportedNetworkOptions, - "", - ) - if err != nil { - return err - } - - if outputTxPath != "" { - if _, err := os.Stat(outputTxPath); err == nil { - return fmt.Errorf("outputTxPath %q already exists", outputTxPath) - } - } - - if len(ledgerAddresses) > 0 { - useLedger = true - } - - if useLedger && keyName != "" { - return ErrMutuallyExlusiveKeyLedger - } - subnetID := sc.Networks[network.Name()].SubnetID - if os.Getenv(constants.SimulatePublicNetwork) != "" { - subnetID = sc.Networks[models.Local.String()].SubnetID - } - if subnetID == ids.Empty { - return errNoSubnetID - } - - nodeID, err := promptNodeIDToAdd(subnetID, false, network) - if err != nil { - return err - } - stakedTokenAmount, err := promptStakeAmount(subnetName, false, network) - if err != nil { - return err - } - start, stakeDuration, err := getTimeParameters(network, nodeID, false) - if err != nil { - return err - } - endTime := start.Add(stakeDuration) - - switch network.Kind { - case models.Local: - return handleAddPermissionlessDelegatorLocal(subnetName, network, nodeID, stakedTokenAmount, start, endTime) - case models.Fuji: - if !useLedger && keyName == "" { - useLedger, keyName, err = prompts.GetKeyOrLedger(app.Prompt, constants.PayTxsFeesMsg, app.GetKeyDir(), false) - if err != nil { - return err - } - } - case models.Mainnet: - return errors.New("addPermissionlessDelegator is not yet supported on Mainnet") - } - - // get keychain accessor - fee := network.GenesisParams().AddSubnetDelegatorFee - kc, err := keychain.GetKeychain(app, false, useLedger, ledgerAddresses, keyName, network, fee) - if err != nil { - return err - } - - network.HandlePublicNetworkSimulation() - - recipientAddr := kc.Addresses().List()[0] - deployer := subnet.NewPublicDeployer(app, kc, network) - assetID, err := getSubnetAssetID(subnetID, network) - if err != nil { - return err - } - txID, err := deployer.AddPermissionlessDelegator(subnetID, assetID, nodeID, stakedTokenAmount, uint64(start.Unix()), uint64(endTime.Unix()), recipientAddr) - if err != nil { - return err - } - printAddPermissionlessDelOutput(txID, nodeID, network, start, endTime, stakedTokenAmount) - return nil -} - -func printAddPermissionlessDelOutput(txID ids.ID, nodeID ids.NodeID, network models.Network, start time.Time, endTime time.Time, stakedTokenAmount uint64) { - ux.Logger.PrintToUser("Node successfully added as delegator!") - ux.Logger.PrintToUser("TX ID: %s", txID.String()) - ux.Logger.PrintToUser("NodeID: %s", nodeID.String()) - ux.Logger.PrintToUser("Network: %s", network.Name()) - ux.Logger.PrintToUser("Start time: %s", start.UTC().Format(constants.TimeParseLayout)) - ux.Logger.PrintToUser("End time: %s", endTime.Format(constants.TimeParseLayout)) - ux.Logger.PrintToUser("Stake Amount: %d", stakedTokenAmount) -} - -func handleAddPermissionlessDelegatorLocal(subnetName string, network models.Network, nodeID ids.NodeID, - stakedTokenAmount uint64, start time.Time, endTime time.Time, -) error { - sc, err := app.LoadSidecar(subnetName) - if err != nil { - return err - } - - if !checkIfSubnetIsElasticOnLocal(sc) { - return fmt.Errorf("%s is not an elastic subnet", subnetName) - } - ux.Logger.PrintToUser("Inputs complete, issuing transaction addPermissionlessDelegatorTx...") - ux.Logger.PrintToUser("") - assetID := sc.ElasticSubnet[network.Name()].AssetID - testKey := genesis.EWOQKey - keyChain := secp256k1fx.NewKeychain(testKey) - subnetID := sc.Networks[network.Name()].SubnetID - txID, err := subnet.IssueAddPermissionlessDelegatorTx(keyChain, subnetID, nodeID, stakedTokenAmount, assetID, uint64(start.Unix()), uint64(endTime.Unix())) - if err != nil { - return err - } - printAddPermissionlessDelOutput(txID, nodeID, network, start, endTime, stakedTokenAmount) - return nil -} diff --git a/cmd/subnetcmd/elastic.go b/cmd/subnetcmd/elastic.go deleted file mode 100644 index 5fdc26616..000000000 --- a/cmd/subnetcmd/elastic.go +++ /dev/null @@ -1,646 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package subnetcmd - -import ( - "context" - "errors" - "fmt" - "math" - "os" - "strings" - "time" - - "github.com/ava-labs/avalanche-cli/pkg/cobrautils" - "github.com/ava-labs/avalanche-cli/pkg/constants" - es "github.com/ava-labs/avalanche-cli/pkg/elasticsubnet" - "github.com/ava-labs/avalanche-cli/pkg/keychain" - "github.com/ava-labs/avalanche-cli/pkg/metrics" - "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/networkoptions" - "github.com/ava-labs/avalanche-cli/pkg/prompts" - subnet "github.com/ava-labs/avalanche-cli/pkg/subnet" - "github.com/ava-labs/avalanche-cli/pkg/txutils" - "github.com/ava-labs/avalanche-cli/pkg/utils" - "github.com/ava-labs/avalanche-cli/pkg/ux" - "github.com/ava-labs/avalanchego/genesis" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/vms/components/verify" - "github.com/ava-labs/avalanchego/vms/platformvm" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" - "github.com/olekukonko/tablewriter" - "github.com/spf13/cobra" -) - -const ( - localDeployment = "Existing local deployment" - fujiDeployment = "Fuji" - mainnetDeployment = "Mainnet (coming soon)" - subnetIsElasticError = "subnet is already elastic" -) - -var ( - elasticSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Fuji, networkoptions.Mainnet} - tokenNameFlag string - tokenSymbolFlag string - useDefaultConfig bool - overrideWarning bool - transformValidators bool - denominationFlag int -) - -// avalanche subnet elastic -func newElasticCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "elastic [subnetName]", - Short: "Transforms a subnet into elastic subnet", - Long: `The elastic command enables anyone to be a validator of a Subnet by simply staking its token on the -P-Chain. When enabling Elastic Validation, the creator permanently locks the Subnet from future modification -(they relinquish their control keys), specifies an Avalanche Native Token (ANT) that validators must use for staking -and that will be distributed as staking rewards, and provides a set of parameters that govern how the Subnet’s staking -mechanics will work.`, - Args: cobrautils.ExactArgs(1), - RunE: transformElasticSubnet, - PersistentPostRun: handlePostRun, - } - networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, elasticSupportedNetworkOptions) - cmd.Flags().StringVar(&tokenNameFlag, "tokenName", "", "specify the token name") - cmd.Flags().StringVar(&tokenSymbolFlag, "tokenSymbol", "", "specify the token symbol") - cmd.Flags().BoolVar(&useDefaultConfig, "default", false, "use default elastic subnet config values") - cmd.Flags().BoolVar(&overrideWarning, "force", false, "override transform into elastic subnet warning") - cmd.Flags().Uint64Var(&stakeAmount, "stake-amount", 0, "amount of tokens to stake on validator") - cmd.Flags().StringVar(&startTimeStr, "start-time", "", "start time that validator starts validating") - cmd.Flags().DurationVar(&duration, "staking-period", 0, "how long validator validates for after start time") - cmd.Flags().BoolVar(&transformValidators, "transform-validators", false, "transform validators to permissionless validators") - cmd.Flags().IntVar(&denominationFlag, "denomination", -1, "specify the token denomination") - cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on fuji)") - cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses") - cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [fuji only]") - cmd.Flags().StringSliceVar(&subnetAuthKeys, "subnet-auth-keys", nil, "control keys that will be used to authenticate the transformSubnet tx") - cmd.Flags().StringVar(&outputTxPath, "output-tx-path", "", "file path of the transformSubnet tx") - return cmd -} - -func checkIfSubnetIsElasticOnLocal(sc models.Sidecar) bool { - if _, ok := sc.ElasticSubnet[models.Local.String()]; ok { - return true - } - return false -} - -func createAssetID(deployer *subnet.PublicDeployer, - maxSupply uint64, - tokenName string, - tokenSymbol string, - tokenDenomination int, - recipientAddr ids.ShortID, -) (ids.ID, error) { - if tokenDenomination > math.MaxUint8 { - return ids.Empty, errors.New("token denomination cannot exceed 32") - } - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - recipientAddr, - }, - } - initialState := map[uint32][]verify.State{ - 0: { - &secp256k1fx.TransferOutput{ - Amt: maxSupply, - OutputOwners: *owner, - }, - }, - } - return deployer.CreateAssetTx(tokenName, tokenSymbol, byte(tokenDenomination), initialState) -} - -func exportToPChain(deployer *subnet.PublicDeployer, - subnetAssetID ids.ID, - recipientAddr ids.ShortID, - maxSupply uint64, -) (ids.ID, error) { - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - recipientAddr, - }, - } - return deployer.ExportToPChainTx(subnetAssetID, owner, maxSupply) -} - -func importFromXChain(deployer *subnet.PublicDeployer, - recipientAddr ids.ShortID, -) (ids.ID, error) { - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - recipientAddr, - }, - } - return deployer.ImportFromXChain(owner) -} - -func transformElasticSubnet(cmd *cobra.Command, args []string) error { - subnetName := args[0] - - if err := DeploySubnetFirst(cmd, subnetName, false, elasticSupportedNetworkOptions); err != nil { - return err - } - - sc, err := app.LoadSidecar(subnetName) - if err != nil { - return fmt.Errorf("unable to load sidecar: %w", err) - } - - network, err := networkoptions.GetNetworkFromCmdLineFlags( - app, - "", - globalNetworkFlags, - true, - false, - elasticSupportedNetworkOptions, - subnetName, - ) - if err != nil { - return err - } - - if outputTxPath != "" { - if _, err := os.Stat(outputTxPath); err == nil { - return fmt.Errorf("outputTxPath %q already exists", outputTxPath) - } - } - - if len(ledgerAddresses) > 0 { - useLedger = true - } - - if useLedger && keyName != "" { - return ErrMutuallyExlusiveKeyLedger - } - - subnetID := sc.Networks[network.Name()].SubnetID - if os.Getenv(constants.SimulatePublicNetwork) != "" { - subnetID = sc.Networks[models.Local.String()].SubnetID - } - if subnetID == ids.Empty { - return errNoSubnetID - } - - if network.Kind != models.Local { - isAlreadyElastic, err := CheckSubnetIsElastic(subnetID, network) - if err != nil && err.Error() != subnetIsElasticError { - return err - } - if isAlreadyElastic { - return errors.New(subnetIsElasticError) - } - } - - tokenName := "" - if tokenNameFlag == "" { - tokenName, err = getTokenName() - if err != nil { - return err - } - } else { - tokenName = tokenNameFlag - } - - tokenSymbol := "" - if tokenSymbolFlag == "" { - tokenSymbol, err = getTokenSymbol() - if err != nil { - return err - } - } else { - tokenSymbol = tokenSymbolFlag - } - - tokenDenomination := 0 - if network.Kind != models.Local { - if denominationFlag == -1 { - tokenDenomination, err = getTokenDenomination() - if err != nil { - return err - } - } else { - tokenDenomination = denominationFlag - } - } - - elasticSubnetConfig, err := es.GetElasticSubnetConfig(app, tokenSymbol, useDefaultConfig) - if err != nil { - return err - } - elasticSubnetConfig.SubnetID = subnetID - - switch network.Kind { - case models.Local: - return transformElasticSubnetLocal(sc, subnetName, tokenName, tokenSymbol, elasticSubnetConfig, cmd) - case models.Fuji: - if !useLedger && keyName == "" { - useLedger, keyName, err = prompts.GetKeyOrLedger(app.Prompt, constants.PayTxsFeesMsg, app.GetKeyDir(), false) - if err != nil { - return err - } - } - case models.Mainnet: - return errors.New("unsupported network") - default: - return errors.New("unsupported network") - } - - // get keychain accessor - fee := network.GenesisParams().CreateAssetTxFee + network.GenesisParams().TransformSubnetTxFee + network.GenesisParams().TxFee*2 - - network.HandlePublicNetworkSimulation() - kc, err := keychain.GetKeychain(app, false, useLedger, ledgerAddresses, keyName, network, fee) - if err != nil { - return err - } - - recipientAddr := kc.Addresses().List()[0] - deployer := subnet.NewPublicDeployer(app, kc, network) - txHasOccurred, txID := checkIfTxHasOccurred(&sc, network, "CreateAssetTx") - var assetID ids.ID - // TODO: replace sleep functions with sticky API sessions - if txHasOccurred { - ux.Logger.PrintToUser(fmt.Sprintf("Skipping CreateAssetTx, transforming subnet with asset ID %s...", txID.String())) - assetID = txID - } else { - assetID, err = createAssetID(deployer, elasticSubnetConfig.MaxSupply, tokenName, tokenSymbol, tokenDenomination, recipientAddr) - if err != nil { - return err - } - err = app.UpdateSidecarElasticSubnetPartialTx(&sc, network, "CreateAssetTx", assetID) - if err != nil { - return err - } - // we need to sleep after each operation to make sure that UTXO is available for consumption - time.Sleep(5 * time.Second) - } - - txHasOccurred, _ = checkIfTxHasOccurred(&sc, network, "ExportTx") - if !txHasOccurred { - txID, err = exportToPChain(deployer, assetID, recipientAddr, elasticSubnetConfig.MaxSupply) - if err != nil { - return err - } - err = app.UpdateSidecarElasticSubnetPartialTx(&sc, network, "ExportTx", txID) - if err != nil { - return err - } - time.Sleep(5 * time.Second) - } else { - ux.Logger.PrintToUser("Skipping ExportTx...") - } - - txHasOccurred, _ = checkIfTxHasOccurred(&sc, network, "ImportTx") - if !txHasOccurred { - txID, err = importFromXChain(deployer, recipientAddr) - if err != nil { - return err - } - err = app.UpdateSidecarElasticSubnetPartialTx(&sc, network, "ImportTx", txID) - if err != nil { - return err - } - time.Sleep(5 * time.Second) - } else { - ux.Logger.PrintToUser("Skipping ImportTx...") - } - - transferSubnetOwnershipTxID := sc.Networks[network.Name()].TransferSubnetOwnershipTxID - - isPermissioned, controlKeys, threshold, err := txutils.GetOwners(network, subnetID) - if err != nil { - return err - } - if !isPermissioned { - return ErrNotPermissionedSubnet - } - - // add control keys to the keychain whenever possible - if err := kc.AddAddresses(controlKeys); err != nil { - return err - } - - kcKeys, err := kc.PChainFormattedStrAddresses() - if err != nil { - return err - } - - // get keys for add validator tx signing - if subnetAuthKeys != nil { - if err := prompts.CheckSubnetAuthKeys(kcKeys, subnetAuthKeys, controlKeys, threshold); err != nil { - return err - } - } else { - subnetAuthKeys, err = prompts.GetSubnetAuthKeys(app.Prompt, kcKeys, controlKeys, threshold) - if err != nil { - return err - } - } - ux.Logger.PrintToUser("Your subnet auth keys for issue transform subnet tx: %s", subnetAuthKeys) - - isFullySigned, txID, tx, remainingSubnetAuthKeys, err := deployer.TransformSubnetTx( - controlKeys, - subnetAuthKeys, - elasticSubnetConfig, - subnetID, - transferSubnetOwnershipTxID, - assetID, - ) - if err != nil { - return err - } - flags := make(map[string]string) - flags[constants.MetricsNetwork] = network.Name() - if !isFullySigned { - flags[constants.MultiSig] = "multi-sig" - } else { - flags[constants.MultiSig] = "non-multi-sig" - } - metrics.HandleTracking(cmd, constants.MetricsSubnetElasticCommand, app, flags) - if !isFullySigned { - if err := SaveNotFullySignedTx( - "Transform Subnet", - tx, - subnetName, - subnetAuthKeys, - remainingSubnetAuthKeys, - outputTxPath, - false, - ); err != nil { - return err - } - } else { - elasticSubnetConfig.AssetID = assetID - if err = app.CreateElasticSubnetConfig(subnetName, &elasticSubnetConfig); err != nil { - return err - } - if err = app.UpdateSidecarElasticSubnet(&sc, network, subnetID, assetID, txID, tokenName, tokenSymbol); err != nil { - return fmt.Errorf("elastic subnet transformation was successful, but failed to update sidecar: %w", err) - } - PrintTransformResults(subnetName, txID, subnetID, tokenName, tokenSymbol, assetID) - } - return nil -} - -func transformElasticSubnetLocal(sc models.Sidecar, subnetName string, tokenName string, tokenSymbol string, elasticSubnetConfig models.ElasticSubnetConfig, cmd *cobra.Command) error { - if checkIfSubnetIsElasticOnLocal(sc) { - return fmt.Errorf("%s is already an elastic subnet", subnetName) - } - var err error - subnetID := sc.Networks[models.Local.String()].SubnetID - if subnetID == ids.Empty { - return errNoSubnetID - } - - if !overrideWarning { - yes, err := app.Prompt.CaptureNoYes("WARNING: Transforming a Permissioned Subnet into an Elastic Subnet is an irreversible operation. Continue?") - if err != nil { - return err - } - if !yes { - return nil - } - } - - ux.Logger.PrintToUser("Starting Elastic Subnet Transformation") - cancel := make(chan struct{}) - go ux.PrintWait(cancel) - testKey := genesis.EWOQKey - keyChain := secp256k1fx.NewKeychain(testKey) - txID, assetID, err := subnet.IssueTransformSubnetTx(elasticSubnetConfig, keyChain, subnetID, tokenName, tokenSymbol, elasticSubnetConfig.MaxSupply) - close(cancel) - if err != nil { - return err - } - ux.Logger.PrintToUser("") - ux.Logger.PrintToUser("Subnet Successfully Transformed To Elastic Subnet!") - - elasticSubnetConfig.AssetID = assetID - if err = app.CreateElasticSubnetConfig(subnetName, &elasticSubnetConfig); err != nil { - return err - } - if err = app.UpdateSidecarElasticSubnet(&sc, models.NewLocalNetwork(), subnetID, assetID, txID, tokenName, tokenSymbol); err != nil { - return fmt.Errorf("elastic subnet transformation was successful, but failed to update sidecar: %w", err) - } - - if !transformValidators { - if !overrideWarning { - yes, err := app.Prompt.CaptureNoYes("Do you want to transform existing validators to permissionless validators with equal weight? " + - "Press if you want to customize the structure of your permissionless validators") - if err != nil { - return err - } - if !yes { - return nil - } - ux.Logger.PrintToUser("Transforming validators to permissionless validators") - if err = transformValidatorsToPermissionlessLocal(sc, subnetID, subnetName); err != nil { - return err - } - } - } else { - ux.Logger.PrintToUser("Transforming validators to permissionless validators") - if err = transformValidatorsToPermissionlessLocal(sc, subnetID, subnetName); err != nil { - return err - } - } - - PrintTransformResults(subnetName, txID, subnetID, tokenName, tokenSymbol, assetID) - flags := make(map[string]string) - flags[constants.MetricsNetwork] = models.Local.String() - metrics.HandleTracking(cmd, constants.MetricsSubnetElasticCommand, app, flags) - return nil -} - -func PrintTransformResults(chain string, txID ids.ID, subnetID ids.ID, tokenName string, tokenSymbol string, assetID ids.ID) { - const art = "\n ______ _ _ _ _____ _ _ _______ __ _____ __ _ " + - "\n | ____| | | | (_) / ____| | | | | |__ __| / _| / ____| / _| | |" + - "\n | |__ | | __ _ ___| |_ _ ___ | (___ _ _| |__ _ __ ___| |_ | |_ __ __ _ _ __ ___| |_ ___ _ __ _ __ ___ | (___ _ _ ___ ___ ___ ___ ___| |_ _ _| |" + - "\n | __| | |/ _` / __| __| |/ __| \\___ \\| | | | '_ \\| '_ \\ / _ \\ __| | | '__/ _` | '_ \\/ __| _/ _ \\| '__| '_ ` _ \\ \\___ \\| | | |/ __/ __/ _ \\/ __/ __| _| | | | |" + - "\n | |____| | (_| \\__ \\ |_| | (__ ____) | |_| | |_) | | | | __/ |_ | | | | (_| | | | \\__ \\ || (_) | | | | | | | | ____) | |_| | (_| (_| __/\\__ \\__ \\ | | |_| | |" + - "\n |______|_|\\__,_|___/\\__|_|\\___| |_____/ \\__,_|_.__/|_| |_|\\___|\\__| |_|_| \\__,_|_| |_|___/_| \\___/|_| |_| |_| |_| |_____/ \\__,_|\\___\\___\\___||___/___/_| \\__,_|_|" + - "\n" - fmt.Print(logging.LightBlue.Wrap(art)) - - table := tablewriter.NewWriter(os.Stdout) - table.SetRowLine(true) - table.SetAutoMergeCells(true) - table.Append([]string{"Token Name", tokenName}) - table.Append([]string{"Token Symbol", tokenSymbol}) - table.Append([]string{"Asset ID", assetID.String()}) - table.Append([]string{"Chain Name", chain}) - table.Append([]string{"Subnet ID", subnetID.String()}) - table.Append([]string{"P-Chain TXID", txID.String()}) - table.Render() -} - -func getTokenName() (string, error) { - ux.Logger.PrintToUser("Select a name for your subnet's native token") - tokenName, err := app.Prompt.CaptureString("Token name") - if err != nil { - return "", err - } - return tokenName, nil -} - -func getTokenSymbol() (string, error) { - ux.Logger.PrintToUser("Select a symbol for your subnet's native token") - tokenSymbol, err := app.Prompt.CaptureString("Token symbol") - if err != nil { - return "", err - } - return tokenSymbol, nil -} - -func checkAllLocalNodesAreCurrentValidators(subnetID ids.ID) error { - api := constants.LocalAPIEndpoint - pClient := platformvm.NewClient(api) - - ctx := context.Background() - validators, err := pClient.GetCurrentValidators(ctx, subnetID, nil) - if err != nil { - return err - } - defaultLocalNetworkNodeIDs, err := getLocalNetworkIDs() - if err != nil { - return err - } - for _, localVal := range defaultLocalNetworkNodeIDs { - currentValidator := false - for _, validator := range validators { - if validator.NodeID.String() == localVal { - currentValidator = true - } - } - if !currentValidator { - return fmt.Errorf("%s is still not a current validator of the elastic subnet", localVal) - } - } - return nil -} - -func transformValidatorsToPermissionlessLocal(sc models.Sidecar, subnetID ids.ID, subnetName string) error { - stakedTokenAmount, err := promptStakeAmount(subnetName, true, models.NewLocalNetwork()) - if err != nil { - return err - } - - validators, err := subnet.GetSubnetValidators(subnetID) - if err != nil { - return err - } - - validatorList := make([]ids.NodeID, len(validators)) - for i, v := range validators { - validatorList[i] = v.NodeID - } - - numToRemoveInitially := len(validatorList) - 1 - for _, validator := range validatorList { - // Remove first 4 nodes locally, wait for minimum lead time (25 seconds) and then remove the last node - // so that we don't end up with a subnet without any current validators - if numToRemoveInitially > 0 { - err = handleRemoveAndAddValidators(sc, subnetID, validator, stakedTokenAmount) - if err != nil { - return err - } - numToRemoveInitially -= 1 - } else { - ux.Logger.PrintToUser("Waiting for the first four nodes to be activated as permissionless validators...") - time.Sleep(constants.StakingMinimumLeadTime) - err = handleRemoveAndAddValidators(sc, subnetID, validator, stakedTokenAmount) - if err != nil { - return err - } - } - } - time.Sleep(constants.StakingMinimumLeadTime) - return checkAllLocalNodesAreCurrentValidators(subnetID) -} - -func handleRemoveAndAddValidators(sc models.Sidecar, subnetID ids.ID, validator ids.NodeID, stakedAmount uint64) error { - startTime := time.Now().Add(constants.StakingMinimumLeadTime).UTC() - endTime := startTime.Add(genesis.MainnetParams.MinStakeDuration) - testKey := genesis.EWOQKey - keyChain := secp256k1fx.NewKeychain(testKey) - _, err := subnet.IssueRemoveSubnetValidatorTx(keyChain, subnetID, validator) - if err != nil { - return err - } - ux.Logger.PrintToUser(fmt.Sprintf("Validator %s removed", validator.String())) - assetID := sc.ElasticSubnet[models.Local.String()].AssetID - txID, err := subnet.IssueAddPermissionlessValidatorTx(keyChain, subnetID, validator, stakedAmount, assetID, uint64(startTime.Unix()), uint64(endTime.Unix())) - if err != nil { - return err - } - ux.Logger.PrintToUser(fmt.Sprintf("%s successfully joined elastic subnet as permissionless validator!", validator.String())) - if err = app.UpdateSidecarPermissionlessValidator(&sc, models.NewLocalNetwork(), validator.String(), txID); err != nil { - return fmt.Errorf("joining permissionless subnet was successful, but failed to update sidecar: %w", err) - } - return nil -} - -func getTokenDenomination() (int, error) { - ux.Logger.PrintToUser("What's the denomination for your token?") - ux.Logger.PrintToUser("Denomination determines how balances of this asset are displayed by user interfaces. " + - "If denomination is 0, 100 units of this asset are displayed as 100. If denomination is 1, 100 units of this asset are displayed as 10.0.") - tokenDenomination, err := app.Prompt.CapturePositiveInt( - "Token Denomination", - []prompts.Comparator{ - { - Label: "Min Denomination Value", - Type: prompts.MoreThanEq, - Value: 0, - }, - { - Label: "Max Denomination Value", - Type: prompts.LessThanEq, - Value: 32, - }, - }, - ) - if err != nil { - return 0, err - } - return tokenDenomination, nil -} - -func CheckSubnetIsElastic(subnetID ids.ID, network models.Network) (bool, error) { - pClient := platformvm.NewClient(network.Endpoint) - ctx, cancel := utils.GetAPIContext() - defer cancel() - _, _, err := pClient.GetCurrentSupply(ctx, subnetID) - if err != nil { - // if subnet is already elastic it will return "not found" error - if strings.Contains(err.Error(), "not found") { - return false, errors.New(subnetIsElasticError) - } - return false, err - } - return true, nil -} - -func checkIfTxHasOccurred( - sc *models.Sidecar, - network models.Network, - txName string, -) (bool, ids.ID) { - if sc.ElasticSubnet == nil { - return false, ids.Empty - } - if sc.ElasticSubnet[network.Name()].Txs != nil { - txID, ok := sc.ElasticSubnet[network.Name()].Txs[txName] - if ok { - return true, txID - } - } - return false, ids.Empty -} diff --git a/cmd/subnetcmd/join.go b/cmd/subnetcmd/join.go deleted file mode 100644 index 0640fe3c7..000000000 --- a/cmd/subnetcmd/join.go +++ /dev/null @@ -1,683 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package subnetcmd - -import ( - "context" - "errors" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/ava-labs/avalanche-cli/pkg/application" - "github.com/ava-labs/avalanche-cli/pkg/binutils" - "github.com/ava-labs/avalanche-cli/pkg/cobrautils" - "github.com/ava-labs/avalanche-cli/pkg/constants" - "github.com/ava-labs/avalanche-cli/pkg/keychain" - "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/networkoptions" - "github.com/ava-labs/avalanche-cli/pkg/plugins" - "github.com/ava-labs/avalanche-cli/pkg/prompts" - "github.com/ava-labs/avalanche-cli/pkg/subnet" - "github.com/ava-labs/avalanche-cli/pkg/utils" - "github.com/ava-labs/avalanche-cli/pkg/ux" - "github.com/ava-labs/avalanche-network-runner/server" - "github.com/ava-labs/avalanchego/genesis" - "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/vms/platformvm" - "github.com/ava-labs/avalanchego/vms/secp256k1fx" - "github.com/spf13/cobra" -) - -const ewoqPChainAddr = "P-custom18jma8ppw3nhx5r4ap8clazz0dps7rv5u9xde7p" - -var ( - joinAllSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Devnet, networkoptions.Fuji, networkoptions.Mainnet} - joinNonElasticSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Devnet, networkoptions.Fuji, networkoptions.Mainnet} - joinElasticSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Fuji} - - // path to avalanchego config file - avagoConfigPath string - // path to avalanchego plugin dir - pluginDir string - // path to avalanchego datadir dir - dataDir string - // if true, print the manual instructions to screen - printManual bool - // if true, doesn't ask for overwriting the config file - forceWrite bool - // if true, validator is joining a permissionless subnet - joinElastic bool - // for permissionless subnet only: how much subnet native token will be staked in the validator - stakeAmount uint64 -) - -// avalanche subnet join -func newJoinCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "join [subnetName]", - Short: "Configure your validator node to begin validating a new subnet", - Long: `The subnet join command configures your validator node to begin validating a new Subnet. - -To complete this process, you must have access to the machine running your validator. If the -CLI is running on the same machine as your validator, it can generate or update your node's -config file automatically. Alternatively, the command can print the necessary instructions -to update your node manually. To complete the validation process, the Subnet's admins must add -the NodeID of your validator to the Subnet's allow list by calling addValidator with your -NodeID. - -After you update your validator's config, you need to restart your validator manually. If -you provide the --avalanchego-config flag, this command attempts to edit the config file -at that path. - -This command currently only supports Subnets deployed on the Fuji Testnet and Mainnet.`, - RunE: joinCmd, - Args: cobrautils.ExactArgs(1), - } - networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, false, joinAllSupportedNetworkOptions) - cmd.Flags().StringVar(&avagoConfigPath, "avalanchego-config", "", "file path of the avalanchego config file") - cmd.Flags().StringVar(&pluginDir, "plugin-dir", "", "file path of avalanchego's plugin directory") - cmd.Flags().StringVar(&dataDir, "data-dir", "", "path of avalanchego's data dir directory") - cmd.Flags().BoolVar(&printManual, "print", false, "if true, print the manual config without prompting") - cmd.Flags().StringVar(&nodeIDStr, "nodeID", "", "set the NodeID of the validator to check") - cmd.Flags().BoolVar(&forceWrite, "force-write", false, "if true, skip to prompt to overwrite the config file") - cmd.Flags().BoolVar(&joinElastic, "elastic", false, "set flag as true if joining elastic subnet") - cmd.Flags().Uint64Var(&stakeAmount, "stake-amount", 0, "amount of tokens to stake on validator") - cmd.Flags().StringVar(&startTimeStr, "start-time", "", "start time that validator starts validating") - cmd.Flags().DurationVar(&duration, "staking-period", 0, "how long validator validates for after start time") - cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [fuji only]") - cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on fuji)") - cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses") - return cmd -} - -func joinCmd(_ *cobra.Command, args []string) error { - if printManual && (avagoConfigPath != "" || pluginDir != "") { - return errors.New("--print cannot be used with --avalanchego-config or --plugin-dir") - } - - chains, err := ValidateSubnetNameAndGetChains(args) - if err != nil { - return err - } - - subnetName := chains[0] - - sc, err := app.LoadSidecar(subnetName) - if err != nil { - return err - } - - var supportedNetworkOptions []networkoptions.NetworkOption - if joinElastic { - supportedNetworkOptions = joinElasticSupportedNetworkOptions - } else { - supportedNetworkOptions = joinNonElasticSupportedNetworkOptions - } - network, err := networkoptions.GetNetworkFromCmdLineFlags( - app, - "", - globalNetworkFlags, - false, - false, - supportedNetworkOptions, - "", - ) - if err != nil { - return err - } - - if joinElastic { - return handleValidatorJoinElasticSubnet(sc, network, subnetName) - } - - network.HandlePublicNetworkSimulation() - - subnetID := sc.Networks[network.Name()].SubnetID - if subnetID == ids.Empty { - return errNoSubnetID - } - subnetIDStr := subnetID.String() - - if printManual { - pluginDir = app.GetTmpPluginDir() - vmPath, err := plugins.CreatePlugin(app, sc.Name, pluginDir) - if err != nil { - return err - } - printJoinCmd(subnetIDStr, network, vmPath) - return nil - } - - // if **both** flags were set, nothing special needs to be done - // just check the following blocks - if avagoConfigPath == "" && pluginDir == "" { - // both flags are NOT set - const ( - choiceManual = "Manual" - choiceAutomatic = "Automatic" - ) - choice, err := app.Prompt.CaptureList( - "How would you like to update the avalanchego config?", - []string{choiceAutomatic, choiceManual}, - ) - if err != nil { - return err - } - if choice == choiceManual { - pluginDir = app.GetTmpPluginDir() - vmPath, err := plugins.CreatePlugin(app, sc.Name, pluginDir) - if err != nil { - return err - } - printJoinCmd(subnetIDStr, network, vmPath) - return nil - } - } - - // if choice is automatic, we just pass through this block - // or, pluginDir was set but not avagoConfigPath - // if **both** flags were set, this will be skipped... - if avagoConfigPath == "" { - avagoConfigPath, err = plugins.FindAvagoConfigPath() - if err != nil { - return err - } - if avagoConfigPath != "" { - ux.Logger.PrintToUser(logging.Bold.Wrap(logging.Green.Wrap("Found a config file at %s")), avagoConfigPath) - yes, err := app.Prompt.CaptureYesNo("Is this the file we should update?") - if err != nil { - return err - } - if yes { - ux.Logger.PrintToUser("Will use file at path %s to update the configuration", avagoConfigPath) - } else { - avagoConfigPath = "" - } - } - if avagoConfigPath == "" { - avagoConfigPath, err = app.Prompt.CaptureString("Path to your existing config file (or where it will be generated)") - if err != nil { - return err - } - } - } - - // ...but not this - avagoConfigPath, err := plugins.SanitizePath(avagoConfigPath) - if err != nil { - return err - } - - // avagoConfigPath was set but not pluginDir - // if **both** flags were set, this will be skipped... - if pluginDir == "" { - pluginDir, err = plugins.FindPluginDir() - if err != nil { - return err - } - if pluginDir != "" { - ux.Logger.PrintToUser(logging.Bold.Wrap(logging.Green.Wrap("Found the VM plugin directory at %s")), pluginDir) - yes, err := app.Prompt.CaptureYesNo("Is this where we should install the VM?") - if err != nil { - return err - } - if yes { - ux.Logger.PrintToUser("Will use plugin directory at %s to install the VM", pluginDir) - } else { - pluginDir = "" - } - } - if pluginDir == "" { - pluginDir, err = app.Prompt.CaptureString("Path to your avalanchego plugin dir (likely .avalanchego/plugins)") - if err != nil { - return err - } - } - } - - // ...but not this - pluginDir, err := plugins.SanitizePath(pluginDir) - if err != nil { - return err - } - - vmPath, err := plugins.CreatePlugin(app, sc.Name, pluginDir) - if err != nil { - return err - } - - ux.Logger.PrintToUser("VM binary written to %s", vmPath) - - if forceWrite { - if err := writeAvagoChainConfigFiles(app, dataDir, subnetName, sc, network); err != nil { - return err - } - } - - subnetAvagoConfigFile := "" - if app.AvagoNodeConfigExists(subnetName) { - subnetAvagoConfigFile = app.GetAvagoNodeConfigPath(subnetName) - } - - if err := plugins.EditConfigFile( - app, - subnetIDStr, - network, - avagoConfigPath, - forceWrite, - subnetAvagoConfigFile, - ); err != nil { - return err - } - - return nil -} - -func writeAvagoChainConfigFiles( - app *application.Avalanche, - dataDir string, - subnetName string, - sc models.Sidecar, - network models.Network, -) error { - if dataDir == "" { - dataDir = utils.UserHomePath(".avalanchego") - } - - subnetID := sc.Networks[network.Name()].SubnetID - if subnetID == ids.Empty { - return errNoSubnetID - } - subnetIDStr := subnetID.String() - blockchainID := sc.Networks[network.Name()].BlockchainID - - configsPath := filepath.Join(dataDir, "configs") - - subnetConfigsPath := filepath.Join(configsPath, "subnets") - subnetConfigPath := filepath.Join(subnetConfigsPath, subnetIDStr+".json") - if app.AvagoSubnetConfigExists(subnetName) { - if err := os.MkdirAll(subnetConfigsPath, constants.DefaultPerms755); err != nil { - return err - } - subnetConfig, err := app.LoadRawAvagoSubnetConfig(subnetName) - if err != nil { - return err - } - if err := os.WriteFile(subnetConfigPath, subnetConfig, constants.DefaultPerms755); err != nil { - return err - } - } else { - _ = os.RemoveAll(subnetConfigPath) - } - - if blockchainID != ids.Empty && app.ChainConfigExists(subnetName) || app.NetworkUpgradeExists(subnetName) { - chainConfigsPath := filepath.Join(configsPath, "chains", blockchainID.String()) - if err := os.MkdirAll(chainConfigsPath, constants.DefaultPerms755); err != nil { - return err - } - chainConfigPath := filepath.Join(chainConfigsPath, "config.json") - if app.ChainConfigExists(subnetName) { - chainConfig, err := app.LoadRawChainConfig(subnetName) - if err != nil { - return err - } - if err := os.WriteFile(chainConfigPath, chainConfig, constants.DefaultPerms755); err != nil { - return err - } - } else { - _ = os.RemoveAll(chainConfigPath) - } - networkUpgradesPath := filepath.Join(chainConfigsPath, "upgrade.json") - if app.NetworkUpgradeExists(subnetName) { - networkUpgrades, err := app.LoadRawNetworkUpgrades(subnetName) - if err != nil { - return err - } - if err := os.WriteFile(networkUpgradesPath, networkUpgrades, constants.DefaultPerms755); err != nil { - return err - } - } else { - _ = os.RemoveAll(networkUpgradesPath) - } - } - - return nil -} - -func handleValidatorJoinElasticSubnet(sc models.Sidecar, network models.Network, subnetName string) error { - var err error - if len(ledgerAddresses) > 0 { - useLedger = true - } - - if useLedger && keyName != "" { - return ErrMutuallyExlusiveKeyLedger - } - - subnetID := sc.Networks[network.Name()].SubnetID - if os.Getenv(constants.SimulatePublicNetwork) != "" { - subnetID = sc.Networks[models.Local.String()].SubnetID - } - if subnetID == ids.Empty { - return errNoSubnetID - } - - nodeID, err := promptNodeIDToAdd(subnetID, true, network) - if err != nil { - return err - } - stakedTokenAmount, err := promptStakeAmount(subnetName, true, network) - if err != nil { - return err - } - start, stakeDuration, err := getTimeParameters(network, nodeID, true) - if err != nil { - return err - } - endTime := start.Add(stakeDuration) - ux.Logger.PrintToUser("Inputs complete, issuing transaction for the provided validator to join elastic subnet...") - ux.Logger.PrintToUser("") - switch network.Kind { - case models.Local: - return handleValidatorJoinElasticSubnetLocal(sc, network, subnetName, nodeID, stakedTokenAmount, start, endTime) - case models.Fuji: - if !useLedger && keyName == "" { - useLedger, keyName, err = prompts.GetKeyOrLedger(app.Prompt, constants.PayTxsFeesMsg, app.GetKeyDir(), false) - if err != nil { - return err - } - } - case models.Mainnet: - return errors.New("unsupported network") - default: - return errors.New("unsupported network") - } - - // get keychain accessor - fee := network.GenesisParams().AddSubnetValidatorFee - kc, err := keychain.GetKeychain(app, false, useLedger, ledgerAddresses, keyName, network, fee) - if err != nil { - return err - } - - network.HandlePublicNetworkSimulation() - - recipientAddr := kc.Addresses().List()[0] - deployer := subnet.NewPublicDeployer(app, kc, network) - assetID, err := getSubnetAssetID(subnetID, network) - if err != nil { - return err - } - delegationFee := network.GenesisParams().MinDelegationFee - txID, err := deployer.AddPermissionlessValidator(subnetID, assetID, nodeID, stakedTokenAmount, uint64(start.Unix()), uint64(endTime.Unix()), recipientAddr, delegationFee, nil, nil) - if err != nil { - return err - } - printAddPermissionlessValOutput(txID, nodeID, network, start, endTime, stakedTokenAmount) - if err = app.UpdateSidecarPermissionlessValidator(&sc, network, nodeID.String(), txID); err != nil { - return fmt.Errorf("joining permissionless subnet was successful, but failed to update sidecar: %w", err) - } - return nil -} - -func getSubnetAssetID(subnetID ids.ID, network models.Network) (ids.ID, error) { - pClient := platformvm.NewClient(network.Endpoint) - ctx := context.Background() - assetID, err := pClient.GetStakingAssetID(ctx, subnetID) - if err != nil { - return ids.Empty, err - } - return assetID, nil -} - -func printAddPermissionlessValOutput(txID ids.ID, nodeID ids.NodeID, network models.Network, start time.Time, endTime time.Time, stakedTokenAmount uint64) { - ux.Logger.PrintToUser("Validator successfully joined elastic subnet!") - ux.Logger.PrintToUser("TX ID: %s", txID.String()) - ux.Logger.PrintToUser("NodeID: %s", nodeID.String()) - ux.Logger.PrintToUser("Network: %s", network.Name()) - ux.Logger.PrintToUser("Start time: %s", start.UTC().Format(constants.TimeParseLayout)) - ux.Logger.PrintToUser("End time: %s", endTime.Format(constants.TimeParseLayout)) - ux.Logger.PrintToUser("Stake Amount: %d", stakedTokenAmount) -} - -func handleValidatorJoinElasticSubnetLocal(sc models.Sidecar, network models.Network, subnetName string, nodeID ids.NodeID, - stakedTokenAmount uint64, start time.Time, endTime time.Time, -) error { - if network.Kind != models.Local { - return errors.New("unsupported network") - } - if !checkIfSubnetIsElasticOnLocal(sc) { - return fmt.Errorf("%s is not an elastic subnet", subnetName) - } - assetID := sc.ElasticSubnet[models.Local.String()].AssetID - testKey := genesis.EWOQKey - keyChain := secp256k1fx.NewKeychain(testKey) - subnetID := sc.Networks[models.Local.String()].SubnetID - txID, err := subnet.IssueAddPermissionlessValidatorTx(keyChain, subnetID, nodeID, stakedTokenAmount, assetID, uint64(start.Unix()), uint64(endTime.Unix())) - if err != nil { - return err - } - printAddPermissionlessValOutput(txID, nodeID, network, start, endTime, stakedTokenAmount) - if err = app.UpdateSidecarPermissionlessValidator(&sc, models.NewLocalNetwork(), nodeID.String(), txID); err != nil { - return fmt.Errorf("joining permissionless subnet was successful, but failed to update sidecar: %w", err) - } - return nil -} - -func checkIsValidating(subnetID ids.ID, nodeID ids.NodeID, pClient platformvm.Client) (bool, error) { - // first check if the node is already an accepted validator on the subnet - ctx := context.Background() - nodeIDs := []ids.NodeID{nodeID} - vals, err := pClient.GetCurrentValidators(ctx, subnetID, nodeIDs) - if err != nil { - return false, err - } - for _, v := range vals { - // strictly this is not needed, as we are providing the nodeID as param - // just a double check - if v.NodeID == nodeID { - return true, nil - } - } - return false, nil -} - -func getLocalNetworkIDs() ([]string, error) { - var localNodeIDs []string - cli, err := binutils.NewGRPCClient() - if err != nil { - return nil, err - } - - ctx, cancel := utils.GetAPIContext() - defer cancel() - status, err := cli.Status(ctx) - if err != nil { - if server.IsServerError(err, server.ErrNotBootstrapped) { - ux.Logger.PrintToUser("No local network running") - return nil, nil - } - return nil, err - } - - if status != nil && status.ClusterInfo != nil { - for _, val := range status.ClusterInfo.NodeInfos { - localNodeIDs = append(localNodeIDs, val.Id) - } - } - return localNodeIDs, nil -} - -func promptNodeIDToAdd(subnetID ids.ID, isValidator bool, network models.Network) (ids.NodeID, error) { - if nodeIDStr == "" { - if network.Kind != models.Local { - promptStr := "Please enter the Node ID of the node that you would like to add to the elastic subnet" - if !isValidator { - promptStr = "Please enter the Node ID of the validator that you would like to delegate to" - } - ux.Logger.PrintToUser(promptStr) - return app.Prompt.CaptureNodeID("Node ID (format it as NodeID-)") - } - defaultLocalNetworkNodeIDs, err := getLocalNetworkIDs() - if err != nil { - return ids.EmptyNodeID, err - } - // Get NodeIDs of all validators on the subnet - validators, err := subnet.GetSubnetValidators(subnetID) - if err != nil { - return ids.EmptyNodeID, err - } - // construct list of validators to choose from - var validatorList []string - valNodeIDsMap := make(map[string]bool) - for _, val := range validators { - valNodeIDsMap[val.NodeID.String()] = true - } - if !isValidator { - for _, v := range validators { - validatorList = append(validatorList, v.NodeID.String()) - } - } else { - for _, localNodeID := range defaultLocalNetworkNodeIDs { - if _, ok := valNodeIDsMap[localNodeID]; !ok { - validatorList = append(validatorList, localNodeID) - } - } - } - promptStr := "Which validator you'd like to join this elastic subnet?" - if !isValidator { - promptStr = "Which validator would you like to delegate to?" - } - nodeIDStr, err = app.Prompt.CaptureList(promptStr, validatorList) - if err != nil { - return ids.EmptyNodeID, err - } - } - nodeID, err := ids.NodeIDFromString(nodeIDStr) - if err != nil { - return ids.NodeID{}, err - } - return nodeID, nil -} - -func promptStakeAmount(subnetName string, isValidator bool, network models.Network) (uint64, error) { - if stakeAmount > 0 { - return stakeAmount, nil - } - if network.Kind == models.Local { - esc, err := app.LoadElasticSubnetConfig(subnetName) - if err != nil { - return 0, err - } - maxValidatorStake := fmt.Sprintf("Maximum Validator Stake (%d)", esc.MaxValidatorStake) - customWeight := fmt.Sprintf("Custom (Has to be between minValidatorStake (%d) and maxValidatorStake (%d) defined during elastic subnet transformation)", esc.MinValidatorStake, esc.MaxValidatorStake) - if !isValidator { - customWeight = fmt.Sprintf("Custom (Has to be between minDelegatorStake (%d) and maxValidatorStake (%d) defined during elastic subnet transformation)", esc.MinDelegatorStake, esc.MaxValidatorStake) - } - - txt := "What amount of the subnet native token would you like to stake?" - weightOptions := []string{maxValidatorStake, customWeight} - weightOption, err := app.Prompt.CaptureList(txt, weightOptions) - if err != nil { - return 0, err - } - pClient := platformvm.NewClient(constants.LocalAPIEndpoint) - walletBalance, err := getAssetBalance(pClient, ewoqPChainAddr, esc.AssetID) - if err != nil { - return 0, err - } - minStakePromptStr := fmt.Sprintf("Min Validator Stake(%d)", esc.MinValidatorStake) - minStakeVal := esc.MinValidatorStake - if !isValidator { - minStakePromptStr = fmt.Sprintf("Min Delegator Stake(%d)", esc.MinValidatorStake) - minStakeVal = esc.MinDelegatorStake - } - switch weightOption { - case maxValidatorStake: - return esc.MaxValidatorStake, nil - default: - return app.Prompt.CaptureUint64Compare( - txt, - []prompts.Comparator{ - { - Label: fmt.Sprintf("Max Validator Stake(%d)", esc.MaxValidatorStake), - Type: prompts.LessThanEq, - Value: esc.MaxValidatorStake, - }, - { - Label: minStakePromptStr, - Type: prompts.MoreThanEq, - Value: minStakeVal, - }, - { - Label: fmt.Sprintf("Wallet Balance(%d)", walletBalance), - Type: prompts.LessThanEq, - Value: walletBalance, - }, - }, - ) - } - } - ux.Logger.PrintToUser("What amount of the subnet native token would you like to stake?") - initialSupply, err := app.Prompt.CaptureUint64("Stake amount") - if err != nil { - return 0, err - } - return initialSupply, nil -} - -func printJoinCmd(subnetID string, network models.Network, vmPath string) { - msg := ` -To setup your node, you must do two things: - -1. Add your VM binary to your node's plugin directory -2. Update your node config to start validating the subnet - -To add the VM to your plugin directory, copy or scp from %s - -If you installed avalanchego with the install script, your plugin directory is likely -~/.avalanchego/plugins. - -If you start your node from the command line WITHOUT a config file (e.g. via command -line or systemd script), add the following flag to your node's startup command: - ---track-subnets=%s -(if the node already has a track-subnets config, append the new value by -comma-separating it). - -For example: -./build/avalanchego --network-id=%s --track-subnets=%s - -If you start the node via a JSON config file, add this to your config file: -track-subnets: %s - -NOTE: The flag --track-subnets is a replacement of the deprecated --whitelisted-subnets. -If the later is present in config, please rename it to track-subnets first. - -TIP: Try this command with the --avalanchego-config flag pointing to your config file, -this tool will try to update the file automatically (make sure it can write to it). - -After you update your config, you will need to restart your node for the changes to -take effect.` - - ux.Logger.PrintToUser(msg, vmPath, subnetID, network.NetworkIDFlagValue(), subnetID, subnetID) -} - -func getAssetBalance(pClient platformvm.Client, addr string, assetID ids.ID) (uint64, error) { - pID, err := address.ParseToID(addr) - if err != nil { - return 0, err - } - ctx, cancel := utils.GetAPIContext() - resp, err := pClient.GetBalance(ctx, []ids.ShortID{pID}) - cancel() - if err != nil { - return 0, err - } - assetIDBalance := resp.Balances[assetID] - return uint64(assetIDBalance), nil -} diff --git a/cmd/teleportercmd/deploy.go b/cmd/teleportercmd/deploy.go index 29abdb443..fef0f1061 100644 --- a/cmd/teleportercmd/deploy.go +++ b/cmd/teleportercmd/deploy.go @@ -5,7 +5,6 @@ package teleportercmd import ( "fmt" - cmdflags "github.com/ava-labs/avalanche-cli/cmd/flags" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/localnet" @@ -13,16 +12,15 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/networkoptions" "github.com/ava-labs/avalanche-cli/pkg/prompts" "github.com/ava-labs/avalanche-cli/pkg/teleporter" - "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/spf13/cobra" ) type DeployFlags struct { Network networkoptions.NetworkFlags - SubnetName string - BlockchainID string - CChain bool + ChainFlags contract.ChainSpec KeyName string GenesisKey bool DeployMessenger bool @@ -60,10 +58,8 @@ func newDeployCmd() *cobra.Command { Args: cobrautils.ExactArgs(0), } networkoptions.AddNetworkFlagsToCmd(cmd, &deployFlags.Network, true, deploySupportedNetworkOptions) - contract.AddPrivateKeyFlagsToCmd(cmd, &msgFlags.PrivateKeyFlags, "to fund teleporter deploy") - cmd.Flags().StringVar(&deployFlags.SubnetName, "subnet", "", "deploy teleporter into the given CLI subnet") - cmd.Flags().StringVar(&deployFlags.BlockchainID, "blockchain-id", "", "deploy teleporter into the given blockchain ID/Alias") - cmd.Flags().BoolVar(&deployFlags.CChain, "c-chain", false, "deploy teleporter into C-Chain") + deployFlags.PrivateKeyFlags.AddToCmd(cmd, "to fund ICM deploy") + deployFlags.ChainFlags.AddToCmd(cmd, "deploy ICM", true) cmd.Flags().BoolVar(&deployFlags.DeployMessenger, "deploy-messenger", true, "deploy Teleporter Messenger") cmd.Flags().BoolVar(&deployFlags.DeployRegistry, "deploy-registry", true, "deploy Teleporter Registry") cmd.Flags().StringVar(&deployFlags.RPCURL, "rpc-url", "", "use the given RPC URL to connect to the subnet") @@ -92,127 +88,70 @@ func CallDeploy(_ []string, flags DeployFlags) error { if err != nil { return err } - if !cmdflags.EnsureMutuallyExclusive([]bool{flags.SubnetName != "", flags.BlockchainID != "", flags.CChain}) { - return fmt.Errorf("--subnet, --blockchain-id and --cchain are mutually exclusive flags") + if err := flags.ChainFlags.CheckMutuallyExclusiveFields(); err != nil { + return err } if !flags.DeployMessenger && !flags.DeployRegistry { return fmt.Errorf("you should set at least one of --deploy-messenger/--deploy-registry to true") } - if flags.SubnetName == "" && flags.BlockchainID == "" && !flags.CChain { - // fill flags based on user prompts - blockchainIDOptions := []string{ - "Get Blockchain ID from an existing subnet (deployed with avalanche subnet deploy)", - "Use C-Chain Blockchain ID", - "Custom", - } - blockchainIDOption, err := app.Prompt.CaptureList("Which Blockchain ID would you like to deploy Teleporter to?", blockchainIDOptions) - if err != nil { + if !flags.ChainFlags.Defined() { + prompt := "Which Blockchain would you like to deploy Teleporter to?" + if cancel, err := contract.PromptChain( + app, + network, + prompt, + false, + "", + false, + &flags.ChainFlags, + ); err != nil { return err - } - switch blockchainIDOption { - case blockchainIDOptions[0]: - subnetNames, err := app.GetSubnetNames() - if err != nil { - return err - } - flags.SubnetName, err = app.Prompt.CaptureList( - "Choose a Subnet", - subnetNames, - ) - if err != nil { - return err - } - case blockchainIDOptions[1]: - flags.CChain = true - default: - flags.BlockchainID, err = app.Prompt.CaptureString("Blockchain ID/Alias") - if err != nil { - return err - } + } else if cancel { + return nil } } - - var ( - blockchainID string - teleporterSubnetDesc string - privateKey string - teleporterVersion string - ) - switch { - case flags.SubnetName != "": - teleporterSubnetDesc = flags.SubnetName - sc, err := app.LoadSidecar(flags.SubnetName) + rpcURL := flags.RPCURL + if rpcURL == "" { + rpcURL, _, err = contract.GetBlockchainEndpoints(app, network, flags.ChainFlags, true, false) if err != nil { - return fmt.Errorf("failed to load sidecar: %w", err) - } - if b, _, err := app.HasSubnetEVMGenesis(flags.SubnetName); err != nil { return err - } else if !b { - return fmt.Errorf("only Subnet-EVM based vms can be used for teleporter") - } - if sc.Networks[network.Name()].BlockchainID == ids.Empty { - return fmt.Errorf("subnet has not been deployed to %s", network.Name()) } - blockchainID = sc.Networks[network.Name()].BlockchainID.String() - if sc.TeleporterVersion != "" { - teleporterVersion = sc.TeleporterVersion - } - if sc.TeleporterKey != "" { - k, err := app.GetKey(sc.TeleporterKey, network, true) - if err != nil { - return err - } - privateKey = k.PrivKeyHex() - } - case flags.BlockchainID != "": - teleporterSubnetDesc = flags.BlockchainID - blockchainID = flags.BlockchainID - case flags.CChain: - teleporterSubnetDesc = cChainName - blockchainID = cChainAlias + ux.Logger.PrintToUser(logging.Yellow.Wrap("RPC Endpoint: %s"), rpcURL) } + genesisAddress, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( app, network, - flags.SubnetName, - flags.CChain, - flags.BlockchainID, + flags.ChainFlags, ) if err != nil { return err } + privateKey, err := flags.PrivateKeyFlags.GetPrivateKey(app, genesisPrivateKey) + if err != nil { + return err + } if privateKey == "" { - privateKey, err = contract.GetPrivateKeyFromFlags( - app, - deployFlags.PrivateKeyFlags, + privateKey, err = prompts.PromptPrivateKey( + app.Prompt, + "deploy teleporter", + app.GetKeyDir(), + app.GetKey, + genesisAddress, genesisPrivateKey, ) if err != nil { return err } - if privateKey == "" { - privateKey, err = prompts.PromptPrivateKey( - app.Prompt, - "deploy teleporter", - app.GetKeyDir(), - app.GetKey, - genesisAddress, - genesisPrivateKey, - ) - if err != nil { - return err - } - } } + var teleporterVersion string switch { case flags.MessengerContractAddressPath != "" || flags.MessengerDeployerAddressPath != "" || flags.MessengerDeployerTxPath != "" || flags.RegistryBydecodePath != "": - teleporterVersion = "" if flags.MessengerContractAddressPath == "" || flags.MessengerDeployerAddressPath == "" || flags.MessengerDeployerTxPath == "" || flags.RegistryBydecodePath == "" { return fmt.Errorf("if setting any teleporter asset path, you must set all teleporter asset paths") } case flags.Version != "" && flags.Version != "latest": teleporterVersion = flags.Version - case teleporterVersion != "": default: teleporterInfo, err := teleporter.GetInfo(app) if err != nil { @@ -221,10 +160,6 @@ func CallDeploy(_ []string, flags DeployFlags) error { teleporterVersion = teleporterInfo.Version } // deploy to subnet - rpcURL := network.BlockchainEndpoint(blockchainID) - if flags.RPCURL != "" { - rpcURL = flags.RPCURL - } td := teleporter.Deployer{} if flags.MessengerContractAddressPath != "" { if err := td.SetAssetsFromPaths( @@ -243,8 +178,12 @@ func CallDeploy(_ []string, flags DeployFlags) error { return err } } + blockchainDesc, err := contract.GetBlockchainDesc(flags.ChainFlags) + if err != nil { + return err + } alreadyDeployed, teleporterMessengerAddress, teleporterRegistryAddress, err := td.Deploy( - teleporterSubnetDesc, + blockchainDesc, rpcURL, privateKey, flags.DeployMessenger, @@ -253,9 +192,9 @@ func CallDeploy(_ []string, flags DeployFlags) error { if err != nil { return err } - if flags.SubnetName != "" && !alreadyDeployed { + if flags.ChainFlags.BlockchainName != "" && !alreadyDeployed { // update sidecar - sc, err := app.LoadSidecar(flags.SubnetName) + sc, err := app.LoadSidecar(flags.ChainFlags.BlockchainName) if err != nil { return fmt.Errorf("failed to load sidecar: %w", err) } @@ -274,7 +213,7 @@ func CallDeploy(_ []string, flags DeployFlags) error { } } // automatic deploy to cchain for local/devnet - if !flags.CChain && (network.Kind == models.Local || network.Kind == models.Devnet) { + if !flags.ChainFlags.CChain && (network.Kind == models.Local || network.Kind == models.Devnet) { ewoq, err := app.GetKey("ewoq", network, false) if err != nil { return err diff --git a/cmd/teleportercmd/msg.go b/cmd/teleportercmd/msg.go index 839b5cb47..0e3146d83 100644 --- a/cmd/teleportercmd/msg.go +++ b/cmd/teleportercmd/msg.go @@ -25,6 +25,8 @@ type MsgFlags struct { DestinationAddress string HexEncodedMessage bool PrivateKeyFlags contract.PrivateKeyFlags + SourceRPCEndpoint string + DestRPCEndpoint string } var ( @@ -39,31 +41,26 @@ var ( // avalanche teleporter msg func newMsgCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "msg [sourceSubnetName] [destinationSubnetName] [messageContent]", + Use: "msg [sourceBlockchainName] [destinationBlockchainName] [messageContent]", Short: "Verifies exchange of teleporter message between two subnets", Long: `Sends and wait reception for a teleporter msg between two subnets (Currently only for local network).`, RunE: msg, Args: cobrautils.ExactArgs(3), } networkoptions.AddNetworkFlagsToCmd(cmd, &msgFlags.Network, true, msgSupportedNetworkOptions) - contract.AddPrivateKeyFlagsToCmd(cmd, &msgFlags.PrivateKeyFlags, "as message originator and to pay source blockchain fees") + msgFlags.PrivateKeyFlags.AddToCmd(cmd, "as message originator and to pay source blockchain fees") cmd.Flags().BoolVar(&msgFlags.HexEncodedMessage, "hex-encoded", false, "given message is hex encoded") cmd.Flags().StringVar(&msgFlags.DestinationAddress, "destination-address", "", "deliver the message to the given contract destination address") + cmd.Flags().StringVar(&msgFlags.SourceRPCEndpoint, "source-rpc", "", "use the given source blockchain rpc endpoint") + cmd.Flags().StringVar(&msgFlags.DestRPCEndpoint, "dest-rpc", "", "use the given destination blockchain rpc endpoint") return cmd } func msg(_ *cobra.Command, args []string) error { - sourceSubnetName := args[0] - destSubnetName := args[1] + sourceBlockchainName := args[0] + destBlockchainName := args[1] message := args[2] - subnetNameToGetNetworkFrom := "" - if !isCChain(sourceSubnetName) { - subnetNameToGetNetworkFrom = sourceSubnetName - } - if !isCChain(destSubnetName) { - subnetNameToGetNetworkFrom = destSubnetName - } network, err := networkoptions.GetNetworkFromCmdLineFlags( app, "", @@ -71,34 +68,55 @@ func msg(_ *cobra.Command, args []string) error { true, false, msgSupportedNetworkOptions, - subnetNameToGetNetworkFrom, + "", ) if err != nil { return err } + sourceChainSpec := contract.ChainSpec{ + BlockchainName: sourceBlockchainName, + CChain: isCChain(sourceBlockchainName), + } + sourceRPCEndpoint := msgFlags.SourceRPCEndpoint + if sourceRPCEndpoint == "" { + sourceRPCEndpoint, _, err = contract.GetBlockchainEndpoints(app, network, sourceChainSpec, true, false) + if err != nil { + return err + } + } + + destChainSpec := contract.ChainSpec{ + BlockchainName: destBlockchainName, + CChain: isCChain(destBlockchainName), + } + destRPCEndpoint := msgFlags.DestRPCEndpoint + if destRPCEndpoint == "" { + destRPCEndpoint, _, err = contract.GetBlockchainEndpoints(app, network, destChainSpec, true, false) + if err != nil { + return err + } + } + genesisAddress, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( app, network, - sourceSubnetName, - isCChain(sourceSubnetName), - "", + contract.ChainSpec{ + BlockchainName: sourceBlockchainName, + CChain: isCChain(sourceBlockchainName), + }, ) if err != nil { return err } - privateKey, err := contract.GetPrivateKeyFromFlags( - app, - msgFlags.PrivateKeyFlags, - genesisPrivateKey, - ) + privateKey, err := msgFlags.PrivateKeyFlags.GetPrivateKey(app, genesisPrivateKey) if err != nil { return err } if privateKey == "" { privateKey, err = prompts.PromptPrivateKey( app.Prompt, - "send the message", + "pay for fees at source blockchain", app.GetKeyDir(), app.GetKey, genesisAddress, @@ -109,21 +127,19 @@ func msg(_ *cobra.Command, args []string) error { } } - _, _, sourceBlockchainID, sourceMessengerAddress, _, _, err := teleporter.GetSubnetParams( - app, - network, - sourceSubnetName, - isCChain(sourceSubnetName), - ) + sourceBlockchainID, err := contract.GetBlockchainID(app, network, sourceChainSpec) if err != nil { return err } - _, _, destBlockchainID, destMessengerAddress, _, _, err := teleporter.GetSubnetParams( - app, - network, - destSubnetName, - isCChain(destSubnetName), - ) + _, sourceMessengerAddress, err := contract.GetICMInfo(app, network, sourceChainSpec, false, false, true) + if err != nil { + return err + } + destBlockchainID, err := contract.GetBlockchainID(app, network, destChainSpec) + if err != nil { + return err + } + _, destMessengerAddress, err := contract.GetICMInfo(app, network, destChainSpec, false, false, true) if err != nil { return err } @@ -144,9 +160,9 @@ func msg(_ *cobra.Command, args []string) error { destAddr = common.HexToAddress(msgFlags.DestinationAddress) } // send tx to the teleporter contract at the source - ux.Logger.PrintToUser("Delivering message %q from source subnet %q (%s)", message, sourceSubnetName, sourceBlockchainID) + ux.Logger.PrintToUser("Delivering message %q from source subnet %q (%s)", message, sourceBlockchainName, sourceBlockchainID) tx, receipt, err := teleporter.SendCrossChainMessage( - network.BlockchainEndpoint(sourceBlockchainID.String()), + sourceRPCEndpoint, common.HexToAddress(sourceMessengerAddress), privateKey, destBlockchainID, @@ -159,7 +175,7 @@ func msg(_ *cobra.Command, args []string) error { if err == contract.ErrFailedReceiptStatus { txHash := tx.Hash().String() ux.Logger.PrintToUser("error: source receipt status for tx %s is not ReceiptStatusSuccessful", txHash) - trace, err := evm.GetTrace(network.BlockchainEndpoint(sourceBlockchainID.String()), txHash) + trace, err := evm.GetTrace(sourceRPCEndpoint, txHash) if err != nil { ux.Logger.PrintToUser("error obtaining tx trace: %s", err) ux.Logger.PrintToUser("") @@ -184,14 +200,14 @@ func msg(_ *cobra.Command, args []string) error { } // receive and process head from destination - ux.Logger.PrintToUser("Waiting for message to be delivered to destination subnet %q (%s)", destSubnetName, destBlockchainID) + ux.Logger.PrintToUser("Waiting for message to be delivered to destination subnet %q (%s)", destBlockchainName, destBlockchainID) arrivalCheckInterval := 100 * time.Millisecond arrivalCheckTimeout := 10 * time.Second t0 := time.Now() for { if b, err := teleporter.MessageReceived( - network.BlockchainEndpoint(destBlockchainID.String()), + destRPCEndpoint, common.HexToAddress(destMessengerAddress), event.MessageID, ); err != nil { diff --git a/cmd/teleportercmd/relayercmd/addSubnetToService.go b/cmd/teleportercmd/relayercmd/addSubnetToService.go deleted file mode 100644 index 52efdea9a..000000000 --- a/cmd/teleportercmd/relayercmd/addSubnetToService.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package relayercmd - -import ( - "os" - "path/filepath" - - "github.com/ava-labs/avalanche-cli/pkg/cobrautils" - "github.com/ava-labs/avalanche-cli/pkg/constants" - "github.com/ava-labs/avalanche-cli/pkg/networkoptions" - "github.com/ava-labs/avalanche-cli/pkg/teleporter" - "github.com/ava-labs/avalanche-cli/pkg/ux" - - "github.com/spf13/cobra" -) - -type AddSubnetToServiceFlags struct { - Network networkoptions.NetworkFlags - CloudNodeID string -} - -var ( - addSubnetToServiceSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Cluster, networkoptions.Fuji, networkoptions.Mainnet, networkoptions.Devnet} - addSubnetToServiceFlags AddSubnetToServiceFlags -) - -// avalanche teleporter relayer addSubnetToService -func newAddSubnetToServiceCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "addSubnetToService [subnetName]", - Short: "Adds a subnet to the AWM relayer service configuration", - Long: `Adds a subnet to the AWM relayer service configuration".`, - RunE: addSubnetToService, - Args: cobrautils.ExactArgs(1), - } - networkoptions.AddNetworkFlagsToCmd(cmd, &addSubnetToServiceFlags.Network, true, addSubnetToServiceSupportedNetworkOptions) - cmd.Flags().StringVar(&addSubnetToServiceFlags.CloudNodeID, "cloud-node-id", "", "generate a config to be used on given cloud node") - return cmd -} - -func addSubnetToService(_ *cobra.Command, args []string) error { - return CallAddSubnetToService(args[0], addSubnetToServiceFlags) -} - -func CallAddSubnetToService(subnetName string, flags AddSubnetToServiceFlags) error { - network, err := networkoptions.GetNetworkFromCmdLineFlags( - app, - "", - flags.Network, - true, - false, - addSubnetToServiceSupportedNetworkOptions, - subnetName, - ) - if err != nil { - return err - } - - relayerAddress, relayerPrivateKey, err := teleporter.GetRelayerKeyInfo(app.GetKeyPath(constants.AWMRelayerKeyName)) - if err != nil { - return err - } - - _, subnetID, chainID, messengerAddress, registryAddress, _, err := teleporter.GetSubnetParams(app, network, "", true) - if err != nil { - return err - } - - configBasePath := "" - storageBasePath := "" - if flags.CloudNodeID != "" { - storageBasePath = constants.AWMRelayerDockerDir - configBasePath = app.GetNodeInstanceDirPath(flags.CloudNodeID) - } - - configPath := app.GetAWMRelayerServiceConfigPath(configBasePath) - if err := os.MkdirAll(filepath.Dir(configPath), constants.DefaultPerms755); err != nil { - return err - } - ux.Logger.PrintToUser("updating configuration file %s", configPath) - - if err = teleporter.UpdateRelayerConfig( - configPath, - app.GetAWMRelayerServiceStorageDir(storageBasePath), - relayerAddress, - relayerPrivateKey, - network, - subnetID.String(), - chainID.String(), - messengerAddress, - registryAddress, - ); err != nil { - return err - } - - _, subnetID, chainID, messengerAddress, registryAddress, _, err = teleporter.GetSubnetParams(app, network, subnetName, false) - if err != nil { - return err - } - - if err = teleporter.UpdateRelayerConfig( - configPath, - app.GetAWMRelayerServiceStorageDir(storageBasePath), - relayerAddress, - relayerPrivateKey, - network, - subnetID.String(), - chainID.String(), - messengerAddress, - registryAddress, - ); err != nil { - return err - } - - return nil -} diff --git a/cmd/teleportercmd/relayercmd/awm-relayer.service b/cmd/teleportercmd/relayercmd/awm-relayer.service deleted file mode 100644 index 886eb6137..000000000 --- a/cmd/teleportercmd/relayercmd/awm-relayer.service +++ /dev/null @@ -1,13 +0,0 @@ -[Unit] -Description=AWM Relayer systemd service -StartLimitIntervalSec=0 -[Service] -Type=simple -User=%s -WorkingDirectory=%s -ExecStart=%s --config-file %s -LimitNOFILE=32768 -Restart=always -RestartSec=1 -[Install] -WantedBy=multi-user.target diff --git a/cmd/teleportercmd/relayercmd/configList.go b/cmd/teleportercmd/relayercmd/configList.go new file mode 100644 index 000000000..6c74bad29 --- /dev/null +++ b/cmd/teleportercmd/relayercmd/configList.go @@ -0,0 +1,396 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package relayercmd + +import ( + "fmt" + "os" + + "github.com/ava-labs/avalanche-cli/pkg/contract" + "github.com/ava-labs/avalanche-cli/pkg/models" + "github.com/ava-labs/avalanche-cli/pkg/prompts" + "github.com/ava-labs/avalanche-cli/pkg/utils" + "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/olekukonko/tablewriter" +) + +type SourceSpec struct { + blockchainDesc string + rpcEndpoint string + wsEndpoint string + blockchainID string + subnetID string + rewardAddress string + icmMessengerAddress string + icmRegistryAddress string +} + +type DestinationSpec struct { + blockchainDesc string + rpcEndpoint string + blockchainID string + subnetID string + privateKey string +} + +type ConfigSpec struct { + sources []SourceSpec + destinations []DestinationSpec +} + +const ( + explainOption = "Explain the difference" + cancelOption = "Cancel" +) + +func preview(configSpec ConfigSpec) { + table := tablewriter.NewWriter(os.Stdout) + table.SetRowLine(true) + table.SetAutoMergeCellsByColumnIndex([]int{0}) + if len(configSpec.sources) > 0 { + for _, source := range configSpec.sources { + table.Append([]string{"Source", source.blockchainDesc}) + } + } + if len(configSpec.destinations) > 0 { + for _, destination := range configSpec.destinations { + table.Append([]string{"Destination", destination.blockchainDesc}) + } + } + table.Render() + fmt.Println() +} + +func addBoth(network models.Network, configSpec ConfigSpec) (ConfigSpec, error) { + prompt := "Which blockchain do you want to set both as source and destination?" + var err error + chainSpec, err := getBlockchain(network, prompt) + if err != nil { + return ConfigSpec{}, err + } + rpcEndpoint, wsEndpoint, err := contract.GetBlockchainEndpoints(app, network, chainSpec, true, true) + if err != nil { + return ConfigSpec{}, err + } + configSpec, err = addSource(network, configSpec, chainSpec, rpcEndpoint, wsEndpoint) + if err != nil { + return ConfigSpec{}, err + } + configSpec, err = addDestination(network, configSpec, chainSpec, rpcEndpoint) + if err != nil { + return ConfigSpec{}, err + } + return configSpec, nil +} + +func getBlockchain(network models.Network, prompt string) (contract.ChainSpec, error) { + chainSpec := contract.ChainSpec{} + if cancel, err := contract.PromptChain( + app, + network, + prompt, + false, + "", + true, + &chainSpec, + ); err != nil { + return chainSpec, err + } else if cancel { + return chainSpec, fmt.Errorf("cancelled by user") + } + return chainSpec, nil +} + +func addSource( + network models.Network, + configSpec ConfigSpec, + chainSpec contract.ChainSpec, + rpcEndpoint string, + wsEndpoint string, +) (ConfigSpec, error) { + if !chainSpec.Defined() { + prompt := "Which blockchain do you want to set as source?" + var err error + chainSpec, err = getBlockchain(network, prompt) + if err != nil { + return ConfigSpec{}, err + } + rpcEndpoint, wsEndpoint, err = contract.GetBlockchainEndpoints(app, network, chainSpec, true, true) + if err != nil { + return ConfigSpec{}, err + } + } + blockchainID, err := contract.GetBlockchainID(app, network, chainSpec) + if err != nil { + return ConfigSpec{}, err + } + if foundSource := utils.Find(configSpec.sources, func(s SourceSpec) bool { return s.blockchainID == blockchainID.String() }); foundSource != nil { + ux.Logger.PrintToUser("blockchain is already a source") + return configSpec, nil + } + blockchainDesc, err := contract.GetBlockchainDesc(chainSpec) + if err != nil { + return ConfigSpec{}, err + } + subnetID, err := contract.GetSubnetID(app, network, chainSpec) + if err != nil { + return ConfigSpec{}, err + } + icmRegistryAddress, icmMessengerAddress, err := contract.GetICMInfo(app, network, chainSpec, true, true, false) + if err != nil { + return ConfigSpec{}, err + } + genesisAddress, _, err := contract.GetEVMSubnetPrefundedKey( + app, + network, + chainSpec, + ) + if err != nil { + return ConfigSpec{}, err + } + rewardAddress, err := prompts.PromptAddress( + app.Prompt, + fmt.Sprintf("receive relayer rewards on %s", blockchainDesc), + app.GetKeyDir(), + app.GetKey, + genesisAddress, + network, + prompts.EVMFormat, + "Address", + ) + if err != nil { + return ConfigSpec{}, err + } + configSpec.sources = append(configSpec.sources, SourceSpec{ + blockchainDesc: blockchainDesc, + blockchainID: blockchainID.String(), + subnetID: subnetID.String(), + rewardAddress: rewardAddress, + icmRegistryAddress: icmRegistryAddress, + icmMessengerAddress: icmMessengerAddress, + rpcEndpoint: rpcEndpoint, + wsEndpoint: wsEndpoint, + }) + return configSpec, nil +} + +func addDestination( + network models.Network, + configSpec ConfigSpec, + chainSpec contract.ChainSpec, + rpcEndpoint string, +) (ConfigSpec, error) { + if !chainSpec.Defined() { + prompt := "Which blockchain do you want to set as destination?" + var err error + chainSpec, err = getBlockchain(network, prompt) + if err != nil { + return ConfigSpec{}, err + } + rpcEndpoint, _, err = contract.GetBlockchainEndpoints(app, network, chainSpec, true, false) + if err != nil { + return ConfigSpec{}, err + } + } + blockchainID, err := contract.GetBlockchainID(app, network, chainSpec) + if err != nil { + return ConfigSpec{}, err + } + if foundDestination := utils.Find(configSpec.destinations, func(s DestinationSpec) bool { return s.blockchainID == blockchainID.String() }); foundDestination != nil { + ux.Logger.PrintToUser("blockchain is already a destination") + return configSpec, nil + } + blockchainDesc, err := contract.GetBlockchainDesc(chainSpec) + if err != nil { + return ConfigSpec{}, err + } + subnetID, err := contract.GetSubnetID(app, network, chainSpec) + if err != nil { + return ConfigSpec{}, err + } + ux.Logger.PrintToUser(logging.Yellow.Wrap("Please provide a key that is not going to be used for any other purpose on destination")) + privateKey, err := prompts.PromptPrivateKey( + app.Prompt, + fmt.Sprintf("pay relayer fees on %s", blockchainDesc), + app.GetKeyDir(), + app.GetKey, + "", + "", + ) + if err != nil { + return ConfigSpec{}, err + } + configSpec.destinations = append(configSpec.destinations, DestinationSpec{ + blockchainDesc: blockchainDesc, + blockchainID: blockchainID.String(), + subnetID: subnetID.String(), + privateKey: privateKey, + rpcEndpoint: rpcEndpoint, + }) + return configSpec, nil +} + +func removeSource( + configSpec ConfigSpec, +) (ConfigSpec, bool, error) { + if len(configSpec.sources) == 0 { + ux.Logger.PrintToUser("There are no sources to remove") + ux.Logger.PrintToUser("") + return configSpec, true, nil + } + prompt := "Select the source you want to remove" + options := utils.Map(configSpec.sources, func(s SourceSpec) string { return s.blockchainDesc }) + options = append(options, cancelOption) + opt, err := app.Prompt.CaptureList(prompt, options) + if err != nil { + return configSpec, false, err + } + if opt != cancelOption { + configSpec.sources = utils.Filter(configSpec.sources, func(s SourceSpec) bool { return s.blockchainDesc != opt }) + return configSpec, false, nil + } + return configSpec, true, nil +} + +func removeDestination( + configSpec ConfigSpec, +) (ConfigSpec, bool, error) { + if len(configSpec.destinations) == 0 { + ux.Logger.PrintToUser("There are no destinations to remove") + ux.Logger.PrintToUser("") + return configSpec, true, nil + } + prompt := "Select the destination you want to remove" + options := utils.Map(configSpec.destinations, func(d DestinationSpec) string { return d.blockchainDesc }) + options = append(options, cancelOption) + opt, err := app.Prompt.CaptureList(prompt, options) + if err != nil { + return configSpec, false, err + } + if opt != cancelOption { + configSpec.destinations = utils.Filter(configSpec.destinations, func(d DestinationSpec) bool { return d.blockchainDesc != opt }) + return configSpec, false, nil + } + return configSpec, true, nil +} + +func GenerateConfigSpec(network models.Network) (ConfigSpec, bool, error) { + configSpec := ConfigSpec{} + + prompt := "Configure the blockchains that will be interconnected by the relayer" + + addOption := "Add a blockchain" + removeOption := "Remove a blockchain" + previewOption := "Preview" + confirmOption := "Confirm" + + for { + options := []string{ + addOption, + removeOption, + previewOption, + confirmOption, + cancelOption, + } + if len(configSpec.sources) == 0 && len(configSpec.destinations) == 0 { + options = utils.RemoveFromSlice(options, removeOption) + options = utils.RemoveFromSlice(options, previewOption) + options = utils.RemoveFromSlice(options, confirmOption) + } + option, err := app.Prompt.CaptureList(prompt, options) + if err != nil { + return ConfigSpec{}, false, err + } + switch option { + case addOption: + addPrompt := "What role should the blockchain have?" + addBothOption := "Source and Destination" + addSourceOption := "Source only" + addDestinationOption := "Destination only" + for { + options := []string{addBothOption, addSourceOption, addDestinationOption, explainOption, cancelOption} + roleOption, err := app.Prompt.CaptureList(addPrompt, options) + if err != nil { + return ConfigSpec{}, false, err + } + switch roleOption { + case addBothOption: + configSpec, err = addBoth(network, configSpec) + if err != nil { + return ConfigSpec{}, false, err + } + case addSourceOption: + configSpec, err = addSource(network, configSpec, contract.ChainSpec{}, "", "") + if err != nil { + return ConfigSpec{}, false, err + } + case addDestinationOption: + configSpec, err = addDestination(network, configSpec, contract.ChainSpec{}, "") + if err != nil { + return ConfigSpec{}, false, err + } + case explainOption: + ux.Logger.PrintToUser("A source blockchain is going to be listened by the relayer to check for new") + ux.Logger.PrintToUser("messages. You need to specify blockchain ID, teleporter addresses.") + ux.Logger.PrintToUser("A destination blockchain is going to be connected by the relayer in order") + ux.Logger.PrintToUser("to deliver a message. You need to specify blockchain ID, private key") + continue + case cancelOption: + } + break + } + case removeOption: + keepAsking := true + for keepAsking { + removePrompt := "Which role do you want to remove?" + removeSourceOption := "Source" + removeDestinationOption := "Destination" + options := []string{} + if len(configSpec.sources) != 0 { + options = append(options, removeSourceOption) + } + if len(configSpec.destinations) != 0 { + options = append(options, removeDestinationOption) + } + options = append(options, cancelOption) + kindOption, err := app.Prompt.CaptureList(removePrompt, options) + if err != nil { + return ConfigSpec{}, false, err + } + switch kindOption { + case removeSourceOption: + configSpec, keepAsking, err = removeSource(configSpec) + if err != nil { + return ConfigSpec{}, false, err + } + case removeDestinationOption: + configSpec, keepAsking, err = removeDestination(configSpec) + if err != nil { + return ConfigSpec{}, false, err + } + case cancelOption: + keepAsking = false + } + } + case previewOption: + preview(configSpec) + case confirmOption: + preview(configSpec) + confirmPrompt := "Confirm?" + yesOption := "Yes" + noOption := "No, keep editing" + confirmOption, err := app.Prompt.CaptureList( + confirmPrompt, []string{yesOption, noOption}, + ) + if err != nil { + return ConfigSpec{}, false, err + } + if confirmOption == yesOption { + return configSpec, false, nil + } + case cancelOption: + return ConfigSpec{}, true, err + } + } +} diff --git a/cmd/teleportercmd/relayercmd/deploy.go b/cmd/teleportercmd/relayercmd/deploy.go new file mode 100644 index 000000000..2ccc4fe8b --- /dev/null +++ b/cmd/teleportercmd/relayercmd/deploy.go @@ -0,0 +1,407 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package relayercmd + +import ( + "fmt" + "math/big" + "os" + "strings" + + "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/constants" + "github.com/ava-labs/avalanche-cli/pkg/contract" + "github.com/ava-labs/avalanche-cli/pkg/evm" + "github.com/ava-labs/avalanche-cli/pkg/localnet" + "github.com/ava-labs/avalanche-cli/pkg/models" + "github.com/ava-labs/avalanche-cli/pkg/networkoptions" + "github.com/ava-labs/avalanche-cli/pkg/prompts" + "github.com/ava-labs/avalanche-cli/pkg/teleporter" + "github.com/ava-labs/avalanche-cli/pkg/utils" + "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanche-cli/pkg/vm" + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ethereum/go-ethereum/crypto" + + "github.com/spf13/cobra" +) + +type DeployFlags struct { + Network networkoptions.NetworkFlags + Version string +} + +var ( + deploySupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.Fuji, + } + deployFlags DeployFlags +) + +const disableDeployToRemotePrompt = true + +// avalanche teleporter relayer deploy +func newDeployCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "deploy", + Short: "Deploys an ICM Relayer for the given Network", + Long: `Deploys an ICM Relayer for the given Network.`, + RunE: deploy, + Args: cobrautils.ExactArgs(0), + } + networkoptions.AddNetworkFlagsToCmd(cmd, &deployFlags.Network, true, deploySupportedNetworkOptions) + cmd.Flags().StringVar(&deployFlags.Version, "version", "latest", "version to deploy") + // flag for binary to use + // flag for local process vs cloud + // flag for provided config file (may need to change tmp dir) + return cmd +} + +func deploy(_ *cobra.Command, args []string) error { + return CallDeploy(args, deployFlags) +} + +func CallDeploy(_ []string, flags DeployFlags) error { + network, err := networkoptions.GetNetworkFromCmdLineFlags( + app, + "In which Network will operate the Relayer?", + flags.Network, + true, + false, + deploySupportedNetworkOptions, + "", + ) + if err != nil { + return err + } + + deployToRemote := false + if !disableDeployToRemotePrompt && network.Kind != models.Local { + prompt := "Do you want to deploy the relayer to a remote or a local host?" + remoteHostOption := "I want to deploy the relayer into a remote node in the cloud" + localHostOption := "I prefer to deploy into a localhost process" + options := []string{remoteHostOption, localHostOption, explainOption} + for { + option, err := app.Prompt.CaptureList( + prompt, + options, + ) + if err != nil { + return err + } + switch option { + case remoteHostOption: + deployToRemote = true + case localHostOption: + case explainOption: + ux.Logger.PrintToUser("A local host relayer is for temporary networks, won't survive a host restart") + ux.Logger.PrintToUser("or a relayer transient failure (but anyway can be manually restarted by cmd)") + ux.Logger.PrintToUser("A remote relayer is deployed into a new cloud node, and will recover from") + ux.Logger.PrintToUser("temporary relayer failures and from host restarts.") + continue + } + break + } + } + + if !deployToRemote { + if isUP, _, _, err := teleporter.RelayerIsUp(app.GetLocalRelayerRunPath(network.Kind)); err != nil { + return err + } else if isUP { + return fmt.Errorf("there is already a local relayer deployed for %s", network.Kind.String()) + } + } + + // TODO: put in prompts + prompt := "Which log level do you prefer for your relayer?" + options := []string{ + logging.Info.LowerString(), + logging.Warn.LowerString(), + logging.Error.LowerString(), + logging.Off.LowerString(), + logging.Fatal.LowerString(), + logging.Debug.LowerString(), + logging.Trace.LowerString(), + logging.Verbo.LowerString(), + } + logLevel, err := app.Prompt.CaptureList( + prompt, + options, + ) + if err != nil { + return err + } + + networkUP := true + _, err = utils.GetChainID(network.Endpoint, "C") + if err != nil { + if !strings.Contains(err.Error(), "connection refused") { + return err + } + networkUP = false + } + + configureBlockchains := false + if networkUP { + prompt = "Do you want to add blockchain information to your relayer?" + yesOption := "Yes, I want to configure source and destination blockchains" + noOption := "No, I prefer to configure the relayer later on" + options = []string{yesOption, noOption, explainOption} + for { + option, err := app.Prompt.CaptureList( + prompt, + options, + ) + if err != nil { + return err + } + switch option { + case yesOption: + configureBlockchains = true + case noOption: + case explainOption: + ux.Logger.PrintToUser("You can configure a list of source and destination blockchains, so that the") + ux.Logger.PrintToUser("relayer will listen for new messages on each source, and deliver them to the") + ux.Logger.PrintToUser("destinations.") + ux.Logger.PrintToUser("Or you can not configure those later on, by using the 'relayer config' cmd.") + continue + } + break + } + } + + var configSpec ConfigSpec + if configureBlockchains { + // TODO: this is the base for a 'relayer config' cmd + // that should load the current config, generate a configSpec for that, + // and use this to change the config, before saving it + // most probably, also, relayer config should restart the relayer + var cancel bool + configSpec, cancel, err = GenerateConfigSpec(network) + if cancel { + return nil + } + if err != nil { + return err + } + } + + fundBlockchains := false + if networkUP && len(configSpec.destinations) > 0 { + // TODO: this (and the next section) are the base for a 'relayer fund' cmd + // it must be based on relayer conf, and try to gather a nice blockchain desc + // from the blockchain id (as relayer logs cmd) + ux.Logger.PrintToUser("") + for _, destination := range configSpec.destinations { + pk, err := crypto.HexToECDSA(destination.privateKey) + if err != nil { + return err + } + addr := crypto.PubkeyToAddress(pk.PublicKey) + client, err := evm.GetClient(destination.rpcEndpoint) + if err != nil { + return err + } + balance, err := evm.GetAddressBalance(client, addr.Hex()) + if err != nil { + return err + } + balanceFlt := new(big.Float).SetInt(balance) + balanceFlt = balanceFlt.Quo(balanceFlt, new(big.Float).SetInt(vm.OneAvax)) + ux.Logger.PrintToUser("Relayer private key on destination %s has a balance of %.9f", destination.blockchainDesc, balanceFlt) + } + ux.Logger.PrintToUser("") + + prompt = "Do you want to fund relayer destinations?" + yesOption := "Yes, I want to fund destination blockchains" + noOption := "No, I prefer to fund the relayer later on" + options = []string{yesOption, noOption, explainOption} + for { + option, err := app.Prompt.CaptureList( + prompt, + options, + ) + if err != nil { + return err + } + switch option { + case yesOption: + fundBlockchains = true + case noOption: + case explainOption: + ux.Logger.PrintToUser("You need to set some balance on the destination addresses") + ux.Logger.PrintToUser("so the relayer can pay for fees when delivering messages.") + continue + } + break + } + } + + if fundBlockchains { + for _, destination := range configSpec.destinations { + pk, err := crypto.HexToECDSA(destination.privateKey) + if err != nil { + return err + } + addr := crypto.PubkeyToAddress(pk.PublicKey) + client, err := evm.GetClient(destination.rpcEndpoint) + if err != nil { + return err + } + balance, err := evm.GetAddressBalance(client, addr.Hex()) + if err != nil { + return err + } + balanceFlt := new(big.Float).SetInt(balance) + balanceFlt = balanceFlt.Quo(balanceFlt, new(big.Float).SetInt(vm.OneAvax)) + prompt = fmt.Sprintf("Do you want to fund relayer for destination %s (balance=%.9f)?", destination.blockchainDesc, balanceFlt) + yesOption := "Yes, I will send funds to it" + noOption := "Not now" + options = []string{yesOption, noOption} + doPay := false + option, err := app.Prompt.CaptureList( + prompt, + options, + ) + if err != nil { + return err + } + switch option { + case yesOption: + doPay = true + case noOption: + } + if doPay { + genesisAddress, genesisPrivateKey, err := contract.GetEVMSubnetPrefundedKey( + app, + network, + contract.ChainSpec{ + BlockchainID: destination.blockchainID, + }, + ) + if err != nil { + return err + } + privateKey, err := prompts.PromptPrivateKey( + app.Prompt, + fmt.Sprintf("fund the relayer destination %s", destination.blockchainDesc), + app.GetKeyDir(), + app.GetKey, + genesisAddress, + genesisPrivateKey, + ) + if err != nil { + return err + } + amountFlt, err := app.Prompt.CaptureFloat( + "Amount to transfer", + func(f float64) error { + if f <= 0 { + return fmt.Errorf("not positive") + } + return nil + }, + ) + if err != nil { + return err + } + amountBigFlt := new(big.Float).SetFloat64(amountFlt) + amountBigFlt = amountBigFlt.Mul(amountBigFlt, new(big.Float).SetInt(vm.OneAvax)) + amount, _ := amountBigFlt.Int(nil) + if err := evm.FundAddress(client, privateKey, addr.Hex(), amount); err != nil { + return err + } + } + } + } + + if deployToRemote { + return nil + } + + runFilePath := app.GetLocalRelayerRunPath(network.Kind) + storageDir := app.GetLocalRelayerStorageDir(network.Kind) + localNetworkRootDir := "" + if network.Kind == models.Local { + clusterInfo, err := localnet.GetClusterInfo() + if err != nil { + return err + } + localNetworkRootDir = clusterInfo.GetRootDataDir() + } + configPath := app.GetLocalRelayerConfigPath(network.Kind, localNetworkRootDir) + logPath := app.GetLocalRelayerLogPath(network.Kind) + + metricsPort := constants.RemoteAWMRelayerMetricsPort + if !deployToRemote { + switch network.Kind { + case models.Local: + metricsPort = constants.LocalNetworkLocalAWMRelayerMetricsPort + case models.Devnet: + metricsPort = constants.DevnetLocalAWMRelayerMetricsPort + case models.Fuji: + metricsPort = constants.FujiLocalAWMRelayerMetricsPort + } + } + + // create config + ux.Logger.PrintToUser("") + ux.Logger.PrintToUser("Generating relayer config file at %s", configPath) + if err := teleporter.CreateBaseRelayerConfig( + configPath, + logLevel, + storageDir, + uint16(metricsPort), + network, + ); err != nil { + return err + } + for _, source := range configSpec.sources { + if err := teleporter.AddSourceToRelayerConfig( + configPath, + source.rpcEndpoint, + source.wsEndpoint, + source.subnetID, + source.blockchainID, + source.icmRegistryAddress, + source.icmMessengerAddress, + source.rewardAddress, + ); err != nil { + return err + } + } + for _, destination := range configSpec.destinations { + if err := teleporter.AddDestinationToRelayerConfig( + configPath, + destination.rpcEndpoint, + destination.subnetID, + destination.blockchainID, + destination.privateKey, + ); err != nil { + return err + } + } + + if len(configSpec.sources) > 0 && len(configSpec.destinations) > 0 { + // relayer fails for empty configs + err := teleporter.DeployRelayer( + flags.Version, + app.GetAWMRelayerBinDir(), + configPath, + logPath, + runFilePath, + storageDir, + ) + if err != nil { + if bs, err := os.ReadFile(logPath); err == nil { + ux.Logger.PrintToUser("") + ux.Logger.PrintToUser(string(bs)) + } + } + return err + } + + return nil +} diff --git a/cmd/teleportercmd/relayercmd/logs.go b/cmd/teleportercmd/relayercmd/logs.go index e0597d43d..42c80beac 100644 --- a/cmd/teleportercmd/relayercmd/logs.go +++ b/cmd/teleportercmd/relayercmd/logs.go @@ -11,9 +11,9 @@ import ( "time" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" - "github.com/ava-labs/avalanche-cli/pkg/teleporter" "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanchego/utils/logging" @@ -24,10 +24,13 @@ import ( ) var ( - logsNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local} - raw bool - last uint - first uint + logsNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Fuji, + } + raw bool + last uint + first uint ) // avalanche teleporter relayer logs @@ -51,7 +54,7 @@ func logs(_ *cobra.Command, _ []string) error { app, "", globalNetworkFlags, - false, + true, false, logsNetworkOptions, "", @@ -61,8 +64,8 @@ func logs(_ *cobra.Command, _ []string) error { } var logLines []string switch { - case network.Kind == models.Local: - logsPath := app.GetAWMRelayerLogPath() + case network.Kind == models.Local || network.Kind == models.Fuji: + logsPath := app.GetLocalRelayerLogPath(network.Kind) bs, err := os.ReadFile(logsPath) if err != nil { return err @@ -91,7 +94,7 @@ func logs(_ *cobra.Command, _ []string) error { } return nil } - blockchainIDToSubnetName, err := getBlockchainIDToSubnetNameMap(network) + blockchainIDToBlockchainName, err := getBlockchainIDToBlockchainNameMap(network) if err != nil { return err } @@ -108,7 +111,7 @@ func logs(_ *cobra.Command, _ []string) error { levelEmoji := "" levelStr, b := logMap["level"].(string) if b { - levelEmoji, err = logLevelToEmoji(levelStr) + levelEmoji, err = utils.LogLevelToEmoji(levelStr) if err != nil { return err } @@ -139,11 +142,11 @@ func logs(_ *cobra.Command, _ []string) error { logMap, k, k, - blockchainIDToSubnetName, + blockchainIDToBlockchainName, ) } } - subnet := getLogSubnet(logMap, blockchainIDToSubnetName) + subnet := getLogSubnet(logMap, blockchainIDToBlockchainName) t.AppendRow(table.Row{levelEmoji, timeStr, subnet, logMsg}) } } @@ -157,13 +160,13 @@ func addAditionalInfo( logMap map[string]interface{}, key string, outputName string, - blockchainIDToSubnetName map[string]string, + blockchainIDToBlockchainName map[string]string, ) string { value, b := logMap[key].(string) if b { - subnetName := blockchainIDToSubnetName[value] - if subnetName != "" { - value = subnetName + blockchainName := blockchainIDToBlockchainName[value] + if blockchainName != "" { + value = blockchainName } logMsg = fmt.Sprintf("%s\n %s=%s", logMsg, outputName, value) } @@ -172,7 +175,7 @@ func addAditionalInfo( func getLogSubnet( logMap map[string]interface{}, - blockchainIDToSubnetName map[string]string, + blockchainIDToBlockchainName map[string]string, ) string { for _, key := range []string{ "blockchainID", @@ -182,53 +185,32 @@ func getLogSubnet( } { value, b := logMap[key].(string) if b { - subnetName := blockchainIDToSubnetName[value] - if subnetName != "" { - return subnetName + blockchainName := blockchainIDToBlockchainName[value] + if blockchainName != "" { + return blockchainName } } } return "" } -func getBlockchainIDToSubnetNameMap(network models.Network) (map[string]string, error) { - subnetNames, err := app.GetSubnetNamesOnNetwork(network) +func getBlockchainIDToBlockchainNameMap(network models.Network) (map[string]string, error) { + blockchainNames, err := app.GetBlockchainNamesOnNetwork(network) if err != nil { return nil, err } - blockchainIDToSubnetName := map[string]string{} - for _, subnetName := range subnetNames { - _, _, blockchainID, _, _, _, err := teleporter.GetSubnetParams(app, network, subnetName, false) + blockchainIDToBlockchainName := map[string]string{} + for _, blockchainName := range blockchainNames { + blockchainID, err := contract.GetBlockchainID(app, network, contract.ChainSpec{BlockchainName: blockchainName}) if err != nil { return nil, err } - blockchainIDToSubnetName[blockchainID.String()] = subnetName + blockchainIDToBlockchainName[blockchainID.String()] = blockchainName } - _, _, blockchainID, _, _, _, err := teleporter.GetSubnetParams(app, network, "", true) + blockchainID, err := contract.GetBlockchainID(app, network, contract.ChainSpec{CChain: true}) if err != nil { return nil, err } - blockchainIDToSubnetName[blockchainID.String()] = "c-chain" - return blockchainIDToSubnetName, nil -} - -func logLevelToEmoji(logLevel string) (string, error) { - levelEmoji := "" - level, err := logging.ToLevel(logLevel) - if err != nil { - return "", err - } - switch level { - case logging.Info: - levelEmoji = "ℹ️" - case logging.Debug: - levelEmoji = "🪲" - case logging.Warn: - levelEmoji = "⚠️" - case logging.Error: - levelEmoji = "⛔" - case logging.Fatal: - levelEmoji = "💀" - } - return levelEmoji, nil + blockchainIDToBlockchainName[blockchainID.String()] = "c-chain" + return blockchainIDToBlockchainName, nil } diff --git a/cmd/teleportercmd/relayercmd/prepareService.go b/cmd/teleportercmd/relayercmd/prepareService.go deleted file mode 100644 index d41a81774..000000000 --- a/cmd/teleportercmd/relayercmd/prepareService.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package relayercmd - -import ( - _ "embed" - "fmt" - "os" - "os/user" - "path/filepath" - - "github.com/ava-labs/avalanche-cli/pkg/cobrautils" - "github.com/ava-labs/avalanche-cli/pkg/constants" - "github.com/ava-labs/avalanche-cli/pkg/teleporter" - "github.com/spf13/cobra" -) - -//go:embed awm-relayer.service -var awmRelayerServiceTemplate []byte - -// avalanche teleporter msg -func newPrepareServiceCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "prepareService", - Short: "Installs AWM relayer as a service", - Long: `Installs AWM relayer as a service. Disabled by default.`, - RunE: prepareService, - Args: cobrautils.ExactArgs(0), - } - return cmd -} - -func prepareService(_ *cobra.Command, _ []string) error { - relayerBin, err := teleporter.InstallRelayer(app.GetAWMRelayerBinDir()) - if err != nil { - return err - } - usr, err := user.Current() - if err != nil { - return err - } - if err := os.MkdirAll(app.GetAWMRelayerServiceDir(""), constants.DefaultPerms755); err != nil { - return err - } - awmRelayerServicePath := filepath.Join(app.GetAWMRelayerServiceDir(""), "awm-relayer.service") - awmRelayerServiceConf := fmt.Sprintf(string(awmRelayerServiceTemplate), usr.Username, usr.HomeDir, relayerBin, app.GetAWMRelayerServiceConfigPath("")) - if err := os.WriteFile(awmRelayerServicePath, []byte(awmRelayerServiceConf), constants.WriteReadReadPerms); err != nil { - return err - } - return os.RemoveAll(app.GetAWMRelayerServiceConfigPath("")) -} diff --git a/cmd/teleportercmd/relayercmd/relayer.go b/cmd/teleportercmd/relayercmd/relayer.go index b750ed613..824e95715 100644 --- a/cmd/teleportercmd/relayercmd/relayer.go +++ b/cmd/teleportercmd/relayercmd/relayer.go @@ -14,16 +14,17 @@ var app *application.Avalanche func NewCmd(injectedApp *application.Avalanche) *cobra.Command { cmd := &cobra.Command{ Use: "relayer", - Short: "Install and configure relayer on localhost", - Long: `The relayert command suite provides a collection of tools for installing -and configuring an AWM relayer on localhost.`, + Short: "Manage ICM relayers", + Long: `The relayer command suite provides a collection of tools for deploying +and configuring an ICM relayers.`, RunE: cobrautils.CommandSuiteUsage, } app = injectedApp - cmd.AddCommand(newPrepareServiceCmd()) - cmd.AddCommand(newAddSubnetToServiceCmd()) - cmd.AddCommand(newStopCmd()) - cmd.AddCommand(newStartCmd()) + cmd.AddCommand(newDeployCmd()) cmd.AddCommand(newLogsCmd()) + cmd.AddCommand(newStartCmd()) + cmd.AddCommand(newStopCmd()) + // TODO: config + // TODO: fund return cmd } diff --git a/cmd/teleportercmd/relayercmd/start.go b/cmd/teleportercmd/relayercmd/start.go index 4558c37e0..f94eac7ab 100644 --- a/cmd/teleportercmd/relayercmd/start.go +++ b/cmd/teleportercmd/relayercmd/start.go @@ -6,19 +6,20 @@ import ( "fmt" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/localnet" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/networkoptions" "github.com/ava-labs/avalanche-cli/pkg/node" "github.com/ava-labs/avalanche-cli/pkg/ssh" - "github.com/ava-labs/avalanche-cli/pkg/subnet" "github.com/ava-labs/avalanche-cli/pkg/teleporter" + "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" "github.com/spf13/cobra" ) var ( - startNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Cluster} + startNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Cluster, networkoptions.Fuji} globalNetworkFlags networkoptions.NetworkFlags ) @@ -49,29 +50,37 @@ func start(_ *cobra.Command, _ []string) error { return err } switch { - case network.Kind == models.Local: + case network.Kind == models.Local || network.Kind == models.Fuji: if relayerIsUp, _, _, err := teleporter.RelayerIsUp( - app.GetAWMRelayerRunPath(), + app.GetLocalRelayerRunPath(network.Kind), ); err != nil { return err } else if relayerIsUp { - return fmt.Errorf("local AWM relayer is already running") + return fmt.Errorf("local AWM relayer is already running for %s", network.Kind) } - if b, relayerConfigPath, err := subnet.GetAWMRelayerConfigPath(); err != nil { - return err - } else if !b { + localNetworkRootDir := "" + if network.Kind == models.Local { + clusterInfo, err := localnet.GetClusterInfo() + if err != nil { + return err + } + localNetworkRootDir = clusterInfo.GetRootDataDir() + } + relayerConfigPath := app.GetLocalRelayerConfigPath(network.Kind, localNetworkRootDir) + if !utils.FileExists(relayerConfigPath) { return fmt.Errorf("there is no relayer configuration available") } else if err := teleporter.DeployRelayer( + "latest", app.GetAWMRelayerBinDir(), relayerConfigPath, - app.GetAWMRelayerLogPath(), - app.GetAWMRelayerRunPath(), - app.GetAWMRelayerStorageDir(), + app.GetLocalRelayerLogPath(network.Kind), + app.GetLocalRelayerRunPath(network.Kind), + app.GetLocalRelayerStorageDir(network.Kind), ); err != nil { return err } - ux.Logger.GreenCheckmarkToUser("Local AWM Relayer successfully started") - ux.Logger.PrintToUser("Logs can be found at %s", app.GetAWMRelayerLogPath()) + ux.Logger.GreenCheckmarkToUser("Local AWM Relayer successfully started for %s", network.Kind) + ux.Logger.PrintToUser("Logs can be found at %s", app.GetLocalRelayerLogPath(network.Kind)) case network.ClusterName != "": host, err := node.GetAWMRelayerHost(app, network.ClusterName) if err != nil { diff --git a/cmd/teleportercmd/relayercmd/stop.go b/cmd/teleportercmd/relayercmd/stop.go index 50222a89c..ea000ba26 100644 --- a/cmd/teleportercmd/relayercmd/stop.go +++ b/cmd/teleportercmd/relayercmd/stop.go @@ -16,7 +16,7 @@ import ( "github.com/spf13/cobra" ) -var stopNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Cluster} +var stopNetworkOptions = []networkoptions.NetworkOption{networkoptions.Local, networkoptions.Cluster, networkoptions.Fuji} // avalanche teleporter relayer stop func newStopCmd() *cobra.Command { @@ -45,23 +45,23 @@ func stop(_ *cobra.Command, _ []string) error { return err } switch { - case network.Kind == models.Local: + case network.Kind == models.Local || network.Kind == models.Fuji: b, _, _, err := teleporter.RelayerIsUp( - app.GetAWMRelayerRunPath(), + app.GetLocalRelayerRunPath(network.Kind), ) if err != nil { return err } if !b { - return fmt.Errorf("there is no CLI-managed local AWM relayer running") + return fmt.Errorf("there is no CLI-managed local AWM relayer running for %s", network.Kind) } if err := teleporter.RelayerCleanup( - app.GetAWMRelayerRunPath(), - app.GetAWMRelayerStorageDir(), + app.GetLocalRelayerRunPath(network.Kind), + app.GetLocalRelayerStorageDir(network.Kind), ); err != nil { return err } - ux.Logger.GreenCheckmarkToUser("Local AWM Relayer successfully stopped") + ux.Logger.GreenCheckmarkToUser("Local AWM Relayer successfully stopped for %s", network.Kind) case network.ClusterName != "": host, err := node.GetAWMRelayerHost(app, network.ClusterName) if err != nil { diff --git a/cmd/transactioncmd/transaction_commit.go b/cmd/transactioncmd/transaction_commit.go index 7df7d79ab..21712c924 100644 --- a/cmd/transactioncmd/transaction_commit.go +++ b/cmd/transactioncmd/transaction_commit.go @@ -5,7 +5,7 @@ package transactioncmd import ( "fmt" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/keychain" "github.com/ava-labs/avalanche-cli/pkg/subnet" @@ -57,14 +57,13 @@ func commitTx(_ *cobra.Command, args []string) error { if subnetID == ids.Empty { return errNoSubnetID } - transferSubnetOwnershipTxID := sc.Networks[network.Name()].TransferSubnetOwnershipTxID isPermissioned, controlKeys, _, err := txutils.GetOwners(network, subnetID) if err != nil { return err } if !isPermissioned { - return subnetcmd.ErrNotPermissionedSubnet + return blockchaincmd.ErrNotPermissionedSubnet } subnetAuthKeys, remainingSubnetAuthKeys, err := txutils.GetRemainingSigners(tx, controlKeys) if err != nil { @@ -74,7 +73,7 @@ func commitTx(_ *cobra.Command, args []string) error { if len(remainingSubnetAuthKeys) != 0 { signedCount := len(subnetAuthKeys) - len(remainingSubnetAuthKeys) ux.Logger.PrintToUser("%d of %d required signatures have been signed.", signedCount, len(subnetAuthKeys)) - subnetcmd.PrintRemainingToSignMsg(subnetName, remainingSubnetAuthKeys, inputTxPath) + blockchaincmd.PrintRemainingToSignMsg(subnetName, remainingSubnetAuthKeys, inputTxPath) return fmt.Errorf("tx is not fully signed") } @@ -95,17 +94,10 @@ func commitTx(_ *cobra.Command, args []string) error { if txutils.IsCreateChainTx(tx) { // TODO: teleporter for multisig - if err := subnetcmd.PrintDeployResults(subnetName, subnetID, txID); err != nil { + if err := blockchaincmd.PrintDeployResults(subnetName, subnetID, txID); err != nil { return err } - return app.UpdateSidecarNetworks(&sc, network, subnetID, transferSubnetOwnershipTxID, txID, "", "") - } - - if txutils.IsTransferSubnetOwnershipTx(tx) { - networkData := sc.Networks[network.Name()] - networkData.TransferSubnetOwnershipTxID = txID - sc.Networks[network.Name()] = networkData - return app.UpdateSidecar(&sc) + return app.UpdateSidecarNetworks(&sc, network, subnetID, txID, "", "") } return nil diff --git a/cmd/transactioncmd/transaction_sign.go b/cmd/transactioncmd/transaction_sign.go index afea50ce7..0299dde25 100644 --- a/cmd/transactioncmd/transaction_sign.go +++ b/cmd/transactioncmd/transaction_sign.go @@ -6,7 +6,7 @@ import ( "errors" "fmt" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" "github.com/ava-labs/avalanche-cli/pkg/keychain" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -64,7 +64,7 @@ func signTx(_ *cobra.Command, args []string) error { } if useLedger && keyName != "" { - return subnetcmd.ErrMutuallyExlusiveKeyLedger + return blockchaincmd.ErrMutuallyExlusiveKeyLedger } // we need network to decide if ledger is forced (mainnet) @@ -90,7 +90,7 @@ func signTx(_ *cobra.Command, args []string) error { case models.Mainnet: useLedger = true if keyName != "" { - return subnetcmd.ErrStoredKeyOnMainnet + return blockchaincmd.ErrStoredKeyOnMainnet } default: return errors.New("unsupported network") @@ -106,7 +106,6 @@ func signTx(_ *cobra.Command, args []string) error { if subnetID == ids.Empty { return errNoSubnetID } - transferSubnetOwnershipTxID := sc.Networks[network.Name()].TransferSubnetOwnershipTxID subnetIDFromTX, err := txutils.GetSubnetID(tx) if err != nil { @@ -121,7 +120,7 @@ func signTx(_ *cobra.Command, args []string) error { return err } if !isPermissioned { - return subnetcmd.ErrNotPermissionedSubnet + return blockchaincmd.ErrNotPermissionedSubnet } // get the remaining tx signers so as to check that the wallet does contain an expected signer @@ -131,7 +130,7 @@ func signTx(_ *cobra.Command, args []string) error { } if len(remainingSubnetAuthKeys) == 0 { - subnetcmd.PrintReadyToSignMsg(subnetName, inputTxPath) + blockchaincmd.PrintReadyToSignMsg(subnetName, inputTxPath) ux.Logger.PrintToUser("") return fmt.Errorf("tx is already fully signed") } @@ -152,7 +151,6 @@ func signTx(_ *cobra.Command, args []string) error { tx, remainingSubnetAuthKeys, subnetID, - transferSubnetOwnershipTxID, ); err != nil { if errors.Is(err, subnet.ErrNoSubnetAuthKeysInWallet) { ux.Logger.PrintToUser("There are no required subnet auth keys present in the wallet") @@ -173,7 +171,7 @@ func signTx(_ *cobra.Command, args []string) error { return err } - if err := subnetcmd.SaveNotFullySignedTx( + if err := blockchaincmd.SaveNotFullySignedTx( "Tx", tx, subnetName, diff --git a/go.mod b/go.mod index b06c4b983..343628f76 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,21 @@ module github.com/ava-labs/avalanche-cli -go 1.21.11 +go 1.21.12 toolchain go1.22.3 require ( github.com/ava-labs/apm v1.0.0 - github.com/ava-labs/avalanche-network-runner v1.8.1-0.20240613173905-80d33ed0b7b9 - github.com/ava-labs/avalanchego v1.11.8 + github.com/ava-labs/avalanche-network-runner v1.8.3-0.20240815175406-50423422fb5b + github.com/ava-labs/avalanchego v1.11.11-0.20240815211142-ce78e7f1799f github.com/ava-labs/awm-relayer v1.3.0 - github.com/ava-labs/coreth v0.13.5-rc.0 - github.com/ava-labs/subnet-evm v0.6.6 + github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180 + github.com/ava-labs/subnet-evm v0.6.9-0.20240815191823-9f3608326298 github.com/aws/aws-sdk-go-v2 v1.30.3 github.com/aws/aws-sdk-go-v2/config v1.27.26 github.com/aws/aws-sdk-go-v2/service/ec2 v1.162.0 github.com/chelnak/ysmrr v0.4.0 - github.com/docker/docker v26.1.3+incompatible + github.com/docker/docker v27.1.1+incompatible github.com/ethereum/go-ethereum v1.13.8 github.com/fatih/color v1.17.0 github.com/go-git/go-git/v5 v5.12.0 @@ -228,6 +228,5 @@ require ( gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect - gotest.tools/v3 v3.5.1 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 8fd088534..5986674f4 100644 --- a/go.sum +++ b/go.sum @@ -83,18 +83,18 @@ github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPd github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/ava-labs/apm v1.0.0 h1:6FwozH67hEkbWVsOXNZGexBy5KLpNeYucN9zcFUHv+Q= github.com/ava-labs/apm v1.0.0/go.mod h1:TJL7pTlZNvQatsQPsLUtDHApEwVZ/qS7iSNtRFU83mc= -github.com/ava-labs/avalanche-network-runner v1.8.1-0.20240613173905-80d33ed0b7b9 h1:jYD3HlF6Z/fI1mYrsK3fCr+v1F9+2odrK75HZoGVuJs= -github.com/ava-labs/avalanche-network-runner v1.8.1-0.20240613173905-80d33ed0b7b9/go.mod h1:5Ejby7M+UaBDl8fhIXkJhVPgAHOzivuZYEUTNv8O0lM= -github.com/ava-labs/avalanchego v1.11.8 h1:Q/der5bC/q3BQbIqxT7nNC0F30c+6X1G/eQzzMQ2CLk= -github.com/ava-labs/avalanchego v1.11.8/go.mod h1:aPYTETkM0KjtC7vFwPO6S8z2L0QTKaXjVUi98pTdNO4= +github.com/ava-labs/avalanche-network-runner v1.8.3-0.20240815175406-50423422fb5b h1:9DEFDbJFdkMxzfjwUm1KbyzqYlhn8QDYrW9pYmFVNdA= +github.com/ava-labs/avalanche-network-runner v1.8.3-0.20240815175406-50423422fb5b/go.mod h1:X4ly6Ebq6vzsj9q9G9W00YftOrS7ZT2jLi9cxLqT4eQ= +github.com/ava-labs/avalanchego v1.11.11-0.20240815211142-ce78e7f1799f h1:fLWva3zOGwhZAnLq+nhiTs1Q24dL1nPvF4qVZZ6IGFg= +github.com/ava-labs/avalanchego v1.11.11-0.20240815211142-ce78e7f1799f/go.mod h1:UkyrRDXK2E15Lq2abyae2Pt+JsWvgsg1pe0/AtoMyAM= github.com/ava-labs/awm-relayer v1.3.0 h1:aI90Daoq7bs9SnfZpyjXajj7YLcdZoFgEkyCd52fOHE= github.com/ava-labs/awm-relayer v1.3.0/go.mod h1:UI1Tm0jfFIpO1S2hpE5WGzJCDpLHLldhsE1bWn2FjEE= -github.com/ava-labs/coreth v0.13.5-rc.0 h1:PJQbR9o2RrW3j9ba4r1glXnmM2PNAP3xR569+gMcBd0= -github.com/ava-labs/coreth v0.13.5-rc.0/go.mod h1:cm5c12xo5NiTgtbmeduv8i2nYdzgkczz9Wm3yiwwTRU= +github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180 h1:6aIHp7wbyGVYdhHVQUbG7BEcbCMEQ5SYopPPJyipyvk= +github.com/ava-labs/coreth v0.13.8-fixed-genesis-upgrade.0.20240813194342-7635a96aa180/go.mod h1:/wNBVq7J7wlC2Kbov7kk6LV5xZvau7VF9zwTVOeyAjY= github.com/ava-labs/ledger-avalanche/go v0.0.0-20240610153809-9c955cc90a95 h1:dOVbtdnZL++pENdTCNZ1nu41eYDQkTML4sWebDnnq8c= github.com/ava-labs/ledger-avalanche/go v0.0.0-20240610153809-9c955cc90a95/go.mod h1:pJxaT9bUgeRNVmNRgtCHb7sFDIRKy7CzTQVi8gGNT6g= -github.com/ava-labs/subnet-evm v0.6.6 h1:F2s40f2SE2Yt/e97ZA6jMVaqDW+/1PLewcqOiUWDFd0= -github.com/ava-labs/subnet-evm v0.6.6/go.mod h1:xq048mtkxCkNPl7Yb0wSkWzXX70aYkPnYQUvfCaBW3U= +github.com/ava-labs/subnet-evm v0.6.9-0.20240815191823-9f3608326298 h1:g7JdXUEZ5Hk3sKhJPoZattE4obRaF7qapHREnB4O+Rg= +github.com/ava-labs/subnet-evm v0.6.9-0.20240815191823-9f3608326298/go.mod h1:QfIzh7YxKj97jbendOHQbaAxM7SMj5MWdV13o1VLn70= github.com/ava-labs/teleporter v1.0.0 h1:io209qZh3SDpwLre0oStMzOFRcPvOrmMQuLq5OFvzJo= github.com/ava-labs/teleporter v1.0.0/go.mod h1:4Wyz/5sZDHMaaLegh2ULyrAOWnyaBk6upTmbwSrVSMs= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= @@ -267,8 +267,8 @@ github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwu github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v26.1.3+incompatible h1:lLCzRbrVZrljpVNobJu1J2FHk8V0s4BawoZippkc+xo= -github.com/docker/docker v26.1.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= +github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA= github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= github.com/dop251/goja v0.0.0-20230806174421-c933cf95e127 h1:qwcF+vdFrvPSEUDSX5RVoRccG8a5DhOdWdQ4zN62zzo= @@ -1283,8 +1283,6 @@ gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= -gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/migrations/subnetEVMRename.go b/internal/migrations/subnet_evm_rename.go similarity index 100% rename from internal/migrations/subnetEVMRename.go rename to internal/migrations/subnet_evm_rename.go diff --git a/internal/migrations/subnetEVMRename_test.go b/internal/migrations/subnet_evm_rename_test.go similarity index 100% rename from internal/migrations/subnetEVMRename_test.go rename to internal/migrations/subnet_evm_rename_test.go diff --git a/internal/mocks/binaryChecker.go b/internal/mocks/binary_checker.go similarity index 100% rename from internal/mocks/binaryChecker.go rename to internal/mocks/binary_checker.go diff --git a/internal/mocks/info.go b/internal/mocks/info.go index 759eecd25..da4f831cb 100644 --- a/internal/mocks/info.go +++ b/internal/mocks/info.go @@ -15,6 +15,8 @@ import ( rpc "github.com/ava-labs/avalanchego/utils/rpc" signer "github.com/ava-labs/avalanchego/vms/platformvm/signer" + + upgrade "github.com/ava-labs/avalanchego/upgrade" ) // InfoClient is an autogenerated mock type for the Client type @@ -393,6 +395,43 @@ func (_m *InfoClient) Peers(_a0 context.Context, _a1 ...rpc.Option) ([]info.Peer return r0, r1 } +// Upgrades provides a mock function with given fields: _a0, _a1 +func (_m *InfoClient) Upgrades(_a0 context.Context, _a1 ...rpc.Option) (*upgrade.Config, error) { + _va := make([]interface{}, len(_a1)) + for _i := range _a1 { + _va[_i] = _a1[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Upgrades") + } + + var r0 *upgrade.Config + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ...rpc.Option) (*upgrade.Config, error)); ok { + return rf(_a0, _a1...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...rpc.Option) *upgrade.Config); ok { + r0 = rf(_a0, _a1...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*upgrade.Config) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ...rpc.Option) error); ok { + r1 = rf(_a0, _a1...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // Uptime provides a mock function with given fields: _a0, _a1, _a2 func (_m *InfoClient) Uptime(_a0 context.Context, _a1 ids.ID, _a2 ...rpc.Option) (*info.UptimeResponse, error) { _va := make([]interface{}, len(_a2)) diff --git a/internal/mocks/pclient.go b/internal/mocks/pclient.go index 9506e5d48..281484347 100644 --- a/internal/mocks/pclient.go +++ b/internal/mocks/pclient.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.42.1. DO NOT EDIT. +// Code generated by mockery v2.43.2. DO NOT EDIT. package mocks @@ -7,6 +7,8 @@ import ( api "github.com/ava-labs/avalanchego/api" + fee "github.com/ava-labs/avalanchego/vms/components/fee" + ids "github.com/ava-labs/avalanchego/ids" mock "github.com/stretchr/testify/mock" @@ -29,43 +31,6 @@ type PClient struct { mock.Mock } -// AwaitTxDecided provides a mock function with given fields: ctx, txID, freq, options -func (_m *PClient) AwaitTxDecided(ctx context.Context, txID ids.ID, freq time.Duration, options ...rpc.Option) (*platformvm.GetTxStatusResponse, error) { - _va := make([]interface{}, len(options)) - for _i := range options { - _va[_i] = options[_i] - } - var _ca []interface{} - _ca = append(_ca, ctx, txID, freq) - _ca = append(_ca, _va...) - ret := _m.Called(_ca...) - - if len(ret) == 0 { - panic("no return value specified for AwaitTxDecided") - } - - var r0 *platformvm.GetTxStatusResponse - var r1 error - if rf, ok := ret.Get(0).(func(context.Context, ids.ID, time.Duration, ...rpc.Option) (*platformvm.GetTxStatusResponse, error)); ok { - return rf(ctx, txID, freq, options...) - } - if rf, ok := ret.Get(0).(func(context.Context, ids.ID, time.Duration, ...rpc.Option) *platformvm.GetTxStatusResponse); ok { - r0 = rf(ctx, txID, freq, options...) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*platformvm.GetTxStatusResponse) - } - } - - if rf, ok := ret.Get(1).(func(context.Context, ids.ID, time.Duration, ...rpc.Option) error); ok { - r1 = rf(ctx, txID, freq, options...) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - // ExportKey provides a mock function with given fields: ctx, user, address, options func (_m *PClient) ExportKey(ctx context.Context, user api.UserPass, address ids.ShortID, options ...rpc.Option) (*secp256k1.PrivateKey, error) { _va := make([]interface{}, len(options)) @@ -420,6 +385,92 @@ func (_m *PClient) GetCurrentValidators(ctx context.Context, subnetID ids.ID, no return r0, r1 } +// GetFeeConfig provides a mock function with given fields: ctx, options +func (_m *PClient) GetFeeConfig(ctx context.Context, options ...rpc.Option) (*fee.Config, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for GetFeeConfig") + } + + var r0 *fee.Config + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, ...rpc.Option) (*fee.Config, error)); ok { + return rf(ctx, options...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...rpc.Option) *fee.Config); ok { + r0 = rf(ctx, options...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*fee.Config) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, ...rpc.Option) error); ok { + r1 = rf(ctx, options...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetFeeState provides a mock function with given fields: ctx, options +func (_m *PClient) GetFeeState(ctx context.Context, options ...rpc.Option) (fee.State, fee.GasPrice, time.Time, error) { + _va := make([]interface{}, len(options)) + for _i := range options { + _va[_i] = options[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for GetFeeState") + } + + var r0 fee.State + var r1 fee.GasPrice + var r2 time.Time + var r3 error + if rf, ok := ret.Get(0).(func(context.Context, ...rpc.Option) (fee.State, fee.GasPrice, time.Time, error)); ok { + return rf(ctx, options...) + } + if rf, ok := ret.Get(0).(func(context.Context, ...rpc.Option) fee.State); ok { + r0 = rf(ctx, options...) + } else { + r0 = ret.Get(0).(fee.State) + } + + if rf, ok := ret.Get(1).(func(context.Context, ...rpc.Option) fee.GasPrice); ok { + r1 = rf(ctx, options...) + } else { + r1 = ret.Get(1).(fee.GasPrice) + } + + if rf, ok := ret.Get(2).(func(context.Context, ...rpc.Option) time.Time); ok { + r2 = rf(ctx, options...) + } else { + r2 = ret.Get(2).(time.Time) + } + + if rf, ok := ret.Get(3).(func(context.Context, ...rpc.Option) error); ok { + r3 = rf(ctx, options...) + } else { + r3 = ret.Error(3) + } + + return r0, r1, r2, r3 +} + // GetHeight provides a mock function with given fields: ctx, options func (_m *PClient) GetHeight(ctx context.Context, options ...rpc.Option) (uint64, error) { _va := make([]interface{}, len(options)) diff --git a/internal/mocks/pluginBinaryDownloader.go b/internal/mocks/plugin_binary_downloader.go similarity index 100% rename from internal/mocks/pluginBinaryDownloader.go rename to internal/mocks/plugin_binary_downloader.go diff --git a/internal/mocks/processChecker.go b/internal/mocks/process_checker.go similarity index 100% rename from internal/mocks/processChecker.go rename to internal/mocks/process_checker.go diff --git a/internal/testutils/dummyPackages.go b/internal/testutils/dummy_packages.go similarity index 100% rename from internal/testutils/dummyPackages.go rename to internal/testutils/dummy_packages.go diff --git a/pkg/application/app.go b/pkg/application/app.go index c728e0524..391484f24 100644 --- a/pkg/application/app.go +++ b/pkg/application/app.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path/filepath" + "strings" "github.com/ava-labs/apm/apm" "github.com/ava-labs/avalanche-cli/pkg/config" @@ -115,16 +116,28 @@ func (app *Avalanche) GetAWMRelayerBinDir() string { return filepath.Join(app.baseDir, constants.AvalancheCliBinDir, constants.AWMRelayerInstallDir) } -func (app *Avalanche) GetAWMRelayerStorageDir() string { - return filepath.Join(app.GetRunDir(), constants.AWMRelayerStorageDir) +func (app *Avalanche) GetLocalRelayerDir(networkKind models.NetworkKind) string { + networkDirName := strings.ReplaceAll(networkKind.String(), " ", "") + return filepath.Join(app.GetRunDir(), networkDirName, constants.LocalRelayerDir) } -func (app *Avalanche) GetAWMRelayerLogPath() string { - return filepath.Join(app.GetRunDir(), constants.AWMRelayerLogFilename) +func (app *Avalanche) GetLocalRelayerStorageDir(networkKind models.NetworkKind) string { + return filepath.Join(app.GetLocalRelayerDir(networkKind), constants.AWMRelayerStorageDir) } -func (app *Avalanche) GetAWMRelayerRunPath() string { - return filepath.Join(app.GetRunDir(), constants.AWMRelayerRunFilename) +func (app *Avalanche) GetLocalRelayerConfigPath(networkKind models.NetworkKind, localNetworkRootDir string) string { + if localNetworkRootDir != "" { + return filepath.Join(localNetworkRootDir, constants.AWMRelayerConfigFilename) + } + return filepath.Join(app.GetLocalRelayerDir(networkKind), constants.AWMRelayerConfigFilename) +} + +func (app *Avalanche) GetLocalRelayerLogPath(networkKind models.NetworkKind) string { + return filepath.Join(app.GetLocalRelayerDir(networkKind), constants.AWMRelayerLogFilename) +} + +func (app *Avalanche) GetLocalRelayerRunPath(networkKind models.NetworkKind) string { + return filepath.Join(app.GetLocalRelayerDir(networkKind), constants.AWMRelayerRunFilename) } func (app *Avalanche) GetAWMRelayerServiceDir(baseDir string) string { @@ -146,36 +159,36 @@ func (app *Avalanche) GetSubnetEVMBinDir() string { return filepath.Join(app.baseDir, constants.AvalancheCliBinDir, constants.SubnetEVMInstallDir) } -func (app *Avalanche) GetUpgradeBytesFilepath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.UpgradeBytesFileName) +func (app *Avalanche) GetUpgradeBytesFilepath(blockchainName string) string { + return filepath.Join(app.GetSubnetDir(), blockchainName, constants.UpgradeBytesFileName) } -func (app *Avalanche) GetCustomVMPath(subnetName string) string { - return filepath.Join(app.GetCustomVMDir(), subnetName) +func (app *Avalanche) GetCustomVMPath(blockchainName string) string { + return filepath.Join(app.GetCustomVMDir(), blockchainName) } func (app *Avalanche) GetAPMVMPath(vmid string) string { return filepath.Join(app.GetAPMPluginDir(), vmid) } -func (app *Avalanche) GetGenesisPath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.GenesisFileName) +func (app *Avalanche) GetGenesisPath(blockchainName string) string { + return filepath.Join(app.GetSubnetDir(), blockchainName, constants.GenesisFileName) } -func (app *Avalanche) GetAvagoNodeConfigPath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.NodeConfigFileName) +func (app *Avalanche) GetAvagoNodeConfigPath(blockchainName string) string { + return filepath.Join(app.GetSubnetDir(), blockchainName, constants.NodeConfigFileName) } -func (app *Avalanche) GetChainConfigPath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.ChainConfigFileName) +func (app *Avalanche) GetChainConfigPath(blockchainName string) string { + return filepath.Join(app.GetSubnetDir(), blockchainName, constants.ChainConfigFileName) } -func (app *Avalanche) GetAvagoSubnetConfigPath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.SubnetConfigFileName) +func (app *Avalanche) GetAvagoSubnetConfigPath(blockchainName string) string { + return filepath.Join(app.GetSubnetDir(), blockchainName, constants.SubnetConfigFileName) } -func (app *Avalanche) GetSidecarPath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.SidecarFileName) +func (app *Avalanche) GetSidecarPath(blockchainName string) string { + return filepath.Join(app.GetSubnetDir(), blockchainName, constants.SidecarFileName) } func (app *Avalanche) GetNodeConfigPath(nodeName string) string { @@ -236,10 +249,6 @@ func (app *Avalanche) GetNodeBLSSecretKeyPath(instanceID string) string { return filepath.Join(app.GetNodeInstanceDirPath(instanceID), constants.BLSKeyFileName) } -func (app *Avalanche) GetElasticSubnetConfigPath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.ElasticSubnetConfigFileName) -} - func (app *Avalanche) GetKeyDir() string { return filepath.Join(app.baseDir, constants.KeyDir) } @@ -276,8 +285,8 @@ func (app *Avalanche) GetKey(keyName string, network models.Network, createIfMis } } -func (app *Avalanche) GetUpgradeBytesFilePath(subnetName string) string { - return filepath.Join(app.GetSubnetDir(), subnetName, constants.UpgradeBytesFileName) +func (app *Avalanche) GetUpgradeBytesFilePath(blockchainName string) string { + return filepath.Join(app.GetSubnetDir(), blockchainName, constants.UpgradeBytesFileName) } func (app *Avalanche) GetDownloader() Downloader { @@ -288,82 +297,82 @@ func (*Avalanche) GetAvalanchegoCompatibilityURL() string { return constants.AvalancheGoCompatibilityURL } -func (app *Avalanche) ReadUpgradeFile(subnetName string) ([]byte, error) { - upgradeBytesFilePath := app.GetUpgradeBytesFilePath(subnetName) +func (app *Avalanche) ReadUpgradeFile(blockchainName string) ([]byte, error) { + upgradeBytesFilePath := app.GetUpgradeBytesFilePath(blockchainName) return app.readFile(upgradeBytesFilePath) } -func (app *Avalanche) ReadLockUpgradeFile(subnetName string) ([]byte, error) { - upgradeBytesLockFilePath := app.GetUpgradeBytesFilePath(subnetName) + constants.UpgradeBytesLockExtension +func (app *Avalanche) ReadLockUpgradeFile(blockchainName string) ([]byte, error) { + upgradeBytesLockFilePath := app.GetUpgradeBytesFilePath(blockchainName) + constants.UpgradeBytesLockExtension return app.readFile(upgradeBytesLockFilePath) } -func (app *Avalanche) WriteUpgradeFile(subnetName string, bytes []byte) error { - upgradeBytesFilePath := app.GetUpgradeBytesFilePath(subnetName) +func (app *Avalanche) WriteUpgradeFile(blockchainName string, bytes []byte) error { + upgradeBytesFilePath := app.GetUpgradeBytesFilePath(blockchainName) return app.writeFile(upgradeBytesFilePath, bytes) } -func (app *Avalanche) WriteLockUpgradeFile(subnetName string, bytes []byte) error { - upgradeBytesLockFilePath := app.GetUpgradeBytesFilePath(subnetName) + constants.UpgradeBytesLockExtension +func (app *Avalanche) WriteLockUpgradeFile(blockchainName string, bytes []byte) error { + upgradeBytesLockFilePath := app.GetUpgradeBytesFilePath(blockchainName) + constants.UpgradeBytesLockExtension return app.writeFile(upgradeBytesLockFilePath, bytes) } -func (app *Avalanche) WriteGenesisFile(subnetName string, genesisBytes []byte) error { - genesisPath := app.GetGenesisPath(subnetName) +func (app *Avalanche) WriteGenesisFile(blockchainName string, genesisBytes []byte) error { + genesisPath := app.GetGenesisPath(blockchainName) return app.writeFile(genesisPath, genesisBytes) } -func (app *Avalanche) WriteAvagoNodeConfigFile(subnetName string, bs []byte) error { - path := app.GetAvagoNodeConfigPath(subnetName) +func (app *Avalanche) WriteAvagoNodeConfigFile(blockchainName string, bs []byte) error { + path := app.GetAvagoNodeConfigPath(blockchainName) return app.writeFile(path, bs) } -func (app *Avalanche) WriteChainConfigFile(subnetName string, bs []byte) error { - path := app.GetChainConfigPath(subnetName) +func (app *Avalanche) WriteChainConfigFile(blockchainName string, bs []byte) error { + path := app.GetChainConfigPath(blockchainName) return app.writeFile(path, bs) } -func (app *Avalanche) WriteAvagoSubnetConfigFile(subnetName string, bs []byte) error { - path := app.GetAvagoSubnetConfigPath(subnetName) +func (app *Avalanche) WriteAvagoSubnetConfigFile(blockchainName string, bs []byte) error { + path := app.GetAvagoSubnetConfigPath(blockchainName) return app.writeFile(path, bs) } -func (app *Avalanche) WriteNetworkUpgradesFile(subnetName string, bs []byte) error { - path := app.GetUpgradeBytesFilepath(subnetName) +func (app *Avalanche) WriteNetworkUpgradesFile(blockchainName string, bs []byte) error { + path := app.GetUpgradeBytesFilepath(blockchainName) return app.writeFile(path, bs) } -func (app *Avalanche) GenesisExists(subnetName string) bool { - genesisPath := app.GetGenesisPath(subnetName) +func (app *Avalanche) GenesisExists(blockchainName string) bool { + genesisPath := app.GetGenesisPath(blockchainName) _, err := os.Stat(genesisPath) return err == nil } -func (app *Avalanche) AvagoNodeConfigExists(subnetName string) bool { - path := app.GetAvagoNodeConfigPath(subnetName) +func (app *Avalanche) AvagoNodeConfigExists(blockchainName string) bool { + path := app.GetAvagoNodeConfigPath(blockchainName) _, err := os.Stat(path) return err == nil } -func (app *Avalanche) ChainConfigExists(subnetName string) bool { - path := app.GetChainConfigPath(subnetName) +func (app *Avalanche) ChainConfigExists(blockchainName string) bool { + path := app.GetChainConfigPath(blockchainName) _, err := os.Stat(path) return err == nil } -func (app *Avalanche) AvagoSubnetConfigExists(subnetName string) bool { - path := app.GetAvagoSubnetConfigPath(subnetName) +func (app *Avalanche) AvagoSubnetConfigExists(blockchainName string) bool { + path := app.GetAvagoSubnetConfigPath(blockchainName) _, err := os.Stat(path) return err == nil } -func (app *Avalanche) NetworkUpgradeExists(subnetName string) bool { - path := app.GetUpgradeBytesFilepath(subnetName) +func (app *Avalanche) NetworkUpgradeExists(blockchainName string) bool { + path := app.GetUpgradeBytesFilepath(blockchainName) _, err := os.Stat(path) return err == nil } @@ -373,15 +382,15 @@ func (app *Avalanche) ClustersConfigExists() bool { return err == nil } -func (app *Avalanche) SidecarExists(subnetName string) bool { - sidecarPath := app.GetSidecarPath(subnetName) +func (app *Avalanche) SidecarExists(blockchainName string) bool { + sidecarPath := app.GetSidecarPath(blockchainName) _, err := os.Stat(sidecarPath) return err == nil } -func (app *Avalanche) SubnetConfigExists(subnetName string) bool { - // There's always a sidecar, but imported subnets don't have a genesis right now - return app.SidecarExists(subnetName) +func (app *Avalanche) BlockchainConfigExists(blockchainName string) bool { + // There's always a sidecar, but imported blockchains don't have a genesis right now + return app.SidecarExists(blockchainName) } func (app *Avalanche) KeyExists(keyName string) bool { @@ -390,12 +399,12 @@ func (app *Avalanche) KeyExists(keyName string) bool { return err == nil } -func (app *Avalanche) CopyGenesisFile(inputFilename string, subnetName string) error { +func (app *Avalanche) CopyGenesisFile(inputFilename string, blockchainName string) error { genesisBytes, err := os.ReadFile(inputFilename) if err != nil { return err } - genesisPath := app.GetGenesisPath(subnetName) + genesisPath := app.GetGenesisPath(blockchainName) if err := os.MkdirAll(filepath.Dir(genesisPath), constants.DefaultPerms755); err != nil { return err } @@ -403,12 +412,12 @@ func (app *Avalanche) CopyGenesisFile(inputFilename string, subnetName string) e return os.WriteFile(genesisPath, genesisBytes, constants.WriteReadReadPerms) } -func (app *Avalanche) CopyVMBinary(inputFilename string, subnetName string) error { +func (app *Avalanche) CopyVMBinary(inputFilename string, blockchainName string) error { vmBytes, err := os.ReadFile(inputFilename) if err != nil { return err } - vmPath := app.GetCustomVMPath(subnetName) + vmPath := app.GetCustomVMPath(blockchainName) return os.WriteFile(vmPath, vmBytes, constants.DefaultPerms755) } @@ -421,20 +430,20 @@ func (app *Avalanche) CopyKeyFile(inputFilename string, keyName string) error { return os.WriteFile(keyPath, keyBytes, constants.WriteReadReadPerms) } -func (app *Avalanche) HasSubnetEVMGenesis(subnetName string) (bool, error, error) { - if _, err := app.LoadRawGenesis(subnetName); err != nil { +func (app *Avalanche) HasSubnetEVMGenesis(blockchainName string) (bool, error, error) { + if _, err := app.LoadRawGenesis(blockchainName); err != nil { return false, nil, err } // from here, we are sure to have a genesis file - _, err := app.LoadEvmGenesis(subnetName) + _, err := app.LoadEvmGenesis(blockchainName) if err != nil { return false, err, nil } return true, nil, nil } -func (app *Avalanche) LoadEvmGenesis(subnetName string) (core.Genesis, error) { - genesisPath := app.GetGenesisPath(subnetName) +func (app *Avalanche) LoadEvmGenesis(blockchainName string) (core.Genesis, error) { + genesisPath := app.GetGenesisPath(blockchainName) bs, err := os.ReadFile(genesisPath) if err != nil { return core.Genesis{}, err @@ -442,25 +451,25 @@ func (app *Avalanche) LoadEvmGenesis(subnetName string) (core.Genesis, error) { return utils.ByteSliceToSubnetEvmGenesis(bs) } -func (app *Avalanche) LoadRawGenesis(subnetName string) ([]byte, error) { - genesisPath := app.GetGenesisPath(subnetName) +func (app *Avalanche) LoadRawGenesis(blockchainName string) ([]byte, error) { + genesisPath := app.GetGenesisPath(blockchainName) return os.ReadFile(genesisPath) } -func (app *Avalanche) LoadRawAvagoNodeConfig(subnetName string) ([]byte, error) { - return os.ReadFile(app.GetAvagoNodeConfigPath(subnetName)) +func (app *Avalanche) LoadRawAvagoNodeConfig(blockchainName string) ([]byte, error) { + return os.ReadFile(app.GetAvagoNodeConfigPath(blockchainName)) } -func (app *Avalanche) LoadRawChainConfig(subnetName string) ([]byte, error) { - return os.ReadFile(app.GetChainConfigPath(subnetName)) +func (app *Avalanche) LoadRawChainConfig(blockchainName string) ([]byte, error) { + return os.ReadFile(app.GetChainConfigPath(blockchainName)) } -func (app *Avalanche) LoadRawAvagoSubnetConfig(subnetName string) ([]byte, error) { - return os.ReadFile(app.GetAvagoSubnetConfigPath(subnetName)) +func (app *Avalanche) LoadRawAvagoSubnetConfig(blockchainName string) ([]byte, error) { + return os.ReadFile(app.GetAvagoSubnetConfigPath(blockchainName)) } -func (app *Avalanche) LoadRawNetworkUpgrades(subnetName string) ([]byte, error) { - return os.ReadFile(app.GetUpgradeBytesFilepath(subnetName)) +func (app *Avalanche) LoadRawNetworkUpgrades(blockchainName string) ([]byte, error) { + return os.ReadFile(app.GetUpgradeBytesFilepath(blockchainName)) } func (app *Avalanche) CreateSidecar(sc *models.Sidecar) error { @@ -484,12 +493,12 @@ func (app *Avalanche) CreateSidecar(sc *models.Sidecar) error { return os.WriteFile(sidecarPath, scBytes, constants.WriteReadReadPerms) } -func (app *Avalanche) LoadSidecar(subnetName string) (models.Sidecar, error) { - if !app.SidecarExists(subnetName) { - return models.Sidecar{}, fmt.Errorf("subnet %q does not exist", subnetName) +func (app *Avalanche) LoadSidecar(blockchainName string) (models.Sidecar, error) { + if !app.SidecarExists(blockchainName) { + return models.Sidecar{}, fmt.Errorf("subnet %q does not exist", blockchainName) } - sidecarPath := app.GetSidecarPath(subnetName) + sidecarPath := app.GetSidecarPath(blockchainName) jsonBytes, err := os.ReadFile(sidecarPath) if err != nil { return models.Sidecar{}, err @@ -521,7 +530,6 @@ func (app *Avalanche) UpdateSidecarNetworks( sc *models.Sidecar, network models.Network, subnetID ids.ID, - transferSubnetOwnershipTxID ids.ID, blockchainID ids.ID, teleporterMessengerAddress string, teleporterRegistryAddress string, @@ -530,12 +538,11 @@ func (app *Avalanche) UpdateSidecarNetworks( sc.Networks = make(map[string]models.NetworkData) } sc.Networks[network.Name()] = models.NetworkData{ - SubnetID: subnetID, - TransferSubnetOwnershipTxID: transferSubnetOwnershipTxID, - BlockchainID: blockchainID, - RPCVersion: sc.RPCVersion, - TeleporterMessengerAddress: teleporterMessengerAddress, - TeleporterRegistryAddress: teleporterRegistryAddress, + SubnetID: subnetID, + BlockchainID: blockchainID, + RPCVersion: sc.RPCVersion, + TeleporterMessengerAddress: teleporterMessengerAddress, + TeleporterRegistryAddress: teleporterRegistryAddress, } if err := app.UpdateSidecar(sc); err != nil { return fmt.Errorf("creation of chains and subnet was successful, but failed to update sidecar: %w", err) @@ -543,88 +550,23 @@ func (app *Avalanche) UpdateSidecarNetworks( return nil } -func (app *Avalanche) UpdateSidecarElasticSubnet( - sc *models.Sidecar, - network models.Network, - subnetID ids.ID, - assetID ids.ID, - pchainTXID ids.ID, - tokenName string, - tokenSymbol string, -) error { - if sc.ElasticSubnet == nil { - sc.ElasticSubnet = make(map[string]models.ElasticSubnet) - } - partialTxs := sc.ElasticSubnet[network.Name()].Txs - sc.ElasticSubnet[network.Name()] = models.ElasticSubnet{ - SubnetID: subnetID, - AssetID: assetID, - PChainTXID: pchainTXID, - TokenName: tokenName, - TokenSymbol: tokenSymbol, - Txs: partialTxs, - } - if err := app.UpdateSidecar(sc); err != nil { - return err - } - return nil -} - -func (app *Avalanche) UpdateSidecarPermissionlessValidator( - sc *models.Sidecar, - network models.Network, - nodeID string, - txID ids.ID, -) error { - elasticSubnet := sc.ElasticSubnet[network.Name()] - if elasticSubnet.Validators == nil { - elasticSubnet.Validators = make(map[string]models.PermissionlessValidators) - } - elasticSubnet.Validators[nodeID] = models.PermissionlessValidators{TxID: txID} - sc.ElasticSubnet[network.Name()] = elasticSubnet - if err := app.UpdateSidecar(sc); err != nil { - return err - } - return nil -} - -func (app *Avalanche) UpdateSidecarElasticSubnetPartialTx( - sc *models.Sidecar, - network models.Network, - txName string, - txID ids.ID, -) error { - if sc.ElasticSubnet == nil { - sc.ElasticSubnet = make(map[string]models.ElasticSubnet) - } - partialTxs := make(map[string]ids.ID) - if sc.ElasticSubnet[network.Name()].Txs != nil { - partialTxs = sc.ElasticSubnet[network.Name()].Txs - } - partialTxs[txName] = txID - sc.ElasticSubnet[network.Name()] = models.ElasticSubnet{ - Txs: partialTxs, - } - return app.UpdateSidecar(sc) -} - -func (app *Avalanche) GetTokenName(subnetName string) string { - sidecar, err := app.LoadSidecar(subnetName) +func (app *Avalanche) GetTokenName(blockchainName string) string { + sidecar, err := app.LoadSidecar(blockchainName) if err != nil { return constants.DefaultTokenName } return sidecar.TokenName } -func (app *Avalanche) GetTokenSymbol(subnetName string) string { - sidecar, err := app.LoadSidecar(subnetName) +func (app *Avalanche) GetTokenSymbol(blockchainName string) string { + sidecar, err := app.LoadSidecar(blockchainName) if err != nil { return constants.DefaultTokenSymbol } return sidecar.TokenSymbol } -func (app *Avalanche) GetSubnetNames() ([]string, error) { +func (app *Avalanche) GetBlockchainNames() ([]string, error) { matches, err := os.ReadDir(app.GetSubnetDir()) if err != nil { return nil, err @@ -643,19 +585,19 @@ func (app *Avalanche) GetSubnetNames() ([]string, error) { return names, nil } -func (app *Avalanche) GetSubnetNamesOnNetwork(network models.Network) ([]string, error) { - subnetNames, err := app.GetSubnetNames() +func (app *Avalanche) GetBlockchainNamesOnNetwork(network models.Network) ([]string, error) { + blockchainNames, err := app.GetBlockchainNames() if err != nil { return nil, err } filtered := []string{} - for _, subnetName := range subnetNames { - sc, err := app.LoadSidecar(subnetName) + for _, blockchainName := range blockchainNames { + sc, err := app.LoadSidecar(blockchainName) if err != nil { return nil, err } if sc.Networks[network.Name()].BlockchainID != ids.Empty { - filtered = append(filtered, subnetName) + filtered = append(filtered, blockchainName) } } return filtered, nil @@ -691,33 +633,6 @@ func (app *Avalanche) CreateNodeCloudConfigFile(nodeName string, nodeConfig *mod return os.WriteFile(nodeConfigPath, esBytes, constants.WriteReadReadPerms) } -func (app *Avalanche) CreateElasticSubnetConfig(subnetName string, es *models.ElasticSubnetConfig) error { - elasticSubetConfigPath := app.GetElasticSubnetConfigPath(subnetName) - if err := os.MkdirAll(filepath.Dir(elasticSubetConfigPath), constants.DefaultPerms755); err != nil { - return err - } - - esBytes, err := json.MarshalIndent(es, "", " ") - if err != nil { - return err - } - - return os.WriteFile(elasticSubetConfigPath, esBytes, constants.WriteReadReadPerms) -} - -func (app *Avalanche) LoadElasticSubnetConfig(subnetName string) (models.ElasticSubnetConfig, error) { - elasticSubnetConfigPath := app.GetElasticSubnetConfigPath(subnetName) - jsonBytes, err := os.ReadFile(elasticSubnetConfigPath) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - - var esc models.ElasticSubnetConfig - err = json.Unmarshal(jsonBytes, &esc) - - return esc, err -} - func (app *Avalanche) LoadClusterNodeConfig(nodeName string) (models.NodeConfig, error) { nodeConfigPath := app.GetNodeConfigPath(nodeName) jsonBytes, err := os.ReadFile(nodeConfigPath) diff --git a/pkg/binutils/subnetEvm.go b/pkg/binutils/subnet_evm.go similarity index 100% rename from pkg/binutils/subnetEvm.go rename to pkg/binutils/subnet_evm.go diff --git a/pkg/cloud/aws/aws.go b/pkg/cloud/aws/aws.go index d8a44338c..694ca0d89 100644 --- a/pkg/cloud/aws/aws.go +++ b/pkg/cloud/aws/aws.go @@ -351,6 +351,9 @@ func (c *AwsCloud) DestroyAWSNode(nodeConfig models.NodeConfig, clusterName stri isRunning, err := c.checkInstanceIsRunning(nodeConfig.NodeID) if err != nil { ux.Logger.PrintToUser(fmt.Sprintf("Failed to destroy node %s due to %s", nodeConfig.NodeID, err.Error())) + if errors.Is(err, ErrNoInstanceState) { + return nil + } return err } if !isRunning { @@ -563,7 +566,7 @@ func (c *AwsCloud) GetUbuntuAMIID(arch string, ubuntuVerLTS string) (string, err {Name: aws.String("description"), Values: []string{descriptionFilterValue}}, {Name: aws.String("architecture"), Values: []string{arch}}, }, - Owners: []string{"self", "931867039610"}, + Owners: []string{"self", "221210582303"}, } images, err := c.ec2Client.DescribeImages(c.ctx, imageInput) if err != nil { diff --git a/pkg/cloud/gcp/gcp.go b/pkg/cloud/gcp/gcp.go index 8be0247bb..c29934a8c 100644 --- a/pkg/cloud/gcp/gcp.go +++ b/pkg/cloud/gcp/gcp.go @@ -321,9 +321,6 @@ func (c *GcpCloud) SetupInstances( return instances, nil } -// // Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// // See the file LICENSE for licensing terms. - func (c *GcpCloud) GetUbuntuImageID() (string, error) { imageListCall := c.gcpClient.Images.List(constants.GCPDefaultImageProvider).Filter(constants.GCPImageFilter) imageList, err := imageListCall.Do() diff --git a/pkg/cobrautils/cobrautils.go b/pkg/cobrautils/cobra_utils.go similarity index 100% rename from pkg/cobrautils/cobrautils.go rename to pkg/cobrautils/cobra_utils.go diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 03ed39911..df572b316 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -6,6 +6,8 @@ import ( "time" ) +type HTTPAccess bool + const ( DefaultPerms755 = 0o755 WriteReadReadPerms = 0o644 @@ -24,7 +26,7 @@ const ( SuffixSeparator = "_" SidecarFileName = "sidecar.json" GenesisFileName = "genesis.json" - ElasticSubnetConfigFileName = "elastic_subnet_config.json" + AliasesFileName = "aliases.json" SidecarSuffix = SuffixSeparator + SidecarFileName GenesisSuffix = SuffixSeparator + GenesisFileName NodeFileName = "node.json" @@ -70,8 +72,10 @@ const ( OperateOfflineEnvVarName = "CLIOFFLINE" - FujiAPIEndpoint = "https://api.avax-test.network" - MainnetAPIEndpoint = "https://api.avax.network" + PublicAccess HTTPAccess = true + PrivateAccess HTTPAccess = false + FujiAPIEndpoint = "https://api.avax-test.network" + MainnetAPIEndpoint = "https://api.avax.network" // this depends on bootstrap snapshot LocalAPIEndpoint = "http://127.0.0.1:9650" @@ -93,6 +97,7 @@ const ( DefaultSnapshotName = "default-1654102509" Cortina17Version = "v1.10.17" + Durango11Version = "v1.11.11" BootstrapSnapshotRawBranch = "https://github.com/ava-labs/avalanche-cli/raw/main/" @@ -120,6 +125,16 @@ const ( BootstrapSnapshotSingleNodePreCortina17URL = BootstrapSnapshotRawBranch + BootstrapSnapshotSingleNodePreCortina17LocalPath BootstrapSnapshotSingleNodePreCortina17SHA256URL = BootstrapSnapshotRawBranch + AssetsDir + "sha256sumSingleNode.PreCortina17.txt" + BootstrapSnapshotPreDurango11ArchiveName = "bootstrapSnapshot.PreDurango11.tar.gz" + BootstrapSnapshotPreDurango11LocalPath = AssetsDir + BootstrapSnapshotPreDurango11ArchiveName + BootstrapSnapshotPreDurango11URL = BootstrapSnapshotRawBranch + BootstrapSnapshotPreDurango11LocalPath + BootstrapSnapshotPreDurango11SHA256URL = BootstrapSnapshotRawBranch + AssetsDir + "sha256sum.PreDurango11.txt" + + BootstrapSnapshotSingleNodePreDurango11ArchiveName = "bootstrapSnapshotSingleNode.PreDurango11.tar.gz" + BootstrapSnapshotSingleNodePreDurango11LocalPath = AssetsDir + BootstrapSnapshotSingleNodePreDurango11ArchiveName + BootstrapSnapshotSingleNodePreDurango11URL = BootstrapSnapshotRawBranch + BootstrapSnapshotSingleNodePreDurango11LocalPath + BootstrapSnapshotSingleNodePreDurango11SHA256URL = BootstrapSnapshotRawBranch + AssetsDir + "sha256sumSingleNode.PreDurango11.txt" + ExtraLocalNetworkDataFilename = "extra-local-network-data.json" CliInstallationURL = "https://raw.githubusercontent.com/ava-labs/avalanche-cli/main/scripts/install.sh" @@ -234,6 +249,7 @@ const ( AWMRelayerInstallDir = "awm-relayer" TeleporterInstallDir = "teleporter" AWMRelayerBin = "awm-relayer" + LocalRelayerDir = "local-relayer" AWMRelayerConfigFilename = "awm-relayer-config.json" AWMRelayerStorageDir = "awm-relayer-storage" AWMRelayerLogFilename = "awm-relayer.log" @@ -242,10 +258,16 @@ const ( AWMRelayerSnapshotConfsDir = "relayer-confs" - TeleporterKeyName = "cli-teleporter-deployer" + ICMKeyName = "cli-teleporter-deployer" AWMRelayerKeyName = "cli-awm-relayer" - AWMRelayerMetricsPort = 9091 + // to not interfere with other node services + RemoteAWMRelayerMetricsPort = 9091 + + // enables having many local relayers + LocalNetworkLocalAWMRelayerMetricsPort = 9091 + DevnetLocalAWMRelayerMetricsPort = 9092 + FujiLocalAWMRelayerMetricsPort = 9093 SubnetEVMBin = "subnet-evm" @@ -285,7 +307,6 @@ const ( MetricsNodeDevnetWizCommand = "avalanche node devnet wiz" MetricsSubnetDeployCommand = "avalanche subnet deploy" MetricsSubnetCreateCommand = "avalanche subnet create" - MetricsSubnetElasticCommand = "avalanche subnet elastic" SubnetType = "subnet type" PrecompileType = "precompile type" CustomAirdrop = "custom-airdrop" @@ -350,7 +371,13 @@ const ( RemoteDockeSocketPath = "/var/run/docker.sock" // Avalanche InterChain Token Transfer - ICTTDir = "avalanche-interchain-token-transfer" - ICTTURL = "https://github.com/ava-labs/avalanche-interchain-token-transfer" - ICTTBranch = "main" + ICTTDir = "avalanche-interchain-token-transfer" + ICTTURL = "https://github.com/ava-labs/avalanche-interchain-token-transfer" + ICTTBranch = "main" + ICTTVersion = "v1.0.0" + + // ICM + DefaultTeleporterMessengerAddress = "0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf" + MainnetCChainTeleporterRegistryAddress = "0x7C43605E14F391720e1b37E49C78C4b03A488d98" + FujiCChainTeleporterRegistryAddress = "0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228" ) diff --git a/pkg/contract/allocations.go b/pkg/contract/allocations.go index 749c1d009..453950b80 100644 --- a/pkg/contract/allocations.go +++ b/pkg/contract/allocations.go @@ -10,19 +10,18 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/key" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/utils" - "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/subnet-evm/precompile/contracts/nativeminter" "github.com/ethereum/go-ethereum/common" ) -// returns information for the subnet default allocation key +// returns information for the blockchain default allocation key // if found, returns // key name, address, private key -func GetDefaultSubnetAirdropKeyInfo( +func GetDefaultBlockchainAirdropKeyInfo( app *application.Avalanche, - subnetName string, + blockchainName string, ) (string, string, string, error) { - keyName := utils.GetDefaultSubnetAirdropKeyName(subnetName) + keyName := utils.GetDefaultBlockchainAirdropKeyName(blockchainName) keyPath := app.GetKeyPath(keyName) if utils.FileExists(keyPath) { k, err := key.LoadSoft(models.NewLocalNetwork().ID, keyPath) @@ -37,28 +36,28 @@ func GetDefaultSubnetAirdropKeyInfo( // from a given genesis, look for known private keys inside it, giving // preference to the ones expected to be default // it searches for: -// 1) default CLI allocation key for subnets +// 1) default CLI allocation key for blockchains // 2) ewoq // 3) all other stored keys managed by CLI // returns address + private key when found -func GetSubnetAirdropKeyInfo( +func GetBlockchainAirdropKeyInfo( app *application.Avalanche, network models.Network, - subnetName string, + blockchainName string, genesisData []byte, ) (string, string, string, error) { genesis, err := utils.ByteSliceToSubnetEvmGenesis(genesisData) if err != nil { return "", "", "", err } - if subnetName != "" { - subnetAirdropKeyName, subnetAirdropAddress, subnetAirdropPrivKey, err := GetDefaultSubnetAirdropKeyInfo(app, subnetName) + if blockchainName != "" { + airdropKeyName, airdropAddress, airdropPrivKey, err := GetDefaultBlockchainAirdropKeyInfo(app, blockchainName) if err != nil { return "", "", "", err } for address := range genesis.Alloc { - if address.Hex() == subnetAirdropAddress { - return subnetAirdropKeyName, subnetAirdropAddress, subnetAirdropPrivKey, nil + if address.Hex() == airdropAddress { + return airdropKeyName, airdropAddress, airdropPrivKey, nil } } } @@ -103,22 +102,18 @@ func searchForManagedKey( return false, "", "", "", nil } -// get the deployed subnet genesis, and then look for known +// get the deployed blockchain genesis, and then look for known // private keys inside it // returns address + private key when found func GetEVMSubnetPrefundedKey( app *application.Avalanche, network models.Network, - subnetName string, - isCChain bool, - blockchainID string, + chainSpec ChainSpec, ) (string, string, error) { genesisData, err := GetBlockchainGenesis( app, network, - subnetName, - isCChain, - blockchainID, + chainSpec, ) if err != nil { return "", "", err @@ -126,10 +121,10 @@ func GetEVMSubnetPrefundedKey( if !utils.ByteSliceIsSubnetEvmGenesis(genesisData) { return "", "", fmt.Errorf("search for prefunded key is only supported on EVM based vms") } - _, genesisAddress, genesisPrivateKey, err := GetSubnetAirdropKeyInfo( + _, genesisAddress, genesisPrivateKey, err := GetBlockchainAirdropKeyInfo( app, network, - subnetName, + chainSpec.BlockchainName, genesisData, ) if err != nil { @@ -142,45 +137,13 @@ func GetEVMSubnetPrefundedKey( func GetBlockchainGenesis( app *application.Avalanche, network models.Network, - subnetName string, - isCChain bool, - blockchainID string, + chainSpec ChainSpec, ) ([]byte, error) { - if blockchainID == "" { - if isCChain { - blockchainID = "C" - } else { - sc, err := app.LoadSidecar(subnetName) - if err != nil { - return nil, fmt.Errorf("failed to load sidecar: %w", err) - } - if b, _, err := app.HasSubnetEVMGenesis(subnetName); err != nil { - return nil, err - } else if !b { - return nil, fmt.Errorf("search for prefunded key is only supported on EVM based vms") - } - if sc.Networks[network.Name()].BlockchainID == ids.Empty { - return nil, fmt.Errorf("subnet has not been deployed to %s", network.Name()) - } - blockchainID = sc.Networks[network.Name()].BlockchainID.String() - } - } - var ( - err error - chainID ids.ID - ) - if isCChain || !network.StandardPublicEndpoint() { - chainID, err = utils.GetChainID(network.Endpoint, blockchainID) - if err != nil { - return nil, err - } - } else { - chainID, err = ids.FromString(blockchainID) - if err != nil { - return nil, err - } + blockchainID, err := GetBlockchainID(app, network, chainSpec) + if err != nil { + return nil, err } - createChainTx, err := utils.GetBlockchainTx(network.Endpoint, chainID) + createChainTx, err := utils.GetBlockchainTx(network.Endpoint, blockchainID) if err != nil { return nil, err } @@ -204,16 +167,12 @@ func sumGenesisSupply( func GetEVMSubnetGenesisSupply( app *application.Avalanche, network models.Network, - subnetName string, - isCChain bool, - blockchainID string, + chainSpec ChainSpec, ) (*big.Int, error) { genesisData, err := GetBlockchainGenesis( app, network, - subnetName, - isCChain, - blockchainID, + chainSpec, ) if err != nil { return nil, err @@ -258,19 +217,49 @@ func getGenesisNativeMinterAdmin( return false, false, "", "", "", nil } +func getGenesisNativeMinterManager( + app *application.Avalanche, + network models.Network, + genesisData []byte, +) (bool, bool, string, string, string, error) { + genesis, err := utils.ByteSliceToSubnetEvmGenesis(genesisData) + if err != nil { + return false, false, "", "", "", err + } + if genesis.Config != nil && genesis.Config.GenesisPrecompiles[nativeminter.ConfigKey] != nil { + allowListCfg, ok := genesis.Config.GenesisPrecompiles[nativeminter.ConfigKey].(*nativeminter.Config) + if !ok { + return false, false, "", "", "", fmt.Errorf( + "expected config of type nativeminter.AllowListConfig, but got %T", + allowListCfg, + ) + } + if len(allowListCfg.AllowListConfig.ManagerAddresses) == 0 { + return false, false, "", "", "", nil + } + for _, admin := range allowListCfg.AllowListConfig.ManagerAddresses { + found, keyName, addressStr, privKey, err := searchForManagedKey(app, network, admin, true) + if err != nil { + return false, false, "", "", "", err + } + if found { + return true, true, keyName, addressStr, privKey, nil + } + } + return true, false, "", allowListCfg.AllowListConfig.ManagerAddresses[0].Hex(), "", nil + } + return false, false, "", "", "", nil +} + func GetEVMSubnetGenesisNativeMinterAdmin( app *application.Avalanche, network models.Network, - subnetName string, - isCChain bool, - blockchainID string, + chainSpec ChainSpec, ) (bool, bool, string, string, string, error) { genesisData, err := GetBlockchainGenesis( app, network, - subnetName, - isCChain, - blockchainID, + chainSpec, ) if err != nil { return false, false, "", "", "", err @@ -280,3 +269,22 @@ func GetEVMSubnetGenesisNativeMinterAdmin( } return getGenesisNativeMinterAdmin(app, network, genesisData) } + +func GetEVMSubnetGenesisNativeMinterManager( + app *application.Avalanche, + network models.Network, + chainSpec ChainSpec, +) (bool, bool, string, string, string, error) { + genesisData, err := GetBlockchainGenesis( + app, + network, + chainSpec, + ) + if err != nil { + return false, false, "", "", "", err + } + if !utils.ByteSliceIsSubnetEvmGenesis(genesisData) { + return false, false, "", "", "", fmt.Errorf("genesis native minter manager query is only supported on EVM based vms") + } + return getGenesisNativeMinterManager(app, network, genesisData) +} diff --git a/pkg/contract/chain.go b/pkg/contract/chain.go index eb1b74462..3b423d3dc 100644 --- a/pkg/contract/chain.go +++ b/pkg/contract/chain.go @@ -5,50 +5,360 @@ package contract import ( "fmt" + cmdflags "github.com/ava-labs/avalanche-cli/cmd/flags" "github.com/ava-labs/avalanche-cli/pkg/application" + "github.com/ava-labs/avalanche-cli/pkg/constants" + "github.com/ava-labs/avalanche-cli/pkg/localnet" "github.com/ava-labs/avalanche-cli/pkg/models" + "github.com/ava-labs/avalanche-cli/pkg/prompts" + "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanchego/ids" "github.com/spf13/cobra" ) -type ChainFlags struct { - SubnetName string - CChain bool +type ChainSpec struct { + blockchainFlagName string + cChainFlagName string + blockchainIDFlagName string + BlockchainName string + CChain bool + BlockchainID string } -func AddChainFlagsToCmd( +const ( + defaultBlockchainFlagName = "blockchain" + defaultCChainFlagName = "c-chain" + defaultBlockchainIDFlagName = "blockchain-id" +) + +func (cs *ChainSpec) CheckMutuallyExclusiveFields() error { + if !cmdflags.EnsureMutuallyExclusive([]bool{ + cs.BlockchainName != "", + cs.BlockchainID != "", + cs.CChain, + }) { + return fmt.Errorf("%s, %s and %s are mutually exclusive flags", + cs.blockchainFlagName, + cs.cChainFlagName, + cs.blockchainIDFlagName, + ) + } + return nil +} + +func (cs *ChainSpec) Defined() bool { + return cs.BlockchainName != "" || cs.BlockchainID != "" || cs.CChain +} + +func (cs *ChainSpec) fillDefaultFlagNames() { + if cs.blockchainFlagName == "" { + cs.blockchainFlagName = defaultBlockchainFlagName + } + if cs.cChainFlagName == "" { + cs.cChainFlagName = defaultCChainFlagName + } + if cs.blockchainIDFlagName == "" { + cs.blockchainIDFlagName = defaultBlockchainIDFlagName + } +} + +func (cs *ChainSpec) SetFlagNames( + blockchainFlagName string, + cChainFlagName string, + blockchainIDFlagName string, +) { + cs.blockchainFlagName = blockchainFlagName + cs.cChainFlagName = cChainFlagName + cs.blockchainIDFlagName = blockchainIDFlagName +} + +func (cs *ChainSpec) AddToCmd( cmd *cobra.Command, - chainFlags *ChainFlags, goal string, - subnetFlagName string, - cChainFlagName string, + addBlockchainIDFlag bool, ) { - if subnetFlagName == "" { - subnetFlagName = "subnet" + cs.fillDefaultFlagNames() + if cs.blockchainFlagName == defaultBlockchainFlagName { + cmd.Flags().StringVar(&cs.BlockchainName, "subnet", "", fmt.Sprintf("%s into the given CLI blockchain", goal)) } - if cChainFlagName == "" { - cChainFlagName = "c-chain" + cmd.Flags().StringVar(&cs.BlockchainName, cs.blockchainFlagName, "", fmt.Sprintf("%s into the given CLI blockchain", goal)) + cmd.Flags().BoolVar(&cs.CChain, cs.cChainFlagName, false, fmt.Sprintf("%s into C-Chain", goal)) + if addBlockchainIDFlag { + cmd.Flags().StringVar(&cs.BlockchainID, cs.blockchainIDFlagName, "", fmt.Sprintf("%s into the given blockchain ID/Alias", goal)) } - cmd.Flags().StringVar(&chainFlags.SubnetName, subnetFlagName, "", fmt.Sprintf("%s into the given CLI subnet", goal)) - cmd.Flags().BoolVar(&chainFlags.CChain, cChainFlagName, false, fmt.Sprintf("%s into C-Chain", goal)) } -func GetRPCURL( +func GetBlockchainEndpoints( app *application.Avalanche, network models.Network, - subnetName string, - isCChain bool, + chainSpec ChainSpec, + promptForRPCEndpoint bool, + promptForWSEndpoint bool, +) (string, string, error) { + var ( + rpcEndpoint string + wsEndpoint string + ) + switch { + case chainSpec.CChain: + rpcEndpoint = network.CChainEndpoint() + wsEndpoint = network.CChainWSEndpoint() + case network.Kind == models.Local || network.Kind == models.Devnet: + blockchainID, err := GetBlockchainID(app, network, chainSpec) + if err != nil { + return "", "", err + } + rpcEndpoint = network.BlockchainEndpoint(blockchainID.String()) + wsEndpoint = network.BlockchainWSEndpoint(blockchainID.String()) + case chainSpec.BlockchainName != "": + sc, err := app.LoadSidecar(chainSpec.BlockchainName) + if err != nil { + return "", "", fmt.Errorf("failed to load sidecar: %w", err) + } + if sc.Networks[network.Name()].BlockchainID == ids.Empty { + return "", "", fmt.Errorf("blockchain has not been deployed to %s", network.Name()) + } + if len(sc.Networks[network.Name()].RPCEndpoints) > 0 { + rpcEndpoint = sc.Networks[network.Name()].RPCEndpoints[0] + } + if len(sc.Networks[network.Name()].WSEndpoints) > 0 { + wsEndpoint = sc.Networks[network.Name()].WSEndpoints[0] + } + } + blockchainDesc, err := GetBlockchainDesc(chainSpec) + if err != nil { + return "", "", err + } + if rpcEndpoint == "" && promptForRPCEndpoint { + rpcEndpoint, err = app.Prompt.CaptureURL("Which is the RPC endpoint for "+blockchainDesc, false) + if err != nil { + return "", "", err + } + } + if wsEndpoint == "" && promptForWSEndpoint { + wsEndpoint, err = app.Prompt.CaptureURL("Which is the WS endpoint for "+blockchainDesc, false) + if err != nil { + return "", "", err + } + } + return rpcEndpoint, wsEndpoint, nil +} + +func GetBlockchainID( + app *application.Avalanche, + network models.Network, + chainSpec ChainSpec, +) (ids.ID, error) { + var blockchainID ids.ID + switch { + case chainSpec.BlockchainID != "": + var err error + blockchainID, err = ids.FromString(chainSpec.BlockchainID) + if err != nil { + // it should be an alias at this point + blockchainID, err = utils.GetChainID(network.Endpoint, chainSpec.BlockchainID) + if err != nil { + return ids.Empty, err + } + } + case chainSpec.CChain: + chainID, err := utils.GetChainID(network.Endpoint, "C") + if err != nil { + return ids.Empty, err + } + blockchainID = chainID + case chainSpec.BlockchainName != "": + sc, err := app.LoadSidecar(chainSpec.BlockchainName) + if err != nil { + return ids.Empty, fmt.Errorf("failed to load sidecar: %w", err) + } + if sc.Networks[network.Name()].BlockchainID == ids.Empty { + return ids.Empty, fmt.Errorf("blockchain has not been deployed to %s", network.Name()) + } + blockchainID = sc.Networks[network.Name()].BlockchainID + default: + return ids.Empty, fmt.Errorf("blockchain is not defined") + } + return blockchainID, nil +} + +func GetSubnetID( + app *application.Avalanche, + network models.Network, + chainSpec ChainSpec, +) (ids.ID, error) { + var subnetID ids.ID + switch { + case chainSpec.CChain: + subnetID = ids.Empty + case chainSpec.BlockchainName != "": + sc, err := app.LoadSidecar(chainSpec.BlockchainName) + if err != nil { + return ids.Empty, fmt.Errorf("failed to load sidecar: %w", err) + } + if sc.Networks[network.Name()].BlockchainID == ids.Empty { + return ids.Empty, fmt.Errorf("blockchain has not been deployed to %s", network.Name()) + } + subnetID = sc.Networks[network.Name()].SubnetID + case chainSpec.BlockchainID != "": + blockchainID, err := ids.FromString(chainSpec.BlockchainID) + if err != nil { + return ids.Empty, fmt.Errorf("failure parsing %s as id: %w", chainSpec.BlockchainID, err) + } + tx, err := utils.GetBlockchainTx(network.Endpoint, blockchainID) + if err != nil { + return ids.Empty, err + } + subnetID = tx.SubnetID + default: + return ids.Empty, fmt.Errorf("blockchain is not defined") + } + return subnetID, nil +} + +func GetBlockchainDesc( + chainSpec ChainSpec, ) (string, error) { - if isCChain { - return network.CChainEndpoint(), nil - } else { - sc, err := app.LoadSidecar(subnetName) + blockchainDesc := "" + switch { + case chainSpec.BlockchainName != "": + blockchainDesc = chainSpec.BlockchainName + case chainSpec.CChain: + blockchainDesc = "C-Chain" + case chainSpec.BlockchainID != "": + blockchainDesc = chainSpec.BlockchainID + default: + return "", fmt.Errorf("blockchain is not defined") + } + return blockchainDesc, nil +} + +func GetICMInfo( + app *application.Avalanche, + network models.Network, + chainSpec ChainSpec, + promptForRegistry bool, + promptForMessenger bool, + defaultToLatestReleasedMessenger bool, +) (string, string, error) { + registryAddress := "" + messengerAddress := "" + switch { + case chainSpec.CChain: + var err error + registryAddress, messengerAddress, err = GetCChainICMInfo(app, network) + if err != nil { + return "", "", err + } + case chainSpec.BlockchainID != "": + case chainSpec.BlockchainName != "": + sc, err := app.LoadSidecar(chainSpec.BlockchainName) if err != nil { - return "", fmt.Errorf("failed to load sidecar: %w", err) + return "", "", fmt.Errorf("failed to load sidecar: %w", err) } if sc.Networks[network.Name()].BlockchainID == ids.Empty { - return "", fmt.Errorf("subnet has not been deployed to %s", network.Name()) + return "", "", fmt.Errorf("blockchain has not been deployed to %s", network.Name()) + } + registryAddress = sc.Networks[network.Name()].TeleporterRegistryAddress + messengerAddress = sc.Networks[network.Name()].TeleporterMessengerAddress + default: + return "", "", fmt.Errorf("blockchain is not defined") + } + blockchainDesc, err := GetBlockchainDesc(chainSpec) + if err != nil { + return "", "", err + } + if registryAddress == "" && promptForRegistry { + addr, err := app.Prompt.CaptureAddress("Which is the ICM Registry address for " + blockchainDesc) + if err != nil { + return "", "", err + } + registryAddress = addr.Hex() + } + if messengerAddress == "" { + if promptForMessenger { + addr, err := app.Prompt.CaptureAddress("Which is the ICM Messenger address for " + blockchainDesc) + if err != nil { + return "", "", err + } + messengerAddress = addr.Hex() + } else if defaultToLatestReleasedMessenger { + messengerAddress = constants.DefaultTeleporterMessengerAddress + } + } + return registryAddress, messengerAddress, nil +} + +func PromptChain( + app *application.Avalanche, + network models.Network, + prompt string, + avoidCChain bool, + avoidBlockchain string, + includeCustom bool, + chainSpec *ChainSpec, +) (bool, error) { + blockchainNames, err := app.GetBlockchainNamesOnNetwork(network) + if err != nil { + return false, err + } + cancel, _, _, cChain, blockchainName, blockchainID, err := prompts.PromptChain( + app.Prompt, + prompt, + blockchainNames, + true, + true, + avoidCChain, + avoidBlockchain, + includeCustom, + ) + if err != nil || cancel { + return cancel, err + } + if blockchainID != "" { + // map from alias to blockchain ID (or identity) + chainID, err := utils.GetChainID(network.Endpoint, blockchainID) + if err != nil { + return cancel, err + } + blockchainID = chainID.String() + } + chainSpec.BlockchainName = blockchainName + chainSpec.CChain = cChain + chainSpec.BlockchainID = blockchainID + return false, nil +} + +func GetCChainICMInfo( + app *application.Avalanche, + network models.Network, +) (string, string, error) { + messengerAddress := "" + registryAddress := "" + switch { + case network.Kind == models.Local: + b, extraLocalNetworkData, err := localnet.GetExtraLocalNetworkData() + if err != nil { + return "", "", err + } + if !b { + return "", "", fmt.Errorf("no extra local network data available") + } + messengerAddress = extraLocalNetworkData.CChainTeleporterMessengerAddress + registryAddress = extraLocalNetworkData.CChainTeleporterRegistryAddress + case network.ClusterName != "": + clusterConfig, err := app.GetClusterConfig(network.ClusterName) + if err != nil { + return "", "", err } - return network.BlockchainEndpoint(sc.Networks[network.Name()].BlockchainID.String()), nil + messengerAddress = clusterConfig.ExtraNetworkData.CChainTeleporterMessengerAddress + registryAddress = clusterConfig.ExtraNetworkData.CChainTeleporterRegistryAddress + case network.Kind == models.Fuji: + messengerAddress = constants.DefaultTeleporterMessengerAddress + registryAddress = constants.FujiCChainTeleporterRegistryAddress + case network.Kind == models.Mainnet: + messengerAddress = constants.DefaultTeleporterMessengerAddress + registryAddress = constants.MainnetCChainTeleporterRegistryAddress } + return registryAddress, messengerAddress, nil } diff --git a/pkg/contract/contract.go b/pkg/contract/contract.go index 77f07b4a5..eeb051b4e 100644 --- a/pkg/contract/contract.go +++ b/pkg/contract/contract.go @@ -201,7 +201,7 @@ func getMap( return r, nil } -func ParseEsp( +func ParseSpec( esp string, indexedFields []int, constructor bool, @@ -288,10 +288,10 @@ func TxToMethod( privateKey string, contractAddress common.Address, payment *big.Int, - methodEsp string, + methodSpec string, params ...interface{}, ) (*types.Transaction, *types.Receipt, error) { - methodName, methodABI, err := ParseEsp(methodEsp, nil, false, false, payment != nil, false, params...) + methodName, methodABI, err := ParseSpec(methodSpec, nil, false, false, payment != nil, false, params...) if err != nil { return nil, nil, err } @@ -329,10 +329,10 @@ func TxToMethod( func CallToMethod( rpcURL string, contractAddress common.Address, - methodEsp string, + methodSpec string, params ...interface{}, ) ([]interface{}, error) { - methodName, methodABI, err := ParseEsp(methodEsp, nil, false, false, false, true, params...) + methodName, methodABI, err := ParseSpec(methodSpec, nil, false, false, false, true, params...) if err != nil { return nil, err } @@ -361,10 +361,10 @@ func DeployContract( rpcURL string, privateKey string, binBytes []byte, - methodEsp string, + methodSpec string, params ...interface{}, ) (common.Address, error) { - _, methodABI, err := ParseEsp(methodEsp, nil, true, false, false, false, params...) + _, methodABI, err := ParseSpec(methodSpec, nil, true, false, false, false, params...) if err != nil { return common.Address{}, err } @@ -399,12 +399,12 @@ func DeployContract( } func UnpackLog( - eventEsp string, + eventSpec string, indexedFields []int, log types.Log, event interface{}, ) error { - eventName, eventABI, err := ParseEsp(eventEsp, indexedFields, false, true, false, false, event) + eventName, eventABI, err := ParseSpec(eventSpec, indexedFields, false, true, false, false, event) if err != nil { return err } diff --git a/pkg/contract/privateKey.go b/pkg/contract/privateKey.go deleted file mode 100644 index 145eb41ec..000000000 --- a/pkg/contract/privateKey.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package contract - -import ( - "fmt" - - cmdflags "github.com/ava-labs/avalanche-cli/cmd/flags" - "github.com/ava-labs/avalanche-cli/pkg/application" - "github.com/ava-labs/avalanche-cli/pkg/models" - - "github.com/spf13/cobra" -) - -type PrivateKeyFlags struct { - PrivateKey string - KeyName string - GenesisKey bool -} - -func AddPrivateKeyFlagsToCmd( - cmd *cobra.Command, - privateKeyFlags *PrivateKeyFlags, - goal string, -) { - cmd.Flags().StringVar( - &privateKeyFlags.PrivateKey, - "private-key", - "", - fmt.Sprintf("private key to use %s", goal), - ) - cmd.Flags().StringVar( - &privateKeyFlags.KeyName, - "key", - "", - fmt.Sprintf("CLI stored key to use %s", goal), - ) - cmd.Flags().BoolVar( - &privateKeyFlags.GenesisKey, - "genesis-key", - false, - fmt.Sprintf("use genesis allocated key %s", goal), - ) -} - -func GetPrivateKeyFromFlags( - app *application.Avalanche, - flags PrivateKeyFlags, - genesisPrivateKey string, -) (string, error) { - if !cmdflags.EnsureMutuallyExclusive([]bool{ - flags.PrivateKey != "", - flags.KeyName != "", - flags.GenesisKey, - }) { - return "", fmt.Errorf("--private-key, --key and --genesis-key are mutually exclusive flags") - } - privateKey := flags.PrivateKey - if flags.KeyName != "" { - k, err := app.GetKey(flags.KeyName, models.NewLocalNetwork(), false) - if err != nil { - return "", err - } - privateKey = k.PrivKeyHex() - } - if flags.GenesisKey { - privateKey = genesisPrivateKey - } - return privateKey, nil -} diff --git a/pkg/contract/private_key.go b/pkg/contract/private_key.go new file mode 100644 index 000000000..1209c0104 --- /dev/null +++ b/pkg/contract/private_key.go @@ -0,0 +1,105 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package contract + +import ( + "fmt" + + cmdflags "github.com/ava-labs/avalanche-cli/cmd/flags" + "github.com/ava-labs/avalanche-cli/pkg/application" + "github.com/ava-labs/avalanche-cli/pkg/models" + + "github.com/spf13/cobra" +) + +type PrivateKeyFlags struct { + privateKeyFlagName string + keyFlagName string + genesisKeyFlagName string + PrivateKey string + KeyName string + GenesisKey bool +} + +const ( + defaultPrivateKeyFlagName = "private-key" + defaultKeyFlagName = "key" + defaultGenesisKeyFlagName = "genesis-key" +) + +func (pkf *PrivateKeyFlags) fillDefaultFlagNames() { + if pkf.privateKeyFlagName == "" { + pkf.privateKeyFlagName = defaultPrivateKeyFlagName + } + if pkf.keyFlagName == "" { + pkf.keyFlagName = defaultKeyFlagName + } + if pkf.genesisKeyFlagName == "" { + pkf.genesisKeyFlagName = defaultGenesisKeyFlagName + } +} + +func (pkf *PrivateKeyFlags) SetFlagNames( + privateKeyFlagName string, + keyFlagName string, + genesisKeyFlagName string, +) { + pkf.privateKeyFlagName = privateKeyFlagName + pkf.keyFlagName = keyFlagName + pkf.genesisKeyFlagName = genesisKeyFlagName +} + +func (pkf *PrivateKeyFlags) AddToCmd( + cmd *cobra.Command, + goal string, +) { + pkf.fillDefaultFlagNames() + cmd.Flags().StringVar( + &pkf.PrivateKey, + pkf.privateKeyFlagName, + "", + fmt.Sprintf("private key to use %s", goal), + ) + cmd.Flags().StringVar( + &pkf.KeyName, + pkf.keyFlagName, + "", + fmt.Sprintf("CLI stored key to use %s", goal), + ) + cmd.Flags().BoolVar( + &pkf.GenesisKey, + pkf.genesisKeyFlagName, + false, + fmt.Sprintf("use genesis allocated key %s", goal), + ) +} + +func (pkf *PrivateKeyFlags) GetPrivateKey( + app *application.Avalanche, + genesisPrivateKey string, +) (string, error) { + pkf.fillDefaultFlagNames() + if !cmdflags.EnsureMutuallyExclusive([]bool{ + pkf.PrivateKey != "", + pkf.KeyName != "", + pkf.GenesisKey, + }) { + return "", fmt.Errorf("%s, %s and %s are mutually exclusive flags", + pkf.privateKeyFlagName, + pkf.keyFlagName, + pkf.genesisKeyFlagName, + ) + } + privateKey := pkf.PrivateKey + if pkf.KeyName != "" { + k, err := app.GetKey(pkf.KeyName, models.NewLocalNetwork(), false) + if err != nil { + return "", err + } + privateKey = k.PrivKeyHex() + } + if pkf.GenesisKey { + privateKey = genesisPrivateKey + } + return privateKey, nil +} diff --git a/pkg/docker/compose.go b/pkg/docker/compose.go index 0d1789eee..9136e9e53 100644 --- a/pkg/docker/compose.go +++ b/pkg/docker/compose.go @@ -7,10 +7,10 @@ import ( "bytes" "embed" "fmt" - "html/template" "os" "path/filepath" "strings" + "text/template" "time" "github.com/ava-labs/avalanche-cli/pkg/constants" diff --git a/pkg/docker/config.go b/pkg/docker/config.go index 125e43b2f..0a9d501f0 100644 --- a/pkg/docker/config.go +++ b/pkg/docker/config.go @@ -9,10 +9,14 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/remoteconfig" + "github.com/ava-labs/avalanche-cli/pkg/utils" ) -func prepareAvalanchegoConfig(host *models.Host, networkID string) (string, string, error) { - avagoConf := remoteconfig.PrepareAvalancheConfig(host.IP, networkID, nil) +func prepareAvalanchegoConfig(host *models.Host, network models.Network, publicAccess bool) (string, string, error) { + avagoConf := remoteconfig.PrepareAvalancheConfig(host.IP, network.NetworkIDFlagValue(), nil) + if publicAccess || utils.IsE2E() { + avagoConf.HTTPHost = "0.0.0.0" + } nodeConf, err := remoteconfig.RenderAvalancheNodeConfig(avagoConf) if err != nil { return "", "", err diff --git a/pkg/docker/ssh.go b/pkg/docker/ssh.go index 11c82c756..ef012b226 100644 --- a/pkg/docker/ssh.go +++ b/pkg/docker/ssh.go @@ -25,7 +25,7 @@ func ValidateComposeFile(host *models.Host, composeFile string, timeout time.Dur } // ComposeSSHSetupNode sets up an AvalancheGo node and dependencies on a remote host over SSH. -func ComposeSSHSetupNode(host *models.Host, network models.Network, avalancheGoVersion string, withMonitoring bool) error { +func ComposeSSHSetupNode(host *models.Host, network models.Network, avalancheGoVersion string, withMonitoring bool, publicAccessToHTTPPort bool) error { startTime := time.Now() folderStructure := remoteconfig.RemoteFoldersToCreateAvalanchego() for _, dir := range folderStructure { @@ -34,11 +34,6 @@ func ComposeSSHSetupNode(host *models.Host, network models.Network, avalancheGoV } } ux.Logger.Info("avalancheCLI folder structure created on remote host %s after %s", folderStructure, time.Since(startTime)) - // configs - networkID := network.NetworkIDFlagValue() - if network.Kind == models.Local || network.Kind == models.Devnet { - networkID = fmt.Sprintf("%d", network.ID) - } avagoDockerImage := fmt.Sprintf("%s:%s", constants.AvalancheGoDockerImage, avalancheGoVersion) ux.Logger.Info("Preparing AvalancheGo Docker image %s on %s[%s]", avagoDockerImage, host.NodeID, host.IP) @@ -46,7 +41,7 @@ func ComposeSSHSetupNode(host *models.Host, network models.Network, avalancheGoV return err } ux.Logger.Info("AvalancheGo Docker image %s ready on %s[%s] after %s", avagoDockerImage, host.NodeID, host.IP, time.Since(startTime)) - nodeConfFile, cChainConfFile, err := prepareAvalanchegoConfig(host, networkID) + nodeConfFile, cChainConfFile, err := prepareAvalanchegoConfig(host, network, publicAccessToHTTPPort) if err != nil { return err } diff --git a/pkg/docker/templates/avalanchego.docker-compose.yml b/pkg/docker/templates/avalanchego.docker-compose.yml index a6ac10666..d64a4b70d 100644 --- a/pkg/docker/templates/avalanchego.docker-compose.yml +++ b/pkg/docker/templates/avalanchego.docker-compose.yml @@ -27,10 +27,10 @@ services: ports: - "9650:9650" - "9651:9651" - networks: - - avalanchego_net + network_mode: "host" {{ end }} {{ end }} + {{if .WithMonitoring}} promtail: image: grafana/promtail:3.0.0 @@ -48,8 +48,6 @@ services: volumes: - /home/ubuntu/.avalanchego/logs:/logs:ro - /home/ubuntu/.avalanche-cli/services/promtail:/etc/promtail:ro - networks: - - avalanchego_net {{ end }} node-exporter: image: prom/node-exporter:v1.7.0 @@ -68,11 +66,8 @@ services: {{if .E2E }} networks: - avalanchego_net_{{.E2ESuffix}} -{{ else }} - networks: - - avalanchego_net -{{ end }} {{ end }} +{{end}} {{if .E2E }} volumes: @@ -80,8 +75,5 @@ volumes: avalanchego_logs_{{.E2ESuffix}}: networks: avalanchego_net_{{.E2ESuffix}}: -{{ else }} -networks: - avalanchego_net: {{ end }} \ No newline at end of file diff --git a/pkg/docker/templates/awmrelayer.docker-compose.yml b/pkg/docker/templates/awmrelayer.docker-compose.yml index bbf48c0e1..3861c4ac7 100644 --- a/pkg/docker/templates/awmrelayer.docker-compose.yml +++ b/pkg/docker/templates/awmrelayer.docker-compose.yml @@ -5,10 +5,7 @@ services: container_name: awm-relayer restart: unless-stopped user: "1000:1000" # ubuntu user - networks: - - avalanchego_net + network_mode: "host" volumes: - /home/ubuntu/.avalanche-cli/services/awm-relayer:/.awm-relayer:rw command: 'awm-relayer --config-file /.awm-relayer/awm-relayer-config.json' -networks: - avalanchego_net: diff --git a/pkg/docker/templates/monitoring.docker-compose.yml b/pkg/docker/templates/monitoring.docker-compose.yml index 6cd25deed..0c101cf23 100644 --- a/pkg/docker/templates/monitoring.docker-compose.yml +++ b/pkg/docker/templates/monitoring.docker-compose.yml @@ -15,8 +15,6 @@ services: - '--storage.tsdb.path=/var/lib/prometheus' links: - node-exporter - networks: - - monitoring_net grafana: image: grafana/grafana:10.4.1 @@ -31,8 +29,6 @@ services: links: - prometheus - loki - networks: - - monitoring_net loki: image: grafana/loki:3.0.0 @@ -45,8 +41,6 @@ services: volumes: - /home/ubuntu/.avalanche-cli/services/loki:/etc/loki:ro - /home/ubuntu/.avalanche-cli/services/loki/data:/var/lib/loki:rw - networks: - - monitoring_net node-exporter: image: prom/node-exporter:v1.7.0 @@ -54,12 +48,7 @@ services: restart: unless-stopped ports: - "9100:9100" - networks: - - monitoring_net volumes: - /proc:/host/proc:ro - /sys:/host/sys:ro - /:/rootfs:ro - -networks: - monitoring_net: diff --git a/pkg/elasticsubnet/config_prompt.go b/pkg/elasticsubnet/config_prompt.go deleted file mode 100644 index 2484879fb..000000000 --- a/pkg/elasticsubnet/config_prompt.go +++ /dev/null @@ -1,384 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package elasticsubnet - -import ( - "fmt" - "math" - "time" - - "github.com/ava-labs/avalanche-cli/pkg/application" - "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/prompts" - "github.com/ava-labs/avalanche-cli/pkg/ux" - "github.com/ava-labs/avalanchego/vms/platformvm/reward" -) - -// default elastic config parameter values are from -// https://docs.avax.network/subnets/reference-elastic-subnets-parameters#primary-network-parameters-on-mainnet -const ( - defaultInitialSupply = 240_000_000 - defaultMaximumSupply = 720_000_000 - defaultMinConsumptionRate = 0.1 - defaultMaxConsumptionRate = 0.12 - defaultMinValidatorStake = 2_000 - defaultMaxValidatorStake = 3_000_000 - defaultMinStakeDurationHours = 14 * 24 - defaultMinStakeDurationHoursString = "14 x 24" - defaultMinStakeDuration = defaultMinStakeDurationHours * time.Hour - defaultMaxStakeDurationHours = 365 * 24 - defaultMaxStakeDurationHoursString = "365 x 24" - defaultMaxStakeDuration = defaultMaxStakeDurationHours * time.Hour - defaultMinDelegationFee = 20_000 - defaultMinDelegatorStake = 25 - defaultMaxValidatorWeightFactor = 5 - defaultUptimeRequirement = 0.8 -) - -func GetElasticSubnetConfig(app *application.Avalanche, tokenSymbol string, useDefaultConfig bool) (models.ElasticSubnetConfig, error) { - const ( - defaultConfig = "Use default elastic subnet config" - customizeConfig = "Customize elastic subnet config" - ) - elasticSubnetConfig := models.ElasticSubnetConfig{ - InitialSupply: defaultInitialSupply, - MaxSupply: defaultMaximumSupply, - MinConsumptionRate: defaultMinConsumptionRate * reward.PercentDenominator, - MaxConsumptionRate: defaultMaxConsumptionRate * reward.PercentDenominator, - MinValidatorStake: defaultMinValidatorStake, - MaxValidatorStake: defaultMaxValidatorStake, - MinStakeDuration: defaultMinStakeDuration, - MaxStakeDuration: defaultMaxStakeDuration, - MinDelegationFee: defaultMinDelegationFee, - MinDelegatorStake: defaultMinDelegatorStake, - MaxValidatorWeightFactor: defaultMaxValidatorWeightFactor, - UptimeRequirement: defaultUptimeRequirement * reward.PercentDenominator, - } - if useDefaultConfig { - return elasticSubnetConfig, nil - } - elasticSubnetConfigOptions := []string{defaultConfig, customizeConfig} - chosenConfig, err := app.Prompt.CaptureList( - "How would you like to set fees", - elasticSubnetConfigOptions, - ) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - - if chosenConfig == defaultConfig { - return elasticSubnetConfig, nil - } - customElasticSubnetConfig, err := getCustomElasticSubnetConfig(app, tokenSymbol) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - return customElasticSubnetConfig, nil -} - -func getCustomElasticSubnetConfig(app *application.Avalanche, tokenSymbol string) (models.ElasticSubnetConfig, error) { - ux.Logger.PrintToUser("More info regarding elastic subnet parameters can be found at https://docs.avax.network/subnets/reference-elastic-subnets-parameters") - initialSupply, err := getInitialSupply(app, tokenSymbol) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - maxSupply, err := getMaximumSupply(app, tokenSymbol, initialSupply) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - minConsumptionRate, maxConsumptionRate, err := getConsumptionRate(app) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - minValidatorStake, maxValidatorStake, err := getValidatorStake(app, initialSupply, maxSupply) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - minStakeDuration, maxStakeDuration, err := getStakeDuration(app) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - minDelegationFee, err := getMinDelegationFee(app) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - minDelegatorStake, err := getMinDelegatorStake(app) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - maxValidatorWeightFactor, err := getMaxValidatorWeightFactor(app) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - uptimeReq, err := getUptimeRequirement(app) - if err != nil { - return models.ElasticSubnetConfig{}, err - } - - elasticSubnetConfig := models.ElasticSubnetConfig{ - InitialSupply: initialSupply, - MaxSupply: maxSupply, - MinConsumptionRate: minConsumptionRate, - MaxConsumptionRate: maxConsumptionRate, - MinValidatorStake: minValidatorStake, - MaxValidatorStake: maxValidatorStake, - MinStakeDuration: minStakeDuration, - MaxStakeDuration: maxStakeDuration, - MinDelegationFee: minDelegationFee, - MinDelegatorStake: minDelegatorStake, - MaxValidatorWeightFactor: maxValidatorWeightFactor, - UptimeRequirement: uptimeReq, - } - return elasticSubnetConfig, err -} - -func getInitialSupply(app *application.Avalanche, tokenName string) (uint64, error) { - ux.Logger.PrintToUser(fmt.Sprintf("Select the Initial Supply of %s. \"_\" can be used as thousand separator", tokenName)) - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Initial Supply is %s", ux.ConvertToStringWithThousandSeparator(defaultInitialSupply))) - initialSupply, err := app.Prompt.CaptureUint64("Initial Supply amount") - if err != nil { - return 0, err - } - return initialSupply, nil -} - -func getMaximumSupply(app *application.Avalanche, tokenName string, initialSupply uint64) (uint64, error) { - ux.Logger.PrintToUser(fmt.Sprintf("Select the Maximum Supply of %s. \"_\" can be used as thousand separator", tokenName)) - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Maximum Supply is %s", ux.ConvertToStringWithThousandSeparator(defaultMaximumSupply))) - maxSupply, err := app.Prompt.CaptureUint64Compare( - "Maximum Supply amount", - []prompts.Comparator{ - { - Label: "Initial Supply", - Type: prompts.MoreThanEq, - Value: initialSupply, - }, - }, - ) - if err != nil { - return 0, err - } - return maxSupply, nil -} - -func getConsumptionRate(app *application.Avalanche) (uint64, uint64, error) { - ux.Logger.PrintToUser("Select the Minimum Consumption Rate. Please denominate your percentage in PercentDenominator") - ux.Logger.PrintToUser("To denominate your percentage in PercentDenominator just multiply it by 10_000. For example, 1 percent corresponds to 10_000") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Minimum Consumption Rate is %s", ux.ConvertToStringWithThousandSeparator(uint64(defaultMinConsumptionRate*reward.PercentDenominator)))) - minConsumptionRate, err := app.Prompt.CaptureUint64Compare( - "Minimum Consumption Rate", - []prompts.Comparator{ - { - Label: "Percent Denominator(1_0000_0000)", - Type: prompts.LessThanEq, - Value: reward.PercentDenominator, - }, - }, - ) - if err != nil { - return 0, 0, err - } - - ux.Logger.PrintToUser("Select the Maximum Consumption Rate. Please denominate your percentage in PercentDenominator") - ux.Logger.PrintToUser("To denominate your percentage in PercentDenominator just multiply it by 10_000. For example, 1 percent corresponds to 10_000") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Maximum Consumption Rate is %s", ux.ConvertToStringWithThousandSeparator(uint64(defaultMaxConsumptionRate*reward.PercentDenominator)))) - maxConsumptionRate, err := app.Prompt.CaptureUint64Compare( - "Maximum Consumption Rate", - []prompts.Comparator{ - { - Label: "Percent Denominator(1_0000_0000)", - Type: prompts.LessThanEq, - Value: reward.PercentDenominator, - }, - { - Label: "Minimum Consumption Rate", - Type: prompts.MoreThanEq, - Value: minConsumptionRate, - }, - }, - ) - if err != nil { - return 0, 0, err - } - return minConsumptionRate, maxConsumptionRate, nil -} - -func getValidatorStake(app *application.Avalanche, initialSupply uint64, maximumSupply uint64) (uint64, uint64, error) { - ux.Logger.PrintToUser("Select the Minimum Validator Stake. \"_\" can be used as thousand separator") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Minimum Validator Stake is %s", ux.ConvertToStringWithThousandSeparator(defaultMinValidatorStake))) - minValidatorStake, err := app.Prompt.CaptureUint64Compare( - "Minimum Validator Stake", - []prompts.Comparator{ - { - Label: "Initial Supply", - Type: prompts.LessThanEq, - Value: initialSupply, - }, - { - Label: "0", - Type: prompts.MoreThan, - Value: 0, - }, - }, - ) - if err != nil { - return 0, 0, err - } - - ux.Logger.PrintToUser("Select the Maximum Validator Stake. \"_\" can be used as thousand separator") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Maximum Validator Stake is %s", ux.ConvertToStringWithThousandSeparator(defaultMaxValidatorStake))) - maxValidatorStake, err := app.Prompt.CaptureUint64Compare( - "Maximum Validator Stake", - []prompts.Comparator{ - { - Label: "Maximum Supply", - Type: prompts.LessThanEq, - Value: maximumSupply, - }, - { - Label: "Minimum Validator Stake", - Type: prompts.MoreThan, - Value: minValidatorStake, - }, - }, - ) - if err != nil { - return 0, 0, err - } - return minValidatorStake, maxValidatorStake, nil -} - -func getStakeDuration(app *application.Avalanche) (time.Duration, time.Duration, error) { - ux.Logger.PrintToUser("Select the Minimum Stake Duration. Please enter in units of hours") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Minimum Stake Duration is %d (%s)", defaultMinStakeDurationHours, defaultMinStakeDurationHoursString)) - minStakeDuration, err := app.Prompt.CaptureUint64Compare( - "Minimum Stake Duration", - []prompts.Comparator{ - { - Label: "0", - Type: prompts.MoreThan, - Value: 0, - }, - { - Label: "Global Max Stake Duration", - Type: prompts.LessThanEq, - Value: uint64(defaultMaxStakeDurationHours), - }, - }, - ) - if err != nil { - return 0, 0, err - } - - ux.Logger.PrintToUser("Select the Maximum Stake Duration") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Maximum Stake Duration is %d (%s)", defaultMaxStakeDurationHours, defaultMaxStakeDurationHoursString)) - maxStakeDuration, err := app.Prompt.CaptureUint64Compare( - "Maximum Stake Duration", - []prompts.Comparator{ - { - Label: "Minimum Stake Duration", - Type: prompts.MoreThanEq, - Value: minStakeDuration, - }, - { - Label: "Global Max Stake Duration", - Type: prompts.LessThanEq, - Value: uint64(defaultMaxStakeDurationHours), - }, - }, - ) - if err != nil { - return 0, 0, err - } - - return time.Duration(minStakeDuration) * time.Hour, time.Duration(maxStakeDuration) * time.Hour, nil -} - -func getMinDelegationFee(app *application.Avalanche) (uint32, error) { - ux.Logger.PrintToUser("Select the Minimum Delegation Fee. Please denominate your percentage in PercentDenominator") - ux.Logger.PrintToUser("To denominate your percentage in PercentDenominator just multiply it by 10_000. For example, 1 percent corresponds to 10_000") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Minimum Delegation Fee is %s", ux.ConvertToStringWithThousandSeparator(uint64(defaultMinDelegationFee)))) - minDelegationFee, err := app.Prompt.CaptureUint64Compare( - "Minimum Delegation Fee", - []prompts.Comparator{ - { - Label: "Percent Denominator(1_0000_0000)", - Type: prompts.LessThanEq, - Value: reward.PercentDenominator, - }, - }, - ) - if err != nil { - return 0, err - } - if minDelegationFee > math.MaxInt32 { - return 0, fmt.Errorf("minimum Delegation Fee needs to be unsigned 32-bit integer") - } - return uint32(minDelegationFee), nil -} - -func getMinDelegatorStake(app *application.Avalanche) (uint64, error) { - ux.Logger.PrintToUser("Select the Minimum Delegator Stake") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Minimum Delegator Stake is %d", defaultMinDelegatorStake)) - minDelegatorStake, err := app.Prompt.CaptureUint64Compare( - "Minimum Delegator Stake", - []prompts.Comparator{ - { - Label: "0", - Type: prompts.MoreThan, - Value: 0, - }, - }, - ) - if err != nil { - return 0, err - } - return minDelegatorStake, nil -} - -func getMaxValidatorWeightFactor(app *application.Avalanche) (byte, error) { - ux.Logger.PrintToUser("Select the Maximum Validator Weight Factor. A value of 1 effectively disables delegation") - ux.Logger.PrintToUser("More info can be found at https://docs.avax.network/subnets/reference-elastic-subnets-parameters#delegators-weight-checks") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Maximum Validator Weight Factor is %d", defaultMaxValidatorWeightFactor)) - maxValidatorWeightFactor, err := app.Prompt.CaptureUint64Compare( - "Maximum Validator Weight Factor", - []prompts.Comparator{ - { - Label: "0", - Type: prompts.MoreThan, - Value: 0, - }, - }, - ) - if err != nil { - return 0, err - } - if maxValidatorWeightFactor > math.MaxInt8 { - return 0, fmt.Errorf("maximum Validator Weight Factor needs to be unsigned 8-bit integer") - } - return byte(maxValidatorWeightFactor), nil -} - -func getUptimeRequirement(app *application.Avalanche) (uint32, error) { - ux.Logger.PrintToUser("Select the Uptime Requirement. Please denominate your percentage in PercentDenominator") - ux.Logger.PrintToUser("To denominate your percentage in PercentDenominator just multiply it by 10_000. For example, 1 percent corresponds to 10_000") - ux.Logger.PrintToUser(fmt.Sprintf("Mainnet Uptime Requirement is %s", ux.ConvertToStringWithThousandSeparator(uint64(defaultUptimeRequirement*reward.PercentDenominator)))) - uptimeReq, err := app.Prompt.CaptureUint64Compare( - "Uptime Requirement", - []prompts.Comparator{ - { - Label: "Percent Denominator(1_0000_0000)", - Type: prompts.LessThanEq, - Value: reward.PercentDenominator, - }, - }, - ) - if err != nil { - return 0, err - } - if uptimeReq > math.MaxInt32 { - return 0, fmt.Errorf("uptime Requirement needs to be unsigned 32-bit integer") - } - return uint32(uptimeReq), nil -} diff --git a/pkg/elasticsubnet/elastic_status.go b/pkg/elasticsubnet/elastic_status.go deleted file mode 100644 index f139a398f..000000000 --- a/pkg/elasticsubnet/elastic_status.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package elasticsubnet - -import ( - "os" - - "github.com/ava-labs/avalanche-cli/pkg/application" - "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/ux" -) - -func GetLocalElasticSubnetsFromFile(app *application.Avalanche) ([]string, error) { - allSubnetDirs, err := os.ReadDir(app.GetSubnetDir()) - if err != nil { - return nil, err - } - - elasticSubnets := []string{} - - for _, subnetDir := range allSubnetDirs { - if !subnetDir.IsDir() { - continue - } - // read sidecar file - sc, err := app.LoadSidecar(subnetDir.Name()) - if err == os.ErrNotExist { - // don't fail on missing sidecar file, just warn - ux.Logger.PrintToUser("warning: inconsistent subnet directory. No sidecar file found for subnet %s", subnetDir.Name()) - continue - } - if err != nil { - return nil, err - } - - // check if sidecar contains local elastic subnets info in Elastic Subnets map - // if so, add to list of elastic subnets - if _, ok := sc.ElasticSubnet[models.Local.String()]; ok { - elasticSubnets = append(elasticSubnets, sc.Name) - } - } - - return elasticSubnets, nil -} diff --git a/pkg/ictt/deploy.go b/pkg/ictt/deploy.go index 0b97743c5..93433d444 100644 --- a/pkg/ictt/deploy.go +++ b/pkg/ictt/deploy.go @@ -53,9 +53,10 @@ func DeployERC20Remote( teleporterManagerAddress common.Address, tokenHomeBlockchainID [32]byte, tokenHomeAddress common.Address, - tokenName string, - tokenSymbol string, - tokenDecimals uint8, + tokenHomeDecimals uint8, + tokenRemoteName string, + tokenRemoteSymbol string, + tokenRemoteDecimals uint8, ) (common.Address, error) { binPath := filepath.Join(srcDir, "contracts/out/ERC20TokenRemote.sol/ERC20TokenRemote.bin") binBytes, err := os.ReadFile(binPath) @@ -67,8 +68,7 @@ func DeployERC20Remote( TeleporterManager: teleporterManagerAddress, TokenHomeBlockchainID: tokenHomeBlockchainID, TokenHomeAddress: tokenHomeAddress, - // TODO: user case for home having diff decimals - TokenHomeDecimals: tokenDecimals, + TokenHomeDecimals: tokenHomeDecimals, } return contract.DeployContract( rpcURL, @@ -76,9 +76,46 @@ func DeployERC20Remote( binBytes, "((address, address, bytes32, address, uint8), string, string, uint8)", tokenRemoteSettings, - tokenName, - tokenSymbol, - tokenDecimals, + tokenRemoteName, + tokenRemoteSymbol, + tokenRemoteDecimals, + ) +} + +func DeployNativeRemote( + srcDir string, + rpcURL string, + privateKey string, + teleporterRegistryAddress common.Address, + teleporterManagerAddress common.Address, + tokenHomeBlockchainID [32]byte, + tokenHomeAddress common.Address, + tokenHomeDecimals uint8, + nativeAssetSymbol string, + initialReserveImbalance *big.Int, + burnedFeesReportingRewardPercentage *big.Int, +) (common.Address, error) { + binPath := filepath.Join(srcDir, "contracts/out/NativeTokenRemote.sol/NativeTokenRemote.bin") + binBytes, err := os.ReadFile(binPath) + if err != nil { + return common.Address{}, err + } + tokenRemoteSettings := TokenRemoteSettings{ + TeleporterRegistryAddress: teleporterRegistryAddress, + TeleporterManager: teleporterManagerAddress, + TokenHomeBlockchainID: tokenHomeBlockchainID, + TokenHomeAddress: tokenHomeAddress, + TokenHomeDecimals: tokenHomeDecimals, + } + return contract.DeployContract( + rpcURL, + privateKey, + binBytes, + "((address, address, bytes32, address, uint8), string, uint256, uint256)", + tokenRemoteSettings, + nativeAssetSymbol, + initialReserveImbalance, + burnedFeesReportingRewardPercentage, ) } diff --git a/pkg/ictt/erc20.go b/pkg/ictt/erc20.go index 56f82cbfd..8683d1da6 100644 --- a/pkg/ictt/erc20.go +++ b/pkg/ictt/erc20.go @@ -10,13 +10,12 @@ import ( "github.com/liyue201/erc20-go/erc20" ) -func GetTokenParams(endpoint string, tokenAddress string) (string, string, uint8, error) { - address := common.HexToAddress(tokenAddress) +func GetTokenParams(endpoint string, tokenAddress common.Address) (string, string, uint8, error) { client, err := ethclient.Dial(endpoint) if err != nil { return "", "", 0, err } - token, err := erc20.NewGGToken(address, client) + token, err := erc20.NewGGToken(tokenAddress, client) if err != nil { return "", "", 0, err } @@ -28,10 +27,21 @@ func GetTokenParams(endpoint string, tokenAddress string) (string, string, uint8 if err != nil { return "", "", 0, err } - // TODO: find out if there are decimals options and why (academy) tokenDecimals, err := token.Decimals(nil) if err != nil { return "", "", 0, err } return tokenSymbol, tokenName, tokenDecimals, nil } + +func GetTokenDecimals(endpoint string, tokenAddress common.Address) (uint8, error) { + client, err := ethclient.Dial(endpoint) + if err != nil { + return 0, err + } + token, err := erc20.NewGGToken(tokenAddress, client) + if err != nil { + return 0, err + } + return token.Decimals(nil) +} diff --git a/pkg/ictt/operate.go b/pkg/ictt/operate.go index d1c055c3a..6b173e8b5 100644 --- a/pkg/ictt/operate.go +++ b/pkg/ictt/operate.go @@ -19,6 +19,7 @@ const ( ERC20TokenHome NativeTokenHome ERC20TokenRemote + NativeTokenRemote ) func GetEndpointKind( @@ -31,6 +32,9 @@ func GetEndpointKind( if _, err := NativeTokenHomeGetTokenAddress(rpcURL, address); err == nil { return NativeTokenHome, nil } + if _, err := NativeTokenRemoteGetTotalNativeAssetSupply(rpcURL, address); err == nil { + return NativeTokenRemote, nil + } if _, err := ERC20TokenRemoteGetTokenHomeAddress(rpcURL, address); err == nil { return ERC20TokenRemote, nil } else { @@ -76,6 +80,90 @@ func NativeTokenHomeGetTokenAddress( return tokenAddress, nil } +func TokenRemoteIsCollateralized( + rpcURL string, + address common.Address, +) (bool, error) { + out, err := contract.CallToMethod( + rpcURL, + address, + "isCollateralized()->(bool)", + ) + if err != nil { + return false, err + } + isCollateralized, b := out[0].(bool) + if !b { + return false, fmt.Errorf("error at isCollateralized call, expected bool, got %T", out[0]) + } + return isCollateralized, nil +} + +func TokenHomeGetDecimals( + rpcURL string, + address common.Address, +) (uint8, error) { + out, err := contract.CallToMethod( + rpcURL, + address, + "tokenDecimals()->(uint8)", + ) + if err != nil { + return 0, err + } + decimals, b := out[0].(uint8) + if !b { + return 0, fmt.Errorf("error at tokenDecimals, expected uint8, got %T", out[0]) + } + return decimals, nil +} + +type RegisteredRemote struct { + Registered bool + CollateralNeeded *big.Int + TokenMultiplier *big.Int + MultiplyOnRemote bool +} + +func TokenHomeGetRegisteredRemote( + rpcURL string, + address common.Address, + remoteBlockchainID [32]byte, + remoteAddress common.Address, +) (RegisteredRemote, error) { + out, err := contract.CallToMethod( + rpcURL, + address, + "registeredRemotes(bytes32, address)->(bool,uint256,uint256,bool)", + remoteBlockchainID, + remoteAddress, + ) + if err != nil { + return RegisteredRemote{}, err + } + var ( + registeredRemote RegisteredRemote + b bool + ) + registeredRemote.Registered, b = out[0].(bool) + if !b { + return RegisteredRemote{}, fmt.Errorf("error at registeredRemotes call, expected bool, got %T", out[0]) + } + registeredRemote.CollateralNeeded, b = out[1].(*big.Int) + if !b { + return RegisteredRemote{}, fmt.Errorf("error at registeredRemotes call, expected *big.Int, got %T", out[1]) + } + registeredRemote.TokenMultiplier, b = out[2].(*big.Int) + if !b { + return RegisteredRemote{}, fmt.Errorf("error at registeredRemotes call, expected *big.Int, got %T", out[2]) + } + registeredRemote.MultiplyOnRemote, b = out[3].(bool) + if !b { + return RegisteredRemote{}, fmt.Errorf("error at registeredRemotes call, expected bool, got %T", out[3]) + } + return registeredRemote, nil +} + func ERC20TokenRemoteGetTokenHomeAddress( rpcURL string, address common.Address, @@ -95,6 +183,25 @@ func ERC20TokenRemoteGetTokenHomeAddress( return tokenHubAddress, nil } +func NativeTokenRemoteGetTotalNativeAssetSupply( + rpcURL string, + address common.Address, +) (*big.Int, error) { + out, err := contract.CallToMethod( + rpcURL, + address, + "totalNativeAssetSupply()->(uint256)", + ) + if err != nil { + return nil, err + } + supply, b := out[0].(*big.Int) + if !b { + return nil, fmt.Errorf("error at totalNativeAssetSupply, expected *big.Int, got %T", out[0]) + } + return supply, nil +} + func ERC20TokenHomeSend( rpcURL string, homeAddress common.Address, @@ -246,3 +353,202 @@ func ERC20TokenRemoteSend( ) return err } + +func NativeTokenRemoteSend( + rpcURL string, + remoteAddress common.Address, + privateKey string, + destinationBlockchainID ids.ID, + destinationICTTEndpoint common.Address, + amountRecipient common.Address, + amount *big.Int, +) error { + type Params struct { + DestinationBlockchainID [32]byte + DestinationICTTEndpoint common.Address + AmountRecipient common.Address + PrimaryFeeTokenAddress common.Address + PrimaryFee *big.Int + SecondaryFee *big.Int + RequiredGasLimit *big.Int + MultiHopFallback common.Address + } + params := Params{ + DestinationBlockchainID: destinationBlockchainID, + DestinationICTTEndpoint: destinationICTTEndpoint, + AmountRecipient: amountRecipient, + PrimaryFeeTokenAddress: remoteAddress, // in theory this is optional + PrimaryFee: big.NewInt(0), + SecondaryFee: big.NewInt(0), + RequiredGasLimit: big.NewInt(250000), + MultiHopFallback: common.Address{}, + } + _, _, err := contract.TxToMethod( + rpcURL, + privateKey, + remoteAddress, + amount, + "send((bytes32, address, address, address, uint256, uint256, uint256, address))", + params, + ) + return err +} + +func NativeTokenHomeAddCollateral( + rpcURL string, + homeAddress common.Address, + privateKey string, + remoteBlockchainID [32]byte, + remoteAddress common.Address, + amount *big.Int, +) error { + _, _, err := contract.TxToMethod( + rpcURL, + privateKey, + homeAddress, + amount, + "addCollateral(bytes32, address)", + remoteBlockchainID, + remoteAddress, + ) + return err +} + +func ERC20TokenHomeAddCollateral( + rpcURL string, + homeAddress common.Address, + privateKey string, + remoteBlockchainID [32]byte, + remoteAddress common.Address, + amount *big.Int, +) error { + tokenAddress, err := ERC20TokenHomeGetTokenAddress(rpcURL, homeAddress) + if err != nil { + return err + } + if _, _, err := contract.TxToMethod( + rpcURL, + privateKey, + tokenAddress, + nil, + "approve(address, uint256)->(bool)", + homeAddress, + amount, + ); err != nil { + return err + } + _, _, err = contract.TxToMethod( + rpcURL, + privateKey, + homeAddress, + nil, + "addCollateral(bytes32, address, uint256)", + remoteBlockchainID, + remoteAddress, + amount, + ) + return err +} + +func TokenHomeAddCollateral( + rpcURL string, + homeAddress common.Address, + privateKey string, + remoteBlockchainID [32]byte, + remoteAddress common.Address, + amount *big.Int, +) error { + endpointKind, err := GetEndpointKind( + rpcURL, + homeAddress, + ) + if err != nil { + return err + } + switch endpointKind { + case ERC20TokenHome: + return ERC20TokenHomeAddCollateral( + rpcURL, + homeAddress, + privateKey, + remoteBlockchainID, + remoteAddress, + amount, + ) + case NativeTokenHome: + return NativeTokenHomeAddCollateral( + rpcURL, + homeAddress, + privateKey, + remoteBlockchainID, + remoteAddress, + amount, + ) + case ERC20TokenRemote: + return fmt.Errorf("trying to add collateral to an erc20 token remote endpoint") + case NativeTokenRemote: + return fmt.Errorf("trying to add collateral to a native token remote endpoint") + } + return fmt.Errorf("unknown ictt endpoint") +} + +func Send( + rpcURL string, + address common.Address, + privateKey string, + destinationBlockchainID ids.ID, + destinationAddress common.Address, + amountRecipient common.Address, + amount *big.Int, +) error { + endpointKind, err := GetEndpointKind( + rpcURL, + address, + ) + if err != nil { + return err + } + switch endpointKind { + case ERC20TokenRemote: + return ERC20TokenRemoteSend( + rpcURL, + address, + privateKey, + destinationBlockchainID, + destinationAddress, + amountRecipient, + amount, + ) + case ERC20TokenHome: + return ERC20TokenHomeSend( + rpcURL, + address, + privateKey, + destinationBlockchainID, + destinationAddress, + amountRecipient, + amount, + ) + case NativeTokenHome: + return NativeTokenHomeSend( + rpcURL, + address, + privateKey, + destinationBlockchainID, + destinationAddress, + amountRecipient, + amount, + ) + case NativeTokenRemote: + return NativeTokenRemoteSend( + rpcURL, + address, + privateKey, + destinationBlockchainID, + destinationAddress, + amountRecipient, + amount, + ) + } + return fmt.Errorf("unknown ictt endpoint") +} diff --git a/pkg/keychain/keychain.go b/pkg/keychain/keychain.go index 429795377..323a7898d 100644 --- a/pkg/keychain/keychain.go +++ b/pkg/keychain/keychain.go @@ -136,10 +136,13 @@ func GetKeychainFromCmdLineFlags( } } case network.Kind == models.Devnet: - // going to just use ewoq atm - useEwoq = true - if keyName != "" || useLedger { - return nil, ErrNonEwoqKeyOnDevnet + // prompt the user if no key source was provided + if !useEwoq && !useLedger && keyName == "" { + var err error + useLedger, keyName, err = prompts.GetKeyOrLedger(app.Prompt, keychainGoal, app.GetKeyDir(), true) + if err != nil { + return nil, err + } } case network.Kind == models.Fuji: if useEwoq { diff --git a/pkg/localnet/statusChecker.go b/pkg/localnet/status_checker.go similarity index 100% rename from pkg/localnet/statusChecker.go rename to pkg/localnet/status_checker.go diff --git a/pkg/models/backwardsCompatibility.go b/pkg/models/backwards_compatibility.go similarity index 100% rename from pkg/models/backwardsCompatibility.go rename to pkg/models/backwards_compatibility.go diff --git a/pkg/models/cloud.go b/pkg/models/cloud.go index 533c374fa..565d21037 100644 --- a/pkg/models/cloud.go +++ b/pkg/models/cloud.go @@ -35,6 +35,15 @@ func (ccm *CloudConfig) GetAllInstanceIDs() []string { return instanceIDs } +// GetAllAPIInstanceIDs returns all API instance IDs +func (ccm *CloudConfig) GetAllAPIInstanceIDs() []string { + apiInstanceIDs := []string{} + for _, cloudConfig := range *ccm { + apiInstanceIDs = append(apiInstanceIDs, cloudConfig.APIInstanceIDs...) + } + return apiInstanceIDs +} + // GetInstanceIDsForRegion returns instance IDs for specific region func (ccm *CloudConfig) GetInstanceIDsForRegion(region string) []string { if regionConf, ok := (*ccm)[region]; ok { diff --git a/pkg/models/clustersConfig.go b/pkg/models/clusters_config.go similarity index 98% rename from pkg/models/clustersConfig.go rename to pkg/models/clusters_config.go index 70100a4ee..cec654905 100644 --- a/pkg/models/clustersConfig.go +++ b/pkg/models/clusters_config.go @@ -28,6 +28,7 @@ type ClusterConfig struct { ExtraNetworkData ExtraNetworkData Subnets []string External bool + HTTPAccess constants.HTTPAccess } type ClustersConfig struct { diff --git a/pkg/models/elasticSubnetConfig.go b/pkg/models/elasticSubnetConfig.go deleted file mode 100644 index e924441d8..000000000 --- a/pkg/models/elasticSubnetConfig.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package models - -import ( - "time" - - "github.com/ava-labs/avalanchego/ids" -) - -type ElasticSubnetConfig struct { - SubnetID ids.ID - AssetID ids.ID - InitialSupply uint64 - MaxSupply uint64 - MinConsumptionRate uint64 - MaxConsumptionRate uint64 - MinValidatorStake uint64 - MaxValidatorStake uint64 - MinStakeDuration time.Duration - MaxStakeDuration time.Duration - MinDelegationFee uint32 - MinDelegatorStake uint64 - MaxValidatorWeightFactor byte - UptimeRequirement uint32 -} diff --git a/pkg/models/exportCluster.go b/pkg/models/export_cluster.go similarity index 100% rename from pkg/models/exportCluster.go rename to pkg/models/export_cluster.go diff --git a/pkg/models/host.go b/pkg/models/host.go index cfcc35d2d..dffa544e0 100644 --- a/pkg/models/host.go +++ b/pkg/models/host.go @@ -235,6 +235,7 @@ func (h *Host) Command(script string, env []string, timeout time.Duration) ([]by } ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() + // ux.Logger.Info("DEBUG Command on host %s: %s", h.IP, script) cmd, err := h.Connection.CommandContext(ctx, "", script) if err != nil { return nil, err diff --git a/pkg/models/network.go b/pkg/models/network.go index 927b44476..08128c2fc 100644 --- a/pkg/models/network.go +++ b/pkg/models/network.go @@ -123,7 +123,14 @@ func (n Network) BlockchainWSEndpoint(blockchainID string) string { trimmedURI := n.Endpoint trimmedURI = strings.TrimPrefix(trimmedURI, "http://") trimmedURI = strings.TrimPrefix(trimmedURI, "https://") - return fmt.Sprintf("ws://%s/ext/bc/%s/ws", trimmedURI, blockchainID) + scheme := "ws" + switch n.Kind { + case Fuji: + scheme = "wss" + case Mainnet: + scheme = "wss" + } + return fmt.Sprintf("%s://%s/ext/bc/%s/ws", scheme, trimmedURI, blockchainID) } func (n Network) NetworkIDFlagValue() string { diff --git a/pkg/models/nodeConfig.go b/pkg/models/node_config.go similarity index 100% rename from pkg/models/nodeConfig.go rename to pkg/models/node_config.go diff --git a/pkg/models/sidecar.go b/pkg/models/sidecar.go index ba41f7ecf..b7f5956ec 100644 --- a/pkg/models/sidecar.go +++ b/pkg/models/sidecar.go @@ -8,25 +8,13 @@ import ( ) type NetworkData struct { - SubnetID ids.ID - TransferSubnetOwnershipTxID ids.ID - BlockchainID ids.ID - RPCVersion int - TeleporterMessengerAddress string - TeleporterRegistryAddress string -} - -type PermissionlessValidators struct { - TxID ids.ID -} -type ElasticSubnet struct { - SubnetID ids.ID - AssetID ids.ID - PChainTXID ids.ID - TokenName string - TokenSymbol string - Validators map[string]PermissionlessValidators - Txs map[string]ids.ID + SubnetID ids.ID + BlockchainID ids.ID + RPCVersion int + TeleporterMessengerAddress string + TeleporterRegistryAddress string + RPCEndpoints []string + WSEndpoints []string } type Sidecar struct { @@ -41,7 +29,6 @@ type Sidecar struct { ChainID string Version string Networks map[string]NetworkData - ElasticSubnet map[string]ElasticSubnet ImportedFromAPM bool ImportedVMID string CustomVMRepoURL string diff --git a/pkg/networkoptions/networkoptions.go b/pkg/networkoptions/network_options.go similarity index 95% rename from pkg/networkoptions/networkoptions.go rename to pkg/networkoptions/network_options.go index 74966973e..7e1c32bcb 100644 --- a/pkg/networkoptions/networkoptions.go +++ b/pkg/networkoptions/network_options.go @@ -5,6 +5,7 @@ package networkoptions import ( "fmt" "os" + "regexp" "strings" "github.com/ava-labs/avalanche-cli/cmd/flags" @@ -71,8 +72,7 @@ type NetworkFlags struct { ClusterName string } -func AddNetworkFlagsToCmd(cmd *cobra.Command, networkFlags *NetworkFlags, alwaysAddEndpoint bool, supportedNetworkOptions []NetworkOption) { - addEndpoint := alwaysAddEndpoint +func AddNetworkFlagsToCmd(cmd *cobra.Command, networkFlags *NetworkFlags, addEndpoint bool, supportedNetworkOptions []NetworkOption) { addCluster := false for _, networkOption := range supportedNetworkOptions { switch networkOption { @@ -133,11 +133,16 @@ func GetSupportedNetworkOptionsForSubnet( for _, networkOption := range supportedNetworkOptions { isInSidecar := false for networkName := range sc.Networks { - if strings.HasPrefix(networkName, networkOption.String()) { + networkOptionWords := strings.Fields(networkOption.String()) + if len(networkOptionWords) == 0 { + return nil, nil, nil, fmt.Errorf("empty network option") + } + firstNetworkOptionWord := networkOptionWords[0] + if strings.HasPrefix(networkName, firstNetworkOptionWord) { isInSidecar = true } if os.Getenv(constants.SimulatePublicNetwork) != "" { - if strings.HasPrefix(networkName, Local.String()) { + if networkName == Local.String() { if networkOption == Fuji || networkOption == Mainnet { isInSidecar = true } @@ -330,6 +335,11 @@ func GetNetworkFromCmdLineFlags( } } + if networkFlags.Endpoint != "" { + re := regexp.MustCompile(`/+$`) + networkFlags.Endpoint = re.ReplaceAllString(networkFlags.Endpoint, "") + } + network := models.UndefinedNetwork switch networkOption { case Local: @@ -356,6 +366,7 @@ func GetNetworkFromCmdLineFlags( return models.UndefinedNetwork, err } } + // on all cases, enable user setting specific endpoint if networkFlags.Endpoint != "" { network.Endpoint = networkFlags.Endpoint diff --git a/pkg/plugins/avagoConfig.go b/pkg/plugins/avago_config.go similarity index 100% rename from pkg/plugins/avagoConfig.go rename to pkg/plugins/avago_config.go diff --git a/pkg/plugins/avagoConfig_test.go b/pkg/plugins/avago_config_test.go similarity index 100% rename from pkg/plugins/avagoConfig_test.go rename to pkg/plugins/avago_config_test.go diff --git a/pkg/plugins/findDefaults.go b/pkg/plugins/find_defaults.go similarity index 100% rename from pkg/plugins/findDefaults.go rename to pkg/plugins/find_defaults.go diff --git a/pkg/plugins/findDefaults_test.go b/pkg/plugins/find_defaults_test.go similarity index 100% rename from pkg/plugins/findDefaults_test.go rename to pkg/plugins/find_defaults_test.go diff --git a/pkg/precompiles/allowlist.go b/pkg/precompiles/allowlist.go new file mode 100644 index 000000000..b5fbc8e4d --- /dev/null +++ b/pkg/precompiles/allowlist.go @@ -0,0 +1,101 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package precompiles + +import ( + _ "embed" + "fmt" + "math/big" + + "github.com/ava-labs/avalanche-cli/pkg/contract" + "github.com/ethereum/go-ethereum/common" +) + +func SetAdmin( + rpcURL string, + precompile common.Address, + privateKey string, + toSet common.Address, +) error { + _, _, err := contract.TxToMethod( + rpcURL, + privateKey, + precompile, + nil, + "setAdmin(address)", + toSet, + ) + return err +} + +func SetManager( + rpcURL string, + precompile common.Address, + privateKey string, + toSet common.Address, +) error { + _, _, err := contract.TxToMethod( + rpcURL, + privateKey, + precompile, + nil, + "setManager(address)", + toSet, + ) + return err +} + +func SetEnabled( + rpcURL string, + precompile common.Address, + privateKey string, + toSet common.Address, +) error { + _, _, err := contract.TxToMethod( + rpcURL, + privateKey, + precompile, + nil, + "setEnabled(address)", + toSet, + ) + return err +} + +func SetNone( + rpcURL string, + precompile common.Address, + privateKey string, + toSet common.Address, +) error { + _, _, err := contract.TxToMethod( + rpcURL, + privateKey, + precompile, + nil, + "setNone(address)", + toSet, + ) + return err +} + +func ReadAllowList( + rpcURL string, + precompile common.Address, + toQuery common.Address, +) (*big.Int, error) { + out, err := contract.CallToMethod( + rpcURL, + precompile, + "readAllowList(address)->(uint256)", + toQuery, + ) + if err != nil { + return nil, err + } + role, b := out[0].(*big.Int) + if !b { + return nil, fmt.Errorf("error at readAllowList, expected *big.Int, got %T", out[0]) + } + return role, nil +} diff --git a/pkg/precompiles/precompiles.go b/pkg/precompiles/precompiles.go new file mode 100644 index 000000000..336bc49c1 --- /dev/null +++ b/pkg/precompiles/precompiles.go @@ -0,0 +1,11 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package precompiles + +import ( + _ "embed" + + "github.com/ethereum/go-ethereum/common" +) + +var NativeMinterPrecompile = common.HexToAddress("0x0200000000000000000000000000000000000001") diff --git a/pkg/prompts/prompts.go b/pkg/prompts/prompts.go index 64b1677f4..b19254d86 100644 --- a/pkg/prompts/prompts.go +++ b/pkg/prompts/prompts.go @@ -46,6 +46,8 @@ const ( MoreThanEq = "More Than Or Eq" MoreThan = "More Than" NotEq = "Not Eq" + + customOption = "Custom" ) var errNoKeys = errors.New("no keys") @@ -901,7 +903,8 @@ func PromptChain( avoidXChain bool, avoidCChain bool, avoidSubnet string, -) (bool, bool, bool, bool, string, error) { + includeCustom bool, +) (bool, bool, bool, bool, string, string, error) { pChainOption := "P-Chain" xChainOption := "X-Chain" cChainOption := "C-Chain" @@ -917,29 +920,40 @@ func PromptChain( subnetOptions = append(subnetOptions, cChainOption) } subnetNames = utils.RemoveFromSlice(subnetNames, avoidSubnet) - subnetOptions = append(subnetOptions, utils.Map(subnetNames, func(s string) string { return "Subnet " + s })...) - subnetOptions = append(subnetOptions, notListedOption) + subnetOptions = append(subnetOptions, utils.Map(subnetNames, func(s string) string { return "Blockchain " + s })...) + if includeCustom { + subnetOptions = append(subnetOptions, customOption) + } else { + subnetOptions = append(subnetOptions, notListedOption) + } subnetOption, err := prompter.CaptureListWithSize( prompt, subnetOptions, 11, ) if err != nil { - return false, false, false, false, "", err + return false, false, false, false, "", "", err + } + if subnetOption == customOption { + blockchainID, err := prompter.CaptureString("Blockchain ID/Alias") + if err != nil { + return false, false, false, false, "", "", err + } + return false, false, false, false, "", blockchainID, nil } if subnetOption == notListedOption { ux.Logger.PrintToUser("Please import the subnet first, using the `avalanche subnet import` command suite") - return true, false, false, false, "", nil + return true, false, false, false, "", "", nil } switch subnetOption { case pChainOption: - return false, true, false, false, "", nil + return false, true, false, false, "", "", nil case xChainOption: - return false, false, true, false, "", nil + return false, false, true, false, "", "", nil case cChainOption: - return false, false, false, true, "", nil + return false, false, false, true, "", "", nil default: - return false, false, false, false, strings.TrimPrefix(subnetOption, "Subnet "), nil + return false, false, false, false, strings.TrimPrefix(subnetOption, "Blockchain "), "", nil } } @@ -953,11 +967,10 @@ func PromptPrivateKey( ) (string, error) { privateKey := "" cliKeyOpt := "Get private key from an existing stored key (created from avalanche key create or avalanche key import)" - customKeyOpt := "Custom" genesisKeyOpt := fmt.Sprintf("Use the private key of the Genesis Allocated address %s", genesisAddress) - keyOptions := []string{cliKeyOpt, customKeyOpt} + keyOptions := []string{cliKeyOpt, customOption} if genesisPrivateKey != "" { - keyOptions = []string{genesisKeyOpt, cliKeyOpt, customKeyOpt} + keyOptions = []string{genesisKeyOpt, cliKeyOpt, customOption} } keyOption, err := prompter.CaptureList( fmt.Sprintf("Which private key do you want to use to %s?", goal), @@ -977,7 +990,7 @@ func PromptPrivateKey( return "", err } privateKey = k.PrivKeyHex() - case customKeyOpt: + case customOption: privateKey, err = prompter.CaptureString("Private Key") if err != nil { return "", err @@ -1000,11 +1013,10 @@ func PromptAddress( ) (string, error) { address := "" cliKeyOpt := "Get address from an existing stored key (created from avalanche key create or avalanche key import)" - customKeyOpt := "Custom" genesisKeyOpt := fmt.Sprintf("Use the Genesis Allocated address %s", genesisAddress) - keyOptions := []string{cliKeyOpt, customKeyOpt} + keyOptions := []string{cliKeyOpt, customOption} if genesisAddress != "" { - keyOptions = []string{genesisKeyOpt, cliKeyOpt, customKeyOpt} + keyOptions = []string{genesisKeyOpt, cliKeyOpt, customOption} } keyOption, err := prompter.CaptureList( fmt.Sprintf("Which address do you want to %s?", goal), @@ -1026,7 +1038,7 @@ func PromptAddress( if err != nil { return "", err } - case customKeyOpt: + case customOption: switch format { case PChainFormat: address, err = prompter.CapturePChainAddress(customPrompt, network) diff --git a/pkg/prompts/validations.go b/pkg/prompts/validations.go index 416b036d3..3a96ddb7d 100644 --- a/pkg/prompts/validations.go +++ b/pkg/prompts/validations.go @@ -203,6 +203,8 @@ func getPChainValidationFunc(network models.Network) func(string) error { return validatePChainMainAddress case models.Local: return validatePChainLocalAddress + case models.Devnet: + return validatePChainLocalAddress default: return func(string) error { return errors.New("unsupported network") diff --git a/pkg/remoteconfig/avalanche.go b/pkg/remoteconfig/avalanche.go index d4e1c12d6..835e99851 100644 --- a/pkg/remoteconfig/avalanche.go +++ b/pkg/remoteconfig/avalanche.go @@ -5,9 +5,9 @@ package remoteconfig import ( "bytes" - "html/template" "path/filepath" "strings" + "text/template" "github.com/ava-labs/avalanche-cli/pkg/constants" ) @@ -22,6 +22,8 @@ type AvalancheConfigInputs struct { PublicIP string StateSyncEnabled bool PruningEnabled bool + Aliases []string + BlockChainID string TrackSubnets string BootstrapIDs string BootstrapIPs string @@ -30,7 +32,7 @@ type AvalancheConfigInputs struct { func PrepareAvalancheConfig(publicIP string, networkID string, subnets []string) AvalancheConfigInputs { return AvalancheConfigInputs{ - HTTPHost: "0.0.0.0", + HTTPHost: "127.0.0.1", NetworkID: networkID, DBDir: "/.avalanchego/db/", LogDir: "/.avalanchego/logs/", @@ -38,6 +40,8 @@ func PrepareAvalancheConfig(publicIP string, networkID string, subnets []string) StateSyncEnabled: true, PruningEnabled: false, TrackSubnets: strings.Join(subnets, ","), + Aliases: nil, + BlockChainID: "", } } @@ -46,7 +50,10 @@ func RenderAvalancheTemplate(templateName string, config AvalancheConfigInputs) if err != nil { return nil, err } - tmpl, err := template.New("config").Parse(string(templateBytes)) + helperFuncs := template.FuncMap{ + "join": strings.Join, + } + tmpl, err := template.New("config").Funcs(helperFuncs).Parse(string(templateBytes)) if err != nil { return nil, err } @@ -75,8 +82,16 @@ func RenderAvalancheCChainConfig(config AvalancheConfigInputs) ([]byte, error) { } } +func RenderAvalancheAliasesConfig(config AvalancheConfigInputs) ([]byte, error) { + if output, err := RenderAvalancheTemplate("templates/avalanche-aliases.tmpl", config); err != nil { + return nil, err + } else { + return output, nil + } +} + func GetRemoteAvalancheNodeConfig() string { - return filepath.Join(constants.CloudNodeConfigPath, "node.json") + return filepath.Join(constants.CloudNodeConfigPath, constants.NodeFileName) } func GetRemoteAvalancheCChainConfig() string { @@ -84,7 +99,11 @@ func GetRemoteAvalancheCChainConfig() string { } func GetRemoteAvalancheGenesis() string { - return filepath.Join(constants.CloudNodeConfigPath, "genesis.json") + return filepath.Join(constants.CloudNodeConfigPath, constants.GenesisFileName) +} + +func GetRemoteAvalancheAliasesConfig() string { + return filepath.Join(constants.CloudNodeConfigPath, "chains", constants.AliasesFileName) } func AvalancheFolderToCreate() []string { diff --git a/pkg/remoteconfig/templates/avalanche-aliases.tmpl b/pkg/remoteconfig/templates/avalanche-aliases.tmpl new file mode 100644 index 000000000..0c2eaa0eb --- /dev/null +++ b/pkg/remoteconfig/templates/avalanche-aliases.tmpl @@ -0,0 +1,3 @@ +{ + "{{.BlockChainID}}": ["{{join .Aliases "\", \""}}"] +} diff --git a/pkg/ssh/shell/buildCustomVM.sh b/pkg/ssh/shell/buildCustomVM.sh index ad813e6f1..fdaa1857c 100644 --- a/pkg/ssh/shell/buildCustomVM.sh +++ b/pkg/ssh/shell/buildCustomVM.sh @@ -3,8 +3,13 @@ if [ -d {{ .CustomVMRepoDir }} ]; then rm -rf {{ .CustomVMRepoDir }} fi +mkdir -p {{ .CustomVMRepoDir }} +# prepare build script cd {{ .CustomVMRepoDir }} +cat <>build.sh +#!/usr/bin/env bash +set -x git init -q git remote add origin {{ .CustomVMRepoURL }} git fetch --depth 1 origin {{ .CustomVMBranch }} -q @@ -12,5 +17,28 @@ git checkout {{ .CustomVMBranch }} chmod +x {{ .CustomVMBuildScript }} ./{{ .CustomVMBuildScript }} {{ .VMBinaryPath }} echo {{ .VMBinaryPath }} [ok] +EOF +chmod +x build.sh + +#prepare build Dockerfile +cat <>Dockerfile +FROM golang:{{ .GoVersion }}-bullseye AS builder +RUN apt-get update +RUN apt-get install -y git ca-certificates +RUN mkdir -p {{ .CustomVMRepoDir }} +WORKDIR {{ .CustomVMRepoDir }} +COPY build.sh . +RUN ./build.sh + +FROM scratch AS vm +COPY --from=builder {{ .VMBinaryPath }} / +EOF + +VMBinaryDir=$(dirname {{ .VMBinaryPath }}) +# build +docker buildx build --target=vm --output type=local,dest=${VMBinaryDir} . + + + diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index ed052d376..7c2f1d486 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -177,13 +177,13 @@ func RunSSHStopAWMRelayerService(host *models.Host) error { } // RunSSHUpgradeAvalanchego runs script to upgrade avalanchego -func RunSSHUpgradeAvalanchego(host *models.Host, network models.Network, avalancheGoVersion string) error { +func RunSSHUpgradeAvalanchego(host *models.Host, network models.Network, avalancheGoVersion string, publicAccessToHTTPPort bool) error { withMonitoring, err := docker.WasNodeSetupWithMonitoring(host) if err != nil { return err } - if err := docker.ComposeSSHSetupNode(host, network, avalancheGoVersion, withMonitoring); err != nil { + if err := docker.ComposeSSHSetupNode(host, network, avalancheGoVersion, withMonitoring, publicAccessToHTTPPort); err != nil { return err } return docker.RestartDockerCompose(host, constants.SSHLongRunningScriptTimeout) @@ -483,8 +483,58 @@ func RunSSHUploadStakingFiles(host *models.Host, nodeInstanceDirPath string) err ) } +// RunSSHRenderAvagoAliasConfigFile renders avalanche alias config to a remote host via SSH. +func RunSSHRenderAvagoAliasConfigFile( + host *models.Host, + blockchainID string, + subnetAliases []string, +) error { + aliasToBlockchain := map[string]string{} + if aliasConfigFileExists(host) { + // load remote aliases + remoteAliases, err := getAvalancheGoAliasData(host) + if err != nil { + return err + } + for chainID, aliases := range remoteAliases { + for _, alias := range aliases { + aliasToBlockchain[alias] = chainID + } + } + } + for _, alias := range subnetAliases { + aliasToBlockchain[alias] = blockchainID + } + newAliases := map[string][]string{} + for alias, chainID := range aliasToBlockchain { + newAliases[chainID] = append(newAliases[chainID], alias) + } + aliasConf, err := json.MarshalIndent(newAliases, "", " ") + if err != nil { + return err + } + aliasConfFile, err := os.CreateTemp("", "avalanchecli-alias-*.yml") + if err != nil { + return err + } + defer os.Remove(aliasConfFile.Name()) + if err := os.WriteFile(aliasConfFile.Name(), aliasConf, constants.DefaultPerms755); err != nil { + return err + } + if err := host.Upload(aliasConfFile.Name(), remoteconfig.GetRemoteAvalancheAliasesConfig(), constants.SSHFileOpsTimeout); err != nil { + return err + } + return nil +} + // RunSSHRenderAvalancheNodeConfig renders avalanche node config to a remote host via SSH. -func RunSSHRenderAvalancheNodeConfig(app *application.Avalanche, host *models.Host, network models.Network, trackSubnets []string) error { +func RunSSHRenderAvalancheNodeConfig( + app *application.Avalanche, + host *models.Host, + network models.Network, + trackSubnets []string, + isAPIHost bool, +) error { // get subnet ids subnetIDs, err := utils.MapWithError(trackSubnets, func(subnetName string) (string, error) { sc, err := app.LoadSidecar(subnetName) @@ -499,24 +549,25 @@ func RunSSHRenderAvalancheNodeConfig(app *application.Avalanche, host *models.Ho } avagoConf := remoteconfig.PrepareAvalancheConfig(host.IP, network.NetworkIDFlagValue(), subnetIDs) - // make sure that genesis and bootstrap data is preserved - if genesisFileExists(host) { - avagoConf.GenesisPath = filepath.Join(constants.DockerNodeConfigPath, constants.GenesisFileName) - } - remoteAvagoConf, err := getAvalancheGoConfigData(host) - if err != nil { - return err - } - bootstrapIDs, err := utils.StringValue(remoteAvagoConf, "bootstrap-ids") - if err != nil { - return err - } - bootstrapIPs, err := utils.StringValue(remoteAvagoConf, "bootstrap-ips") - if err != nil { - return err + // preserve remote configuration if it exists + if nodeConfigFileExists(host) { + // make sure that genesis and bootstrap data is preserved + if genesisFileExists(host) { + avagoConf.GenesisPath = filepath.Join(constants.DockerNodeConfigPath, constants.GenesisFileName) + } + if network.Kind == models.Local || network.Kind == models.Devnet || isAPIHost { + avagoConf.HTTPHost = "0.0.0.0" + } + remoteAvagoConf, err := getAvalancheGoConfigData(host) + if err != nil { + return err + } + // ignore errors if bootstrap configuration is not present - it's fine + bootstrapIDs, _ := utils.StringValue(remoteAvagoConf, "bootstrap-ids") + bootstrapIPs, _ := utils.StringValue(remoteAvagoConf, "bootstrap-ips") + avagoConf.BootstrapIDs = bootstrapIDs + avagoConf.BootstrapIPs = bootstrapIPs } - avagoConf.BootstrapIDs = bootstrapIDs - avagoConf.BootstrapIPs = bootstrapIPs // ready to render node config nodeConf, err := remoteconfig.RenderAvalancheNodeConfig(avagoConf) if err != nil { @@ -555,6 +606,7 @@ func RunSSHCreatePlugin(host *models.Host, sc models.Sidecar) error { CustomVMBranch: sc.CustomVMBranch, CustomVMBuildScript: sc.CustomVMBuildScript, VMBinaryPath: subnetVMBinaryPath, + GoVersion: constants.BuildEnvGolangVersion, }, ); err != nil { return err @@ -632,8 +684,10 @@ func RunSSHSyncSubnetData(app *application.Avalanche, host *models.Host, network blockchainID := sc.Networks[network.Name()].BlockchainID // genesis config genesisFilename := filepath.Join(app.GetNodesDir(), host.GetCloudID(), constants.GenesisFileName) - if err := host.Upload(genesisFilename, remoteconfig.GetRemoteAvalancheGenesis(), constants.SSHFileOpsTimeout); err != nil { - return fmt.Errorf("error uploading genesis config to %s: %w", remoteconfig.GetRemoteAvalancheGenesis(), err) + if utils.FileExists(genesisFilename) { + if err := host.Upload(genesisFilename, remoteconfig.GetRemoteAvalancheGenesis(), constants.SSHFileOpsTimeout); err != nil { + return fmt.Errorf("error uploading genesis config to %s: %w", remoteconfig.GetRemoteAvalancheGenesis(), err) + } } // end genesis config // subnet node config @@ -844,9 +898,19 @@ func genesisFileExists(host *models.Host) bool { return genesisFileExists } +func nodeConfigFileExists(host *models.Host) bool { + nodeConfigFileExists, _ := host.FileExists(remoteconfig.GetRemoteAvalancheNodeConfig()) + return nodeConfigFileExists +} + +func aliasConfigFileExists(host *models.Host) bool { + aliasConfigFileExists, _ := host.FileExists(remoteconfig.GetRemoteAvalancheAliasesConfig()) + return aliasConfigFileExists +} + func getAvalancheGoConfigData(host *models.Host) (map[string]interface{}, error) { // get remote node.json file - nodeJSONPath := filepath.Join(constants.CloudNodeConfigPath, constants.NodeFileName) + nodeJSONPath := filepath.Join(constants.CloudNodeConfigPath, constants.NodeConfigJSONFile) // parse node.json file nodeJSON, err := host.ReadFileBytes(nodeJSONPath, constants.SSHFileOpsTimeout) if err != nil { @@ -858,3 +922,16 @@ func getAvalancheGoConfigData(host *models.Host) (map[string]interface{}, error) } return avagoConfig, nil } + +func getAvalancheGoAliasData(host *models.Host) (map[string][]string, error) { + // parse aliases.json file + aliasesJSON, err := host.ReadFileBytes(remoteconfig.GetRemoteAvalancheAliasesConfig(), constants.SSHFileOpsTimeout) + if err != nil { + return nil, err + } + var aliases map[string][]string + if err := json.Unmarshal(aliasesJSON, &aliases); err != nil { + return nil, err + } + return aliases, nil +} diff --git a/pkg/statemachine/stateMachine.go b/pkg/statemachine/state_machine.go similarity index 100% rename from pkg/statemachine/stateMachine.go rename to pkg/statemachine/state_machine.go diff --git a/pkg/statemachine/stateMachine_test.go b/pkg/statemachine/state_machine_test.go similarity index 100% rename from pkg/statemachine/stateMachine_test.go rename to pkg/statemachine/state_machine_test.go diff --git a/pkg/subnet/deployStatus.go b/pkg/subnet/deploy_status.go similarity index 100% rename from pkg/subnet/deployStatus.go rename to pkg/subnet/deploy_status.go diff --git a/pkg/subnet/helpers.go b/pkg/subnet/helpers.go index 7bcf2cccf..49b1e0b80 100644 --- a/pkg/subnet/helpers.go +++ b/pkg/subnet/helpers.go @@ -10,7 +10,7 @@ import ( ) func GetDefaultSubnetAirdropKeyInfo(app *application.Avalanche, subnetName string) (string, string, string, error) { - keyName := utils.GetDefaultSubnetAirdropKeyName(subnetName) + keyName := utils.GetDefaultBlockchainAirdropKeyName(subnetName) keyPath := app.GetKeyPath(keyName) if utils.FileExists(keyPath) { k, err := key.LoadSoft(models.NewLocalNetwork().ID, keyPath) diff --git a/pkg/subnet/local.go b/pkg/subnet/local.go index 057aa15e9..cd6efa2af 100644 --- a/pkg/subnet/local.go +++ b/pkg/subnet/local.go @@ -20,6 +20,7 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/application" "github.com/ava-labs/avalanche-cli/pkg/binutils" "github.com/ava-labs/avalanche-cli/pkg/constants" + "github.com/ava-labs/avalanche-cli/pkg/key" "github.com/ava-labs/avalanche-cli/pkg/localnet" "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/teleporter" @@ -30,21 +31,13 @@ import ( "github.com/ava-labs/avalanche-network-runner/server" anrutils "github.com/ava-labs/avalanche-network-runner/utils" "github.com/ava-labs/avalanchego/config" - "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/utils/crypto/keychain" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/avalanchego/utils/set" "github.com/ava-labs/avalanchego/utils/storage" - "github.com/ava-labs/avalanchego/vms/components/avax" - "github.com/ava-labs/avalanchego/vms/components/verify" "github.com/ava-labs/avalanchego/vms/platformvm" - "github.com/ava-labs/avalanchego/vms/platformvm/reward" - "github.com/ava-labs/avalanchego/vms/platformvm/signer" - "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/ava-labs/avalanchego/wallet/subnet/primary" - "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" "go.uber.org/zap" ) @@ -85,8 +78,9 @@ type getGRPCClientFunc func(...binutils.GRPCClientOpOption) (client.Client, erro type setDefaultSnapshotFunc func(string, bool, string, bool) (bool, error) -type TeleporterEsp struct { - SkipDeploy bool +type ICMSpec struct { + SkipICMDeploy bool + SkipRelayerDeploy bool Version string MessengerContractAddressPath string MessengerDeployerAddressPath string @@ -95,242 +89,25 @@ type TeleporterEsp struct { } type DeployInfo struct { - SubnetID ids.ID - BlockchainID ids.ID - TeleporterMessengerAddress string - TeleporterRegistryAddress string + SubnetID ids.ID + BlockchainID ids.ID + ICMMessengerAddress string + ICMRegistryAddress string } // DeployToLocalNetwork does the heavy lifting: // * it checks the gRPC is running, if not, it starts it // * kicks off the actual deployment -func (d *LocalDeployer) DeployToLocalNetwork(chain string, genesisPath string, teleporterEsp TeleporterEsp, subnetIDStr string) (*DeployInfo, error) { +func (d *LocalDeployer) DeployToLocalNetwork( + chain string, + genesisPath string, + icmSpec ICMSpec, + subnetIDStr string, +) (*DeployInfo, error) { if err := d.StartServer(); err != nil { return nil, err } - return d.doDeploy(chain, genesisPath, teleporterEsp, subnetIDStr) -} - -func getAssetID(wallet primary.Wallet, tokenName string, tokenSymbol string, maxSupply uint64) (ids.ID, error) { - xWallet := wallet.X() - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - genesis.EWOQKey.PublicKey().Address(), - }, - } - ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultWalletCreationTimeout) - subnetAssetTx, err := xWallet.IssueCreateAssetTx( - tokenName, - tokenSymbol, - 9, // denomination for UI purposes only in explorer - map[uint32][]verify.State{ - 0: { - &secp256k1fx.TransferOutput{ - Amt: maxSupply, - OutputOwners: *owner, - }, - }, - }, - common.WithContext(ctx), - ) - defer cancel() - if err != nil { - return ids.Empty, err - } - return subnetAssetTx.ID(), nil -} - -func exportToPChain(wallet primary.Wallet, owner *secp256k1fx.OutputOwners, subnetAssetID ids.ID, maxSupply uint64) error { - xWallet := wallet.X() - ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultWalletCreationTimeout) - - _, err := xWallet.IssueExportTx( - ids.Empty, - []*avax.TransferableOutput{ - { - Asset: avax.Asset{ - ID: subnetAssetID, - }, - Out: &secp256k1fx.TransferOutput{ - Amt: maxSupply, - OutputOwners: *owner, - }, - }, - }, - common.WithContext(ctx), - ) - defer cancel() - return err -} - -func importFromXChain(wallet primary.Wallet, owner *secp256k1fx.OutputOwners) error { - xWallet := wallet.X() - pWallet := wallet.P() - xChainID := xWallet.Builder().Context().BlockchainID - ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultWalletCreationTimeout) - _, err := pWallet.IssueImportTx( - xChainID, - owner, - common.WithContext(ctx), - ) - defer cancel() - return err -} - -func IssueTransformSubnetTx( - elasticSubnetConfig models.ElasticSubnetConfig, - kc keychain.Keychain, - subnetID ids.ID, - tokenName string, - tokenSymbol string, - maxSupply uint64, -) (ids.ID, ids.ID, error) { - ctx := context.Background() - api := constants.LocalAPIEndpoint - wallet, err := primary.MakeWallet( - ctx, - &primary.WalletConfig{ - URI: api, - AVAXKeychain: kc, - EthKeychain: secp256k1fx.NewKeychain(), - PChainTxsToFetch: set.Of(subnetID), - }, - ) - if err != nil { - return ids.Empty, ids.Empty, err - } - subnetAssetID, err := getAssetID(wallet, tokenName, tokenSymbol, maxSupply) - if err != nil { - return ids.Empty, ids.Empty, err - } - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - genesis.EWOQKey.PublicKey().Address(), - }, - } - err = exportToPChain(wallet, owner, subnetAssetID, maxSupply) - if err != nil { - return ids.Empty, ids.Empty, err - } - err = importFromXChain(wallet, owner) - if err != nil { - return ids.Empty, ids.Empty, err - } - - ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultConfirmTxTimeout) - transformSubnetTx, err := wallet.P().IssueTransformSubnetTx(elasticSubnetConfig.SubnetID, subnetAssetID, - elasticSubnetConfig.InitialSupply, elasticSubnetConfig.MaxSupply, elasticSubnetConfig.MinConsumptionRate, - elasticSubnetConfig.MaxConsumptionRate, elasticSubnetConfig.MinValidatorStake, elasticSubnetConfig.MaxValidatorStake, - elasticSubnetConfig.MinStakeDuration, elasticSubnetConfig.MaxStakeDuration, elasticSubnetConfig.MinDelegationFee, - elasticSubnetConfig.MinDelegatorStake, elasticSubnetConfig.MaxValidatorWeightFactor, elasticSubnetConfig.UptimeRequirement, - common.WithContext(ctx), - ) - defer cancel() - if err != nil { - return ids.Empty, ids.Empty, err - } - return transformSubnetTx.ID(), subnetAssetID, err -} - -func IssueAddPermissionlessValidatorTx( - kc keychain.Keychain, - subnetID ids.ID, - nodeID ids.NodeID, - stakeAmount uint64, - assetID ids.ID, - startTime uint64, - endTime uint64, -) (ids.ID, error) { - ctx := context.Background() - api := constants.LocalAPIEndpoint - wallet, err := primary.MakeWallet( - ctx, - &primary.WalletConfig{ - URI: api, - AVAXKeychain: kc, - EthKeychain: secp256k1fx.NewKeychain(), - PChainTxsToFetch: set.Of(subnetID), - }, - ) - if err != nil { - return ids.Empty, err - } - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - genesis.EWOQKey.PublicKey().Address(), - }, - } - ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultConfirmTxTimeout) - tx, err := wallet.P().IssueAddPermissionlessValidatorTx( - &txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: nodeID, - Start: startTime, - End: endTime, - Wght: stakeAmount, - }, - Subnet: subnetID, - }, - &signer.Empty{}, - assetID, - owner, - owner, - reward.PercentDenominator, - common.WithContext(ctx), - ) - defer cancel() - if err != nil { - return ids.Empty, err - } - return tx.ID(), err -} - -func IssueAddPermissionlessDelegatorTx( - kc keychain.Keychain, - subnetID ids.ID, - nodeID ids.NodeID, - stakeAmount uint64, - assetID ids.ID, - startTime uint64, - endTime uint64, -) (ids.ID, error) { - ctx := context.Background() - api := constants.LocalAPIEndpoint - wallet, err := primary.MakeWallet( - ctx, - &primary.WalletConfig{ - URI: api, - AVAXKeychain: kc, - EthKeychain: secp256k1fx.NewKeychain(), - PChainTxsToFetch: set.Of(subnetID), - }, - ) - if err != nil { - return ids.Empty, err - } - ctx, cancel := context.WithTimeout(context.Background(), constants.DefaultConfirmTxTimeout) - tx, err := wallet.P().IssueAddPermissionlessDelegatorTx( - &txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: nodeID, - Start: startTime, - End: endTime, - Wght: stakeAmount, - }, - Subnet: subnetID, - }, - assetID, - &secp256k1fx.OutputOwners{}, - common.WithContext(ctx), - ) - defer cancel() - if err != nil { - return ids.Empty, err - } - return tx.ID(), err + return d.doDeploy(chain, genesisPath, icmSpec, subnetIDStr) } func (d *LocalDeployer) StartServer() error { @@ -374,7 +151,7 @@ func (d *LocalDeployer) BackendStartedHere() bool { // - deploy a new blockchain for the given VM ID, genesis, and available subnet ID // - waits completion of operation // - show status -func (d *LocalDeployer) doDeploy(chain string, genesisPath string, teleporterEsp TeleporterEsp, subnetIDStr string) (*DeployInfo, error) { +func (d *LocalDeployer) doDeploy(chain string, genesisPath string, icmSpec ICMSpec, subnetIDStr string) (*DeployInfo, error) { needsRestart, avalancheGoBinPath, err := d.SetupLocalEnv() if err != nil { return nil, err @@ -421,12 +198,15 @@ func (d *LocalDeployer) doDeploy(chain string, genesisPath string, teleporterEsp } d.app.Log.Debug("this VM will get ID", zap.String("vm-id", chainVMID.String())) - // cleanup if neeeded in the case relayer is registered to current blockchains - if err := teleporter.RelayerCleanup( - d.app.GetAWMRelayerRunPath(), - d.app.GetAWMRelayerStorageDir(), - ); err != nil { - return nil, err + if sc.RunRelayer && !icmSpec.SkipRelayerDeploy { + // relayer stop/cleanup is neeeded in the case it is registered to blockchains + // if not, network restart fails + if err := teleporter.RelayerCleanup( + d.app.GetLocalRelayerRunPath(models.Local), + d.app.GetLocalRelayerStorageDir(models.Local), + ); err != nil { + return nil, err + } } if networkBooted && needsRestart { @@ -565,10 +345,10 @@ func (d *LocalDeployer) doDeploy(chain string, genesisPath string, teleporterEsp } var ( - teleporterMessengerAddress string - teleporterRegistryAddress string + icmMessengerAddress string + icmRegistryAddress string ) - if sc.TeleporterReady && !teleporterEsp.SkipDeploy { + if sc.TeleporterReady && !icmSpec.SkipICMDeploy { network := models.NewLocalNetwork() // get relayer address relayerAddress, relayerPrivateKey, err := teleporter.GetRelayerKeyInfo(d.app.GetKeyPath(constants.AWMRelayerKeyName)) @@ -576,73 +356,68 @@ func (d *LocalDeployer) doDeploy(chain string, genesisPath string, teleporterEsp return nil, err } // relayer config file - _, relayerConfigPath, err := GetAWMRelayerConfigPath() + _, relayerConfigPath, err := GetLocalNetworkRelayerConfigPath(d.app) if err != nil { return nil, err } + if err = teleporter.CreateBaseRelayerConfigIfMissing( + relayerConfigPath, + logging.Info.LowerString(), + d.app.GetLocalRelayerStorageDir(models.Local), + constants.LocalNetworkLocalAWMRelayerMetricsPort, + network, + ); err != nil { + return nil, err + } // deploy C-Chain ux.Logger.PrintToUser("") - td := teleporter.Deployer{} - if teleporterEsp.MessengerContractAddressPath != "" { - if err := td.SetAssetsFromPaths( - teleporterEsp.MessengerContractAddressPath, - teleporterEsp.MessengerDeployerAddressPath, - teleporterEsp.MessengerDeployerTxPath, - teleporterEsp.RegistryBydecodePath, + icmd := teleporter.Deployer{} + if icmSpec.MessengerContractAddressPath != "" { + if err := icmd.SetAssetsFromPaths( + icmSpec.MessengerContractAddressPath, + icmSpec.MessengerDeployerAddressPath, + icmSpec.MessengerDeployerTxPath, + icmSpec.RegistryBydecodePath, ); err != nil { return nil, err } } else { - teleporterVersion := "" + icmVersion := "" switch { - case teleporterEsp.Version != "" && teleporterEsp.Version != "latest": - teleporterVersion = teleporterEsp.Version + case icmSpec.Version != "" && icmSpec.Version != "latest": + icmVersion = icmSpec.Version case sc.TeleporterVersion != "": - teleporterVersion = sc.TeleporterVersion + icmVersion = sc.TeleporterVersion default: - teleporterInfo, err := teleporter.GetInfo(d.app) + icmInfo, err := teleporter.GetInfo(d.app) if err != nil { return nil, err } - teleporterVersion = teleporterInfo.Version + icmVersion = icmInfo.Version } - if err := td.DownloadAssets( + if err := icmd.DownloadAssets( d.app.GetTeleporterBinDir(), - teleporterVersion, + icmVersion, ); err != nil { return nil, err } } - alreadyDeployed, cchainTeleporterMessengerAddress, cchainTeleporterRegistryAddress, err := teleporter.DeployAndFundRelayer( - d.app, - &td, - network, + cChainKey, err := key.LoadEwoq(network.ID) + if err != nil { + return nil, err + } + cchainAlreadyDeployed, cchainIcmMessengerAddress, cchainIcmRegistryAddress, err := icmd.Deploy( "c-chain", - "C", - "", + network.BlockchainEndpoint("C"), + cChainKey.PrivKeyHex(), + true, + true, ) if err != nil { return nil, err } - if !alreadyDeployed { - subnetID, blockchainID, err := utils.GetChainIDs(network.Endpoint, "C-Chain") - if err != nil { - return nil, err - } - if err = teleporter.UpdateRelayerConfig( - relayerConfigPath, - d.app.GetAWMRelayerStorageDir(), - relayerAddress, - relayerPrivateKey, - network, - subnetID, - blockchainID, - cchainTeleporterMessengerAddress, - cchainTeleporterRegistryAddress, - ); err != nil { - return nil, err - } - if err := localnet.WriteExtraLocalNetworkData(cchainTeleporterMessengerAddress, cchainTeleporterRegistryAddress); err != nil { + if !cchainAlreadyDeployed { + if err := localnet.WriteExtraLocalNetworkData(cchainIcmMessengerAddress, cchainIcmRegistryAddress); err != nil { return nil, err } } @@ -663,40 +438,81 @@ func (d *LocalDeployer) doDeploy(chain string, genesisPath string, teleporterEsp return nil, err } } - _, teleporterMessengerAddress, teleporterRegistryAddress, err = teleporter.DeployAndFundRelayer( - d.app, - &td, - network, - chain, - blockchainID, - teleporterKeyName, - ) + blockchainKey, err := key.LoadSoft(network.ID, d.app.GetKeyPath(teleporterKeyName)) if err != nil { return nil, err } - if err = teleporter.UpdateRelayerConfig( - relayerConfigPath, - d.app.GetAWMRelayerStorageDir(), - relayerAddress, - relayerPrivateKey, - network, - subnetID, - blockchainID, - teleporterMessengerAddress, - teleporterRegistryAddress, - ); err != nil { + _, icmMessengerAddress, icmRegistryAddress, err = icmd.Deploy( + chain, + network.BlockchainEndpoint(blockchainID), + blockchainKey.PrivKeyHex(), + true, + true, + ) + if err != nil { return nil, err } - if sc.RunRelayer { + if sc.RunRelayer && !icmSpec.SkipRelayerDeploy { + if !cchainAlreadyDeployed { + if err := teleporter.FundRelayer( + network.BlockchainEndpoint("C"), + cChainKey.PrivKeyHex(), + relayerAddress, + ); err != nil { + return nil, err + } + cchainSubnetID, cchainBlockchainID, err := utils.GetChainIDs(network.Endpoint, "C-Chain") + if err != nil { + return nil, err + } + if err = teleporter.AddSourceAndDestinationToRelayerConfig( + relayerConfigPath, + network.BlockchainEndpoint(cchainBlockchainID), + network.BlockchainWSEndpoint(cchainBlockchainID), + cchainSubnetID, + cchainBlockchainID, + cchainIcmRegistryAddress, + cchainIcmMessengerAddress, + relayerAddress, + relayerPrivateKey, + ); err != nil { + return nil, err + } + } + if err := teleporter.FundRelayer( + network.BlockchainEndpoint(blockchainID), + blockchainKey.PrivKeyHex(), + relayerAddress, + ); err != nil { + return nil, err + } + if err = teleporter.AddSourceAndDestinationToRelayerConfig( + relayerConfigPath, + network.BlockchainEndpoint(blockchainID), + network.BlockchainWSEndpoint(blockchainID), + subnetID, + blockchainID, + icmRegistryAddress, + icmMessengerAddress, + relayerAddress, + relayerPrivateKey, + ); err != nil { + return nil, err + } ux.Logger.PrintToUser("") // start relayer if err := teleporter.DeployRelayer( + "latest", d.app.GetAWMRelayerBinDir(), relayerConfigPath, - d.app.GetAWMRelayerLogPath(), - d.app.GetAWMRelayerRunPath(), - d.app.GetAWMRelayerStorageDir(), + d.app.GetLocalRelayerLogPath(models.Local), + d.app.GetLocalRelayerRunPath(models.Local), + d.app.GetLocalRelayerStorageDir(models.Local), ); err != nil { + logPath := d.app.GetLocalRelayerLogPath(models.Local) + if bs, err := os.ReadFile(logPath); err == nil { + ux.Logger.PrintToUser(string(bs)) + } return nil, err } } @@ -715,10 +531,10 @@ func (d *LocalDeployer) doDeploy(chain string, genesisPath string, teleporterEsp } } return &DeployInfo{ - SubnetID: subnetID, - BlockchainID: blockchainID, - TeleporterMessengerAddress: teleporterMessengerAddress, - TeleporterRegistryAddress: teleporterRegistryAddress, + SubnetID: subnetID, + BlockchainID: blockchainID, + ICMMessengerAddress: icmMessengerAddress, + ICMRegistryAddress: icmRegistryAddress, }, nil } @@ -853,30 +669,42 @@ func (d *LocalDeployer) removeInstalledPlugin( return d.binaryDownloader.RemoveVM(vmID.String()) } -func getSnapshotLocs(isSingleNode bool, isPreCortina17 bool) (string, string, string, string) { +func getSnapshotLocs(isSingleNode bool, isPreCortina17 bool, isPreDurango11 bool) (string, string, string, string) { bootstrapSnapshotArchiveName := "" url := "" shaSumURL := "" pathInShaSum := "" if isSingleNode { - if isPreCortina17 { + switch { + case isPreCortina17: bootstrapSnapshotArchiveName = constants.BootstrapSnapshotSingleNodePreCortina17ArchiveName url = constants.BootstrapSnapshotSingleNodePreCortina17URL shaSumURL = constants.BootstrapSnapshotSingleNodePreCortina17SHA256URL pathInShaSum = constants.BootstrapSnapshotSingleNodePreCortina17LocalPath - } else { + case isPreDurango11: + bootstrapSnapshotArchiveName = constants.BootstrapSnapshotSingleNodePreDurango11ArchiveName + url = constants.BootstrapSnapshotSingleNodePreDurango11URL + shaSumURL = constants.BootstrapSnapshotSingleNodePreDurango11SHA256URL + pathInShaSum = constants.BootstrapSnapshotSingleNodePreDurango11LocalPath + default: bootstrapSnapshotArchiveName = constants.BootstrapSnapshotSingleNodeArchiveName url = constants.BootstrapSnapshotSingleNodeURL shaSumURL = constants.BootstrapSnapshotSingleNodeSHA256URL pathInShaSum = constants.BootstrapSnapshotSingleNodeLocalPath } } else { - if isPreCortina17 { + switch { + case isPreCortina17: bootstrapSnapshotArchiveName = constants.BootstrapSnapshotPreCortina17ArchiveName url = constants.BootstrapSnapshotPreCortina17URL shaSumURL = constants.BootstrapSnapshotPreCortina17SHA256URL pathInShaSum = constants.BootstrapSnapshotPreCortina17LocalPath - } else { + case isPreDurango11: + bootstrapSnapshotArchiveName = constants.BootstrapSnapshotPreDurango11ArchiveName + url = constants.BootstrapSnapshotPreDurango11URL + shaSumURL = constants.BootstrapSnapshotPreDurango11SHA256URL + pathInShaSum = constants.BootstrapSnapshotPreDurango11LocalPath + default: bootstrapSnapshotArchiveName = constants.BootstrapSnapshotArchiveName url = constants.BootstrapSnapshotURL shaSumURL = constants.BootstrapSnapshotSHA256URL @@ -886,8 +714,8 @@ func getSnapshotLocs(isSingleNode bool, isPreCortina17 bool) (string, string, st return bootstrapSnapshotArchiveName, url, shaSumURL, pathInShaSum } -func getExpectedDefaultSnapshotSHA256Sum(isSingleNode bool, isPreCortina17 bool) (string, error) { - _, _, url, path := getSnapshotLocs(isSingleNode, isPreCortina17) +func getExpectedDefaultSnapshotSHA256Sum(isSingleNode bool, isPreCortina17 bool, isPreDurango11 bool) (string, error) { + _, _, url, path := getSnapshotLocs(isSingleNode, isPreCortina17, isPreDurango11) resp, err := http.Get(url) if err != nil { return "", fmt.Errorf("failed downloading sha256 sums: %w", err) @@ -910,11 +738,15 @@ func getExpectedDefaultSnapshotSHA256Sum(isSingleNode bool, isPreCortina17 bool) // Initialize default snapshot with bootstrap snapshot archive // If force flag is set to true, overwrite the default snapshot if it exists func SetDefaultSnapshot(snapshotsDir string, resetCurrentSnapshot bool, avagoVersion string, isSingleNode bool) (bool, error) { - var isPreCortina17 bool + var ( + isPreCortina17 bool + isPreDurango11 bool + ) if avagoVersion != "" { isPreCortina17 = semver.Compare(avagoVersion, constants.Cortina17Version) < 0 + isPreDurango11 = semver.Compare(avagoVersion, constants.Durango11Version) < 0 } - bootstrapSnapshotArchiveName, url, _, _ := getSnapshotLocs(isSingleNode, isPreCortina17) + bootstrapSnapshotArchiveName, url, _, _ := getSnapshotLocs(isSingleNode, isPreCortina17, isPreDurango11) currentBootstrapNamePath := filepath.Join(snapshotsDir, constants.CurrentBootstrapNamePath) exists, err := storage.FileExists(currentBootstrapNamePath) if err != nil { @@ -949,7 +781,7 @@ func SetDefaultSnapshot(snapshotsDir string, resetCurrentSnapshot bool, avagoVer if err != nil { return false, err } - expectedSum, err := getExpectedDefaultSnapshotSHA256Sum(isSingleNode, isPreCortina17) + expectedSum, err := getExpectedDefaultSnapshotSHA256Sum(isSingleNode, isPreCortina17, isPreDurango11) if err != nil { ux.Logger.PrintToUser("Warning: failure verifying that the local snapshot is the latest one: %s", err) } else if gotSum != expectedSum { @@ -1079,10 +911,10 @@ func IssueRemoveSubnetValidatorTx(kc keychain.Keychain, subnetID ids.ID, nodeID wallet, err := primary.MakeWallet( ctx, &primary.WalletConfig{ - URI: api, - AVAXKeychain: kc, - EthKeychain: secp256k1fx.NewKeychain(), - PChainTxsToFetch: set.Of(subnetID), + URI: api, + AVAXKeychain: kc, + EthKeychain: secp256k1fx.NewKeychain(), + SubnetIDs: []ids.ID{subnetID}, }, ) if err != nil { @@ -1120,11 +952,11 @@ func CheckNodeIsInSubnetValidators(subnetID ids.ID, nodeID string) (bool, error) return false, nil } -func GetAWMRelayerConfigPath() (bool, string, error) { +func GetLocalNetworkRelayerConfigPath(app *application.Avalanche) (bool, string, error) { clusterInfo, err := localnet.GetClusterInfo() if err != nil { return false, "", err } - relayerConfigPath := filepath.Join(clusterInfo.GetRootDataDir(), constants.AWMRelayerConfigFilename) + relayerConfigPath := app.GetLocalRelayerConfigPath(models.Local, clusterInfo.GetRootDataDir()) return utils.FileExists(relayerConfigPath), relayerConfigPath, nil } diff --git a/pkg/subnet/local_test.go b/pkg/subnet/local_test.go index 7ef753cdb..e136286ba 100644 --- a/pkg/subnet/local_test.go +++ b/pkg/subnet/local_test.go @@ -140,10 +140,10 @@ func TestDeployToLocal(t *testing.T) { err = os.WriteFile(testSidecar.Name(), []byte(sidecar), constants.DefaultPerms755) require.NoError(err) // test actual deploy - teleporterEsp := TeleporterEsp{ - SkipDeploy: true, + icmSpec := ICMSpec{ + SkipICMDeploy: true, } - deployInfo, err := testDeployer.DeployToLocalNetwork(testChainName, testGenesis.Name(), teleporterEsp, "") + deployInfo, err := testDeployer.DeployToLocalNetwork(testChainName, testGenesis.Name(), icmSpec, "") require.NoError(err) require.Equal(testSubnetID2, deployInfo.SubnetID.String()) require.Equal(testBlockChainID2, deployInfo.BlockchainID.String()) diff --git a/pkg/subnet/public.go b/pkg/subnet/public.go index 13f80debe..e82de963e 100644 --- a/pkg/subnet/public.go +++ b/pkg/subnet/public.go @@ -63,13 +63,12 @@ func (d *PublicDeployer) AddValidator( controlKeys []string, subnetAuthKeysStrs []string, subnetID ids.ID, - transferSubnetOwnershipTxID ids.ID, nodeID ids.NodeID, weight uint64, startTime time.Time, duration time.Duration, ) (bool, *txs.Tx, []string, error) { - wallet, err := d.loadCacheWallet(subnetID, transferSubnetOwnershipTxID) + wallet, err := d.loadCacheWallet(subnetID) if err != nil { return false, nil, nil, err } @@ -122,11 +121,10 @@ func (d *PublicDeployer) TransferSubnetOwnership( controlKeys []string, subnetAuthKeysStrs []string, subnetID ids.ID, - transferSubnetOwnershipTxID ids.ID, newControlKeys []string, newThreshold uint32, ) (bool, *txs.Tx, []string, error) { - wallet, err := d.loadCacheWallet(subnetID, transferSubnetOwnershipTxID) + wallet, err := d.loadCacheWallet(subnetID) if err != nil { return false, nil, nil, err } @@ -213,94 +211,6 @@ func (d *PublicDeployer) CreateAssetTx( return tx.ID(), err } -func (d *PublicDeployer) ExportToPChainTx( - subnetAssetID ids.ID, - owner *secp256k1fx.OutputOwners, - assetAmount uint64, -) (ids.ID, error) { - wallet, err := d.loadWallet() - if err != nil { - return ids.Empty, err - } - txID, err := IssueXToPExportTx( - wallet, - d.kc.UsesLedger, - d.kc.HasOnlyOneKey(), - subnetAssetID, - assetAmount, - owner, - ) - if err != nil { - return txID, err - } - ux.Logger.PrintToUser("Export to P-Chain Transaction successful, transaction ID: %s", txID) - ux.Logger.PrintToUser("Now importing asset from X-Chain ...") - return txID, nil -} - -func (d *PublicDeployer) ImportFromXChain( - owner *secp256k1fx.OutputOwners, -) (ids.ID, error) { - wallet, err := d.loadWallet() - if err != nil { - return ids.Empty, err - } - txID, err := IssuePFromXImportTx( - wallet, - d.kc.UsesLedger, - d.kc.HasOnlyOneKey(), - owner, - ) - if err != nil { - return txID, err - } - ux.Logger.PrintToUser("Import from X Chain Transaction successful, transaction ID: %s", txID) - ux.Logger.PrintToUser("Now transforming subnet into elastic subnet ...") - return txID, nil -} - -func (d *PublicDeployer) TransformSubnetTx( - controlKeys []string, - subnetAuthKeysStrs []string, - elasticSubnetConfig models.ElasticSubnetConfig, - subnetID ids.ID, - transferSubnetOwnershipTxID ids.ID, - subnetAssetID ids.ID, -) (bool, ids.ID, *txs.Tx, []string, error) { - wallet, err := d.loadCacheWallet(subnetID, transferSubnetOwnershipTxID) - if err != nil { - return false, ids.Empty, nil, nil, err - } - subnetAuthKeys, err := address.ParseToIDs(subnetAuthKeysStrs) - if err != nil { - return false, ids.Empty, nil, nil, fmt.Errorf("failure parsing subnet auth keys: %w", err) - } - - showLedgerSignatureMsg(d.kc.UsesLedger, d.kc.HasOnlyOneKey(), "Transform Subnet hash") - - tx, err := d.createTransformSubnetTX(subnetAuthKeys, elasticSubnetConfig, wallet, subnetAssetID) - if err != nil { - return false, ids.Empty, nil, nil, err - } - _, remainingSubnetAuthKeys, err := txutils.GetRemainingSigners(tx, controlKeys) - if err != nil { - return false, ids.Empty, nil, nil, err - } - isFullySigned := len(remainingSubnetAuthKeys) == 0 - - if isFullySigned { - txID, err := d.Commit(tx, true) - if err != nil { - return false, ids.Empty, nil, nil, err - } - ux.Logger.PrintToUser("Transaction successful, transaction ID: %s", txID) - return true, txID, nil, nil, nil - } - - ux.Logger.PrintToUser("Partial tx created") - return false, ids.Empty, tx, remainingSubnetAuthKeys, nil -} - // removes a subnet validator from the given [subnet] // - verifies that the wallet is one of the subnet auth keys (so as to sign the AddSubnetValidator tx) // - if operation is multisig (len(subnetAuthKeysStrs) > 1): @@ -313,10 +223,9 @@ func (d *PublicDeployer) RemoveValidator( controlKeys []string, subnetAuthKeysStrs []string, subnetID ids.ID, - transferSubnetOwnershipTxID ids.ID, nodeID ids.NodeID, ) (bool, *txs.Tx, []string, error) { - wallet, err := d.loadCacheWallet(subnetID, transferSubnetOwnershipTxID) + wallet, err := d.loadCacheWallet(subnetID) if err != nil { return false, nil, nil, err } @@ -379,27 +288,6 @@ func (d *PublicDeployer) AddPermissionlessValidator( return txID, nil } -func (d *PublicDeployer) AddPermissionlessDelegator( - subnetID ids.ID, - subnetAssetID ids.ID, - nodeID ids.NodeID, - stakeAmount uint64, - startTime uint64, - endTime uint64, - recipientAddr ids.ShortID, -) (ids.ID, error) { - wallet, err := d.loadWallet(subnetID) - if err != nil { - return ids.Empty, err - } - txID, err := d.issueAddPermissionlessDelegatorTX(recipientAddr, stakeAmount, subnetID, nodeID, subnetAssetID, startTime, endTime, wallet) - if err != nil { - return ids.Empty, err - } - ux.Logger.PrintToUser("Transaction successful, transaction ID: %s", txID) - return txID, nil -} - // - creates a subnet for [chain] using the given [controlKeys] and [threshold] as subnet authentication parameters func (d *PublicDeployer) DeploySubnet( controlKeys []string, @@ -428,13 +316,12 @@ func (d *PublicDeployer) DeployBlockchain( controlKeys []string, subnetAuthKeysStrs []string, subnetID ids.ID, - transferSubnetOwnershipTxID ids.ID, chain string, genesis []byte, ) (bool, ids.ID, *txs.Tx, []string, error) { ux.Logger.PrintToUser("Now creating blockchain...") - wallet, err := d.loadCacheWallet(subnetID, transferSubnetOwnershipTxID) + wallet, err := d.loadCacheWallet(subnetID) if err != nil { return false, ids.Empty, nil, nil, err } @@ -515,9 +402,8 @@ func (d *PublicDeployer) Sign( tx *txs.Tx, subnetAuthKeysStrs []string, subnetID ids.ID, - transferSubnetOwnershipTxID ids.ID, ) error { - wallet, err := d.loadWallet(subnetID, transferSubnetOwnershipTxID) + wallet, err := d.loadWallet(subnetID) if err != nil { return err } @@ -542,17 +428,17 @@ func (d *PublicDeployer) Sign( return nil } -func (d *PublicDeployer) loadWallet(preloadTxs ...ids.ID) (primary.Wallet, error) { +func (d *PublicDeployer) loadWallet(subnetIDs ...ids.ID) (primary.Wallet, error) { ctx := context.Background() // filter out ids.Empty txs - filteredTxs := utils.Filter(preloadTxs, func(e ids.ID) bool { return e != ids.Empty }) + filteredTxs := utils.Filter(subnetIDs, func(e ids.ID) bool { return e != ids.Empty }) wallet, err := primary.MakeWallet( ctx, &primary.WalletConfig{ - URI: d.network.Endpoint, - AVAXKeychain: d.kc.Keychain, - EthKeychain: secp256k1fx.NewKeychain(), - PChainTxsToFetch: set.Of(filteredTxs...), + URI: d.network.Endpoint, + AVAXKeychain: d.kc.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + SubnetIDs: filteredTxs, }, ) if err != nil { @@ -694,30 +580,6 @@ func (d *PublicDeployer) createRemoveValidatorTX( return &tx, nil } -func (d *PublicDeployer) createTransformSubnetTX( - subnetAuthKeys []ids.ShortID, - elasticSubnetConfig models.ElasticSubnetConfig, - wallet primary.Wallet, - assetID ids.ID, -) (*txs.Tx, error) { - options := d.getMultisigTxOptions(subnetAuthKeys) - // create tx - unsignedTx, err := wallet.P().Builder().NewTransformSubnetTx(elasticSubnetConfig.SubnetID, assetID, - elasticSubnetConfig.InitialSupply, elasticSubnetConfig.MaxSupply, elasticSubnetConfig.MinConsumptionRate, - elasticSubnetConfig.MaxConsumptionRate, elasticSubnetConfig.MinValidatorStake, elasticSubnetConfig.MaxValidatorStake, - elasticSubnetConfig.MinStakeDuration, elasticSubnetConfig.MaxStakeDuration, elasticSubnetConfig.MinDelegationFee, - elasticSubnetConfig.MinDelegatorStake, elasticSubnetConfig.MaxValidatorWeightFactor, elasticSubnetConfig.UptimeRequirement, options...) - if err != nil { - return nil, fmt.Errorf("error building tx: %w", err) - } - tx := txs.Tx{Unsigned: unsignedTx} - // sign with current wallet - if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil { - return nil, fmt.Errorf("error signing tx: %w", err) - } - return &tx, nil -} - // issueAddPermissionlessValidatorTX calls addPermissionlessValidatorTx API on P-Chain // if subnetID is empty, node nodeID is going to be added as a validator on Primary Network // if popBytes is empty, that means that we are using BLS proof generated from signer.key file @@ -803,67 +665,6 @@ func (d *PublicDeployer) issueAddPermissionlessValidatorTX( return tx.ID(), nil } -func (d *PublicDeployer) issueAddPermissionlessDelegatorTX( - recipientAddr ids.ShortID, - stakeAmount uint64, - subnetID ids.ID, - nodeID ids.NodeID, - assetID ids.ID, - startTime uint64, - endTime uint64, - wallet primary.Wallet, -) (ids.ID, error) { - options := d.getMultisigTxOptions([]ids.ShortID{}) - owner := &secp256k1fx.OutputOwners{ - Threshold: 1, - Addrs: []ids.ShortID{ - recipientAddr, - }, - } - - if d.kc.UsesLedger { - showLedgerSignatureMsg(d.kc.UsesLedger, d.kc.HasOnlyOneKey(), "Add Permissionless Delegator hash") - } - unsignedTx, err := wallet.P().Builder().NewAddPermissionlessDelegatorTx( - &txs.SubnetValidator{ - Validator: txs.Validator{ - NodeID: nodeID, - Start: startTime, - End: endTime, - Wght: stakeAmount, - }, - Subnet: subnetID, - }, - assetID, - owner, - options..., - ) - if err != nil { - return ids.Empty, fmt.Errorf("error building tx: %w", err) - } - tx := txs.Tx{Unsigned: unsignedTx} - if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil { - return ids.Empty, fmt.Errorf("error signing tx: %w", err) - } - - ctx, cancel := utils.GetAPIContext() - defer cancel() - err = wallet.P().IssueTx( - &tx, - common.WithContext(ctx), - ) - if err != nil { - if ctx.Err() != nil { - err = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), err) - } else { - err = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), err) - } - return ids.Empty, err - } - - return tx.ID(), nil -} - func (*PublicDeployer) signTx( tx *txs.Tx, wallet primary.Wallet, diff --git a/pkg/teleporter/helpers.go b/pkg/teleporter/helpers.go deleted file mode 100644 index 6566db567..000000000 --- a/pkg/teleporter/helpers.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package teleporter - -import ( - _ "embed" - "fmt" - - "github.com/ava-labs/avalanche-cli/pkg/application" - "github.com/ava-labs/avalanche-cli/pkg/key" - "github.com/ava-labs/avalanche-cli/pkg/localnet" - "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/utils" - "github.com/ava-labs/avalanchego/ids" -) - -// For the given network and chain name, return parameters commonly used in interchain apps: -// - url endpoint -// - subnet ID -// - chain ID -// - messenger address -// - registry address -// - preconfigured key for interchain -func GetSubnetParams( - app *application.Avalanche, - network models.Network, - subnetName string, - isCChain bool, -) (string, ids.ID, ids.ID, string, string, *key.SoftKey, error) { - var ( - subnetID ids.ID - chainID ids.ID - err error - teleporterMessengerAddress string - teleporterRegistryAddress string - k *key.SoftKey - endpoint string - ) - if isCChain { - subnetID = ids.Empty - chainID, err = utils.GetChainID(network.Endpoint, "C") - if err != nil { - return "", ids.Empty, ids.Empty, "", "", nil, err - } - if network.Kind == models.Local { - b, extraLocalNetworkData, err := localnet.GetExtraLocalNetworkData() - if err != nil { - return "", ids.Empty, ids.Empty, "", "", nil, err - } - if !b { - return "", ids.Empty, ids.Empty, "", "", nil, fmt.Errorf("no extra local network data available") - } - teleporterMessengerAddress = extraLocalNetworkData.CChainTeleporterMessengerAddress - teleporterRegistryAddress = extraLocalNetworkData.CChainTeleporterRegistryAddress - } else if network.ClusterName != "" { - clusterConfig, err := app.GetClusterConfig(network.ClusterName) - if err != nil { - return "", ids.Empty, ids.Empty, "", "", nil, err - } - teleporterMessengerAddress = clusterConfig.ExtraNetworkData.CChainTeleporterMessengerAddress - teleporterRegistryAddress = clusterConfig.ExtraNetworkData.CChainTeleporterRegistryAddress - } - k, err = key.LoadEwoq(network.ID) - if err != nil { - return "", ids.Empty, ids.Empty, "", "", nil, err - } - endpoint = network.CChainEndpoint() - } else { - sc, err := app.LoadSidecar(subnetName) - if err != nil { - return "", ids.Empty, ids.Empty, "", "", nil, err - } - if !sc.TeleporterReady { - return "", ids.Empty, ids.Empty, "", "", nil, fmt.Errorf("subnet %s is not enabled for teleporter", subnetName) - } - subnetID = sc.Networks[network.Name()].SubnetID - chainID = sc.Networks[network.Name()].BlockchainID - teleporterMessengerAddress = sc.Networks[network.Name()].TeleporterMessengerAddress - teleporterRegistryAddress = sc.Networks[network.Name()].TeleporterRegistryAddress - keyPath := app.GetKeyPath(sc.TeleporterKey) - k, err = key.LoadSoft(network.ID, keyPath) - if err != nil { - return "", ids.Empty, ids.Empty, "", "", nil, err - } - endpoint = network.BlockchainEndpoint(chainID.String()) - } - if chainID == ids.Empty { - return "", ids.Empty, ids.Empty, "", "", nil, fmt.Errorf("chainID for subnet %s not found on network %s", subnetName, network.Name()) - } - if teleporterMessengerAddress == "" { - return "", ids.Empty, ids.Empty, "", "", nil, fmt.Errorf("teleporter messenger address for subnet %s not found on network %s", subnetName, network.Name()) - } - return endpoint, subnetID, chainID, teleporterMessengerAddress, teleporterRegistryAddress, k, nil -} diff --git a/pkg/teleporter/relayer.go b/pkg/teleporter/relayer.go index acf71c26c..6ff0edb77 100644 --- a/pkg/teleporter/relayer.go +++ b/pkg/teleporter/relayer.go @@ -24,12 +24,12 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" - "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/awm-relayer/config" offchainregistry "github.com/ava-labs/awm-relayer/messages/off-chain-registry" ) const ( + localRelayerSetupTime = 2 * time.Second localRelayerCheckPoolTime = 100 * time.Millisecond localRelayerCheckTimeout = 3 * time.Second ) @@ -92,6 +92,7 @@ type relayerRunFile struct { } func DeployRelayer( + version string, binDir string, configPath string, logFilePath string, @@ -101,7 +102,7 @@ func DeployRelayer( if err := RelayerCleanup(runFilePath, storageDir); err != nil { return err } - binPath, err := InstallRelayer(binDir) + binPath, err := InstallRelayer(binDir, version) if err != nil { return err } @@ -124,19 +125,24 @@ func RelayerIsUp(runFilePath string) (bool, int, *os.Process, error) { if err := json.Unmarshal(bs, &rf); err != nil { return false, 0, nil, err } - proc, err := os.FindProcess(rf.Pid) + proc, err := GetProcess(rf.Pid) if err != nil { // after a reboot without network cleanup, it is expected that the file pid will exist but the process not - err := removeRelayerRunFile(runFilePath) - return false, 0, nil, err + return false, 0, nil, removeRelayerRunFile(runFilePath) + } + return true, rf.Pid, proc, nil +} + +func GetProcess(pid int) (*os.Process, error) { + proc, err := os.FindProcess(pid) + if err != nil { + return nil, err } if err := proc.Signal(syscall.Signal(0)); err != nil { - // after a reboot without network cleanup, it is expected that the file pid will exist but the process not // sometimes FindProcess returns without error, but Signal 0 will surely fail if the process doesn't exist - err := removeRelayerRunFile(runFilePath) - return false, 0, nil, err + return nil, err } - return true, rf.Pid, proc, nil + return proc, nil } func RelayerCleanup(runFilePath string, storageDir string) error { @@ -195,29 +201,31 @@ func saveRelayerRunFile(runFilePath string, pid int) error { if err != nil { return err } + if err := os.MkdirAll(filepath.Dir(runFilePath), constants.DefaultPerms755); err != nil { + return err + } if err := os.WriteFile(runFilePath, bs, constants.WriteReadReadPerms); err != nil { return fmt.Errorf("could not write awm relater run file to %s: %w", runFilePath, err) } return nil } -func InstallRelayer(binDir string) (string, error) { - downloader := application.NewDownloader() - version, err := downloader.GetLatestReleaseVersion(binutils.GetGithubLatestReleaseURL(constants.AvaLabsOrg, constants.AWMRelayerRepoName)) - if err != nil { - return "", err +func InstallRelayer(binDir, version string) (string, error) { + if version == "" || version == "latest" { + downloader := application.NewDownloader() + var err error + version, err = downloader.GetLatestReleaseVersion(binutils.GetGithubLatestReleaseURL(constants.AvaLabsOrg, constants.AWMRelayerRepoName)) + if err != nil { + return "", err + } } - ux.Logger.PrintToUser("using awm-relayer version (%s)", version) + ux.Logger.PrintToUser("Relayer version %s", version) versionBinDir := filepath.Join(binDir, version) - return installRelayer(versionBinDir, version) -} - -func installRelayer(binDir, version string) (string, error) { - binPath := filepath.Join(binDir, constants.AWMRelayerBin) + binPath := filepath.Join(versionBinDir, constants.AWMRelayerBin) if utils.IsExecutable(binPath) { return binPath, nil } - ux.Logger.PrintToUser("Installing AWM-Relayer %s", version) + ux.Logger.PrintToUser("Installing Relayer") url, err := getRelayerURL(version) if err != nil { return "", err @@ -226,19 +234,22 @@ func installRelayer(binDir, version string) (string, error) { if err != nil { return "", err } - if err := binutils.InstallArchive("tar.gz", bs, binDir); err != nil { + if err := binutils.InstallArchive("tar.gz", bs, versionBinDir); err != nil { return "", err } return binPath, nil } func executeRelayer(binPath string, configPath string, logFile string) (int, error) { + if err := os.MkdirAll(filepath.Dir(logFile), constants.DefaultPerms755); err != nil { + return 0, err + } logWriter, err := os.Create(logFile) if err != nil { return 0, err } - ux.Logger.PrintToUser("Executing AWM-Relayer...") + ux.Logger.PrintToUser("Executing Relayer") cmd := exec.Command(binPath, "--config-file", configPath) cmd.Stdout = logWriter @@ -247,7 +258,26 @@ func executeRelayer(binPath string, configPath string, logFile string) (int, err return 0, err } - return cmd.Process.Pid, nil + ch := make(chan struct{}) + go func() { + _ = cmd.Wait() + ch <- struct{}{} + }() + time.Sleep(localRelayerSetupTime) + select { + case <-ch: + return 0, fmt.Errorf("relayer process failed during setup") + default: + } + + err = waitForRelayerInitialization( + configPath, + logFile, + 0, + 0, + ) + + return cmd.Process.Pid, err } func getRelayerURL(version string) (string, error) { @@ -267,104 +297,185 @@ func getRelayerURL(version string) (string, error) { ), nil } -func UpdateRelayerConfig( - relayerConfigPath string, - relayerStorageDir string, - relayerAddress string, - relayerPrivateKey string, - network models.Network, - subnetID string, - blockchainID string, - teleporterContractAddress string, - teleporterRegistryAddress string, -) error { +func loadRelayerConfig(relayerConfigPath string) (*config.Config, error) { awmRelayerConfig := config.Config{} - if utils.FileExists(relayerConfigPath) { - bs, err := os.ReadFile(relayerConfigPath) - if err != nil { - return err - } - if err := json.Unmarshal(bs, &awmRelayerConfig); err != nil { - return err - } - } else { - awmRelayerConfig = createRelayerConfig( - logging.Info.LowerString(), - relayerStorageDir, - network.Endpoint, - ) - } - host, port, _, err := utils.GetURIHostPortAndPath(network.Endpoint) + bs, err := os.ReadFile(relayerConfigPath) if err != nil { + return nil, err + } + if err := json.Unmarshal(bs, &awmRelayerConfig); err != nil { + return nil, err + } + return &awmRelayerConfig, nil +} + +func saveRelayerConfig(relayerConfig *config.Config, relayerConfigPath string) error { + if err := os.MkdirAll(filepath.Dir(relayerConfigPath), constants.DefaultPerms755); err != nil { return err } - addChainToRelayerConfig( - &awmRelayerConfig, - host, - port, - subnetID, - blockchainID, - teleporterContractAddress, - teleporterRegistryAddress, - relayerAddress, - relayerPrivateKey, - ) - bs, err := json.MarshalIndent(awmRelayerConfig, "", " ") + bs, err := json.MarshalIndent(relayerConfig, "", " ") if err != nil { return err } - if err := os.WriteFile(relayerConfigPath, bs, constants.WriteReadReadPerms); err != nil { - return err + return os.WriteFile(relayerConfigPath, bs, constants.WriteReadReadPerms) +} + +func CreateBaseRelayerConfigIfMissing( + relayerConfigPath string, + logLevel string, + storageLocation string, + metricsPort uint16, + network models.Network, +) error { + if !utils.FileExists(relayerConfigPath) { + return CreateBaseRelayerConfig( + relayerConfigPath, + logLevel, + storageLocation, + metricsPort, + network, + ) } return nil } -func createRelayerConfig( +func CreateBaseRelayerConfig( + relayerConfigPath string, logLevel string, storageLocation string, - endpoint string, -) config.Config { - return config.Config{ + metricsPort uint16, + network models.Network, +) error { + awmRelayerConfig := &config.Config{ LogLevel: logLevel, PChainAPI: &config.APIConfig{ - BaseURL: endpoint, + BaseURL: network.Endpoint, QueryParams: map[string]string{}, }, InfoAPI: &config.APIConfig{ - BaseURL: endpoint, + BaseURL: network.Endpoint, QueryParams: map[string]string{}, }, StorageLocation: storageLocation, ProcessMissedBlocks: false, SourceBlockchains: []*config.SourceBlockchain{}, DestinationBlockchains: []*config.DestinationBlockchain{}, - MetricsPort: constants.AWMRelayerMetricsPort, + MetricsPort: metricsPort, } + return saveRelayerConfig(awmRelayerConfig, relayerConfigPath) } -func addChainToRelayerConfig( +func AddSourceAndDestinationToRelayerConfig( + relayerConfigPath string, + rpcEndpoint string, + wsEndpoint string, + subnetID string, + blockchainID string, + icmRegistryAddress string, + icmMessengerAddress string, + relayerRewardAddress string, + relayerPrivateKey string, +) error { + awmRelayerConfig, err := loadRelayerConfig(relayerConfigPath) + if err != nil { + return err + } + addSourceToRelayerConfig( + awmRelayerConfig, + rpcEndpoint, + wsEndpoint, + subnetID, + blockchainID, + icmRegistryAddress, + icmMessengerAddress, + relayerRewardAddress, + ) + addDestinationToRelayerConfig( + awmRelayerConfig, + rpcEndpoint, + subnetID, + blockchainID, + relayerPrivateKey, + ) + return saveRelayerConfig(awmRelayerConfig, relayerConfigPath) +} + +func AddSourceToRelayerConfig( + relayerConfigPath string, + rpcEndpoint string, + wsEndpoint string, + subnetID string, + blockchainID string, + icmRegistryAddress string, + icmMessengerAddress string, + relayerRewardAddress string, +) error { + awmRelayerConfig, err := loadRelayerConfig(relayerConfigPath) + if err != nil { + return err + } + addSourceToRelayerConfig( + awmRelayerConfig, + rpcEndpoint, + wsEndpoint, + subnetID, + blockchainID, + icmRegistryAddress, + icmMessengerAddress, + relayerRewardAddress, + ) + return saveRelayerConfig(awmRelayerConfig, relayerConfigPath) +} + +func AddDestinationToRelayerConfig( + relayerConfigPath string, + rpcEndpoint string, + subnetID string, + blockchainID string, + relayerPrivateKey string, +) error { + awmRelayerConfig, err := loadRelayerConfig(relayerConfigPath) + if err != nil { + return err + } + addDestinationToRelayerConfig( + awmRelayerConfig, + rpcEndpoint, + subnetID, + blockchainID, + relayerPrivateKey, + ) + return saveRelayerConfig(awmRelayerConfig, relayerConfigPath) +} + +func addSourceToRelayerConfig( relayerConfig *config.Config, - host string, - port uint32, + rpcEndpoint string, + wsEndpoint string, subnetID string, blockchainID string, - teleporterContractAddress string, - teleporterRegistryAddress string, + icmRegistryAddress string, + icmMessengerAddress string, relayerRewardAddress string, - relayerFundedAddressKey string, ) { + if wsEndpoint == "" { + wsEndpoint = strings.TrimPrefix(rpcEndpoint, "https") + wsEndpoint = strings.TrimPrefix(wsEndpoint, "http") + wsEndpoint = strings.TrimSuffix(wsEndpoint, "rpc") + wsEndpoint = fmt.Sprintf("%s%s%s", "ws", wsEndpoint, "ws") + } source := &config.SourceBlockchain{ SubnetID: subnetID, BlockchainID: blockchainID, VM: config.EVM.String(), RPCEndpoint: config.APIConfig{ - BaseURL: fmt.Sprintf("http://%s:%d/ext/bc/%s/rpc", host, port, blockchainID), + BaseURL: rpcEndpoint, }, WSEndpoint: config.APIConfig{ - BaseURL: fmt.Sprintf("ws://%s:%d/ext/bc/%s/ws", host, port, blockchainID), + BaseURL: wsEndpoint, }, MessageContracts: map[string]config.MessageProtocolConfig{ - teleporterContractAddress: { + icmMessengerAddress: { MessageFormat: config.TELEPORTER.String(), Settings: map[string]interface{}{ "reward-address": relayerRewardAddress, @@ -373,24 +484,79 @@ func addChainToRelayerConfig( offchainregistry.OffChainRegistrySourceAddress.Hex(): { MessageFormat: config.OFF_CHAIN_REGISTRY.String(), Settings: map[string]interface{}{ - "teleporter-registry-address": teleporterRegistryAddress, + "teleporter-registry-address": icmRegistryAddress, }, }, }, } + if !utils.Any(relayerConfig.SourceBlockchains, func(s *config.SourceBlockchain) bool { return s.BlockchainID == blockchainID }) { + relayerConfig.SourceBlockchains = append(relayerConfig.SourceBlockchains, source) + } +} + +func addDestinationToRelayerConfig( + relayerConfig *config.Config, + rpcEndpoint string, + subnetID string, + blockchainID string, + relayerFundedAddressKey string, +) { destination := &config.DestinationBlockchain{ SubnetID: subnetID, BlockchainID: blockchainID, VM: config.EVM.String(), RPCEndpoint: config.APIConfig{ - BaseURL: fmt.Sprintf("http://%s:%d/ext/bc/%s/rpc", host, port, blockchainID), + BaseURL: rpcEndpoint, }, AccountPrivateKey: relayerFundedAddressKey, } - if !utils.Any(relayerConfig.SourceBlockchains, func(s *config.SourceBlockchain) bool { return s.BlockchainID == blockchainID }) { - relayerConfig.SourceBlockchains = append(relayerConfig.SourceBlockchains, source) - } if !utils.Any(relayerConfig.DestinationBlockchains, func(s *config.DestinationBlockchain) bool { return s.BlockchainID == blockchainID }) { relayerConfig.DestinationBlockchains = append(relayerConfig.DestinationBlockchains, destination) } } + +func waitForRelayerInitialization( + relayerConfigPath string, + logPath string, + checkInterval time.Duration, + checkTimeout time.Duration, +) error { + config, err := loadRelayerConfig(relayerConfigPath) + if err != nil { + return err + } + sourceBlockchains := []string{} + for _, source := range config.SourceBlockchains { + sourceBlockchains = append(sourceBlockchains, source.BlockchainID) + } + if checkInterval == 0 { + checkInterval = 100 * time.Millisecond + } + if checkTimeout == 0 { + checkTimeout = 120 * time.Second + } + t0 := time.Now() + for { + bs, err := os.ReadFile(logPath) + if err != nil { + return err + } + sourcesInitialized := 0 + for _, l := range strings.Split(string(bs), "\n") { + for _, sourceBlockchain := range sourceBlockchains { + if strings.Contains(l, "Listener initialized") && strings.Contains(l, sourceBlockchain) { + sourcesInitialized++ + } + } + } + if sourcesInitialized == len(sourceBlockchains) { + break + } + elapsed := time.Since(t0) + if elapsed > checkTimeout { + return fmt.Errorf("timeout waiting for relayer initialization") + } + time.Sleep(checkInterval) + } + return nil +} diff --git a/pkg/teleporter/teleporter.go b/pkg/teleporter/teleporter.go index e8bcd488c..05f5ab288 100644 --- a/pkg/teleporter/teleporter.go +++ b/pkg/teleporter/teleporter.go @@ -452,7 +452,7 @@ func GetInfo( ti := Info{} ti.FundedAddress, _, ti.FundedBalance, err = getTeleporterKeyInfo( app, - constants.TeleporterKeyName, + constants.ICMKeyName, ) if err != nil { return nil, err diff --git a/pkg/utils/common.go b/pkg/utils/common.go index 95b1318d5..00d34c24c 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -26,6 +26,7 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanchego/api/info" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/logging" "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/txs" "github.com/ava-labs/subnet-evm/core" @@ -523,8 +524,8 @@ func GetKeyNames(keyDir string, addEwoq bool) ([]string, error) { return names, nil } -func GetDefaultSubnetAirdropKeyName(subnetName string) string { - return "subnet_" + subnetName + "_airdrop" +func GetDefaultBlockchainAirdropKeyName(blockchainName string) string { + return "subnet_" + blockchainName + "_airdrop" } // AppendSlices appends multiple slices into a single slice. @@ -567,3 +568,24 @@ func StringValue(data map[string]interface{}, key string) (string, error) { } return "", fmt.Errorf("key %s not found", key) } + +func LogLevelToEmoji(logLevel string) (string, error) { + levelEmoji := "" + level, err := logging.ToLevel(logLevel) + if err != nil { + return "", err + } + switch level { + case logging.Info: + levelEmoji = "ℹ️" + case logging.Debug: + levelEmoji = "🪲" + case logging.Warn: + levelEmoji = "⚠️" + case logging.Error: + levelEmoji = "⛔" + case logging.Fatal: + levelEmoji = "💀" + } + return levelEmoji, nil +} diff --git a/pkg/utils/e2e.go b/pkg/utils/e2e.go index b4e2d23f9..514511eda 100644 --- a/pkg/utils/e2e.go +++ b/pkg/utils/e2e.go @@ -6,11 +6,11 @@ import ( "bytes" "encoding/base64" "fmt" - "html/template" "os" "os/exec" "regexp" "strings" + "text/template" "github.com/ava-labs/avalanche-cli/pkg/constants" ) diff --git a/pkg/utils/strings.go b/pkg/utils/strings.go index f13066c50..a6ff8cb34 100644 --- a/pkg/utils/strings.go +++ b/pkg/utils/strings.go @@ -4,6 +4,8 @@ package utils import ( "fmt" + "math" + "math/big" "strconv" "strings" ) @@ -60,3 +62,12 @@ func CleanupString(s string) string { func CleanupStrings(s []string) []string { return Map(s, CleanupString) } + +// Formats an amount of base units as a string representing the amount in the given denomination. +// (i.e. An amount of 54321 with a decimals value of 3 results in the stirng "54.321") +func FormatAmount(amount *big.Int, decimals uint8) string { + amountFloat := new(big.Float).SetInt(amount) + divisor := new(big.Float).SetFloat64(math.Pow10(int(decimals))) + val := new(big.Float).Quo(amountFloat, divisor) + return fmt.Sprintf("%.*f", decimals, val) +} diff --git a/pkg/utils/strings_test.go b/pkg/utils/strings_test.go index ffbbdc542..c78025d43 100644 --- a/pkg/utils/strings_test.go +++ b/pkg/utils/strings_test.go @@ -3,6 +3,7 @@ package utils import ( + "math/big" "reflect" "testing" ) @@ -26,3 +27,46 @@ func TestSpitStringWithQuotes(t *testing.T) { t.Errorf("Expected %v, but got %v", expected1, result1) } } + +func TestFormatAmount(t *testing.T) { + testCases := []struct { + name string + amount uint64 + decimals uint8 + expected string + }{ + { + name: "greater than 1", + amount: 54321, + decimals: 3, + expected: "54.321", + }, + { + name: "less than 1", + amount: 1, + decimals: 10, + expected: "0.0000000001", + }, + { + name: "18 decimals", + amount: 9988776655443322110, + decimals: 18, + expected: "9.988776655443322110", + }, + { + name: "9 decimals, all zeros", + amount: 5000000000, + decimals: 9, + expected: "5.000000000", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := FormatAmount(new(big.Int).SetUint64(tc.amount), tc.decimals) + if result != tc.expected { + t.Errorf("Expected %s, but got %s", tc.expected, result) + } + }) + } +} diff --git a/pkg/vm/allocations.go b/pkg/vm/allocations.go deleted file mode 100644 index 4ea133e5a..000000000 --- a/pkg/vm/allocations.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package vm - -import ( - "errors" - "math/big" - - "github.com/ava-labs/avalanche-cli/pkg/application" - "github.com/ava-labs/avalanche-cli/pkg/models" - "github.com/ava-labs/avalanche-cli/pkg/utils" - "github.com/ava-labs/avalanche-cli/pkg/ux" - "github.com/ava-labs/subnet-evm/core" - "github.com/ethereum/go-ethereum/common" -) - -func addAllocation(allocations core.GenesisAlloc, address string, amount *big.Int) { - allocations[common.HexToAddress(address)] = core.GenesisAccount{ - Balance: amount, - } -} - -func getNewAllocation( - app *application.Avalanche, - subnetName string, - defaultAirdropAmount string, -) (core.GenesisAlloc, error) { - keyName := utils.GetDefaultSubnetAirdropKeyName(subnetName) - k, err := app.GetKey(keyName, models.NewLocalNetwork(), true) - if err != nil { - return core.GenesisAlloc{}, err - } - ux.Logger.PrintToUser("prefunding address %s with balance %s", k.C(), defaultAirdropAmount) - allocations := core.GenesisAlloc{} - defaultAmount, ok := new(big.Int).SetString(defaultAirdropAmount, 10) - if !ok { - return allocations, errors.New("unable to decode default allocation") - } - addAllocation(allocations, k.C(), defaultAmount) - return allocations, nil -} - -func getEwoqAllocation(defaultAirdropAmount string) (core.GenesisAlloc, error) { - allocations := core.GenesisAlloc{} - defaultAmount, ok := new(big.Int).SetString(defaultAirdropAmount, 10) - if !ok { - return allocations, errors.New("unable to decode default allocation") - } - - ux.Logger.PrintToUser("prefunding address %s with balance %s", PrefundedEwoqAddress, defaultAirdropAmount) - addAllocation(allocations, PrefundedEwoqAddress.String(), defaultAmount) - return allocations, nil -} - -func addInterchainMessagingAllocation( - allocations core.GenesisAlloc, - teleporterKeyAddress string, - teleporterKeyBalance *big.Int, -) core.GenesisAlloc { - if allocations != nil { - addAllocation(allocations, teleporterKeyAddress, teleporterKeyBalance) - } - return allocations -} - -func getAllocation( - params SubnetEVMGenesisParams, - app *application.Avalanche, - subnetName string, - defaultAirdropAmount string, - multiplier *big.Int, - useExternalGasToken bool, -) (core.GenesisAlloc, error) { - allocations := core.GenesisAlloc{} - if !useExternalGasToken { - if params.initialTokenAllocation.allocToNewKey { - return getNewAllocation(app, subnetName, defaultAirdropAmount) - } - if params.initialTokenAllocation.allocToEwoq { - return getEwoqAllocation(defaultAirdropAmount) - } - amount := new(big.Int).SetUint64(params.initialTokenAllocation.customBalance) - amount = amount.Mul(amount, multiplier) - addAllocation(allocations, params.initialTokenAllocation.customAddress.Hex(), amount) - } - return allocations, nil -} diff --git a/pkg/vm/createCustom.go b/pkg/vm/create_custom.go similarity index 100% rename from pkg/vm/createCustom.go rename to pkg/vm/create_custom.go diff --git a/pkg/vm/createEvm.go b/pkg/vm/create_evm.go similarity index 72% rename from pkg/vm/createEvm.go rename to pkg/vm/create_evm.go index 3749d99ac..b25092c3d 100644 --- a/pkg/vm/createEvm.go +++ b/pkg/vm/create_evm.go @@ -15,8 +15,8 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/models" "github.com/ava-labs/avalanche-cli/pkg/teleporter" "github.com/ava-labs/avalanche-cli/pkg/ux" + blockchainSDK "github.com/ava-labs/avalanche-cli/sdk/blockchain" "github.com/ava-labs/subnet-evm/core" - subnetevmparams "github.com/ava-labs/subnet-evm/params" "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) @@ -71,60 +71,41 @@ func CreateEvmSidecar( return &sc, nil } -func CreateEvmGenesis( - app *application.Avalanche, - subnetName string, +func CreateEVMGenesis( + blockchainName string, params SubnetEVMGenesisParams, teleporterInfo *teleporter.Info, ) ([]byte, error) { - ux.Logger.PrintToUser("creating genesis for subnet %s", subnetName) - - genesis := core.Genesis{} - genesis.Timestamp = *utils.TimeToNewUint64(time.Now()) - conf := subnetevmparams.SubnetEVMDefaultChainConfig - conf.NetworkUpgrades = subnetevmparams.NetworkUpgrades{} - - chainID := new(big.Int).SetUint64(params.chainID) - conf.ChainID = chainID - - setFeeConfig(params, conf) + ux.Logger.PrintToUser("creating genesis for blockchain %s", blockchainName) - allocations, err := getAllocation( - params, - app, - subnetName, - defaultEvmAirdropAmount, - oneAvax, - params.UseExternalGasToken, - ) - if err != nil { - return nil, err - } + feeConfig := getFeeConfig(params) + // Validity checks on the parameter settings. if params.enableTransactionPrecompile { - if !someAllowedHasBalance(params.transactionPrecompileAllowList, allocations) { + if someoneWasAllowed(params.transactionPrecompileAllowList) && + !someAllowedHasBalance(params.transactionPrecompileAllowList, params.initialTokenAllocation) { return nil, errors.New("none of the addresses in the transaction allow list precompile have any tokens allocated to them. Currently, no address can transact on the network. Allocate some funds to one of the allow list addresses to continue") } } - if (params.UseTeleporter || params.UseExternalGasToken) && !params.enableWarpPrecompile { return nil, fmt.Errorf("a teleporter enabled blockchain was requested but warp precompile is disabled") } - if (params.UseTeleporter || params.UseExternalGasToken) && teleporterInfo == nil { return nil, fmt.Errorf("a teleporter enabled blockchain was requested but no teleporter info was provided") } + // Add the teleporter deployer to the initial token allocation if necessary. if params.UseTeleporter || params.UseExternalGasToken { balance := teleporterBalance if params.UseExternalGasToken { balance = externalGasTokenBalance } - allocations = addInterchainMessagingAllocation( - allocations, - teleporterInfo.FundedAddress, - balance, - ) + if params.initialTokenAllocation == nil { + params.initialTokenAllocation = core.GenesisAlloc{} + } + params.initialTokenAllocation[common.HexToAddress(teleporterInfo.FundedAddress)] = core.GenesisAccount{ + Balance: balance, + } } if params.UseExternalGasToken { @@ -135,29 +116,35 @@ func CreateEvmGenesis( ) } - getPrecompiles(conf, params, &genesis.Timestamp) + genesisBlock0Timestamp := utils.TimeToNewUint64(time.Now()) + precompiles := getPrecompiles(params, genesisBlock0Timestamp) if params.UseTeleporter || params.UseExternalGasToken { addTeleporterAddressesToAllowLists( - conf, + &precompiles, teleporterInfo.FundedAddress, teleporterInfo.MessengerDeployerAddress, teleporterInfo.RelayerAddress, ) } - genesis.Alloc = allocations - genesis.Config = conf - genesis.Difficulty = Difficulty - genesis.GasLimit = conf.FeeConfig.GasLimit.Uint64() - - jsonBytes, err := genesis.MarshalJSON() + subnetConfig, err := blockchainSDK.New( + &blockchainSDK.SubnetParams{ + SubnetEVM: &blockchainSDK.SubnetEVMParams{ + ChainID: new(big.Int).SetUint64(params.chainID), + FeeConfig: feeConfig, + Allocation: params.initialTokenAllocation, + Precompiles: precompiles, + Timestamp: genesisBlock0Timestamp, + }, + Name: "TestSubnet", + }) if err != nil { return nil, err } var prettyJSON bytes.Buffer - err = json.Indent(&prettyJSON, jsonBytes, "", " ") + err = json.Indent(&prettyJSON, subnetConfig.Genesis, "", " ") if err != nil { return nil, err } @@ -165,6 +152,11 @@ func CreateEvmGenesis( return prettyJSON.Bytes(), nil } +func someoneWasAllowed(allowList AllowList) bool { + addrs := append(append(allowList.AdminAddresses, allowList.ManagerAddresses...), allowList.EnabledAddresses...) + return len(addrs) > 0 +} + func someAllowedHasBalance(allowList AllowList, allocations core.GenesisAlloc) bool { addrs := append(append(allowList.AdminAddresses, allowList.ManagerAddresses...), allowList.EnabledAddresses...) for _, addr := range addrs { diff --git a/pkg/vm/createEvm_test.go b/pkg/vm/create_evm_test.go similarity index 100% rename from pkg/vm/createEvm_test.go rename to pkg/vm/create_evm_test.go diff --git a/pkg/vm/evmPrompts.go b/pkg/vm/evm_prompts.go similarity index 70% rename from pkg/vm/evmPrompts.go rename to pkg/vm/evm_prompts.go index 0cc7e8439..be886b2ab 100644 --- a/pkg/vm/evmPrompts.go +++ b/pkg/vm/evm_prompts.go @@ -11,25 +11,51 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/binutils" "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/models" + "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/subnet-evm/core" "github.com/ava-labs/subnet-evm/plugin/evm" "github.com/ethereum/go-ethereum/common" + "github.com/olekukonko/tablewriter" "github.com/ava-labs/avalanchego/utils/logging" ) +type DefaultsKind uint + +const ( + NoDefaults DefaultsKind = iota + TestDefaults + ProductionDefaults +) + const ( latest = "latest" preRelease = "pre-release" explainOption = "Explain the difference" enableExternalGasTokenPrompt = false + + // Options for native token allocation in genesis configuration + allocateToNewKeyOption = "Allocate 1m tokens to a new account" + allocateToEwoqOption = "Allocate 1m to the ewoq account 0x8db...2FC (Only recommended for testing, not recommended for production)" + customAllocationOption = "Define a custom allocation (Recommended for production)" + + // Options for native minter precompile configuration + fixedSupplyOption = "No, I want the supply of the native tokens be hard-capped" + dynamicSupplyOption = "Yes, I want to be able to mint additional the native tokens (Native Minter Precompile ON)" + + // Options for modifying the initial token allocation + addAddressAllocationOption = "Add an address to the initial token allocation" + changeAddressAllocationOption = "Edit the amount of an address in the initial token allocation" + removeAddressAllocationOption = "Remove an address from the initial token allocation" + previewAddressAllocationOption = "Preview the initial token allocation" + confirmAddressAllocationOption = "Confirm and finalize the initial token allocation" ) -type InitialTokenAllocation struct { - allocToNewKey bool - allocToEwoq bool - customAddress common.Address - customBalance uint64 +var DefaultEwoqAllocation = core.GenesisAlloc{ + PrefundedEwoqAddress: { + Balance: defaultEVMAirdropAmount, + }, } type FeeConfig struct { @@ -51,7 +77,7 @@ type SubnetEVMGenesisParams struct { chainID uint64 UseTeleporter bool UseExternalGasToken bool - initialTokenAllocation InitialTokenAllocation + initialTokenAllocation core.GenesisAlloc feeConfig FeeConfig enableNativeMinterPrecompile bool nativeMinterPrecompileAllowList AllowList @@ -107,7 +133,7 @@ func PromptVMType( case explainOption: ux.Logger.PrintToUser("Virtual machines are the blueprint the defines the application-level logic of a blockchain. It determines the language and rules for writing and executing smart contracts, as well as other blockchain logic.") ux.Logger.PrintToUser("") - ux.Logger.PrintToUser("Subnet-EVM is an EVM-compatible virtual machine that supports smart contract development in Solidity. This VM is an out-of-the-box solution for Subnet deployers who want a dApp development experience that is nearly identical to Ethereum, without having to manage or create a custom virtual machine. For more information, please visit: https://github.com/ava-labs/subnet-evm") + ux.Logger.PrintToUser("Subnet-EVM is an EVM-compatible virtual machine that supports smart contract development in Solidity. This VM is an out-of-the-box solution for Blockchain deployers who want a dApp development experience that is nearly identical to Ethereum, without having to manage or create a custom virtual machine. For more information, please visit: https://github.com/ava-labs/subnet-evm") ux.Logger.PrintToUser("") ux.Logger.PrintToUser("Custom VMs are virtual machines created using SDKs such as Precompile-EVM, HyperSDK, Rust-SDK. For more information please visit: https://docs.avax.network/learn/avalanche/virtual-machines.") continue @@ -137,8 +163,9 @@ func PromptSubnetEVMGenesisParams( version string, chainID uint64, tokenSymbol string, + blockchainName string, useTeleporter *bool, - useDefaults bool, + defaultsKind DefaultsKind, useWarp bool, useExternalGasToken bool, ) (SubnetEVMGenesisParams, string, error) { @@ -146,6 +173,7 @@ func PromptSubnetEVMGenesisParams( err error params SubnetEVMGenesisParams ) + // Chain ID params.chainID = chainID if params.chainID == 0 { @@ -154,51 +182,58 @@ func PromptSubnetEVMGenesisParams( return SubnetEVMGenesisParams{}, "", err } } + // Gas Kind - params, err = promptGasTokenKind(app, useDefaults, useExternalGasToken, params) + params, err = promptGasTokenKind(app, defaultsKind, useExternalGasToken, params) if err != nil { return SubnetEVMGenesisParams{}, "", err } + // Native Gas Details if !params.UseExternalGasToken { - params, tokenSymbol, err = promptNativeGasToken(app, version, tokenSymbol, useDefaults, params) + params, tokenSymbol, err = promptNativeGasToken(app, version, tokenSymbol, blockchainName, defaultsKind, params) if err != nil { return SubnetEVMGenesisParams{}, "", err } } + // Transaction / Gas Fees - params, err = promptFeeConfig(app, version, useDefaults, params) + params, err = promptFeeConfig(app, version, defaultsKind, params) if err != nil { return SubnetEVMGenesisParams{}, "", err } + // Interoperability - params.UseTeleporter, err = PromptInteropt(app, useTeleporter, useDefaults, params.UseExternalGasToken) + params.UseTeleporter, err = PromptInterop(app, useTeleporter, defaultsKind, params.UseExternalGasToken) if err != nil { return SubnetEVMGenesisParams{}, "", err } + // Warp params.enableWarpPrecompile = useWarp if (params.UseTeleporter || params.UseExternalGasToken) && !params.enableWarpPrecompile { return SubnetEVMGenesisParams{}, "", fmt.Errorf("warp should be enabled for teleporter to work") } + // Permissioning - params, err = promptPermissioning(app, version, useDefaults, params) + params, err = promptPermissioning(app, version, defaultsKind, params) if err != nil { return SubnetEVMGenesisParams{}, "", err } + return params, tokenSymbol, nil } // prompts for wether to use a remote or native gas token func promptGasTokenKind( app *application.Avalanche, - useDefaults bool, + defaultsKind DefaultsKind, useExternalGasToken bool, params SubnetEVMGenesisParams, ) (SubnetEVMGenesisParams, error) { if useExternalGasToken { params.UseExternalGasToken = true - } else if enableExternalGasTokenPrompt && !useDefaults { + } else if enableExternalGasTokenPrompt && defaultsKind == NoDefaults { var err error nativeTokenOption := "The blockchain's native token" externalTokenOption := "A token from another blockchain" @@ -241,33 +276,207 @@ func promptGasTokenKind( // prompts for wether to use defaults to build the config func PromptDefaults( app *application.Avalanche, - useDefaults bool, -) (bool, error) { - if !useDefaults { - useDefaultsOption := "Use default values" - specifyMyValuesOption := "Don't use default values" - options := []string{useDefaultsOption, specifyMyValuesOption, explainOption} + defaultsKind DefaultsKind, +) (DefaultsKind, error) { + if defaultsKind == NoDefaults { + useTestDefaultsOption := "I want to use defaults for a test environment" + useProductionDefaultsOption := "I want to use defaults for a production environment" + specifyMyValuesOption := "I don't want to use default values" + options := []string{useTestDefaultsOption, useProductionDefaultsOption, specifyMyValuesOption, explainOption} for { option, err := app.Prompt.CaptureList( "Do you want to use default values for the Blockchain configuration?", options, ) if err != nil { - return false, err + return NoDefaults, err } switch option { - case useDefaultsOption: - useDefaults = true + case useTestDefaultsOption: + defaultsKind = TestDefaults + case useProductionDefaultsOption: + defaultsKind = ProductionDefaults case specifyMyValuesOption: - useDefaults = false + defaultsKind = NoDefaults case explainOption: - ux.Logger.PrintToUser("Subnet configuration default values:\n- Use latest Subnet-EVM release\n- Allocate 1 million tokens to a newly created key\n- Supply of the native token will be hard-capped\n- Set gas fee config as low throughput (12 mil gas per block)\n- Use constant gas prices\n- Disable further adjustments in transaction fee configuration\n- Transaction fees are burned\n- Enable interoperation with other blockchains\n- Allow any user to deploy smart contracts, send transactions, and interact with your blockchain.") + ux.Logger.PrintToUser("Blockchain configuration default values:\n- Use latest Subnet-EVM release\n- Allocate 1 million tokens to:\n - a newly created key (production)\n - ewoq - %s (test)\n- Supply of the native token will be hard-capped\n- Set gas fee config as low throughput (12 mil gas per block)\n- Use constant gas prices\n- Disable further adjustments in transaction fee configuration\n- Transaction fees are burned\n- Enable interoperation with other blockchains\n- Allow any user to deploy smart contracts, send transactions, and interact with your blockchain.\n", PrefundedEwoqAddress.Hex()) continue } break } } - return useDefaults, nil + return defaultsKind, nil +} + +func displayAllocations(alloc core.GenesisAlloc) { + header := []string{"Address", "Balance"} + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader(header) + table.SetAutoMergeCellsByColumnIndex([]int{0}) + table.SetAutoMergeCells(true) + table.SetRowLine(true) + for address, account := range alloc { + table.Append([]string{address.Hex(), utils.FormatAmount(account.Balance, 18)}) + } + table.Render() +} + +func createNewKeyAllocation(app *application.Avalanche, subnetName string) (core.GenesisAlloc, error) { + keyName := utils.GetDefaultBlockchainAirdropKeyName(subnetName) + k, err := app.GetKey(keyName, models.NewLocalNetwork(), true) + if err != nil { + return core.GenesisAlloc{}, err + } + ux.Logger.PrintToUser("prefunding address %s with balance %s", k.C(), defaultEVMAirdropAmount) + + return core.GenesisAlloc{ + common.HexToAddress(k.C()): { + Balance: defaultEVMAirdropAmount, + }, + }, nil +} + +func getNativeGasTokenAllocationConfig( + app *application.Avalanche, + subnetName string, + tokenSymbol string, +) (core.GenesisAlloc, error) { + // Get the type of initial token allocation from the user prompt. + allocOption, err := app.Prompt.CaptureList( + "How should the initial token allocation be structured?", + []string{allocateToNewKeyOption, allocateToEwoqOption, customAllocationOption}, + ) + if err != nil { + return core.GenesisAlloc{}, err + } + + // If the user chooses to allocate to a new key, generate a new key and allocate the default amount to it. + if allocOption == allocateToNewKeyOption { + return createNewKeyAllocation(app, subnetName) + } + + if allocOption == allocateToEwoqOption { + ux.Logger.PrintToUser("prefunding address %s with balance %s", PrefundedEwoqAddress, defaultEVMAirdropAmount) + return DefaultEwoqAllocation, nil + } + + if allocOption == customAllocationOption { + res := core.GenesisAlloc{} + for { + // Prompt for the action the user wants to take on the allocation list. + action, err := app.Prompt.CaptureList( + "How would you like to modify the initial token allocation?", + []string{ + addAddressAllocationOption, + changeAddressAllocationOption, + removeAddressAllocationOption, + previewAddressAllocationOption, + confirmAddressAllocationOption, + }, + ) + if err != nil { + return core.GenesisAlloc{}, err + } + + switch action { + case addAddressAllocationOption: + address, err := app.Prompt.CaptureAddress("Address to allocate to") + if err != nil { + return core.GenesisAlloc{}, err + } + + // Check if the address already has an allocation entry. + if _, ok := res[address]; ok { + ux.Logger.PrintToUser("Address already has an allocation entry. Use edit or remove to modify.") + continue + } + + balance, err := app.Prompt.CaptureUint64(fmt.Sprintf("Amount to allocate (in %s units)", tokenSymbol)) + if err != nil { + return core.GenesisAlloc{}, err + } + + res[address] = core.GenesisAccount{ + Balance: new(big.Int).Mul(new(big.Int).SetUint64(balance), OneAvax), + } + case changeAddressAllocationOption: + address, err := app.Prompt.CaptureAddress("Address to update the allocation of") + if err != nil { + return core.GenesisAlloc{}, err + } + + // Check the address has an existing allocation entry. + if _, ok := res[address]; !ok { + ux.Logger.PrintToUser("Address not found in the allocation list") + continue + } + + balance, err := app.Prompt.CaptureUint64(fmt.Sprintf("Updated amount to allocate (in %s units)", tokenSymbol)) + if err != nil { + return core.GenesisAlloc{}, err + } + res[address] = core.GenesisAccount{ + Balance: new(big.Int).Mul(new(big.Int).SetUint64(balance), OneAvax), + } + case removeAddressAllocationOption: + address, err := app.Prompt.CaptureAddress("Address to remove from the allocation list") + if err != nil { + return core.GenesisAlloc{}, err + } + + // Check the address has an existing allocation entry. + if _, ok := res[address]; !ok { + ux.Logger.PrintToUser("Address not found in the allocation list") + continue + } + + delete(res, address) + case previewAddressAllocationOption: + displayAllocations(res) + case confirmAddressAllocationOption: + displayAllocations(res) + confirm, err := app.Prompt.CaptureYesNo("Are you sure you want to finalize this allocation list?") + if err != nil { + return core.GenesisAlloc{}, err + } + if confirm { + return res, nil + } + default: + return core.GenesisAlloc{}, fmt.Errorf("invalid allocation modification option") + } + } + } + return core.GenesisAlloc{}, fmt.Errorf("invalid allocation option") +} + +func getNativeMinterPrecompileConfig(app *application.Avalanche, version string) (AllowList, bool, error) { + option, err := app.Prompt.CaptureList( + "Allow minting of new native tokens?", + []string{fixedSupplyOption, dynamicSupplyOption}, + ) + if err != nil { + return AllowList{}, false, err + } + + if option == fixedSupplyOption { + return AllowList{}, false, nil + } + + if option == dynamicSupplyOption { + for { + allowList, cancel, err := GenerateAllowList(app, "mint native tokens", version) + if err != nil { + return AllowList{}, false, err + } + if cancel { + continue + } + return allowList, true, nil + } + } + + return AllowList{}, false, fmt.Errorf("invalid option") } // prompts for token symbol, initial token allocation, and native minter precompile @@ -281,72 +490,41 @@ func promptNativeGasToken( app *application.Avalanche, version string, tokenSymbol string, - useDefaults bool, + blockchainName string, + defaultsKind DefaultsKind, params SubnetEVMGenesisParams, ) (SubnetEVMGenesisParams, string, error) { - var ( - err error - cancel bool - ) + var err error tokenSymbol, err = PromptTokenSymbol(app, tokenSymbol) if err != nil { return SubnetEVMGenesisParams{}, "", err } - if useDefaults { - params.initialTokenAllocation.allocToNewKey = true - } else { - allocateToNewKeyOption := "Allocate 1m tokens to a new account" - allocateToEwoqOption := "Allocate 1m to the ewoq account 0x8db...2FC (Only recommended for testing, not recommended for production)" - customAllocationOption := "Define a custom allocation (Recommended for production)" - options := []string{allocateToNewKeyOption, allocateToEwoqOption, customAllocationOption} - option, err := app.Prompt.CaptureList( - "How should the initial token allocation be structured?", - options, - ) - if err != nil { - return SubnetEVMGenesisParams{}, "", err - } - switch option { - case allocateToNewKeyOption: - params.initialTokenAllocation.allocToNewKey = true - case allocateToEwoqOption: - params.initialTokenAllocation.allocToEwoq = true - case customAllocationOption: - params.initialTokenAllocation.customAddress, err = app.Prompt.CaptureAddress("Address to allocate to") - if err != nil { - return SubnetEVMGenesisParams{}, "", err - } - params.initialTokenAllocation.customBalance, err = app.Prompt.CaptureUint64(fmt.Sprintf("Amount to allocate (in %s units)", tokenSymbol)) - if err != nil { - return SubnetEVMGenesisParams{}, "", err - } - } - for { - fixedSupplyOption := "No, I want the supply of the native tokens be hard-capped" - dynamicSupplyOption := "Yes, I want to be able to mint additional the native tokens (Native Minter Precompile ON)" - options = []string{fixedSupplyOption, dynamicSupplyOption} - option, err = app.Prompt.CaptureList( - "Allow minting of new native tokens?", - options, - ) - if err != nil { - return SubnetEVMGenesisParams{}, "", err - } - switch option { - case fixedSupplyOption: - case dynamicSupplyOption: - params.nativeMinterPrecompileAllowList, cancel, err = GenerateAllowList(app, "mint native tokens", version) - if err != nil { - return SubnetEVMGenesisParams{}, "", err - } - if cancel { - continue - } - params.enableNativeMinterPrecompile = true - } - break - } + + if defaultsKind == TestDefaults { + ux.Logger.PrintToUser("prefunding address %s with balance %s", PrefundedEwoqAddress, defaultEVMAirdropAmount) + params.initialTokenAllocation = DefaultEwoqAllocation + return params, tokenSymbol, nil + } + + if defaultsKind == ProductionDefaults { + params.initialTokenAllocation, err = createNewKeyAllocation(app, blockchainName) + return params, tokenSymbol, err + } + + // No defaults case. Prompt for initial token allocation and native minter precompile options. + alloc, err := getNativeGasTokenAllocationConfig(app, blockchainName, tokenSymbol) + if err != nil { + return SubnetEVMGenesisParams{}, "", err + } + + allowList, nativeMinterEnabled, err := getNativeMinterPrecompileConfig(app, version) + if err != nil { + return SubnetEVMGenesisParams{}, "", err } + + params.initialTokenAllocation = alloc + params.enableNativeMinterPrecompile = nativeMinterEnabled + params.nativeMinterPrecompileAllowList = allowList return params, tokenSymbol, nil } @@ -360,10 +538,10 @@ func promptNativeGasToken( func promptFeeConfig( app *application.Avalanche, version string, - useDefaults bool, + defaultsKind DefaultsKind, params SubnetEVMGenesisParams, ) (SubnetEVMGenesisParams, error) { - if useDefaults { + if defaultsKind != NoDefaults { params.feeConfig.lowThroughput = true params.feeConfig.useDynamicFees = false return params, nil @@ -527,16 +705,16 @@ func promptFeeConfig( // is useDefaults is true, will enable teleporter // if using external gas token, will assume teleporter to be enabled // if other cases, prompts the user for wether to enable teleporter -func PromptInteropt( +func PromptInterop( app *application.Avalanche, useTeleporterFlag *bool, - useDefaults bool, + defaultsKind DefaultsKind, useExternalGasToken bool, ) (bool, error) { switch { case useTeleporterFlag != nil: return *useTeleporterFlag, nil - case useDefaults: + case defaultsKind != NoDefaults: return true, nil case useExternalGasToken: return true, nil @@ -568,10 +746,10 @@ func PromptInteropt( func promptPermissioning( app *application.Avalanche, version string, - useDefaults bool, + defaultsKind DefaultsKind, params SubnetEVMGenesisParams, ) (SubnetEVMGenesisParams, error) { - if useDefaults { + if defaultsKind != NoDefaults { return params, nil } var cancel bool diff --git a/pkg/vm/evmSettings.go b/pkg/vm/evm_settings.go similarity index 53% rename from pkg/vm/evmSettings.go rename to pkg/vm/evm_settings.go index f930585bf..f48bb0b28 100644 --- a/pkg/vm/evmSettings.go +++ b/pkg/vm/evm_settings.go @@ -6,18 +6,10 @@ package vm import ( "math/big" - "github.com/ava-labs/subnet-evm/commontype" "github.com/ethereum/go-ethereum/common" ) -const ( - defaultEvmAirdropAmount = "1000000000000000000000000" - goBackMsg = "Go back to previous step" -) - var ( - Difficulty = big.NewInt(0) - // current avacloud settings LowGasLimit = big.NewInt(12_000_000) MediumGasLimit = big.NewInt(15_000_000) // C-Chain value @@ -28,20 +20,9 @@ var ( NoDynamicFeesGasLimitToTargetGasFactor = big.NewInt(5) - // This is the current c-chain gas config - StarterFeeConfig = commontype.FeeConfig{ - GasLimit: big.NewInt(15_000_000), - MinBaseFee: big.NewInt(25_000_000_000), - TargetGas: big.NewInt(15_000_000), - BaseFeeChangeDenominator: big.NewInt(36), - MinBlockGasCost: big.NewInt(0), - MaxBlockGasCost: big.NewInt(1_000_000), - TargetBlockRate: 2, - BlockGasCostStep: big.NewInt(200_000), - } - PrefundedEwoqAddress = common.HexToAddress("0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC") PrefundedEwoqPrivate = "56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027" - oneAvax = new(big.Int).SetUint64(1000000000000000000) + OneAvax = new(big.Int).SetUint64(1000000000000000000) + defaultEVMAirdropAmount = new(big.Int).Exp(big.NewInt(10), big.NewInt(24), nil) // 10^24 ) diff --git a/pkg/vm/fees.go b/pkg/vm/fees.go index 5598af492..8bdea6ec6 100644 --- a/pkg/vm/fees.go +++ b/pkg/vm/fees.go @@ -6,46 +6,44 @@ package vm import ( "math/big" + "github.com/ava-labs/avalanche-cli/sdk/vm" "github.com/ava-labs/subnet-evm/commontype" - "github.com/ava-labs/subnet-evm/params" ) func SetStandardGas( - config *params.ChainConfig, + feeConfig *commontype.FeeConfig, gasLimit *big.Int, targetGas *big.Int, useDynamicFees bool, ) { - config.FeeConfig.GasLimit = gasLimit - config.FeeConfig.TargetGas = targetGas + feeConfig.GasLimit = gasLimit + feeConfig.TargetGas = targetGas if !useDynamicFees { - config.FeeConfig.TargetGas = config.FeeConfig.TargetGas.Mul(config.FeeConfig.GasLimit, NoDynamicFeesGasLimitToTargetGasFactor) + feeConfig.TargetGas = feeConfig.TargetGas.Mul(feeConfig.GasLimit, NoDynamicFeesGasLimitToTargetGasFactor) } } -func setFeeConfig( +func getFeeConfig( params SubnetEVMGenesisParams, - config *params.ChainConfig, -) { - config.FeeConfig = StarterFeeConfig - +) commontype.FeeConfig { + feeConfig := vm.StarterFeeConfig switch { case params.feeConfig.lowThroughput: - SetStandardGas(config, LowGasLimit, LowTargetGas, params.feeConfig.useDynamicFees) + SetStandardGas(&feeConfig, LowGasLimit, LowTargetGas, params.feeConfig.useDynamicFees) case params.feeConfig.mediumThroughput: - SetStandardGas(config, MediumGasLimit, MediumTargetGas, params.feeConfig.useDynamicFees) + SetStandardGas(&feeConfig, MediumGasLimit, MediumTargetGas, params.feeConfig.useDynamicFees) case params.feeConfig.highThroughput: - SetStandardGas(config, HighGasLimit, HighTargetGas, params.feeConfig.useDynamicFees) + SetStandardGas(&feeConfig, HighGasLimit, HighTargetGas, params.feeConfig.useDynamicFees) default: - setCustomFeeConfig(params, config) + feeConfig = getCustomFeeConfig(params) } + return feeConfig } -func setCustomFeeConfig( +func getCustomFeeConfig( params SubnetEVMGenesisParams, - config *params.ChainConfig, -) { - config.FeeConfig = commontype.FeeConfig{ +) commontype.FeeConfig { + return commontype.FeeConfig{ GasLimit: params.feeConfig.gasLimit, TargetBlockRate: params.feeConfig.blockRate.Uint64(), MinBaseFee: params.feeConfig.minBaseFee, diff --git a/pkg/vm/precompiles.go b/pkg/vm/precompiles.go index ea317077c..ee32c5a64 100644 --- a/pkg/vm/precompiles.go +++ b/pkg/vm/precompiles.go @@ -14,99 +14,100 @@ import ( "github.com/ava-labs/subnet-evm/precompile/contracts/txallowlist" "github.com/ava-labs/subnet-evm/precompile/contracts/warp" "github.com/ava-labs/subnet-evm/precompile/precompileconfig" - subnetevmutils "github.com/ava-labs/subnet-evm/utils" "github.com/ethereum/go-ethereum/common" ) func configureContractDeployerAllowList( params SubnetEVMGenesisParams, + timestamp *uint64, ) deployerallowlist.Config { - config := deployerallowlist.Config{} - config.AllowListConfig = allowlist.AllowListConfig{ - AdminAddresses: params.contractDeployerPrecompileAllowList.AdminAddresses, - ManagerAddresses: params.contractDeployerPrecompileAllowList.ManagerAddresses, - EnabledAddresses: params.contractDeployerPrecompileAllowList.EnabledAddresses, + return deployerallowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ + AdminAddresses: params.contractDeployerPrecompileAllowList.AdminAddresses, + ManagerAddresses: params.contractDeployerPrecompileAllowList.ManagerAddresses, + EnabledAddresses: params.contractDeployerPrecompileAllowList.EnabledAddresses, + }, + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: timestamp, + }, } - config.Upgrade = precompileconfig.Upgrade{ - BlockTimestamp: subnetevmutils.NewUint64(0), - } - return config } func configureTransactionAllowList( params SubnetEVMGenesisParams, + timestamp *uint64, ) txallowlist.Config { - config := txallowlist.Config{} - config.AllowListConfig = allowlist.AllowListConfig{ - AdminAddresses: params.transactionPrecompileAllowList.AdminAddresses, - ManagerAddresses: params.transactionPrecompileAllowList.ManagerAddresses, - EnabledAddresses: params.transactionPrecompileAllowList.EnabledAddresses, - } - config.Upgrade = precompileconfig.Upgrade{ - BlockTimestamp: subnetevmutils.NewUint64(0), + return txallowlist.Config{ + AllowListConfig: allowlist.AllowListConfig{ + AdminAddresses: params.transactionPrecompileAllowList.AdminAddresses, + ManagerAddresses: params.transactionPrecompileAllowList.ManagerAddresses, + EnabledAddresses: params.transactionPrecompileAllowList.EnabledAddresses, + }, + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: timestamp, + }, } - return config } func configureNativeMinter( params SubnetEVMGenesisParams, + timestamp *uint64, ) nativeminter.Config { - config := nativeminter.Config{} - config.AllowListConfig = allowlist.AllowListConfig{ - AdminAddresses: params.nativeMinterPrecompileAllowList.AdminAddresses, - ManagerAddresses: params.nativeMinterPrecompileAllowList.ManagerAddresses, - EnabledAddresses: params.nativeMinterPrecompileAllowList.EnabledAddresses, + return nativeminter.Config{ + AllowListConfig: allowlist.AllowListConfig{ + AdminAddresses: params.nativeMinterPrecompileAllowList.AdminAddresses, + ManagerAddresses: params.nativeMinterPrecompileAllowList.ManagerAddresses, + EnabledAddresses: params.nativeMinterPrecompileAllowList.EnabledAddresses, + }, + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: timestamp, + }, } - config.Upgrade = precompileconfig.Upgrade{ - BlockTimestamp: subnetevmutils.NewUint64(0), - } - return config } func configureFeeManager( params SubnetEVMGenesisParams, + timestamp *uint64, ) feemanager.Config { - config := feemanager.Config{} - config.AllowListConfig = allowlist.AllowListConfig{ - AdminAddresses: params.feeManagerPrecompileAllowList.AdminAddresses, - ManagerAddresses: params.feeManagerPrecompileAllowList.ManagerAddresses, - EnabledAddresses: params.feeManagerPrecompileAllowList.EnabledAddresses, - } - config.Upgrade = precompileconfig.Upgrade{ - BlockTimestamp: subnetevmutils.NewUint64(0), + return feemanager.Config{ + AllowListConfig: allowlist.AllowListConfig{ + AdminAddresses: params.feeManagerPrecompileAllowList.AdminAddresses, + ManagerAddresses: params.feeManagerPrecompileAllowList.ManagerAddresses, + EnabledAddresses: params.feeManagerPrecompileAllowList.EnabledAddresses, + }, + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: timestamp, + }, } - return config } func configureRewardManager( params SubnetEVMGenesisParams, + timestamp *uint64, ) rewardmanager.Config { - config := rewardmanager.Config{} - config.AllowListConfig = allowlist.AllowListConfig{ - AdminAddresses: params.rewardManagerPrecompileAllowList.AdminAddresses, - ManagerAddresses: params.rewardManagerPrecompileAllowList.ManagerAddresses, - EnabledAddresses: params.rewardManagerPrecompileAllowList.EnabledAddresses, + return rewardmanager.Config{ + AllowListConfig: allowlist.AllowListConfig{ + AdminAddresses: params.rewardManagerPrecompileAllowList.AdminAddresses, + ManagerAddresses: params.rewardManagerPrecompileAllowList.ManagerAddresses, + EnabledAddresses: params.rewardManagerPrecompileAllowList.EnabledAddresses, + }, + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: timestamp, + }, } - config.Upgrade = precompileconfig.Upgrade{ - BlockTimestamp: subnetevmutils.NewUint64(0), - } - return config } func configureWarp(timestamp *uint64) warp.Config { - config := warp.Config{ + return warp.Config{ QuorumNumerator: warp.WarpDefaultQuorumNumerator, + Upgrade: precompileconfig.Upgrade{ + BlockTimestamp: timestamp, + }, } - config.Upgrade = precompileconfig.Upgrade{ - BlockTimestamp: timestamp, - } - return config } -// adds teleporter-related addresses (main funded key, messenger deploy key, relayer key) -// to the allow list of relevant enabled precompiles func addTeleporterAddressesToAllowLists( - config *params.ChainConfig, + precompile *params.Precompiles, teleporterAddress string, teleporterMessengerDeployerAddress string, relayerAddress string, @@ -115,7 +116,8 @@ func addTeleporterAddressesToAllowLists( // teleporterAddress funds the other two and also deploys the registry // teleporterMessengerDeployerAddress deploys the messenger // relayerAddress is used by the relayer to send txs to the target chain - precompileConfig := config.GenesisPrecompiles[txallowlist.ConfigKey] + currentPrecompile := *precompile + precompileConfig := currentPrecompile[txallowlist.ConfigKey] if precompileConfig != nil { txAllowListConfig := precompileConfig.(*txallowlist.Config) for _, address := range []string{teleporterAddress, teleporterMessengerDeployerAddress, relayerAddress} { @@ -128,7 +130,7 @@ func addTeleporterAddressesToAllowLists( // contract deploy allow list: // teleporterAddress deploys the registry // teleporterMessengerDeployerAddress deploys the messenger - precompileConfig = config.GenesisPrecompiles[deployerallowlist.ConfigKey] + precompileConfig = currentPrecompile[deployerallowlist.ConfigKey] if precompileConfig != nil { deployerAllowListConfig := precompileConfig.(*deployerallowlist.Config) for _, address := range []string{teleporterAddress, teleporterMessengerDeployerAddress} { @@ -176,34 +178,35 @@ func addAddressToAllowed( } func getPrecompiles( - config *params.ChainConfig, - params SubnetEVMGenesisParams, + subnetEVMGenesisParams SubnetEVMGenesisParams, genesisTimestamp *uint64, -) { - if params.enableWarpPrecompile { +) params.Precompiles { + precompiles := make(params.Precompiles) + if subnetEVMGenesisParams.enableWarpPrecompile { warpConfig := configureWarp(genesisTimestamp) - config.GenesisPrecompiles[warp.ConfigKey] = &warpConfig + precompiles[warp.ConfigKey] = &warpConfig } - if params.enableNativeMinterPrecompile { - mintConfig := configureNativeMinter(params) - config.GenesisPrecompiles[nativeminter.ConfigKey] = &mintConfig + if subnetEVMGenesisParams.enableNativeMinterPrecompile { + mintConfig := configureNativeMinter(subnetEVMGenesisParams, genesisTimestamp) + precompiles[nativeminter.ConfigKey] = &mintConfig } - if params.enableContractDeployerPrecompile { - contractConfig := configureContractDeployerAllowList(params) - config.GenesisPrecompiles[deployerallowlist.ConfigKey] = &contractConfig + if subnetEVMGenesisParams.enableContractDeployerPrecompile { + contractConfig := configureContractDeployerAllowList(subnetEVMGenesisParams, genesisTimestamp) + precompiles[deployerallowlist.ConfigKey] = &contractConfig } - if params.enableTransactionPrecompile { - txConfig := configureTransactionAllowList(params) - config.GenesisPrecompiles[txallowlist.ConfigKey] = &txConfig + if subnetEVMGenesisParams.enableTransactionPrecompile { + txConfig := configureTransactionAllowList(subnetEVMGenesisParams, genesisTimestamp) + precompiles[txallowlist.ConfigKey] = &txConfig } - if params.enableFeeManagerPrecompile { - feeConfig := configureFeeManager(params) - config.GenesisPrecompiles[feemanager.ConfigKey] = &feeConfig + if subnetEVMGenesisParams.enableFeeManagerPrecompile { + feeConfig := configureFeeManager(subnetEVMGenesisParams, genesisTimestamp) + precompiles[feemanager.ConfigKey] = &feeConfig } - if params.enableRewardManagerPrecompile { - rewardManagerConfig := configureRewardManager(params) - config.GenesisPrecompiles[rewardmanager.ConfigKey] = &rewardManagerConfig + if subnetEVMGenesisParams.enableRewardManagerPrecompile { + rewardManagerConfig := configureRewardManager(subnetEVMGenesisParams, genesisTimestamp) + precompiles[rewardmanager.ConfigKey] = &rewardManagerConfig } + return precompiles } diff --git a/sdk/blockchain/blockchain.go b/sdk/blockchain/blockchain.go new file mode 100644 index 000000000..64324a1c3 --- /dev/null +++ b/sdk/blockchain/blockchain.go @@ -0,0 +1,308 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blockchain + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "math/big" + "os" + "time" + + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + + "github.com/ava-labs/avalanche-cli/sdk/multisig" + utilsSDK "github.com/ava-labs/avalanche-cli/sdk/utils" + "github.com/ava-labs/avalanche-cli/sdk/wallet" + + "github.com/ava-labs/avalanche-cli/sdk/vm" + + "github.com/ava-labs/avalanchego/ids" + commonAvago "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" + "github.com/ava-labs/subnet-evm/commontype" + "github.com/ava-labs/subnet-evm/core" + "github.com/ava-labs/subnet-evm/params" +) + +type SubnetParams struct { + // File path of Genesis to use + // Do not set SubnetEVMParams or CustomVMParams + // if GenesisFilePath value is set + // + // See https://docs.avax.network/build/subnet/upgrade/customize-a-subnet#genesis for + // information on Genesis + GenesisFilePath string + + // Subnet-EVM parameters to use + // Do not set SubnetEVM value if you are using Custom VM + SubnetEVM *SubnetEVMParams + + // Name is alias for the Subnet, it is used to derive VM ID, which is required + // during for createBlockchainTx + Name string +} + +type SubnetEVMParams struct { + // ChainID identifies the current chain and is used for replay protection + ChainID *big.Int + + // FeeConfig sets the configuration for the dynamic fee algorithm + FeeConfig commontype.FeeConfig + + // Allocation specifies the initial state that is part of the genesis block. + Allocation core.GenesisAlloc + + // Ethereum uses Precompiles to efficiently implement cryptographic primitives within the EVM + // instead of re-implementing the same primitives in Solidity. + // + // Precompiles are a shortcut to execute a function implemented by the EVM itself, + // rather than an actual contract. A precompile is associated with a fixed address defined in + // the EVM. There is no byte code associated with that address. + // + // For more information regarding Precompiles, head to https://docs.avax.network/build/vm/evm/intro. + Precompiles params.Precompiles + + // Timestamp + // TODO: add description what timestamp is + Timestamp *uint64 +} + +type CustomVMParams struct { + // File path of the Custom VM binary to use + VMFilePath string + + // Git Repo URL to be used to build Custom VM + // Only set CustomVMRepoURL value when VMFilePath value is not set + CustomVMRepoURL string + + // Git branch or commit to be used to build Custom VM + // Only set CustomVMBranch value when VMFilePath value is not set + CustomVMBranch string + + // Filepath of the script to be used to build Custom VM + // Only set CustomVMBuildScript value when VMFilePath value is not set + CustomVMBuildScript string +} + +type Subnet struct { + // Name is alias for the Subnet + Name string + + // Genesis is the initial state of a blockchain when it is first created. Each Virtual Machine + // defines the format and semantics of its genesis data. + // + // For more information regarding Genesis, head to https://docs.avax.network/build/subnet/upgrade/customize-a-subnet#genesis + Genesis []byte + + // SubnetID is the transaction ID from an issued CreateSubnetTX and is used to identify + // the target Subnet for CreateChainTx and AddValidatorTx + SubnetID ids.ID + + // VMID specifies the vm that the new chain will run when CreateChainTx is called + VMID ids.ID + + // DeployInfo contains all the necessary information for createSubnetTx + DeployInfo DeployParams +} + +func (c *Subnet) SetParams(controlKeys []ids.ShortID, subnetAuthKeys []ids.ShortID, threshold uint32) { + c.DeployInfo = DeployParams{ + ControlKeys: controlKeys, + SubnetAuthKeys: subnetAuthKeys, + Threshold: threshold, + } +} + +// SetSubnetControlParams sets: +// - control keys, which are keys that are allowed to make changes to a Subnet +// - threshold, which is the number of keys that need to sign a transaction that changes +// a Subnet +func (c *Subnet) SetSubnetControlParams(controlKeys []ids.ShortID, threshold uint32) { + c.DeployInfo.ControlKeys = controlKeys + c.DeployInfo.Threshold = threshold +} + +// SetSubnetAuthKeys sets subnetAuthKeys, which are keys that are being used to sign a transaction +// that changes a Subnet +func (c *Subnet) SetSubnetAuthKeys(subnetAuthKeys []ids.ShortID) { + c.DeployInfo.SubnetAuthKeys = subnetAuthKeys +} + +type DeployParams struct { + // ControlKeys is a list of P-Chain addresses that are authorized to create new chains and add + // new validators to the Subnet + ControlKeys []ids.ShortID + + // SubnetAuthKeys is a list of P-Chain addresses that will be used to sign transactions that + // will modify the Subnet. + // + // SubnetAuthKeys has to be a subset of ControlKeys + SubnetAuthKeys []ids.ShortID + + // Threshold is the minimum number of signatures needed before a transaction can be issued + // Number of addresses in SubnetAuthKeys has to be more than or equal to Threshold number + Threshold uint32 +} + +// New takes SubnetParams as input and creates Subnet as an output +// +// The created Subnet object can be used to : +// - Create the Subnet on a specified network (Fuji / Mainnet) +// - Create Blockchain(s) in the Subnet +// - Add Validator(s) into the Subnet +func New(subnetParams *SubnetParams) (*Subnet, error) { + if subnetParams.GenesisFilePath != "" && subnetParams.SubnetEVM != nil { + return nil, fmt.Errorf("genesis file path cannot be non-empty if SubnetEVM params is not empty") + } + + if subnetParams.GenesisFilePath == "" && subnetParams.SubnetEVM == nil { + return nil, fmt.Errorf("genesis file path and SubnetEVM params params cannot all be empty") + } + + if subnetParams.Name == "" { + return nil, fmt.Errorf("SubnetEVM name cannot be empty") + } + + var genesisBytes []byte + var err error + switch { + case subnetParams.GenesisFilePath != "": + genesisBytes, err = os.ReadFile(subnetParams.GenesisFilePath) + case subnetParams.SubnetEVM != nil: + genesisBytes, err = createEvmGenesis(subnetParams.SubnetEVM) + default: + } + if err != nil { + return nil, err + } + + vmID, err := vmID(subnetParams.Name) + if err != nil { + return nil, fmt.Errorf("failed to create VM ID from %s: %w", subnetParams.Name, err) + } + subnet := Subnet{ + Name: subnetParams.Name, + VMID: vmID, + Genesis: genesisBytes, + } + return &subnet, nil +} + +func (c *Subnet) SetSubnetID(subnetID ids.ID) { + c.SubnetID = subnetID +} + +func createEvmGenesis( + subnetEVMParams *SubnetEVMParams, +) ([]byte, error) { + genesis := core.Genesis{} + genesis.Timestamp = *subnetEVMParams.Timestamp + + conf := params.SubnetEVMDefaultChainConfig + conf.NetworkUpgrades = params.NetworkUpgrades{} + + var err error + + if subnetEVMParams.ChainID == nil { + return nil, fmt.Errorf("genesis params chain ID cannot be empty") + } + + if subnetEVMParams.FeeConfig == commontype.EmptyFeeConfig { + return nil, fmt.Errorf("genesis params fee config cannot be empty") + } + + if subnetEVMParams.Allocation == nil { + return nil, fmt.Errorf("genesis params allocation cannot be empty") + } + allocation := subnetEVMParams.Allocation + + if subnetEVMParams.Precompiles == nil { + return nil, fmt.Errorf("genesis params precompiles cannot be empty") + } + + conf.FeeConfig = subnetEVMParams.FeeConfig + conf.GenesisPrecompiles = subnetEVMParams.Precompiles + + conf.ChainID = subnetEVMParams.ChainID + + genesis.Alloc = allocation + genesis.Config = conf + genesis.Difficulty = vm.Difficulty + genesis.GasLimit = conf.FeeConfig.GasLimit.Uint64() + + jsonBytes, err := genesis.MarshalJSON() + if err != nil { + return nil, err + } + + var prettyJSON bytes.Buffer + err = json.Indent(&prettyJSON, jsonBytes, "", " ") + if err != nil { + return nil, err + } + + return prettyJSON.Bytes(), nil +} + +func vmID(vmName string) (ids.ID, error) { + if len(vmName) > 32 { + return ids.Empty, fmt.Errorf("VM name must be <= 32 bytes, found %d", len(vmName)) + } + b := make([]byte, 32) + copy(b, []byte(vmName)) + return ids.ToID(b) +} + +func (c *Subnet) Commit(ms multisig.Multisig, wallet wallet.Wallet, waitForTxAcceptance bool) (ids.ID, error) { + if ms.Undefined() { + return ids.Empty, multisig.ErrUndefinedTx + } + isReady, err := ms.IsReadyToCommit() + if err != nil { + return ids.Empty, err + } + if !isReady { + return ids.Empty, errors.New("tx is not fully signed so can't be committed") + } + tx, err := ms.GetWrappedPChainTx() + if err != nil { + return ids.Empty, err + } + const ( + repeats = 3 + sleepBetweenRepeats = 2 * time.Second + ) + var issueTxErr error + if err != nil { + return ids.Empty, err + } + for i := 0; i < repeats; i++ { + ctx, cancel := utilsSDK.GetAPILargeContext() + defer cancel() + options := []commonAvago.Option{commonAvago.WithContext(ctx)} + if !waitForTxAcceptance { + options = append(options, commonAvago.WithAssumeDecided()) + } + // TODO: split error checking and recovery between issuing and waiting for status + issueTxErr = wallet.P().IssueTx(tx, options...) + if issueTxErr == nil { + break + } + if ctx.Err() != nil { + issueTxErr = fmt.Errorf("timeout issuing/verifying tx with ID %s: %w", tx.ID(), issueTxErr) + } else { + issueTxErr = fmt.Errorf("error issuing tx with ID %s: %w", tx.ID(), issueTxErr) + } + time.Sleep(sleepBetweenRepeats) + } + if issueTxErr != nil { + return ids.Empty, fmt.Errorf("issue tx error %w", issueTxErr) + } + if _, ok := ms.PChainTx.Unsigned.(*txs.CreateSubnetTx); ok { + c.SubnetID = tx.ID() + } + return tx.ID(), issueTxErr +} diff --git a/sdk/blockchain/blockchain_test.go b/sdk/blockchain/blockchain_test.go new file mode 100644 index 000000000..9c42cd5f7 --- /dev/null +++ b/sdk/blockchain/blockchain_test.go @@ -0,0 +1,231 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blockchain + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ava-labs/subnet-evm/utils" + + "github.com/stretchr/testify/require" + + "github.com/ava-labs/avalanche-cli/sdk/keychain" + "github.com/ava-labs/avalanche-cli/sdk/network" + "github.com/ava-labs/avalanche-cli/sdk/vm" + "github.com/ava-labs/avalanche-cli/sdk/wallet" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "github.com/ava-labs/subnet-evm/core" + "github.com/ava-labs/subnet-evm/params" + "github.com/ethereum/go-ethereum/common" +) + +func getDefaultSubnetEVMGenesis() SubnetParams { + allocation := core.GenesisAlloc{} + defaultAmount, _ := new(big.Int).SetString(vm.DefaultEvmAirdropAmount, 10) + allocation[common.HexToAddress("INITIAL_ALLOCATION_ADDRESS")] = core.GenesisAccount{ + Balance: defaultAmount, + } + genesisBlock0Timestamp := utils.TimeToNewUint64(time.Now()) + return SubnetParams{ + SubnetEVM: &SubnetEVMParams{ + ChainID: big.NewInt(123456), + FeeConfig: vm.StarterFeeConfig, + Allocation: allocation, + Precompiles: params.Precompiles{}, + Timestamp: genesisBlock0Timestamp, + }, + Name: "TestSubnet", + } +} + +func TestSubnetDeploy(t *testing.T) { + require := require.New(t) + subnetParams := getDefaultSubnetEVMGenesis() + newSubnet, err := New(&subnetParams) + require.NoError(err) + network := network.FujiNetwork() + + keychain, err := keychain.NewKeychain(network, "KEY_PATH", nil) + require.NoError(err) + + controlKeys := keychain.Addresses().List() + subnetAuthKeys := keychain.Addresses().List() + threshold := 1 + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) + wallet, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychain.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + SubnetIDs: nil, + }, + ) + require.NoError(err) + deploySubnetTx, err := newSubnet.CreateSubnetTx(wallet) + require.NoError(err) + subnetID, err := newSubnet.Commit(*deploySubnetTx, wallet, true) + require.NoError(err) + fmt.Printf("subnetID %s \n", subnetID.String()) + time.Sleep(2 * time.Second) + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) + deployChainTx, err := newSubnet.CreateBlockchainTx(wallet) + require.NoError(err) + blockchainID, err := newSubnet.Commit(*deployChainTx, wallet, true) + require.NoError(err) + fmt.Printf("blockchainID %s \n", blockchainID.String()) +} + +func TestSubnetDeployMultiSig(t *testing.T) { + require := require.New(t) + subnetParams := getDefaultSubnetEVMGenesis() + newSubnet, _ := New(&subnetParams) + network := network.FujiNetwork() + + keychainA, err := keychain.NewKeychain(network, "KEY_PATH_A", nil) + require.NoError(err) + keychainB, err := keychain.NewKeychain(network, "KEY_PATH_B", nil) + require.NoError(err) + keychainC, err := keychain.NewKeychain(network, "KEY_PATH_C", nil) + require.NoError(err) + + controlKeys := []ids.ShortID{} + controlKeys = append(controlKeys, keychainA.Addresses().List()[0]) + controlKeys = append(controlKeys, keychainB.Addresses().List()[0]) + controlKeys = append(controlKeys, keychainC.Addresses().List()[0]) + + subnetAuthKeys := []ids.ShortID{} + subnetAuthKeys = append(subnetAuthKeys, keychainA.Addresses().List()[0]) + subnetAuthKeys = append(subnetAuthKeys, keychainB.Addresses().List()[0]) + threshold := 2 + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) + + walletA, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychainA.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + SubnetIDs: nil, + }, + ) + require.NoError(err) + + deploySubnetTx, err := newSubnet.CreateSubnetTx(walletA) + require.NoError(err) + subnetID, err := newSubnet.Commit(*deploySubnetTx, walletA, true) + require.NoError(err) + fmt.Printf("subnetID %s \n", subnetID.String()) + + // we need to wait to allow the transaction to reach other nodes in Fuji + time.Sleep(2 * time.Second) + + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) + // first signature of CreateChainTx using keychain A + deployChainTx, err := newSubnet.CreateBlockchainTx(walletA) + require.NoError(err) + + // include subnetID in PChainTxsToFetch when creating second wallet + walletB, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychainB.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + SubnetIDs: []ids.ID{subnetID}, + }, + ) + require.NoError(err) + + // second signature using keychain B + err = walletB.P().Signer().Sign(context.Background(), deployChainTx.PChainTx) + require.NoError(err) + + // since we are using the fee paying key as control key too, we can commit the transaction + // on chain immediately since the number of signatures has been reached + blockchainID, err := newSubnet.Commit(*deployChainTx, walletA, true) + require.NoError(err) + fmt.Printf("blockchainID %s \n", blockchainID.String()) +} + +func TestSubnetDeployLedger(t *testing.T) { + require := require.New(t) + subnetParams := getDefaultSubnetEVMGenesis() + newSubnet, err := New(&subnetParams) + require.NoError(err) + network := network.FujiNetwork() + + ledgerInfo := keychain.LedgerParams{ + LedgerAddresses: []string{"P-fujixxxxxxxxx"}, + } + keychainA, err := keychain.NewKeychain(network, "", &ledgerInfo) + require.NoError(err) + + addressesIDs, err := address.ParseToIDs([]string{"P-fujiyyyyyyyy"}) + require.NoError(err) + controlKeys := addressesIDs + subnetAuthKeys := addressesIDs + threshold := 1 + + newSubnet.SetSubnetControlParams(controlKeys, uint32(threshold)) + + walletA, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychainA.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + SubnetIDs: nil, + }, + ) + + require.NoError(err) + deploySubnetTx, err := newSubnet.CreateSubnetTx(walletA) + require.NoError(err) + subnetID, err := newSubnet.Commit(*deploySubnetTx, walletA, true) + require.NoError(err) + fmt.Printf("subnetID %s \n", subnetID.String()) + + time.Sleep(2 * time.Second) + + newSubnet.SetSubnetAuthKeys(subnetAuthKeys) + deployChainTx, err := newSubnet.CreateBlockchainTx(walletA) + require.NoError(err) + + ledgerInfoB := keychain.LedgerParams{ + LedgerAddresses: []string{"P-fujiyyyyyyyy"}, + } + err = keychainA.Ledger.LedgerDevice.Disconnect() + require.NoError(err) + + keychainB, err := keychain.NewKeychain(network, "", &ledgerInfoB) + require.NoError(err) + + walletB, err := wallet.New( + context.Background(), + &primary.WalletConfig{ + URI: network.Endpoint, + AVAXKeychain: keychainB.Keychain, + EthKeychain: secp256k1fx.NewKeychain(), + SubnetIDs: []ids.ID{subnetID}, + }, + ) + require.NoError(err) + + // second signature + err = walletB.P().Signer().Sign(context.Background(), deployChainTx.PChainTx) + require.NoError(err) + + blockchainID, err := newSubnet.Commit(*deployChainTx, walletB, true) + require.NoError(err) + + fmt.Printf("blockchainID %s \n", blockchainID.String()) +} diff --git a/sdk/blockchain/deploy_subnet.go b/sdk/blockchain/deploy_subnet.go new file mode 100644 index 000000000..d1cc58873 --- /dev/null +++ b/sdk/blockchain/deploy_subnet.go @@ -0,0 +1,83 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blockchain + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanche-cli/sdk/multisig" + "github.com/ava-labs/avalanche-cli/sdk/wallet" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +// CreateSubnetTx creates uncommitted CreateSubnetTx +// keychain in wallet will be used to build, sign and pay for the transaction +func (c *Subnet) CreateSubnetTx(wallet wallet.Wallet) (*multisig.Multisig, error) { + if c.DeployInfo.ControlKeys == nil { + return nil, fmt.Errorf("control keys are not provided") + } + if c.DeployInfo.Threshold == 0 { + return nil, fmt.Errorf("threshold is not provided") + } + addrs := c.DeployInfo.ControlKeys + owners := &secp256k1fx.OutputOwners{ + Addrs: addrs, + Threshold: c.DeployInfo.Threshold, + Locktime: 0, + } + unsignedTx, err := wallet.P().Builder().NewCreateSubnetTx( + owners, + ) + if err != nil { + return nil, fmt.Errorf("error building tx: %w", err) + } + tx := txs.Tx{Unsigned: unsignedTx} + if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil { + return nil, fmt.Errorf("error signing tx: %w", err) + } + return multisig.New(&tx), nil +} + +// CreateBlockchainTx creates uncommitted CreateChainTx +// keychain in wallet will be used to build, sign and pay for the transaction +func (c *Subnet) CreateBlockchainTx(wallet wallet.Wallet) (*multisig.Multisig, error) { + if c.SubnetID == ids.Empty { + return nil, fmt.Errorf("subnet ID is not provided") + } + if c.DeployInfo.SubnetAuthKeys == nil { + return nil, fmt.Errorf("subnet authkeys are not provided") + } + if c.Genesis == nil { + return nil, fmt.Errorf("threshold is not provided") + } + if c.VMID == ids.Empty { + return nil, fmt.Errorf("vm ID is not provided") + } + if c.Name == "" { + return nil, fmt.Errorf("subnet name is not provided") + } + wallet.SetSubnetAuthMultisig(c.DeployInfo.SubnetAuthKeys) + + // create tx + fxIDs := make([]ids.ID, 0) + unsignedTx, err := wallet.P().Builder().NewCreateChainTx( + c.SubnetID, + c.Genesis, + c.VMID, + fxIDs, + c.Name, + ) + if err != nil { + return nil, fmt.Errorf("error building tx: %w", err) + } + tx := txs.Tx{Unsigned: unsignedTx} + if err := wallet.P().Signer().Sign(context.Background(), &tx); err != nil { + return nil, fmt.Errorf("error signing tx: %w", err) + } + return multisig.New(&tx), nil +} diff --git a/sdk/constants/constants.go b/sdk/constants/constants.go new file mode 100644 index 000000000..128abe30a --- /dev/null +++ b/sdk/constants/constants.go @@ -0,0 +1,17 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package constants + +import "time" + +const ( + // http + APIRequestTimeout = 30 * time.Second + APIRequestLargeTimeout = 2 * time.Minute + + RemoteHostUser = "ubuntu" + + // node + CloudNodeCLIConfigBasePath = "/home/ubuntu/.avalanche-cli/" + WriteReadUserOnlyPerms = 0o600 +) diff --git a/sdk/key/key.go b/sdk/key/key.go new file mode 100644 index 000000000..df07993ed --- /dev/null +++ b/sdk/key/key.go @@ -0,0 +1,96 @@ +// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package key implements key manager and helper functions. +package key + +import ( + "bytes" + "errors" + "sort" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" +) + +var ( + ErrInvalidType = errors.New("invalid type") + ErrCantSpend = errors.New("can't spend") +) + +// Key defines methods for key manager interface. +type Key interface { + // P returns all formatted P-Chain addresses. + P(string) (string, error) + // C returns the C-Chain address in Ethereum format + C() string + // Addresses returns the all raw ids.ShortID address. + Addresses() []ids.ShortID + // Match attempts to match a list of addresses up to the provided threshold. + Match(owners *secp256k1fx.OutputOwners, time uint64) ([]uint32, []ids.ShortID, bool) + // Spends attempts to spend all specified UTXOs (outputs) + // and returns the new UTXO inputs. + // + // If target amount is specified, it only uses the + // outputs until the total spending is below the target + // amount. + Spends(outputs []*avax.UTXO, opts ...OpOption) ( + totalBalanceToSpend uint64, + inputs []*avax.TransferableInput, + signers [][]ids.ShortID, + ) + // Sign generates [numSigs] signatures and attaches them to [pTx]. + Sign(pTx *txs.Tx, signers [][]ids.ShortID) error +} + +type Op struct { + time uint64 + targetAmount uint64 + feeDeduct uint64 +} + +type OpOption func(*Op) + +func (op *Op) applyOpts(opts []OpOption) { + for _, opt := range opts { + opt(op) + } +} + +type innerSortTransferableInputsWithSigners struct { + ins []*avax.TransferableInput + signers [][]ids.ShortID +} + +func (ins *innerSortTransferableInputsWithSigners) Less(i, j int) bool { + iID, iIndex := ins.ins[i].InputSource() + jID, jIndex := ins.ins[j].InputSource() + + switch bytes.Compare(iID[:], jID[:]) { + case -1: + return true + case 0: + return iIndex < jIndex + default: + return false + } +} + +func (ins *innerSortTransferableInputsWithSigners) Len() int { + return len(ins.ins) +} + +func (ins *innerSortTransferableInputsWithSigners) Swap(i, j int) { + ins.ins[j], ins.ins[i] = ins.ins[i], ins.ins[j] + ins.signers[j], ins.signers[i] = ins.signers[i], ins.signers[j] +} + +// SortTransferableInputsWithSigners sorts the inputs and signers based on the +// input's utxo ID. +// +// This is based off of (generics?): https://github.com/ava-labs/avalanchego/blob/224c9fd23d41839201dd0275ac864a845de6e93e/vms/components/avax/transferables.go#L202 +func SortTransferableInputsWithSigners(ins []*avax.TransferableInput, signers [][]ids.ShortID) { + sort.Sort(&innerSortTransferableInputsWithSigners{ins: ins, signers: signers}) +} diff --git a/sdk/key/key_test.go b/sdk/key/key_test.go new file mode 100644 index 000000000..f7ce51485 --- /dev/null +++ b/sdk/key/key_test.go @@ -0,0 +1,115 @@ +// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package key + +import ( + "bytes" + "errors" + "path/filepath" + "testing" + + "github.com/ava-labs/avalanchego/utils/cb58" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" +) + +const ewoqPChainAddr = "P-custom18jma8ppw3nhx5r4ap8clazz0dps7rv5u9xde7p" + +func TestNewKeyEwoq(t *testing.T) { + t.Parallel() + + m, err := NewSoft( + WithPrivateKeyEncoded(EwoqPrivateKey), + ) + if err != nil { + t.Fatal(err) + } + + pAddr, err := m.P("custom") + if err != nil { + t.Fatal(err) + } + if pAddr != ewoqPChainAddr { + t.Fatalf("unexpected P-Chain address %q, expected %q", pAddr, ewoqPChainAddr) + } + + keyPath := filepath.Join(t.TempDir(), "key.pk") + if err := m.Save(keyPath); err != nil { + t.Fatal(err) + } + + m2, err := LoadSoft(keyPath) + if err != nil { + t.Fatal(err) + } + + if !bytes.Equal(m.PrivKeyRaw(), m2.PrivKeyRaw()) { + t.Fatalf("loaded key unexpected %v, expected %v", m2.PrivKeyRaw(), m.PrivKeyRaw()) + } +} + +func TestNewKey(t *testing.T) { + t.Parallel() + + skBytes, err := cb58.Decode(rawEwoqPk) + if err != nil { + t.Fatal(err) + } + ewoqPk, err := secp256k1.ToPrivateKey(skBytes) + if err != nil { + t.Fatal(err) + } + + privKey2, err := secp256k1.NewPrivateKey() + if err != nil { + t.Fatal(err) + } + + tt := []struct { + name string + opts []SOpOption + expErr error + }{ + { + name: "test", + opts: nil, + expErr: nil, + }, + { + name: "ewop with WithPrivateKey", + opts: []SOpOption{ + WithPrivateKey(ewoqPk), + }, + expErr: nil, + }, + { + name: "ewop with WithPrivateKeyEncoded", + opts: []SOpOption{ + WithPrivateKeyEncoded(EwoqPrivateKey), + }, + expErr: nil, + }, + { + name: "ewop with WithPrivateKey/WithPrivateKeyEncoded", + opts: []SOpOption{ + WithPrivateKey(ewoqPk), + WithPrivateKeyEncoded(EwoqPrivateKey), + }, + expErr: nil, + }, + { + name: "ewop with invalid WithPrivateKey", + opts: []SOpOption{ + WithPrivateKey(privKey2), + WithPrivateKeyEncoded(EwoqPrivateKey), + }, + expErr: ErrInvalidPrivateKey, + }, + } + for i, tv := range tt { + _, err := NewSoft(tv.opts...) + if !errors.Is(err, tv.expErr) { + t.Fatalf("#%d(%s): unexpected error %v, expected %v", i, tv.name, err, tv.expErr) + } + } +} diff --git a/sdk/key/soft_key.go b/sdk/key/soft_key.go new file mode 100644 index 000000000..9729748be --- /dev/null +++ b/sdk/key/soft_key.go @@ -0,0 +1,375 @@ +// Copyright (C) 2019-2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package key + +import ( + "bufio" + "bytes" + "encoding/hex" + "errors" + "io" + "os" + "strings" + + "github.com/ava-labs/avalanche-cli/sdk/constants" + "github.com/ava-labs/avalanche-cli/sdk/utils" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/cb58" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/vms/components/avax" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + eth_crypto "github.com/ethereum/go-ethereum/crypto" + "go.uber.org/zap" +) + +var ( + ErrInvalidPrivateKey = errors.New("invalid private key") + ErrInvalidPrivateKeyLen = errors.New("invalid private key length (expect 64 bytes in hex)") + ErrInvalidPrivateKeyEnding = errors.New("invalid private key ending") + ErrInvalidPrivateKeyEncoding = errors.New("invalid private key encoding") +) + +var _ Key = &SoftKey{} + +type SoftKey struct { + privKey *secp256k1.PrivateKey + privKeyRaw []byte + privKeyEncoded string + + keyChain *secp256k1fx.Keychain +} + +const ( + privKeyEncPfx = "PrivateKey-" + privKeySize = 64 + + rawEwoqPk = "ewoqjP7PxY4yr3iLTpLisriqt94hdyDFNgchSxGGztUrTXtNN" + EwoqPrivateKey = privKeyEncPfx + rawEwoqPk +) + +type SOp struct { + privKey *secp256k1.PrivateKey + privKeyEncoded string +} + +type SOpOption func(*SOp) + +func (sop *SOp) applyOpts(opts []SOpOption) { + for _, opt := range opts { + opt(sop) + } +} + +// To create a new key SoftKey with a pre-loaded private key. +func WithPrivateKey(privKey *secp256k1.PrivateKey) SOpOption { + return func(sop *SOp) { + sop.privKey = privKey + } +} + +// To create a new key SoftKey with a pre-defined private key. +func WithPrivateKeyEncoded(privKey string) SOpOption { + return func(sop *SOp) { + sop.privKeyEncoded = privKey + } +} + +func NewSoft(opts ...SOpOption) (*SoftKey, error) { + ret := &SOp{} + ret.applyOpts(opts) + + // set via "WithPrivateKeyEncoded" + if len(ret.privKeyEncoded) > 0 { + privKey, err := decodePrivateKey(ret.privKeyEncoded) + if err != nil { + return nil, err + } + // to not overwrite + if ret.privKey != nil && + !bytes.Equal(ret.privKey.Bytes(), privKey.Bytes()) { + return nil, ErrInvalidPrivateKey + } + ret.privKey = privKey + } + + // generate a new one + if ret.privKey == nil { + var err error + ret.privKey, err = secp256k1.NewPrivateKey() + if err != nil { + return nil, err + } + } + + privKey := ret.privKey + privKeyEncoded, err := encodePrivateKey(ret.privKey) + if err != nil { + return nil, err + } + + // double-check encoding is consistent + if ret.privKeyEncoded != "" && + ret.privKeyEncoded != privKeyEncoded { + return nil, ErrInvalidPrivateKeyEncoding + } + + keyChain := secp256k1fx.NewKeychain() + keyChain.Add(privKey) + + m := &SoftKey{ + privKey: privKey, + privKeyRaw: privKey.Bytes(), + privKeyEncoded: privKeyEncoded, + + keyChain: keyChain, + } + + return m, nil +} + +// LoadSoft loads the private key from disk and creates the corresponding SoftKey. +func LoadSoft(keyPath string) (*SoftKey, error) { + kb, err := os.ReadFile(keyPath) + if err != nil { + return nil, err + } + return LoadSoftFromBytes(kb) +} + +func LoadSoftOrCreate(keyPath string) (*SoftKey, error) { + if utils.FileExists(keyPath) { + return LoadSoft(keyPath) + } else { + k, err := NewSoft() + if err != nil { + return nil, err + } + if err := k.Save(keyPath); err != nil { + return nil, err + } + return k, nil + } +} + +// LoadSoftFromBytes loads the private key from bytes and creates the corresponding SoftKey. +func LoadSoftFromBytes(kb []byte) (*SoftKey, error) { + // in case, it's already encoded + k, err := NewSoft(WithPrivateKeyEncoded(string(kb))) + if err == nil { + return k, nil + } + + r := bufio.NewReader(bytes.NewBuffer(kb)) + buf := make([]byte, privKeySize) + n, err := readASCII(buf, r) + if err != nil { + return nil, err + } + if n != len(buf) { + return nil, ErrInvalidPrivateKeyLen + } + if err := checkKeyFileEnd(r); err != nil { + return nil, err + } + + skBytes, err := hex.DecodeString(string(buf)) + if err != nil { + return nil, err + } + privKey, err := secp256k1.ToPrivateKey(skBytes) + if err != nil { + return nil, err + } + + return NewSoft(WithPrivateKey(privKey)) +} + +// readASCII reads into 'buf', stopping when the buffer is full or +// when a non-printable control character is encountered. +func readASCII(buf []byte, r io.ByteReader) (n int, err error) { + for ; n < len(buf); n++ { + buf[n], err = r.ReadByte() + switch { + case errors.Is(err, io.EOF) || buf[n] < '!': + return n, nil + case err != nil: + return n, err + } + } + return n, nil +} + +const fileEndLimit = 1 + +// checkKeyFileEnd skips over additional newlines at the end of a key file. +func checkKeyFileEnd(r io.ByteReader) error { + for idx := 0; ; idx++ { + b, err := r.ReadByte() + switch { + case errors.Is(err, io.EOF): + return nil + case err != nil: + return err + case b != '\n' && b != '\r': + return ErrInvalidPrivateKeyEnding + case idx > fileEndLimit: + return ErrInvalidPrivateKeyLen + } + } +} + +func encodePrivateKey(pk *secp256k1.PrivateKey) (string, error) { + privKeyRaw := pk.Bytes() + enc, err := cb58.Encode(privKeyRaw) + if err != nil { + return "", err + } + return privKeyEncPfx + enc, nil +} + +func decodePrivateKey(enc string) (*secp256k1.PrivateKey, error) { + rawPk := strings.Replace(enc, privKeyEncPfx, "", 1) + skBytes, err := cb58.Decode(rawPk) + if err != nil { + return nil, err + } + privKey, err := secp256k1.ToPrivateKey(skBytes) + if err != nil { + return nil, err + } + return privKey, nil +} + +func (m *SoftKey) C() string { + ecdsaPrv := m.privKey.ToECDSA() + pub := ecdsaPrv.PublicKey + + addr := eth_crypto.PubkeyToAddress(pub) + return addr.String() +} + +// Returns the KeyChain +func (m *SoftKey) KeyChain() *secp256k1fx.Keychain { + return m.keyChain +} + +// Returns the private key. +func (m *SoftKey) PrivKey() *secp256k1.PrivateKey { + return m.privKey +} + +// Returns the private key in raw bytes. +func (m *SoftKey) PrivKeyRaw() []byte { + return m.privKeyRaw +} + +// Returns the private key encoded in CB58 and "PrivateKey-" prefix. +func (m *SoftKey) PrivKeyCB58() string { + return m.privKeyEncoded +} + +// Returns the private key encoded hex +func (m *SoftKey) PrivKeyHex() string { + return hex.EncodeToString(m.privKeyRaw) +} + +// Saves the private key to disk with hex encoding. +func (m *SoftKey) Save(p string) error { + return os.WriteFile(p, []byte(m.PrivKeyHex()), constants.WriteReadUserOnlyPerms) +} + +func (m *SoftKey) P(networkHRP string) (string, error) { + return address.Format("P", networkHRP, m.privKey.PublicKey().Address().Bytes()) +} + +func (m *SoftKey) X(networkHRP string) (string, error) { + return address.Format("X", networkHRP, m.privKey.PublicKey().Address().Bytes()) +} + +func (m *SoftKey) Spends(outputs []*avax.UTXO, opts ...OpOption) ( + totalBalanceToSpend uint64, + inputs []*avax.TransferableInput, + signers [][]ids.ShortID, +) { + ret := &Op{} + ret.applyOpts(opts) + + for _, out := range outputs { + input, psigners, err := m.spend(out, ret.time) + if err != nil { + zap.L().Warn("cannot spend with current key", zap.Error(err)) + continue + } + totalBalanceToSpend += input.Amount() + inputs = append(inputs, &avax.TransferableInput{ + UTXOID: out.UTXOID, + Asset: out.Asset, + In: input, + }) + // Convert to ids.ShortID to adhere with interface + pksigners := make([]ids.ShortID, len(psigners)) + for i, psigner := range psigners { + pksigners[i] = psigner.PublicKey().Address() + } + signers = append(signers, pksigners) + if ret.targetAmount > 0 && + totalBalanceToSpend > ret.targetAmount+ret.feeDeduct { + break + } + } + SortTransferableInputsWithSigners(inputs, signers) + return totalBalanceToSpend, inputs, signers +} + +func (m *SoftKey) spend(output *avax.UTXO, time uint64) ( + input avax.TransferableIn, + signers []*secp256k1.PrivateKey, + err error, +) { + // "time" is used to check whether the key owner + // is still within the lock time (thus can't spend). + inputf, psigners, err := m.keyChain.Spend(output.Out, time) + if err != nil { + return nil, nil, err + } + var ok bool + input, ok = inputf.(avax.TransferableIn) + if !ok { + return nil, nil, ErrInvalidType + } + return input, psigners, nil +} + +func (m *SoftKey) Addresses() []ids.ShortID { + return []ids.ShortID{m.privKey.PublicKey().Address()} +} + +func (m *SoftKey) Sign(pTx *txs.Tx, signers [][]ids.ShortID) error { + privsigners := make([][]*secp256k1.PrivateKey, len(signers)) + for i, inputSigners := range signers { + privsigners[i] = make([]*secp256k1.PrivateKey, len(inputSigners)) + for j, signer := range inputSigners { + if signer != m.privKey.PublicKey().Address() { + // Should never happen + return ErrCantSpend + } + privsigners[i][j] = m.privKey + } + } + + return pTx.Sign(txs.Codec, privsigners) +} + +func (m *SoftKey) Match(owners *secp256k1fx.OutputOwners, time uint64) ([]uint32, []ids.ShortID, bool) { + indices, privs, ok := m.keyChain.Match(owners, time) + pks := make([]ids.ShortID, len(privs)) + for i, priv := range privs { + pks[i] = priv.PublicKey().Address() + } + return indices, pks, ok +} diff --git a/sdk/keychain/keychain.go b/sdk/keychain/keychain.go new file mode 100644 index 000000000..f060064ad --- /dev/null +++ b/sdk/keychain/keychain.go @@ -0,0 +1,138 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package keychain + +import ( + "fmt" + + "github.com/ava-labs/avalanche-cli/sdk/key" + "github.com/ava-labs/avalanche-cli/sdk/ledger" + "github.com/ava-labs/avalanche-cli/sdk/network" + "github.com/ava-labs/avalanche-cli/sdk/utils" + "github.com/ava-labs/avalanchego/utils/crypto/keychain" + "golang.org/x/exp/maps" +) + +type Keychain struct { + keychain.Keychain + network network.Network + Ledger *Ledger +} + +// LedgerParams is an input to NewKeyChain if a new keychain is to be created using Ledger +// +// To view Ledger addresses and their balances, you can use Avalanche CLI and use the command +// avalanche key list --ledger [0,1,2,3,4] +// The example command above will list the first five addresses in your Ledger +// +// To transfer funds between addresses in Ledger, refer to https://docs.avax.network/tooling/cli-transfer-funds/how-to-transfer-funds +type LedgerParams struct { + // LedgerAddresses specify which addresses in Ledger should be in the Keychain + // NewKeyChain will then look for the indexes of the specified addresses and add the indexes + // into LedgerIndices in Ledger + LedgerAddresses []string + + // RequiredFunds is the minimum total AVAX that the selected addresses from Ledger should contain. + // NewKeychain will then look through all indexes of all addresses in the Ledger until + // sufficient AVAX balance is reached. + // For example if Ledger's index 0 and index 1 each contains 0.1 AVAX and RequiredFunds is + // 0.2 AVAX, LedgerIndices will have value of [0,1] + RequiredFunds uint64 +} + +// Ledger is part of the output of NewKeyChain if a new keychain is to be created using Ledger +type Ledger struct { + // LedgerDevice is the main interface of interacting with the Ledger Device + LedgerDevice *ledger.LedgerDevice + + // LedgerIndices contain indexes of the addresses selected from Ledger + LedgerIndices []uint32 +} + +// NewKeychain generates a new key pair from either a stored key path or Ledger. +// For stored keys, NewKeychain will generate a new key pair in the provided keyPath if no .pk +// file currently exists in the provided path. +func NewKeychain( + network network.Network, + keyPath string, + ledgerInfo *LedgerParams, +) (*Keychain, error) { + if ledgerInfo != nil { + if keyPath != "" { + return nil, fmt.Errorf("keychain can only created either from key path or ledger, not both") + } + dev, err := ledger.New() + if err != nil { + return nil, err + } + kc := Keychain{ + Ledger: &Ledger{ + LedgerDevice: dev, + }, + network: network, + } + if ledgerInfo.RequiredFunds > 0 { + if err := kc.AddLedgerFunds(ledgerInfo.RequiredFunds); err != nil { + return nil, err + } + } + if len(ledgerInfo.LedgerAddresses) > 0 { + if err := kc.AddLedgerAddresses(ledgerInfo.LedgerAddresses); err != nil { + return nil, err + } + } + if len(kc.Ledger.LedgerIndices) == 0 { + return nil, fmt.Errorf("keychain currently does not contain any addresses from ledger") + } + return &kc, nil + } + sf, err := key.LoadSoftOrCreate(keyPath) + if err != nil { + return nil, err + } + kc := Keychain{ + Keychain: sf.KeyChain(), + network: network, + } + return &kc, nil +} + +func (kc *Keychain) LedgerEnabled() bool { + return kc.Ledger.LedgerDevice != nil +} + +func (kc *Keychain) AddLedgerIndices(indices []uint32) error { + if kc.LedgerEnabled() { + kc.Ledger.LedgerIndices = utils.Unique(append(kc.Ledger.LedgerIndices, indices...)) + utils.Uint32Sort(kc.Ledger.LedgerIndices) + newKc, err := keychain.NewLedgerKeychainFromIndices(kc.Ledger.LedgerDevice, kc.Ledger.LedgerIndices) + if err != nil { + return err + } + kc.Keychain = newKc + return nil + } + return fmt.Errorf("keychain is not ledger enabled") +} + +func (kc *Keychain) AddLedgerAddresses(addresses []string) error { + if kc.LedgerEnabled() { + indices, err := kc.Ledger.LedgerDevice.FindAddresses(addresses, 0) + if err != nil { + return err + } + return kc.AddLedgerIndices(maps.Values(indices)) + } + return fmt.Errorf("keychain is not ledger enabled") +} + +func (kc *Keychain) AddLedgerFunds(amount uint64) error { + if kc.LedgerEnabled() { + indices, err := kc.Ledger.LedgerDevice.FindFunds(kc.network, amount, 0) + if err != nil { + return err + } + return kc.AddLedgerIndices(indices) + } + return fmt.Errorf("keychain is not ledger enabled") +} diff --git a/sdk/ledger/ledger.go b/sdk/ledger/ledger.go new file mode 100644 index 000000000..c7e4e5cb9 --- /dev/null +++ b/sdk/ledger/ledger.go @@ -0,0 +1,101 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package ledger + +import ( + "fmt" + + "github.com/ava-labs/avalanche-cli/sdk/network" + "github.com/ava-labs/avalanche-cli/sdk/utils" + + "github.com/ava-labs/avalanchego/utils/crypto/keychain" + "github.com/ava-labs/avalanchego/utils/crypto/ledger" + "github.com/ava-labs/avalanchego/utils/formatting/address" + "github.com/ava-labs/avalanchego/vms/platformvm" +) + +const ( + maxIndexToSearch = 1000 + maxIndexToSearchForBalance = 100 +) + +type LedgerDevice struct { + keychain.Ledger +} + +func New() (*LedgerDevice, error) { + avagoDev, err := ledger.New() + if err != nil { + return nil, err + } + dev := LedgerDevice{ + Ledger: avagoDev, + } + return &dev, nil +} + +func (dev *LedgerDevice) FindAddresses(addresses []string, maxIndex uint32) (map[string]uint32, error) { + addressesIDs, err := address.ParseToIDs(addresses) + if err != nil { + return nil, fmt.Errorf("failure parsing ledger addresses: %w", err) + } + // for all ledger indices to search for, find if the ledger address belongs to the input + // addresses and, if so, add an index association to indexMap. + // breaks the loop if all addresses were found + if maxIndex == 0 { + maxIndex = maxIndexToSearch + } + indices := map[string]uint32{} + for index := uint32(0); index < maxIndex; index++ { + ledgerAddress, err := dev.Addresses([]uint32{index}) + if err != nil { + return nil, err + } + for addressIndex, addr := range addressesIDs { + if addr == ledgerAddress[0] { + indices[addresses[addressIndex]] = index + } + } + if len(indices) == len(addresses) { + break + } + } + return indices, nil +} + +// FindFunds searches for a set of indices that pay a given amount +func (dev *LedgerDevice) FindFunds( + network network.Network, + amount uint64, + maxIndex uint32, +) ([]uint32, error) { + pClient := platformvm.NewClient(network.Endpoint) + totalBalance := uint64(0) + indices := []uint32{} + if maxIndex == 0 { + maxIndex = maxIndexToSearchForBalance + } + for index := uint32(0); index < maxIndex; index++ { + ledgerAddress, err := dev.Addresses([]uint32{index}) + if err != nil { + return []uint32{}, err + } + ctx, cancel := utils.GetAPIContext() + resp, err := pClient.GetBalance(ctx, ledgerAddress) + cancel() + if err != nil { + return nil, err + } + if resp.Balance > 0 { + totalBalance += uint64(resp.Balance) + indices = append(indices, index) + } + if totalBalance >= amount { + break + } + } + if totalBalance < amount { + return nil, fmt.Errorf("not enough funds on ledger") + } + return indices, nil +} diff --git a/sdk/multisig/multisig.go b/sdk/multisig/multisig.go new file mode 100644 index 000000000..72cb8fc65 --- /dev/null +++ b/sdk/multisig/multisig.go @@ -0,0 +1,357 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package multisig + +import ( + "context" + "fmt" + + "github.com/ava-labs/avalanchego/vms/platformvm" + + "github.com/ava-labs/avalanche-cli/sdk/network" + "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" + "github.com/ava-labs/avalanchego/vms/components/verify" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/txs" +) + +type TxKind int64 + +var ErrUndefinedTx = fmt.Errorf("tx is undefined") + +const ( + Undefined TxKind = iota + PChainRemoveSubnetValidatorTx + PChainAddSubnetValidatorTx + PChainCreateChainTx + PChainTransformSubnetTx + PChainAddPermissionlessValidatorTx + PChainTransferSubnetOwnershipTx +) + +type Multisig struct { + PChainTx *txs.Tx + controlKeys []ids.ShortID + threshold uint32 +} + +func New(pChainTx *txs.Tx) *Multisig { + ms := Multisig{ + PChainTx: pChainTx, + } + return &ms +} + +func (ms *Multisig) String() string { + if ms.PChainTx != nil { + return ms.PChainTx.ID().String() + } + return "" +} + +func (ms *Multisig) Undefined() bool { + return ms.PChainTx == nil +} + +func (ms *Multisig) ToBytes() ([]byte, error) { + if ms.Undefined() { + return nil, ErrUndefinedTx + } + txBytes, err := txs.Codec.Marshal(txs.CodecVersion, ms.PChainTx) + if err != nil { + return nil, fmt.Errorf("couldn't marshal signed tx: %w", err) + } + return txBytes, nil +} + +func (ms *Multisig) FromBytes(txBytes []byte) error { + var tx txs.Tx + if _, err := txs.Codec.Unmarshal(txBytes, &tx); err != nil { + return fmt.Errorf("error unmarshaling signed tx: %w", err) + } + if err := tx.Initialize(txs.Codec); err != nil { + return fmt.Errorf("error initializing signed tx: %w", err) + } + ms.PChainTx = &tx + return nil +} + +func (ms *Multisig) IsReadyToCommit() (bool, error) { + if ms.Undefined() { + return false, ErrUndefinedTx + } + unsignedTx := ms.PChainTx.Unsigned + switch unsignedTx.(type) { + case *txs.CreateSubnetTx: + return true, nil + default: + } + _, remainingSigners, err := ms.GetRemainingAuthSigners() + if err != nil { + return false, err + } + return len(remainingSigners) == 0, nil +} + +// GetRemainingAuthSigners gets subnet auth addresses that have not signed a given tx +// - get the string slice of auth signers for the tx (GetAuthSigners) +// - verifies that all creds in tx.Creds, except the last one, are fully signed +// (a cred is fully signed if all the signatures in cred.Sigs are non-empty) +// - computes remaining signers by iterating the last cred in tx.Creds, associated to subnet auth signing +// - for each sig in cred.Sig: if sig is empty, then add the associated auth signer address (obtained from +// authSigners by using the index) to the remaining signers list +// +// if the tx is fully signed, returns empty slice +func (ms *Multisig) GetRemainingAuthSigners() ([]ids.ShortID, []ids.ShortID, error) { + if ms.Undefined() { + return nil, nil, ErrUndefinedTx + } + authSigners, err := ms.GetAuthSigners() + if err != nil { + return nil, nil, err + } + emptySig := [secp256k1.SignatureLen]byte{} + numCreds := len(ms.PChainTx.Creds) + // we should have at least 1 cred for output owners and 1 cred for subnet auth + if numCreds < 2 { + return nil, nil, fmt.Errorf("expected tx.Creds of len 2, got %d. doesn't seem to be a multisig tx with subnet auth requirements", numCreds) + } + // signatures for output owners should be filled (all creds except last one) + for credIndex := range ms.PChainTx.Creds[:numCreds-1] { + cred, ok := ms.PChainTx.Creds[credIndex].(*secp256k1fx.Credential) + if !ok { + return nil, nil, fmt.Errorf("expected cred to be of type *secp256k1fx.Credential, got %T", ms.PChainTx.Creds[credIndex]) + } + for i, sig := range cred.Sigs { + if sig == emptySig { + return nil, nil, fmt.Errorf("expected funding sig %d of cred %d to be filled", i, credIndex) + } + } + } + // signatures for subnet auth (last cred) + cred, ok := ms.PChainTx.Creds[numCreds-1].(*secp256k1fx.Credential) + if !ok { + return nil, nil, fmt.Errorf("expected cred to be of type *secp256k1fx.Credential, got %T", ms.PChainTx.Creds[1]) + } + if len(cred.Sigs) != len(authSigners) { + return nil, nil, fmt.Errorf("expected number of cred's signatures %d to equal number of auth signers %d", + len(cred.Sigs), + len(authSigners), + ) + } + remainingSigners := []ids.ShortID{} + for i, sig := range cred.Sigs { + if sig == emptySig { + remainingSigners = append(remainingSigners, authSigners[i]) + } + } + return authSigners, remainingSigners, nil +} + +// GetAuthSigners gets all subnet auth addresses that are required to sign a given tx +// - get subnet control keys as string slice using P-Chain API (GetOwners) +// - get subnet auth indices from the tx, field tx.UnsignedTx.SubnetAuth +// - creates the string slice of required subnet auth addresses by applying +// the indices to the control keys slice +func (ms *Multisig) GetAuthSigners() ([]ids.ShortID, error) { + if ms.Undefined() { + return nil, ErrUndefinedTx + } + controlKeys, _, err := ms.GetSubnetOwners() + if err != nil { + return nil, err + } + unsignedTx := ms.PChainTx.Unsigned + var subnetAuth verify.Verifiable + switch unsignedTx := unsignedTx.(type) { + case *txs.RemoveSubnetValidatorTx: + subnetAuth = unsignedTx.SubnetAuth + case *txs.AddSubnetValidatorTx: + subnetAuth = unsignedTx.SubnetAuth + case *txs.CreateChainTx: + subnetAuth = unsignedTx.SubnetAuth + case *txs.TransformSubnetTx: + subnetAuth = unsignedTx.SubnetAuth + case *txs.TransferSubnetOwnershipTx: + subnetAuth = unsignedTx.SubnetAuth + default: + return nil, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx) + } + subnetInput, ok := subnetAuth.(*secp256k1fx.Input) + if !ok { + return nil, fmt.Errorf("expected subnetAuth of type *secp256k1fx.Input, got %T", subnetAuth) + } + authSigners := []ids.ShortID{} + for _, sigIndex := range subnetInput.SigIndices { + if sigIndex >= uint32(len(controlKeys)) { + return nil, fmt.Errorf("signer index %d exceeds number of control keys", sigIndex) + } + authSigners = append(authSigners, controlKeys[sigIndex]) + } + return authSigners, nil +} + +func (*Multisig) GetSpendSigners() ([]ids.ShortID, error) { + return nil, fmt.Errorf("not implemented yet") +} + +func (ms *Multisig) GetTxKind() (TxKind, error) { + if ms.Undefined() { + return Undefined, ErrUndefinedTx + } + unsignedTx := ms.PChainTx.Unsigned + switch unsignedTx := unsignedTx.(type) { + case *txs.RemoveSubnetValidatorTx: + return PChainRemoveSubnetValidatorTx, nil + case *txs.AddSubnetValidatorTx: + return PChainAddSubnetValidatorTx, nil + case *txs.CreateChainTx: + return PChainCreateChainTx, nil + case *txs.TransformSubnetTx: + return PChainTransformSubnetTx, nil + case *txs.AddPermissionlessValidatorTx: + return PChainAddPermissionlessValidatorTx, nil + case *txs.TransferSubnetOwnershipTx: + return PChainTransferSubnetOwnershipTx, nil + default: + return Undefined, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx) + } +} + +// get network id associated to tx +func (ms *Multisig) GetNetworkID() (uint32, error) { + if ms.Undefined() { + return 0, ErrUndefinedTx + } + unsignedTx := ms.PChainTx.Unsigned + var networkID uint32 + switch unsignedTx := unsignedTx.(type) { + case *txs.RemoveSubnetValidatorTx: + networkID = unsignedTx.NetworkID + case *txs.AddSubnetValidatorTx: + networkID = unsignedTx.NetworkID + case *txs.CreateChainTx: + networkID = unsignedTx.NetworkID + case *txs.TransformSubnetTx: + networkID = unsignedTx.NetworkID + case *txs.AddPermissionlessValidatorTx: + networkID = unsignedTx.NetworkID + case *txs.TransferSubnetOwnershipTx: + networkID = unsignedTx.NetworkID + default: + return 0, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx) + } + return networkID, nil +} + +// get network model associated to tx +func (ms *Multisig) GetNetwork() (network.Network, error) { + if ms.Undefined() { + return network.UndefinedNetwork, ErrUndefinedTx + } + networkID, err := ms.GetNetworkID() + if err != nil { + return network.UndefinedNetwork, err + } + newNetwork := network.NetworkFromNetworkID(networkID) + if newNetwork.Kind == network.Undefined { + return network.UndefinedNetwork, fmt.Errorf("undefined network model for tx") + } + return newNetwork, nil +} + +func (ms *Multisig) GetBlockchainID() (ids.ID, error) { + if ms.Undefined() { + return ids.Empty, ErrUndefinedTx + } + unsignedTx := ms.PChainTx.Unsigned + var blockchainID ids.ID + switch unsignedTx := unsignedTx.(type) { + case *txs.RemoveSubnetValidatorTx: + blockchainID = unsignedTx.BlockchainID + case *txs.AddSubnetValidatorTx: + blockchainID = unsignedTx.BlockchainID + case *txs.CreateChainTx: + blockchainID = unsignedTx.BlockchainID + case *txs.TransformSubnetTx: + blockchainID = unsignedTx.BlockchainID + case *txs.AddPermissionlessValidatorTx: + blockchainID = unsignedTx.BlockchainID + case *txs.TransferSubnetOwnershipTx: + blockchainID = unsignedTx.BlockchainID + default: + return ids.Empty, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx) + } + return blockchainID, nil +} + +// GetSubnetID gets subnet id associated to tx +func (ms *Multisig) GetSubnetID() (ids.ID, error) { + if ms.Undefined() { + return ids.Empty, ErrUndefinedTx + } + unsignedTx := ms.PChainTx.Unsigned + var subnetID ids.ID + switch unsignedTx := unsignedTx.(type) { + case *txs.RemoveSubnetValidatorTx: + subnetID = unsignedTx.Subnet + case *txs.AddSubnetValidatorTx: + subnetID = unsignedTx.SubnetValidator.Subnet + case *txs.CreateChainTx: + subnetID = unsignedTx.SubnetID + case *txs.TransformSubnetTx: + subnetID = unsignedTx.Subnet + case *txs.AddPermissionlessValidatorTx: + subnetID = unsignedTx.Subnet + case *txs.TransferSubnetOwnershipTx: + subnetID = unsignedTx.Subnet + default: + return ids.Empty, fmt.Errorf("unexpected unsigned tx type %T", unsignedTx) + } + return subnetID, nil +} + +func (ms *Multisig) GetSubnetOwners() ([]ids.ShortID, uint32, error) { + if ms.Undefined() { + return nil, 0, ErrUndefinedTx + } + if ms.controlKeys == nil { + subnetID, err := ms.GetSubnetID() + if err != nil { + return nil, 0, err + } + + network, err := ms.GetNetwork() + if err != nil { + return nil, 0, err + } + controlKeys, threshold, err := GetOwners(network, subnetID) + if err != nil { + return nil, 0, err + } + ms.controlKeys = controlKeys + ms.threshold = threshold + } + return ms.controlKeys, ms.threshold, nil +} + +func GetOwners(network network.Network, subnetID ids.ID) ([]ids.ShortID, uint32, error) { + pClient := platformvm.NewClient(network.Endpoint) + ctx := context.Background() + subnetResponse, err := pClient.GetSubnet(ctx, subnetID) + if err != nil { + return nil, 0, fmt.Errorf("subnet tx %s query error: %w", subnetID, err) + } + controlKeys := subnetResponse.ControlKeys + threshold := subnetResponse.Threshold + return controlKeys, threshold, nil +} + +func (ms *Multisig) GetWrappedPChainTx() (*txs.Tx, error) { + if ms.Undefined() { + return nil, ErrUndefinedTx + } + return ms.PChainTx, nil +} diff --git a/sdk/network/network.go b/sdk/network/network.go new file mode 100644 index 000000000..a37c2cca5 --- /dev/null +++ b/sdk/network/network.go @@ -0,0 +1,56 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package network + +import ( + "github.com/ava-labs/avalanchego/utils/constants" +) + +type NetworkKind int64 + +const ( + Undefined NetworkKind = iota + Mainnet + Fuji + Devnet +) + +const ( + FujiAPIEndpoint = "https://api.avax-test.network" + MainnetAPIEndpoint = "https://api.avax.network" +) + +type Network struct { + Kind NetworkKind + ID uint32 + Endpoint string +} + +var UndefinedNetwork = Network{} + +func NetworkFromNetworkID(networkID uint32) Network { + switch networkID { + case constants.MainnetID: + return MainnetNetwork() + case constants.FujiID: + return FujiNetwork() + } + return UndefinedNetwork +} + +func NewNetwork(kind NetworkKind, id uint32, endpoint string) Network { + return Network{ + Kind: kind, + ID: id, + Endpoint: endpoint, + } +} + +func FujiNetwork() Network { + return NewNetwork(Fuji, constants.FujiID, FujiAPIEndpoint) +} + +func MainnetNetwork() Network { + return NewNetwork(Mainnet, constants.MainnetID, MainnetAPIEndpoint) +} diff --git a/sdk/utils/common.go b/sdk/utils/common.go new file mode 100644 index 000000000..5739ede0c --- /dev/null +++ b/sdk/utils/common.go @@ -0,0 +1,81 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package utils + +import ( + "context" + "fmt" + "time" +) + +// AppendSlices appends multiple slices into a single slice. +func AppendSlices[T any](slices ...[]T) []T { + totalLength := 0 + for _, slice := range slices { + totalLength += len(slice) + } + result := make([]T, 0, totalLength) + for _, slice := range slices { + result = append(result, slice...) + } + return result +} + +// Retry retries the given function until it succeeds or the maximum number of attempts is reached. +func Retry[T any]( + fn func(context.Context) (T, error), + attempTimeout time.Duration, + maxAttempts int, + errMsg string, +) (T, error) { + const defaultAttempTimeout = 2 * time.Second + if attempTimeout == 0 { + attempTimeout = defaultAttempTimeout + } + var ( + result T + err error + ) + for attempt := 0; attempt < maxAttempts; attempt++ { + start := time.Now() + ctx, cancel := context.WithTimeout(context.Background(), attempTimeout) + defer cancel() + result, err = fn(ctx) + if err == nil { + return result, nil + } + elapsed := time.Since(start) + if elapsed < attempTimeout { + time.Sleep(attempTimeout - elapsed) + } + } + return result, fmt.Errorf( + "%s: maximum retry attempts %d reached: last err = %w", + errMsg, + maxAttempts, + err, + ) +} + +// WrapContext adds a context based timeout to a given function +func WrapContext[T any]( + f func() (T, error), +) func(context.Context) (T, error) { + return func(ctx context.Context) (T, error) { + var ( + ret T + err error + ) + ch := make(chan struct{}) + go func() { + ret, err = f() + close(ch) + }() + select { + case <-ctx.Done(): + return ret, ctx.Err() + case <-ch: + } + return ret, err + } +} diff --git a/sdk/utils/common_test.go b/sdk/utils/common_test.go new file mode 100644 index 000000000..eae406a9c --- /dev/null +++ b/sdk/utils/common_test.go @@ -0,0 +1,105 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package utils + +import ( + "errors" + "reflect" + "testing" + "time" +) + +// TestAppendSlices tests AppendSlices +func TestAppendSlices(t *testing.T) { + tests := []struct { + name string + slices [][]interface{} + want []interface{} + }{ + { + name: "AppendSlices with strings", + slices: [][]interface{}{{"a", "b", "c"}, {"d", "e", "f"}, {"g", "h", "i"}}, + want: []interface{}{"a", "b", "c", "d", "e", "f", "g", "h", "i"}, + }, + { + name: "AppendSlices with ints", + slices: [][]interface{}{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}, + want: []interface{}{1, 2, 3, 4, 5, 6, 7, 8, 9}, + }, + { + name: "AppendSlices with empty slices", + slices: [][]interface{}{{}, {}, {}}, + want: []interface{}{}, + }, + { + name: "Append identical slices", + slices: [][]interface{}{{"a", "b", "c"}, {"a", "b", "c"}}, + want: []interface{}{"a", "b", "c", "a", "b", "c"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := AppendSlices(tt.slices...) + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("AppendSlices() = %v, want %v", got, tt.want) + } + }) + } +} + +// Mock function for testing retries. +func mockFunction() (interface{}, error) { + return nil, errors.New("error occurred") +} + +// TestRetry tests Retry. +func TestRetry(t *testing.T) { + success := "success" + // Test with a function that always returns an error. + result, err := Retry(WrapContext(mockFunction), 100*time.Millisecond, 3, "") + if err == nil { + t.Errorf("Expected an error, got nil") + } + if result != nil { + t.Errorf("Expected nil result, got %v", result) + } + + // Test with a function that succeeds on the first attempt. + fn := func() (interface{}, error) { + return success, nil + } + result, err = Retry(WrapContext(fn), 100*time.Millisecond, 3, "") + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if result != success { + t.Errorf("Expected 'success' result, got %v", result) + } + + // Test with a function that succeeds after multiple attempts. + count := 0 + fn = func() (interface{}, error) { + count++ + if count < 3 { + return nil, errors.New("error occurred") + } + return success, nil + } + result, err = Retry(WrapContext(fn), 100*time.Millisecond, 5, "") + if err != nil { + t.Errorf("Expected no error, got %v", err) + } + if result != success { + t.Errorf("Expected 'success' result, got %v", result) + } + + // Test with invalid retry interval. + result, err = Retry(WrapContext(mockFunction), 0, 3, "") + if err == nil { + t.Errorf("Expected an error, got nil") + } + if result != nil { + t.Errorf("Expected nil result, got %v", result) + } +} diff --git a/sdk/utils/file.go b/sdk/utils/file.go new file mode 100644 index 000000000..18bef1598 --- /dev/null +++ b/sdk/utils/file.go @@ -0,0 +1,30 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package utils + +import ( + "os" + "path/filepath" +) + +// FileExists checks if a file exists. +func FileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +// ExpandHome expands ~ symbol to home directory +func ExpandHome(path string) string { + if path == "" { + home, _ := os.UserHomeDir() + return home + } + if len(path) > 0 && path[0] == '~' { + home, _ := os.UserHomeDir() + path = filepath.Join(home, path[1:]) + } + return path +} diff --git a/sdk/utils/file_test.go b/sdk/utils/file_test.go new file mode 100644 index 000000000..abebe6613 --- /dev/null +++ b/sdk/utils/file_test.go @@ -0,0 +1,46 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package utils + +import ( + "os" + "path/filepath" + "testing" +) + +func TestExpandHome(t *testing.T) { + // Test case 1: Absolute path + absolutePath := "/tmp/testfile.txt" + expandedAbsolutePath := ExpandHome(absolutePath) + if expandedAbsolutePath != absolutePath { + t.Errorf("ExpandHome failed for absolute path: expected %s, got %s", absolutePath, expandedAbsolutePath) + } + + // Test case 2: Relative path + relativePath := "testfile.txt" + expectedRelativePath := filepath.Join(".", relativePath) + expandedRelativePath := ExpandHome(relativePath) + if expandedRelativePath != expectedRelativePath { + t.Errorf("ExpandHome failed for relative path: expected %s, got %s", expectedRelativePath, expandedRelativePath) + } + + // Test case 3: Path starting with ~ + homeDir, err := os.UserHomeDir() + if err != nil { + t.Fatalf("Error getting user home directory: %v", err) + } + tildePath := "~/testfile.txt" + expectedTildePath := filepath.Join(homeDir, "testfile.txt") + expandedTildePath := ExpandHome(tildePath) + if expandedTildePath != expectedTildePath { + t.Errorf("ExpandHome failed for path starting with ~: expected %s, got %s", expectedTildePath, expandedTildePath) + } + + // Test case 4: Empty path + emptyPath := "" + expectedEmptyPath := homeDir + expandedEmptyPath := ExpandHome(emptyPath) + if expandedEmptyPath != expectedEmptyPath { + t.Errorf("ExpandHome failed for empty path: expected %s, got %s", expectedEmptyPath, expandedEmptyPath) + } +} diff --git a/sdk/utils/utils.go b/sdk/utils/utils.go new file mode 100644 index 000000000..470705a01 --- /dev/null +++ b/sdk/utils/utils.go @@ -0,0 +1,37 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package utils + +import ( + "context" + "sort" + + "github.com/ava-labs/avalanche-cli/sdk/constants" +) + +// Unique returns a new slice containing only the unique elements from the input slice. +func Unique[T comparable](arr []T) []T { + visited := map[T]bool{} + unique := []T{} + for _, e := range arr { + if !visited[e] { + unique = append(unique, e) + visited[e] = true + } + } + return unique +} + +func Uint32Sort(arr []uint32) { + sort.Slice(arr, func(i, j int) bool { return arr[i] < arr[j] }) +} + +// Context for API requests +func GetAPIContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), constants.APIRequestTimeout) +} + +// Context for API requests with large timeout +func GetAPILargeContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), constants.APIRequestLargeTimeout) +} diff --git a/sdk/vm/evm_settings.go b/sdk/vm/evm_settings.go new file mode 100644 index 000000000..70c9e7439 --- /dev/null +++ b/sdk/vm/evm_settings.go @@ -0,0 +1,30 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package vm + +import ( + "math/big" + + "github.com/ava-labs/subnet-evm/commontype" +) + +const ( + DefaultEvmAirdropAmount = "1000000000000000000000000" +) + +var ( + Difficulty = big.NewInt(0) + + // StarterFeeConfig is the current c-chain gas config + StarterFeeConfig = commontype.FeeConfig{ + GasLimit: big.NewInt(8_000_000), + MinBaseFee: big.NewInt(25_000_000_000), + TargetGas: big.NewInt(15_000_000), + BaseFeeChangeDenominator: big.NewInt(36), + MinBlockGasCost: big.NewInt(0), + MaxBlockGasCost: big.NewInt(1_000_000), + TargetBlockRate: 2, + BlockGasCostStep: big.NewInt(200_000), + } +) diff --git a/sdk/wallet/wallet.go b/sdk/wallet/wallet.go new file mode 100644 index 000000000..2037ba16d --- /dev/null +++ b/sdk/wallet/wallet.go @@ -0,0 +1,69 @@ +// Copyright (C) 2024, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package wallet + +import ( + "context" + + "github.com/ava-labs/avalanche-cli/sdk/keychain" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/vms/secp256k1fx" + "github.com/ava-labs/avalanchego/wallet/subnet/primary" + "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" +) + +type Wallet struct { + primary.Wallet + Keychain keychain.Keychain + options []common.Option + config *primary.WalletConfig +} + +func New(ctx context.Context, config *primary.WalletConfig) (Wallet, error) { + wallet, err := primary.MakeWallet( + ctx, + config, + ) + return Wallet{ + Wallet: wallet, + Keychain: keychain.Keychain{ + Keychain: config.AVAXKeychain, + }, + config: config, + }, err +} + +// SecureWalletIsChangeOwner ensures that a fee paying address (wallet's keychain) will receive +// the change UTXO and not a randomly selected auth key that may not be paying fees +func (w *Wallet) SecureWalletIsChangeOwner() { + addrs := w.Addresses() + changeAddr := addrs[0] + // sets change to go to wallet addr (instead of any other subnet auth key) + changeOwner := &secp256k1fx.OutputOwners{ + Threshold: 1, + Addrs: []ids.ShortID{changeAddr}, + } + w.options = append(w.options, common.WithChangeOwner(changeOwner)) + w.Wallet = primary.NewWalletWithOptions(w.Wallet, w.options...) +} + +// SetAuthKeys sets auth keys that will be used when signing txs, besides the wallet's Keychain fee +// paying ones +func (w *Wallet) SetAuthKeys(authKeys []ids.ShortID) { + addrs := w.Addresses() + addrsSet := set.Set[ids.ShortID]{} + addrsSet.Add(addrs...) + addrsSet.Add(authKeys...) + w.options = append(w.options, common.WithCustomAddresses(addrsSet)) + w.Wallet = primary.NewWalletWithOptions(w.Wallet, w.options...) +} + +func (w *Wallet) SetSubnetAuthMultisig(authKeys []ids.ShortID) { + w.SecureWalletIsChangeOwner() + w.SetAuthKeys(authKeys) +} + +func (w *Wallet) Addresses() []ids.ShortID { + return w.Keychain.Addresses().List() +} diff --git a/tests/e2e/commands/constants.go b/tests/e2e/commands/constants.go index 8aad6a43b..007a70962 100644 --- a/tests/e2e/commands/constants.go +++ b/tests/e2e/commands/constants.go @@ -4,13 +4,9 @@ package commands const ( - CLIBinary = "./bin/avalanche" - SubnetCmd = "subnet" - NetworkCmd = "network" - KeyCmd = "key" - UpgradeCmd = "upgrade" - ElasticTransformCmd = "elastic" - JoinCmd = "join" - RemoveValidatorCmd = "removeValidator" - AddPermissionlessDelegatorCmd = "addPermissionlessDelegator" + CLIBinary = "./bin/avalanche" + SubnetCmd = "subnet" + NetworkCmd = "network" + KeyCmd = "key" + UpgradeCmd = "upgrade" ) diff --git a/tests/e2e/commands/subnet.go b/tests/e2e/commands/subnet.go index 514a794c9..ccd8b3b97 100644 --- a/tests/e2e/commands/subnet.go +++ b/tests/e2e/commands/subnet.go @@ -8,9 +8,7 @@ import ( "fmt" "os" "os/exec" - "path/filepath" "strings" - "time" "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -180,18 +178,6 @@ func DeleteSubnetConfig(subnetName string) { gomega.Expect(exists).Should(gomega.BeFalse()) } -func DeleteElasticSubnetConfig(subnetName string) { - var err error - elasticSubnetConfig := filepath.Join(utils.GetBaseDir(), constants.SubnetDir, subnetName, constants.ElasticSubnetConfigFileName) - if _, err = os.Stat(elasticSubnetConfig); errors.Is(err, os.ErrNotExist) { - // does *not* exist - err = nil - } else { - err = os.Remove(elasticSubnetConfig) - } - gomega.Expect(err).Should(gomega.BeNil()) -} - // Returns the deploy output /* #nosec G204 */ func DeploySubnetLocally(subnetName string) string { @@ -567,103 +553,6 @@ func SimulateFujiRemoveValidator( return string(output) } -func SimulateFujiTransformSubnet( - subnetName string, - key string, -) (string, error) { - // Check config exists - exists, err := utils.SubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - // enable simulation of public network execution paths on a local network - err = os.Setenv(constants.SimulatePublicNetwork, "true") - gomega.Expect(err).Should(gomega.BeNil()) - cmd := exec.Command( - CLIBinary, - SubnetCmd, - ElasticTransformCmd, - "--fuji", - "--key", - key, - "--tokenName", - "BLIZZARD", - "--tokenSymbol", - "BRRR", - "--denomination", - "0", - "--default", - "--force", - subnetName, - ) - output, err := cmd.CombinedOutput() - if err != nil { - fmt.Println(cmd.String()) - fmt.Println(string(output)) - utils.PrintStdErr(err) - err2 := os.Unsetenv(constants.SimulatePublicNetwork) - gomega.Expect(err2).Should(gomega.BeNil()) - return "", err - } - - // disable simulation of public network execution paths on a local network - err = os.Unsetenv(constants.SimulatePublicNetwork) - gomega.Expect(err).Should(gomega.BeNil()) - - return string(output), nil -} - -func SimulateFujiAddPermissionlessValidator( - subnetName string, - key string, - nodeID string, - stakeAmount string, - stakingPeriod string, -) (string, error) { - // Check config exists - exists, err := utils.SubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - // enable simulation of public network execution paths on a local network - err = os.Setenv(constants.SimulatePublicNetwork, "true") - gomega.Expect(err).Should(gomega.BeNil()) - startTimeStr := time.Now().Add(constants.StakingStartLeadTime).UTC().Format(constants.TimeParseLayout) - cmd := exec.Command( - CLIBinary, - SubnetCmd, - JoinCmd, - "--fuji", - "--key", - key, - "--elastic", - "--nodeID", - nodeID, - "--stake-amount", - stakeAmount, - "--start-time", - startTimeStr, - "--staking-period", - stakingPeriod, - subnetName, - ) - output, err := cmd.CombinedOutput() - if err != nil { - fmt.Println(cmd.String()) - fmt.Println(string(output)) - utils.PrintStdErr(err) - err2 := os.Unsetenv(constants.SimulatePublicNetwork) - gomega.Expect(err2).Should(gomega.BeNil()) - return "", err - } - - // disable simulation of public network execution paths on a local network - err = os.Unsetenv(constants.SimulatePublicNetwork) - gomega.Expect(err).Should(gomega.BeNil()) - - return string(output), nil -} - // simulates mainnet add validator execution path on a local network /* #nosec G204 */ func SimulateMainnetAddValidator( @@ -948,126 +837,6 @@ func SimulateGetSubnetStatsFuji(subnetName, subnetID string) string { return string(output) } -func TransformElasticSubnetLocally(subnetName string) (string, error) { - // Check config exists - exists, err := utils.SubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - cmd := exec.Command( - CLIBinary, - SubnetCmd, - ElasticTransformCmd, - "--local", - "--tokenName", - "BLIZZARD", - "--tokenSymbol", - "BRRR", - "--default", - "--force", - subnetName, - ) - output, err := cmd.CombinedOutput() - if err != nil { - var stderr string - fmt.Println(string(output)) - utils.PrintStdErr(err) - fmt.Println(stderr) - } - return string(output), err -} - -func TransformElasticSubnetLocallyandTransformValidators(subnetName string, stakeAmount string) (string, error) { - // Check config exists - exists, err := utils.SubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - cmd := exec.Command( - CLIBinary, - SubnetCmd, - ElasticTransformCmd, - "--local", - "--tokenName", - "BLIZZARD", - "--tokenSymbol", - "BRRR", - "--default", - "--force", - "--transform-validators", - "--stake-amount", - stakeAmount, - subnetName, - ) - output, err := cmd.CombinedOutput() - if err != nil { - var stderr string - fmt.Println(string(output)) - utils.PrintStdErr(err) - fmt.Println(stderr) - } - return string(output), err -} - -func RemoveValidator(subnetName string, nodeID string) (string, error) { - // Check config exists - exists, err := utils.SubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - cmd := exec.Command( - CLIBinary, - SubnetCmd, - RemoveValidatorCmd, - "--local", - "--nodeID", - nodeID, - subnetName, - ) - output, err := cmd.CombinedOutput() - if err != nil { - var stderr string - fmt.Println(string(output)) - utils.PrintStdErr(err) - fmt.Println(stderr) - } - return string(output), err -} - -func AddPermissionlessValidator(subnetName string, nodeID string, stakeAmount string, stakingPeriod string) (string, error) { - // Check config exists - exists, err := utils.SubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - startTimeStr := time.Now().Add(constants.StakingStartLeadTime).UTC().Format(constants.TimeParseLayout) - cmd := exec.Command( - CLIBinary, - SubnetCmd, - JoinCmd, - "--local", - "--elastic", - "--nodeID", - nodeID, - "--stake-amount", - stakeAmount, - "--start-time", - startTimeStr, - "--staking-period", - stakingPeriod, - subnetName, - ) - - output, err := cmd.CombinedOutput() - if err != nil { - var stderr string - fmt.Println(string(output)) - utils.PrintStdErr(err) - fmt.Println(stderr) - } - return string(output), err -} - /* #nosec G204 */ func ListValidators(subnetName string, network string) (string, error) { // Create config @@ -1083,35 +852,3 @@ func ListValidators(subnetName string, network string) (string, error) { out, err := cmd.CombinedOutput() return string(out), err } - -func AddPermissionlessDelegator(subnetName string, nodeID string, stakeAmount string, stakingPeriod string) (string, error) { - // Check config exists - exists, err := utils.SubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - startTimeStr := time.Now().Add(constants.StakingStartLeadTime).UTC().Format(constants.TimeParseLayout) - - cmd := exec.Command( - CLIBinary, - SubnetCmd, - AddPermissionlessDelegatorCmd, - "--local", - "--nodeID", - nodeID, - "--stake-amount", - stakeAmount, - "--start-time", - startTimeStr, - "--staking-period", - stakingPeriod, - subnetName, - ) - output, err := cmd.CombinedOutput() - if err != nil { - var stderr string - fmt.Println(string(output)) - utils.PrintStdErr(err) - fmt.Println(stderr) - } - return string(output), err -} diff --git a/tests/e2e/testcases/key/suite.go b/tests/e2e/testcases/key/suite.go index f58cee30d..a13cd4c95 100644 --- a/tests/e2e/testcases/key/suite.go +++ b/tests/e2e/testcases/key/suite.go @@ -269,7 +269,7 @@ var _ = ginkgo.Describe("[Key]", func() { amount := 0.2 amountStr := fmt.Sprintf("%.2f", amount) - feeNAvax := genesis.LocalParams.StaticConfig.TxFee * 4 + feeNAvax := genesis.LocalParams.TxFeeConfig.StaticFeeConfig.TxFee * 1 amountNAvax := uint64(amount * float64(units.Avax)) // send/receive without recovery @@ -294,13 +294,7 @@ var _ = ginkgo.Describe("[Key]", func() { _, ewoqKeyBalance2, err := utils.ParseAddrBalanceFromKeyListOutput(output, ewoqKeyName) gomega.Expect(err).Should(gomega.BeNil()) gomega.Expect(ewoqKeyBalance1 - ewoqKeyBalance2).Should(gomega.Equal(feeNAvax + amountNAvax)) - gomega.Expect(keyBalance2 - keyBalance1).Should(gomega.Equal(uint64(0))) - - output, err = commands.KeyTransferReceive(keyName, amountStr, "0") - if err != nil { - fmt.Println(output) - } - gomega.Expect(err).Should(gomega.BeNil()) + gomega.Expect(keyBalance2 - keyBalance1).Should(gomega.Equal(amountNAvax)) output, err = commands.ListKeys("local", true, true) gomega.Expect(err).Should(gomega.BeNil()) @@ -310,65 +304,5 @@ var _ = ginkgo.Describe("[Key]", func() { gomega.Expect(err).Should(gomega.BeNil()) gomega.Expect(ewoqKeyBalance1 - ewoqKeyBalance3).Should(gomega.Equal(feeNAvax + amountNAvax)) gomega.Expect(keyBalance3 - keyBalance1).Should(gomega.Equal(amountNAvax)) - - // send/receive with recovery 1 - - output, err = commands.ListKeys("local", true, true) - gomega.Expect(err).Should(gomega.BeNil()) - keyAddr, keyBalance1, err = utils.ParseAddrBalanceFromKeyListOutput(output, keyName) - gomega.Expect(err).Should(gomega.BeNil()) - _, ewoqKeyBalance1, err = utils.ParseAddrBalanceFromKeyListOutput(output, ewoqKeyName) - gomega.Expect(err).Should(gomega.BeNil()) - - output, err = commands.KeyTransferSend(ewoqKeyName, keyAddr, amountStr) - if err != nil { - fmt.Println(output) - } - gomega.Expect(err).Should(gomega.BeNil()) - - output, err = commands.ListKeys("local", true, true) - gomega.Expect(err).Should(gomega.BeNil()) - _, keyBalance2, err = utils.ParseAddrBalanceFromKeyListOutput(output, keyName) - gomega.Expect(err).Should(gomega.BeNil()) - _, ewoqKeyBalance2, err = utils.ParseAddrBalanceFromKeyListOutput(output, ewoqKeyName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(ewoqKeyBalance1 - ewoqKeyBalance2).Should(gomega.Equal(feeNAvax + amountNAvax)) - gomega.Expect(keyBalance2 - keyBalance1).Should(gomega.Equal(uint64(0))) - - output, err = commands.KeyTransferReceive(keyName, "0.3", "0") // make 2nd step to fail with bad amount - if err == nil { - fmt.Println(output) - } - gomega.Expect(err).Should(gomega.HaveOccurred()) - - output, err = commands.ListKeys("local", true, true) - gomega.Expect(err).Should(gomega.BeNil()) - _, keyBalance3, err = utils.ParseAddrBalanceFromKeyListOutput(output, keyName) - gomega.Expect(err).Should(gomega.BeNil()) - _, ewoqKeyBalance3, err = utils.ParseAddrBalanceFromKeyListOutput(output, ewoqKeyName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(ewoqKeyBalance1 - ewoqKeyBalance3).Should(gomega.Equal(feeNAvax + amountNAvax)) - gomega.Expect(keyBalance3 - keyBalance1).Should(gomega.Equal(uint64(0))) - - output, err = commands.KeyTransferReceive(keyName, amountStr, "1") // do recovery of 2nd step - if err != nil { - fmt.Println(output) - } - gomega.Expect(err).Should(gomega.BeNil()) - - output, err = commands.ListKeys("local", true, true) - gomega.Expect(err).Should(gomega.BeNil()) - _, keyBalance3, err = utils.ParseAddrBalanceFromKeyListOutput(output, keyName) - gomega.Expect(err).Should(gomega.BeNil()) - _, ewoqKeyBalance3, err = utils.ParseAddrBalanceFromKeyListOutput(output, ewoqKeyName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(ewoqKeyBalance1 - ewoqKeyBalance3).Should(gomega.Equal(feeNAvax + amountNAvax)) - gomega.Expect(keyBalance3 - keyBalance1).Should(gomega.Equal(amountNAvax)) - - err = utils.DeleteKey(keyName) - gomega.Expect(err).Should(gomega.BeNil()) - err = utils.DeleteKey(ewoqKeyName) - gomega.Expect(err).Should(gomega.BeNil()) - commands.CleanNetwork() }) }) diff --git a/tests/e2e/testcases/node/devnet/suite.go b/tests/e2e/testcases/node/devnet/suite.go index 6b4236073..3915b3211 100644 --- a/tests/e2e/testcases/node/devnet/suite.go +++ b/tests/e2e/testcases/node/devnet/suite.go @@ -34,11 +34,6 @@ const ( ) var _ = ginkgo.Describe("[Node devnet]", func() { - ginkgo.It("can't create a fuji node with devnet api", func() { - output := commands.NodeCreate("fuji", "", 1, false, 1, commands.ExpectFail) - fmt.Println(output) - gomega.Expect(output).To(gomega.ContainSubstring("Error: API nodes can only be created in Devnet")) - }) ginkgo.It("can create a node", func() { outputB, err := ansi.Strip([]byte(commands.NodeDevnet(avalanchegoVersion, NumNodes, NumAPINodes))) gomega.Expect(err).Should(gomega.BeNil()) @@ -114,13 +109,7 @@ var _ = ginkgo.Describe("[Node devnet]", func() { // make sure there is no API node in the genesis gomega.Expect(genesisFile).To(gomega.Not(gomega.ContainSubstring(apiNodeID))) }) - ginkgo.It("installs and configures avalanche-cli on the node ", func() { - stakingFiles := commands.NodeSSH(constants.E2EClusterName, "cat /home/ubuntu/.avalanche-cli/config.json") - gomega.Expect(stakingFiles).To(gomega.ContainSubstring("\"metricsenabled\": false")) - avalanceCliVersion := commands.NodeSSH(constants.E2EClusterName, "/home/ubuntu/bin/avalanche --version") - gomega.Expect(avalanceCliVersion).To(gomega.ContainSubstring("avalanche version")) - }) - ginkgo.It("can waitßß 20 seconds for avago to startup", func() { + ginkgo.It("can wait for 20 seconds for avago to startup", func() { time.Sleep(20 * time.Second) }) /*ginkgo.It("can get cluster status", func() { diff --git a/tests/e2e/testcases/node/monitoring/suite.go b/tests/e2e/testcases/node/monitoring/suite.go index 2aeacee9f..ab571088c 100644 --- a/tests/e2e/testcases/node/monitoring/suite.go +++ b/tests/e2e/testcases/node/monitoring/suite.go @@ -10,7 +10,6 @@ import ( "os/user" "path/filepath" "regexp" - "strings" "github.com/ava-labs/avalanche-cli/pkg/ansible" "github.com/ava-labs/avalanche-cli/pkg/constants" @@ -156,16 +155,6 @@ var _ = ginkgo.Describe("[Node monitoring]", func() { gomega.Expect(sshOutput).To(gomega.ContainSubstring("chunks_directory: /var/lib/loki/chunks")) gomega.Expect(sshOutput).To(gomega.ContainSubstring("store: tsdb")) }) - ginkgo.It("installs and runs avalanchego", func() { - avalancegoProcess := commands.NodeSSH(constants.E2EClusterName, "ps -elf") - gomega.Expect(avalancegoProcess).To(gomega.ContainSubstring("/home/ubuntu/avalanche-node/avalanchego")) - }) - ginkgo.It("installs latest version of avalanchego", func() { - avalanchegoVersionClean := strings.TrimPrefix(avalanchegoVersion, "v") - avalancegoVersion := commands.NodeSSH(constants.E2EClusterName, "/home/ubuntu/avalanche-node/avalanchego --version") - gomega.Expect(avalancegoVersion).To(gomega.ContainSubstring("go=")) - gomega.Expect(avalancegoVersion).To(gomega.ContainSubstring("avalanchego/" + avalanchegoVersionClean)) - }) ginkgo.It("configured avalanchego", func() { avalancegoConfig := commands.NodeSSH(constants.E2EClusterName, "cat /home/ubuntu/.avalanchego/configs/node.json") gomega.Expect(avalancegoConfig).To(gomega.ContainSubstring("\"network-id\": \"" + network + "\"")) @@ -179,12 +168,6 @@ var _ = ginkgo.Describe("[Node monitoring]", func() { gomega.Expect(stakingFiles).To(gomega.ContainSubstring("staker.crt")) gomega.Expect(stakingFiles).To(gomega.ContainSubstring("staker.key")) }) - ginkgo.It("installs and configures avalanche-cli on the node ", func() { - stakingFiles := commands.NodeSSH(constants.E2EClusterName, "cat /home/ubuntu/.avalanche-cli/config.json") - gomega.Expect(stakingFiles).To(gomega.ContainSubstring("\"metricsenabled\": false")) - avalanceCliVersion := commands.NodeSSH(constants.E2EClusterName, "/home/ubuntu/bin/avalanche --version") - gomega.Expect(avalanceCliVersion).To(gomega.ContainSubstring("avalanche version")) - }) ginkgo.It("can get cluster status", func() { output := commands.NodeStatus() fmt.Println(output) diff --git a/tests/e2e/testcases/subnet/local/suite.go b/tests/e2e/testcases/subnet/local/suite.go index a49db6686..235629127 100644 --- a/tests/e2e/testcases/subnet/local/suite.go +++ b/tests/e2e/testcases/subnet/local/suite.go @@ -11,7 +11,6 @@ import ( "path" "strconv" "strings" - "time" "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/tests/e2e/commands" @@ -105,124 +104,6 @@ var _ = ginkgo.Describe("[Local Subnet]", ginkgo.Ordered, func() { commands.DeleteSubnetConfig(subnetName) }) - ginkgo.It("can transform a deployed SubnetEvm subnet to elastic subnet only once", func() { - commands.CreateSubnetEvmConfig(subnetName, utils.SubnetEvmGenesisPath) - deployOutput := commands.DeploySubnetLocally(subnetName) - rpcs, err := utils.ParseRPCsFromOutput(deployOutput) - if err != nil { - fmt.Println(deployOutput) - } - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(rpcs).Should(gomega.HaveLen(1)) - rpc := rpcs[0] - - err = utils.SetHardhatRPC(rpc) - gomega.Expect(err).Should(gomega.BeNil()) - - err = utils.RunHardhatTests(utils.BaseTest) - gomega.Expect(err).Should(gomega.BeNil()) - - // GetCurrentSupply will return error if queried for non-elastic subnet - err = utils.GetCurrentSupply(subnetName) - gomega.Expect(err).Should(gomega.HaveOccurred()) - - _, err = commands.TransformElasticSubnetLocally(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - exists, err := utils.ElasticSubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - // GetCurrentSupply will return result if queried for elastic subnet - err = utils.GetCurrentSupply(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - - _, err = commands.TransformElasticSubnetLocally(subnetName) - gomega.Expect(err).Should(gomega.HaveOccurred()) - - commands.DeleteSubnetConfig(subnetName) - commands.DeleteElasticSubnetConfig(subnetName) - }) - - ginkgo.It("can transform subnet to elastic subnet and automatically transform validators to permissionless", func() { - commands.CreateSubnetEvmConfig(subnetName, utils.SubnetEvmGenesisPath) - deployOutput := commands.DeploySubnetLocally(subnetName) - _, err = utils.ParseRPCsFromOutput(deployOutput) - if err != nil { - fmt.Println(deployOutput) - } - gomega.Expect(err).Should(gomega.BeNil()) - - _, err = commands.TransformElasticSubnetLocallyandTransformValidators(subnetName, stakeAmount) - gomega.Expect(err).Should(gomega.BeNil()) - - // GetCurrentSupply will return result if queried for elastic subnet - err = utils.GetCurrentSupply(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - - // wait for the last node to be current validator - time.Sleep(constants.StakingMinimumLeadTime) - - areCurrentValidators, err := utils.CheckAllNodesAreCurrentValidators(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(areCurrentValidators).Should(gomega.BeTrue()) - - exists, err := utils.AllPermissionlessValidatorExistsInSidecar(subnetName, localNetwork) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - commands.DeleteSubnetConfig(subnetName) - commands.DeleteElasticSubnetConfig(subnetName) - }) - - ginkgo.It("can add permissionless validator to elastic subnet and delegate to it", func() { - commands.CreateSubnetEvmConfig(subnetName, utils.SubnetEvmGenesisPath) - deployOutput := commands.DeploySubnetLocally(subnetName) - _, err := utils.ParseRPCsFromOutput(deployOutput) - if err != nil { - fmt.Println(deployOutput) - } - gomega.Expect(err).Should(gomega.BeNil()) - - _, err = commands.TransformElasticSubnetLocally(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - - nodeIDs, err := utils.GetValidators(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(len(nodeIDs)).Should(gomega.Equal(5)) - - _, err = commands.RemoveValidator(subnetName, nodeIDs[0]) - gomega.Expect(err).Should(gomega.BeNil()) - - _, err = commands.AddPermissionlessValidator(subnetName, nodeIDs[0], stakeAmount, stakeDuration) - gomega.Expect(err).Should(gomega.BeNil()) - exists, err := utils.PermissionlessValidatorExistsInSidecar(subnetName, nodeIDs[0], localNetwork) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - isValidator, err := utils.IsNodeInValidators(subnetName, nodeIDs[0]) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(isValidator).Should(gomega.BeTrue()) - - _, err = commands.RemoveValidator(subnetName, nodeIDs[1]) - gomega.Expect(err).Should(gomega.BeNil()) - - _, err = commands.AddPermissionlessValidator(subnetName, nodeIDs[1], stakeAmount, stakeDuration) - gomega.Expect(err).Should(gomega.BeNil()) - exists, err = utils.PermissionlessValidatorExistsInSidecar(subnetName, nodeIDs[1], localNetwork) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - isValidator, err = utils.IsNodeInValidators(subnetName, nodeIDs[1]) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(isValidator).Should(gomega.BeTrue()) - - _, err = commands.AddPermissionlessDelegator(subnetName, nodeIDs[1], delegateAmount, delegateDuration) - gomega.Expect(err).Should(gomega.BeNil()) - - commands.DeleteSubnetConfig(subnetName) - commands.DeleteElasticSubnetConfig(subnetName) - }) - ginkgo.It("can load viper config and setup node properties for local deploy", func() { commands.CreateSubnetEvmConfig(subnetName, utils.SubnetEvmGenesisPath) deployOutput := commands.DeploySubnetLocallyWithViperConf(subnetName, confPath) diff --git a/tests/e2e/testcases/subnet/public/suite.go b/tests/e2e/testcases/subnet/public/suite.go index 637c51b60..a1f829cf9 100644 --- a/tests/e2e/testcases/subnet/public/suite.go +++ b/tests/e2e/testcases/subnet/public/suite.go @@ -105,7 +105,7 @@ var _ = ginkgo.Describe("[Public Subnet]", func() { } // fund ledger address genesisParams := genesis.MainnetParams - err := utils.FundLedgerAddress(genesisParams.CreateSubnetTxFee + genesisParams.CreateBlockchainTxFee + genesisParams.TxFee) + err := utils.FundLedgerAddress(genesisParams.TxFeeConfig.StaticFeeConfig.CreateSubnetTxFee + genesisParams.TxFeeConfig.StaticFeeConfig.CreateBlockchainTxFee + genesisParams.TxFeeConfig.StaticFeeConfig.TxFee) gomega.Expect(err).Should(gomega.BeNil()) fmt.Println() fmt.Println(logging.LightRed.Wrap("DEPLOYING SUBNET. VERIFY LEDGER ADDRESS HAS CUSTOM HRP BEFORE SIGNING")) @@ -168,44 +168,6 @@ var _ = ginkgo.Describe("[Public Subnet]", func() { gomega.Expect(subnetMainnetChainID).Should(gomega.Equal(uint(mainnetChainID))) }) - ginkgo.It("can transform a deployed SubnetEvm subnet to elastic subnet only on fuji", func() { - subnetIDStr, _ := deploySubnetToFuji() - subnetID, err := ids.FromString(subnetIDStr) - gomega.Expect(err).Should(gomega.BeNil()) - - // GetCurrentSupply will return error if queried for non-elastic subnet - err = subnet.GetCurrentSupply(subnetID) - gomega.Expect(err).Should(gomega.HaveOccurred()) - - _, err = commands.SimulateFujiTransformSubnet(subnetName, keyName) - gomega.Expect(err).Should(gomega.BeNil()) - exists, err := utils.ElasticSubnetConfigExists(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - // GetCurrentSupply will return result if queried for elastic subnet - err = subnet.GetCurrentSupply(subnetID) - gomega.Expect(err).Should(gomega.BeNil()) - - _, err = commands.SimulateFujiTransformSubnet(subnetName, keyName) - gomega.Expect(err).Should(gomega.HaveOccurred()) - - nodeIDs, err := utils.GetValidators(subnetName) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(len(nodeIDs)).Should(gomega.Equal(5)) - - _, err = commands.RemoveValidator(subnetName, nodeIDs[0]) - gomega.Expect(err).Should(gomega.BeNil()) - - _, err = commands.SimulateFujiAddPermissionlessValidator(subnetName, keyName, nodeIDs[0], stakeAmount, stakeDuration) - gomega.Expect(err).Should(gomega.BeNil()) - exists, err = utils.PermissionlessValidatorExistsInSidecar(subnetName, nodeIDs[0], localNetwork) - gomega.Expect(err).Should(gomega.BeNil()) - gomega.Expect(exists).Should(gomega.BeTrue()) - - commands.DeleteElasticSubnetConfig(subnetName) - }) - ginkgo.It("remove validator fuji", func() { subnetIDStr, nodeInfos := deploySubnetToFuji() @@ -301,7 +263,7 @@ var _ = ginkgo.Describe("[Public Subnet]", func() { // let's fund the ledger genesisParams := genesis.MainnetParams - err = utils.FundLedgerAddress(genesisParams.CreateSubnetTxFee + genesisParams.CreateBlockchainTxFee + genesisParams.TxFee) + err = utils.FundLedgerAddress(genesisParams.TxFeeConfig.StaticFeeConfig.CreateSubnetTxFee + genesisParams.TxFeeConfig.StaticFeeConfig.CreateBlockchainTxFee + genesisParams.TxFeeConfig.StaticFeeConfig.TxFee) gomega.Expect(err).Should(gomega.BeNil()) // multisig deploy from funded ledger1 should create the subnet but not deploy the blockchain, diff --git a/tests/e2e/testcases/upgrade/suite.go b/tests/e2e/testcases/upgrade/suite.go index b7cd91663..52bebc2a4 100644 --- a/tests/e2e/testcases/upgrade/suite.go +++ b/tests/e2e/testcases/upgrade/suite.go @@ -12,7 +12,7 @@ import ( "time" "unicode" - "github.com/ava-labs/avalanche-cli/cmd/subnetcmd/upgradecmd" + "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd/upgradecmd" "github.com/ava-labs/avalanche-cli/pkg/application" "github.com/ava-labs/avalanche-cli/pkg/binutils" "github.com/ava-labs/avalanche-cli/pkg/constants" diff --git a/tests/e2e/utils/helpers.go b/tests/e2e/utils/helpers.go index aca0c642c..f08c6786a 100644 --- a/tests/e2e/utils/helpers.go +++ b/tests/e2e/utils/helpers.go @@ -125,29 +125,6 @@ func sidecarExists(subnetName string) (bool, error) { 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 { @@ -485,7 +462,8 @@ func RunLedgerSim( showStdout bool, ) error { cmd := exec.Command( - "ts-node", + "npx", + "tsx", basicLedgerSimScript, fmt.Sprintf("%d", iters), seed, @@ -1013,21 +991,6 @@ func CheckAllNodesAreCurrentValidators(subnetName string) (bool, error) { 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 -} - func GetTmpFilePath(fnamePrefix string) (string, error) { file, err := os.CreateTemp("", fnamePrefix+"*") if err != nil {