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 }