feat(cosmovisor): extend add upgrade to support non governance upgrade (#16549)

This commit is contained in:
Julien Robert 2023-07-03 10:30:08 +02:00 committed by GitHub
parent 9b237c7189
commit c6d5b7c050
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 170 additions and 104 deletions

View File

@ -38,10 +38,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
## Features
* [#16413](https://github.com/cosmos/cosmos-sdk/issues/16413) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
* [#16573](https://github.com/cosmos/cosmos-sdk/pull/16573) Extend `cosmovisor` configuration with new log format options
* [#16550](https://github.com/cosmos/cosmos-sdk/pull/16550) Add COSMOVISOR_CUSTOM_PREUPGRADE to cosmovisor to execute custom pre-upgrade scripts (separate from daemon pre-upgrade).
* [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor.
* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
## Improvements

View File

@ -52,10 +52,10 @@ To install the latest version of `cosmovisor`, run the following command:
go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
```
To install a previous version, you can specify the version. IMPORTANT: Chains that use Cosmos SDK v0.44.3 or earlier (eg v0.44.2) and want to use auto-download feature MUST use `cosmovisor v0.1.0`
To install a previous version, you can specify the version:
```shell
go install github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor@v0.1.0
go install github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor@v1.5.0
```
Run `cosmovisor version` to check the cosmovisor version.
@ -367,7 +367,7 @@ Open a new terminal window and submit an upgrade proposal along with a deposit a
**>= v0.50+**:
```shell
./build/simd tx upgrade software-upgrade test1 --title upgrade --summary upgrade --upgrade-height 200 --from validator --yes
./build/simd tx upgrade software-upgrade test1 --title upgrade --summary upgrade --upgrade-height 200 --upgrade-info "{}" --no-validate --from validator --yes
./build/simd tx gov deposit 1 10000000stake --from validator --yes
./build/simd tx gov vote 1 yes --from validator --yes
```

View File

@ -12,8 +12,6 @@ import (
"strings"
"time"
"github.com/rs/zerolog"
"cosmossdk.io/log"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
@ -44,9 +42,6 @@ const (
currentLink = "current"
)
// must be the same as x/upgrade/types.UpgradeInfoFilename
const defaultFilename = "upgrade-info.json"
// Config is the information passed in to control the daemon
type Config struct {
Home string
@ -96,7 +91,7 @@ func (cfg *Config) BaseUpgradeDir() string {
// UpgradeInfoFilePath is the expected upgrade-info filename created by `x/upgrade/keeper`.
func (cfg *Config) UpgradeInfoFilePath() string {
return filepath.Join(cfg.Home, "data", defaultFilename)
return filepath.Join(cfg.Home, "data", upgradetypes.UpgradeInfoFilename)
}
// SymLinkToGenesis creates a symbolic link from "./current" to the genesis directory.
@ -224,7 +219,7 @@ func (cfg *Config) Logger(dst io.Writer) log.Logger {
var logger log.Logger
if cfg.DisableLogs {
logger = log.NewCustomLogger(zerolog.Nop())
logger = log.NewNopLogger()
} else {
logger = log.NewLogger(dst,
log.ColorOption(cfg.ColorLogs),

View File

@ -1,13 +1,16 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"github.com/spf13/cobra"
"cosmossdk.io/tools/cosmovisor"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
func NewAddUpgradeCmd() *cobra.Command {
@ -19,7 +22,8 @@ func NewAddUpgradeCmd() *cobra.Command {
RunE: AddUpgrade,
}
addUpgrade.Flags().Bool(cosmovisor.FlagForce, false, "overwrite existing upgrade binary")
addUpgrade.Flags().Bool(cosmovisor.FlagForce, false, "overwrite existing upgrade binary / upgrade-info.json file")
addUpgrade.Flags().Int64(cosmovisor.FlagUpgradeHeight, 0, "define a height at which to upgrade the binary automatically (without governance proposal)")
return addUpgrade
}
@ -33,7 +37,7 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
logger := cfg.Logger(os.Stdout)
upgradeName := args[0]
upgradeName := strings.ToLower(args[0])
if len(upgradeName) == 0 {
return fmt.Errorf("upgrade name cannot be empty")
}
@ -59,22 +63,55 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to read binary: %w", err)
}
if _, err := os.Stat(cfg.UpgradeBin(upgradeName)); err == nil {
if force, _ := cmd.Flags().GetBool(cosmovisor.FlagForce); !force {
return fmt.Errorf("upgrade binary already exists at %s", cfg.UpgradeBin(upgradeName))
}
logger.Info(fmt.Sprintf("Overwriting %s for %s upgrade", executablePath, upgradeName))
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to check if upgrade binary exists: %w", err)
force, err := cmd.Flags().GetBool(cosmovisor.FlagForce)
if err != nil {
return fmt.Errorf("failed to get force flag: %w", err)
}
if err := os.WriteFile(cfg.UpgradeBin(upgradeName), executableData, 0o600); err != nil {
return fmt.Errorf("failed to write binary to location: %w", err)
if err := saveOrAbort(cfg.UpgradeBin(upgradeName), executableData, force); err != nil {
return err
}
logger.Info(fmt.Sprintf("Using %s for %s upgrade", executablePath, upgradeName))
logger.Info(fmt.Sprintf("Upgrade binary located at %s", cfg.UpgradeBin(upgradeName)))
if upgradeHeight, err := cmd.Flags().GetInt64(cosmovisor.FlagUpgradeHeight); err != nil {
return fmt.Errorf("failed to get upgrade-height flag: %w", err)
} else if upgradeHeight > 0 {
plan := upgradetypes.Plan{Name: upgradeName, Height: upgradeHeight}
if err := plan.ValidateBasic(); err != nil {
panic(fmt.Errorf("something is wrong with cosmovisor: %w", err))
}
// create upgrade-info.json file
planData, err := json.Marshal(plan)
if err != nil {
return fmt.Errorf("failed to marshal upgrade plan: %w", err)
}
if err := saveOrAbort(cfg.UpgradeInfoFilePath(), planData, force); err != nil {
return err
}
logger.Info(fmt.Sprintf("%s created, %s upgrade binary will switch at height %d", upgradetypes.UpgradeInfoFilename, upgradeName, upgradeHeight))
}
return nil
}
// saveOrAbort saves data to path or aborts if file exists and force is false
func saveOrAbort(path string, data []byte, force bool) error {
if _, err := os.Stat(path); err == nil {
if !force {
return fmt.Errorf("file already exists at %s", path)
}
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to check if file exists: %w", err)
}
if err := os.WriteFile(path, data, 0o600); err != nil {
return fmt.Errorf("failed to write binary to location: %w", err)
}
return nil
}

View File

@ -16,9 +16,10 @@ import (
)
var initCmd = &cobra.Command{
Use: "init <path to executable>",
Short: "Initialize a cosmovisor daemon home directory.",
Args: cobra.ExactArgs(1),
Use: "init <path to executable>",
Short: "Initialize a cosmovisor daemon home directory.",
Args: cobra.ExactArgs(1),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
return InitializeCosmovisor(nil, args)
},

View File

@ -9,7 +9,6 @@ import (
"testing"
"time"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
@ -244,8 +243,7 @@ func (p *BufferedPipe) panicIfStarted(msg string) {
func (s *InitTestSuite) NewCapturingLogger() (*BufferedPipe, log.Logger) {
bufferedStdOut, err := StartNewBufferedPipe("stdout", os.Stdout)
s.Require().NoError(err, "creating stdout buffered pipe")
output := zerolog.ConsoleWriter{Out: bufferedStdOut, TimeFormat: time.RFC3339Nano}
logger := log.NewCustomLogger(zerolog.New(output).With().Str("module", "cosmovisor").Timestamp().Logger())
logger := log.NewLogger(bufferedStdOut, log.ColorOption(false), log.TimeFormatOption(time.RFC3339Nano)).With(log.ModuleKey, "cosmovisor")
return &bufferedStdOut, logger
}

View File

@ -13,10 +13,11 @@ import (
func NewVersionCmd() *cobra.Command {
versionCmd := &cobra.Command{
Use: "version",
Short: "Display cosmovisor and APP version.",
Use: "version",
Short: "Display cosmovisor and APP version.",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
noAppVersion, _ := cmd.Flags().GetBool(cosmovisor.FlagNoAppVersion)
noAppVersion, _ := cmd.Flags().GetBool(cosmovisor.FlagCosmovisorOnly)
if val, err := cmd.Flags().GetString(cosmovisor.FlagOutput); val == "json" && err == nil {
return printVersionJSON(cmd, args, noAppVersion)
}
@ -26,7 +27,7 @@ func NewVersionCmd() *cobra.Command {
}
versionCmd.Flags().StringP(cosmovisor.FlagOutput, "o", "text", "Output format (text|json)")
versionCmd.Flags().Bool(cosmovisor.FlagNoAppVersion, false, "Don't print APP version")
versionCmd.Flags().Bool(cosmovisor.FlagCosmovisorOnly, false, "Print cosmovisor version only")
return versionCmd
}

View File

@ -3,6 +3,7 @@ package cosmovisor
const (
FlagOutput = "output"
FlagSkipUpgradeHeight = "unsafe-skip-upgrades"
FlagNoAppVersion = "no-app-version"
FlagCosmovisorOnly = "cosmovisor-only"
FlagForce = "force"
FlagUpgradeHeight = "upgrade-height"
)

View File

@ -6,7 +6,6 @@ require (
cosmossdk.io/log v1.1.0
cosmossdk.io/x/upgrade v0.0.0-20230614103911-b3da8bb4e801
github.com/otiai10/copy v1.12.0
github.com/rs/zerolog v1.29.1
github.com/spf13/cobra v1.7.0
github.com/stretchr/testify v1.8.4
)
@ -133,6 +132,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/rs/cors v1.8.3 // indirect
github.com/rs/zerolog v1.29.1 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/spf13/afero v1.9.5 // indirect
github.com/spf13/cast v1.5.1 // indirect

View File

@ -14,7 +14,6 @@ import (
"time"
"github.com/otiai10/copy"
"github.com/rs/zerolog"
"cosmossdk.io/log"
"cosmossdk.io/x/upgrade/plan"
@ -22,19 +21,18 @@ import (
)
type Launcher struct {
logger *zerolog.Logger
logger log.Logger
cfg *Config
fw *fileWatcher
}
func NewLauncher(logger log.Logger, cfg *Config) (Launcher, error) {
fw, err := newUpgradeFileWatcher(logger, cfg.UpgradeInfoFilePath(), cfg.PollInterval)
fw, err := newUpgradeFileWatcher(cfg, logger)
if err != nil {
return Launcher{}, err
}
zl := logger.Impl().(*zerolog.Logger)
return Launcher{logger: zl, cfg: cfg, fw: fw}, nil
return Launcher{logger: logger, cfg: cfg, fw: fw}, nil
}
// Run launches the app in a subprocess and returns when the subprocess (app)
@ -50,7 +48,7 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
return false, fmt.Errorf("current binary is invalid: %w", err)
}
l.logger.Info().Str("path", bin).Strs("args", args).Msg("running app")
l.logger.Info("running app", "path", bin, "args", args)
cmd := exec.Command(bin, args...)
cmd.Stdout = stdout
cmd.Stderr = stderr
@ -63,7 +61,8 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
go func() {
sig := <-sigs
if err := cmd.Process.Signal(sig); err != nil {
l.logger.Fatal().Err(err).Str("bin", bin).Msg("terminated")
l.logger.Error("terminated", "error", err, "bin", bin)
os.Exit(1)
}
}()
@ -82,7 +81,7 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
return false, err
}
if err := UpgradeBinary(log.NewCustomLogger(*l.logger), l.cfg, l.fw.currentInfo); err != nil {
if err := UpgradeBinary(l.logger, l.cfg, l.fw.currentInfo); err != nil {
return false, err
}
@ -100,13 +99,14 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
// When it returns, the process (app) is finished.
//
// It returns (true, nil) if an upgrade should be initiated (and we killed the process)
// It returns (false, err) if the process died by itself, or there was an issue reading the upgrade-info file.
// It returns (false, err) if the process died by itself
// It returns (false, nil) if the process exited normally without triggering an upgrade. This is very unlikely
// to happened with "start" but may happened with short-lived commands like `gaiad export ...`
// to happen with "start" but may happen with short-lived commands like `simd export ...`
func (l Launcher) WaitForUpgradeOrExit(cmd *exec.Cmd) (bool, error) {
currentUpgrade, err := l.cfg.UpgradeInfo()
if err != nil {
l.logger.Error().Err(err)
// upgrade info not found do nothing
currentUpgrade = upgradetypes.Plan{}
}
cmdDone := make(chan error)
@ -117,7 +117,7 @@ func (l Launcher) WaitForUpgradeOrExit(cmd *exec.Cmd) (bool, error) {
select {
case <-l.fw.MonitorUpdate(currentUpgrade):
// upgrade - kill the process and restart
l.logger.Info().Msg("daemon shutting down in an attempt to restart")
l.logger.Info("daemon shutting down in an attempt to restart")
_ = cmd.Process.Kill()
case err := <-cmdDone:
l.fw.Stop()
@ -139,7 +139,7 @@ func (l Launcher) doBackup() error {
if !l.cfg.UnsafeSkipBackup {
// check if upgrade-info.json is not empty.
var uInfo upgradetypes.Plan
upgradeInfoFile, err := os.ReadFile(filepath.Join(l.cfg.Home, "data", "upgrade-info.json"))
upgradeInfoFile, err := os.ReadFile(l.cfg.UpgradeInfoFilePath())
if err != nil {
return fmt.Errorf("error while reading upgrade-info.json: %w", err)
}
@ -157,7 +157,7 @@ func (l Launcher) doBackup() error {
stStr := fmt.Sprintf("%d-%d-%d", st.Year(), st.Month(), st.Day())
dst := filepath.Join(l.cfg.DataBackupPath, fmt.Sprintf("data"+"-backup-%s", stStr))
l.logger.Info().Time("backup start time", st).Msg("starting to take backup of data directory")
l.logger.Info("starting to take backup of data directory", "backup start time", st)
// copy the $DAEMON_HOME/data to a backup dir
if err = copy.Copy(filepath.Join(l.cfg.Home, "data"), dst); err != nil {
@ -166,38 +166,39 @@ func (l Launcher) doBackup() error {
// backup is done, lets check endtime to calculate total time taken for backup process
et := time.Now()
l.logger.Info().Str("backup saved at", dst).Time("backup completion time", et).TimeDiff("time taken to complete backup", et, st).Msg("backup completed")
l.logger.Info("backup completed", "backup saved at", dst, "backup completion time", et, "time taken to complete backup", et.Sub(st))
}
return nil
}
// doCustomPreUpgrade executes the custom preupgrade script if provided.
func (l Launcher) doCustomPreUpgrade() error {
if l.cfg.CustomPreupgrade == "" {
return nil
}
// check if upgrade-info.json is not empty.
var uInfo upgradetypes.Plan
var upgradePlan upgradetypes.Plan
upgradeInfoFile, err := os.ReadFile(l.cfg.UpgradeInfoFilePath())
if err != nil {
return fmt.Errorf("error while reading upgrade-info.json: %w", err)
}
if err = json.Unmarshal(upgradeInfoFile, &uInfo); err != nil {
if err = json.Unmarshal(upgradeInfoFile, &upgradePlan); err != nil {
return err
}
if err = uInfo.ValidateBasic(); err != nil {
if err = upgradePlan.ValidateBasic(); err != nil {
return fmt.Errorf("invalid upgrade plan: %w", err)
}
// check if preupgradeFile is executable file
preupgradeFile := filepath.Join(l.cfg.Home, "cosmovisor", l.cfg.CustomPreupgrade)
l.logger.Info().Str("Looking for COSMOVISOR_CUSTOM_PREUPGRADE file ", preupgradeFile)
l.logger.Info("looking for COSMOVISOR_CUSTOM_PREUPGRADE file", "file", preupgradeFile)
info, err := os.Stat(preupgradeFile)
if err != nil {
l.logger.Error().Str("file", preupgradeFile).Msg("COSMOVISOR_CUSTOM_PREUPGRADE file missing")
l.logger.Error("COSMOVISOR_CUSTOM_PREUPGRADE file missing", "file", preupgradeFile)
return err
}
if !info.Mode().IsRegular() {
@ -212,20 +213,20 @@ func (l Launcher) doCustomPreUpgrade() error {
newMode := oldMode | 0o100
if oldMode != newMode {
if err := os.Chmod(preupgradeFile, newMode); err != nil {
l.logger.Info().Msg("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
l.logger.Info("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
return fmt.Errorf("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
}
}
// Run preupgradeFile
cmd := exec.Command(preupgradeFile, uInfo.Name, fmt.Sprintf("%d", uInfo.Height))
cmd := exec.Command(preupgradeFile, upgradePlan.Name, fmt.Sprintf("%d", upgradePlan.Height))
cmd.Dir = l.cfg.Home
result, err := cmd.Output()
if err != nil {
return err
}
l.logger.Info().Str("command", preupgradeFile).Str("argv1", uInfo.Name).Str("argv2", fmt.Sprintf("%d", uInfo.Height)).Bytes("result", result).Msg("COSMOVISOR_CUSTOM_PREUPGRADE result")
l.logger.Info("COSMOVISOR_CUSTOM_PREUPGRADE result", "command", preupgradeFile, "argv1", upgradePlan.Name, "argv2", fmt.Sprintf("%d", upgradePlan.Height), "result", result)
return nil
}
@ -245,17 +246,17 @@ func (l *Launcher) doPreUpgrade() error {
switch err.(*exec.ExitError).ProcessState.ExitCode() {
case 1:
l.logger.Info().Msg("pre-upgrade command does not exist. continuing the upgrade.")
l.logger.Info("pre-upgrade command does not exist. continuing the upgrade.")
return nil
case 30:
return fmt.Errorf("pre-upgrade command failed : %w", err)
case 31:
l.logger.Error().Err(err).Int("attempt", counter).Msg("pre-upgrade command failed. retrying")
l.logger.Error("pre-upgrade command failed. retrying", "error", err, "attempt", counter)
continue
}
}
l.logger.Info().Msg("pre-upgrade successful. continuing the upgrade.")
l.logger.Info("pre-upgrade successful. continuing the upgrade.")
return nil
}
}
@ -273,7 +274,7 @@ func (l *Launcher) executePreUpgradeCmd() error {
return err
}
l.logger.Info().Bytes("result", result).Msg("pre-upgrade result")
l.logger.Info("pre-upgrade result", "result", result)
return nil
}

View File

@ -93,7 +93,6 @@ func (s *processTestSuite) TestLaunchProcessWithRestartDelay() {
upgradeFile := cfg.UpgradeInfoFilePath()
start := time.Now()
doUpgrade, err := launcher.Run([]string{"foo", "bar", "1234", upgradeFile}, stdout, stderr)
require.NoError(err)
require.True(doUpgrade)
@ -115,8 +114,7 @@ func (s *processTestSuite) TestLaunchProcessWithDownloads() {
require := s.Require()
home := copyTestData(s.T(), "download")
cfg := &cosmovisor.Config{Home: home, Name: "autod", AllowDownloadBinaries: true, PollInterval: 100, UnsafeSkipBackup: true}
buf := newBuffer() // inspect output using buf.String()
logger := log.NewLogger(buf).With(log.ModuleKey, "cosmovisor")
logger := log.NewTestLogger(s.T()).With(log.ModuleKey, "cosmovisor")
upgradeFilename := cfg.UpgradeInfoFilePath()
// should run the genesis binary and produce expected output
@ -130,7 +128,6 @@ func (s *processTestSuite) TestLaunchProcessWithDownloads() {
stdout, stderr := newBuffer(), newBuffer()
args := []string{"some", "args", upgradeFilename}
doUpgrade, err := launcher.Run(args, stdout, stderr)
require.NoError(err)
require.True(doUpgrade)
require.Equal("", stderr.String())
@ -180,14 +177,14 @@ func (s *processTestSuite) TestLaunchProcessWithDownloadsAndMissingPreupgrade()
require := s.Require()
home := copyTestData(s.T(), "download")
cfg := &cosmovisor.Config{
Home: home, Name: "autod",
Home: home,
Name: "autod",
AllowDownloadBinaries: true,
PollInterval: 100,
UnsafeSkipBackup: true,
CustomPreupgrade: "missing.sh",
}
buf := newBuffer() // inspect output using buf.String()
logger := log.NewLogger(buf).With(log.ModuleKey, "cosmovisor")
logger := log.NewTestLogger(s.T()).With(log.ModuleKey, "cosmovisor")
upgradeFilename := cfg.UpgradeInfoFilePath()
// should run the genesis binary and produce expected output

View File

@ -5,57 +5,59 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/rs/zerolog"
"cosmossdk.io/log"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
type fileWatcher struct {
logger log.Logger
// full path to a watched file
filename string
filename string // full path to a watched file
interval time.Duration
currentBin string
currentInfo upgradetypes.Plan
lastModTime time.Time
cancel chan bool
ticker *time.Ticker
needsUpdate bool
needsUpdate bool
initialized bool
}
func newUpgradeFileWatcher(logger log.Logger, filename string, interval time.Duration) (*fileWatcher, error) {
func newUpgradeFileWatcher(cfg *Config, logger log.Logger) (*fileWatcher, error) {
filename := cfg.UpgradeInfoFilePath()
if filename == "" {
return nil, errors.New("filename undefined")
}
filenameAbs, err := filepath.Abs(filename)
if err != nil {
return nil,
fmt.Errorf("invalid path; %s must be a valid file path: %w", filename, err)
return nil, fmt.Errorf("invalid path: %s must be a valid file path: %w", filename, err)
}
dirname := filepath.Dir(filename)
info, err := os.Stat(dirname)
if err != nil || !info.IsDir() {
return nil, fmt.Errorf("invalid path; %s must be an existing directory: %w", dirname, err)
if info, err := os.Stat(dirname); err != nil || !info.IsDir() {
return nil, fmt.Errorf("invalid path: %s must be an existing directory: %w", dirname, err)
}
bin, err := cfg.CurrentBin()
if err != nil {
return nil, fmt.Errorf("error creating symlink to genesis: %w", err)
}
return &fileWatcher{
logger: logger,
currentBin: bin,
filename: filenameAbs,
interval: interval,
interval: cfg.PollInterval,
currentInfo: upgradetypes.Plan{},
lastModTime: time.Time{},
cancel: make(chan bool),
ticker: time.NewTicker(interval),
ticker: time.NewTicker(cfg.PollInterval),
needsUpdate: false,
initialized: false,
}, nil
@ -65,9 +67,9 @@ func (fw *fileWatcher) Stop() {
close(fw.cancel)
}
// pools the filesystem to check for new upgrade currentInfo. currentName is the name
// of currently running upgrade. The check is rejected if it finds an upgrade with the same
// name.
// MonitorUpdate pools the filesystem to check for new upgrade currentInfo.
// currentName is the name of currently running upgrade. The check is rejected if it finds
// an upgrade with the same name.
func (fw *fileWatcher) MonitorUpdate(currentUpgrade upgradetypes.Plan) <-chan struct{} {
fw.ticker.Reset(fw.interval)
done := make(chan struct{})
@ -112,8 +114,12 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
info, err := parseUpgradeInfoFile(fw.filename)
if err != nil {
zl := fw.logger.Impl().(*zerolog.Logger)
zl.Fatal().Err(err).Msg("failed to parse upgrade info file")
panic(fmt.Errorf("failed to parse upgrade info file: %w", err))
}
// file exist but too early in height
currentHeight, _ := fw.checkHeight()
if currentHeight != 0 && currentHeight < info.Height {
return false
}
@ -142,27 +148,60 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
return false
}
func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) {
var ui upgradetypes.Plan
// checkHeight checks if the current block height
func (fw *fileWatcher) checkHeight() (int64, error) {
// TODO(@julienrbrt) use `if !testing.Testing()` from Go 1.22
// The tests from `process_test.go`, which run only on linux, are failing when using `autod` that is a bash script.
// In production, the binary will always be an application with a status command, but in tests it isn't not.
if strings.HasSuffix(os.Args[0], ".test") {
return 0, nil
}
f, err := os.Open(filename)
result, err := exec.Command(fw.currentBin, "status").Output() //nolint:gosec // we want to execute the status command
if err != nil {
return 0, err
}
type response struct {
SyncInfo struct {
LatestBlockHeight string `json:"latest_block_height"`
} `json:"SyncInfo"`
}
var resp response
if err := json.Unmarshal(result, &resp); err != nil {
return 0, err
}
if resp.SyncInfo.LatestBlockHeight == "" {
return 0, errors.New("latest block height is empty")
}
return strconv.ParseInt(resp.SyncInfo.LatestBlockHeight, 10, 64)
}
func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) {
f, err := os.ReadFile(filename)
if err != nil {
return upgradetypes.Plan{}, err
}
defer f.Close()
d := json.NewDecoder(f)
if err := d.Decode(&ui); err != nil {
if len(f) == 0 {
return upgradetypes.Plan{}, errors.New("empty upgrade-info.json")
}
var upgradePlan upgradetypes.Plan
if err := json.Unmarshal(f, &upgradePlan); err != nil {
return upgradetypes.Plan{}, err
}
// required values must be set
if ui.Height <= 0 || ui.Name == "" {
return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content; name and height must be not empty; got: %v", ui)
if err := upgradePlan.ValidateBasic(); err != nil {
return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content: %w, got: %v", err, upgradePlan)
}
// normalize name to prevent operator error in upgrade name case sensitivity errors.
ui.Name = strings.ToLower(ui.Name)
upgradePlan.Name = strings.ToLower(upgradePlan.Name)
return ui, err
return upgradePlan, err
}

View File

@ -31,10 +31,6 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
)
// Deprecated: UpgradeInfoFileName file to store upgrade information
// use x/upgrade/types.UpgradeInfoFilename instead.
const UpgradeInfoFileName string = "upgrade-info.json"
type Keeper struct {
homePath string // root directory of app config
skipUpgradeHeights map[int64]bool // map of heights to skip for an upgrade