Skip to content
Open
Show file tree
Hide file tree
Changes from 99 commits
Commits
Show all changes
119 commits
Select commit Hold shift + click to select a range
09fe843
fix(cosmovisor): make manual upgrades use halt-height
aaronc May 14, 2025
4087e2b
WIP on mock-node
aaronc May 15, 2025
5689795
WIP on watcher
aaronc May 16, 2025
c489524
poll watcher tests pass
aaronc May 21, 2025
2277ffb
data watcher tests
aaronc May 21, 2025
f0f5caf
notes
aaronc May 22, 2025
3f628b9
use common logic for add-batch-upgrade and add-upgrade
aaronc May 22, 2025
19858b3
refactoring watchers
aaronc May 22, 2025
496bba8
watcher initialization
aaronc May 22, 2025
fc37590
WIP on checkers
aaronc May 27, 2025
14124ea
WIP
aaronc May 28, 2025
7177cbb
working mock node
aaronc May 28, 2025
37f7c9f
switch to jsonpb
aaronc May 28, 2025
5a01495
WIP on refactoring
aaronc May 28, 2025
71c03a9
WIP on watchers
aaronc May 28, 2025
37ae97a
WIP
aaronc May 29, 2025
4745c63
WIP on state machine
aaronc May 29, 2025
ddc0e33
WIP on state machine diagrams
aaronc May 30, 2025
4953995
WIP on state machine
aaronc May 30, 2025
522f5a4
WIP on state machine
aaronc May 30, 2025
5622f47
simplify state machine
aaronc May 30, 2025
07545e8
WIP on runner
aaronc May 30, 2025
090cdfa
WIP on runner
aaronc May 30, 2025
869406b
WIP on runner
aaronc May 30, 2025
20fb950
WIP on runner
aaronc May 30, 2025
0681722
WIP on runner
aaronc May 30, 2025
3100d72
revert
aaronc May 30, 2025
d9ec079
WIP on test setup and run implementation
aaronc Jun 2, 2025
489b234
WIP on runner and test setup
aaronc Jun 2, 2025
be564e6
testing manual upgrades
aaronc Jun 2, 2025
cc3082f
WIP on upgrade flow
aaronc Jun 2, 2025
b7c813f
WIP on upgrade restart flow
aaronc Jun 2, 2025
8e83e45
WIP on upgrade restart flow
aaronc Jun 2, 2025
ac2e934
WIP on upgrade restart flow
aaronc Jun 2, 2025
a53bc4d
WIP on testing setup
aaronc Jun 3, 2025
b1b7eb4
WIP on testing setup
aaronc Jun 3, 2025
291e00f
fixes from testing
aaronc Jun 3, 2025
9445e42
WIP on testing
aaronc Jun 4, 2025
c700952
manual upgrade detection works
aaronc Jun 4, 2025
6a2f118
successful tests so far
aaronc Jun 4, 2025
cc0bbe6
working manual upgrade swapping
aaronc Jun 4, 2025
f0dac99
full test passes
aaronc Jun 4, 2025
100f756
WIP on more test conditions
aaronc Jun 4, 2025
68644ad
WIP on correct batch upgrade processing
aaronc Jun 5, 2025
0b87586
most upgrade tests working, shutdown isn't
aaronc Jun 5, 2025
427c526
shutdown works with some sleep time
aaronc Jun 5, 2025
bd0c895
integrate backoff manager
aaronc Jun 6, 2025
5a03e7c
backoff logging
aaronc Jun 6, 2025
638c317
delete refactored code, fix tests
aaronc Jun 6, 2025
990aeaf
migrate most existing tests, add backoff retry count
aaronc Jun 6, 2025
16e7a4b
existing tests migrated
aaronc Jun 6, 2025
fba2bac
remove dead test code
aaronc Jun 9, 2025
6cd1227
switch to known error for signaling upgrade completion
aaronc Jun 9, 2025
54d625b
simplify test setup
aaronc Jun 9, 2025
cb624f8
fix tests
aaronc Jun 9, 2025
d0d6e66
comments
aaronc Jun 9, 2025
d9affb9
add additional notes
aaronc Jun 9, 2025
94675f9
switch to just logging watcher errors
aaronc Jun 9, 2025
83ef052
switch to ErrorHandler interface
aaronc Jun 9, 2025
c9d0e11
sniffing for /block or /v1/block
aaronc Jun 9, 2025
7b128d2
refactor file deletion
aaronc Jun 9, 2025
b429d86
test case with node shutting down on upgrade
aaronc Jun 9, 2025
7d3c0a3
add additional test
aaronc Jun 9, 2025
7a6ece7
include test to check both json encodings work
aaronc Jun 9, 2025
85b75bd
make more code internal
aaronc Jun 10, 2025
06f8c53
update logs, cleanup
aaronc Jun 12, 2025
f971fa2
refactor show manual upgrades command, make config loading more consi…
aaronc Jun 12, 2025
e595bb9
document manual upgrade behavior
aaronc Jun 12, 2025
947c5f8
WIP on cosmovisor system tests
aaronc Jun 12, 2025
e1ccecc
WIP on cosmovisor system tests
aaronc Jun 12, 2025
8cbf93b
fix NPE error
aaronc Jun 13, 2025
822be0e
Merge branch 'main' of github.com:cosmos/cosmos-sdk into aaronc/22731…
aaronc Jun 13, 2025
8ec6925
create separate cosmovisor systemtest
aaronc Jun 13, 2025
f9f5c62
logging fixes
aaronc Jun 13, 2025
e4e3feb
WIP on cosmovisor system tests
aaronc Jun 13, 2025
41be9b0
WIP on cosmovisor system tests
aaronc Jun 13, 2025
2ffffde
working cosmovisor system tests
aaronc Jun 16, 2025
f190de8
only start height watcher if we have a halt height set
aaronc Jun 24, 2025
896202f
remove completed TODOs
aaronc Jun 24, 2025
1e7cc72
update system test make task to include cosmovisor
aaronc Jun 25, 2025
2577a32
WIP on adding some non-determinism during manual upgrade
aaronc Jun 25, 2025
3175579
add a proper manual upgrade test
aaronc Jun 26, 2025
b8a4948
fix test
aaronc Jun 26, 2025
36fe230
don't delete upgrade-info.json file, instead check that the upgrade n…
aaronc Jun 26, 2025
2b82656
rename
aaronc Jun 30, 2025
0c5ad53
move shutdown go routine to run because that's where it's really rele…
aaronc Jun 30, 2025
0d07bce
address TODOs
aaronc Jun 30, 2025
9cbc260
remove TODO
aaronc Jun 30, 2025
f93a7cb
fix scanner_test.go
aaronc Jun 30, 2025
c9fb18f
fix bug
aaronc Jun 30, 2025
1aac5d1
refactor process runner to better handle cases where process could ha…
aaronc Jun 30, 2025
03e7c72
add TODOs
aaronc Jun 30, 2025
7d2d1c6
move everything to v2
aaronc Jun 30, 2025
726288e
update CHANGELOG.md
aaronc Jun 30, 2025
7cd0292
Merge branch 'main' of github.com:cosmos/cosmos-sdk into aaronc/22731…
aaronc Jun 30, 2025
b7721c8
go mod tidy
aaronc Jun 30, 2025
28e2e68
update CHANGELOG.md's
aaronc Jun 30, 2025
9cd07c0
check cosmovisor symlinks, confirm upgrade info readable
aaronc Jun 30, 2025
3122f9e
switch to pointer
aaronc Jun 30, 2025
c0efa1d
add comments
aaronc Jun 30, 2025
53032d4
revert CHANGELOG.md reformatting, remove old RELEASE_NOTES.md
aaronc Jun 30, 2025
2b3a5d7
add more state breakage to manual upgrade
aaronc Jun 30, 2025
42fb734
fix x/upgrade tests
aaronc Jun 30, 2025
1ffada5
fix systemtests
aaronc Jun 30, 2025
1ea885b
lint-fix, go mod tidy, cleanup
aaronc Jun 30, 2025
c005143
check that upgrade handlers are called
aaronc Jun 30, 2025
1cf11cd
only set env when we're using cosmovisor
aaronc Jun 30, 2025
0183068
Update tools/cosmovisor/internal/watchers/fsnotify_watcher.go
aaronc Jul 1, 2025
61ad5e9
fix code suggestion
aaronc Jul 1, 2025
f08259a
fix test isolation
aaronc Jul 7, 2025
dfbcffd
Merge branch 'main' into aaronc/22731-cosmovisor-fixes
aaronc Jul 7, 2025
9113c9d
isolate all system tests
aaronc Jul 7, 2025
49f0ea7
Merge remote-tracking branch 'origin/aaronc/22731-cosmovisor-fixes' i…
aaronc Jul 7, 2025
be003aa
go mod tidy
aaronc Jul 7, 2025
e2b2fce
attempt to fix cosmovisor tests
aaronc Jul 7, 2025
03c5cac
fix comments
aaronc Jul 7, 2025
857e809
fail on unexpected callback count
aaronc Jul 7, 2025
a92aba1
attempt to fix cosmovisor tests
aaronc Jul 7, 2025
0fac9ff
attempt to fix cosmovisor tests
aaronc Jul 7, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -505,13 +505,21 @@ localnet-debug: localnet-stop localnet-build-dlv localnet-build-nodes

