Skip to content

Commit

Permalink
feat(cosmovisor): Add prepare-upgrade cmd (cosmos#21972)
Browse files Browse the repository at this point in the history
  • Loading branch information
lucaslopezf authored Oct 1, 2024
1 parent 52d8b2e commit 3f9c9a0
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 5 deletions.
12 changes: 8 additions & 4 deletions tools/cosmovisor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

### Features

* [#21972](https://github.com/cosmos/cosmos-sdk/pull/21972) Add `prepare-upgrade` command

### Improvements

* [#21462](https://github.com/cosmos/cosmos-sdk/pull/21462) Pass `stdin` to binary.
Expand All @@ -50,10 +54,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

* Bump `cosmossdk.io/x/upgrade` to v0.1.4 (including go-getter vulnerability fix)
* [#19995](https://github.com/cosmos/cosmos-sdk/pull/19995):
* `init command` writes the configuration to the config file only at the default path `DAEMON_HOME/cosmovisor/config.toml`.
* Provide `--cosmovisor-config` flag with value as args to provide the path to the configuration file in the `run` command. `run --cosmovisor-config <path> (other cmds with flags) ...`.
* Add `--cosmovisor-config` flag to provide `config.toml` path to the configuration file in root command used by `add-upgrade` and `config` subcommands.
* `config command` now displays the configuration from the config file if it is provided. If the config file is not provided, it will display the configuration from the environment variables.
* `init command` writes the configuration to the config file only at the default path `DAEMON_HOME/cosmovisor/config.toml`.
* Provide `--cosmovisor-config` flag with value as args to provide the path to the configuration file in the `run` command. `run --cosmovisor-config <path> (other cmds with flags) ...`.
* Add `--cosmovisor-config` flag to provide `config.toml` path to the configuration file in root command used by `add-upgrade` and `config` subcommands.
* `config command` now displays the configuration from the config file if it is provided. If the config file is not provided, it will display the configuration from the environment variables.

## Bug Fixes

Expand Down
33 changes: 33 additions & 0 deletions tools/cosmovisor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ It polls the `upgrade-info.json` file that is created by the x/upgrade module at
* [Initialization](#initialization)
* [Detecting Upgrades](#detecting-upgrades)
* [Adding Upgrade Binary](#adding-upgrade-binary)
* [Preparing for an Upgrade](#preparing-for-an-upgrade)
* [Auto-Download](#auto-download)
* [Example: SimApp Upgrade](#example-simapp-upgrade)
* [Chain Setup](#chain-setup)
Expand Down Expand Up @@ -263,6 +264,38 @@ The result will look something like the following: `29139e1381b8177aec909fab9a75

You can also use `sha512sum` if you would prefer to use longer hashes, or `md5sum` if you would prefer to use broken hashes. Whichever you choose, make sure to set the hash algorithm properly in the checksum argument to the URL.

### Preparing for an Upgrade

To prepare for an upgrade, use the `prepare-upgrade` command:

```shell
cosmovisor prepare-upgrade
```

This command performs the following actions:

1. Retrieves upgrade information directly from the blockchain about the next scheduled upgrade.
2. Downloads the new binary specified in the upgrade plan.
3. Verifies the binary's checksum (if required by configuration).
4. Places the new binary in the appropriate directory for Cosmovisor to use during the upgrade.

The `prepare-upgrade` command provides detailed logging throughout the process, including:

* The name and height of the upcoming upgrade
* The URL from which the new binary is being downloaded
* Confirmation of successful download and verification
* The path where the new binary has been placed

Example output:

```bash
INFO Preparing for upgrade name=v1.0.0 height=1000000
INFO Downloading upgrade binary url=https://example.com/binary/v1.0.0?checksum=sha256:339911508de5e20b573ce902c500ee670589073485216bee8b045e853f24bce8
INFO Upgrade preparation complete name=v1.0.0 height=1000000
```

*Note: The current way of downloading manually and placing the binary at the right place would still work.*

## Example: SimApp Upgrade

The following instructions provide a demonstration of `cosmovisor` using the simulation application (`simapp`) shipped with the Cosmos SDK's source code. The following commands are to be run from within the `cosmos-sdk` repository.
Expand Down
7 changes: 7 additions & 0 deletions tools/cosmovisor/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const (
EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR"
EnvInterval = "DAEMON_POLL_INTERVAL"
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
EnvGRPCAddress = "DAEMON_GRPC_ADDRESS"
EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS"
EnvColorLogs = "COSMOVISOR_COLOR_LOGS"
EnvTimeFormatLogs = "COSMOVISOR_TIMEFORMAT_LOGS"
Expand Down Expand Up @@ -63,6 +64,7 @@ type Config struct {
UnsafeSkipBackup bool `toml:"unsafe_skip_backup" mapstructure:"unsafe_skip_backup" default:"false"`
DataBackupPath string `toml:"daemon_data_backup_dir" mapstructure:"daemon_data_backup_dir"`
PreUpgradeMaxRetries int `toml:"daemon_preupgrade_max_retries" mapstructure:"daemon_preupgrade_max_retries" default:"0"`
GRPCAddress string `toml:"daemon_grpc_address" mapstructure:"daemon_grpc_address"`
DisableLogs bool `toml:"cosmovisor_disable_logs" mapstructure:"cosmovisor_disable_logs" default:"false"`
ColorLogs bool `toml:"cosmovisor_color_logs" mapstructure:"cosmovisor_color_logs" default:"true"`
TimeFormatLogs string `toml:"cosmovisor_timeformat_logs" mapstructure:"cosmovisor_timeformat_logs" default:"kitchen"`
Expand Down Expand Up @@ -282,6 +284,11 @@ func GetConfigFromEnv(skipValidate bool) (*Config, error) {
errs = append(errs, fmt.Errorf("%s could not be parsed to int: %w", EnvPreupgradeMaxRetries, err))
}

cfg.GRPCAddress = os.Getenv(EnvGRPCAddress)
if cfg.GRPCAddress == "" {
cfg.GRPCAddress = "localhost:9090"
}

if !skipValidate {
errs = append(errs, cfg.validate()...)
if len(errs) > 0 {
Expand Down
126 changes: 126 additions & 0 deletions tools/cosmovisor/cmd/cosmovisor/prepare_upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package main

import (
"context"
"crypto/tls"
"fmt"
"path/filepath"
"strings"
"time"

"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"

"cosmossdk.io/tools/cosmovisor"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
)

func NewPrepareUpgradeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "prepare-upgrade",
Short: "Prepare for the next upgrade",
Long: `Prepare for the next upgrade by downloading and verifying the upgrade binary.
This command will query the chain for the current upgrade plan and download the specified binary.
gRPC must be enabled on the node for this command to work.`,
RunE: prepareUpgradeHandler,
SilenceUsage: false,
Args: cobra.NoArgs,
}

return cmd
}

func prepareUpgradeHandler(cmd *cobra.Command, _ []string) error {
configPath, err := cmd.Flags().GetString(cosmovisor.FlagCosmovisorConfig)
if err != nil {
return fmt.Errorf("failed to get config flag: %w", err)
}

cfg, err := cosmovisor.GetConfigFromFile(configPath)
if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}

logger := cfg.Logger(cmd.OutOrStdout())

grpcAddress := cfg.GRPCAddress
logger.Info("Using gRPC address", "address", grpcAddress)

upgradeInfo, err := queryUpgradeInfoFromChain(grpcAddress)
if err != nil {
return fmt.Errorf("failed to query upgrade info: %w", err)
}

if upgradeInfo == nil {
logger.Info("No active upgrade plan found")
return nil
}

logger.Info("Preparing for upgrade", "name", upgradeInfo.Name, "height", upgradeInfo.Height)

upgradeInfoParsed, err := plan.ParseInfo(upgradeInfo.Info, plan.ParseOptionEnforceChecksum(cfg.DownloadMustHaveChecksum))
if err != nil {
return fmt.Errorf("failed to parse upgrade info: %w", err)
}

binaryURL, err := cosmovisor.GetBinaryURL(upgradeInfoParsed.Binaries)
if err != nil {
return fmt.Errorf("binary URL not found in upgrade plan. Cannot prepare for upgrade: %w", err)
}

logger.Info("Downloading upgrade binary", "url", binaryURL)

upgradeBin := filepath.Join(cfg.UpgradeBin(upgradeInfo.Name), cfg.Name)
if err := plan.DownloadUpgrade(filepath.Dir(upgradeBin), binaryURL, cfg.Name); err != nil {
return fmt.Errorf("failed to download and verify binary: %w", err)
}

logger.Info("Upgrade preparation complete", "name", upgradeInfo.Name, "height", upgradeInfo.Height)

return nil
}

func queryUpgradeInfoFromChain(grpcAddress string) (*upgradetypes.Plan, error) {
if grpcAddress == "" {
return nil, fmt.Errorf("gRPC address is empty")
}

grpcConn, err := getClient(grpcAddress)
if err != nil {
return nil, fmt.Errorf("failed to open gRPC client: %w", err)
}
defer grpcConn.Close()

queryClient := upgradetypes.NewQueryClient(grpcConn)

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

res, err := queryClient.CurrentPlan(ctx, &upgradetypes.QueryCurrentPlanRequest{})
if err != nil {
return nil, fmt.Errorf("failed to query current upgrade plan: %w", err)
}

return res.Plan, nil
}

func getClient(endpoint string) (*grpc.ClientConn, error) {
var creds credentials.TransportCredentials
if strings.HasPrefix(endpoint, "https://") {
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
}
creds = credentials.NewTLS(tlsConfig)
} else {
creds = insecure.NewCredentials()
}

opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
}

return grpc.NewClient(endpoint, opts...)
}
1 change: 1 addition & 0 deletions tools/cosmovisor/cmd/cosmovisor/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ func NewRootCmd() *cobra.Command {
NewVersionCmd(),
NewAddUpgradeCmd(),
NewShowUpgradeInfoCmd(),
NewPrepareUpgradeCmd(),
)

rootCmd.PersistentFlags().StringP(cosmovisor.FlagCosmovisorConfig, "c", "", "path to cosmovisor config file")
Expand Down
2 changes: 1 addition & 1 deletion tools/cosmovisor/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
google.golang.org/grpc v1.67.0
)

require (
Expand Down Expand Up @@ -175,7 +176,6 @@ require (
google.golang.org/genproto v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240814211410-ddb44dafa142 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect
google.golang.org/grpc v1.67.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
Expand Down

0 comments on commit 3f9c9a0

Please sign in to comment.