feat(cosmovisor): Add prepare-upgrade cmd (#21972)
This commit is contained in:
parent
52d8b2eb43
commit
3f9c9a0877
@ -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
|
||||
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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 {
|
||||
|
||||
126
tools/cosmovisor/cmd/cosmovisor/prepare_upgrade.go
Normal file
126
tools/cosmovisor/cmd/cosmovisor/prepare_upgrade.go
Normal 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...)
|
||||
}
|
||||
@ -20,6 +20,7 @@ func NewRootCmd() *cobra.Command {
|
||||
NewVersionCmd(),
|
||||
NewAddUpgradeCmd(),
|
||||
NewShowUpgradeInfoCmd(),
|
||||
NewPrepareUpgradeCmd(),
|
||||
)
|
||||
|
||||
rootCmd.PersistentFlags().StringP(cosmovisor.FlagCosmovisorConfig, "c", "", "path to cosmovisor config 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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user