From 215158d52dc2d5483d283ee768fd1b39e7873529 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 12 Jun 2023 23:48:34 +0200 Subject: [PATCH 1/5] feat: make checksum optional in upgrade validation --- tools/cosmovisor/CHANGELOG.md | 4 --- x/upgrade/CHANGELOG.md | 7 ++-- x/upgrade/client/cli/query.go | 16 ++++----- x/upgrade/client/cli/tx.go | 24 ++++++++----- x/upgrade/plan/downloader.go | 27 ++++++--------- x/upgrade/plan/downloader_test.go | 24 +++++-------- x/upgrade/plan/info.go | 56 +++++++++++++++++++++++++------ x/upgrade/plan/info_test.go | 21 +++++++----- 8 files changed, 105 insertions(+), 74 deletions(-) diff --git a/tools/cosmovisor/CHANGELOG.md b/tools/cosmovisor/CHANGELOG.md index 59b93c7ce318..2782966af87e 100644 --- a/tools/cosmovisor/CHANGELOG.md +++ b/tools/cosmovisor/CHANGELOG.md @@ -41,10 +41,6 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor. * [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor. -## Client Breaking Changes - -* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Cosmovisor supports only upgrade plan with a checksum. This is enforced by the `x/upgrade` module for better security. - ## Improvements * [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to use `x/upgrade` validation logic. diff --git a/x/upgrade/CHANGELOG.md b/x/upgrade/CHANGELOG.md index 78c064a6f508..1358ed44bb0d 100644 --- a/x/upgrade/CHANGELOG.md +++ b/x/upgrade/CHANGELOG.md @@ -30,6 +30,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ * [#14880](https://github.com/cosmos/cosmos-sdk/pull/14880) Switch from using gov v1beta1 to gov v1 in upgrade CLIs. * [#14764](https://github.com/cosmos/cosmos-sdk/pull/14764) The `x/upgrade` module is extracted to have a separate go.mod file which allows it be a standalone module. -### API Breaking +### API Breaking Changes -* (x/upgrade) [#16227](https://github.com/cosmos/cosmos-sdk/issues/16227) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error`. `UpgradeHandler` now receives a `context.Context`. `GetUpgradedClient`, `GetUpgradedConsensusState`, `GetUpgradePlan` now return a specific error for "not found". +* []() `BinaryDownloadURLMap.ValidateBasic()` and `BinaryDownloadURLMap.CheckURLs` now both take a checksum parameter when willing to ensure a checksum is provided for each URL. +* []() `plan.DownloadURLWithChecksum` has been renamed to `plan.DownloadURL` and does not validate the URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadURL` to validate the URL. +* []() `plan.DownloadUpgrade` does not validate URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadUpgrade` to validate the URL. +* [#16227](https://github.com/cosmos/cosmos-sdk/issues/16227) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error`. `UpgradeHandler` now receives a `context.Context`. `GetUpgradedClient`, `GetUpgradedConsensusState`, `GetUpgradePlan` now return a specific error for "not found". diff --git a/x/upgrade/client/cli/query.go b/x/upgrade/client/cli/query.go index a8ff931ad2ac..b5eda8e20493 100644 --- a/x/upgrade/client/cli/query.go +++ b/x/upgrade/client/cli/query.go @@ -42,8 +42,8 @@ func GetCurrentPlanCmd() *cobra.Command { } queryClient := types.NewQueryClient(clientCtx) - params := types.QueryCurrentPlanRequest{} - res, err := queryClient.CurrentPlan(cmd.Context(), ¶ms) + req := types.QueryCurrentPlanRequest{} + res, err := queryClient.CurrentPlan(cmd.Context(), &req) if err != nil { return err } @@ -77,8 +77,8 @@ func GetAppliedPlanCmd() *cobra.Command { } queryClient := types.NewQueryClient(clientCtx) ctx := cmd.Context() - params := types.QueryAppliedPlanRequest{Name: args[0]} - res, err := queryClient.AppliedPlan(ctx, ¶ms) + req := types.QueryAppliedPlanRequest{Name: args[0]} + res, err := queryClient.AppliedPlan(ctx, &req) if err != nil { return err } @@ -130,15 +130,15 @@ func GetModuleVersionsCmd() *cobra.Command { } queryClient := types.NewQueryClient(clientCtx) - var params types.QueryModuleVersionsRequest + var req types.QueryModuleVersionsRequest if len(args) == 1 { - params = types.QueryModuleVersionsRequest{ModuleName: args[0]} + req = types.QueryModuleVersionsRequest{ModuleName: args[0]} } else { - params = types.QueryModuleVersionsRequest{} + req = types.QueryModuleVersionsRequest{} } - res, err := queryClient.ModuleVersions(cmd.Context(), ¶ms) + res, err := queryClient.ModuleVersions(cmd.Context(), &req) if err != nil { return err } diff --git a/x/upgrade/client/cli/tx.go b/x/upgrade/client/cli/tx.go index 893b74680e1f..4702adce3e2c 100644 --- a/x/upgrade/client/cli/tx.go +++ b/x/upgrade/client/cli/tx.go @@ -19,11 +19,12 @@ import ( ) const ( - FlagUpgradeHeight = "upgrade-height" - FlagUpgradeInfo = "upgrade-info" - FlagNoValidate = "no-validate" - FlagDaemonName = "daemon-name" - FlagAuthority = "authority" + FlagUpgradeHeight = "upgrade-height" + FlagUpgradeInfo = "upgrade-info" + FlagNoValidate = "no-validate" + FlagNoChecksumRequired = "no-checksum-required" + FlagDaemonName = "daemon-name" + FlagAuthority = "authority" ) // GetTxCmd returns the transaction commands for this module @@ -73,15 +74,21 @@ func NewCmdSubmitUpgradeProposal(ac addresscodec.Codec) *cobra.Command { } if !noValidate { - var daemonName string - if daemonName, err = cmd.Flags().GetString(FlagDaemonName); err != nil { + daemonName, err := cmd.Flags().GetString(FlagDaemonName) + if err != nil { + return err + } + + noChecksum, err := cmd.Flags().GetBool(FlagNoChecksumRequired) + if err != nil { return err } var planInfo *plan.Info - if planInfo, err = plan.ParseInfo(p.Info); err != nil { + if planInfo, err = plan.ParseInfo(p.Info, plan.ParseOptionEnforceChecksum(!noChecksum)); err != nil { return err } + if err = planInfo.ValidateFull(daemonName); err != nil { return err } @@ -112,6 +119,7 @@ func NewCmdSubmitUpgradeProposal(ac addresscodec.Codec) *cobra.Command { cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen") 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 (dangerous!)") + cmd.Flags().Bool(FlagNoChecksumRequired, false, "Skip validation of checksums in 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") cmd.Flags().String(FlagAuthority, "", "The address of the upgrade module authority (defaults to gov)") diff --git a/x/upgrade/plan/downloader.go b/x/upgrade/plan/downloader.go index d09d4725b8ea..1af03d7a2db2 100644 --- a/x/upgrade/plan/downloader.go +++ b/x/upgrade/plan/downloader.go @@ -11,8 +11,7 @@ import ( "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. +// DownloadUpgrade downloads the given url into the provided directory. // 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}. @@ -21,12 +20,9 @@ import ( // 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. +// NOTE: This functions does not check the provided url for validity. 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 { @@ -97,19 +93,16 @@ func EnsureBinary(path string) error { 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. +// DownloadURL gets the contents of the given url. +// The provided url can 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. +// - The url is not a URL or does not contain a checksum parameter (when required). // - 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 - } +func DownloadURL(url string) (string, error) { tempDir, err := os.MkdirTemp("", "reference") if err != nil { return "", fmt.Errorf("could not create temp directory: %w", err) @@ -130,14 +123,16 @@ func DownloadURLWithChecksum(url string) (string, error) { return tempFileStr, nil } -// ValidateIsURLWithChecksum checks that the given string is a url and contains a checksum query parameter. -func ValidateIsURLWithChecksum(urlStr string) error { +// ValidateURL checks that the given string is a valid url and optionally contains a checksum query parameter. +func ValidateURL(urlStr string, mustChecksum bool) error { url, err := neturl.Parse(urlStr) if err != nil { return err } - if len(url.Query().Get("checksum")) == 0 { + + if mustChecksum && len(url.Query().Get("checksum")) == 0 { return errors.New("missing checksum query parameter") } + return nil } diff --git a/x/upgrade/plan/downloader_test.go b/x/upgrade/plan/downloader_test.go index ef0f727550ab..7768b30c1663 100644 --- a/x/upgrade/plan/downloader_test.go +++ b/x/upgrade/plan/downloader_test.go @@ -158,14 +158,6 @@ func (s *DownloaderTestSuite) TestDownloadUpgrade() { assert.Contains(t, err.Error(), "no such file or directory") }) - s.T().Run("url does not have checksum", func(t *testing.T) { - dstRoot := getDstDir(t.Name()) - url := "file://" + justAFilePath - err := DownloadUpgrade(dstRoot, url, justAFile.Name) - require.Error(t, err) - require.Contains(t, err.Error(), "missing checksum query parameter") - }) - s.T().Run("url has incorrect checksum", func(t *testing.T) { dstRoot := getDstDir(t.Name()) badChecksum := "2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48" @@ -248,7 +240,7 @@ func (s *DownloaderTestSuite) TestEnsureBinary() { }) } -func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() { +func (s *DownloaderTestSuite) TestDownloadURL() { planContents := `{"binaries":{"xxx/yyy":"url"}}` planFile := NewTestFile("plan-info.json", planContents) planPath := s.saveSrcTestFile(planFile) @@ -259,21 +251,21 @@ func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() { s.T().Run("url does not exist", func(t *testing.T) { url := "file:///never-gonna-be-a-thing?checksum=sha256:2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48" - _, err := DownloadURLWithChecksum(url) + _, err := DownloadURL(url) require.Error(t, err) assert.Contains(t, err.Error(), "could not download url") }) s.T().Run("without checksum", func(t *testing.T) { url := "file://" + planPath - _, err := DownloadURLWithChecksum(url) - require.Error(t, err) - assert.Contains(t, err.Error(), "missing checksum query parameter") + actual, err := DownloadURL(url) + require.NoError(t, err) + require.Equal(t, planContents, actual) }) s.T().Run("with correct checksum", func(t *testing.T) { url := "file://" + planPath + "?checksum=sha256:" + planChecksum - actual, err := DownloadURLWithChecksum(url) + actual, err := DownloadURL(url) require.NoError(t, err) require.Equal(t, planContents, actual) }) @@ -281,7 +273,7 @@ func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() { s.T().Run("with incorrect checksum", func(t *testing.T) { badChecksum := "2c22e34510bd1d4ad2343cdc54f7165bccf30caef73f39af7dd1db2795a3da48" url := "file://" + planPath + "?checksum=sha256:" + badChecksum - _, err := DownloadURLWithChecksum(url) + _, err := DownloadURL(url) require.Error(t, err) assert.Contains(t, err.Error(), "Checksums did not match") assert.Contains(t, err.Error(), "Expected: "+badChecksum) @@ -290,7 +282,7 @@ func (s *DownloaderTestSuite) TestDownloadURLWithChecksum() { s.T().Run("plan is empty", func(t *testing.T) { url := "file://" + emptyPlanPath + "?checksum=sha256:" + emptyChecksum - _, err := DownloadURLWithChecksum(url) + _, err := DownloadURL(url) require.Error(t, err) assert.Contains(t, err.Error(), "no content returned") }) diff --git a/x/upgrade/plan/info.go b/x/upgrade/plan/info.go index ac636412cf68..545991c71b94 100644 --- a/x/upgrade/plan/info.go +++ b/x/upgrade/plan/info.go @@ -15,24 +15,51 @@ import ( // Info is the special structure that the Plan.Info string can be (as json). type Info struct { + parseConfig ParseConfig `json:"-"` + Binaries BinaryDownloadURLMap `json:"binaries"` } // BinaryDownloadURLMap is a map of os/architecture stings to a URL where the binary can be downloaded. type BinaryDownloadURLMap map[string]string +// ParseConfig is used to configure the parsing of a Plan.Info string. +type ParseConfig struct { + // EnforceChecksum, if true, will cause all downloaded files to be checked against their checksums. + // When false, checksums are not enforced to be present in the url. + EnforceChecksum bool +} + +// ParseOption is used to configure the parsing of a Plan.Info string. +type ParseOption func(*ParseConfig) + +// ParseOptionEnforceChecksum returns a ParseOption that sets the EnforceChecksum field of the ParseConfig. +func ParseOptionEnforceChecksum(enforce bool) ParseOption { + return func(c *ParseConfig) { + c.EnforceChecksum = enforce + } +} + // ParseInfo parses an info string into a map of os/arch strings to URL string. // If the infoStr is a url, an GET request will be made to it, and its response will be parsed instead. -func ParseInfo(infoStr string) (*Info, error) { - infoStr = strings.TrimSpace(infoStr) +func ParseInfo(infoStr string, opts ...ParseOption) (*Info, error) { + parseConfig := &ParseConfig{} + for _, opt := range opts { + opt(parseConfig) + } + infoStr = strings.TrimSpace(infoStr) if len(infoStr) == 0 { return nil, errors.New("plan info must not be blank") } // If it's a url, download it and treat the result as the real info. if _, err := neturl.Parse(infoStr); err == nil { - infoStr, err = DownloadURLWithChecksum(infoStr) + if err := ValidateURL(infoStr, parseConfig.EnforceChecksum); err != nil { + return nil, err + } + + infoStr, err = DownloadURL(infoStr) if err != nil { return nil, err } @@ -44,6 +71,8 @@ func ParseInfo(infoStr string) (*Info, error) { return nil, fmt.Errorf("could not parse plan info: %v", err) } + planInfo.parseConfig = *parseConfig + return &planInfo, nil } @@ -55,10 +84,10 @@ func ParseInfo(infoStr string) (*Info, error) { // // Warning: This is an expensive process. See BinaryDownloadURLMap.CheckURLs for more info. func (m Info) ValidateFull(daemonName string) error { - if err := m.Binaries.ValidateBasic(); err != nil { + if err := m.Binaries.ValidateBasic(m.parseConfig.EnforceChecksum); err != nil { return err } - if err := m.Binaries.CheckURLs(daemonName); err != nil { + if err := m.Binaries.CheckURLs(daemonName, m.parseConfig.EnforceChecksum); err != nil { return err } return nil @@ -69,8 +98,8 @@ func (m Info) ValidateFull(daemonName string) error { // - This has at least one entry. // - All entry keys have the format "os/arch" or are "any". // - All entry values are valid URLs. -// - All URLs contain a checksum query parameter. -func (m BinaryDownloadURLMap) ValidateBasic() error { +// - When `enforceChecksum` is true all URLs must contain a checksum query parameter. +func (m BinaryDownloadURLMap) ValidateBasic(enforceChecksum bool) error { // Make sure there's at least one. if len(m) == 0 { return errors.New("no \"binaries\" entries found") @@ -81,8 +110,9 @@ func (m BinaryDownloadURLMap) ValidateBasic() error { if key != "any" && !osArchRx.MatchString(key) { return fmt.Errorf("invalid os/arch format in key \"%s\"", key) } - if err := ValidateIsURLWithChecksum(val); err != nil { - return fmt.Errorf("invalid url \"%s\" in binaries[%s]: %v", val, key, err) + + if err := ValidateURL(val, enforceChecksum); err != nil { + return fmt.Errorf("invalid url \"%s\" in binaries[%s]: %w", val, key, err) } } @@ -93,7 +123,7 @@ func (m BinaryDownloadURLMap) ValidateBasic() error { // The provided daemonName is the name of the executable file expected in all downloaded directories. // Warning: This is an expensive process. // It will make an HTTP GET request to each URL and download the response. -func (m BinaryDownloadURLMap) CheckURLs(daemonName string) error { +func (m BinaryDownloadURLMap) CheckURLs(daemonName string, enforceChecksum bool) error { tempDir, err := os.MkdirTemp("", "os-arch-downloads") if err != nil { return fmt.Errorf("could not create temp directory: %w", err) @@ -101,8 +131,12 @@ func (m BinaryDownloadURLMap) CheckURLs(daemonName string) error { defer os.RemoveAll(tempDir) for osArch, url := range m { dstRoot := filepath.Join(tempDir, strings.ReplaceAll(osArch, "/", "-")) + if err := ValidateURL(url, enforceChecksum); err != nil { + return fmt.Errorf("error validating url for os/arch %s: %w", osArch, err) + } + if err = DownloadUpgrade(dstRoot, url, daemonName); err != nil { - return fmt.Errorf("error downloading binary for os/arch %s: %v", osArch, err) + return fmt.Errorf("error downloading binary for os/arch %s: %w", osArch, err) } } return nil diff --git a/x/upgrade/plan/info_test.go b/x/upgrade/plan/info_test.go index ed5a206762d7..b9b89e369ec3 100644 --- a/x/upgrade/plan/info_test.go +++ b/x/upgrade/plan/info_test.go @@ -191,9 +191,10 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapValidateBasic() { return url + "?checksum=sha256:b5a2c96250612366ea272ffac6d9744aaf4b45aacd96aa7cfcb931ee3b558259" } tests := []struct { - name string - urlMap BinaryDownloadURLMap - errs []string + name string + urlMap BinaryDownloadURLMap + parseConfig ParseConfig + errs []string }{ { name: "empty map", @@ -241,7 +242,8 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapValidateBasic() { urlMap: BinaryDownloadURLMap{ "darwin/amd64": "https://v1.cosmos.network/sdk", }, - errs: []string{"invalid url", "darwin/amd64", "missing checksum query parameter"}, + parseConfig: ParseConfig{EnforceChecksum: false}, + errs: nil, }, { name: "multiple valid entries but one bad url", @@ -269,7 +271,7 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapValidateBasic() { for _, tc := range tests { s.T().Run(tc.name, func(t *testing.T) { - actualErr := tc.urlMap.ValidateBasic() + actualErr := tc.urlMap.ValidateBasic(tc.parseConfig.EnforceChecksum) if len(tc.errs) > 0 { require.Error(t, actualErr) for _, expectedErr := range tc.errs { @@ -291,9 +293,10 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapCheckURLs() { linux386URL := makeFileURL(s.T(), linux386Path) tests := []struct { - name string - urlMap BinaryDownloadURLMap - errs []string + name string + urlMap BinaryDownloadURLMap + parseConfig ParseConfig + errs []string }{ { name: "two good entries", @@ -321,7 +324,7 @@ func (s *InfoTestSuite) TestBinaryDownloadURLMapCheckURLs() { for _, tc := range tests { s.T().Run(tc.name, func(t *testing.T) { - actualErr := tc.urlMap.CheckURLs("daemon") + actualErr := tc.urlMap.CheckURLs("daemon", tc.parseConfig.EnforceChecksum) if len(tc.errs) > 0 { require.Error(t, actualErr) for _, expectedErr := range tc.errs { From ea7d5cfbac04626ff7c41ef1120697412b7df7b6 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 13 Jun 2023 00:08:09 +0200 Subject: [PATCH 2/5] update cosmovisor --- tools/cosmovisor/README.md | 19 +-- tools/cosmovisor/args.go | 46 +++---- tools/cosmovisor/args_test.go | 219 +++++++++++++++++++--------------- tools/cosmovisor/go.mod | 2 +- tools/cosmovisor/go.sum | 4 +- tools/cosmovisor/upgrade.go | 2 +- x/upgrade/CHANGELOG.md | 6 +- 7 files changed, 163 insertions(+), 135 deletions(-) diff --git a/tools/cosmovisor/README.md b/tools/cosmovisor/README.md index be2aded5dbaf..0287d8c0bbd3 100644 --- a/tools/cosmovisor/README.md +++ b/tools/cosmovisor/README.md @@ -9,17 +9,17 @@ sidebar_position: 1 * [Design](#design) * [Contributing](#contributing) * [Setup](#setup) - * [Installation](#installation) - * [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables) - * [Folder Layout](#folder-layout) + * [Installation](#installation) + * [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables) + * [Folder Layout](#folder-layout) * [Usage](#usage) - * [Initialization](#initialization) - * [Detecting Upgrades](#detecting-upgrades) - * [Auto-Download](#auto-download) + * [Initialization](#initialization) + * [Detecting Upgrades](#detecting-upgrades) + * [Auto-Download](#auto-download) * [Example: SimApp Upgrade](#example-simapp-upgrade) - * [Chain Setup](#chain-setup) - * [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain) - * [Update App](#update-app) + * [Chain Setup](#chain-setup) + * [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain) + * [Update App](#update-app) ## Design @@ -87,6 +87,7 @@ Use of `cosmovisor` without one of the action arguments is deprecated. For backw * `DAEMON_HOME` is the location where the `cosmovisor/` directory is kept that contains the genesis binary, the upgrade binaries, and any additional auxiliary files associated with each binary (e.g. `$HOME/.gaiad`, `$HOME/.regend`, `$HOME/.simd`, etc.). * `DAEMON_NAME` is the name of the binary itself (e.g. `gaiad`, `regend`, `simd`, etc.). * `DAEMON_ALLOW_DOWNLOAD_BINARIES` (*optional*), if set to `true`, will enable auto-downloading of new binaries (for security reasons, this is intended for full nodes rather than validators). By default, `cosmovisor` will not auto-download new binaries. +* `DAEMON_DOWNLOAD_MUST_HAVE_CHECKSUM` (*optional*, default = `false`), if `true` cosmovisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. If `false`, cosmovisor will not require a checksum to be provided, but still check the checksum if one is provided. * `DAEMON_RESTART_AFTER_UPGRADE` (*optional*, default = `true`), if `true`, restarts the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (`false`), `cosmovisor` stops running after an upgrade and requires the system administrator to manually restart it. Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs. * `DAEMON_RESTART_DELAY` (*optional*, default none), allow a node operator to define a delay between the node halt (for upgrade) and backup by the specified time. The value must be a duration (e.g. `1s`). * `DAEMON_POLL_INTERVAL` (*optional*, default 300 milliseconds), is the interval length for polling the upgrade plan file. The value must be a duration (e.g. `1s`). diff --git a/tools/cosmovisor/args.go b/tools/cosmovisor/args.go index 3d8c33a1e161..6f380b42d95e 100644 --- a/tools/cosmovisor/args.go +++ b/tools/cosmovisor/args.go @@ -18,16 +18,17 @@ import ( // environment variable names const ( - EnvHome = "DAEMON_HOME" - EnvName = "DAEMON_NAME" - EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES" - EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE" - EnvRestartDelay = "DAEMON_RESTART_DELAY" - EnvSkipBackup = "UNSAFE_SKIP_BACKUP" - EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR" - EnvInterval = "DAEMON_POLL_INTERVAL" - EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES" - EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS" + EnvHome = "DAEMON_HOME" + EnvName = "DAEMON_NAME" + EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES" + EnvDownloadMustHaveChecksum = "DAEMON_DOWNLOAD_MUST_HAVE_CHECKSUM" + EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE" + EnvRestartDelay = "DAEMON_RESTART_DELAY" + EnvSkipBackup = "UNSAFE_SKIP_BACKUP" + EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR" + EnvInterval = "DAEMON_POLL_INTERVAL" + EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES" + EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS" ) const ( @@ -42,16 +43,17 @@ const defaultFilename = "upgrade-info.json" // Config is the information passed in to control the daemon type Config struct { - Home string - Name string - AllowDownloadBinaries bool - RestartAfterUpgrade bool - RestartDelay time.Duration - PollInterval time.Duration - UnsafeSkipBackup bool - DataBackupPath string - PreupgradeMaxRetries int - DisableLogs bool + Home string + Name string + AllowDownloadBinaries bool + DownloadMustHaveChecksum bool + RestartAfterUpgrade bool + RestartDelay time.Duration + PollInterval time.Duration + UnsafeSkipBackup bool + DataBackupPath string + PreupgradeMaxRetries int + DisableLogs bool // currently running upgrade currentUpgrade upgradetypes.Plan @@ -153,6 +155,9 @@ func GetConfigFromEnv() (*Config, error) { if cfg.AllowDownloadBinaries, err = booleanOption(EnvDownloadBin, false); err != nil { errs = append(errs, err) } + if cfg.DownloadMustHaveChecksum, err = booleanOption(EnvDownloadMustHaveChecksum, false); err != nil { + errs = append(errs, err) + } if cfg.RestartAfterUpgrade, err = booleanOption(EnvRestartUpgrade, true); err != nil { errs = append(errs, err) } @@ -367,6 +372,7 @@ func (cfg Config) DetailString() string { {EnvHome, cfg.Home}, {EnvName, cfg.Name}, {EnvDownloadBin, fmt.Sprintf("%t", cfg.AllowDownloadBinaries)}, + {EnvDownloadMustHaveChecksum, fmt.Sprintf("%t", cfg.DownloadMustHaveChecksum)}, {EnvRestartUpgrade, fmt.Sprintf("%t", cfg.RestartAfterUpgrade)}, {EnvRestartDelay, cfg.RestartDelay.String()}, {EnvInterval, cfg.PollInterval.String()}, diff --git a/tools/cosmovisor/args_test.go b/tools/cosmovisor/args_test.go index 4651c387af34..f1b9e967fd84 100644 --- a/tools/cosmovisor/args_test.go +++ b/tools/cosmovisor/args_test.go @@ -29,31 +29,33 @@ func TestArgsTestSuite(t *testing.T) { // cosmovisorEnv are the string values of environment variables used to configure Cosmovisor. type cosmovisorEnv struct { - Home string - Name string - DownloadBin string - RestartUpgrade string - RestartDelay string - SkipBackup string - DataBackupPath string - Interval string - PreupgradeMaxRetries string - DisableLogs string + Home string + Name string + DownloadBin string + DownloadMustHaveChecksum string + RestartUpgrade string + RestartDelay string + SkipBackup string + DataBackupPath string + Interval string + PreupgradeMaxRetries string + DisableLogs string } // ToMap creates a map of the cosmovisorEnv where the keys are the env var names. func (c cosmovisorEnv) ToMap() map[string]string { return map[string]string{ - EnvHome: c.Home, - EnvName: c.Name, - EnvDownloadBin: c.DownloadBin, - EnvRestartUpgrade: c.RestartUpgrade, - EnvRestartDelay: c.RestartDelay, - EnvSkipBackup: c.SkipBackup, - EnvDataBackupPath: c.DataBackupPath, - EnvInterval: c.Interval, - EnvPreupgradeMaxRetries: c.PreupgradeMaxRetries, - EnvDisableLogs: c.DisableLogs, + EnvHome: c.Home, + EnvName: c.Name, + EnvDownloadBin: c.DownloadBin, + EnvDownloadMustHaveChecksum: c.DownloadMustHaveChecksum, + EnvRestartUpgrade: c.RestartUpgrade, + EnvRestartDelay: c.RestartDelay, + EnvSkipBackup: c.SkipBackup, + EnvDataBackupPath: c.DataBackupPath, + EnvInterval: c.Interval, + EnvPreupgradeMaxRetries: c.PreupgradeMaxRetries, + EnvDisableLogs: c.DisableLogs, } } @@ -66,6 +68,8 @@ func (c *cosmovisorEnv) Set(envVar, envVal string) { c.Name = envVal case EnvDownloadBin: c.DownloadBin = envVal + case EnvDownloadMustHaveChecksum: + c.DownloadMustHaveChecksum = envVal case EnvRestartUpgrade: c.RestartUpgrade = envVal case EnvRestartDelay: @@ -322,20 +326,22 @@ func (s *argsTestSuite) TestDetailString() { home := "/home" name := "test-name" allowDownloadBinaries := true + downloadMustHaveChecksum := true restartAfterUpgrade := true pollInterval := 406 * time.Millisecond unsafeSkipBackup := false dataBackupPath := "/home" preupgradeMaxRetries := 8 cfg := &Config{ - Home: home, - Name: name, - AllowDownloadBinaries: allowDownloadBinaries, - RestartAfterUpgrade: restartAfterUpgrade, - PollInterval: pollInterval, - UnsafeSkipBackup: unsafeSkipBackup, - DataBackupPath: dataBackupPath, - PreupgradeMaxRetries: preupgradeMaxRetries, + Home: home, + Name: name, + AllowDownloadBinaries: allowDownloadBinaries, + DownloadMustHaveChecksum: downloadMustHaveChecksum, + RestartAfterUpgrade: restartAfterUpgrade, + PollInterval: pollInterval, + UnsafeSkipBackup: unsafeSkipBackup, + DataBackupPath: dataBackupPath, + PreupgradeMaxRetries: preupgradeMaxRetries, } expectedPieces := []string{ @@ -343,6 +349,7 @@ func (s *argsTestSuite) TestDetailString() { fmt.Sprintf("%s: %s", EnvHome, home), fmt.Sprintf("%s: %s", EnvName, name), fmt.Sprintf("%s: %t", EnvDownloadBin, allowDownloadBinaries), + fmt.Sprintf("%s: %t", EnvDownloadMustHaveChecksum, downloadMustHaveChecksum), fmt.Sprintf("%s: %t", EnvRestartUpgrade, restartAfterUpgrade), fmt.Sprintf("%s: %s", EnvInterval, pollInterval), fmt.Sprintf("%s: %t", EnvSkipBackup, unsafeSkipBackup), @@ -371,18 +378,19 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { absPath, perr := filepath.Abs(relPath) s.Require().NoError(perr) - newConfig := func(home, name string, downloadBin, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, disableLogs bool) *Config { + newConfig := func(home, name string, downloadBin, downloadMustHaveChecksum, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, disableLogs bool) *Config { return &Config{ - Home: home, - Name: name, - AllowDownloadBinaries: downloadBin, - RestartAfterUpgrade: restartUpgrade, - RestartDelay: time.Millisecond * time.Duration(restartDelay), - PollInterval: time.Millisecond * time.Duration(interval), - UnsafeSkipBackup: skipBackup, - DataBackupPath: dataBackupPath, - PreupgradeMaxRetries: preupgradeMaxRetries, - DisableLogs: disableLogs, + Home: home, + Name: name, + AllowDownloadBinaries: downloadBin, + DownloadMustHaveChecksum: downloadMustHaveChecksum, + RestartAfterUpgrade: restartUpgrade, + RestartDelay: time.Millisecond * time.Duration(restartDelay), + PollInterval: time.Millisecond * time.Duration(interval), + UnsafeSkipBackup: skipBackup, + DataBackupPath: dataBackupPath, + PreupgradeMaxRetries: preupgradeMaxRetries, + DisableLogs: disableLogs, } } @@ -395,210 +403,223 @@ func (s *argsTestSuite) TestGetConfigFromEnv() { { name: "all bad", envVals: cosmovisorEnv{ - Home: "", - Name: "", - DownloadBin: "bad", - RestartUpgrade: "bad", - RestartDelay: "bad", - SkipBackup: "bad", - DataBackupPath: "bad", - Interval: "bad", - PreupgradeMaxRetries: "bad", + Home: "", + Name: "", + DownloadBin: "bad", + DownloadMustHaveChecksum: "bad", + RestartUpgrade: "bad", + RestartDelay: "bad", + SkipBackup: "bad", + DataBackupPath: "bad", + Interval: "bad", + PreupgradeMaxRetries: "bad", }, expectedCfg: nil, - expectedErrCount: 9, + expectedErrCount: 10, }, { name: "all good", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "nothing set", - envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "false"}, + envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false"}, expectedCfg: nil, expectedErrCount: 3, }, // Note: Home and Name tests are done in TestValidate { name: "download bin bad", - envVals: cosmovisorEnv{absPath, "testname", "bad", "false", "600ms", "true", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "download bin not set", - envVals: cosmovisorEnv{absPath, "testname", "", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "download bin true", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "download bin false", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, false), + expectedErrCount: 0, + }, + { + name: "download ensure checksum true", + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), + expectedErrCount: 0, + }, + { + name: "download ensure checksum false", + envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "restart upgrade bad", - envVals: cosmovisorEnv{absPath, "testname", "true", "bad", "600ms", "true", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart upgrade not set", - envVals: cosmovisorEnv{absPath, "testname", "true", "", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "restart upgrade true", - envVals: cosmovisorEnv{absPath, "testname", "true", "true", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "restart upgrade true", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "skip unsafe backups bad", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "bad", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "skip unsafe backups not set", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "skip unsafe backups true", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "skip unsafe backups false", - envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "false", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "poll interval bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "bad", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "poll interval 0", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "0", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "poll interval not set", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 300, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, false), expectedErrCount: 0, }, { name: "poll interval 600", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "600", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "poll interval 1s", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "1s", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 1000, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, false), expectedErrCount: 0, }, { name: "poll interval -3m", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "-3m", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "bad", "false", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay 0", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "0", "false", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay not set", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "", "false", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 0, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "restart delay 600", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600", "false", "", "300ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "restart delay 1s", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "1s", "false", "", "303ms", "1", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 1000, false, absPath, 303, 1, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, false), expectedErrCount: 0, }, { name: "restart delay -3m", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "-3m", "false", "", "303ms", "1", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "prepupgrade max retries bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "bad", "false"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "prepupgrade max retries 0", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "0", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false), expectedErrCount: 0, }, { name: "prepupgrade max retries not set", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, false), expectedErrCount: 0, }, { name: "prepupgrade max retries 5", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "5", "false"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 5, false), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, false), expectedErrCount: 0, }, { name: "disable logs bad", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "5", "bad"}, + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad"}, expectedCfg: nil, expectedErrCount: 1, }, { name: "disable logs good", - envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "", "true"}, - expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, true), + envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true"}, + expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, true), expectedErrCount: 0, }, } diff --git a/tools/cosmovisor/go.mod b/tools/cosmovisor/go.mod index 3decf8853b88..b8a22cec16ad 100644 --- a/tools/cosmovisor/go.mod +++ b/tools/cosmovisor/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( cosmossdk.io/log v1.1.0 - cosmossdk.io/x/upgrade v0.0.0-20230607190716-2877190997a2 + cosmossdk.io/x/upgrade v0.0.0-20230612214834-215158d52dc2 github.com/otiai10/copy v1.11.0 github.com/rs/zerolog v1.29.1 github.com/spf13/cobra v1.7.0 diff --git a/tools/cosmovisor/go.sum b/tools/cosmovisor/go.sum index 8501adc4b5a9..0f63e0fb9da0 100644 --- a/tools/cosmovisor/go.sum +++ b/tools/cosmovisor/go.sum @@ -206,8 +206,8 @@ cosmossdk.io/store v0.1.0-alpha.1.0.20230606190835-3e18f4088b2c h1:A+FMPW9GtfcPB cosmossdk.io/store v0.1.0-alpha.1.0.20230606190835-3e18f4088b2c/go.mod h1:RbYGvXCbz8uNBCXrwS9Z8SyydeWi+W5x5MZ33muyzMw= cosmossdk.io/x/tx v0.8.0 h1:gLiGRL/Fy7fs6dd0IX8jOf0PrVr56/SG6XVMGQjyvJU= cosmossdk.io/x/tx v0.8.0/go.mod h1:T9uEumGNgKU61gJYRv1t3uzQwLnASpJGmSE229HM3xA= -cosmossdk.io/x/upgrade v0.0.0-20230607190716-2877190997a2 h1:84kBxTNfeRkBUhzhiuZdo5uym9B+wffX8ehRpOREucA= -cosmossdk.io/x/upgrade v0.0.0-20230607190716-2877190997a2/go.mod h1:CTz9FMom9siXKVeb9PKhexmqlixRH2xtXfxIuCWqMIM= +cosmossdk.io/x/upgrade v0.0.0-20230612214834-215158d52dc2 h1:bipLi8KgB0bFor804hAGTISZQNW8UogoYPXaxAYZU00= +cosmossdk.io/x/upgrade v0.0.0-20230612214834-215158d52dc2/go.mod h1:CTz9FMom9siXKVeb9PKhexmqlixRH2xtXfxIuCWqMIM= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= diff --git a/tools/cosmovisor/upgrade.go b/tools/cosmovisor/upgrade.go index 65d49bf82126..e4886dd334e6 100644 --- a/tools/cosmovisor/upgrade.go +++ b/tools/cosmovisor/upgrade.go @@ -39,7 +39,7 @@ func UpgradeBinary(logger log.Logger, cfg *Config, p upgradetypes.Plan) error { return fmt.Errorf("unhandled error: %w", err) } - upgradeInfo, err := plan.ParseInfo(p.Info) + upgradeInfo, err := plan.ParseInfo(p.Info, plan.ParseOptionEnforceChecksum(cfg.DownloadMustHaveChecksum)) if err != nil { return fmt.Errorf("cannot parse upgrade info: %w", err) } diff --git a/x/upgrade/CHANGELOG.md b/x/upgrade/CHANGELOG.md index 1358ed44bb0d..b265582235a3 100644 --- a/x/upgrade/CHANGELOG.md +++ b/x/upgrade/CHANGELOG.md @@ -32,7 +32,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes -* []() `BinaryDownloadURLMap.ValidateBasic()` and `BinaryDownloadURLMap.CheckURLs` now both take a checksum parameter when willing to ensure a checksum is provided for each URL. -* []() `plan.DownloadURLWithChecksum` has been renamed to `plan.DownloadURL` and does not validate the URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadURL` to validate the URL. -* []() `plan.DownloadUpgrade` does not validate URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadUpgrade` to validate the URL. +* [#16511](https://github.com/cosmos/cosmos-sdk/pull/16511) `BinaryDownloadURLMap.ValidateBasic()` and `BinaryDownloadURLMap.CheckURLs` now both take a checksum parameter when willing to ensure a checksum is provided for each URL. +* [#16511](https://github.com/cosmos/cosmos-sdk/pull/16511) `plan.DownloadURLWithChecksum` has been renamed to `plan.DownloadURL` and does not validate the URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadURL` to validate the URL. +* [#16511](https://github.com/cosmos/cosmos-sdk/pull/16511) `plan.DownloadUpgrade` does not validate URL anymore. Call `plan.ValidateURL` before calling `plan.DownloadUpgrade` to validate the URL. * [#16227](https://github.com/cosmos/cosmos-sdk/issues/16227) `NewKeeper` now takes a `KVStoreService` instead of a `StoreKey`, methods in the `Keeper` now take a `context.Context` instead of a `sdk.Context` and return an `error`. `UpgradeHandler` now receives a `context.Context`. `GetUpgradedClient`, `GetUpgradedConsensusState`, `GetUpgradePlan` now return a specific error for "not found". From 7b9f89a6a1419ec8078c87905b5a5c0f4feb333d Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 13 Jun 2023 00:10:04 +0200 Subject: [PATCH 3/5] updates --- x/upgrade/client/cli/tx.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/upgrade/client/cli/tx.go b/x/upgrade/client/cli/tx.go index 4702adce3e2c..a627aa679630 100644 --- a/x/upgrade/client/cli/tx.go +++ b/x/upgrade/client/cli/tx.go @@ -119,7 +119,7 @@ func NewCmdSubmitUpgradeProposal(ac addresscodec.Codec) *cobra.Command { cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen") 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 (dangerous!)") - cmd.Flags().Bool(FlagNoChecksumRequired, false, "Skip validation of checksums in the upgrade info") + cmd.Flags().Bool(FlagNoChecksumRequired, false, "Skip requirement of checksums for binaries in 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") cmd.Flags().String(FlagAuthority, "", "The address of the upgrade module authority (defaults to gov)") From 0fc2d5e58da74e5342e64005759a123e6080c5a7 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 13 Jun 2023 17:25:59 +0200 Subject: [PATCH 4/5] updates --- tools/cosmovisor/README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/cosmovisor/README.md b/tools/cosmovisor/README.md index 0287d8c0bbd3..5070ba139be5 100644 --- a/tools/cosmovisor/README.md +++ b/tools/cosmovisor/README.md @@ -9,17 +9,17 @@ sidebar_position: 1 * [Design](#design) * [Contributing](#contributing) * [Setup](#setup) - * [Installation](#installation) - * [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables) - * [Folder Layout](#folder-layout) + * [Installation](#installation) + * [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables) + * [Folder Layout](#folder-layout) * [Usage](#usage) - * [Initialization](#initialization) - * [Detecting Upgrades](#detecting-upgrades) - * [Auto-Download](#auto-download) + * [Initialization](#initialization) + * [Detecting Upgrades](#detecting-upgrades) + * [Auto-Download](#auto-download) * [Example: SimApp Upgrade](#example-simapp-upgrade) - * [Chain Setup](#chain-setup) - * [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain) - * [Update App](#update-app) + * [Chain Setup](#chain-setup) + * [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain) + * [Update App](#update-app) ## Design From 80bc9494c7d3a8b2c400baba0e1739b3836d9377 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Tue, 13 Jun 2023 17:28:55 +0200 Subject: [PATCH 5/5] updates --- tools/cosmovisor/go.mod | 2 +- tools/cosmovisor/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/cosmovisor/go.mod b/tools/cosmovisor/go.mod index f2a7d85a0c0f..f844a7c55813 100644 --- a/tools/cosmovisor/go.mod +++ b/tools/cosmovisor/go.mod @@ -4,7 +4,7 @@ go 1.20 require ( cosmossdk.io/log v1.1.0 - cosmossdk.io/x/upgrade v0.0.0-20230612214834-215158d52dc2 + cosmossdk.io/x/upgrade v0.0.0-20230613152654-3f214535967c github.com/otiai10/copy v1.11.0 github.com/rs/zerolog v1.29.1 github.com/spf13/cobra v1.7.0 diff --git a/tools/cosmovisor/go.sum b/tools/cosmovisor/go.sum index f9ca62fad3b5..c2292777df00 100644 --- a/tools/cosmovisor/go.sum +++ b/tools/cosmovisor/go.sum @@ -206,8 +206,8 @@ cosmossdk.io/store v0.1.0-alpha.1.0.20230606190835-3e18f4088b2c h1:A+FMPW9GtfcPB cosmossdk.io/store v0.1.0-alpha.1.0.20230606190835-3e18f4088b2c/go.mod h1:RbYGvXCbz8uNBCXrwS9Z8SyydeWi+W5x5MZ33muyzMw= cosmossdk.io/x/tx v0.8.0 h1:gLiGRL/Fy7fs6dd0IX8jOf0PrVr56/SG6XVMGQjyvJU= cosmossdk.io/x/tx v0.8.0/go.mod h1:T9uEumGNgKU61gJYRv1t3uzQwLnASpJGmSE229HM3xA= -cosmossdk.io/x/upgrade v0.0.0-20230612214834-215158d52dc2 h1:bipLi8KgB0bFor804hAGTISZQNW8UogoYPXaxAYZU00= -cosmossdk.io/x/upgrade v0.0.0-20230612214834-215158d52dc2/go.mod h1:CTz9FMom9siXKVeb9PKhexmqlixRH2xtXfxIuCWqMIM= +cosmossdk.io/x/upgrade v0.0.0-20230613152654-3f214535967c h1:kmCU/rMaXMmZ9GPUNNLen/m4PpS6IV3hyBp7X0xju9M= +cosmossdk.io/x/upgrade v0.0.0-20230613152654-3f214535967c/go.mod h1:Nqm1dOl9yTTtG+uibprZTQp50rW+pd+XjAYGVQ5+Ojc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek= filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=