refactor: make cosmovisor depend on x/upgrade only (#14881)

This commit is contained in:
Julien Robert 2023-02-02 23:16:41 +01:00 committed by GitHub
parent 12394c8b99
commit b3724f1a0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 619 additions and 348 deletions

View File

@ -36,6 +36,15 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
## Client Breaking Changes
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Cosmovisor supports only upgrade plan with a checksum. This is enforced by the `x/upgrade` module for better security.
## Improvements
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to use `x/upgrade` validation logic.
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to depend only on the `x/upgrade` module.
## v1.4.0 2022-10-23
### API Breaking Changes

View File

@ -11,8 +11,9 @@ import (
"time"
cverrors "cosmossdk.io/tools/cosmovisor/errors"
upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
upgradekeeper "cosmossdk.io/x/upgrade/keeper"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/rs/zerolog"
)
@ -275,7 +276,7 @@ func (cfg *Config) SetCurrentUpgrade(u upgradetypes.Plan) (rerr error) {
// ensure named upgrade exists
bin := cfg.UpgradeBin(u.Name)
if err := EnsureBinary(bin); err != nil {
if err := plan.EnsureBinary(bin); err != nil {
return err
}

View File

@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/suite"
"cosmossdk.io/tools/cosmovisor/errors"
"cosmossdk.io/x/upgrade/plan"
)
type argsTestSuite struct {
@ -250,20 +251,19 @@ func (s *argsTestSuite) TestEnsureBin() {
cfg := Config{Home: absPath, Name: "dummyd", DataBackupPath: absPath}
s.Require().Len(cfg.validate(), 0, "validation errors")
s.Require().NoError(EnsureBinary(cfg.GenesisBin()))
s.Require().NoError(plan.EnsureBinary(cfg.GenesisBin()))
cases := map[string]struct {
upgrade string
hasBin bool
}{
"proper": {"chain2", true},
"no binary": {"nobin", false},
"not executable": {"noexec", false},
"no directory": {"foobarbaz", false},
"proper": {"chain2", true},
"no binary": {"nobin", false},
"no directory": {"foobarbaz", false},
}
for _, tc := range cases {
err := EnsureBinary(cfg.UpgradeBin(tc.upgrade))
err := plan.EnsureBinary(cfg.UpgradeBin(tc.upgrade))
if tc.hasBin {
s.Require().NoError(err)
} else {

View File

@ -18,7 +18,7 @@ the proposal. Cosmovisor interprets that data to perform an update: switch a cur
and restart the App.
Configuration of Cosmovisor is done through environment variables, which are
documented in: https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor/README.md`,
documented in: https://docs.cosmos.network/main/tooling/cosmovisor`,
cosmovisor.EnvName, cosmovisor.EnvHome,
)
}

View File

@ -92,7 +92,7 @@ func (s *HelpTestSuite) TestGetHelpText() {
expectedPieces := []string{
"Cosmovisor",
cosmovisor.EnvName, cosmovisor.EnvHome,
"https://github.com/cosmos/cosmos-sdk/tree/main/tools/cosmovisor/README.md",
"https://docs.cosmos.network/main/tooling/cosmovisor",
}
actual := GetHelpText()

View File

@ -12,6 +12,7 @@ import (
"cosmossdk.io/tools/cosmovisor"
cverrors "cosmossdk.io/tools/cosmovisor/errors"
"cosmossdk.io/x/upgrade/plan"
)
func init() {
@ -77,10 +78,7 @@ func InitializeCosmovisor(logger *zerolog.Logger, args []string) error {
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 {
if err = plan.EnsureBinary(genBinExe); err != nil {
return err
}

View File

@ -1,11 +1,11 @@
package main
import (
"bytes"
"context"
"testing"
"cosmossdk.io/tools/cosmovisor"
"github.com/cosmos/cosmos-sdk/testutil"
"github.com/stretchr/testify/require"
)
@ -13,7 +13,11 @@ func TestVersionCommand_Error(t *testing.T) {
logger := cosmovisor.NewLogger()
rootCmd.SetArgs([]string{"version"})
_, out := testutil.ApplyMockIO(rootCmd)
out := bytes.NewBufferString("")
rootCmd.SetOut(out)
rootCmd.SetErr(out)
ctx := context.WithValue(context.Background(), cosmovisor.LoggerKey, logger)
require.Error(t, rootCmd.ExecuteContext(ctx))

View File

@ -3,8 +3,7 @@ module cosmossdk.io/tools/cosmovisor
go 1.19
require (
github.com/cosmos/cosmos-sdk v0.47.0-rc2
github.com/hashicorp/go-getter v1.6.2
cosmossdk.io/x/upgrade v0.0.0-20230202115111-f719cd32adf3
github.com/otiai10/copy v1.9.0
github.com/rs/zerolog v1.29.0
github.com/spf13/cobra v1.6.1
@ -18,45 +17,54 @@ require (
cloud.google.com/go/iam v0.8.0 // indirect
cloud.google.com/go/storage v1.27.0 // indirect
cosmossdk.io/api v0.2.6 // indirect
cosmossdk.io/collections v0.0.0-20230202103518-eb86b68caea0 // indirect
cosmossdk.io/core v0.5.1 // indirect
cosmossdk.io/depinject v1.0.0-alpha.3 // indirect
cosmossdk.io/errors v1.0.0-beta.7 // indirect
cosmossdk.io/math v1.0.0-beta.4 // indirect
cosmossdk.io/store v0.0.0-20230202103518-eb86b68caea0 // indirect
filippo.io/edwards25519 v1.0.0-rc.1 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
github.com/ChainSafe/go-schnorrkel v0.0.0-20200405005733-88cbf1b4c40d // indirect
github.com/DataDog/zstd v1.4.5 // indirect
github.com/HdrHistogram/hdrhistogram-go v1.1.2 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go v1.40.45 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/cockroachdb/errors v1.9.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v0.0.0-20220817183557-09c6e030a677 // indirect
github.com/cockroachdb/redact v1.1.3 // indirect
github.com/confio/ics23/go v0.9.0 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v0.0.0-20230119180254-161cf3632b7c // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.1 // indirect
github.com/cosmos/cosmos-sdk v0.47.0-rc2 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/gogoproto v1.4.4 // indirect
github.com/cosmos/gorocksdb v1.2.0 // indirect
github.com/cosmos/iavl v0.19.5-rc.1 // indirect
github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect
github.com/cosmos/iavl v0.20.0-alpha1 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.0 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/getsentry/sentry-go v0.17.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.0.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
@ -65,29 +73,37 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.1 // indirect
github.com/googleapis/gax-go/v2 v2.7.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/gtank/merlin v0.1.1 // indirect
github.com/gtank/ristretto255 v0.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.6.2 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hdevalence/ed25519consensus v0.0.0-20220222234857-c00d1f31bab3 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.0.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.15.12 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/libp2p/go-buffer-pool v0.1.0 // indirect
github.com/linxGnu/grocksdb v1.7.10 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
@ -99,9 +115,11 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.14.0 // indirect
github.com/prometheus/client_model v0.3.0 // indirect
github.com/prometheus/common v0.37.0 // indirect
github.com/prometheus/common v0.39.0 // indirect
github.com/prometheus/procfs v0.8.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.9.0 // indirect
github.com/rs/cors v1.8.2 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/cast v1.5.0 // indirect
@ -112,17 +130,15 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tendermint/tendermint v0.37.0-rc2 // indirect
github.com/tendermint/tm-db v0.6.7 // indirect
github.com/tidwall/btree v1.5.2 // indirect
github.com/tidwall/btree v1.6.0 // indirect
github.com/ulikunitz/xz v0.5.8 // indirect
github.com/zondax/hid v0.9.1 // indirect
github.com/zondax/ledger-go v0.14.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
github.com/zondax/ledger-go v0.14.1 // indirect
go.opencensus.io v0.24.0 // indirect
golang.org/x/crypto v0.4.0 // indirect
golang.org/x/exp v0.0.0-20221019170559-20944726eadf // indirect
golang.org/x/crypto v0.5.0 // indirect
golang.org/x/exp v0.0.0-20230118134722-a68e582fa157 // indirect
golang.org/x/net v0.5.0 // indirect
golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783 // indirect
golang.org/x/oauth2 v0.3.0 // indirect
golang.org/x/sys v0.4.0 // indirect
golang.org/x/term v0.4.0 // indirect
golang.org/x/text v0.6.0 // indirect
@ -135,6 +151,14 @@ require (
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
pgregory.net/rapid v0.5.3 // indirect
gotest.tools/v3 v3.4.0 // indirect
nhooyr.io/websocket v1.8.6 // indirect
pgregory.net/rapid v0.5.5 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
// Using this replace directive as v0.20.0-alpha1 > v0.20.0-alpha.2
// This can be deleted when a v0.20.0-alpha3 is tagged or anything lexicographically above than v0.20.0-alpha1
replace github.com/cosmos/iavl => github.com/cosmos/iavl v0.20.0-alpha.2
replace github.com/cosmos/cosmos-sdk => ../..

File diff suppressed because it is too large Load Diff

View File

@ -16,7 +16,8 @@ import (
"github.com/otiai10/copy"
"github.com/rs/zerolog"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
type Launcher struct {
@ -43,7 +44,7 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
return false, fmt.Errorf("error creating symlink to genesis: %w", err)
}
if err := EnsureBinary(bin); err != nil {
if err := plan.EnsureBinary(bin); err != nil {
return false, fmt.Errorf("current binary is invalid: %w", err)
}

View File

@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/suite"
"cosmossdk.io/tools/cosmovisor"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
type processTestSuite struct {

View File

@ -9,7 +9,7 @@ import (
"strings"
"time"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/rs/zerolog"
)

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/require"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
func TestParseUpgradeInfoFile(t *testing.T) {

View File

@ -6,7 +6,7 @@ echo 'ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary'
# create upgrade info
# this info contains directly information about binaries (in chain2->chain3 update we test with info containing a link to the file with an address for the new chain binary)
echo '{"name":"chain2","height":49,"info":"{\"binaries\":{\"linux/amd64\":\"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain2-zip_bin/autod.zip?checksum=sha256:fd20f3163e5ab2ab430c0c2936de82fcf5f100a8497d7f121b9f34a1a4b96683\"}}"}' >$3
echo '{"name":"chain2","height":49,"info":"{\"binaries\":{\"linux/amd64\":\"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain2-zip_bin/autod.zip?checksum=sha256:13767eb0b57bf51a0f43d49f6277d5df97d4dec672dc39822d23a82fb8e70a7b\"}}"}' >$3
sleep 0.1
echo Never should be printed!!!

View File

@ -6,8 +6,8 @@ echo Args: $@
echo 'ERROR: UPGRADE "chain3" NEEDED at height: 936: ref_to_chain3-zip_dir.json module=main'
# this update info doesn't contain binaries, instead it is a reference for further download instructions.
# echo '{"name":"chain3","height":936,"info":"{\"binaries\":{\"linux/amd64\":\"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/ref_to_chain3-zip_dir.json\"}}"}' > $3
echo '{"name":"chain3","height":936,"info":"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/ref_to_chain3-zip_dir.json"}' >$3
# echo '{"name":"chain3","height":936,"info":"{\"binaries\":{\"linux/amd64\":\"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/ref_to_chain3-zip_dir.json?checksum=sha256:a95075f4dd83bc9f0f556ef73e64ce000f9bf3a6beeb9d4ae32f594b1417ef7a\"}}"}' > $3
echo '{"name":"chain3","height":936,"info":"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/ref_to_chain3-zip_dir.json?checksum=sha256:a95075f4dd83bc9f0f556ef73e64ce000f9bf3a6beeb9d4ae32f594b1417ef7a"}' >$3
sleep 1
echo 'Do not print'

View File

@ -1,3 +0,0 @@
#!/bin/sh
echo 'exec flag not set'

View File

@ -1,30 +1,25 @@
package cosmovisor
import (
"encoding/json"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
"github.com/hashicorp/go-getter"
"github.com/otiai10/copy"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/rs/zerolog"
)
// UpgradeBinary will be called after the log message has been parsed and the process has terminated.
// We can now make any changes to the underlying directory without interference and leave it
// in a state, so we can make a proper restart
func UpgradeBinary(logger *zerolog.Logger, cfg *Config, info upgradetypes.Plan) error {
func UpgradeBinary(logger *zerolog.Logger, cfg *Config, p upgradetypes.Plan) error {
// simplest case is to switch the link
err := EnsureBinary(cfg.UpgradeBin(info.Name))
err := plan.EnsureBinary(cfg.UpgradeBin(p.Name))
if err == nil {
// we have the binary - do it
return cfg.SetCurrentUpgrade(info)
return cfg.SetCurrentUpgrade(p)
}
// if auto-download is disabled, we fail
@ -33,7 +28,7 @@ func UpgradeBinary(logger *zerolog.Logger, cfg *Config, info upgradetypes.Plan)
}
// if the dir is there already, don't download either
switch fi, err := os.Stat(cfg.UpgradeDir(info.Name)); {
switch fi, err := os.Stat(cfg.UpgradeDir(p.Name)); {
case fi != nil: // The directory exists, do not overwrite.
return errors.New("upgrade dir already exists, won't overwrite")
@ -44,136 +39,47 @@ func UpgradeBinary(logger *zerolog.Logger, cfg *Config, info upgradetypes.Plan)
return fmt.Errorf("unhandled error: %w", err)
}
upgradeInfo, err := plan.ParseInfo(p.Info)
if err != nil {
return fmt.Errorf("cannot parse upgrade info: %w", err)
}
if err := upgradeInfo.ValidateFull(cfg.Name); err != nil {
return fmt.Errorf("invalid binaries: %w", err)
}
url, err := GetBinaryURL(upgradeInfo.Binaries)
if err != nil {
return err
}
// If not there, then we try to download it... maybe
logger.Info().Msg("no upgrade binary found, beginning to download it")
if err := DownloadBinary(cfg, info); err != nil {
if err := plan.DownloadUpgrade(cfg.UpgradeDir(p.Name), url, cfg.Name); err != nil {
return fmt.Errorf("cannot download binary. %w", err)
}
logger.Info().Msg("downloading binary complete")
// and then set the binary again
if err := EnsureBinary(cfg.UpgradeBin(info.Name)); err != nil {
if err := plan.EnsureBinary(cfg.UpgradeBin(p.Name)); err != nil {
return fmt.Errorf("downloaded binary doesn't check out: %w", err)
}
return cfg.SetCurrentUpgrade(info)
return cfg.SetCurrentUpgrade(p)
}
// DownloadBinary will grab the binary and place it in the proper directory
func DownloadBinary(cfg *Config, info upgradetypes.Plan) error {
url, err := GetDownloadURL(info)
if err != nil {
return err
func GetBinaryURL(binaries plan.BinaryDownloadURLMap) (string, error) {
url, ok := binaries[OSArch()]
if !ok {
url, ok = binaries["any"]
}
if !ok {
return "", fmt.Errorf("cannot find binary for os/arch: neither %s, nor any", OSArch())
}
// download into the bin dir (works for one file)
binPath := cfg.UpgradeBin(info.Name)
err = getter.GetFile(binPath, url)
// if this fails, let's see if it is a zipped directory
if err != nil {
dirPath := cfg.UpgradeDir(info.Name)
err = getter.Get(dirPath, url)
if err != nil {
return err
}
err = EnsureBinary(binPath)
// copy binary to binPath from dirPath if zipped directory don't contain bin directory to wrap the binary
if err != nil {
err = copy.Copy(filepath.Join(dirPath, cfg.Name), binPath)
if err != nil {
return err
}
}
}
// if it is successful, let's ensure the binary is executable
return MarkExecutable(binPath)
}
// MarkExecutable will try to set the executable bits if not already set
// Fails if file doesn't exist or we cannot set those bits
func MarkExecutable(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("stating binary: %w", err)
}
// end early if world exec already set
if info.Mode()&0o001 == 1 {
return nil
}
// now try to set all exec bits
newMode := info.Mode().Perm() | 0o111
return os.Chmod(path, newMode)
}
// UpgradeConfig is expected format for the info field to allow auto-download
type UpgradeConfig struct {
Binaries map[string]string `json:"binaries"`
}
// GetDownloadURL will check if there is an arch-dependent binary specified in Info
func GetDownloadURL(info upgradetypes.Plan) (string, error) {
doc := strings.TrimSpace(info.Info)
// if this is a url, then we download that and try to get a new doc with the real info
if _, err := url.Parse(doc); err == nil {
tmpDir, err := os.MkdirTemp("", "upgrade-manager-reference")
if err != nil {
return "", fmt.Errorf("create tempdir for reference file: %w", err)
}
defer os.RemoveAll(tmpDir)
refPath := filepath.Join(tmpDir, "ref")
if err := getter.GetFile(refPath, doc); err != nil {
return "", fmt.Errorf("downloading reference link %s: %w", doc, err)
}
refBytes, err := os.ReadFile(refPath)
if err != nil {
return "", fmt.Errorf("reading downloaded reference: %w", err)
}
// if download worked properly, then we use this new file as the binary map to parse
doc = string(refBytes)
}
// check if it is the upgrade config
var config UpgradeConfig
if err := json.Unmarshal([]byte(doc), &config); err == nil {
url, ok := config.Binaries[OSArch()]
if !ok {
url, ok = config.Binaries["any"]
}
if !ok {
return "", fmt.Errorf("cannot find binary for os/arch: neither %s, nor any", OSArch())
}
return url, nil
}
return "", errors.New("upgrade info doesn't contain binary map")
return url, nil
}
func OSArch() string {
return fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH)
}
// EnsureBinary ensures the file exists and is executable, or returns an error
func EnsureBinary(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("cannot stat dir %s: %w", path, err)
}
if !info.Mode().IsRegular() {
return fmt.Errorf("%s is not a regular file", info.Name())
}
// this checks if the world-executable bit is set (we cannot check owner easily)
exec := info.Mode().Perm() & 0o001
if exec == 0 {
return fmt.Errorf("%s is not world executable", info.Name())
}
return nil
}

View File

@ -4,9 +4,7 @@
package cosmovisor_test
import (
"errors"
"fmt"
"net"
"os"
"path/filepath"
"strings"
@ -18,7 +16,7 @@ import (
"github.com/stretchr/testify/suite"
"cosmossdk.io/tools/cosmovisor"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
type upgradeTestSuite struct {
@ -39,7 +37,7 @@ func (s *upgradeTestSuite) TestCurrentBin() {
s.Require().Equal(cfg.GenesisBin(), currentBin)
// ensure we cannot set this to an invalid value
for _, name := range []string{"missing", "nobin", "noexec"} {
for _, name := range []string{"missing", "nobin"} {
s.Require().Error(cfg.SetCurrentUpgrade(upgradetypes.Plan{Name: name}), name)
currentBin, err := cfg.CurrentBin()
@ -103,7 +101,7 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() {
s.Require().Equal(cfg.GenesisBin(), currentBin)
// do upgrade ignores bad files
for _, name := range []string{"missing", "nobin", "noexec"} {
for _, name := range []string{"missing", "nobin"} {
info := upgradetypes.Plan{Name: name}
err = cosmovisor.UpgradeBinary(logger, cfg, info)
s.Require().Error(err, name)
@ -127,97 +125,14 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() {
}
}
func (s *upgradeTestSuite) TestOsArch() {
// all download tests will fail if we are not on linux...
s.Require().Equal("linux/amd64", cosmovisor.OSArch())
}
func (s *upgradeTestSuite) TestUpgradeBinary() {
logger := cosmovisor.NewLogger()
func (s *upgradeTestSuite) TestGetDownloadURL() {
// all download tests will fail if we are not on linux...
ref, err := filepath.Abs(filepath.FromSlash("./testdata/repo/ref_to_chain3-zip_dir.json"))
s.Require().NoError(err)
badref, err := filepath.Abs(filepath.FromSlash("./testdata/repo/chain2-zip_bin/autod.zip")) // "./testdata/repo/zip_binary/autod.zip"))
s.Require().NoError(err)
cases := map[string]struct {
info string
url string
err interface{}
// If err == nil, the test must not report an error.
// If err is a string, the test must report an error whose string has err
// as a substring.
// If err is a func(suite.Suite, error), it is called to check the error
// value.
}{
"missing": {
err: "downloading reference link : invalid source string:",
},
"follow reference": {
info: ref,
url: "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
},
"malformated reference target": {
info: badref,
err: "upgrade info doesn't contain binary map",
},
"missing link": {
info: "https://no.such.domain/exists.txt",
err: func(s suite.Suite, err error) {
var dns *net.DNSError
s.Require().True(errors.As(err, &dns), "result is not a DNSError")
s.Require().Equal("no.such.domain", dns.Name)
s.Require().Equal(true, dns.IsNotFound)
},
},
"proper binary": {
info: `{"binaries": {"linux/amd64": "https://foo.bar/", "windows/amd64": "https://something.else"}}`,
url: "https://foo.bar/",
},
"any architecture not used": {
info: `{"binaries": {"linux/amd64": "https://foo.bar/", "*": "https://something.else"}}`,
url: "https://foo.bar/",
},
"any architecture used": {
info: `{"binaries": {"linux/arm": "https://foo.bar/arm-only", "any": "https://foo.bar/portable"}}`,
url: "https://foo.bar/portable",
},
"missing binary": {
info: `{"binaries": {"linux/arm": "https://foo.bar/"}}`,
err: "cannot find binary for",
},
}
for name, tc := range cases {
s.Run(name, func() {
url, err := cosmovisor.GetDownloadURL(upgradetypes.Plan{Info: tc.info})
switch e := tc.err.(type) {
case nil:
s.Require().NoError(err)
s.Require().Equal(tc.url, url)
case string:
s.Require().Error(err)
s.Require().Contains(err.Error(), tc.err)
case func(suite.Suite, error):
e(s.Suite, err)
}
})
}
}
func (s *upgradeTestSuite) TestDownloadBinary() {
cases := map[string]struct {
url string
canDownload bool
validBinary bool
}{
"get raw binary": {
url: "./testdata/repo/raw_binary/autod",
canDownload: true,
validBinary: true,
},
"get raw binary with checksum": {
// sha256sum ./testdata/repo/raw_binary/autod
url: "./testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d",
@ -228,11 +143,6 @@ func (s *upgradeTestSuite) TestDownloadBinary() {
url: "./testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
canDownload: false,
},
"get zipped directory": {
url: "./testdata/repo/chain3-zip_dir/autod.zip",
canDownload: true,
validBinary: true,
},
"get zipped directory with valid checksum": {
// sha256sum ./testdata/repo/chain3-zip_dir/autod.zip
url: "./testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
@ -244,9 +154,14 @@ func (s *upgradeTestSuite) TestDownloadBinary() {
canDownload: false,
},
"invalid url": {
url: "./testdata/repo/bad_dir/autod",
url: "./testdata/repo/bad_dir/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
canDownload: false,
},
"valid remote": {
url: "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
canDownload: true,
validBinary: true,
},
}
for label, tc := range cases {
@ -267,27 +182,26 @@ func (s *upgradeTestSuite) TestDownloadBinary() {
s.Require().NoError(err)
}
const upgrade = "amazonas"
info := upgradetypes.Plan{
Name: upgrade,
plan := upgradetypes.Plan{
Name: "amazonas",
Info: fmt.Sprintf(`{"binaries":{"%s": "%s"}}`, cosmovisor.OSArch(), url),
}
err = cosmovisor.DownloadBinary(cfg, info)
err = cosmovisor.UpgradeBinary(logger, cfg, plan)
if !tc.canDownload {
s.Require().Error(err)
} else {
s.Require().NoError(err)
}
err = cosmovisor.EnsureBinary(cfg.UpgradeBin(upgrade))
if tc.validBinary {
s.Require().NoError(err)
}
})
}
}
func (s *upgradeTestSuite) TestOsArch() {
// all download tests will fail if we are not on linux...
s.Require().Equal("linux/amd64", cosmovisor.OSArch())
}
// copyTestData will make a tempdir and then
// "cp -r" a subdirectory under testdata there
// returns the directory (which can now be used as Config.Home) and modified safely

View File

@ -111,7 +111,7 @@ func NewCmdSubmitUpgradeProposal() *cobra.Command {
cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen")
cmd.Flags().String(FlagUpgradeInfo, "", "Info for the upgrade plan such as new version download urls, etc.")
cmd.Flags().Bool(FlagNoValidate, false, "Skip validation of the upgrade info")
cmd.Flags().Bool(FlagNoValidate, false, "Skip validation of the upgrade info (dangerous!)")
cmd.Flags().String(FlagDaemonName, getDefaultDaemonName(), "The name of the executable being upgraded (for upgrade-info validation). Default is the DAEMON_NAME env var if set, or else this executable")
cmd.Flags().String(FlagAuthority, "", "The address of the upgrade module authority (defaults to gov)")