Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add protection against accidental downgrades #10407

Merged
merged 32 commits into from
Jan 25, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
0f65ae5
add handler check in BeginBlock
aleem1314 Oct 19, 2021
7011976
review changes
aleem1314 Nov 3, 2021
9d8e964
add tests
aleem1314 Nov 3, 2021
6f9780e
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Nov 3, 2021
00aa578
review changes
aleem1314 Nov 3, 2021
bbb6259
Merge branch 'aleem/10318-binary-downgrades' of https://github.com/co…
aleem1314 Nov 3, 2021
7f51fac
review changes
aleem1314 Nov 3, 2021
b023d1a
review changes
aleem1314 Nov 9, 2021
ea8b276
review changes
aleem1314 Nov 9, 2021
0bce20c
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Nov 9, 2021
cac3107
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Nov 10, 2021
512a73c
chore: cleanup
aleem1314 Nov 10, 2021
ad8823b
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Nov 10, 2021
23ccc63
chore: add more tests
aleem1314 Nov 11, 2021
cc9fa0a
Merge branch 'aleem/10318-binary-downgrades' of https://github.com/co…
aleem1314 Nov 11, 2021
cf1251a
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Nov 11, 2021
1e8e047
chore: address review changes
aleem1314 Nov 12, 2021
670e2d6
Merge branch 'aleem/10318-binary-downgrades' of https://github.com/co…
aleem1314 Nov 12, 2021
ecd03d3
chore: cleanup
aleem1314 Nov 12, 2021
16fdc29
Merge branch 'master' of https://github.com/cosmos/cosmos-sdk into al…
aleem1314 Nov 12, 2021
1f7cc72
chore: refactor tests to table tests
aleem1314 Nov 17, 2021
665a108
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Nov 17, 2021
9914bfe
chore: add changelog
aleem1314 Nov 17, 2021
5a2f4ae
chore: fix test
aleem1314 Nov 17, 2021
51d7d21
chore: review changes
aleem1314 Nov 19, 2021
3cb9b82
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Nov 24, 2021
42c72a9
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Jan 10, 2022
97fada7
chore: review changes
aleem1314 Jan 10, 2022
6bae3a7
chore: move downgradeVerified to keeper
aleem1314 Jan 12, 2022
1032b33
Merge branch 'master' into aleem/10318-binary-downgrades
robert-zaremba Jan 19, 2022
42c6ac5
Merge branch 'master' into aleem/10318-binary-downgrades
robert-zaremba Jan 19, 2022
4229034
Merge branch 'master' into aleem/10318-binary-downgrades
aleem1314 Jan 25, 2022
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#10311](https://github.com/cosmos/cosmos-sdk/pull/10311) Adds cli to use tips transactions. It adds an `--aux` flag to all CLI tx commands to generate the aux signer data (with optional tip), and a new `tx aux-to-fee` subcommand to let the fee payer gather aux signer data and broadcast the tx
* [\#10430](https://github.com/cosmos/cosmos-sdk/pull/10430) ADR-040: Add store/v2 `MultiStore` implementation
* [\#10947](https://github.com/cosmos/cosmos-sdk/pull/10947) Add `AllowancesByGranter` query to the feegrant module
* [\#10407](https://github.com/cosmos/cosmos-sdk/pull/10407) Add validation to `x/upgrade` module's `BeginBlock` to check accidental binary downgrades

### API Breaking Changes

Expand Down
17 changes: 17 additions & 0 deletions x/upgrade/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,24 @@ import (
// skipUpgradeHeightArray is a set of block heights for which the upgrade must be skipped
func BeginBlocker(k keeper.Keeper, ctx sdk.Context, _ abci.RequestBeginBlock) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyBeginBlocker)

plan, found := k.GetUpgradePlan(ctx)

if !k.DowngradeVerified() {
k.SetDowngradeVerified(true)
lastAppliedPlan, _ := k.GetLastCompletedUpgrade(ctx)
// This check will make sure that we are using a valid binary.
// It'll panic in these cases if there is no upgrade handler registered for the last applied upgrade.
// 1. If there is no scheduled upgrade.
// 2. If the plan is not ready.
// 3. If the plan is ready and skip upgrade height is set for current height.
if !found || !plan.ShouldExecute(ctx) || (plan.ShouldExecute(ctx) && k.IsSkipHeight(ctx.BlockHeight())) {
aleem1314 marked this conversation as resolved.
Show resolved Hide resolved
if lastAppliedPlan != "" && !k.HasHandler(lastAppliedPlan) {
panic(fmt.Sprintf("Wrong app version %d, upgrade handler is missing for %s upgrade plan", ctx.ConsensusParams().Version.AppVersion, lastAppliedPlan))
}
}
}

if !found {
return
}
Expand Down
67 changes: 67 additions & 0 deletions x/upgrade/abci_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,70 @@ func TestDumpUpgradeInfoToFile(t *testing.T) {
err = os.Remove(upgradeInfoFilePath)
require.Nil(err)
}

// TODO: add testcase to for `no upgrade handler is present for last applied upgrade`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

how about this TODO?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is hard to add tests for a few cases without exposing/adding few functions. And we cannot move those tests to internal package (getting import cycles).

func TestBinaryVersion(t *testing.T) {
var skipHeight int64 = 15
s := setupTest(t, 10, map[int64]bool{skipHeight: true})

testCases := []struct {
name string
preRun func() (sdk.Context, abci.RequestBeginBlock)
expectPanic bool
}{
{
"test not panic: no scheduled upgrade or applied upgrade is present",
func() (sdk.Context, abci.RequestBeginBlock) {
req := abci.RequestBeginBlock{Header: s.ctx.BlockHeader()}
return s.ctx, req
},
false,
},
{
"test not panic: upgrade handler is present for last applied upgrade",
func() (sdk.Context, abci.RequestBeginBlock) {
s.keeper.SetUpgradeHandler("test0", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
return vm, nil
})

err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "Upgrade test", Plan: types.Plan{Name: "test0", Height: s.ctx.BlockHeight() + 2}})
require.Nil(t, err)

newCtx := s.ctx.WithBlockHeight(12)
s.keeper.ApplyUpgrade(newCtx, types.Plan{
Name: "test0",
Height: 12,
})

req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
return newCtx, req
},
false,
},
{
"test panic: upgrade needed",
func() (sdk.Context, abci.RequestBeginBlock) {
err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "Upgrade test", Plan: types.Plan{Name: "test2", Height: 13}})
require.Nil(t, err)

newCtx := s.ctx.WithBlockHeight(13)
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
return newCtx, req
},
true,
},
}

