diff --git a/Dockerfile b/Dockerfile index 9c0138559..d38cb693c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # ============= Compilation Stage ================ -FROM golang:1.22.8-bullseye AS builder +FROM golang:1.22.10-bookworm AS builder WORKDIR /build # Copy and download avalanche dependencies using go mod @@ -12,7 +12,7 @@ COPY . . RUN ./scripts/build.sh # ============= Cleanup Stage ================ -FROM debian:11-slim +FROM debian:12-slim WORKDIR / # Copy the executables into the container diff --git a/Dockerfile.release b/Dockerfile.release index 509665106..18c5a1428 100644 --- a/Dockerfile.release +++ b/Dockerfile.release @@ -1,4 +1,4 @@ -FROM debian:11-slim +FROM debian:12-slim RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/* COPY avalanche / RUN /avalanche config update disable diff --git a/VERSION b/VERSION index afa2b3515..b9268dae2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.0 \ No newline at end of file +1.8.1 \ No newline at end of file diff --git a/cmd/blockchaincmd/add_validator.go b/cmd/blockchaincmd/add_validator.go index 9b9e937d7..31059c702 100644 --- a/cmd/blockchaincmd/add_validator.go +++ b/cmd/blockchaincmd/add_validator.go @@ -95,7 +95,7 @@ Testnet or Mainnet.`, cmd.Flags().StringVarP(&keyName, "key", "k", "", "select the key to use [fuji/devnet only]") cmd.Flags().Uint64Var(&weight, "weight", constants.NonBootstrapValidatorWeight, "set the staking weight of the validator to add") - cmd.Flags().Uint64Var(&balance, "balance", 0, "set the AVAX balance of the validator that will be used for continuous fee to P-Chain") + cmd.Flags().Uint64Var(&balance, "balance", 0, "set the AVAX balance of the validator that will be used for continuous fee on P-Chain") cmd.Flags().BoolVarP(&useEwoq, "ewoq", "e", false, "use ewoq key [fuji/devnet only]") cmd.Flags().BoolVarP(&useLedger, "ledger", "g", false, "use ledger instead of key (always true on mainnet, defaults to false on fuji/devnet)") cmd.Flags().StringSliceVar(&ledgerAddresses, "ledger-addrs", []string{}, "use the given ledger addresses") @@ -285,6 +285,7 @@ func addValidator(_ *cobra.Command, args []string) error { if err := prompts.ValidateNodeID(nodeIDStr); err != nil { return err } + if sovereign && publicKey == "" && pop == "" { publicKey, pop, err = promptProofOfPossession(true, true) if err != nil { @@ -404,13 +405,14 @@ func CallAddValidator( if err != nil { return err } - balance, err = promptValidatorBalance(availableBalance) + balance, err = promptValidatorBalance(availableBalance / units.Avax) if err != nil { return err } + } else { + // convert to nanoAVAX + balance *= units.Avax } - // convert to nanoAVAX - balance *= units.Avax if remainingBalanceOwnerAddr == "" { remainingBalanceOwnerAddr, err = getKeyForChangeOwner(network) @@ -680,22 +682,6 @@ func PromptDuration(start time.Time, network models.Network) (time.Duration, err } } -func getMaxValidationTime(network models.Network, nodeID ids.NodeID, startTime time.Time) (time.Duration, error) { - ctx, cancel := utils.GetAPIContext() - defer cancel() - platformCli := platformvm.NewClient(network.Endpoint) - vs, err := platformCli.GetCurrentValidators(ctx, avagoconstants.PrimaryNetworkID, nil) - if err != nil { - return 0, err - } - for _, v := range vs { - if v.NodeID == nodeID { - return time.Unix(int64(v.EndTime), 0).Sub(startTime), nil - } - } - return 0, errors.New("nodeID not found in validator set: " + nodeID.String()) -} - func getBlockchainTimestamp(network models.Network) (time.Time, error) { ctx, cancel := utils.GetAPIContext() defer cancel() @@ -781,7 +767,7 @@ func getTimeParameters(network models.Network, nodeID ids.NodeID, isValidator bo var selectedDuration time.Duration if useDefaultDuration { // avoid setting both globals useDefaultDuration and duration - selectedDuration, err = getMaxValidationTime(network, nodeID, start) + selectedDuration, err = utils.GetRemainingValidationTime(network.Endpoint, nodeID, avagoconstants.PrimaryNetworkID, start) if err != nil { return time.Time{}, 0, err } diff --git a/cmd/blockchaincmd/change_weight.go b/cmd/blockchaincmd/change_weight.go index a3fb5a949..18b496c67 100644 --- a/cmd/blockchaincmd/change_weight.go +++ b/cmd/blockchaincmd/change_weight.go @@ -67,10 +67,6 @@ func setWeight(_ *cobra.Command, args []string) error { return err } - if network.Kind == models.Mainnet && sc.Sovereign { - return errNotSupportedOnMainnet - } - if outputTxPath != "" { if _, err := os.Stat(outputTxPath); err == nil { return fmt.Errorf("outputTxPath %q already exists", outputTxPath) @@ -152,6 +148,8 @@ func setWeight(_ *cobra.Command, args []string) error { network, blockchainName, nodeID, + 0, // automatic uptime + isBootstrapValidatorForNetwork(nodeID, sc.Networks[network.Name()]), false, // don't force ) if err != nil { diff --git a/cmd/blockchaincmd/deploy.go b/cmd/blockchaincmd/deploy.go index 69cbd0725..5b1a58cc7 100644 --- a/cmd/blockchaincmd/deploy.go +++ b/cmd/blockchaincmd/deploy.go @@ -12,46 +12,44 @@ import ( "path/filepath" "time" - blockchainSDK "github.com/ava-labs/avalanche-cli/sdk/blockchain" - validatorManagerSDK "github.com/ava-labs/avalanche-cli/sdk/validatormanager" - - "github.com/ava-labs/avalanchego/api/info" - "github.com/ava-labs/avalanchego/config" - "github.com/ava-labs/avalanchego/network/peer" - "github.com/ava-labs/avalanche-cli/cmd/interchaincmd/messengercmd" "github.com/ava-labs/avalanche-cli/cmd/interchaincmd/relayercmd" "github.com/ava-labs/avalanche-cli/cmd/networkcmd" - "github.com/ava-labs/avalanche-cli/pkg/evm" - "github.com/ava-labs/avalanche-cli/pkg/keychain" - "github.com/ava-labs/avalanche-cli/pkg/node" - avagoutils "github.com/ava-labs/avalanchego/utils" - "github.com/ava-labs/avalanchego/utils/set" - "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" - "github.com/ethereum/go-ethereum/common" - - "github.com/ava-labs/avalanche-cli/pkg/contract" - "github.com/ava-labs/avalanche-cli/pkg/utils" - "github.com/ava-labs/avalanchego/utils/formatting/address" - "github.com/ava-labs/avalanchego/vms/platformvm/fx" - "github.com/ava-labs/avalanchego/vms/platformvm/signer" - "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/contract" + "github.com/ava-labs/avalanche-cli/pkg/evm" + "github.com/ava-labs/avalanche-cli/pkg/keychain" "github.com/ava-labs/avalanche-cli/pkg/localnet" "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/node" "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" + blockchainSDK "github.com/ava-labs/avalanche-cli/sdk/blockchain" + validatorManagerSDK "github.com/ava-labs/avalanche-cli/sdk/validatormanager" anrutils "github.com/ava-labs/avalanche-network-runner/utils" + "github.com/ava-labs/avalanchego/api/info" + "github.com/ava-labs/avalanchego/config" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/network/peer" + avagoutils "github.com/ava-labs/avalanchego/utils" + "github.com/ava-labs/avalanchego/utils/formatting/address" "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/utils/set" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanchego/vms/platformvm/fx" + "github.com/ava-labs/avalanchego/vms/platformvm/signer" "github.com/ava-labs/avalanchego/vms/platformvm/txs" + "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" + + "github.com/ethereum/go-ethereum/common" "github.com/olekukonko/tablewriter" "github.com/spf13/cobra" ) @@ -101,6 +99,7 @@ var ( cChainFundingKey string icmKeyName string cchainIcmKeyName string + relayerAllowPrivateIPs bool poSMinimumStakeAmount uint64 poSMaximumStakeAmount uint64 @@ -108,6 +107,7 @@ var ( poSMinimumDelegationFee uint16 poSMaximumStakeMultiplier uint8 poSWeightToValueFactor uint64 + deployBalanceAVAX float64 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") @@ -187,6 +187,7 @@ so you can take your locally tested Subnet and deploy it on Fuji or Mainnet.`, cmd.Flags().StringVar(&cchainIcmKeyName, "cchain-icm-key", "", "key to be used to pay for ICM deploys on C-Chain") cmd.Flags().BoolVar(&relayCChain, "relay-cchain", true, "relay C-Chain as source and destination") cmd.Flags().StringVar(&cChainFundingKey, "cchain-funding-key", "", "key to be used to fund relayer account on cchain") + cmd.Flags().BoolVar(&relayerAllowPrivateIPs, "relayer-allow-private-ips", true, "allow relayer to connec to private ips") cmd.Flags().StringVar(&icmSpec.MessengerContractAddressPath, "teleporter-messenger-contract-address-path", "", "path to an ICM Messenger contract address file") cmd.Flags().StringVar(&icmSpec.MessengerDeployerAddressPath, "teleporter-messenger-deployer-address-path", "", "path to an ICM Messenger deployer address file") cmd.Flags().StringVar(&icmSpec.MessengerDeployerTxPath, "teleporter-messenger-deployer-tx-path", "", "path to an ICM Messenger deployer tx file") @@ -200,6 +201,12 @@ so you can take your locally tested Subnet and deploy it on Fuji or Mainnet.`, cmd.Flags().BoolVar(&aggregatorAllowPrivatePeers, "aggregator-allow-private-peers", true, "allow the signature aggregator to connect to peers with private IP") cmd.Flags().BoolVar(&useLocalMachine, "use-local-machine", false, "use local machine as a blockchain validator") cmd.Flags().IntVar(&numBootstrapValidators, "num-bootstrap-validators", 0, "(only if --generate-node-id is true) number of bootstrap validators to set up in sovereign L1 validator)") + cmd.Flags().Float64Var( + &deployBalanceAVAX, + "balance", + float64(constants.BootstrapValidatorBalanceNanoAVAX)/float64(units.Avax), + "set the AVAX balance of each bootstrap validator that will be used for continuous fee on P-Chain", + ) cmd.Flags().IntVar(&numLocalNodes, "num-local-nodes", 0, "number of nodes to be created on local machine") cmd.Flags().StringVar(&changeOwnerAddress, "change-owner-address", "", "address that will receive change if node is no longer L1 validator") @@ -450,12 +457,6 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { } clusterNameFlagValue = globalNetworkFlags.ClusterName - if !simulatedPublicNetwork() { - if network.Kind == models.Mainnet && sidecar.Sovereign { - return errNotSupportedOnMainnet - } - } - isEVMGenesis, validationErr, err := app.HasSubnetEVMGenesis(chain) if err != nil { return err @@ -589,6 +590,13 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { return err } + availableBalance, err := utils.GetNetworkBalance(kc.Addresses().List(), network.Endpoint) + if err != nil { + return err + } + + deployBalance := uint64(deployBalanceAVAX * float64(units.Avax)) + if sidecar.Sovereign { if changeOwnerAddress == "" { // use provided key as change owner unless already set @@ -653,6 +661,15 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { } // if no cluster provided - we create one with fmt.Sprintf("%s-local-node", blockchainName) name if useLocalMachine && clusterNameFlagValue == "" { + requiredBalance := deployBalance * uint64(numLocalNodes) + if availableBalance < requiredBalance { + return fmt.Errorf( + "required balance for %d validators dynamic fee on PChain is %d but the given key has %d", + numLocalNodes, + requiredBalance, + availableBalance, + ) + } // stop local avalanchego process so that we can generate new local cluster _ = node.StopLocalNode(app) anrSettings := node.ANRSettings{} @@ -695,6 +712,9 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { if network.Kind == models.Fuji { globalNetworkFlags.UseFuji = true } + if network.Kind == models.Mainnet { + globalNetworkFlags.UseMainnet = true + } // anrSettings, avagoVersionSettings, globalNetworkFlags are empty if err = node.StartLocalNode( app, @@ -742,7 +762,7 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { bootstrapValidators = append(bootstrapValidators, models.SubnetValidator{ NodeID: nodeID.String(), Weight: constants.BootstrapValidatorWeight, - Balance: constants.BootstrapValidatorBalance, + Balance: deployBalance, BLSPublicKey: publicKey, BLSProofOfPossession: pop, ChangeOwnerAddr: changeOwnerAddress, @@ -750,13 +770,19 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { } case clusterNameFlagValue != "": // for remote clusters we don't need to ask for bootstrap validators and can read it from filesystem - bootstrapValidators, err = getClusterBootstrapValidators(clusterNameFlagValue, network) + bootstrapValidators, err = getClusterBootstrapValidators(clusterNameFlagValue, network, deployBalance) if err != nil { return fmt.Errorf("error getting bootstrap validators from cluster %s: %w", clusterNameFlagValue, err) } default: - bootstrapValidators, err = promptBootstrapValidators(network, changeOwnerAddress, numBootstrapValidators) + bootstrapValidators, err = promptBootstrapValidators( + network, + changeOwnerAddress, + numBootstrapValidators, + deployBalance, + availableBalance, + ) if err != nil { return err } @@ -770,6 +796,18 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { return errMutuallyExlusiveSubnetFlags } + if sidecar.Sovereign { + requiredBalance := deployBalance * uint64(len(bootstrapValidators)) + if availableBalance < requiredBalance { + return fmt.Errorf( + "required balance for %d validators dynamic fee on PChain is %d but the given key has %d", + len(bootstrapValidators), + requiredBalance, + availableBalance, + ) + } + } + network.HandlePublicNetworkSimulation() if createSubnet { @@ -931,7 +969,7 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { return err } ux.Logger.PrintToUser("") - + setBootstrapValidatorValidationID(avaGoBootstrapValidators, bootstrapValidators, subnetID) if err := app.UpdateSidecarNetworks( &sidecar, network, @@ -1050,8 +1088,8 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { aggregatorAllowPrivatePeers, logLvl, validatorManagerSDK.PoSParams{ - MinimumStakeAmount: utils.ApplyDefaultDenomination(poSMinimumStakeAmount), - MaximumStakeAmount: utils.ApplyDefaultDenomination(poSMaximumStakeAmount), + MinimumStakeAmount: big.NewInt(int64(poSMinimumStakeAmount)), + MaximumStakeAmount: big.NewInt(int64(poSMaximumStakeAmount)), MinimumStakeDuration: poSMinimumStakeDuration, MinimumDelegationFee: poSMinimumDelegationFee, MaximumStakeMultiplier: poSMaximumStakeMultiplier, @@ -1143,7 +1181,7 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { icmSpec.SkipRelayerDeploy = !yes } } - if !icmSpec.SkipRelayerDeploy && (network.Kind != models.Fuji && network.Kind != models.Mainnet) { + if !icmSpec.SkipRelayerDeploy && network.Kind != models.Mainnet { deployRelayerFlags := relayercmd.DeployFlags{ Version: icmSpec.RelayerVersion, BinPath: icmSpec.RelayerBinPath, @@ -1153,6 +1191,7 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { BlockchainsToRelay: []string{blockchainName}, Key: relayerKeyName, Amount: relayerAmount, + AllowPrivateIPs: relayerAllowPrivateIPs, } if network.Kind == models.Local || useLocalMachine { deployRelayerFlags.Key = constants.ICMRelayerKeyName @@ -1180,7 +1219,23 @@ func deployBlockchain(cmd *cobra.Command, args []string) error { return nil } -func getClusterBootstrapValidators(clusterName string, network models.Network) ([]models.SubnetValidator, error) { +func setBootstrapValidatorValidationID(avaGoBootstrapValidators []*txs.ConvertSubnetToL1Validator, bootstrapValidators []models.SubnetValidator, subnetID ids.ID) { + for index, avagoValidator := range avaGoBootstrapValidators { + for bootstrapValidatorIndex, validator := range bootstrapValidators { + avagoValidatorNodeID, _ := ids.ToNodeID(avagoValidator.NodeID) + if validator.NodeID == avagoValidatorNodeID.String() { + validationID := subnetID.Append(uint32(index)) + bootstrapValidators[bootstrapValidatorIndex].ValidationID = validationID.String() + } + } + } +} + +func getClusterBootstrapValidators( + clusterName string, + network models.Network, + deployBalance uint64, +) ([]models.SubnetValidator, error) { clusterConf, err := app.GetClusterConfig(clusterName) if err != nil { return nil, err @@ -1204,7 +1259,7 @@ func getClusterBootstrapValidators(clusterName string, network models.Network) ( subnetValidators = append(subnetValidators, models.SubnetValidator{ NodeID: nodeID.String(), Weight: constants.BootstrapValidatorWeight, - Balance: constants.BootstrapValidatorBalance, + Balance: deployBalance, BLSPublicKey: fmt.Sprintf("%s%s", "0x", hex.EncodeToString(pub)), BLSProofOfPossession: fmt.Sprintf("%s%s", "0x", hex.EncodeToString(pop)), ChangeOwnerAddr: changeAddr, diff --git a/cmd/blockchaincmd/helpers.go b/cmd/blockchaincmd/helpers.go index 8f539d7d9..8ee40ca17 100644 --- a/cmd/blockchaincmd/helpers.go +++ b/cmd/blockchaincmd/helpers.go @@ -85,13 +85,10 @@ func UpdateKeychainWithSubnetControlKeys( if subnetID == ids.Empty { return errNoSubnetID } - isPermissioned, controlKeys, _, err := txutils.GetOwners(network, subnetID) + _, controlKeys, _, 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 diff --git a/cmd/blockchaincmd/import_public.go b/cmd/blockchaincmd/import_public.go index dcb2d813d..8e75d240d 100644 --- a/cmd/blockchaincmd/import_public.go +++ b/cmd/blockchaincmd/import_public.go @@ -179,7 +179,7 @@ func importPublic(*cobra.Command, []string) error { // no node was queried, ask the user switch vmType { case models.SubnetEvm: - versions, err = app.Downloader.GetAllReleasesForRepo(constants.AvaLabsOrg, constants.SubnetEVMRepoName, application.All) + versions, err = app.Downloader.GetAllReleasesForRepo(constants.AvaLabsOrg, constants.SubnetEVMRepoName, "", application.All) if err != nil { return err } diff --git a/cmd/blockchaincmd/prompt_genesis_input.go b/cmd/blockchaincmd/prompt_genesis_input.go index 63d60bfa6..5605a3329 100644 --- a/cmd/blockchaincmd/prompt_genesis_input.go +++ b/cmd/blockchaincmd/prompt_genesis_input.go @@ -27,7 +27,7 @@ func getValidatorContractManagerAddr() (string, error) { "", models.UndefinedNetwork, prompts.EVMFormat, - "Enter address", + "Enter address (C-Chain address)", ) } @@ -119,13 +119,33 @@ func generateNewNodeAndBLS() (string, string, string, error) { return nodeID.String(), publicKey, pop, nil } -func promptBootstrapValidators(network models.Network, changeOwnerAddress string, numBootstrapValidators int) ([]models.SubnetValidator, error) { +func promptBootstrapValidators( + network models.Network, + changeOwnerAddress string, + numBootstrapValidators int, + validatorBalance uint64, + availableBalance uint64, +) ([]models.SubnetValidator, error) { var subnetValidators []models.SubnetValidator var err error if numBootstrapValidators == 0 { + maxNumValidators := availableBalance / validatorBalance numBootstrapValidators, err = app.Prompt.CaptureInt( "How many bootstrap validators do you want to set up?", - prompts.ValidatePositiveInt, + func(n int) error { + if err := prompts.ValidatePositiveInt(n); err != nil { + return err + } + if n > int(maxNumValidators) { + return fmt.Errorf( + "given available balance %d, the maximum number of validators with balance %d is %d", + availableBalance, + validatorBalance, + maxNumValidators, + ) + } + return nil + }, ) } if err != nil { @@ -173,7 +193,7 @@ func promptBootstrapValidators(network models.Network, changeOwnerAddress string subnetValidator := models.SubnetValidator{ NodeID: nodeID.String(), Weight: constants.BootstrapValidatorWeight, - Balance: constants.BootstrapValidatorBalance, + Balance: validatorBalance, BLSPublicKey: publicKey, BLSProofOfPossession: pop, ChangeOwnerAddr: changeOwnerAddress, diff --git a/cmd/blockchaincmd/remove_validator.go b/cmd/blockchaincmd/remove_validator.go index 54059861a..e59030858 100644 --- a/cmd/blockchaincmd/remove_validator.go +++ b/cmd/blockchaincmd/remove_validator.go @@ -22,8 +22,10 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" "github.com/ava-labs/avalanche-cli/pkg/validatormanager" + validatormanagerSDK "github.com/ava-labs/avalanche-cli/sdk/validatormanager" "github.com/ava-labs/avalanchego/genesis" "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/vms/platformvm/warp" "github.com/ava-labs/avalanchego/vms/secp256k1fx" "github.com/spf13/cobra" ) @@ -36,6 +38,11 @@ var removeValidatorSupportedNetworkOptions = []networkoptions.NetworkOption{ networkoptions.EtnaDevnet, } +var ( + uptimeSec uint64 + force bool +) + // avalanche blockchain removeValidator func newRemoveValidatorCmd() *cobra.Command { cmd := &cobra.Command{ @@ -62,6 +69,8 @@ these prompts by providing the values with flags.`, privateKeyFlags.AddToCmd(cmd, "to pay fees for completing the validator's removal (blockchain gas token)") cmd.Flags().StringVar(&rpcURL, "rpc", "", "connect to validator manager at the given rpc endpoint") cmd.Flags().StringVar(&aggregatorLogLevel, "aggregator-log-level", "Off", "log level to use with signature aggregator") + cmd.Flags().Uint64Var(&uptimeSec, "uptime", 0, "validator's uptime in seconds. If not provided, it will be automatically calculated") + cmd.Flags().BoolVar(&force, "force", false, "force validator removal even if it's not getting rewarded") return cmd } @@ -109,10 +118,6 @@ func removeValidator(_ *cobra.Command, args []string) error { } network.HandlePublicNetworkSimulation() - if sc.Sovereign && network.Kind == models.Mainnet { - return errNotSupportedOnMainnet - } - if !sc.Sovereign { if outputTxPath != "" { return errors.New("--output-tx-path flag cannot be used for non-SOV (Subnet-Only Validators) blockchains") @@ -176,15 +181,15 @@ func removeValidator(_ *cobra.Command, args []string) error { } return removeValidatorNonSOV(deployer, network, subnetID, kc, blockchainName, nodeID) } - // check if node is a bootstrap validator to force it to be removed - filteredBootstrapValidators := utils.Filter(scNetwork.BootstrapValidators, func(b models.SubnetValidator) bool { - if id, err := ids.NodeIDFromString(b.NodeID); err == nil && id == nodeID { - return true - } - return false - }) - force := len(filteredBootstrapValidators) > 0 - if err := removeValidatorSOV(deployer, network, blockchainName, nodeID, force); err != nil { + if err := removeValidatorSOV( + deployer, + network, + blockchainName, + nodeID, + uptimeSec, + isBootstrapValidatorForNetwork(nodeID, scNetwork), + force, + ); err != nil { return err } // remove the validator from the list of bootstrap validators @@ -203,11 +208,23 @@ func removeValidator(_ *cobra.Command, args []string) error { return nil } +func isBootstrapValidatorForNetwork(nodeID ids.NodeID, scNetwork models.NetworkData) bool { + filteredBootstrapValidators := utils.Filter(scNetwork.BootstrapValidators, func(b models.SubnetValidator) bool { + if id, err := ids.NodeIDFromString(b.NodeID); err == nil && id == nodeID { + return true + } + return false + }) + return len(filteredBootstrapValidators) > 0 +} + func removeValidatorSOV( deployer *subnet.PublicDeployer, network models.Network, blockchainName string, nodeID ids.NodeID, + uptimeSec uint64, + isBootstrapValidator bool, force bool, ) error { chainSpec := contract.ChainSpec{ @@ -254,7 +271,12 @@ func removeValidatorSOV( ux.Logger.PrintToUser(logging.Yellow.Wrap("Forcing removal of %s as it is a PoS bootstrap validator"), nodeID) } - signedMessage, validationID, err := validatormanager.InitValidatorRemoval( + var ( + signedMessage *warp.Message + validationID ids.ID + ) + // try to remove the validator. If err is "delegator ineligible for rewards" confirm with user and force remove + signedMessage, validationID, err = validatormanager.InitValidatorRemoval( app, network, rpcURL, @@ -265,13 +287,40 @@ func removeValidatorSOV( aggregatorAllowPrivatePeers, aggregatorLogLevel, sc.PoS(), - force, + uptimeSec, + isBootstrapValidator || force, ) - if err != nil { + if err != nil && errors.Is(err, validatormanagerSDK.ErrValidatorIneligibleForRewards) { + ux.Logger.PrintToUser("Calculated rewards is zero. Validator %s is not eligible for rewards", nodeID) + force, err = app.Prompt.CaptureNoYes("Do you want to continue with validator removal?") + if err != nil { + return err + } + if !force { + return fmt.Errorf("validator %s is not eligible for rewards. Use --force flag to force removal", nodeID) + } + signedMessage, validationID, err = validatormanager.InitValidatorRemoval( + app, + network, + rpcURL, + chainSpec, + ownerPrivateKey, + nodeID, + extraAggregatorPeers, + aggregatorAllowPrivatePeers, + aggregatorLogLevel, + sc.PoS(), + uptimeSec, + true, // force + ) + if err != nil { + return err + } + } else if err != nil { return err } - ux.Logger.PrintToUser("ValidationID: %s", validationID) + ux.Logger.PrintToUser("ValidationID: %s", validationID) txID, _, err := deployer.SetL1ValidatorWeight(signedMessage) if err != nil { return err @@ -291,20 +340,16 @@ func removeValidatorSOV( ); err != nil { return err } - ux.Logger.GreenCheckmarkToUser("Validator successfully removed from the Subnet") return nil } func removeValidatorNonSOV(deployer *subnet.PublicDeployer, network models.Network, subnetID ids.ID, kc *keychain.Keychain, blockchainName string, nodeID ids.NodeID) error { - isPermissioned, controlKeys, threshold, err := txutils.GetOwners(network, subnetID) + _, 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 { diff --git a/cmd/contractcmd/init_validator_manager.go b/cmd/contractcmd/init_validator_manager.go index 05afcb0d2..d064f8fb7 100644 --- a/cmd/contractcmd/init_validator_manager.go +++ b/cmd/contractcmd/init_validator_manager.go @@ -12,7 +12,6 @@ import ( "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/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" "github.com/ava-labs/avalanche-cli/pkg/validatormanager" blockchainSDK "github.com/ava-labs/avalanche-cli/sdk/blockchain" @@ -206,8 +205,8 @@ func initValidatorManager(_ *cobra.Command, args []string) error { validatorManagerFlags.aggregatorAllowPrivatePeers, validatorManagerFlags.aggregatorLogLevel, validatorManagerSDK.PoSParams{ - MinimumStakeAmount: utils.ApplyDefaultDenomination(initPOSManagerFlags.minimumStakeAmount), - MaximumStakeAmount: utils.ApplyDefaultDenomination(initPOSManagerFlags.maximumStakeAmount), + MinimumStakeAmount: big.NewInt(int64(initPOSManagerFlags.minimumStakeAmount)), + MaximumStakeAmount: big.NewInt(int64(initPOSManagerFlags.maximumStakeAmount)), MinimumStakeDuration: initPOSManagerFlags.minimumStakeDuration, MinimumDelegationFee: initPOSManagerFlags.minimumDelegationFee, MaximumStakeMultiplier: initPOSManagerFlags.maximumStakeMultiplier, diff --git a/cmd/interchaincmd/relayercmd/deploy.go b/cmd/interchaincmd/relayercmd/deploy.go index 1e93fc83b..a4831015c 100644 --- a/cmd/interchaincmd/relayercmd/deploy.go +++ b/cmd/interchaincmd/relayercmd/deploy.go @@ -37,6 +37,7 @@ type DeployFlags struct { BlockchainFundingKey string CChainFundingKey string BinPath string + AllowPrivateIPs bool } var ( @@ -62,7 +63,12 @@ func newDeployCmd() *cobra.Command { } networkoptions.AddNetworkFlagsToCmd(cmd, &deployFlags.Network, true, deploySupportedNetworkOptions) cmd.Flags().StringVar(&deployFlags.BinPath, "bin-path", "", "use the given relayer binary") - cmd.Flags().StringVar(&deployFlags.Version, "version", "latest", "version to deploy") + cmd.Flags().StringVar( + &deployFlags.Version, + "version", + constants.LatestPreReleaseVersionTag, + "version to deploy", + ) cmd.Flags().StringVar(&deployFlags.LogLevel, "log-level", "", "log level to use for relayer logs") cmd.Flags().StringSliceVar(&deployFlags.BlockchainsToRelay, "blockchains", nil, "blockchains to relay as source and destination") cmd.Flags().BoolVar(&deployFlags.RelayCChain, "cchain", false, "relay C-Chain as source and destination") @@ -70,6 +76,7 @@ func newDeployCmd() *cobra.Command { cmd.Flags().Float64Var(&deployFlags.Amount, "amount", 0, "automatically fund fee payments with the given amount") cmd.Flags().StringVar(&deployFlags.BlockchainFundingKey, "blockchain-funding-key", "", "key to be used to fund relayer account on all l1s") cmd.Flags().StringVar(&deployFlags.CChainFundingKey, "cchain-funding-key", "", "key to be used to fund relayer account on cchain") + cmd.Flags().BoolVar(&deployFlags.AllowPrivateIPs, "allow-private-ips", true, "allow relayer to connec to private ips") return cmd } @@ -432,6 +439,7 @@ func CallDeploy(_ []string, flags DeployFlags, network models.Network) error { storageDir, uint16(metricsPort), network, + flags.AllowPrivateIPs, ); err != nil { return err } diff --git a/cmd/interchaincmd/relayercmd/start.go b/cmd/interchaincmd/relayercmd/start.go index 64ed7361d..ddcf4e2ce 100644 --- a/cmd/interchaincmd/relayercmd/start.go +++ b/cmd/interchaincmd/relayercmd/start.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/interchain" "github.com/ava-labs/avalanche-cli/pkg/localnet" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -27,6 +28,7 @@ var ( } globalNetworkFlags networkoptions.NetworkFlags binPath string + version string ) // avalanche interchain relayer start @@ -40,6 +42,12 @@ func newStartCmd() *cobra.Command { } networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, true, startNetworkOptions) cmd.Flags().StringVar(&binPath, "bin-path", "", "use the given relayer binary") + cmd.Flags().StringVar( + &version, + "version", + constants.LatestPreReleaseVersionTag, + "version to use", + ) return cmd } @@ -93,7 +101,7 @@ func start(_ *cobra.Command, _ []string) error { if !utils.FileExists(relayerConfigPath) { return fmt.Errorf("there is no relayer configuration available") } else if binPath, err := interchain.DeployRelayer( - "latest", + version, binPath, app.GetICMRelayerBinDir(), relayerConfigPath, diff --git a/cmd/networkcmd/start.go b/cmd/networkcmd/start.go index 434c24e28..abb47e588 100644 --- a/cmd/networkcmd/start.go +++ b/cmd/networkcmd/start.go @@ -34,6 +34,7 @@ type StartFlags struct { SnapshotName string AvagoBinaryPath string RelayerBinaryPath string + RelayerVersion string NumNodes uint32 } @@ -63,6 +64,12 @@ already running.`, cmd.Flags().StringVar(&startFlags.RelayerBinaryPath, "relayer-path", "", "use this relayer binary path") cmd.Flags().StringVar(&startFlags.SnapshotName, "snapshot-name", constants.DefaultSnapshotName, "name of snapshot to use to start the network from") cmd.Flags().Uint32Var(&startFlags.NumNodes, "num-nodes", constants.LocalNetworkNumNodes, "number of nodes to be created on local network") + cmd.Flags().StringVar( + &startFlags.RelayerVersion, + "relayer-version", + constants.LatestPreReleaseVersionTag, + "use this relayer version", + ) return cmd } @@ -200,7 +207,7 @@ func Start(flags StartFlags, printEndpoints bool) error { relayerBinPath = extraLocalNetworkData.RelayerPath } if relayerBinPath, err := interchain.DeployRelayer( - "latest", + flags.RelayerVersion, relayerBinPath, app.GetICMRelayerBinDir(), relayerConfigPath, diff --git a/cmd/nodecmd/create.go b/cmd/nodecmd/create.go index 191a521b3..284c8265a 100644 --- a/cmd/nodecmd/create.go +++ b/cmd/nodecmd/create.go @@ -45,7 +45,7 @@ const ( ) var ( - createSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Fuji, networkoptions.Devnet, networkoptions.EtnaDevnet} + createSupportedNetworkOptions = []networkoptions.NetworkOption{networkoptions.Fuji, networkoptions.Devnet, networkoptions.EtnaDevnet, networkoptions.Mainnet} globalNetworkFlags networkoptions.NetworkFlags useAWS bool useGCP bool diff --git a/cmd/nodecmd/wiz.go b/cmd/nodecmd/wiz.go index 43a51caea..33bac722f 100644 --- a/cmd/nodecmd/wiz.go +++ b/cmd/nodecmd/wiz.go @@ -987,6 +987,7 @@ func addBlockchainToRelayerConf(network models.Network, cloudNodeID string, bloc app.GetICMRelayerServiceStorageDir(storageBasePath), constants.RemoteICMRelayerMetricsPort, network, + true, ); err != nil { return err } diff --git a/cmd/root.go b/cmd/root.go index c6e5976e8..a4c04b1f7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,6 +13,8 @@ import ( "syscall" "time" + "github.com/ava-labs/avalanche-cli/cmd/validatorcmd" + "github.com/ava-labs/avalanche-cli/cmd/backendcmd" "github.com/ava-labs/avalanche-cli/cmd/blockchaincmd" "github.com/ava-labs/avalanche-cli/cmd/configcmd" @@ -136,6 +138,8 @@ Deprecation notice: use 'avalanche blockchain'` // add contract command rootCmd.AddCommand(contractcmd.NewCmd(app)) + // add validator command + rootCmd.AddCommand(validatorcmd.NewCmd(app)) cobrautils.ConfigureRootCmd(rootCmd) diff --git a/cmd/validatorcmd/getBalance.go b/cmd/validatorcmd/getBalance.go new file mode 100644 index 000000000..d2f6f6cb2 --- /dev/null +++ b/cmd/validatorcmd/getBalance.go @@ -0,0 +1,126 @@ +// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package validatorcmd + +import ( + "fmt" + + "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/ava-labs/avalanche-cli/pkg/networkoptions" + "github.com/ava-labs/avalanche-cli/pkg/txutils" + "github.com/ava-labs/avalanche-cli/pkg/ux" + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/spf13/cobra" +) + +var globalNetworkFlags networkoptions.NetworkFlags + +var ( + l1 string + validationIDStr string +) + +var getBalanceSupportedNetworkOptions = []networkoptions.NetworkOption{ + networkoptions.Local, + networkoptions.Devnet, + networkoptions.EtnaDevnet, + networkoptions.Fuji, + networkoptions.Mainnet, +} + +func NewGetBalanceCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "getBalance", + Short: "Gets current balance of validator on P-Chain", + Long: `This command gets the remaining validator P-Chain balance that is available to pay +P-Chain continuous fee`, + RunE: getBalance, + Args: cobrautils.ExactArgs(0), + } + + networkoptions.AddNetworkFlagsToCmd(cmd, &globalNetworkFlags, true, getBalanceSupportedNetworkOptions) + cmd.Flags().StringVar(&l1, "l1", "", "name of L1 (required to get balance of bootstrap validators)") + cmd.Flags().StringVar(&validationIDStr, "validation-id", "", "validationIDStr of the validator") + return cmd +} + +func getBalance(_ *cobra.Command, _ []string) error { + network, err := networkoptions.GetNetworkFromCmdLineFlags( + app, + "", + globalNetworkFlags, + true, + false, + getBalanceSupportedNetworkOptions, + "", + ) + if err != nil { + return err + } + + var balance uint64 + if validationIDStr != "" { + validationID, err := ids.FromString(validationIDStr) + if err != nil { + return err + } + balance, err = txutils.GetValidatorPChainBalanceValidationID(network, validationID) + if err != nil { + return err + } + ux.Logger.PrintToUser(" Validator Balance: %.5f", float64(balance)/float64(units.Avax)) + return nil + } + + isBootstrapValidator, err := app.Prompt.CaptureYesNo("Is the validator a bootstrap validator?") + if err != nil { + return err + } + if isBootstrapValidator { + if l1 == "" { + return fmt.Errorf("--l1 flag is required to get bootstrap validator balance") + } + sc, err := app.LoadSidecar(l1) + if err != nil { + return fmt.Errorf("failed to load sidecar: %w", err) + } + if !sc.Sovereign { + return fmt.Errorf("avalanche validator getBalance command is only applicable to sovereign L1s") + } + bootstrapValidators := sc.Networks[network.Name()].BootstrapValidators + if len(bootstrapValidators) == 0 { + return fmt.Errorf("this L1 does not have any bootstrap validators") + } + bootstrapValidatorsString := []string{} + bootstrapValidatorsToIndexMap := make(map[string]int) + for index, validator := range bootstrapValidators { + bootstrapValidatorsString = append(bootstrapValidatorsString, validator.NodeID) + bootstrapValidatorsToIndexMap[validator.NodeID] = index + } + chosenValidator, err := app.Prompt.CaptureList("Which bootstrap validator do you want to get balance of?", bootstrapValidatorsString) + if err != nil { + return err + } + validationID, err := ids.FromString(bootstrapValidators[bootstrapValidatorsToIndexMap[chosenValidator]].ValidationID) + if err != nil { + return err + } + balance, err = txutils.GetValidatorPChainBalanceValidationID(network, validationID) + if err != nil { + return err + } + } else { + validationID, err := app.Prompt.CaptureID("What is the validator's validationID?") + if err != nil { + return err + } + balance, err = txutils.GetValidatorPChainBalanceValidationID(network, validationID) + if err != nil { + return err + } + } + ux.Logger.PrintToUser(" Validator Balance: %.5f", float64(balance)/float64(units.Avax)) + + return nil +} diff --git a/cmd/validatorcmd/validator.go b/cmd/validatorcmd/validator.go new file mode 100644 index 000000000..f16fa6c7b --- /dev/null +++ b/cmd/validatorcmd/validator.go @@ -0,0 +1,30 @@ +// Copyright (C) 2022, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. +package validatorcmd + +import ( + "github.com/ava-labs/avalanche-cli/pkg/application" + "github.com/ava-labs/avalanche-cli/pkg/cobrautils" + "github.com/spf13/cobra" +) + +var app *application.Avalanche + +// avalanche validator +func NewCmd(injectedApp *application.Avalanche) *cobra.Command { + cmd := &cobra.Command{ + Use: "validator", + Short: "Manage P-Chain validator balance", + Long: `The validator command suite provides a collection of tools for managing validator +balance on P-Chain. + +Validator's balance is used to pay for continuous fee to the P-Chain. When this Balance reaches 0, +the validator will be considered inactive and will no longer participate in validating the L1`, + RunE: cobrautils.CommandSuiteUsage, + } + app = injectedApp + // validator getBalance + cmd.AddCommand(NewGetBalanceCmd()) + + return cmd +} diff --git a/go.mod b/go.mod index fec59e2b9..a68e30fbd 100644 --- a/go.mod +++ b/go.mod @@ -5,10 +5,10 @@ go 1.22.10 require ( github.com/ava-labs/apm v1.0.0 github.com/ava-labs/avalanche-network-runner v1.8.4-0.20241130135139-a0946c5366be - github.com/ava-labs/avalanchego v1.12.1-0.20241203223023-13e07a1b3a1e + github.com/ava-labs/avalanchego v1.12.1-0.20241210172525-c7ebd8fbae88 github.com/ava-labs/coreth v0.13.9-rc.1 - github.com/ava-labs/icm-services v1.4.1-0.20241206150258-be351e56eea0 - github.com/ava-labs/subnet-evm v0.6.12 + github.com/ava-labs/icm-services v1.4.1-0.20241210192415-fbb658863e67 + github.com/ava-labs/subnet-evm v0.6.13-0.20241205165027-6c98da796f35 github.com/aws/aws-sdk-go-v2 v1.32.6 github.com/aws/aws-sdk-go-v2/config v1.28.6 github.com/aws/aws-sdk-go-v2/service/ec2 v1.182.0 @@ -27,7 +27,7 @@ require ( github.com/okteto/remote v0.0.0-20210428052247-99de42c04148 github.com/olekukonko/tablewriter v0.0.5 github.com/onsi/ginkgo/v2 v2.22.0 - github.com/onsi/gomega v1.36.0 + github.com/onsi/gomega v1.36.1 github.com/pborman/ansi v1.0.0 github.com/pingcap/errors v0.11.4 github.com/posthog/posthog-go v1.2.24 @@ -37,16 +37,16 @@ require ( github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 go.uber.org/mock v0.5.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.29.0 + golang.org/x/crypto v0.31.0 golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 golang.org/x/mod v0.22.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.32.0 golang.org/x/oauth2 v0.23.0 golang.org/x/sync v0.10.0 - golang.org/x/text v0.20.0 + golang.org/x/text v0.21.0 google.golang.org/api v0.184.0 google.golang.org/protobuf v1.35.2 gopkg.in/yaml.v2 v2.4.0 @@ -66,8 +66,8 @@ require ( github.com/ProtonMail/go-crypto v1.0.0 // indirect github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect + github.com/ava-labs/icm-contracts v1.0.9-0.20241210181701-a4bd5c92b056 // indirect github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60 // indirect - github.com/ava-labs/teleporter v1.0.8-0.20241122194201-a6e92843c3b1 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.47 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.21 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.25 // indirect @@ -216,10 +216,10 @@ require ( go.opentelemetry.io/otel/trace v1.24.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.26.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.27.0 // indirect + golang.org/x/tools v0.28.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect diff --git a/go.sum b/go.sum index 34ab53091..e147720e9 100644 --- a/go.sum +++ b/go.sum @@ -85,18 +85,18 @@ 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.4-0.20241130135139-a0946c5366be h1:6KxDYXwG5ErQl4+mHhLhj+j71CzHYJVTRd9onWYs6gE= github.com/ava-labs/avalanche-network-runner v1.8.4-0.20241130135139-a0946c5366be/go.mod h1:TOfc8r6sxs01mi2mWOYsRcJAbf3x3a4x0uxi8gVThso= -github.com/ava-labs/avalanchego v1.12.1-0.20241203223023-13e07a1b3a1e h1:pJJYryEedoOobUPuRNUYBwQgsaQXMqBkeMq28lRDpSo= -github.com/ava-labs/avalanchego v1.12.1-0.20241203223023-13e07a1b3a1e/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= +github.com/ava-labs/avalanchego v1.12.1-0.20241210172525-c7ebd8fbae88 h1:tZdtOPFNblKZx+FmJOhqEfxUUscvPhcLHKGZO3BtQ6A= +github.com/ava-labs/avalanchego v1.12.1-0.20241210172525-c7ebd8fbae88/go.mod h1:yhD5dpZyStIVbxQ550EDi5w5SL7DQ/xGE6TIxosb7U0= github.com/ava-labs/coreth v0.13.9-rc.1 h1:qIICpC/OZGYUP37QnLgIqqwGmxnLwLpZaUlqJNI85vU= github.com/ava-labs/coreth v0.13.9-rc.1/go.mod h1:7aMsRIo/3GBE44qWZMjnfqdqfcfZ5yShTTm2LObLaYo= -github.com/ava-labs/icm-services v1.4.1-0.20241206150258-be351e56eea0 h1:tmR+oBO0ODzFDIkur/6tcruo+nLFu6dxXKYvB2XTqIA= -github.com/ava-labs/icm-services v1.4.1-0.20241206150258-be351e56eea0/go.mod h1:insjVlfHLuCnEA2ZFbclnGAanO9EeXkryQO04WBSgE8= +github.com/ava-labs/icm-contracts v1.0.9-0.20241210181701-a4bd5c92b056 h1:VJDtg5UW4SWBeEMtv6qdWgEohdZxEp3jGDAllH6f8eI= +github.com/ava-labs/icm-contracts v1.0.9-0.20241210181701-a4bd5c92b056/go.mod h1:b6Hr01e5LWPua+nuZrSjjCyqfEhgFFdjBmKoJMoWTZo= +github.com/ava-labs/icm-services v1.4.1-0.20241210192415-fbb658863e67 h1:KPgYE1Ij9IPH2LQ/9FuZVfiugOGaRxsW+SU2rKenXnI= +github.com/ava-labs/icm-services v1.4.1-0.20241210192415-fbb658863e67/go.mod h1:5KIsL1x6K17YM53Cn2EEmCEILJxe3COHmPQY9afbmAo= github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60 h1:EL66gtXOAwR/4KYBjOV03LTWgkEXvLePribLlJNu4g0= github.com/ava-labs/ledger-avalanche/go v0.0.0-20241009183145-e6f90a8a1a60/go.mod h1:/7qKobTfbzBu7eSTVaXMTr56yTYk4j2Px6/8G+idxHo= -github.com/ava-labs/subnet-evm v0.6.12 h1:jL3FmjdFcNfS0qwbehwN6DkAg9y7zexB1riiGBxRsM0= -github.com/ava-labs/subnet-evm v0.6.12/go.mod h1:vffwL4UqAh7ibpWjveUuUhamm3a9w75q92bG5vXdX5k= -github.com/ava-labs/teleporter v1.0.8-0.20241122194201-a6e92843c3b1 h1:y1zjdfGlfTZQoPyUyPjsu9FjDK8w19OWUTpgVzQSh0w= -github.com/ava-labs/teleporter v1.0.8-0.20241122194201-a6e92843c3b1/go.mod h1:45NrpvVlms+xHL/rFZT7VrRJqajT7UUW78lzBe3hAzU= +github.com/ava-labs/subnet-evm v0.6.13-0.20241205165027-6c98da796f35 h1:CbXWon0fwGDEDCCiChx2VeIIwO3UML9+8OUTyNwPsxA= +github.com/ava-labs/subnet-evm v0.6.13-0.20241205165027-6c98da796f35/go.mod h1:SfAF4jjYPkezKWShPY/T31WQdD/UHrDyqy0kxA0LE0w= github.com/aws/aws-sdk-go-v2 v1.2.0/go.mod h1:zEQs02YRBw1DjK0PoJv3ygDYOFTre1ejlJWl8FwAuQo= github.com/aws/aws-sdk-go-v2 v1.32.6 h1:7BokKRgRPuGmKkFMhEg/jSul+tB9VvXhcViILtfG8b4= github.com/aws/aws-sdk-go-v2 v1.32.6/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= @@ -692,8 +692,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/onsi/gomega v1.36.0 h1:Pb12RlruUtj4XUuPUqeEWc6j5DkVVVA49Uf6YLfC95Y= -github.com/onsi/gomega v1.36.0/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.0.3-0.20180606204148-bd9c31933947/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -838,8 +838,9 @@ github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1F github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= @@ -954,8 +955,8 @@ golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1030,8 +1031,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1113,16 +1114,16 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -1135,8 +1136,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1179,8 +1180,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/mocks/downloader.go b/internal/mocks/downloader.go index 9412aebc2..b4df6354d 100644 --- a/internal/mocks/downloader.go +++ b/internal/mocks/downloader.go @@ -42,9 +42,9 @@ func (_m *Downloader) Download(url string) ([]byte, error) { return r0, r1 } -// GetAllReleasesForRepo provides a mock function with given fields: org, repo, kind -func (_m *Downloader) GetAllReleasesForRepo(org string, repo string, kind application.ReleaseKind) ([]string, error) { - ret := _m.Called(org, repo, kind) +// GetAllReleasesForRepo provides a mock function with given fields: org, repo, component, kind +func (_m *Downloader) GetAllReleasesForRepo(org string, repo string, component string, kind application.ReleaseKind) ([]string, error) { + ret := _m.Called(org, repo, component, kind) if len(ret) == 0 { panic("no return value specified for GetAllReleasesForRepo") @@ -52,19 +52,19 @@ func (_m *Downloader) GetAllReleasesForRepo(org string, repo string, kind applic var r0 []string var r1 error - if rf, ok := ret.Get(0).(func(string, string, application.ReleaseKind) ([]string, error)); ok { - return rf(org, repo, kind) + if rf, ok := ret.Get(0).(func(string, string, string, application.ReleaseKind) ([]string, error)); ok { + return rf(org, repo, component, kind) } - if rf, ok := ret.Get(0).(func(string, string, application.ReleaseKind) []string); ok { - r0 = rf(org, repo, kind) + if rf, ok := ret.Get(0).(func(string, string, string, application.ReleaseKind) []string); ok { + r0 = rf(org, repo, component, kind) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]string) } } - if rf, ok := ret.Get(1).(func(string, string, application.ReleaseKind) error); ok { - r1 = rf(org, repo, kind) + if rf, ok := ret.Get(1).(func(string, string, string, application.ReleaseKind) error); ok { + r1 = rf(org, repo, component, kind) } else { r1 = ret.Error(1) } diff --git a/pkg/application/downloader.go b/pkg/application/downloader.go index f99c0dfe6..cefee8a8b 100644 --- a/pkg/application/downloader.go +++ b/pkg/application/downloader.go @@ -37,7 +37,7 @@ type Downloader interface { Download(url string) ([]byte, error) GetLatestReleaseVersion(org, repo, component string) (string, error) GetLatestPreReleaseVersion(org, repo, component string) (string, error) - GetAllReleasesForRepo(org, repo string, kind ReleaseKind) ([]string, error) + GetAllReleasesForRepo(org, repo, component string, kind ReleaseKind) ([]string, error) } type downloader struct{} @@ -61,7 +61,7 @@ func (downloader) Download(url string) ([]byte, error) { // GetLatestPreReleaseVersion returns the latest available pre release or release version from github func (d downloader) GetLatestPreReleaseVersion(org, repo, component string) (string, error) { - releases, err := d.GetAllReleasesForRepo(org, repo, All) + releases, err := d.GetAllReleasesForRepo(org, repo, component, All) if err != nil { return "", err } @@ -84,7 +84,7 @@ func (d downloader) GetLatestReleaseVersion(org, repo, component string) (string if component == "" { return d.getLatestReleaseVersion(org, repo) } - releases, err := d.GetAllReleasesForRepo(org, repo, Release) + releases, err := d.GetAllReleasesForRepo(org, repo, component, Release) if err != nil { return "", err } @@ -99,7 +99,7 @@ func (d downloader) GetLatestReleaseVersion(org, repo, component string) (string return "", fmt.Errorf("no releases found for org %s repo %s component %s", org, repo, component) } -func (d downloader) GetAllReleasesForRepo(org, repo string, kind ReleaseKind) ([]string, error) { +func (d downloader) GetAllReleasesForRepo(org, repo, component string, kind ReleaseKind) ([]string, error) { url := fmt.Sprintf("https://api.github.com/repos/%s/%s/releases", org, repo) token := os.Getenv(constants.GithubAPITokenEnvVarName) body, err := d.doAPIRequest(url, token) @@ -134,7 +134,7 @@ func (d downloader) GetAllReleasesForRepo(org, repo string, kind ReleaseKind) ([ continue } version := r[githubVersionTagName].(string) - if !utils.IsValidSemanticVersion(version) { + if !utils.IsValidSemanticVersion(version, component) { // will skip ICM services version format errors until format is firmly defined if repo == constants.ICMServicesRepoName { continue @@ -188,7 +188,7 @@ func (d downloader) getLatestReleaseVersion(org, repo string) (string, error) { } version := jsonStr[githubVersionTagName].(string) - if !utils.IsValidSemanticVersion(version) { + if !utils.IsValidSemanticVersion(version, "") { return "", fmt.Errorf("invalid version string: %s", version) } diff --git a/pkg/binutils/release.go b/pkg/binutils/release.go index 1ac64f742..31a384cd7 100644 --- a/pkg/binutils/release.go +++ b/pkg/binutils/release.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/ava-labs/avalanche-cli/pkg/application" + "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/utils" "github.com/ava-labs/avalanche-cli/pkg/ux" @@ -64,30 +65,26 @@ func InstallBinary( binPrefix string, org string, repo string, - kind string, + component string, downloader GithubDownloader, installer Installer, ) (string, string, error) { switch { - case version == "latest": + case version == constants.LatestReleaseVersionTag: // get latest version var err error - version, err = app.Downloader.GetLatestReleaseVersion(org, repo, "") + version, err = app.Downloader.GetLatestReleaseVersion(org, repo, component) if err != nil { return "", "", err } - case version == "latest-prerelease": + case version == constants.LatestPreReleaseVersionTag: // get latest pre release version var err error - version, err = app.Downloader.GetLatestPreReleaseVersion( - org, - repo, - kind, - ) + version, err = app.Downloader.GetLatestPreReleaseVersion(org, repo, component) if err != nil { return "", "", err } - case !utils.IsValidSemanticVersion(version): + case !utils.IsValidSemanticVersion(version, component): return "", "", fmt.Errorf( "invalid version string. Must be semantic version ex: v1.7.14: %s", version) } diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index 34fe2a6bb..b790c7686 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -4,6 +4,8 @@ package constants import ( "time" + + "github.com/ava-labs/avalanchego/utils/units" ) type HTTPAccess bool @@ -56,7 +58,8 @@ const ( APIRequestLargeTimeout = 10 * time.Second FastGRPCDialTimeout = 100 * time.Millisecond - FujiBootstrapTimeout = 15 * time.Minute + FujiBootstrapTimeout = 15 * time.Minute + MainnetBootstrapTimeout = 2 * time.Hour SSHServerStartTimeout = 1 * time.Minute SSHScriptTimeout = 2 * time.Minute @@ -120,7 +123,8 @@ const ( MinStakeWeight = 1 // Default balance when we prompt users for bootstrap validators // nAVAX - BootstrapValidatorBalance = 1000000000 + BootstrapValidatorBalanceNanoAVAX = uint64(BootstrapValidatorBalanceAVAX * float64(units.Avax)) + BootstrapValidatorBalanceAVAX = 0.1 // Default weight when we prompt users for bootstrap validators BootstrapValidatorWeight = 100 // Default weight when we prompt users for non bootstrap validators @@ -332,4 +336,6 @@ const ( MainnetCChainICMRegistryAddress = "0x7C43605E14F391720e1b37E49C78C4b03A488d98" FujiCChainICMRegistryAddress = "0xF86Cb19Ad8405AEFa7d09C778215D2Cb6eBfB228" EtnaDevnetCChainICMRegistryAddress = "0xEe40DFF876204A99eCCB783FDc01eE0a2678Ae93" + + ValidatorUptimeDeductible = uint64(10) // seconds to make sure all L1 validators would agree on uptime ) diff --git a/pkg/interchain/relayer.go b/pkg/interchain/relayer.go index ea4ab7898..f0a2ceb6a 100644 --- a/pkg/interchain/relayer.go +++ b/pkg/interchain/relayer.go @@ -256,13 +256,6 @@ func InstallRelayer(binDir, version string) (string, error) { } } ux.Logger.PrintToUser("Relayer version %s", version) - if version == "" || version == "latest" { - var err error - version, err = GetLatestRelayerReleaseVersion() - if err != nil { - return "", err - } - } versionBinDir := filepath.Join(binDir, version) binPath := filepath.Join(versionBinDir, constants.ICMRelayerBin) if utils.IsExecutable(binPath) { @@ -328,18 +321,37 @@ func getRelayerURL(version string) (string, error) { if goos != "linux" && goos != "darwin" { return "", fmt.Errorf("OS not supported: %s", goos) } - splittedVersion := strings.Split(version, "/") - if len(splittedVersion) != 2 { - return "", fmt.Errorf("invalid relayer version %s", version) + component := "icm-relayer" + semanticVersion := strings.TrimPrefix(version, component+"/") + if semanticVersion != version { + return fmt.Sprintf( + "https://github.com/%s/%s/releases/download/icm-relayer%%2F%s/icm-relayer_%s_%s_%s.tar.gz", + constants.AvaLabsOrg, + constants.ICMServicesRepoName, + semanticVersion, + strings.TrimPrefix(semanticVersion, "v"), + goos, + goarch, + ), nil + } + semanticVersion = strings.TrimPrefix(version, component+"-") + if semanticVersion != version { + return fmt.Sprintf( + "https://github.com/%s/%s/releases/download/icm-relayer-%s/icm-relayer_%s_%s_%s.tar.gz", + constants.AvaLabsOrg, + constants.ICMServicesRepoName, + semanticVersion, + strings.TrimPrefix(semanticVersion, "v"), + goos, + goarch, + ), nil } - version = splittedVersion[1] - trimmedVersion := strings.TrimPrefix(version, "v") return fmt.Sprintf( - "https://github.com/%s/%s/releases/download/icm-relayer%%2F%s/icm-relayer_%s_%s_%s.tar.gz", + "https://github.com/%s/%s/releases/download/%s/icm-relayer_%s_%s_%s.tar.gz", constants.AvaLabsOrg, constants.ICMServicesRepoName, - version, - trimmedVersion, + semanticVersion, + strings.TrimPrefix(semanticVersion, "v"), goos, goarch, ), nil @@ -374,6 +386,7 @@ func CreateBaseRelayerConfigIfMissing( storageLocation string, metricsPort uint16, network models.Network, + allowPrivateIPs bool, ) error { if !utils.FileExists(relayerConfigPath) { return CreateBaseRelayerConfig( @@ -382,6 +395,7 @@ func CreateBaseRelayerConfigIfMissing( storageLocation, metricsPort, network, + allowPrivateIPs, ) } return nil @@ -393,6 +407,7 @@ func CreateBaseRelayerConfig( storageLocation string, metricsPort uint16, network models.Network, + allowPrivateIPs bool, ) error { awmRelayerConfig := &config.Config{ LogLevel: logLevel, @@ -411,6 +426,7 @@ func CreateBaseRelayerConfig( MetricsPort: metricsPort, DBWriteIntervalSeconds: defaultDBWriteIntervalSeconds, SignatureCacheSize: defaultSignatureCacheSize, + AllowPrivateIPs: allowPrivateIPs, } return saveRelayerConfig(awmRelayerConfig, relayerConfigPath) } diff --git a/pkg/models/bootstrap_validator.go b/pkg/models/bootstrap_validator.go index 8072bf39a..c3419a04f 100644 --- a/pkg/models/bootstrap_validator.go +++ b/pkg/models/bootstrap_validator.go @@ -14,4 +14,6 @@ type SubnetValidator struct { BLSProofOfPossession string `json:"BLSProofOfPossession"` ChangeOwnerAddr string `json:"ChangeOwnerAddr"` + + ValidationID string `json:"ValidationID"` } diff --git a/pkg/models/network.go b/pkg/models/network.go index 47249f3d1..c7475cfd0 100644 --- a/pkg/models/network.go +++ b/pkg/models/network.go @@ -232,8 +232,11 @@ func (n *Network) Equals(n2 Network) bool { // Context for bootstrapping a partial synced Node func (n *Network) BootstrappingContext() (context.Context, context.CancelFunc) { timeout := constants.ANRRequestTimeout - if n.Kind == Fuji { + switch n.Kind { + case Fuji: timeout = constants.FujiBootstrapTimeout + case Mainnet: + timeout = constants.MainnetBootstrapTimeout } return context.WithTimeout(context.Background(), timeout) } diff --git a/pkg/node/local.go b/pkg/node/local.go index 0a02089a4..0c1eed470 100644 --- a/pkg/node/local.go +++ b/pkg/node/local.go @@ -315,6 +315,13 @@ func StartLocalNode( constants.EtnaDevnetEndpoint, clusterName, ) + case globalNetworkFlags.UseLocal: + network = models.NewNetwork( + models.Local, + constants.LocalNetworkID, + constants.LocalAPIEndpoint, + clusterName, + ) case globalNetworkFlags.UseFuji: network = models.NewNetwork( models.Fuji, @@ -322,11 +329,11 @@ func StartLocalNode( constants.FujiAPIEndpoint, clusterName, ) - case globalNetworkFlags.UseLocal: + case globalNetworkFlags.UseMainnet: network = models.NewNetwork( - models.Local, - constants.LocalNetworkID, - constants.LocalAPIEndpoint, + models.Mainnet, + avagoconstants.MainnetID, + constants.MainnetAPIEndpoint, clusterName, ) default: @@ -346,6 +353,8 @@ func StartLocalNode( } if network.Kind == models.Fuji { ux.Logger.PrintToUser(logging.Yellow.Wrap("Warning: Fuji Bootstrapping can take several minutes")) + } else if network.Kind == models.Mainnet { + ux.Logger.PrintToUser(logging.Yellow.Wrap("Warning: Mainnet Bootstrapping can take 1-2 hours")) } if network.Kind == models.Local { cli, err := binutils.NewGRPCClient() diff --git a/pkg/prompts/prompts.go b/pkg/prompts/prompts.go index 987e997fe..78e5e85c4 100644 --- a/pkg/prompts/prompts.go +++ b/pkg/prompts/prompts.go @@ -11,6 +11,8 @@ import ( "strings" "time" + "github.com/ava-labs/avalanchego/utils/units" + "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanche-cli/pkg/key" "github.com/ava-labs/avalanche-cli/pkg/models" @@ -301,6 +303,7 @@ func (*realPrompter) CaptureNodeID(promptStr string) (ids.NodeID, error) { return ids.NodeIDFromString(nodeIDStr) } +// CaptureValidatorBalance captures balance in nanoAVAX func (*realPrompter) CaptureValidatorBalance( promptStr string, availableBalance uint64, @@ -314,7 +317,12 @@ func (*realPrompter) CaptureValidatorBalance( return 0, err } - return strconv.ParseUint(amountStr, 10, 64) + amountFloat, err := strconv.ParseFloat(amountStr, 64) + if err != nil { + return 0, err + } + + return uint64(amountFloat * float64(units.Avax)), nil } func (*realPrompter) CaptureWeight(promptStr string) (uint64, error) { diff --git a/pkg/prompts/validations.go b/pkg/prompts/validations.go index baf7e0e2c..a35b65a60 100644 --- a/pkg/prompts/validations.go +++ b/pkg/prompts/validations.go @@ -147,15 +147,15 @@ func validateWeight(input string) error { func validateValidatorBalanceFunc(availableBalance uint64) func(string) error { return func(input string) error { - val, err := strconv.ParseUint(input, 10, 64) + val, err := strconv.ParseFloat(input, 64) if err != nil { return err } - if val < 1 { - return fmt.Errorf("subnet validator balance must be at least 1 AVAX") + if val < constants.BootstrapValidatorBalanceAVAX { + return fmt.Errorf("subnet validator balance must be at least 0.1 AVAX") } - if val > availableBalance { - return fmt.Errorf("current balance of %d is not sufficient for subnet validator balance to be %d AVAX", availableBalance, val) + if val > float64(availableBalance) { + return fmt.Errorf("current balance of %d is not sufficient for subnet validator balance to be %2f AVAX", availableBalance, val) } return nil } diff --git a/pkg/txutils/info.go b/pkg/txutils/info.go index 30bdae4da..4cd48b8d1 100644 --- a/pkg/txutils/info.go +++ b/pkg/txutils/info.go @@ -111,3 +111,13 @@ func GetOwners(network models.Network, subnetID ids.ID) (bool, []string, uint32, } return isPermissioned, controlKeysStrs, threshold, nil } + +func GetValidatorPChainBalanceValidationID(network models.Network, validationID ids.ID) (uint64, error) { + pClient := platformvm.NewClient(network.Endpoint) + ctx := context.Background() + validatorResponse, _, err := pClient.GetL1Validator(ctx, validationID) + if err != nil { + return 0, err + } + return validatorResponse.Balance, nil +} diff --git a/pkg/utils/common.go b/pkg/utils/common.go index 169d6e76d..26218b47e 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -561,14 +561,14 @@ func LogLevelToEmoji(logLevel string) (string, error) { return levelEmoji, nil } -func IsValidSemanticVersion(version string) bool { +func IsValidSemanticVersion(version string, component string) bool { if !semver.IsValid(version) { - // remove tool part, just in case (eg icm-relayer/v1.5.1) - versionParts := strings.Split(version, "/") - if len(versionParts) == 2 && semver.IsValid(versionParts[1]) { + versionTail := strings.TrimPrefix(version, component+"-") + if semver.IsValid(versionTail) { return true } - return false + versionTail = strings.TrimPrefix(version, component+"/") + return semver.IsValid(versionTail) } return true } diff --git a/pkg/utils/decimals.go b/pkg/utils/decimals.go deleted file mode 100644 index 28e4f2bef..000000000 --- a/pkg/utils/decimals.go +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (C) 2023, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. -package utils - -import ( - "math/big" -) - -const defaultDenomination = 18 - -// Convert an integed amount of the given denomination to base units -// (i.e. An amount of 54 with a decimals value of 3 results in 54000) -func ApplyDenomination(amount uint64, decimals uint8) *big.Int { - multiplier := new(big.Int).Exp( - big.NewInt(10), - big.NewInt(int64(decimals)), - nil, - ) - return new(big.Int).Mul( - big.NewInt(int64(amount)), - multiplier, - ) -} - -// Convert an integed amount of the default denomination to base units -func ApplyDefaultDenomination(amount uint64) *big.Int { - return ApplyDenomination(amount, defaultDenomination) -} diff --git a/pkg/utils/keys.go b/pkg/utils/keys.go index 102fca95f..ff362a795 100644 --- a/pkg/utils/keys.go +++ b/pkg/utils/keys.go @@ -8,7 +8,6 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanchego/ids" - "github.com/ava-labs/avalanchego/utils/units" "github.com/ava-labs/avalanchego/vms/platformvm" ) @@ -51,5 +50,5 @@ func GetNetworkBalance(addressList []ids.ShortID, networkEndpoint string) (uint6 if err != nil { return 0, err } - return uint64(bal.Balance) / units.Avax, nil + return uint64(bal.Balance), nil } diff --git a/pkg/utils/net.go b/pkg/utils/net.go index 408a79627..99d466898 100644 --- a/pkg/utils/net.go +++ b/pkg/utils/net.go @@ -5,11 +5,13 @@ package utils import ( "encoding/json" "errors" + "fmt" "io" "net" "net/http" "net/netip" "net/url" + "regexp" ) // GetUserIPAddress retrieves the IP address of the user. @@ -65,3 +67,24 @@ func IsValidIPPort(ipPortPair string) bool { } return true } + +// SplitRPCURI splits the RPC URI into `endpoint` and `chain`. +// Reverse operation of `fmt.Sprintf("%s/ext/bc/%s", endpoint, chain)`. +// returns the `uri` and `chain` as strings, or an error if the request URI is invalid. +func SplitAvalanchegoRPCURI(requestURI string) (string, string, error) { + // Define the regex pattern + pattern := `^(https?://[^/]+)/ext/bc/([^/]+)/rpc$` + regex := regexp.MustCompile(pattern) + + // Match the pattern + matches := regex.FindStringSubmatch(requestURI) + if matches == nil || len(matches) != 3 { + return "", "", fmt.Errorf("invalid request URI format") + } + + // Extract `endpoint` and `chain` + endpoint := matches[1] + chain := matches[2] + + return endpoint, chain, nil +} diff --git a/pkg/utils/net_test.go b/pkg/utils/net_test.go index 57a9d2a5b..fff2fcef6 100644 --- a/pkg/utils/net_test.go +++ b/pkg/utils/net_test.go @@ -4,6 +4,8 @@ package utils import ( "testing" + + "github.com/stretchr/testify/require" ) func TestIsValidIPPort(t *testing.T) { @@ -11,13 +13,13 @@ func TestIsValidIPPort(t *testing.T) { input string expected bool }{ - {"127.0.0.1:8080", true}, // valid IP:port - {"256.0.0.1:8080", false}, // invalid IP address - {"example.com:8080", false}, // only ip address is allowed - {"127.0.0.1", false}, // missing port - {"[::1]:8080", true}, // valid IPv6 address - {"[::1]", false}, // missing port for IPv6 - {"", false}, // empty string + {"127.0.0.1:8080", true}, // valid IP:port + {"256.0.0.1:8080", false}, // invalid IP address + {"127.0.0.1:9650:8080", false}, // only ip address is allowed + {"127.0.0.1", false}, // missing port + {"[::1]:8080", true}, // valid IPv6 address + {"[::1]", false}, // missing port for IPv6 + {"", false}, // empty string } for _, test := range tests { @@ -29,3 +31,63 @@ func TestIsValidIPPort(t *testing.T) { }) } } + +func TestSplitRPCURI(t *testing.T) { + tests := []struct { + name string + requestURI string + expectedEndpoint string + expectedChain string + expectError bool + }{ + { + name: "Valid URI", + requestURI: "http://127.0.0.1:9660/ext/bc/nL95ujcHLPFhuQdHYkvS3CSUvDr9EfZduzyJ5Ty6VXXMgyEEF/rpc", + expectedEndpoint: "http://127.0.0.1:9660", + expectedChain: "nL95ujcHLPFhuQdHYkvS3CSUvDr9EfZduzyJ5Ty6VXXMgyEEF", + expectError: false, + }, + { + name: "Valid URI with https", + requestURI: "https://example.com:8080/ext/bc/testChain/rpc", + expectedEndpoint: "https://example.com:8080", + expectedChain: "testChain", + expectError: false, + }, + { + name: "Invalid URI - missing /rpc", + requestURI: "http://127.0.0.1:9660/ext/bc/nL95ujcHLPFhuQdHYkvS3CSUvDr9EfZduzyJ5Ty6VXXMgyEEF", + expectedEndpoint: "", + expectedChain: "", + expectError: true, + }, + { + name: "Invalid URI - missing /ext/bc/", + requestURI: "http://127.0.0.1:9660/some/other/path/rpc", + expectedEndpoint: "", + expectedChain: "", + expectError: true, + }, + { + name: "Invalid URI - malformed URL", + requestURI: "127.0.0.1:9660/ext/bc/chainId/rpc", + expectedEndpoint: "", + expectedChain: "", + expectError: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + endpoint, chain, err := SplitAvalanchegoRPCURI(tt.requestURI) + + if tt.expectError { + require.Error(t, err, "expected an error but got nil") + } else { + require.NoError(t, err, "did not expect an error but got one") + require.Equal(t, tt.expectedEndpoint, endpoint, "unexpected Endpoint") + require.Equal(t, tt.expectedChain, chain, "unexpected Chain") + } + }) + } +} diff --git a/pkg/utils/staking.go b/pkg/utils/staking.go index 5df9d88a6..fa2df1f19 100644 --- a/pkg/utils/staking.go +++ b/pkg/utils/staking.go @@ -4,15 +4,20 @@ package utils import ( "encoding/pem" + "errors" "fmt" "os" "path/filepath" + "time" "github.com/ava-labs/avalanche-cli/pkg/constants" "github.com/ava-labs/avalanchego/ids" "github.com/ava-labs/avalanchego/staking" "github.com/ava-labs/avalanchego/utils/crypto/bls" + "github.com/ava-labs/avalanchego/vms/platformvm" "github.com/ava-labs/avalanchego/vms/platformvm/signer" + + "github.com/ava-labs/subnet-evm/plugin/evm" ) func NewBlsSecretKeyBytes() ([]byte, error) { @@ -73,3 +78,40 @@ func GetNodeParams(nodeDir string) ( } return nodeID, blsPub, blsPoP, nil } + +func GetRemainingValidationTime(networkEndpoint string, nodeID ids.NodeID, subnetID ids.ID, startTime time.Time) (time.Duration, error) { + ctx, cancel := GetAPIContext() + defer cancel() + platformCli := platformvm.NewClient(networkEndpoint) + vs, err := platformCli.GetCurrentValidators(ctx, subnetID, nil) + cancel() + if err != nil { + return 0, err + } + for _, v := range vs { + if v.NodeID == nodeID { + return time.Unix(int64(v.EndTime), 0).Sub(startTime), nil + } + } + return 0, errors.New("nodeID not found in validator set: " + nodeID.String()) +} + +// GetL1ValidatorUptimeSeconds returns the uptime of the L1 validator +func GetL1ValidatorUptimeSeconds(rpcURL string, nodeID ids.NodeID) (uint64, error) { + ctx, cancel := GetAPIContext() + defer cancel() + networkEndpoint, blockchainID, err := SplitAvalanchegoRPCURI(rpcURL) + if err != nil { + return 0, err + } + evmCli := evm.NewClient(networkEndpoint, blockchainID) + validators, err := evmCli.GetCurrentValidators(ctx, []ids.NodeID{nodeID}) + if err != nil { + return 0, err + } + if len(validators) > 0 { + return validators[0].UptimeSeconds - constants.ValidatorUptimeDeductible, nil + } + + return 0, errors.New("nodeID not found in validator set: " + nodeID.String()) +} diff --git a/pkg/validatormanager/registration.go b/pkg/validatormanager/registration.go index eaa17223a..7a675330d 100644 --- a/pkg/validatormanager/registration.go +++ b/pkg/validatormanager/registration.go @@ -32,7 +32,7 @@ import ( "google.golang.org/protobuf/proto" ) -func NativePoSValidatorManagerInitializeValidatorRegistration( +func InitializeValidatorRegistrationPoSNative( rpcURL string, managerAddress common.Address, managerOwnerPrivateKey string, @@ -93,7 +93,7 @@ func NativePoSValidatorManagerInitializeValidatorRegistration( } // step 1 of flow for adding a new validator -func PoAValidatorManagerInitializeValidatorRegistration( +func InitializeValidatorRegistrationPoA( rpcURL string, managerAddress common.Address, managerOwnerPrivateKey string, @@ -147,7 +147,7 @@ func PoAValidatorManagerInitializeValidatorRegistration( ) } -func ValidatorManagerGetSubnetValidatorRegistrationMessage( +func GetSubnetValidatorRegistrationMessage( rpcURL string, network models.Network, aggregatorLogLevel logging.Level, @@ -277,7 +277,7 @@ func GetValidatorWeight( return weight, nil } -func ValidatorManagerGetPChainSubnetValidatorRegistrationWarpMessage( +func GetPChainSubnetValidatorRegistrationWarpMessage( network models.Network, rpcURL string, aggregatorLogLevel logging.Level, @@ -329,7 +329,7 @@ func ValidatorManagerGetPChainSubnetValidatorRegistrationWarpMessage( } // last step of flow for adding a new validator -func ValidatorManagerCompleteValidatorRegistration( +func CompleteValidatorRegistration( rpcURL string, managerAddress common.Address, privateKey string, // not need to be owner atm @@ -392,7 +392,7 @@ func InitValidatorRegistration( ux.Logger.PrintToUser("Using rpcURL: %s", rpcURL) ux.Logger.PrintToUser("NodeID: %s staking %s for %ds", nodeID.String(), stakeAmount, uint64(stakeDuration.Seconds())) ux.Logger.PrintLineSeparator() - tx, _, err := NativePoSValidatorManagerInitializeValidatorRegistration( + tx, _, err := InitializeValidatorRegistrationPoSNative( rpcURL, managerAddress, ownerPrivateKey, @@ -414,7 +414,7 @@ func InitValidatorRegistration( } } else { managerAddress = common.HexToAddress(validatorManagerSDK.ProxyContractAddress) - tx, _, err := PoAValidatorManagerInitializeValidatorRegistration( + tx, _, err := InitializeValidatorRegistrationPoA( rpcURL, managerAddress, ownerPrivateKey, @@ -451,7 +451,7 @@ func InitValidatorRegistration( } ux.Logger.PrintToUser(fmt.Sprintf("Validator weight: %d", weight)) - return ValidatorManagerGetSubnetValidatorRegistrationMessage( + return GetSubnetValidatorRegistrationMessage( rpcURL, network, aggregatorLogLevel, @@ -495,7 +495,7 @@ func FinishValidatorRegistration( aggregatorLogLevel = defaultAggregatorLogLevel } managerAddress := common.HexToAddress(validatorManagerSDK.ProxyContractAddress) - signedMessage, err := ValidatorManagerGetPChainSubnetValidatorRegistrationWarpMessage( + signedMessage, err := GetPChainSubnetValidatorRegistrationWarpMessage( network, rpcURL, aggregatorLogLevel, @@ -515,7 +515,7 @@ func FinishValidatorRegistration( ); err != nil { return err } - tx, _, err := ValidatorManagerCompleteValidatorRegistration( + tx, _, err := CompleteValidatorRegistration( rpcURL, managerAddress, privateKey, diff --git a/pkg/validatormanager/removal.go b/pkg/validatormanager/removal.go index c0139877d..0fcb492f6 100644 --- a/pkg/validatormanager/removal.go +++ b/pkg/validatormanager/removal.go @@ -11,6 +11,7 @@ import ( "github.com/ava-labs/avalanche-cli/pkg/contract" "github.com/ava-labs/avalanche-cli/pkg/evm" "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/avalanche-cli/sdk/interchain" validatorManagerSDK "github.com/ava-labs/avalanche-cli/sdk/validatormanager" @@ -21,42 +22,53 @@ import ( warpMessage "github.com/ava-labs/avalanchego/vms/platformvm/warp/message" warpPayload "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload" "github.com/ava-labs/subnet-evm/core/types" + "github.com/ava-labs/subnet-evm/warp/messages" "github.com/ethereum/go-ethereum/common" ) -func ValidatorManagerInitializeValidatorRemoval( +func InitializeValidatorRemoval( rpcURL string, managerAddress common.Address, - ownerPrivateKey string, - validationID [32]byte, + privateKey string, + validationID ids.ID, isPoS bool, + uptimeProofSignedMessage *warp.Message, force bool, ) (*types.Transaction, *types.Receipt, error) { if isPoS { - // PoS only supports forcefull removal //TODO: implement uptime proof to remove this restriction - // posEndValidation := "initializeEndValidation(bytes32,bool,uint32)" - posEndValidation := "forceInitializeEndValidation(bytes32,bool,uint32)" if force { - posEndValidation = "forceInitializeEndValidation(bytes32,bool,uint32)" + return contract.TxToMethod( + rpcURL, + privateKey, + managerAddress, + big.NewInt(0), + "force POS validator removal", + validatorManagerSDK.ErrorSignatureToError, + "forceInitializeEndValidation(bytes32,bool,uint32)", + validationID, + false, // no uptime proof if force + uint32(0), + ) } - - return contract.TxToMethod( + // remove PoS validator with uptime proof + return contract.TxToMethodWithWarpMessage( rpcURL, - ownerPrivateKey, + privateKey, managerAddress, + uptimeProofSignedMessage, big.NewInt(0), - "POS validator removal initialization", + "POS validator removal with uptime proof", validatorManagerSDK.ErrorSignatureToError, - posEndValidation, + "initializeEndValidation(bytes32,bool,uint32)", validationID, - false, // don't include uptime proof - rely on network to calculate uptime + true, // submit uptime proof uint32(0), ) } // PoA case return contract.TxToMethod( rpcURL, - ownerPrivateKey, + privateKey, managerAddress, big.NewInt(0), "POA validator removal initialization", @@ -66,7 +78,47 @@ func ValidatorManagerInitializeValidatorRemoval( ) } -func ValidatorManagerGetSubnetValidatorWeightMessage( +func GetUptimeProofMessage( + network models.Network, + aggregatorLogLevel logging.Level, + aggregatorQuorumPercentage uint64, + aggregatorExtraPeerEndpoints []info.Peer, + subnetID ids.ID, + blockchainID ids.ID, + validationID ids.ID, + uptime uint64, +) (*warp.Message, error) { + uptimePayload, err := messages.NewValidatorUptime(validationID, uptime) + if err != nil { + return nil, err + } + addressedCall, err := warpPayload.NewAddressedCall(nil, uptimePayload.Bytes()) + if err != nil { + return nil, err + } + uptimeProofUnsignedMessage, err := warp.NewUnsignedMessage( + network.ID, + blockchainID, + addressedCall.Bytes(), + ) + if err != nil { + return nil, err + } + signatureAggregator, err := interchain.NewSignatureAggregator( + network, + aggregatorLogLevel, + subnetID, + aggregatorQuorumPercentage, + true, // allow private peers + aggregatorExtraPeerEndpoints, + ) + if err != nil { + return nil, err + } + return signatureAggregator.Sign(uptimeProofUnsignedMessage, nil) +} + +func GetSubnetValidatorWeightMessage( network models.Network, aggregatorLogLevel logging.Level, aggregatorQuorumPercentage uint64, @@ -127,6 +179,7 @@ func InitValidatorRemoval( aggregatorAllowPrivatePeers bool, aggregatorLogLevelStr string, initWithPos bool, + uptimeSec uint64, force bool, ) (*warp.Message, ids.ID, error) { subnetID, err := contract.GetSubnetID( @@ -154,13 +207,44 @@ func InitValidatorRemoval( if err != nil { return nil, ids.Empty, err } - ux.Logger.PrintToUser("Using validationID: %s for nodeID: %s", validationID, nodeID) - tx, _, err := ValidatorManagerInitializeValidatorRemoval( + + aggregatorLogLevel, err := logging.ToLevel(aggregatorLogLevelStr) + if err != nil { + aggregatorLogLevel = defaultAggregatorLogLevel + } + signedUptimeProof := &warp.Message{} + if initWithPos { + if err != nil { + return nil, ids.Empty, evm.TransactionError(nil, err, "failure getting uptime data") + } + if uptimeSec == 0 { + uptimeSec, err = utils.GetL1ValidatorUptimeSeconds(rpcURL, nodeID) + if err != nil { + return nil, ids.Empty, evm.TransactionError(nil, err, "failure getting uptime data for nodeID: %s via %s ", nodeID, rpcURL) + } + } + ux.Logger.PrintToUser("Using uptime: %ds", uptimeSec) + signedUptimeProof, err = GetUptimeProofMessage( + network, + aggregatorLogLevel, + 0, + aggregatorExtraPeerEndpoints, + subnetID, + blockchainID, + validationID, + uptimeSec, + ) + if err != nil { + return nil, ids.Empty, evm.TransactionError(nil, err, "failure getting uptime proof") + } + } + tx, _, err := InitializeValidatorRemoval( rpcURL, managerAddress, ownerPrivateKey, validationID, initWithPos, + signedUptimeProof, // is empty for non-PoS force, ) if err != nil { @@ -170,12 +254,8 @@ func InitValidatorRemoval( ux.Logger.PrintToUser("the validator removal process was already initialized. Proceeding to the next step") } - aggregatorLogLevel, err := logging.ToLevel(aggregatorLogLevelStr) - if err != nil { - aggregatorLogLevel = defaultAggregatorLogLevel - } nonce := uint64(1) - signedMsg, err := ValidatorManagerGetSubnetValidatorWeightMessage( + signedMsg, err := GetSubnetValidatorWeightMessage( network, aggregatorLogLevel, 0, @@ -191,7 +271,7 @@ func InitValidatorRemoval( return signedMsg, validationID, err } -func ValidatorManagerCompleteValidatorRemoval( +func CompleteValidatorRemoval( rpcURL string, managerAddress common.Address, privateKey string, // not need to be owner atm @@ -234,7 +314,7 @@ func FinishValidatorRemoval( if err != nil { aggregatorLogLevel = defaultAggregatorLogLevel } - signedMessage, err := ValidatorManagerGetPChainSubnetValidatorRegistrationWarpMessage( + signedMessage, err := GetPChainSubnetValidatorRegistrationWarpMessage( network, rpcURL, aggregatorLogLevel, @@ -254,7 +334,7 @@ func FinishValidatorRemoval( ); err != nil { return err } - tx, _, err := ValidatorManagerCompleteValidatorRemoval( + tx, _, err := CompleteValidatorRemoval( rpcURL, managerAddress, privateKey, diff --git a/pkg/vm/evm_prompts.go b/pkg/vm/evm_prompts.go index c54d606d6..1eb7839f6 100644 --- a/pkg/vm/evm_prompts.go +++ b/pkg/vm/evm_prompts.go @@ -963,6 +963,7 @@ func promptUserForVMVersion( versions, err := app.Downloader.GetAllReleasesForRepo( constants.AvaLabsOrg, constants.SubnetEVMRepoName, + "", application.All, ) if err != nil { diff --git a/sdk/interchain/signature-aggregator.go b/sdk/interchain/signature-aggregator.go index ac0f583ec..85764b84e 100644 --- a/sdk/interchain/signature-aggregator.go +++ b/sdk/interchain/signature-aggregator.go @@ -66,8 +66,8 @@ func createAppRequestNetwork( InfoAPI: &apiConfig.APIConfig{ BaseURL: network.Endpoint, }, + AllowPrivateIPs: allowPrivatePeers, }, - allowPrivatePeers, ) if err != nil { return nil, fmt.Errorf("failed to create peer network: %w", err) diff --git a/tests/e2e/commands/etna.go b/tests/e2e/commands/etna.go index 691ae2ce8..31dbb4dc7 100644 --- a/tests/e2e/commands/etna.go +++ b/tests/e2e/commands/etna.go @@ -41,7 +41,7 @@ func CreateEtnaSubnetEvmConfig( rewardBasisPoints := "" subnetManagementStr := PoAString if subnetManagementType == PoS { - rewardBasisPoints = "--reward-basis-points=100" + rewardBasisPoints = "--reward-basis-points=1000000000" subnetManagementStr = PoSString } // Create config @@ -64,6 +64,7 @@ func CreateEtnaSubnetEvmConfig( if rewardBasisPoints != "" { cmd.Args = append(cmd.Args, rewardBasisPoints) } + fmt.Println(cmd) output, err := cmd.CombinedOutput() fmt.Println(string(output)) if err != nil { @@ -232,6 +233,7 @@ func AddEtnaSubnetValidatorToCluster( nodeEndpoint string, ewoqPChainAddress string, balance int, + stakeAmount int, createLocalValidator bool, ) (string, error) { cmd := exec.Command( @@ -247,7 +249,7 @@ func AddEtnaSubnetValidatorToCluster( "--disable-owner", ewoqPChainAddress, "--stake-amount", - "2", + strconv.Itoa(stakeAmount), "--delegation-fee", "100", "--staking-period", @@ -281,6 +283,7 @@ func RemoveEtnaSubnetValidatorFromCluster( subnetName string, nodeEndpoint string, keyName string, + uptimeSec uint64, ) (string, error) { cmd := exec.Command( CLIBinary, @@ -296,6 +299,9 @@ func RemoveEtnaSubnetValidatorFromCluster( keyName, "--key", keyName, + "--uptime", + strconv.Itoa(int(uptimeSec)), + "--force", "--"+constants.SkipUpdateFlag, ) output, err := cmd.CombinedOutput() diff --git a/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoA/suite.go b/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoA/suite.go index 4e4300f6d..c1a75e560 100644 --- a/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoA/suite.go +++ b/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoA/suite.go @@ -91,6 +91,7 @@ var _ = ginkgo.Describe("[Etna AddRemove Validator SOV PoA]", func() { "http://127.0.0.1:9660", ewoqPChainAddress, 1, + 100, false, // use existing avago running ) gomega.Expect(err).Should(gomega.BeNil()) @@ -104,6 +105,7 @@ var _ = ginkgo.Describe("[Etna AddRemove Validator SOV PoA]", func() { "http://127.0.0.1:9662", ewoqPChainAddress, 1, + 100, false, // use existing avago running ) gomega.Expect(err).Should(gomega.BeNil()) @@ -126,6 +128,7 @@ var _ = ginkgo.Describe("[Etna AddRemove Validator SOV PoA]", func() { subnetName, "http://127.0.0.1:9654", keyName, + 0, ) gomega.Expect(err).Should(gomega.BeNil()) fmt.Println(output) @@ -137,6 +140,7 @@ var _ = ginkgo.Describe("[Etna AddRemove Validator SOV PoA]", func() { subnetName, "http://127.0.0.1:9660", keyName, + 0, ) gomega.Expect(err).Should(gomega.BeNil()) fmt.Println(output) diff --git a/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoS/suite.go b/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoS/suite.go index cc4cbe681..d56409bc7 100644 --- a/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoS/suite.go +++ b/tests/e2e/testcases/subnet/sov/addRemoveValidatorPoS/suite.go @@ -93,6 +93,7 @@ var _ = ginkgo.Describe("[Etna AddRemove Validator SOV PoS]", func() { "http://127.0.0.1:9660", ewoqPChainAddress, 1, + 100, false, // use existing ) gomega.Expect(err).Should(gomega.BeNil()) @@ -106,6 +107,7 @@ var _ = ginkgo.Describe("[Etna AddRemove Validator SOV PoS]", func() { "http://127.0.0.1:9662", ewoqPChainAddress, 1, + 100, false, // use existing ) gomega.Expect(err).Should(gomega.BeNil()) @@ -122,28 +124,17 @@ var _ = ginkgo.Describe("[Etna AddRemove Validator SOV PoS]", func() { gomega.Expect(output).To(gomega.MatchRegexp(`http://127\.0\.0\.1:9654.*Validating`), "expect to have L1 validating") }) - ginkgo.It("Can wait for min staking period to pass", func() { - time.Sleep(120 * time.Second) + ginkgo.It("Can sleep for min stake duration", func() { + time.Sleep(3 * time.Minute) }) - /* - ginkgo.It("Can remove non-bootstrap validator", func() { - output, err := commands.RemoveEtnaSubnetValidatorFromCluster( - testLocalNodeName, - subnetName, - "http://127.0.0.1:9662", - keyName, - ) - gomega.Expect(err).Should(gomega.BeNil()) - fmt.Println(output) - }) - */ ginkgo.It("Can remove bootstrap validator", func() { output, err := commands.RemoveEtnaSubnetValidatorFromCluster( testLocalNodeName, subnetName, "http://127.0.0.1:9654", keyName, + 0, ) gomega.Expect(err).Should(gomega.BeNil()) fmt.Println(output) diff --git a/tests/e2e/testcases/subnet/sov/addValidatorLocal/suite.go b/tests/e2e/testcases/subnet/sov/addValidatorLocal/suite.go index 6ab1ee2c9..1d25c009b 100644 --- a/tests/e2e/testcases/subnet/sov/addValidatorLocal/suite.go +++ b/tests/e2e/testcases/subnet/sov/addValidatorLocal/suite.go @@ -44,6 +44,7 @@ var _ = ginkgo.Describe("[Etna Add Validator SOV Local]", func() { "", ewoqPChainAddress, 1, + 1, true, ) gomega.Expect(err).Should(gomega.BeNil()) diff --git a/tests/e2e/testcases/validatormanager/suite.go b/tests/e2e/testcases/validatormanager/suite.go index c7c39f6e7..47bdc13a2 100644 --- a/tests/e2e/testcases/validatormanager/suite.go +++ b/tests/e2e/testcases/validatormanager/suite.go @@ -142,7 +142,7 @@ func getBootstrapValidator() ([]*txs.ConvertSubnetToL1Validator, error) { bootstrapValidator := models.SubnetValidator{ NodeID: nodeID.String(), Weight: constants.BootstrapValidatorWeight, - Balance: constants.BootstrapValidatorBalance, + Balance: constants.BootstrapValidatorBalanceNanoAVAX, BLSPublicKey: publicKey, BLSProofOfPossession: pop, ChangeOwnerAddr: ewoqPChainAddress,