.PHONY: localnet-start localnet-stop localnet-debug localnet-build-env localnet-build-dlv localnet-build-nodes

test-system: build-v53 build
# build-system-test-current builds the binaries necessary for running system tests, but only those on the current branch
# this is useful if you are iterating on tests which rely on changes to the current branch only (which is most common in development)
build-system-test-current: build cosmovisor
mkdir -p ./tests/systemtests/binaries/
cp $(BUILDDIR)/simd ./tests/systemtests/binaries/
cp tools/cosmovisor/cosmovisor ./tests/systemtests/binaries/

# build-system-test builds the binaries necessary for runnings system tests and places them in the correct locations
build-system-test: build-v53
mkdir -p ./tests/systemtests/binaries/v0.53
mv $(BUILDDIR)/simdv53 ./tests/systemtests/binaries/v0.53/simd

test-system: build-system-test
$(MAKE) -C tests/systemtests test
.PHONY: test-system
.PHONY: build-system-test-current build-system-test test-system
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Build targets look good, but investigate system test failures.

The new build targets are well-structured and properly handle binary preparation for system tests. However, there's a pipeline failure mentioned for system tests.

Run the following script to investigate the system test failures:


🏁 Script executed:

#!/bin/bash
# Description: Check system test setup and identify potential issues