for _, tc := range testCases {
ctx, req := tc.preRun()
if tc.expectPanic {
require.Panics(t, func() {
s.module.BeginBlock(ctx, req)
})
} else {
require.NotPanics(t, func() {
s.module.BeginBlock(ctx, req)
})
}
}
}
29 changes: 29 additions & 0 deletions x/upgrade/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/cosmos/cosmos-sdk/types/module"
xp "github.com/cosmos/cosmos-sdk/x/upgrade/exported"
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
Expand All @@ -33,6 +34,7 @@ type Keeper struct {
cdc codec.BinaryCodec // App-wide binary codec
upgradeHandlers map[string]types.UpgradeHandler // map of plan name to upgrade handler
versionSetter xp.ProtocolVersionSetter // implements setting the protocol version field on BaseApp
downgradeVerified bool // tells if we've already sanity checked that this binary version isn't being used against an old state.
}

// NewKeeper constructs an upgrade Keeper which requires the following arguments:
Expand Down Expand Up @@ -228,6 +230,23 @@ func (k Keeper) GetUpgradedConsensusState(ctx sdk.Context, lastHeight int64) ([]
return bz, true
}

// GetLastCompletedUpgrade returns the last applied upgrade name and height.
func (k Keeper) GetLastCompletedUpgrade(ctx sdk.Context) (string, int64) {
aleem1314 marked this conversation as resolved.
Show resolved Hide resolved
iter := sdk.KVStoreReversePrefixIterator(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
defer iter.Close()
if iter.Valid() {
aleem1314 marked this conversation as resolved.
Show resolved Hide resolved
return parseDoneKey(iter.Key()), int64(binary.BigEndian.Uint64(iter.Value()))
}

return "", 0
}

// parseDoneKey - split upgrade name from the done key
func parseDoneKey(key []byte) string {
kv.AssertKeyAtLeastLength(key, 2)
return string(key[1:])
}

// GetDoneHeight returns the height at which the given upgrade was executed
func (k Keeper) GetDoneHeight(ctx sdk.Context, name string) int64 {
store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte{types.DoneByte})
Expand Down Expand Up @@ -389,3 +408,13 @@ func (k Keeper) ReadUpgradeInfoFromDisk() (types.Plan, error) {

return upgradeInfo, nil
}

// SetDowngradeVerified updates downgradeVerified.
func (k *Keeper) SetDowngradeVerified(v bool) {
k.downgradeVerified = v
}

// DowngradeVerified returns downgradeVerified.
func (k Keeper) DowngradeVerified() bool {
return k.downgradeVerified
}
39 changes: 39 additions & 0 deletions x/upgrade/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,45 @@ func (s *KeeperTestSuite) TestMigrations() {
s.Require().Equal(vmBefore["bank"]+1, vm["bank"])
}

func (s *KeeperTestSuite) TestLastCompletedUpgrade() {
keeper := s.app.UpgradeKeeper
require := s.Require()

s.T().Log("verify empty name if applied upgrades are empty")
name, height := keeper.GetLastCompletedUpgrade(s.ctx)
require.Equal("", name)
require.Equal(int64(0), height)

keeper.SetUpgradeHandler("test0", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
return vm, nil
})

keeper.ApplyUpgrade(s.ctx, types.Plan{
Name: "test0",
Height: 10,
})

s.T().Log("verify valid upgrade name and height")
name, height = keeper.GetLastCompletedUpgrade(s.ctx)
require.Equal("test0", name)
require.Equal(int64(10), height)

keeper.SetUpgradeHandler("test1", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
return vm, nil
})

newCtx := s.ctx.WithBlockHeight(15)
keeper.ApplyUpgrade(newCtx, types.Plan{
Name: "test1",
Height: 15,
})

s.T().Log("verify valid upgrade name and height with multiple upgrades")
name, height = keeper.GetLastCompletedUpgrade(newCtx)
require.Equal("test1", name)
require.Equal(int64(15), height)
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}