Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add upgrade proposal plan validation to CLI #10379

Merged
merged 47 commits into from
Nov 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
ac64aa1
[10286]: Create planinfo.go to house some validation pieces related t…
dwedul-figure Oct 13, 2021
22886b7
[10286]: Update the cli software-upgrade process to have a --no-valid…
dwedul-figure Oct 13, 2021
7cfac32
[10286]: Refactor the new planinfo.go stuff to make it hopefully hand…
dwedul-figure Oct 14, 2021
4ce1a17
[10286]: Switch up the --daemon-name flag a bit. Just use the DAEMON_…
dwedul-figure Oct 14, 2021
5c869f5
[10286]: Add github.com/hashicorp/go-getter v1.4.1 to the main go.mod…
dwedul-figure Oct 14, 2021
04d2f54
[10286]: Move the planinfo.go file into a new planinfo folder/package…
dwedul-figure Oct 14, 2021
161fff8
[10286]: Prep and plan some new unit tests on the new stuff.
dwedul-figure Oct 14, 2021
581e32b
[10286]: Update the flag descriptions of the new flags.
dwedul-figure Oct 14, 2021
b67858d
[10286]: Start work on some unit tests.
dwedul-figure Oct 15, 2021
65b84a8
[10286]: Require a checksum query parameter in the URLs.
dwedul-figure Oct 15, 2021
6f3175b
[10286]: For the binaries map URLs, use make sure there's a checksum …
dwedul-figure Oct 15, 2021
b646d58
[10286]: Finish up unit tests on download.go.
dwedul-figure Oct 15, 2021
0b835f8
[10286]: Fix wording of no-validate flag.
dwedul-figure Oct 15, 2021
7a7f795
[10286]: Fix some wording on the daemon-name flag.
dwedul-figure Oct 15, 2021
71abcf6
[10286]: Move the makeFileURL out of the function it's in so it can b…
dwedul-figure Oct 15, 2021
8d5cec9
[10286]: Create plan info unit tests.
dwedul-figure Oct 15, 2021
604a5be
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 15, 2021
aecbdda
[10286]: gofmt the new unit test files.
dwedul-figure Oct 15, 2021
3ad28b4
[10286]: Add changelog line for issue 10286.
dwedul-figure Oct 15, 2021
2d727d1
[10286]: Get rid of the package global osArchRx and make it just a va…
dwedul-figure Oct 18, 2021
0860283
[10286]: Rename planinfo.go to plan_info.go
dwedul-figure Oct 18, 2021
b7ec6b7
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 18, 2021
3faff93
[10286]: Fail validation if the plan.info string is blank.
dwedul-figure Oct 18, 2021
13a6cea
[10286]: Add unit test for new blank infoStr error.
dwedul-figure Oct 18, 2021
4d7384a
[10286]: Update the usage string for the --upgrade-info flag.
dwedul-figure Oct 18, 2021
fbc8e65
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 21, 2021
233beb3
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 22, 2021
73ae27a
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 25, 2021
71f1ba1
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 28, 2021
7acb128
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 28, 2021
fda7d0c
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Oct 29, 2021
2f1dfaa
[10286]: Add a little description of the default for the --daemon-nam…
dwedul-figure Nov 1, 2021
5a39cad
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Nov 1, 2021
1fa8830
[10286]: Add comment on the binary operation done to check executable…
dwedul-figure Nov 1, 2021
fff68d4
[10286]: Update comment on DownloadUpgrade to provide more information.
dwedul-figure Nov 1, 2021
5176c9d
[10286]: Update the comment on EnsureBinary to have more details.
dwedul-figure Nov 1, 2021
89efbb1
[10286]: Rename checkURL to validateIsURLWithChecksum to be clearer a…
dwedul-figure Nov 1, 2021
236af7d
[10286]: Rename downloadUpgradeAsDir to downloadUpgradeAsArchive sinc…
dwedul-figure Nov 1, 2021
34781f8
[10286]: Rename DownloadPlanInfoFromURL to DownloadURLWithChecksum an…
dwedul-figure Nov 1, 2021
4cdeb58
[10286]: Update error message from 'plan info cannot be blank' to 'pl…
dwedul-figure Nov 1, 2021
482c9f1
[10286]: Move the x/upgrade/types/planinfo stuff into x/upgrade/plan …
dwedul-figure Nov 1, 2021
716bc54
[10286]: Remove the punctuation at the end of the new software-upgrad…
dwedul-figure Nov 1, 2021
26093e8
[10286]: Add a little more to the --daemon-name description to hopefu…
dwedul-figure Nov 1, 2021
61706d7
[10286]: Make validateIsURLWithChecksum public since it's now named b…
dwedul-figure Nov 1, 2021
2641576
[10286]: Fix unit test that broke due to change.
dwedul-figure Nov 1, 2021
320d49f
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Nov 2, 2021
382a137
Merge branch 'master' into dwedul/10286-upgrade-prop-validation
dwedul-figure Nov 12, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#9837](https://github.com/cosmos/cosmos-sdk/issues/9837) `--generate-only` flag will accept the keyname now.
* [\#10326](https://github.com/cosmos/cosmos-sdk/pull/10326) `x/authz` add query all grants by granter query.
* [\#10348](https://github.com/cosmos/cosmos-sdk/pull/10348) Add `fee.{payer,granter}` and `tip` fields to StdSignDoc for signing tipped transactions.
* [\#10379](https://github.com/cosmos/cosmos-sdk/pull/10379) Add validation to `x/upgrade` CLI `software-upgrade` command `--plan-info` value.

### Improvements

Expand Down
18 changes: 18 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ require (
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/go-getter v1.4.1
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d
github.com/hdevalence/ed25519consensus v0.0.0-20210204194344-59a8610d2b87
github.com/improbable-eng/grpc-web v0.15.0
Expand Down Expand Up @@ -56,10 +58,14 @@ require (
)

require (
cloud.google.com/go v0.93.3 // indirect
cloud.google.com/go/storage v1.10.0 // indirect
filippo.io/edwards25519 v1.0.0-beta.2 // indirect
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
github.com/Workiva/go-datastructures v1.0.52 // indirect
github.com/aws/aws-sdk-go v1.27.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
Expand All @@ -78,16 +84,21 @@ require (
github.com/go-logfmt/logfmt v0.5.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/googleapis/gax-go/v2 v2.1.0 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/gtank/merlin v0.1.1 // indirect
github.com/gtank/ristretto255 v0.1.2 // indirect
github.com/hashicorp/go-immutable-radix v1.0.0 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.2.0 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d // indirect
github.com/klauspost/compress v1.12.3 // indirect
Expand All @@ -96,6 +107,8 @@ require (
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
github.com/mimoo/StrobeGo v0.0.0-20181016162300-f8f6d4d2b643 // indirect
github.com/minio/highwayhash v1.0.1 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.4.2 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
Expand All @@ -111,12 +124,17 @@ require (
github.com/subosito/gotenv v1.2.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tecbot/gorocksdb v0.0.0-20191217155057-f0fad39f321c // indirect
github.com/ulikunitz/xz v0.5.5 // indirect
github.com/zondax/hid v0.9.0 // indirect
go.etcd.io/bbolt v1.3.5 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f // indirect
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.6 // indirect
google.golang.org/api v0.56.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
Expand Down
31 changes: 31 additions & 0 deletions go.sum

Large diffs are not rendered by default.

40 changes: 39 additions & 1 deletion x/upgrade/client/cli/tx.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
package cli

import (
"os"
"path/filepath"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
gov "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/cosmos/cosmos-sdk/x/upgrade/plan"
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
)

const (
FlagUpgradeHeight = "upgrade-height"
FlagUpgradeInfo = "upgrade-info"
FlagNoValidate = "no-validate"
FlagDaemonName = "daemon-name"
)

// GetTxCmd returns the transaction commands for this module
Expand Down Expand Up @@ -45,6 +51,24 @@ func NewCmdSubmitUpgradeProposal() *cobra.Command {
if err != nil {
return err
}
noValidate, err := cmd.Flags().GetBool(FlagNoValidate)
if err != nil {
return err
}
if !noValidate {
prop := content.(*types.SoftwareUpgradeProposal)
var daemonName string
if daemonName, err = cmd.Flags().GetString(FlagDaemonName); err != nil {
return err
}
var planInfo *plan.Info
if planInfo, err = plan.ParseInfo(prop.Plan.Info); err != nil {
return err
}
if err = planInfo.ValidateFull(daemonName); err != nil {
return err
}
}

from := clientCtx.GetFromAddress()

Expand All @@ -70,7 +94,9 @@ func NewCmdSubmitUpgradeProposal() *cobra.Command {
cmd.Flags().String(cli.FlagDescription, "", "description of proposal")
cmd.Flags().String(cli.FlagDeposit, "", "deposit of proposal")
cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen")
cmd.Flags().String(FlagUpgradeInfo, "", "Optional info for the planned upgrade such as commit hash, etc.")
cmd.Flags().String(FlagUpgradeInfo, "", "Info for the upgrade plan such as new version download urls, etc.")
cmd.Flags().Bool(FlagNoValidate, false, "Skip validation of the upgrade info")
cmd.Flags().String(FlagDaemonName, getDefaultDaemonName(), "The name of the executable being upgraded (for upgrade-info validation). Default is the DAEMON_NAME env var if set, or else this executable")

return cmd
}
Expand Down Expand Up @@ -154,3 +180,15 @@ func parseArgsToContent(cmd *cobra.Command, name string) (gov.Content, error) {
content := types.NewSoftwareUpgradeProposal(title, description, plan)
return content, nil
}

// getDefaultDaemonName gets the default name to use for the daemon.
// If a DAEMON_NAME env var is set, that is used.
// Otherwise, the last part of the currently running executable is used.
func getDefaultDaemonName() string {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typically, I think env vars take precedence over flags.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Nope. Flags take top priority. Environment variables aren't obvious when executing a command, but flags and options are. Having an env var take precedence over a flag would cause confusion. For reference, the Viper library uses this order of precedence: flag > env var > config file value > default.

// DAEMON_NAME is specifically used here to correspond with the Comsovisor setup env vars.
name := os.Getenv("DAEMON_NAME")
if len(name) == 0 {
_, name = filepath.Split(os.Args[0])
}
return name
}
141 changes: 141 additions & 0 deletions x/upgrade/plan/downloader.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package plan

import (
"errors"
"fmt"
neturl "net/url"
"os"
"path/filepath"
"strings"

"github.com/hashicorp/go-getter"
)

// DownloadUpgrade downloads the given url into the provided directory and ensures it's valid.
// The provided url must contain a checksum parameter that matches the file being downloaded.
// If this returns nil, the download was successful, and {dstRoot}/bin/{daemonName} is a regular executable file.
// This is an opinionated directory structure that corresponds with Cosmovisor requirements.
// If the url is not an archive, it is downloaded and saved to {dstRoot}/bin/{daemonName}.
// If the url is an archive, it is downloaded and unpacked to {dstRoot}.
// If the archive does not contain a /bin/{daemonName} file, then this will attempt to move /{daemonName} to /bin/{daemonName}.
// If the archive does not contain either /bin/{daemonName} or /{daemonName}, an error is returned.
// Note: Because a checksum is required, this function cannot be used to download non-archive directories.
// If dstRoot already exists, some or all of its contents might be updated.
func DownloadUpgrade(dstRoot, url, daemonName string) error {
if err := ValidateIsURLWithChecksum(url); err != nil {
return err
}
target := filepath.Join(dstRoot, "bin", daemonName)
// First try to download it as a single file. If there's no error, it's okay and we're done.
if err := getter.GetFile(target, url); err != nil {
// If it was a checksum error, no need to try as directory.
if _, ok := err.(*getter.ChecksumError); ok {
return err
}
// File download didn't work, try it as an archive.
if err = downloadUpgradeAsArchive(dstRoot, url, daemonName); err != nil {
// Out of options, send back the error.
return err
}
}
return EnsureBinary(target)
}

// downloadUpgradeAsArchive tries to download the given url as an archive.
// The archive is unpacked and saved in dstDir.
// If the archive contains /{daemonName} and not /bin/{daemonName}, then /{daemonName} will be moved to /bin/{daemonName}.
// If this returns nil, the download was successful, and {dstDir}/bin/{daemonName} is a regular executable file.
func downloadUpgradeAsArchive(dstDir, url, daemonName string) error {
err := getter.Get(dstDir, url)
if err != nil {
return err
}

// If bin/{daemonName} exists, we're done.
dstDirBinFile := filepath.Join(dstDir, "bin", daemonName)
err = EnsureBinary(dstDirBinFile)
if err == nil {
return nil
}

// Otherwise, check for a root {daemonName} file and move it to the bin/ directory if found.
dstDirFile := filepath.Join(dstDir, daemonName)
err = EnsureBinary(dstDirFile)
if err == nil {
err = os.Rename(dstDirFile, dstDirBinFile)
if err != nil {
return fmt.Errorf("could not move %s to the bin directory: %w", daemonName, err)
}
return nil
}

return fmt.Errorf("url \"%s\" result does not contain a bin/%s or %s file", url, daemonName, daemonName)
}

// EnsureBinary checks that the given file exists as a regular file and is executable.
// An error is returned if:
// - The file does not exist.
// - The path exists, but is one of: Dir, Symlink, NamedPipe, Socket, Device, CharDevice, or Irregular.
// - The file exists, is not executable by all three of User, Group, and Other, and cannot be made executable.
func EnsureBinary(path string) error {
info, err := os.Stat(path)
if err != nil {
return err
}
if !info.Mode().IsRegular() {
_, f := filepath.Split(path)
return fmt.Errorf("%s is not a regular file", f)
}
// Make sure all executable bits are set.
oldMode := info.Mode().Perm()
newMode := oldMode | 0111 // Set the three execute bits to on (a+x).
if oldMode != newMode {
return os.Chmod(path, newMode)
}
return nil
}

// DownloadURLWithChecksum gets the contents of the given url, ensuring the checksum is correct.
// The provided url must contain a checksum parameter that matches the file being downloaded.
// If there isn't an error, the content returned by the url will be returned as a string.
// Returns an error if:
// - The url is not a URL or does not contain a checksum parameter.
// - Downloading the URL fails.
// - The checksum does not match what is returned by the URL.
// - The URL does not return a regular file.
// - The downloaded file is empty or only whitespace.
func DownloadURLWithChecksum(url string) (string, error) {
if err := ValidateIsURLWithChecksum(url); err != nil {
return "", err
}
tempDir, err := os.MkdirTemp("", "reference")
if err != nil {
return "", fmt.Errorf("could not create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
tempFile := filepath.Join(tempDir, "content")
if err = getter.GetFile(tempFile, url); err != nil {
return "", fmt.Errorf("could not download url \"%s\": %w", url, err)
}
tempFileBz, rerr := os.ReadFile(tempFile)
if rerr != nil {
return "", fmt.Errorf("could not read downloaded temporary file: %w", rerr)
}
tempFileStr := strings.TrimSpace(string(tempFileBz))
if len(tempFileStr) == 0 {
return "", fmt.Errorf("no content returned by \"%s\"", url)
}
return tempFileStr, nil
}

// ValidateIsURLWithChecksum checks that the given string is a url and contains a checksum query parameter.
func ValidateIsURLWithChecksum(urlStr string) error {
url, err := neturl.Parse(urlStr)
if err != nil {
return err
}
if len(url.Query().Get("checksum")) == 0 {
return errors.New("missing checksum query parameter")
}
return nil
}
Loading