# Check if the system test directory structure exists
fd -t d systemtests

# Check if the test files exist and are executable
fd -t f -e go . tests/systemtests/

# Verify the binaries directory structure after build
echo "Checking expected binary locations..."
ls -la tests/systemtests/binaries/ 2>/dev/null || echo "Binaries directory not found"

Length of output: 493


Fix build-system-test to include current branch binaries

The test-system target depends on build-system-test, which only invokes build-v53 and never creates the root tests/systemtests/binaries/ directory or copies the current branch simd and cosmovisor binaries. As your verification showed, the binaries/ folder is missing, causing system tests to fail.

· Makefile (lines 508–522): update build-system-test dependencies
· Ensure both current and v0.53 binaries are prepared before running tests

Proposed diff:

- build-system-test: build-v53
+ build-system-test: build-system-test-current build-v53

This change makes build-system-test first run build-system-test-current (creating binaries/ and copying simd & cosmovisor), then produce the v0.53 binary, so all tests have the expected binaries available.

🧰 Tools
🪛 GitHub Actions: System Tests

[error] Make target 'test-system' failed with exit code 2 due to system test failures.

🤖 Prompt for AI Agents
In the Makefile around lines 508 to 522, the build-system-test target currently
only runs build-v53 and does not prepare the binaries directory or copy the
current branch binaries, causing system tests to fail. To fix this, modify
build-system-test to first depend on or invoke build-system-test-current so that
the binaries directory is created and current branch binaries (simd and
cosmovisor) are copied, then proceed with building v0.53 binaries. This ensures
all necessary binaries are available before running tests.


# build-v53 checks out the v0.53.x branch, builds the binary, and renames it to simdv53.
build-v53:
Expand Down
21 changes: 21 additions & 0 deletions simapp/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package simapp

