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