diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e7bb32..f541a18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Improvements - [#27](https://github.com/MalteHerrmann/upgrade-local-node-go/pull/27) Add MIT license +- [#28](https://github.com/MalteHerrmann/upgrade-local-node-go/pull/28) Use [Cobra CLI](https://github.com/spf13/cobra) package ## [v0.3.0](https://github.com/MalteHerrmann/upgrade-local-node-go/releases/tag/v0.3.0) - 2023-08-30 diff --git a/cmd/deposit.go b/cmd/deposit.go new file mode 100644 index 0000000..1162dc3 --- /dev/null +++ b/cmd/deposit.go @@ -0,0 +1,38 @@ +package cmd + +import ( + "log" + + "github.com/MalteHerrmann/upgrade-local-node-go/gov" + "github.com/MalteHerrmann/upgrade-local-node-go/utils" + "github.com/spf13/cobra" +) + +//nolint:gochecknoglobals // required by cobra +var depositCmd = &cobra.Command{ + Use: "deposit", + Short: "Deposit for a governance proposal", + Long: `Deposit the minimum needed deposit for a given governance proposal. +If no proposal ID is given by the user, the latest proposal is queried and deposited for.`, + Args: cobra.RangeArgs(0, 1), + Run: func(cmd *cobra.Command, args []string) { + bin, err := utils.NewEvmosTestingBinary() + if err != nil { + log.Fatalf("error creating binary: %v", err) + } + + if err = bin.GetAccounts(); err != nil { + log.Fatalf("error getting accounts: %v", err) + } + + err = gov.Deposit(bin, args) + if err != nil { + log.Fatalf("error depositing: %v", err) + } + }, +} + +//nolint:gochecknoinits // required by cobra +func init() { + rootCmd.AddCommand(depositCmd) +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..5a9f429 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,26 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands. +// +//nolint:gochecknoglobals // required by cobra +var rootCmd = &cobra.Command{ + Use: "evmos-utils", + Short: "A collection of utilities to interact with an Evmos node during development.", + Long: `The evmos-utils collection offers helpers to interact with an Evmos node during development. +It can be used to test upgrades, deposit or vote for specific or the latest proposals, etc.`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} diff --git a/cmd/upgrade.go b/cmd/upgrade.go new file mode 100644 index 0000000..d2b6260 --- /dev/null +++ b/cmd/upgrade.go @@ -0,0 +1,70 @@ +package cmd + +import ( + "log" + "regexp" + + "github.com/MalteHerrmann/upgrade-local-node-go/gov" + "github.com/MalteHerrmann/upgrade-local-node-go/utils" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +//nolint:gochecknoglobals // required by cobra +var upgradeCmd = &cobra.Command{ + Use: "upgrade", + Short: "Prepare an upgrade of a node", + Long: `Prepare an upgrade of a node by submitting a governance proposal, +voting for it using all keys of in the keyring and having it pass.`, + Args: cobra.ExactArgs(1), + Run: func(cmd *cobra.Command, args []string) { + bin, err := utils.NewEvmosTestingBinary() + if err != nil { + log.Fatalf("error creating binary: %v", err) + } + + if err = bin.GetAccounts(); err != nil { + log.Fatalf("error getting accounts: %v", err) + } + + targetVersion := args[0] + if matched, _ := regexp.MatchString(`v\d+\.\d+\.\d(-rc\d+)?`, targetVersion); !matched { + log.Fatalf("invalid target version: %s; please use the format vX.Y.Z(-rc*).\n", targetVersion) + } + + if err := upgradeLocalNode(bin, targetVersion); err != nil { + log.Fatalf("error upgrading local node: %v", err) + } + }, +} + +//nolint:gochecknoinits // required by cobra +func init() { + rootCmd.AddCommand(upgradeCmd) +} + +// upgradeLocalNode prepares upgrading the local node to the target version +// by submitting the upgrade proposal and voting on it using all testing accounts. +func upgradeLocalNode(bin *utils.Binary, targetVersion string) error { + currentHeight, err := utils.GetCurrentHeight(bin) + if err != nil { + return errors.Wrap(err, "error getting current height") + } + + upgradeHeight := currentHeight + utils.DeltaHeight + + log.Println("Submitting upgrade proposal...") + + proposalID, err := gov.SubmitUpgradeProposal(bin, targetVersion, upgradeHeight) + if err != nil { + return errors.Wrap(err, "error executing upgrade proposal") + } + + log.Printf("Scheduled upgrade to %s at height %d.\n", targetVersion, upgradeHeight) + + if err = gov.SubmitAllVotesForProposal(bin, proposalID); err != nil { + return errors.Wrapf(err, "error submitting votes for proposal %d", proposalID) + } + + return nil +} diff --git a/cmd/vote.go b/cmd/vote.go new file mode 100644 index 0000000..878dacf --- /dev/null +++ b/cmd/vote.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "log" + + "github.com/MalteHerrmann/upgrade-local-node-go/gov" + "github.com/MalteHerrmann/upgrade-local-node-go/utils" + "github.com/spf13/cobra" +) + +//nolint:gochecknoglobals // required by cobra +var voteCmd = &cobra.Command{ + Use: "vote", + Short: "Vote for a governance proposal", + Long: `Vote for a governance proposal with all keys in the keyring. +If no proposal ID is passed, the latest proposal on chain is queried and used.`, + Args: cobra.RangeArgs(0, 1), + Run: func(cmd *cobra.Command, args []string) { + bin, err := utils.NewEvmosTestingBinary() + if err != nil { + log.Fatalf("error creating binary: %v", err) + } + + if err = bin.GetAccounts(); err != nil { + log.Fatalf("error getting accounts: %v", err) + } + + if err = gov.SubmitAllVotes(bin, args); err != nil { + log.Fatal(err) + } + }, +} + +//nolint:gochecknoinits // required by cobra +func init() { + rootCmd.AddCommand(voteCmd) +} diff --git a/go.mod b/go.mod index 739f8d6..8377abf 100644 --- a/go.mod +++ b/go.mod @@ -155,7 +155,7 @@ require ( github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect - github.com/spf13/cobra v1.7.0 // indirect + github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/viper v1.16.0 // indirect diff --git a/go.sum b/go.sum index 81c9567..a9c1cb0 100644 --- a/go.sum +++ b/go.sum @@ -375,6 +375,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creachadair/taskgroup v0.4.2 h1:jsBLdAJE42asreGss2xZGZ8fJra7WtwnHWeJFxv2Li8= github.com/creachadair/taskgroup v0.4.2/go.mod h1:qiXUOSrbwAY3u0JPGTzObbE3yf9hcXHDKBZ2ZjpCbgM= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= @@ -1031,6 +1032,8 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= diff --git a/gov/deposit.go b/gov/deposit.go index 1a519ea..10f30df 100644 --- a/gov/deposit.go +++ b/gov/deposit.go @@ -2,6 +2,7 @@ package gov import ( "fmt" + "log" "regexp" "strconv" @@ -10,10 +11,27 @@ import ( "github.com/pkg/errors" ) +// Deposit deposits the minimum needed deposit for a given governance proposal. +func Deposit(bin *utils.Binary, args []string) error { + deposit, err := GetMinDeposit(bin) + if err != nil { + log.Fatalf("error getting minimum deposit: %v", err) + } + + proposalID, err := GetProposalIDFromInput(bin, args) + if err != nil { + log.Fatalf("error getting proposal ID: %v", err) + } + + return DepositForProposal( + bin, proposalID, bin.Accounts[0].Name, deposit.String(), + ) +} + // DepositForProposal deposits the given amount for the proposal with the given proposalID // from the given account. -func DepositForProposal(bin *utils.Binary, proposalID int, sender, deposit string) (string, error) { - out, err := utils.ExecuteBinaryCmd(bin, utils.BinaryCmdArgs{ +func DepositForProposal(bin *utils.Binary, proposalID int, sender, deposit string) error { + _, err := utils.ExecuteBinaryCmd(bin, utils.BinaryCmdArgs{ Subcommand: []string{ "tx", "gov", "deposit", strconv.Itoa(proposalID), deposit, }, @@ -22,10 +40,10 @@ func DepositForProposal(bin *utils.Binary, proposalID int, sender, deposit strin Quiet: true, }) if err != nil { - return out, errors.Wrap(err, fmt.Sprintf("failed to deposit for proposal %d", proposalID)) + return errors.Wrap(err, fmt.Sprintf("failed to deposit for proposal %d", proposalID)) } - return out, nil + return nil } // GetMinDeposit returns the minimum deposit necessary for a proposal from the governance parameters of @@ -48,7 +66,6 @@ func GetMinDeposit(bin *utils.Binary) (sdk.Coins, error) { // FIXME: It wasn't possible to unmarshal the JSON output of the query because of a missing unit in the max_deposit_period // parameter. This should rather be done using GRPC. func ParseMinDepositFromResponse(out string) (sdk.Coins, error) { - // FIXME: This is a workaround for the missing unit in the max_deposit_period parameter. Should be done with gRPC. depositPatternRaw := `min_deposit":\[{"denom":"(\w+)","amount":"(\d+)` depositPattern := regexp.MustCompile(depositPatternRaw) diff --git a/gov/utils.go b/gov/utils.go new file mode 100644 index 0000000..fae595d --- /dev/null +++ b/gov/utils.go @@ -0,0 +1,34 @@ +package gov + +import ( + "fmt" + "strconv" + + "github.com/MalteHerrmann/upgrade-local-node-go/utils" + "github.com/pkg/errors" +) + +// GetProposalIDFromInput gets the proposal ID from the command line arguments. +func GetProposalIDFromInput(bin *utils.Binary, args []string) (int, error) { + var ( + err error + proposalID int + ) + + switch len(args) { + case 0: + proposalID, err = QueryLatestProposalID(bin) + if err != nil { + return 0, errors.Wrap(err, "error querying latest proposal ID") + } + case 1: + proposalID, err = strconv.Atoi(args[2]) + if err != nil { + return 0, errors.Wrapf(err, "error converting proposal ID %s to integer", args[2]) + } + default: + return 0, fmt.Errorf("invalid number of arguments; expected 0 or 1; got %d", len(args)) + } + + return proposalID, nil +} diff --git a/gov/vote.go b/gov/vote.go index b7e5310..e50af3f 100644 --- a/gov/vote.go +++ b/gov/vote.go @@ -10,6 +10,16 @@ import ( "github.com/pkg/errors" ) +// SubmitAllVotes submits a vote for the given proposal ID using all testing accounts. +func SubmitAllVotes(bin *utils.Binary, args []string) error { + proposalID, err := GetProposalIDFromInput(bin, args) + if err != nil { + return err + } + + return SubmitAllVotesForProposal(bin, proposalID) +} + // SubmitAllVotesForProposal submits a vote for the given proposal ID using all testing accounts. func SubmitAllVotesForProposal(bin *utils.Binary, proposalID int) error { accsWithDelegations, err := utils.FilterAccountsWithDelegations(bin) diff --git a/main.go b/main.go index 18618ea..fbca868 100644 --- a/main.go +++ b/main.go @@ -1,122 +1,9 @@ package main import ( - "log" - "os" - "regexp" - "strconv" - - "github.com/MalteHerrmann/upgrade-local-node-go/gov" - "github.com/MalteHerrmann/upgrade-local-node-go/utils" - "github.com/pkg/errors" + "github.com/MalteHerrmann/upgrade-local-node-go/cmd" ) func main() { - if len(os.Args) < 2 { - log.Printf( - "Possible usages:\n" + - " upgrade-local-node-go \n" + - " upgrade-local-node-go vote [proposal-id]\n" + - " upgrade-local-node-go deposit [proposal-id]\n", - ) - os.Exit(1) - } - - bin, err := utils.NewEvmosTestingBinary() - if err != nil { - log.Fatalf("error creating binary: %v", err) - } - - if err = bin.GetAccounts(); err != nil { - log.Fatalf("error getting accounts: %v", err) - } - - // TODO: use with Cobra CLI - switch os.Args[1] { - case "vote": - proposalID, err := getProposalIDFromInput(bin, os.Args) - if err != nil { - log.Fatalf("error getting proposal ID: %v", err) - } - - if err = gov.SubmitAllVotesForProposal(bin, proposalID); err != nil { - log.Fatalf("error submitting votes for proposal %d: %v", proposalID, err) - } - - case "deposit": - deposit, err := gov.GetMinDeposit(bin) - if err != nil { - log.Fatalf("error getting minimum deposit: %v", err) - } - - proposalID, err := getProposalIDFromInput(bin, os.Args) - if err != nil { - log.Fatalf("error getting proposal ID: %v", err) - } - - if _, err = gov.DepositForProposal(bin, proposalID, bin.Accounts[0].Name, deposit.String()); err != nil { - log.Fatalf("error depositing for proposal %d: %v", proposalID, err) - } - - default: - targetVersion := os.Args[1] - if matched, _ := regexp.MatchString(`v\d+\.\d+\.\d(-rc\d+)?`, targetVersion); !matched { - log.Fatalf("invalid target version: %s; please use the format vX.Y.Z(-rc*).\n", targetVersion) - } - - if err := upgradeLocalNode(bin, targetVersion); err != nil { - log.Fatalf("error upgrading local node: %v", err) - } - } -} - -// getProposalIDFromInput gets the proposal ID from the command line arguments. -func getProposalIDFromInput(bin *utils.Binary, args []string) (int, error) { - var ( - err error - proposalID int - ) - - switch len(args) { - case 2: - proposalID, err = gov.QueryLatestProposalID(bin) - if err != nil { - return 0, errors.Wrap(err, "error querying latest proposal ID") - } - case 3: - proposalID, err = strconv.Atoi(args[2]) - if err != nil { - return 0, errors.Wrapf(err, "error converting proposal ID %s to integer", args[2]) - } - default: - return 0, errors.New("invalid number of arguments") - } - - return proposalID, nil -} - -// upgradeLocalNode prepares upgrading the local node to the target version -// by submitting the upgrade proposal and voting on it using all testing accounts. -func upgradeLocalNode(bin *utils.Binary, targetVersion string) error { - currentHeight, err := utils.GetCurrentHeight(bin) - if err != nil { - return errors.Wrap(err, "error getting current height") - } - - upgradeHeight := currentHeight + utils.DeltaHeight - - log.Println("Submitting upgrade proposal...") - - proposalID, err := gov.SubmitUpgradeProposal(bin, targetVersion, upgradeHeight) - if err != nil { - return errors.Wrap(err, "error executing upgrade proposal") - } - - log.Printf("Scheduled upgrade to %s at height %d.\n", targetVersion, upgradeHeight) - - if err = gov.SubmitAllVotesForProposal(bin, proposalID); err != nil { - return errors.Wrapf(err, "error submitting votes for proposal %d", proposalID) - } - - return nil + cmd.Execute() } diff --git a/utils/constants.go b/utils/constants.go index 85c96c5..bd66bbc 100644 --- a/utils/constants.go +++ b/utils/constants.go @@ -5,10 +5,10 @@ import ( ) const ( - // The amount of fees to be sent with a default transaction. + // defaultFees is the amount of fees to be sent with a default transaction. defaultFees int = 1e18 // 1 aevmos - // The amount of blocks in the future that the upgrade will be scheduled. + // DeltaHeight is the amount of blocks in the future that the upgrade will be scheduled. DeltaHeight = 10 - // The denomination used for the local node. + // denom is the denomination used for the local node. denom = evmosutils.BaseDenom )