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(cosmovisor): Add prepare-upgrade cmd #21972

Merged
merged 10 commits into from
Oct 1, 2024
Merged
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
lucaslopezf marked this conversation as resolved.
Show resolved Hide resolved

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 @@ -38,6 +38,7 @@ const (
EnvTimeFormatLogs = "COSMOVISOR_TIMEFORMAT_LOGS"
EnvCustomPreupgrade = "COSMOVISOR_CUSTOM_PREUPGRADE"
EnvDisableRecase = "COSMOVISOR_DISABLE_RECASE"
EnvGRPCAddress = "COSMOVISOR_GRPC_ADDRESS"
lucaslopezf marked this conversation as resolved.
Show resolved Hide resolved
)

const (
Expand Down Expand Up @@ -68,6 +69,7 @@ type Config struct {
TimeFormatLogs string `toml:"cosmovisor_timeformat_logs" mapstructure:"cosmovisor_timeformat_logs" default:"kitchen"`
CustomPreUpgrade string `toml:"cosmovisor_custom_preupgrade" mapstructure:"cosmovisor_custom_preupgrade" default:""`
DisableRecase bool `toml:"cosmovisor_disable_recase" mapstructure:"cosmovisor_disable_recase" default:"false"`
GRPCAddress string `toml:"cosmovisor_grpc_address" mapstructure:"cosmovisor_grpc_address"`
lucaslopezf marked this conversation as resolved.
Show resolved Hide resolved

// currently running upgrade
currentUpgrade upgradetypes.Plan
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"
}
lucaslopezf marked this conversation as resolved.
Show resolved Hide resolved

if !skipValidate {
errs = append(errs, cfg.validate()...)
if len(errs) > 0 {
Expand Down
124 changes: 124 additions & 0 deletions tools/cosmovisor/cmd/cosmovisor/prepare_upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
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.
lucaslopezf marked this conversation as resolved.
Show resolved Hide resolved
This command will query the chain for the current upgrade plan and download the specified binary.`,
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 {
lucaslopezf marked this conversation as resolved.
Show resolved Hide resolved
return fmt.Errorf("no active upgrade plan found")
}

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
Loading