From 0eb2b8a140746a443581bce6e48f6423215c1362 Mon Sep 17 00:00:00 2001 From: hard-nett Date: Sun, 22 Dec 2024 01:34:42 -0500 Subject: [PATCH] add upgrade test for v020 --- app/test_helper.go | 24 ++-- app/testing/test_suite.go | 14 +- app/upgrades/v020/upgrade_test.go | 226 ++++++++++++------------------ 3 files changed, 109 insertions(+), 155 deletions(-) diff --git a/app/test_helper.go b/app/test_helper.go index 4c49e8d1..3feb6929 100644 --- a/app/test_helper.go +++ b/app/test_helper.go @@ -96,6 +96,7 @@ func Setup(t *testing.T) *BitsongApp { return app } +// Setup node for testing simulation func setup(t *testing.T, withGenesis bool, opts ...wasmkeeper.Option) (*BitsongApp, GenesisState) { db := dbm.NewMemDB() nodeHome := t.TempDir() @@ -110,7 +111,7 @@ func setup(t *testing.T, withGenesis bool, opts ...wasmkeeper.Option) (*BitsongA appOptions := make(simtestutil.AppOptionsMap, 0) appOptions[flags.FlagHome] = nodeHome // ensure unique folder - app := NewBitsongApp(log.NewNopLogger(), db, nil, true, EmptyAppOptions{}, opts, bam.SetChainID("testing"), bam.SetSnapshot(snapshotStore, snapshottypes.SnapshotOptions{KeepRecent: 2})) + app := NewBitsongApp(log.NewNopLogger(), db, nil, true, appOptions, opts, bam.SetChainID("testing"), bam.SetSnapshot(snapshotStore, snapshottypes.SnapshotOptions{KeepRecent: 2})) if withGenesis { return app, NewDefaultGenesisState() } @@ -123,6 +124,7 @@ func SetupWithGenesisAccounts(t *testing.T, valSet *tmtypes.ValidatorSet, genAcc t.Helper() btsgApp, genesisState := setup(t, true) + genesisState = GenesisStateWithValSet(t, btsgApp, genesisState, valSet, genAccs, balances...) stateBytes, err := json.MarshalIndent(genesisState, "", " ") @@ -231,19 +233,17 @@ func GenesisStateWithValSet(t *testing.T, bondAmt := sdk.DefaultPowerReduction initValPowers := []abci.ValidatorUpdate{} - for i, val := range valSet.Validators { - if i == 0 { - - } + for _, val := range valSet.Validators { pk, _ := cryptocodec.FromTmPubKeyInterface(val.PubKey) pkAny, _ := codectypes.NewAnyWithValue(pk) validator := stakingtypes.Validator{ - OperatorAddress: sdk.ValAddress(val.Address).String(), - ConsensusPubkey: pkAny, - Jailed: false, - Status: stakingtypes.Bonded, - Tokens: bondAmt, - DelegatorShares: math.LegacyOneDec().MulTruncate(math.LegacyOneDec().Sub(math.LegacyNewDecWithPrec(1, 3))), // 1 % slash + OperatorAddress: sdk.ValAddress(val.Address).String(), + ConsensusPubkey: pkAny, + Jailed: false, + Status: stakingtypes.Bonded, + Tokens: bondAmt, + DelegatorShares: math.LegacyOneDec(), + // DelegatorShares: math.LegacyOneDec().MulTruncate(math.LegacyOneDec().Sub(math.LegacyNewDecWithPrec(1, 3))), // 1 % slash Description: stakingtypes.Description{}, UnbondingHeight: int64(0), UnbondingTime: time.Unix(0, 0).UTC(), @@ -284,7 +284,7 @@ func GenesisStateWithValSet(t *testing.T, for range delegations { // add delegated tokens to total supply - totalSupply = totalSupply.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)) + totalSupply = totalSupply.Add(sdk.NewCoin(appparams.DefaultBondDenom, bondAmt)) } // add bonded amount to bonded pool module account diff --git a/app/testing/test_suite.go b/app/testing/test_suite.go index 053475ec..28bd6c3f 100644 --- a/app/testing/test_suite.go +++ b/app/testing/test_suite.go @@ -12,23 +12,23 @@ import ( bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" stakinghelper "github.com/cosmos/cosmos-sdk/x/staking/testutil" + "github.com/stretchr/testify/suite" ) type KeeperTestHelper struct { suite.Suite - App *app.BitsongApp - Ctx sdk.Context - QueryHelper *baseapp.QueryServiceTestHelper - TestAccs []sdk.AccAddress + App *app.BitsongApp // Mock bitsong application + Ctx sdk.Context // simulated context + QueryHelper *baseapp.QueryServiceTestHelper // GRPC query simulator + TestAccs []sdk.AccAddress // Test accounts - StakingHelper *stakinghelper.Helper + StakingHelper *stakinghelper.Helper // Useful staking helpers } func (s *KeeperTestHelper) Setup() { - t := s.T() - s.App = app.Setup(t) + s.App = app.Setup(s.T()) s.Ctx = s.App.BaseApp.NewContext(false, tmproto.Header{Height: 1, ChainID: "testing", Time: time.Now().UTC()}) s.QueryHelper = &baseapp.QueryServiceTestHelper{ GRPCQueryRouter: s.App.GRPCQueryRouter(), diff --git a/app/upgrades/v020/upgrade_test.go b/app/upgrades/v020/upgrade_test.go index 508ffcae..9b94b79c 100644 --- a/app/upgrades/v020/upgrade_test.go +++ b/app/upgrades/v020/upgrade_test.go @@ -1,166 +1,120 @@ package v020_test import ( + "fmt" "testing" - "time" "cosmossdk.io/math" - "github.com/bitsongofficial/go-bitsong/app" - "github.com/bitsongofficial/go-bitsong/app/helpers" - "github.com/bitsongofficial/go-bitsong/app/keepers" - appparams "github.com/bitsongofficial/go-bitsong/app/params" apptesting "github.com/bitsongofficial/go-bitsong/app/testing" - abci "github.com/cometbft/cometbft/abci/types" + abcitypes "github.com/cometbft/cometbft/abci/types" - tmtypes "github.com/cometbft/cometbft/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/cosmos/cosmos-sdk/x/staking/types" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) +const dummyUpgradeHeight = 5 + type UpgradeTestSuite struct { apptesting.KeeperTestHelper } -func TestUpgradeSuite(t *testing.T) { - suite.Run(t, new(UpgradeTestSuite)) - -} - func (s *UpgradeTestSuite) SetupTest() { - V020Setup(s.T()) -} - -const dummyUpgradeHeight = 5 - -func (s *UpgradeTestSuite) dummyUpgrade() { - s.Ctx = s.Ctx.WithBlockHeight(dummyUpgradeHeight - 1) - plan := upgradetypes.Plan{Name: "v20", Height: dummyUpgradeHeight} - err := s.App.AppKeepers.UpgradeKeeper.ScheduleUpgrade(s.Ctx, plan) - s.Require().NoError(err) - _, exists := s.App.AppKeepers.UpgradeKeeper.GetUpgradePlan(s.Ctx) - s.Require().True(exists) - - s.Ctx = s.Ctx.WithBlockHeight(dummyUpgradeHeight) - s.Require().NotPanics(func() { - beginBlockRequest := abcitypes.RequestBeginBlock{} - s.App.BeginBlocker(s.Ctx, beginBlockRequest) - }) + s.Setup() } -// Setup initializes a new BitsongApp -func V020Setup(t *testing.T) *app.BitsongApp { - t.Helper() - - privVal := helpers.NewPV() - pubKey, err := privVal.GetPubKey() - require.NoError(t, err) - - // create validator set with single validator - validator := tmtypes.NewValidator(pubKey, 1) - valSet := tmtypes.NewValidatorSet([]*tmtypes.Validator{validator}) - - // generate genesis account - senderPrivKey := secp256k1.GenPrivKey() - acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0) - balance := banktypes.Balance{ - Address: acc.GetAddress().String(), - Coins: sdk.NewCoins(sdk.NewCoin(appparams.DefaultBondDenom, sdk.NewInt(100000000000000))), - } - - app := app.SetupWithGenesisAccounts(t, valSet, []authtypes.GenesisAccount{acc}, balance) - return app +func TestUpgradeTestSuite(t *testing.T) { + suite.Run(t, new(UpgradeTestSuite)) } func (s *UpgradeTestSuite) TestUpgrade() { - - // corrupt state to match mainnet - s.setupCorruptedState() - - // upgrade software - s.dummyUpgrade() - s.App.BeginBlocker(s.Ctx, abci.RequestBeginBlock{}) - s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(time.Hour * 24)) - - // ensure post upgrade reward query works - s.ensurePostUpgradeDistributionWorks() -} - -func (s *UpgradeTestSuite) ensurePostUpgradeDistributionWorks() { - -} - -// setupCorruptedState aligns the testing environment with the mainnet state. -// By running this method, it will modify the delegations tokens from shares, -// reflecting a slashing event that -func (s *UpgradeTestSuite) setupCorruptedState() { - // infractionHeight := int64(3) - - power := int64(1000000) - slashFactor := sdk.NewDecWithPrec(1, 2) //1 % - staking := s.App.AppKeepers.StakingKeeper - - // Amount of slashing = slash slashFactor * power at time of infraction - amount := staking.TokensFromConsensusPower(s.Ctx, power) - slashAmountDec := sdk.NewDecFromInt(amount).Mul(slashFactor) - slashAmount := slashAmountDec.TruncateInt() - - // mimic slashing event on staking power, but not update slashing event to distribution module - validator, _ := staking.GetValidatorByConsAddr(s.Ctx, sdk.ConsAddress{}) - - operatorAddress := validator.GetOperator() - - // call the before-modification hook - if err := staking.Hooks().BeforeValidatorModified(s.Ctx, operatorAddress); err != nil { - staking.Logger(s.Ctx).Error("failed to call before validator modified hook", "error", err) - } - remainingSlashAmount := slashAmount - - // cannot decrease balance below zero - tokensToBurn := sdk.MinInt(remainingSlashAmount, validator.Tokens) - tokensToBurn = sdk.MaxInt(tokensToBurn, math.ZeroInt()) // defensive. - - // we need to calculate the *effective* slash fraction for distribution - if validator.Tokens.IsPositive() { - effectiveFraction := sdk.NewDecFromInt(tokensToBurn).QuoRoundUp(sdk.NewDecFromInt(validator.Tokens)) - // possible if power has changed - if effectiveFraction.GT(math.LegacyOneDec()) { - effectiveFraction = math.LegacyOneDec() - } - // call the before-slashed hook. Omitted to simulate v0.18.0 error bitsong is experiencing - // if err := staking.Hooks().BeforeValidatorSlashed(s.Ctx, operatorAddress, effectiveFraction); err != nil { - // staking.Logger(s.Ctx).Error("failed to call before validator slashed hook", "error", err) - // } - validator = staking.RemoveValidatorTokens(s.Ctx, validator, tokensToBurn) - - if err := s.burnBondedTokens(s.App.AppKeepers, s.Ctx, tokensToBurn); err != nil { - panic(err) + upgradeSetup := func() { + validators := s.App.AppKeepers.StakingKeeper.GetAllValidators(s.Ctx) + for _, val := range validators { + // update the tokens staked to validator due to slashing event + // mimic slashing event on staking power, but not update slashing event to distribution module + val.Tokens = math.LegacyNewDecFromInt(val.Tokens).MulTruncate(math.LegacyOneDec().Sub(math.LegacyNewDecWithPrec(1, 3))).RoundInt() // 1 % slash + + dels := s.App.AppKeepers.StakingKeeper.GetAllDelegations(s.Ctx) + // fmt.Println("Delegations:", dels) + for _, del := range dels { + endingPeriod := s.App.AppKeepers.DistrKeeper.IncrementValidatorPeriod(s.Ctx, val) + // assert v018 bug is present prior to upgrade + assertPanic(s.T(), func() { + s.App.AppKeepers.DistrKeeper.CalculateDelegationRewards(s.Ctx, val, del, endingPeriod) + }) + } } } - // Deduct from validator's bonded tokens and update the validator. - // Burn the slashed tokens from the pool account and decrease the total supply. - -} - -func (s *UpgradeTestSuite) ensurePreUpgradeDistributionPanics() { - // calculate rewards from distribution + testCases := []struct { + name string + pre_upgrade func() + upgrade func() + post_upgrade func() + }{ + { + "Test that the upgrade succeeds", + func() { + upgradeSetup() + }, + func() { + s.Ctx = s.Ctx.WithBlockHeight(dummyUpgradeHeight - 1) + plan := upgradetypes.Plan{Name: "v020", Height: dummyUpgradeHeight} + err := s.App.AppKeepers.UpgradeKeeper.ScheduleUpgrade(s.Ctx, plan) + s.Require().NoError(err) + _, exists := s.App.AppKeepers.UpgradeKeeper.GetUpgradePlan(s.Ctx) + s.Require().True(exists) + + s.Ctx = s.Ctx.WithBlockHeight(dummyUpgradeHeight) + s.Require().NotPanics(func() { + beginBlockRequest := abcitypes.RequestBeginBlock{} + s.App.BeginBlocker(s.Ctx, beginBlockRequest) + }) + }, + func() { + // assert rewards can be calculated + validators := s.App.AppKeepers.StakingKeeper.GetAllValidators(s.Ctx) + for _, val := range validators { + dels := s.App.AppKeepers.StakingKeeper.GetAllDelegations(s.Ctx) + fmt.Println("Delegations:", dels) + for _, del := range dels { + // confirm delegators can query, withdraw and stake + // require all rewards to have been claimed for this delegator + // confirm delegators claimed tokens was accurate + endingPeriod := s.App.AppKeepers.DistrKeeper.IncrementValidatorPeriod(s.Ctx, val) + rewards := s.App.AppKeepers.DistrKeeper.CalculateDelegationRewards(s.Ctx, val, del, endingPeriod) + fmt.Println("rewards:", rewards) + s.Ctx = s.Ctx.WithBlockHeight(dummyUpgradeHeight + 10) + s.StakingHelper.Delegate(del.GetDelegatorAddr(), del.GetValidatorAddr(), math.NewInt(1000000)) + s.Ctx = s.Ctx.WithBlockHeight(dummyUpgradeHeight + 10) + withdraw, err := s.App.AppKeepers.DistrKeeper.WithdrawDelegationRewards(s.Ctx, del.GetDelegatorAddr(), del.GetValidatorAddr()) + s.Require().NoError(err) + fmt.Println("withdraw:", withdraw) + } + } + + }, + }, + } - // expect difference due to unregistered slashing event from validator -} + for _, tc := range testCases { + s.Run(fmt.Sprintf("Case %s", tc.name), func() { + s.SetupTest() // reset -// burnBondedTokens removes coins from the bonded pool module account -func (s *UpgradeTestSuite) burnBondedTokens(k keepers.AppKeepers, ctx sdk.Context, amt math.Int) error { - if !amt.IsPositive() { - // skip as no coins need to be burned - return nil + tc.pre_upgrade() + tc.upgrade() + tc.post_upgrade() + }) } +} - coins := sdk.NewCoins(sdk.NewCoin(k.StakingKeeper.BondDenom(ctx), amt)) - return k.BankKeeper.BurnCoins(ctx, types.BondedPoolName, coins) +func assertPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r != nil { + return + } + t.Errorf("Expected panic did not occur") + t.FailNow() + }() + f() }