Skip to content

Commit

Permalink
Merge branch 'main' into likhita/migrate-slashing-params
Browse files Browse the repository at this point in the history
  • Loading branch information
alexanderbez authored Jul 6, 2022
2 parents e94a72a + e7f78b4 commit 12cfd15
Show file tree
Hide file tree
Showing 4 changed files with 726 additions and 2 deletions.
1 change: 1 addition & 0 deletions cosmovisor/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

* [\#12464](https://github.com/cosmos/cosmos-sdk/pull/12464) Create the `cosmovisor init` command.
* [\#12188](https://github.com/cosmos/cosmos-sdk/pull/12188) Add a `DAEMON_RESTART_DELAY` for allowing a node operator to define a delay between the node halt (for upgrade) and backup.
* [\#11823](https://github.com/cosmos/cosmos-sdk/pull/11823) Refactor `cosmovisor` CLI to use `cobra`.
* [\#11731](https://github.com/cosmos/cosmos-sdk/pull/11731) `cosmovisor version -o json` returns the cosmovisor version and the result of `simd --output json --long` in one JSON object.
Expand Down
37 changes: 35 additions & 2 deletions cosmovisor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

`cosmovisor` is a small process manager for Cosmos SDK application binaries that monitors the governance module for incoming chain upgrade proposals. If it sees a proposal that gets approved, `cosmovisor` can automatically download the new binary, stop the current binary, switch from the old binary to the new one, and finally restart the node with the new binary.

<!-- TOC -->
- [Design](#design)
- [Contributing](#contributing)
- [Setup](#setup)
- [Installation](#installation)
- [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables)
- [Folder Layout](#folder-layout)
- [Usage](#usage)
- [Initialization](#initialization)
- [Detecting Upgrades](#detecting-upgrades)
- [Auto-Download](#auto-download)
- [Example: SimApp Upgrade](#example-simapp-upgrade)
- [Chain Setup](#chain-setup)



## Design

Cosmovisor is designed to be used as a wrapper for a `Cosmos SDK` app:
Expand Down Expand Up @@ -115,15 +131,32 @@ The system administrator is responsible for:
* installing the `cosmovisor` binary
* configuring the host's init system (e.g. `systemd`, `launchd`, etc.)
* appropriately setting the environmental variables
* manually installing the `genesis` folder
* manually installing the `upgrades/<name>` folders
* creating the `<DAEMON_HOME>/cosmovisor` directory
* creating the `<DAEMON_HOME>/cosmovisor/genesis/bin` folder
* creating the `<DAEMON_HOME>/cosmovisor/upgrades/<name>/bin` folders
* placing the different versions of the `<DAEMON_NAME>` executable in the appropriate `bin` folders.

`cosmovisor` will set the `current` link to point to `genesis` at first start (i.e. when no `current` link exists) and then handle switching binaries at the correct points in time so that the system administrator can prepare days in advance and relax at upgrade time.

In order to support downloadable binaries, a tarball for each upgrade binary will need to be packaged up and made available through a canonical URL. Additionally, a tarball that includes the genesis binary and all available upgrade binaries can be packaged up and made available so that all the necessary binaries required to sync a fullnode from start can be easily downloaded.

The `DAEMON` specific code and operations (e.g. tendermint config, the application db, syncing blocks, etc.) all work as expected. The application binaries' directives such as command-line flags and environment variables also work as expected.

### Initialization

The `cosmovisor init <path to executable>` command creates the folder structure required for using cosmovisor.

It does the following:

* creates the `<DAEMON_HOME>/cosmovisor` folder if it doesn't yet exist
* creates the `<DAEMON_HOME>/cosmovisor/genesis/bin` folder if it doesn't yet exist
* copies the provided executable file to `<DAEMON_HOME>/cosmovisor/genesis/bin/<DAEMON_NAME>`
* creates the `current` link, pointing to the `genesis` folder

It uses the `DAEMON_HOME` and `DAEMON_NAME` environment variables for folder location and executable name.

The `cosmovisor init` command is specifically for initializing cosmovisor, and should not be confused with a chain's `init` command (e.g. `cosmovisor run init`).

### Detecting Upgrades

`cosmovisor` is polling the `$DAEMON_HOME/data/upgrade-info.json` file for new upgrade instructions. The file is created by the x/upgrade module in `BeginBlocker` when an upgrade is detected and the blockchain reaches the upgrade height.
Expand Down
136 changes: 136 additions & 0 deletions cosmovisor/cmd/cosmovisor/init.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main

import (
"errors"
"fmt"
"io"
"os"
"path/filepath"

"github.com/rs/zerolog"
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/cosmovisor"
cverrors "github.com/cosmos/cosmos-sdk/cosmovisor/errors"
)

func init() {
rootCmd.AddCommand(initCmd)
}

var initCmd = &cobra.Command{
Use: "init <path to executable>",
Short: "Initializes a cosmovisor daemon home directory.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
logger := cmd.Context().Value(cosmovisor.LoggerKey).(*zerolog.Logger)

return InitializeCosmovisor(logger, args)
},
}

// InitializeCosmovisor initializes the cosmovisor directories, current link, and initial executable.
func InitializeCosmovisor(logger *zerolog.Logger, args []string) error {
if len(args) < 1 || len(args[0]) == 0 {
return errors.New("no <path to executable> provided")
}
pathToExe := args[0]
switch exeInfo, err := os.Stat(pathToExe); {
case os.IsNotExist(err):
return fmt.Errorf("executable file not found: %w", err)
case err != nil:
return fmt.Errorf("could not stat executable: %w", err)
case exeInfo.IsDir():
return errors.New("invalid path to executable: must not be a directory")
}
cfg, err := getConfigForInitCmd()
if err != nil {
return err
}

logger.Info().Msg("checking on the genesis/bin directory")
genBinExe := cfg.GenesisBin()
genBinDir, _ := filepath.Split(genBinExe)
genBinDir = filepath.Clean(genBinDir)
switch genBinDirInfo, genBinDirErr := os.Stat(genBinDir); {
case os.IsNotExist(genBinDirErr):
logger.Info().Msgf("creating directory (and any parents): %q", genBinDir)
mkdirErr := os.MkdirAll(genBinDir, 0o755)
if mkdirErr != nil {
return mkdirErr
}
case genBinDirErr != nil:
return fmt.Errorf("error getting info on genesis/bin directory: %w", genBinDirErr)
case !genBinDirInfo.IsDir():
return fmt.Errorf("the path %q already exists but is not a directory", genBinDir)
default:
logger.Info().Msgf("the %q directory already exists", genBinDir)
}

logger.Info().Msg("checking on the genesis/bin executable")
if _, err = os.Stat(genBinExe); os.IsNotExist(err) {
logger.Info().Msgf("copying executable into place: %q", genBinExe)
if cpErr := copyFile(pathToExe, genBinExe); cpErr != nil {
return cpErr
}
} else {
logger.Info().Msgf("the %q file already exists", genBinExe)
}
logger.Info().Msgf("making sure %q is executable", genBinExe)
if err = cosmovisor.MarkExecutable(genBinExe); err != nil {
return err
}
if err = cosmovisor.EnsureBinary(genBinExe); err != nil {
return err
}

logger.Info().Msg("checking on the current symlink and creating it if needed")
cur, curErr := cfg.CurrentBin()
if curErr != nil {
return curErr
}
logger.Info().Msgf("the current symlink points to: %q", cur)

return nil
}

// getConfigForInitCmd gets just the configuration elements needed to initialize cosmovisor.
func getConfigForInitCmd() (*cosmovisor.Config, error) {
var errs []error
// Note: Not using GetConfigFromEnv here because that checks that the directories already exist.
// We also don't care about the rest of the configuration stuff in here.
cfg := &cosmovisor.Config{
Home: os.Getenv(cosmovisor.EnvHome),
Name: os.Getenv(cosmovisor.EnvName),
}
if len(cfg.Name) == 0 {
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvName))
}
switch {
case len(cfg.Home) == 0:
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvHome))
case !filepath.IsAbs(cfg.Home):
errs = append(errs, fmt.Errorf("%s must be an absolute path", cosmovisor.EnvHome))
}
if len(errs) > 0 {
return nil, cverrors.FlattenErrors(errs...)
}
return cfg, nil
}

// copyFile copies the file at the given source to the given destination.
func copyFile(source, destination string) error {
// assume we already know that src exists and is a regular file.
src, err := os.Open(source)
if err != nil {
return err
}
defer src.Close()
dst, err := os.Create(destination)
if err != nil {
return err
}
defer dst.Close()
_, err = io.Copy(dst, src)
return err
}
Loading

0 comments on commit 12cfd15

Please sign in to comment.