feat(cosmovisor): Add prepare-upgrade cmd (#21972)

This commit is contained in:
Lucas Francisco López 2024-10-01 20:28:21 +02:00 committed by GitHub
parent 52d8b2eb43
commit 3f9c9a0877
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 176 additions and 5 deletions

View File

@ -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.
@ -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

View File

@ -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)
@ -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.

View File

@ -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"
@ -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"`
@ -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 {

View File

@ -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...)
}

View File

@ -20,6 +20,7 @@ func NewRootCmd() *cobra.Command {
NewVersionCmd(),
NewAddUpgradeCmd(),
NewShowUpgradeInfoCmd(),
NewPrepareUpgradeCmd(),
)
rootCmd.PersistentFlags().StringP(cosmovisor.FlagCosmovisorConfig, "c", "", "path to cosmovisor config file")

View File

@ -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 (
@ -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