import (
"context"
"os"
"strconv"

storetypes "cosmossdk.io/store/types"

Expand Down Expand Up @@ -31,6 +33,25 @@ func (app SimApp) RegisterUpgradeHandlers() {
if err != nil {
panic(err)
}
if upgradeInfo.Name != "" {
app.Logger().Info("read upgrade info from disk", "upgrade_info", upgradeInfo)
}

// this allows us to check migration to v0.54.x in the system tests via a manual (non-governance upgrade)
if manualUpgrade, ok := os.LookupEnv("SIMAPP_MANUAL_UPGRADE_HEIGHT"); ok {
height, err := strconv.ParseUint(manualUpgrade, 10, 64)
if err != nil {
panic("invalid SIMAPP_MANUAL_UPGRADE_HEIGHT height: " + err.Error())
}
upgradeInfo = upgradetypes.Plan{
Name: UpgradeName,
Height: int64(height),
}
err = app.UpgradeKeeper.SetManualUpgrade(&upgradeInfo)
if err != nil {
panic("failed to set manual upgrade: " + err.Error())
}
}

if upgradeInfo.Name == UpgradeName && !app.UpgradeKeeper.IsSkipHeight(upgradeInfo.Height) {
storeUpgrades := storetypes.StoreUpgrades{
Expand Down
70 changes: 66 additions & 4 deletions systemtests/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,23 @@
MustCopyFilesInDir(src, dest)
}

func (s *SystemUnderTest) StartChain(t *testing.T, xargs ...string) {

Check failure on line 176 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / golangci-lint

test helper function should start from t.Helper() (thelper)

Check failure on line 176 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / Analyze

test helper function should start from t.Helper() (thelper)
s.doStartChain(t, false, xargs...)
}

func (s *SystemUnderTest) StartChainWithCosmovisor(t *testing.T, xargs ...string) {

Check failure on line 180 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / golangci-lint

test helper function should start from t.Helper() (thelper)

Check failure on line 180 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / Analyze

test helper function should start from t.Helper() (thelper)
s.doStartChain(t, true, xargs...)
}

func (s *SystemUnderTest) doStartChain(t *testing.T, useCosmovisor bool, xargs ...string) {
t.Helper()
if useCosmovisor {
s.initCosmovisor(t)
}
s.Log("Start chain\n")
s.ChainStarted = true
// HACK: force db_backend
s.startNodesAsync(t, append([]string{"start", "--log_level=info", "--log_no_color", "--db_backend=goleveldb"}, xargs...)...)
s.startNodesAsync(t, useCosmovisor, append([]string{"start", "--log_level=info", "--log_no_color", "--db_backend=goleveldb"}, xargs...)...)

s.AwaitNodeUp(t, s.rpcAddr)

Expand All @@ -195,6 +206,43 @@
s.AwaitNextBlock(t, 10e9)
}

func (s *SystemUnderTest) cosmovisorEnv(t *testing.T, home string) []string {

Check failure on line 209 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / golangci-lint

test helper function should start from t.Helper() (thelper)

Check failure on line 209 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / Analyze

test helper function should start from t.Helper() (thelper)
absHome, err := filepath.Abs(home)
require.NoError(t, err)
return []string{
fmt.Sprintf("DAEMON_HOME=%s", absHome),
fmt.Sprintf("DAEMON_NAME=%s", s.projectName),
}
}

func (s *SystemUnderTest) cosmovisorPath() string {
return filepath.Join(WorkDir, "binaries", "cosmovisor")
}

Check failure on line 220 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not properly formatted (gofumpt)

Check failure on line 220 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / Analyze

File is not properly formatted (gofumpt)
func (s *SystemUnderTest) ExecCosmovisor(t *testing.T, async bool, args ...string) {

Check failure on line 221 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / golangci-lint

test helper function should start from t.Helper() (thelper)

Check failure on line 221 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / Analyze

test helper function should start from t.Helper() (thelper)
s.withEachNodeHome(func(i int, home string) {
env := s.cosmovisorEnv(t, home)
t.Logf("Calling Cosmovisor with args %+v and env %+v", args, env)
cmd := exec.Command(

Check failure on line 225 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / golangci-lint

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)

Check failure on line 225 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / Analyze

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)
s.cosmovisorPath(),
args...,
)
Comment on lines +232 to +235

Check failure

Code scanning / gosec

Subprocess launched with variable Error

Subprocess launched with a potential tainted input or cmd arguments
cmd.Dir = WorkDir
env = append(env, "COSMOVISOR_COLOR_LOGS=false")
cmd.Env = env
if async {
require.NoError(t, cmd.Start(), "cosmovisor init %d", i)
s.awaitProcessCleanup(cmd)
} else {
require.NoError(t, cmd.Run(), "cosmovisor init %d", i)
}
})
}

func (s *SystemUnderTest) initCosmovisor(t *testing.T) {
binary := locateExecutable(s.execBinary)
s.ExecCosmovisor(t, false, "init", binary)
}

// MarkDirty whole chain will be reset when marked dirty
func (s *SystemUnderTest) MarkDirty() {
s.dirty = true
Expand Down Expand Up @@ -591,16 +639,30 @@
}

// startNodesAsync runs the given app cli command for all cluster nodes and returns without waiting
func (s *SystemUnderTest) startNodesAsync(t *testing.T, xargs ...string) {
func (s *SystemUnderTest) startNodesAsync(t *testing.T, useCosmovisor bool, xargs ...string) {
t.Helper()
s.withEachNodeHome(func(i int, home string) {
args := append(xargs, "--home="+home)
absHome, err := filepath.Abs(home)
require.NoError(t, err, "failed to get absolute home path")
args := append(xargs, "--home="+absHome)
var binary string
var env []string
if useCosmovisor {
binary = s.cosmovisorPath()
args = append([]string{"run"}, args...) // cosmovisor run <args>
cfgPath := filepath.Join(absHome, "cosmovisor", "config.toml")
args = append(args, "--cosmovisor-config", cfgPath)
env = s.cosmovisorEnv(t, absHome)
} else {
binary = locateExecutable(s.execBinary)
}
s.Logf("Execute `%s %s`\n", s.execBinary, strings.Join(args, " "))
cmd := exec.Command( //nolint:gosec // used by tests only

Check failure on line 660 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / golangci-lint

directive `//nolint:gosec // used by tests only` is unused for linter "gosec" (nolintlint)

Check failure on line 660 in systemtests/system.go

View workflow job for this annotation

GitHub Actions / Analyze

directive `//nolint:gosec // used by tests only` is unused for linter "gosec" (nolintlint)
locateExecutable(s.execBinary),
binary,
args...,
)
cmd.Dir = WorkDir
cmd.Env = env
s.watchLogs(i, cmd)
require.NoError(t, cmd.Start(), "node %d", i)
s.Logf("Node started: %d\n", cmd.Process.Pid)
Expand Down
180 changes: 180 additions & 0 deletions tests/systemtests/cosmovisor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
//go:build system_test

package systemtests

import (
"fmt"
"os"
"path/filepath"
"regexp"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"

systest "cosmossdk.io/systemtests"

Check failure on line 16 in tests/systemtests/cosmovisor_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

File is not properly formatted (gci)

Check failure on line 16 in tests/systemtests/cosmovisor_test.go

View workflow job for this annotation

GitHub Actions / Analyze

File is not properly formatted (gci)
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)

func TestCosmovisorUpgrade(t *testing.T) {
t.Run("gov upgrade, then manual upgrade", func(t *testing.T) {
const (
upgrade1Height = 25
upgrade1Name = "v053-to-v054" // must match UpgradeName in simapp/upgrades.go
upgrade2Height int64 = 30
upgrade2Name = "manual1"
)

// Scenario:
// start a legacy chain with some state
// when a chain upgrade proposal is executed
// then the chain upgrades successfully
systest.Sut.StopChain()

currentBranchBinary := systest.Sut.ExecBinary()

legacyBinary := systest.WorkDir + "/binaries/v0.53/simd"
systest.Sut.SetExecBinary(legacyBinary)
systest.Sut.SetupChain()

votingPeriod := 5 * time.Second // enough time to vote
systest.Sut.ModifyGenesisJSON(t, systest.SetGovVotingPeriod(t, votingPeriod))

systest.Sut.StartChainWithCosmovisor(t)

cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
govAddr := sdk.AccAddress(address.Module("gov")).String()
// submit upgrade proposal
proposal := fmt.Sprintf(`
{
"messages": [
{
"@type": "/cosmos.upgrade.v1beta1.MsgSoftwareUpgrade",
"authority": %q,
"plan": {
"name": %q,
"height": "%d"
}
}
],
"metadata": "ipfs://CID",
"deposit": "100000000stake",
"title": "my upgrade",
"summary": "testing"
}`, govAddr, upgrade1Name, upgrade1Height)
proposalID := cli.SubmitAndVoteGovProposal(proposal)

// add binary for gov upgrade
systest.Sut.ExecCosmovisor(
t,
true,
"add-upgrade",
upgrade1Name,
currentBranchBinary,
)

Comment on lines +76 to +83
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation after adding the upgrade binary.

The test doesn't verify that the upgrade binary was successfully added to Cosmovisor. This could lead to silent failures.

 // add binary for gov upgrade
 systest.Sut.ExecCosmovisor(
     t,
-    true,
+    false, // run synchronously to check for errors
     "add-upgrade",
     upgrade1Name,
     currentBranchBinary,
 )
+// Verify the upgrade was added
+upgradePath := filepath.Join(systest.WorkDir, systest.Sut.NodeDir(0), "cosmovisor", "upgrades", upgrade1Name, "bin")
+_, err := os.Stat(upgradePath)
+require.NoError(t, err, "upgrade binary should exist after add-upgrade")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
systest.Sut.ExecCosmovisor(
t,
true,
"add-upgrade",
upgrade1Name,
currentBranchBinary,
)
// add binary for gov upgrade
systest.Sut.ExecCosmovisor(
t,
false, // run synchronously to check for errors
"add-upgrade",
upgrade1Name,
currentBranchBinary,
)
// Verify the upgrade was added
upgradePath := filepath.Join(
systest.WorkDir,
systest.Sut.NodeDir(0),
"cosmovisor",
"upgrades",
upgrade1Name,
"bin",
)
_, err := os.Stat(upgradePath)
require.NoError(t, err, "upgrade binary should exist after add-upgrade")
🤖 Prompt for AI Agents
In tests/systemtests/cosmovisor_test.go around lines 70 to 77, after calling
ExecCosmovisor to add the upgrade binary, add validation code to confirm the
upgrade binary was successfully added to Cosmovisor. This can be done by
checking the expected state or output that indicates the binary is registered
correctly, ensuring the test does not silently pass without verifying the
addition.

requireCurrentPointsTo(t, "genesis")

systest.Sut.AwaitBlockHeight(t, 21, 60*time.Second)

t.Logf("current_height: %d\n", systest.Sut.CurrentHeight())
raw := cli.CustomQuery("q", "gov", "proposal", proposalID)
proposalStatus := gjson.Get(raw, "proposal.status").String()
require.Equal(t, "PROPOSAL_STATUS_PASSED", proposalStatus, raw)

// add manual upgrade
systest.Sut.ExecCosmovisor(
t,
true,
"add-upgrade",
upgrade2Name,
currentBranchBinary,
fmt.Sprintf("--upgrade-height=%d", upgrade2Height),
)

systest.Sut.AwaitBlockHeight(t, upgrade1Height+1)

requireCurrentPointsTo(t, fmt.Sprintf("upgrades/%s", upgrade1Name))
// make sure a gov upgrade was triggered
regex, err := regexp.Compile(fmt.Sprintf(`UPGRADE %q NEEDED at height: %d: module=x/upgrade`,
upgrade1Name, upgrade1Height))
require.NoError(t, err)
require.Equal(t, systest.Sut.NodesCount(), systest.Sut.FindLogMessage(regex))
// make sure the upgrade-info.json was readable by nodes when they restarted
regex, err = regexp.Compile("read upgrade info from disk")
require.NoError(t, err)
require.Equal(t, systest.Sut.NodesCount(), systest.Sut.FindLogMessage(regex))

systest.Sut.AwaitBlockHeight(t, upgrade2Height+1)

requireCurrentPointsTo(t, fmt.Sprintf("upgrades/%s", upgrade2Name))

// smoke test that new version runs
cli = systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
got := cli.Run("tx", "protocolpool", "fund-community-pool", "100stake", "--from=node0")
systest.RequireTxSuccess(t, got)
})

t.Run("manual upgrade", func(t *testing.T) {
const (
upgradeHeight = 10
upgradeName = "v053-to-v054" // must match UpgradeName in simapp/upgrades.go
)
// Scenario:
// start a legacy chain with some state
// when a chain upgrade proposal is executed
// then the chain upgrades successfully
systest.Sut.StopChain()

currentBranchBinary := systest.Sut.ExecBinary()

legacyBinary := systest.WorkDir + "/binaries/v0.53/simd"
systest.Sut.SetExecBinary(legacyBinary)
systest.Sut.SetupChain()

systest.Sut.StartChainWithCosmovisor(t)
requireCurrentPointsTo(t, "genesis")

// we create a wrapper for the current branch binary which sets the
// SIMAPP_MANUAL_UPGRADE_HEIGHT which will cause the upgrade to be applied manually
wrapperTxt := fmt.Sprintf(`#!/usr/bin/env bash
set -e
SIMAPP_MANUAL_UPGRADE_HEIGHT="%d" exec %s "$@"`, upgradeHeight, currentBranchBinary)
wrapperPath := filepath.Join(systest.WorkDir, "testnet", fmt.Sprintf("%s.sh", upgradeName))
wrapperPath, err := filepath.Abs(wrapperPath)
require.NoError(t, err, "failed to get absolute path for manual upgrade script")
err = os.WriteFile(wrapperPath, []byte(wrapperTxt), 0o755)

Check failure on line 148 in tests/systemtests/cosmovisor_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

G306: Expect WriteFile permissions to be 0600 or less (gosec)

Check failure on line 148 in tests/systemtests/cosmovisor_test.go

View workflow job for this annotation

GitHub Actions / Analyze

G306: Expect WriteFile permissions to be 0600 or less (gosec)
require.NoError(t, err, "failed to write manual upgrade script")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential shell injection vulnerability in wrapper script.

The shell script generation uses string interpolation without proper escaping, which could lead to command injection if paths contain special characters.

-wrapperTxt := fmt.Sprintf(`#!/usr/bin/env bash
-set -e
-SIMAPP_MANUAL_UPGRADE_HEIGHT="%d" exec %s "$@"`, upgradeHeight, currentBranchBinary)
+import "strconv"
+import "strings"
+
+// Escape the binary path for shell safety
+escapedBinary := strconv.Quote(currentBranchBinary)
+wrapperTxt := fmt.Sprintf(`#!/usr/bin/env bash
+set -e
+SIMAPP_MANUAL_UPGRADE_HEIGHT="%d" exec %s "$@"`, upgradeHeight, escapedBinary)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In tests/systemtests/cosmovisor_test.go around lines 142 to 149, the shell
script is generated using fmt.Sprintf with unescaped variables, which risks
shell injection if upgradeHeight or currentBranchBinary contain special
characters. To fix this, properly escape or sanitize these variables before
including them in the script string, or use a safer method to construct the
script that avoids direct string interpolation of untrusted input.


// schedule manual upgrade to latest version
systest.Sut.ExecCosmovisor(
t,
true,
"add-upgrade",
upgradeName,
wrapperPath,
fmt.Sprintf("--upgrade-height=%d", upgradeHeight),
)

systest.Sut.AwaitBlockHeight(t, upgradeHeight+1, 60*time.Second)

requireCurrentPointsTo(t, fmt.Sprintf("upgrades/%s", upgradeName))

// smoke test that new version runs
cli := systest.NewCLIWrapper(t, systest.Sut, systest.Verbose)
got := cli.Run("tx", "protocolpool", "fund-community-pool", "100stake", "--from=node0")
systest.RequireTxSuccess(t, got)
})
}

func requireCurrentPointsTo(t *testing.T, expected string) {
t.Helper()
for i := 0; i < systest.Sut.NodesCount(); i++ {
curSymLink := filepath.Join(systest.Sut.NodeDir(i), "cosmovisor", "current")
resolved, err := os.Readlink(curSymLink)
require.NoError(t, err, "failed to read current symlink for node %d", i)
require.Equal(t, expected, resolved, "current symlink for node %d does not point to expected directory", i)
}
}
9 changes: 6 additions & 3 deletions tests/systemtests/upgrade_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@
)

const (
testSeed = "scene learn remember glide apple expand quality spawn property shoe lamp carry upset blossom draft reject aim file trash miss script joy only measure"
upgradeHeight int64 = 22
upgradeName = "v053-to-v054" // must match UpgradeName in simapp/upgrades.go
testSeed = "scene learn remember glide apple expand quality spawn property shoe lamp carry upset blossom draft reject aim file trash miss script joy only measure"

Check failure on line 21 in tests/systemtests/upgrade_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

const testSeed is unused (unused)

Check failure on line 21 in tests/systemtests/upgrade_test.go

View workflow job for this annotation

GitHub Actions / Analyze

const testSeed is unused (unused)
)

func TestChainUpgrade(t *testing.T) {
const (
upgradeHeight int64 = 22
upgradeName = "v053-to-v054" // must match UpgradeName in simapp/upgrades.go
)

// Scenario:
// start a legacy chain with some state
// when a chain upgrade proposal is executed
Expand Down
Loading
Loading