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 }