github.com/Finschia/finschia-sdk@v0.48.1/x/upgrade/abci_test.go (about)

     1  package upgrade_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"os"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/require"
    11  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    12  
    13  	ocabci "github.com/Finschia/ostracon/abci/types"
    14  	"github.com/Finschia/ostracon/libs/log"
    15  	abci "github.com/tendermint/tendermint/abci/types"
    16  	dbm "github.com/tendermint/tm-db"
    17  
    18  	"github.com/Finschia/finschia-sdk/simapp"
    19  	storetypes "github.com/Finschia/finschia-sdk/store/types"
    20  	sdk "github.com/Finschia/finschia-sdk/types"
    21  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
    22  	"github.com/Finschia/finschia-sdk/types/module"
    23  	govtypes "github.com/Finschia/finschia-sdk/x/gov/types"
    24  	"github.com/Finschia/finschia-sdk/x/upgrade"
    25  	"github.com/Finschia/finschia-sdk/x/upgrade/keeper"
    26  	"github.com/Finschia/finschia-sdk/x/upgrade/types"
    27  )
    28  
    29  type TestSuite struct {
    30  	module  module.BeginBlockAppModule
    31  	keeper  keeper.Keeper
    32  	querier sdk.Querier
    33  	handler govtypes.Handler
    34  	ctx     sdk.Context
    35  }
    36  
    37  var s TestSuite
    38  
    39  func setupTest(height int64, skip map[int64]bool) TestSuite {
    40  	db := dbm.NewMemDB()
    41  	app := simapp.NewSimApp(log.NewNopLogger(), db, nil, true, skip, simapp.DefaultNodeHome, 0, simapp.MakeTestEncodingConfig(), simapp.EmptyAppOptions{})
    42  	genesisState := simapp.NewDefaultGenesisState(app.AppCodec())
    43  	stateBytes, err := json.MarshalIndent(genesisState, "", "  ")
    44  	if err != nil {
    45  		panic(err)
    46  	}
    47  	app.InitChain(
    48  		abci.RequestInitChain{
    49  			Validators:    []abci.ValidatorUpdate{},
    50  			AppStateBytes: stateBytes,
    51  		},
    52  	)
    53  
    54  	s.keeper = app.UpgradeKeeper
    55  	s.ctx = app.BaseApp.NewContext(false, tmproto.Header{Height: height, Time: time.Now()})
    56  
    57  	s.module = upgrade.NewAppModule(s.keeper)
    58  	s.querier = s.module.LegacyQuerierHandler(app.LegacyAmino())
    59  	s.handler = upgrade.NewSoftwareUpgradeProposalHandler(s.keeper)
    60  	return s
    61  }
    62  
    63  func TestRequireName(t *testing.T) {
    64  	s := setupTest(10, map[int64]bool{})
    65  
    66  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{}})
    67  	require.Error(t, err)
    68  	require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
    69  }
    70  
    71  func TestRequireFutureBlock(t *testing.T) {
    72  	s := setupTest(10, map[int64]bool{})
    73  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: s.ctx.BlockHeight() - 1}})
    74  	require.Error(t, err)
    75  	require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
    76  }
    77  
    78  func TestDoHeightUpgrade(t *testing.T) {
    79  	s := setupTest(10, map[int64]bool{})
    80  	t.Log("Verify can schedule an upgrade")
    81  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: s.ctx.BlockHeight() + 1}})
    82  	require.NoError(t, err)
    83  
    84  	VerifyDoUpgrade(t)
    85  }
    86  
    87  func TestCanOverwriteScheduleUpgrade(t *testing.T) {
    88  	s := setupTest(10, map[int64]bool{})
    89  	t.Log("Can overwrite plan")
    90  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "bad_test", Height: s.ctx.BlockHeight() + 10}})
    91  	require.NoError(t, err)
    92  	err = s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: s.ctx.BlockHeight() + 1}})
    93  	require.NoError(t, err)
    94  
    95  	VerifyDoUpgrade(t)
    96  }
    97  
    98  func VerifyDoUpgrade(t *testing.T) {
    99  	t.Log("Verify that a panic happens at the upgrade height")
   100  	newCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1).WithBlockTime(time.Now())
   101  
   102  	req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   103  	require.Panics(t, func() {
   104  		s.module.BeginBlock(newCtx, req)
   105  	})
   106  
   107  	t.Log("Verify that the upgrade can be successfully applied with a handler")
   108  	s.keeper.SetUpgradeHandler("test", func(ctx sdk.Context, plan types.Plan, vm module.VersionMap) (module.VersionMap, error) {
   109  		return vm, nil
   110  	})
   111  	require.NotPanics(t, func() {
   112  		s.module.BeginBlock(newCtx, req)
   113  	})
   114  
   115  	VerifyCleared(t, newCtx)
   116  }
   117  
   118  func VerifyDoUpgradeWithCtx(t *testing.T, newCtx sdk.Context, proposalName string) {
   119  	t.Log("Verify that a panic happens at the upgrade height")
   120  	req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   121  	require.Panics(t, func() {
   122  		s.module.BeginBlock(newCtx, req)
   123  	})
   124  
   125  	t.Log("Verify that the upgrade can be successfully applied with a handler")
   126  	s.keeper.SetUpgradeHandler(proposalName, func(ctx sdk.Context, plan types.Plan, vm module.VersionMap) (module.VersionMap, error) {
   127  		return vm, nil
   128  	})
   129  	require.NotPanics(t, func() {
   130  		s.module.BeginBlock(newCtx, req)
   131  	})
   132  
   133  	VerifyCleared(t, newCtx)
   134  }
   135  
   136  func TestHaltIfTooNew(t *testing.T) {
   137  	s := setupTest(10, map[int64]bool{})
   138  	t.Log("Verify that we don't panic with registered plan not in database at all")
   139  	var called int
   140  	s.keeper.SetUpgradeHandler("future", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
   141  		called++
   142  		return vm, nil
   143  	})
   144  
   145  	newCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1).WithBlockTime(time.Now())
   146  	req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   147  	require.NotPanics(t, func() {
   148  		s.module.BeginBlock(newCtx, req)
   149  	})
   150  	require.Equal(t, 0, called)
   151  
   152  	t.Log("Verify we panic if we have a registered handler ahead of time")
   153  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "future", Height: s.ctx.BlockHeight() + 3}})
   154  	require.NoError(t, err)
   155  	require.Panics(t, func() {
   156  		s.module.BeginBlock(newCtx, req)
   157  	})
   158  	require.Equal(t, 0, called)
   159  
   160  	t.Log("Verify we no longer panic if the plan is on time")
   161  
   162  	futCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 3).WithBlockTime(time.Now())
   163  	req = ocabci.RequestBeginBlock{Header: futCtx.BlockHeader()}
   164  	require.NotPanics(t, func() {
   165  		s.module.BeginBlock(futCtx, req)
   166  	})
   167  	require.Equal(t, 1, called)
   168  
   169  	VerifyCleared(t, futCtx)
   170  }
   171  
   172  func VerifyCleared(t *testing.T, newCtx sdk.Context) {
   173  	t.Log("Verify that the upgrade plan has been cleared")
   174  	bz, err := s.querier(newCtx, []string{types.QueryCurrent}, abci.RequestQuery{})
   175  	require.NoError(t, err)
   176  	require.Nil(t, bz)
   177  }
   178  
   179  func TestCanClear(t *testing.T) {
   180  	s := setupTest(10, map[int64]bool{})
   181  	t.Log("Verify upgrade is scheduled")
   182  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: s.ctx.BlockHeight() + 100}})
   183  	require.NoError(t, err)
   184  
   185  	err = s.handler(s.ctx, &types.CancelSoftwareUpgradeProposal{Title: "cancel"})
   186  	require.NoError(t, err)
   187  
   188  	VerifyCleared(t, s.ctx)
   189  }
   190  
   191  func TestCantApplySameUpgradeTwice(t *testing.T) {
   192  	s := setupTest(10, map[int64]bool{})
   193  	height := s.ctx.BlockHeader().Height + 1
   194  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: height}})
   195  	require.NoError(t, err)
   196  	VerifyDoUpgrade(t)
   197  	t.Log("Verify an executed upgrade \"test\" can't be rescheduled")
   198  	err = s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: height}})
   199  	require.Error(t, err)
   200  	require.True(t, errors.Is(sdkerrors.ErrInvalidRequest, err), err)
   201  }
   202  
   203  func TestNoSpuriousUpgrades(t *testing.T) {
   204  	s := setupTest(10, map[int64]bool{})
   205  	t.Log("Verify that no upgrade panic is triggered in the BeginBlocker when we haven't scheduled an upgrade")
   206  	req := ocabci.RequestBeginBlock{Header: s.ctx.BlockHeader()}
   207  	require.NotPanics(t, func() {
   208  		s.module.BeginBlock(s.ctx, req)
   209  	})
   210  }
   211  
   212  func TestPlanStringer(t *testing.T) {
   213  	require.Equal(t, `Upgrade Plan
   214    Name: test
   215    Height: 100
   216    Info: .`, types.Plan{Name: "test", Height: 100, Info: ""}.String())
   217  
   218  	require.Equal(t, `Upgrade Plan
   219    Name: test
   220    Height: 100
   221    Info: .`, types.Plan{Name: "test", Height: 100, Info: ""}.String())
   222  }
   223  
   224  func VerifyNotDone(t *testing.T, newCtx sdk.Context, name string) {
   225  	t.Log("Verify that upgrade was not done")
   226  	height := s.keeper.GetDoneHeight(newCtx, name)
   227  	require.Zero(t, height)
   228  }
   229  
   230  func VerifyDone(t *testing.T, newCtx sdk.Context, name string) {
   231  	t.Log("Verify that the upgrade plan has been executed")
   232  	height := s.keeper.GetDoneHeight(newCtx, name)
   233  	require.NotZero(t, height)
   234  }
   235  
   236  func VerifySet(t *testing.T, skipUpgradeHeights map[int64]bool) {
   237  	t.Log("Verify if the skip upgrade has been set")
   238  
   239  	for k := range skipUpgradeHeights {
   240  		require.True(t, s.keeper.IsSkipHeight(k))
   241  	}
   242  }
   243  
   244  func TestContains(t *testing.T) {
   245  	var skipOne int64 = 11
   246  	s := setupTest(10, map[int64]bool{skipOne: true})
   247  
   248  	VerifySet(t, map[int64]bool{skipOne: true})
   249  	t.Log("case where array contains the element")
   250  	require.True(t, s.keeper.IsSkipHeight(11))
   251  
   252  	t.Log("case where array doesn't contain the element")
   253  	require.False(t, s.keeper.IsSkipHeight(4))
   254  }
   255  
   256  func TestSkipUpgradeSkippingAll(t *testing.T) {
   257  	var (
   258  		skipOne int64 = 11
   259  		skipTwo int64 = 20
   260  	)
   261  	s := setupTest(10, map[int64]bool{skipOne: true, skipTwo: true})
   262  
   263  	newCtx := s.ctx
   264  
   265  	req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   266  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: skipOne}})
   267  	require.NoError(t, err)
   268  
   269  	t.Log("Verify if skip upgrade flag clears upgrade plan in both cases")
   270  	VerifySet(t, map[int64]bool{skipOne: true, skipTwo: true})
   271  
   272  	newCtx = newCtx.WithBlockHeight(skipOne)
   273  	require.NotPanics(t, func() {
   274  		s.module.BeginBlock(newCtx, req)
   275  	})
   276  
   277  	t.Log("Verify a second proposal also is being cleared")
   278  	err = s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop2", Plan: types.Plan{Name: "test2", Height: skipTwo}})
   279  	require.NoError(t, err)
   280  
   281  	newCtx = newCtx.WithBlockHeight(skipTwo)
   282  	require.NotPanics(t, func() {
   283  		s.module.BeginBlock(newCtx, req)
   284  	})
   285  
   286  	// To ensure verification is being done only after both upgrades are cleared
   287  	t.Log("Verify if both proposals are cleared")
   288  	VerifyCleared(t, s.ctx)
   289  	VerifyNotDone(t, s.ctx, "test")
   290  	VerifyNotDone(t, s.ctx, "test2")
   291  }
   292  
   293  func TestUpgradeSkippingOne(t *testing.T) {
   294  	var (
   295  		skipOne int64 = 11
   296  		skipTwo int64 = 20
   297  	)
   298  	s := setupTest(10, map[int64]bool{skipOne: true})
   299  
   300  	newCtx := s.ctx
   301  
   302  	req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   303  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: skipOne}})
   304  	require.NoError(t, err)
   305  
   306  	t.Log("Verify if skip upgrade flag clears upgrade plan in one case and does upgrade on another")
   307  	VerifySet(t, map[int64]bool{skipOne: true})
   308  
   309  	// Setting block height of proposal test
   310  	newCtx = newCtx.WithBlockHeight(skipOne)
   311  	require.NotPanics(t, func() {
   312  		s.module.BeginBlock(newCtx, req)
   313  	})
   314  
   315  	t.Log("Verify the second proposal is not skipped")
   316  	err = s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop2", Plan: types.Plan{Name: "test2", Height: skipTwo}})
   317  	require.NoError(t, err)
   318  	// Setting block height of proposal test2
   319  	newCtx = newCtx.WithBlockHeight(skipTwo)
   320  	VerifyDoUpgradeWithCtx(t, newCtx, "test2")
   321  
   322  	t.Log("Verify first proposal is cleared and second is done")
   323  	VerifyNotDone(t, s.ctx, "test")
   324  	VerifyDone(t, s.ctx, "test2")
   325  }
   326  
   327  func TestUpgradeSkippingOnlyTwo(t *testing.T) {
   328  	var (
   329  		skipOne   int64 = 11
   330  		skipTwo   int64 = 20
   331  		skipThree int64 = 25
   332  	)
   333  	s := setupTest(10, map[int64]bool{skipOne: true, skipTwo: true})
   334  
   335  	newCtx := s.ctx
   336  
   337  	req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   338  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: skipOne}})
   339  	require.NoError(t, err)
   340  
   341  	t.Log("Verify if skip upgrade flag clears upgrade plan in both cases and does third upgrade")
   342  	VerifySet(t, map[int64]bool{skipOne: true, skipTwo: true})
   343  
   344  	// Setting block height of proposal test
   345  	newCtx = newCtx.WithBlockHeight(skipOne)
   346  	require.NotPanics(t, func() {
   347  		s.module.BeginBlock(newCtx, req)
   348  	})
   349  
   350  	// A new proposal with height in skipUpgradeHeights
   351  	err = s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop2", Plan: types.Plan{Name: "test2", Height: skipTwo}})
   352  	require.NoError(t, err)
   353  	// Setting block height of proposal test2
   354  	newCtx = newCtx.WithBlockHeight(skipTwo)
   355  	require.NotPanics(t, func() {
   356  		s.module.BeginBlock(newCtx, req)
   357  	})
   358  
   359  	t.Log("Verify a new proposal is not skipped")
   360  	err = s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop3", Plan: types.Plan{Name: "test3", Height: skipThree}})
   361  	require.NoError(t, err)
   362  	newCtx = newCtx.WithBlockHeight(skipThree)
   363  	VerifyDoUpgradeWithCtx(t, newCtx, "test3")
   364  
   365  	t.Log("Verify two proposals are cleared and third is done")
   366  	VerifyNotDone(t, s.ctx, "test")
   367  	VerifyNotDone(t, s.ctx, "test2")
   368  	VerifyDone(t, s.ctx, "test3")
   369  }
   370  
   371  func TestUpgradeWithoutSkip(t *testing.T) {
   372  	s := setupTest(10, map[int64]bool{})
   373  	newCtx := s.ctx.WithBlockHeight(s.ctx.BlockHeight() + 1).WithBlockTime(time.Now())
   374  	req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   375  	err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "prop", Plan: types.Plan{Name: "test", Height: s.ctx.BlockHeight() + 1}})
   376  	require.NoError(t, err)
   377  	t.Log("Verify if upgrade happens without skip upgrade")
   378  	require.Panics(t, func() {
   379  		s.module.BeginBlock(newCtx, req)
   380  	})
   381  
   382  	VerifyDoUpgrade(t)
   383  	VerifyDone(t, s.ctx, "test")
   384  }
   385  
   386  func TestDumpUpgradeInfoToFile(t *testing.T) {
   387  	s := setupTest(10, map[int64]bool{})
   388  
   389  	planHeight := s.ctx.BlockHeight() + 1
   390  	name := "test"
   391  	t.Log("verify if upgrade height is dumped to file")
   392  	err := s.keeper.DumpUpgradeInfoToDisk(planHeight, name)
   393  	require.Nil(t, err)
   394  
   395  	upgradeInfoFilePath, err := s.keeper.GetUpgradeInfoPath()
   396  	require.Nil(t, err)
   397  
   398  	data, err := os.ReadFile(upgradeInfoFilePath)
   399  	require.NoError(t, err)
   400  
   401  	var upgradeInfo storetypes.UpgradeInfo
   402  	err = json.Unmarshal(data, &upgradeInfo)
   403  	require.Nil(t, err)
   404  
   405  	t.Log("Verify upgrade height from file matches ")
   406  	require.Equal(t, upgradeInfo.Height, planHeight)
   407  
   408  	// clear the test file
   409  	err = os.Remove(upgradeInfoFilePath)
   410  	require.Nil(t, err)
   411  }
   412  
   413  // TODO: add testcase to for `no upgrade handler is present for last applied upgrade`.
   414  func TestBinaryVersion(t *testing.T) {
   415  	var skipHeight int64 = 15
   416  	s := setupTest(10, map[int64]bool{skipHeight: true})
   417  
   418  	testCases := []struct {
   419  		name        string
   420  		preRun      func() (sdk.Context, ocabci.RequestBeginBlock)
   421  		expectPanic bool
   422  	}{
   423  		{
   424  			"test not panic: no scheduled upgrade or applied upgrade is present",
   425  			func() (sdk.Context, ocabci.RequestBeginBlock) {
   426  				req := ocabci.RequestBeginBlock{Header: s.ctx.BlockHeader()}
   427  				return s.ctx, req
   428  			},
   429  			false,
   430  		},
   431  		{
   432  			"test not panic: upgrade handler is present for last applied upgrade",
   433  			func() (sdk.Context, ocabci.RequestBeginBlock) {
   434  				s.keeper.SetUpgradeHandler("test0", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) {
   435  					return vm, nil
   436  				})
   437  
   438  				err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "Upgrade test", Plan: types.Plan{Name: "test0", Height: s.ctx.BlockHeight() + 2}})
   439  				require.NoError(t, err)
   440  
   441  				newCtx := s.ctx.WithBlockHeight(12)
   442  				s.keeper.ApplyUpgrade(newCtx, types.Plan{
   443  					Name:   "test0",
   444  					Height: 12,
   445  				})
   446  
   447  				req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   448  				return newCtx, req
   449  			},
   450  			false,
   451  		},
   452  		{
   453  			"test panic: upgrade needed",
   454  			func() (sdk.Context, ocabci.RequestBeginBlock) {
   455  				err := s.handler(s.ctx, &types.SoftwareUpgradeProposal{Title: "Upgrade test", Plan: types.Plan{Name: "test2", Height: 13}})
   456  				require.NoError(t, err)
   457  
   458  				newCtx := s.ctx.WithBlockHeight(13)
   459  				req := ocabci.RequestBeginBlock{Header: newCtx.BlockHeader()}
   460  				return newCtx, req
   461  			},
   462  			true,
   463  		},
   464  	}
   465  
   466  	for _, tc := range testCases {
   467  		ctx, req := tc.preRun()
   468  		if tc.expectPanic {
   469  			require.Panics(t, func() {
   470  				s.module.BeginBlock(ctx, req)
   471  			})
   472  		} else {
   473  			require.NotPanics(t, func() {
   474  				s.module.BeginBlock(ctx, req)
   475  			})
   476  		}
   477  	}
   478  }