Merge PR #5367: Regen network/skip upgrade

This commit is contained in:
Anil Kumar Kammari 2020-01-03 20:07:29 +05:30 committed by Alexander Bezobchuk
parent 900502fc21
commit a5fc7d6ba6
12 changed files with 350 additions and 120 deletions

View File

@ -20,15 +20,16 @@ import (
// Tendermint full-node start flags
const (
flagWithTendermint = "with-tendermint"
flagAddress = "address"
flagTraceStore = "trace-store"
flagPruning = "pruning"
flagCPUProfile = "cpu-profile"
FlagMinGasPrices = "minimum-gas-prices"
FlagHaltHeight = "halt-height"
FlagHaltTime = "halt-time"
FlagInterBlockCache = "inter-block-cache"
flagWithTendermint = "with-tendermint"
flagAddress = "address"
flagTraceStore = "trace-store"
flagPruning = "pruning"
flagCPUProfile = "cpu-profile"
FlagMinGasPrices = "minimum-gas-prices"
FlagHaltHeight = "halt-height"
FlagHaltTime = "halt-time"
FlagInterBlockCache = "inter-block-cache"
FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades"
)
// StartCmd runs the service passed in, either stand-alone or in-process with
@ -77,6 +78,7 @@ which accepts a path for the resulting pprof file.
FlagMinGasPrices, "",
"Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)",
)
cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary")
cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching")
@ -130,12 +132,13 @@ func startStandAlone(ctx *Context, appCreator AppCreator) error {
func startInProcess(ctx *Context, appCreator AppCreator) (*node.Node, error) {
cfg := ctx.Config
home := cfg.RootDir
traceWriterFile := viper.GetString(flagTraceStore)
traceWriterFile := viper.GetString(flagTraceStore)
db, err := openDB(home)
if err != nil {
return nil, err
}
traceWriter, err := openTraceWriter(traceWriterFile)
if err != nil {
return nil, err

View File

@ -131,7 +131,7 @@ type SimApp struct {
// NewSimApp returns a reference to an initialized SimApp.
func NewSimApp(
logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, skipUpgradeHeights map[int64]bool,
invCheckPeriod uint, baseAppOptions ...func(*bam.BaseApp),
) *SimApp {
@ -196,7 +196,7 @@ func NewSimApp(
app.CrisisKeeper = crisis.NewKeeper(
app.subspaces[crisis.ModuleName], invCheckPeriod, app.SupplyKeeper, auth.FeeCollectorName,
)
app.UpgradeKeeper = upgrade.NewKeeper(keys[upgrade.StoreKey], app.cdc)
app.UpgradeKeeper = upgrade.NewKeeper(skipUpgradeHeights, keys[upgrade.StoreKey], app.cdc)
// create evidence keeper with router
evidenceKeeper := evidence.NewKeeper(

View File

@ -15,7 +15,7 @@ import (
func TestSimAppExport(t *testing.T) {
db := dbm.NewMemDB()
app := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
app := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, 0)
genesisState := NewDefaultGenesisState()
stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState)
@ -31,7 +31,7 @@ func TestSimAppExport(t *testing.T) {
app.Commit()
// Making a new app object with the db, so that initchain hasn't been called
app2 := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
app2 := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, 0)
_, _, err = app2.ExportAppStateAndValidators(false, []string{})
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
}
@ -39,7 +39,7 @@ func TestSimAppExport(t *testing.T) {
// ensure that black listed addresses are properly set in bank keeper
func TestBlackListedAddrs(t *testing.T) {
db := dbm.NewMemDB()
app := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
app := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, 0)
for acc := range maccPerms {
require.Equal(t, !allowedReceivingModAcc[acc], app.BankKeeper.BlacklistedAddr(app.SupplyKeeper.GetModuleAddress(acc)))

View File

@ -25,7 +25,7 @@ func BenchmarkFullAppSimulation(b *testing.B) {
}
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())
app := NewSimApp(logger, db, nil, true, map[int64]bool{}, FlagPeriodValue, interBlockCacheOpt())
// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
@ -64,7 +64,7 @@ func BenchmarkInvariants(b *testing.B) {
}
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())
app := NewSimApp(logger, db, nil, true, map[int64]bool{}, FlagPeriodValue, interBlockCacheOpt())
// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(

View File

@ -62,7 +62,7 @@ func TestFullAppSimulation(t *testing.T) {
require.NoError(t, os.RemoveAll(dir))
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, fauxMerkleModeOpt)
app := NewSimApp(logger, db, nil, true, map[int64]bool{}, FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "SimApp", app.Name())
// run randomized simulation
@ -94,7 +94,7 @@ func TestAppImportExport(t *testing.T) {
require.NoError(t, os.RemoveAll(dir))
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, fauxMerkleModeOpt)
app := NewSimApp(logger, db, nil, true, map[int64]bool{}, FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "SimApp", app.Name())
// Run randomized simulation
@ -128,7 +128,7 @@ func TestAppImportExport(t *testing.T) {
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewSimApp(log.NewNopLogger(), newDB, nil, true, FlagPeriodValue, fauxMerkleModeOpt)
newApp := NewSimApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "SimApp", newApp.Name())
var genesisState GenesisState
@ -180,7 +180,7 @@ func TestAppSimulationAfterImport(t *testing.T) {
require.NoError(t, os.RemoveAll(dir))
}()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, fauxMerkleModeOpt)
app := NewSimApp(logger, db, nil, true, map[int64]bool{}, FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "SimApp", app.Name())
// Run randomized simulation
@ -219,7 +219,7 @@ func TestAppSimulationAfterImport(t *testing.T) {
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewSimApp(log.NewNopLogger(), newDB, nil, true, FlagPeriodValue, fauxMerkleModeOpt)
newApp := NewSimApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, FlagPeriodValue, fauxMerkleModeOpt)
require.Equal(t, "SimApp", newApp.Name())
newApp.InitChain(abci.RequestInitChain{
@ -265,7 +265,7 @@ func TestAppStateDeterminism(t *testing.T) {
db := dbm.NewMemDB()
app := NewSimApp(logger, db, nil, true, FlagPeriodValue, interBlockCacheOpt())
app := NewSimApp(logger, db, nil, true, map[int64]bool{}, FlagPeriodValue, interBlockCacheOpt())
fmt.Printf(
"running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n",

View File

@ -23,7 +23,7 @@ import (
// Setup initializes a new SimApp. A Nop logger is set in SimApp.
func Setup(isCheckTx bool) *SimApp {
db := dbm.NewMemDB()
app := NewSimApp(log.NewNopLogger(), db, nil, true, 0)
app := NewSimApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, 0)
if !isCheckTx {
// init chain must be called to stop deliverState from being nil
genesisState := NewDefaultGenesisState()
@ -48,7 +48,7 @@ func Setup(isCheckTx bool) *SimApp {
// genesis accounts.
func SetupWithGenesisAccounts(genAccs []authexported.GenesisAccount) *SimApp {
db := dbm.NewMemDB()
app := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0)
app := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, map[int64]bool{}, 0)
// initialize the chain with the passed in genesis accounts
genesisState := NewDefaultGenesisState()

View File

@ -25,7 +25,7 @@ var (
func createTestApp() (*simapp.SimApp, sdk.Context, []sdk.AccAddress) {
db := dbm.NewMemDB()
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, 1)
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, 1)
ctx := app.NewContext(true, abci.Header{})
constantFee := sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)

View File

@ -11,7 +11,7 @@ import (
func createTestApp() *simapp.SimApp {
db := dbm.NewMemDB()
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, 5)
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, map[int64]bool{}, 5)
// init chain must be called to stop deliverState from being nil
genesisState := simapp.NewDefaultGenesisState()
stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState)

View File

@ -2,37 +2,47 @@ package upgrade
import (
"fmt"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// BeginBlock will check if there is a scheduled plan and if it is ready to be executed.
// If the current height is in the provided set of heights to skip, it will skip and clear the upgrade plan.
// If it is ready, it will execute it if the handler is installed, and panic/abort otherwise.
// If the plan is not ready, it will ensure the handler is not registered too early (and abort otherwise).
//
// The purpose is to ensure the binary is switch EXACTLY at the desired block, and to allow
// The purpose is to ensure the binary is switched EXACTLY at the desired block, and to allow
// a migration to be executed if needed upon this switch (migration defined in the new binary)
// skipUpgradeHeightArray is a set of block heights for which the upgrade must be skipped
func BeginBlocker(k Keeper, ctx sdk.Context, _ abci.RequestBeginBlock) {
plan, found := k.GetUpgradePlan(ctx)
if !found {
return
}
// To make sure clear upgrade is executed at the same block
if plan.ShouldExecute(ctx) {
// If skip upgrade has been set for current height, we clear the upgrade plan
if k.IsSkipHeight(ctx.BlockHeight()) {
skipUpgradeMsg := fmt.Sprintf("UPGRADE \"%s\" SKIPPED at %d: %s", plan.Name, plan.Height, plan.Info)
ctx.Logger().Info(skipUpgradeMsg)
// Clear the upgrade plan at current height
k.ClearUpgradePlan(ctx)
return
}
if !k.HasHandler(plan.Name) {
upgradeMsg := fmt.Sprintf("UPGRADE \"%s\" NEEDED at %s: %s", plan.Name, plan.DueAt(), plan.Info)
// We don't have an upgrade handler for this upgrade name, meaning this software is out of date so shutdown
ctx.Logger().Error(upgradeMsg)
panic(upgradeMsg)
}
// We have an upgrade handler for this upgrade name, so apply the upgrade
ctx.Logger().Info(fmt.Sprintf("applying upgrade \"%s\" at %s", plan.Name, plan.DueAt()))
ctx = ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())
k.ApplyUpgrade(ctx, plan)
return
}

View File

@ -5,19 +5,21 @@ import (
"testing"
"time"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/upgrade"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
)
type TestSuite struct {
suite.Suite
module module.AppModule
keeper upgrade.Keeper
querier sdk.Querier
@ -25,163 +27,369 @@ type TestSuite struct {
ctx sdk.Context
}
func (s *TestSuite) SetupTest() {
checkTx := false
app := simapp.Setup(checkTx)
var s TestSuite
func setupTest(height int64, skip map[int64]bool) TestSuite {
db := dbm.NewMemDB()
app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, skip, 0)
genesisState := simapp.NewDefaultGenesisState()
stateBytes, err := codec.MarshalJSONIndent(app.Codec(), genesisState)
if err != nil {
panic(err)
}
app.InitChain(
abci.RequestInitChain{
Validators: []abci.ValidatorUpdate{},
AppStateBytes: stateBytes,
},
)
s.keeper = app.UpgradeKeeper
s.handler = upgrade.NewSoftwareUpgradeProposalHandler(s.keeper)
s.querier = upgrade.NewQuerier(s.keeper)
s.ctx = app.BaseApp.NewContext(checkTx, abci.Header{Height: 10, Time: time.Now()})
s.ctx = app.BaseApp.NewContext(false, abci.Header{Height: height, Time: time.Now()})
s.module = upgrade.NewAppModule(s.keeper)
s.querier = s.module.NewQuerierHandler()
s.handler = upgrade.NewSoftwareUpgradeProposalHandler(s.keeper)
return s
}
func (s *TestSuite) TestRequireName() {
func TestRequireName(t *testing.T) {
s := setupTest(10, map[int64]bool{})
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{}})
s.Require().NotNil(err)
s.Require().True(errors.Is(sdkerrors.ErrInvalidRequest, err), err)
require.NotNil(t, err)
require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
}
func (s *TestSuite) TestRequireFutureTime() {
func TestRequireFutureTime(t *testing.T) {
s := setupTest(10, map[int64]bool{})
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Time: s.ctx.BlockHeader().Time}})
s.Require().NotNil(err)
s.Require().True(errors.Is(sdkerrors.ErrInvalidRequest, err), err)
require.NotNil(t, err)
require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
}
func (s *TestSuite) TestRequireFutureBlock() {
func TestRequireFutureBlock(t *testing.T) {
s := setupTest(10, map[int64]bool{})
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Height: s.ctx.BlockHeight()}})
s.Require().NotNil(err)
s.Require().True(errors.Is(sdkerrors.ErrInvalidRequest, err), err)
require.NotNil(t, err)
require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
}
func (s *TestSuite) TestCantSetBothTimeAndHeight() {
func TestCantSetBothTimeAndHeight(t *testing.T) {
s := setupTest(10, map[int64]bool{})
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Time: time.Now(), Height: s.ctx.BlockHeight() + 1}})
s.Require().NotNil(err)
s.Require().True(errors.Is(sdkerrors.ErrInvalidRequest, err), err)
require.NotNil(t, err)
require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
}
func (s *TestSuite) TestDoTimeUpgrade() {
s.T().Log("Verify can schedule an upgrade")
func TestDoTimeUpgrade(t *testing.T) {
s := setupTest(10, map[int64]bool{})
t.Log("Verify can schedule an upgrade")
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Time: time.Now()}})
s.Require().Nil(err)
require.Nil(t, err)
s.VerifyDoUpgrade()
VerifyDoUpgrade(t)
}
func (s *TestSuite) TestDoHeightUpgrade() {
s.T().Log("Verify can schedule an upgrade")
func TestDoHeightUpgrade(t *testing.T) {
s := setupTest(10, map[int64]bool{})
t.Log("Verify can schedule an upgrade")
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Height: s.ctx.BlockHeight() + 1}})
s.Require().Nil(err)
require.Nil(t, err)
s.VerifyDoUpgrade()
VerifyDoUpgrade(t)
}
func (s *TestSuite) TestCanOverwriteScheduleUpgrade() {
s.T().Log("Can overwrite plan")
func TestCanOverwriteScheduleUpgrade(t *testing.T) {
s := setupTest(10, map[int64]bool{})
t.Log("Can overwrite plan")
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "bad_test", Height: s.ctx.BlockHeight() + 10}})
s.Require().Nil(err)
require.Nil(t, err)
err = s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Height: s.ctx.BlockHeight() + 1}})
s.Require().Nil(err)
require.Nil(t, err)
s.VerifyDoUpgrade()
VerifyDoUpgrade(t)
}
func (s *TestSuite) VerifyDoUpgrade() {
s.T().Log("Verify that a panic happens at the upgrade time/height")
func VerifyDoUpgrade(t *testing.T) {
t.Log("Verify that a panic happens at the upgrade time/height")
newCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1).WithBlockTime(time.Now())
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
s.Require().Panics(func() {
require.Panics(t, func() {
s.module.BeginBlock(newCtx, req)
})
s.T().Log("Verify that the upgrade can be successfully applied with a handler")
t.Log("Verify that the upgrade can be successfully applied with a handler")
s.keeper.SetUpgradeHandler("test", func(ctx sdk.Context, plan upgrade.Plan) {})
s.Require().NotPanics(func() {
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
s.VerifyCleared(newCtx)
VerifyCleared(t, newCtx)
}
func (s *TestSuite) TestHaltIfTooNew() {
s.T().Log("Verify that we don't panic with registered plan not in database at all")
func VerifyDoUpgradeWithCtx(t *testing.T, newCtx sdk.Context, proposalName string) {
t.Log("Verify that a panic happens at the upgrade time/height")
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
require.Panics(t, func() {
s.module.BeginBlock(newCtx, req)
})
t.Log("Verify that the upgrade can be successfully applied with a handler")
s.keeper.SetUpgradeHandler(proposalName, func(ctx sdk.Context, plan upgrade.Plan) {})
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
VerifyCleared(t, newCtx)
}
func TestHaltIfTooNew(t *testing.T) {
s := setupTest(10, map[int64]bool{})
t.Log("Verify that we don't panic with registered plan not in database at all")
var called int
s.keeper.SetUpgradeHandler("future", func(ctx sdk.Context, plan upgrade.Plan) { called++ })
newCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1).WithBlockTime(time.Now())
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
s.Require().NotPanics(func() {
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
s.Require().Equal(0, called)
require.Equal(t, 0, called)
s.T().Log("Verify we panic if we have a registered handler ahead of time")
t.Log("Verify we panic if we have a registered handler ahead of time")
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "future", Height: s.ctx.BlockHeight() + 3}})
s.Require().NoError(err)
s.Require().Panics(func() {
require.NoError(t, err)
require.Panics(t, func() {
s.module.BeginBlock(newCtx, req)
})
s.Require().Equal(0, called)
require.Equal(t, 0, called)
s.T().Log("Verify we no longer panic if the plan is on time")
t.Log("Verify we no longer panic if the plan is on time")
futCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 3).WithBlockTime(time.Now())
req = abci.RequestBeginBlock{Header: futCtx.BlockHeader()}
s.Require().NotPanics(func() {
require.NotPanics(t, func() {
s.module.BeginBlock(futCtx, req)
})
s.Require().Equal(1, called)
require.Equal(t, 1, called)
s.VerifyCleared(futCtx)
VerifyCleared(t, futCtx)
}
func (s *TestSuite) VerifyCleared(newCtx sdk.Context) {
s.T().Log("Verify that the upgrade plan has been cleared")
func VerifyCleared(t *testing.T, newCtx sdk.Context) {
t.Log("Verify that the upgrade plan has been cleared")
bz, err := s.querier(newCtx, []string{upgrade.QueryCurrent}, abci.RequestQuery{})
s.Require().NoError(err)
s.Require().Nil(bz)
require.NoError(t, err)
require.Nil(t, bz)
}
func (s *TestSuite) TestCanClear() {
s.T().Log("Verify upgrade is scheduled")
func TestCanClear(t *testing.T) {
s := setupTest(10, map[int64]bool{})
t.Log("Verify upgrade is scheduled")
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Time: time.Now()}})
s.Require().Nil(err)
require.Nil(t, err)
s.handler(s.ctx, upgrade.CancelSoftwareUpgradeProposal{Title: "cancel"})
err = s.handler(s.ctx, upgrade.CancelSoftwareUpgradeProposal{Title: "cancel"})
require.Nil(t, err)
s.VerifyCleared(s.ctx)
VerifyCleared(t, s.ctx)
}
func (s *TestSuite) TestCantApplySameUpgradeTwice() {
s.TestDoTimeUpgrade()
s.T().Log("Verify an upgrade named \"test\" can't be scheduled twice")
func TestCantApplySameUpgradeTwice(t *testing.T) {
s := setupTest(10, map[int64]bool{})
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Time: time.Now()}})
s.Require().NotNil(err)
s.Require().True(errors.Is(sdkerrors.ErrInvalidRequest, err), err)
require.Nil(t, err)
VerifyDoUpgrade(t)
t.Log("Verify an executed upgrade \"test\" can't be rescheduled")
err = s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Time: time.Now()}})
require.NotNil(t, err)
require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
}
func (s *TestSuite) TestNoSpuriousUpgrades() {
s.T().Log("Verify that no upgrade panic is triggered in the BeginBlocker when we haven't scheduled an upgrade")
func TestNoSpuriousUpgrades(t *testing.T) {
s := setupTest(10, map[int64]bool{})
t.Log("Verify that no upgrade panic is triggered in the BeginBlocker when we haven't scheduled an upgrade")
req := abci.RequestBeginBlock{Header: s.ctx.BlockHeader()}
s.Require().NotPanics(func() {
require.NotPanics(t, func() {
s.module.BeginBlock(s.ctx, req)
})
}
func (s *TestSuite) TestPlanStringer() {
t, err := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z")
s.Require().Nil(err)
s.Require().Equal(`Upgrade Plan
func TestPlanStringer(t *testing.T) {
ti, err := time.Parse(time.RFC3339, "2020-01-01T00:00:00Z")
require.Nil(t, err)
require.Equal(t, `Upgrade Plan
Name: test
Time: 2020-01-01T00:00:00Z
Info: `, upgrade.Plan{Name: "test", Time: t}.String())
s.Require().Equal(`Upgrade Plan
Info: `, upgrade.Plan{Name: "test", Time: ti}.String())
require.Equal(t, `Upgrade Plan
Name: test
Height: 100
Info: `, upgrade.Plan{Name: "test", Height: 100}.String())
}
func TestTestSuite(t *testing.T) {
suite.Run(t, new(TestSuite))
func VerifyNotDone(t *testing.T, newCtx sdk.Context, name string) {
t.Log("Verify that upgrade was not done")
height := s.keeper.GetDoneHeight(newCtx, name)
require.Zero(t, height)
}
func VerifyDone(t *testing.T, newCtx sdk.Context, name string) {
t.Log("Verify that the upgrade plan has been executed")
height := s.keeper.GetDoneHeight(newCtx, name)
require.NotZero(t, height)
}
func VerifySet(t *testing.T, skipUpgradeHeights map[int64]bool) {
t.Log("Verify if the skip upgrade has been set")
for k := range skipUpgradeHeights {
require.True(t, s.keeper.IsSkipHeight(k))
}
}
func TestContains(t *testing.T) {
var (
skipOne int64 = 11
)
s := setupTest(10, map[int64]bool{skipOne: true})
VerifySet(t, map[int64]bool{skipOne: true})
t.Log("case where array contains the element")
require.True(t, s.keeper.IsSkipHeight(11))
t.Log("case where array doesn't contain the element")
require.False(t, s.keeper.IsSkipHeight(4))
}
func TestSkipUpgradeSkippingAll(t *testing.T) {
var (
skipOne int64 = 11
skipTwo int64 = 20
)
s := setupTest(10, map[int64]bool{skipOne: true, skipTwo: true})
newCtx := s.ctx
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Height: skipOne}})
require.NoError(t, err)
t.Log("Verify if skip upgrade flag clears upgrade plan in both cases")
VerifySet(t, map[int64]bool{skipOne: true, skipTwo: true})
newCtx = newCtx.WithBlockHeight(skipOne)
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
t.Log("Verify a second proposal also is being cleared")
err = s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop2", Plan: upgrade.Plan{Name: "test2", Height: skipTwo}})
require.NoError(t, err)
newCtx = newCtx.WithBlockHeight(skipTwo)
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
// To ensure verification is being done only after both upgrades are cleared
t.Log("Verify if both proposals are cleared")
VerifyCleared(t, s.ctx)
VerifyNotDone(t, s.ctx, "test")
VerifyNotDone(t, s.ctx, "test2")
}
func TestUpgradeSkippingOne(t *testing.T) {
var (
skipOne int64 = 11
skipTwo int64 = 20
)
s := setupTest(10, map[int64]bool{skipOne: true})
newCtx := s.ctx
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Height: skipOne}})
require.Nil(t, err)
t.Log("Verify if skip upgrade flag clears upgrade plan in one case and does upgrade on another")
VerifySet(t, map[int64]bool{skipOne: true})
// Setting block height of proposal test
newCtx = newCtx.WithBlockHeight(skipOne)
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
t.Log("Verify the second proposal is not skipped")
err = s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop2", Plan: upgrade.Plan{Name: "test2", Height: skipTwo}})
require.Nil(t, err)
// Setting block height of proposal test2
newCtx = newCtx.WithBlockHeight(skipTwo)
VerifyDoUpgradeWithCtx(t, newCtx, "test2")
t.Log("Verify first proposal is cleared and second is done")
VerifyNotDone(t, s.ctx, "test")
VerifyDone(t, s.ctx, "test2")
}
func TestUpgradeSkippingOnlyTwo(t *testing.T) {
var (
skipOne int64 = 11
skipTwo int64 = 20
skipThree int64 = 25
)
s := setupTest(10, map[int64]bool{skipOne: true, skipTwo: true})
newCtx := s.ctx
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Height: skipOne}})
require.Nil(t, err)
t.Log("Verify if skip upgrade flag clears upgrade plan in both cases and does third upgrade")
VerifySet(t, map[int64]bool{skipOne: true, skipTwo: true})
// Setting block height of proposal test
newCtx = newCtx.WithBlockHeight(skipOne)
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
// A new proposal with height in skipUpgradeHeights
err = s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop2", Plan: upgrade.Plan{Name: "test2", Height: skipTwo}})
require.Nil(t, err)
// Setting block height of proposal test2
newCtx = newCtx.WithBlockHeight(skipTwo)
require.NotPanics(t, func() {
s.module.BeginBlock(newCtx, req)
})
t.Log("Verify a new proposal is not skipped")
err = s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop3", Plan: upgrade.Plan{Name: "test3", Height: skipThree}})
require.Nil(t, err)
newCtx = newCtx.WithBlockHeight(skipThree)
VerifyDoUpgradeWithCtx(t, newCtx, "test3")
t.Log("Verify two proposals are cleared and third is done")
VerifyNotDone(t, s.ctx, "test")
VerifyNotDone(t, s.ctx, "test2")
VerifyDone(t, s.ctx, "test3")
}
func TestUpgradeWithoutSkip(t *testing.T) {
s := setupTest(10, map[int64]bool{})
newCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1).WithBlockTime(time.Now())
req := abci.RequestBeginBlock{Header: newCtx.BlockHeader()}
err := s.handler(s.ctx, upgrade.SoftwareUpgradeProposal{Title: "prop", Plan: upgrade.Plan{Name: "test", Height: s.ctx.BlockHeight() + 1}})
require.Nil(t, err)
t.Log("Verify if upgrade happens without skip upgrade")
require.Panics(t, func() {
s.module.BeginBlock(newCtx, req)
})
VerifyDoUpgrade(t)
VerifyDone(t, s.ctx, "test")
}

View File

@ -4,26 +4,29 @@ import (
"encoding/binary"
"fmt"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"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/x/upgrade/internal/types"
"github.com/tendermint/tendermint/libs/log"
)
type Keeper struct {
storeKey sdk.StoreKey
cdc *codec.Codec
upgradeHandlers map[string]types.UpgradeHandler
skipUpgradeHeights map[int64]bool
storeKey sdk.StoreKey
cdc *codec.Codec
upgradeHandlers map[string]types.UpgradeHandler
}
// NewKeeper constructs an upgrade Keeper
func NewKeeper(storeKey sdk.StoreKey, cdc *codec.Codec) Keeper {
func NewKeeper(skipUpgradeHeights map[int64]bool, storeKey sdk.StoreKey, cdc *codec.Codec) Keeper {
return Keeper{
storeKey: storeKey,
cdc: cdc,
upgradeHandlers: map[string]types.UpgradeHandler{},
skipUpgradeHeights: skipUpgradeHeights,
storeKey: storeKey,
cdc: cdc,
upgradeHandlers: map[string]types.UpgradeHandler{},
}
}
@ -50,7 +53,7 @@ func (k Keeper) ScheduleUpgrade(ctx sdk.Context, plan types.Plan) error {
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "upgrade cannot be scheduled in the past")
}
if k.getDoneHeight(ctx, plan.Name) != 0 {
if k.GetDoneHeight(ctx, plan.Name) != 0 {
return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "upgrade with name %s has already been completed", plan.Name)
}
@ -61,7 +64,8 @@ func (k Keeper) ScheduleUpgrade(ctx sdk.Context, plan types.Plan) error {
return nil
}
func (k Keeper) getDoneHeight(ctx sdk.Context, name string) int64 {
// 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})
bz := store.Get([]byte(name))
if len(bz) == 0 {
@ -121,3 +125,8 @@ func (k Keeper) ApplyUpgrade(ctx sdk.Context, plan types.Plan) {
k.ClearUpgradePlan(ctx)
k.setDone(ctx, plan.Name)
}
// IsSkipHeight checks if the given height is part of skipUpgradeHeights
func (k Keeper) IsSkipHeight(height int64) bool {
return k.skipUpgradeHeights[height]
}

View File

@ -48,7 +48,7 @@ func queryApplied(ctx sdk.Context, req abci.RequestQuery, k Keeper) ([]byte, err
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
applied := k.getDoneHeight(ctx, params.Name)
applied := k.GetDoneHeight(ctx, params.Name)
if applied == 0 {
return nil, nil
}