From af206bd0ed9450675a50fd0a21dc5fa0d697f638 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 20:50:30 +0200 Subject: [PATCH] Update stake simulation --- x/stake/simulation/invariants.go | 76 ++++++++++++++++++++ x/stake/simulation/sim_test.go | 115 ++++++++----------------------- 2 files changed, 105 insertions(+), 86 deletions(-) create mode 100644 x/stake/simulation/invariants.go diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go new file mode 100644 index 000000000..08567bb4e --- /dev/null +++ b/x/stake/simulation/invariants.go @@ -0,0 +1,76 @@ +package simulation + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" + "github.com/cosmos/cosmos-sdk/x/stake" + abci "github.com/tendermint/tendermint/abci/types" +) + +// AllInvariants runs all invariants of the stake module. +// Currently: total supply, positive power +func AllInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simulation.Invariant { + return func(t *testing.T, app *baseapp.BaseApp, log string) { + SupplyInvariants(ck, k, am)(t, app, log) + PositivePowerInvariant(k)(t, app, log) + ValidatorSetInvariant(k)(t, app, log) + } +} + +// SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations +func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) simulation.Invariant { + return func(t *testing.T, app *baseapp.BaseApp, log string) { + ctx := app.NewContext(false, abci.Header{}) + pool := k.GetPool(ctx) + + // Loose tokens should equal coin supply + loose := sdk.ZeroInt() + am.IterateAccounts(ctx, func(acc auth.Account) bool { + loose = loose.Add(acc.GetCoins().AmountOf("steak")) + return false + }) + require.True(t, pool.LooseTokens.RoundInt64() == loose.Int64(), "expected loose tokens to equal total steak held by accounts - pool.LooseTokens: %v, sum of account tokens: %v\nlog: %s", + pool.LooseTokens, loose, log) + // stats["stake/invariant/looseTokens"] += 1 + + // Bonded tokens should equal sum of tokens with bonded validators + bonded := sdk.ZeroRat() + k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { + switch validator.GetStatus() { + case sdk.Bonded: + bonded = bonded.Add(validator.GetPower()) + } + return false + }) + require.True(t, pool.BondedTokens.Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators\nlog: %s", log) + // stats["stake/invariant/bondedTokens"] += 1 + + // TODO Inflation check on total supply + } +} + +// PositivePowerInvariant checks that all stored validators have > 0 power +func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { + return func(t *testing.T, app *baseapp.BaseApp, log string) { + ctx := app.NewContext(false, abci.Header{}) + k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool { + require.True(t, validator.GetPower().GT(sdk.ZeroRat()), "validator with non-positive power stored") + return false + }) + // stats["stake/invariant/positivePower"] += 1 + } +} + +// ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set +func ValidatorSetInvariant(k stake.Keeper) simulation.Invariant { + return func(t *testing.T, app *baseapp.BaseApp, log string) { + // TODO + } +} diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 0280bab70..8e5a48a21 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -1,16 +1,19 @@ package simulation import ( + "encoding/json" "fmt" "math/rand" "testing" "github.com/stretchr/testify/require" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" "github.com/cosmos/cosmos-sdk/x/stake" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" @@ -20,73 +23,12 @@ var ( stats = make(map[string]int) ) -// ModuleInvariants runs all invariants of the stake module. -// Currently: total supply, positive power -func ModuleInvariants(ck bank.Keeper, k stake.Keeper) mock.Invariant { - return func(t *testing.T, app *mock.App, log string) { - SupplyInvariants(ck, k)(t, app, log) - PositivePowerInvariant(k)(t, app, log) - ValidatorSetInvariant(k)(t, app, log) - } -} - -// SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations -func SupplyInvariants(ck bank.Keeper, k stake.Keeper) mock.Invariant { - return func(t *testing.T, app *mock.App, log string) { - ctx := app.NewContext(false, abci.Header{}) - pool := k.GetPool(ctx) - - // Loose tokens should equal coin supply - loose := sdk.ZeroInt() - app.AccountMapper.IterateAccounts(ctx, func(acc auth.Account) bool { - loose = loose.Add(acc.GetCoins().AmountOf("steak")) - return false - }) - require.True(t, pool.LooseTokens.RoundInt64() == loose.Int64(), "expected loose tokens to equal total steak held by accounts - pool.LooseTokens: %v, sum of account tokens: %v\nlog: %s", - pool.LooseTokens, loose, log) - stats["stake/invariant/looseTokens"] += 1 - - // Bonded tokens should equal sum of tokens with bonded validators - bonded := sdk.ZeroRat() - k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { - switch validator.GetStatus() { - case sdk.Bonded: - bonded = bonded.Add(validator.GetPower()) - } - return false - }) - require.True(t, pool.BondedTokens.Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators\nlog: %s", log) - stats["stake/invariant/bondedTokens"] += 1 - - // TODO Inflation check on total supply - } -} - -// PositivePowerInvariant checks that all stored validators have > 0 power -func PositivePowerInvariant(k stake.Keeper) mock.Invariant { - return func(t *testing.T, app *mock.App, log string) { - ctx := app.NewContext(false, abci.Header{}) - k.IterateValidatorsBonded(ctx, func(_ int64, validator sdk.Validator) bool { - require.True(t, validator.GetPower().GT(sdk.ZeroRat()), "validator with non-positive power stored") - return false - }) - stats["stake/invariant/positivePower"] += 1 - } -} - -// ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set -func ValidatorSetInvariant(k stake.Keeper) mock.Invariant { - return func(t *testing.T, app *mock.App, log string) { - // TODO - } -} - // SimulateMsgCreateValidator -func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg { - return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { +func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom description := stake.Description{ - Moniker: mock.RandStringOfLength(r, 10), + Moniker: simulation.RandStringOfLength(r, 10), } key := keys[r.Intn(len(keys))] pubkey := key.PubKey() @@ -119,13 +61,13 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) mock.TestA } // SimulateMsgEditValidator -func SimulateMsgEditValidator(k stake.Keeper) mock.TestAndRunMsg { - return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { +func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { description := stake.Description{ - Moniker: mock.RandStringOfLength(r, 10), - Identity: mock.RandStringOfLength(r, 10), - Website: mock.RandStringOfLength(r, 10), - Details: mock.RandStringOfLength(r, 10), + Moniker: simulation.RandStringOfLength(r, 10), + Identity: simulation.RandStringOfLength(r, 10), + Website: simulation.RandStringOfLength(r, 10), + Details: simulation.RandStringOfLength(r, 10), } key := keys[r.Intn(len(keys))] pubkey := key.PubKey() @@ -147,8 +89,8 @@ func SimulateMsgEditValidator(k stake.Keeper) mock.TestAndRunMsg { } // SimulateMsgDelegate -func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg { - return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { +func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := keys[r.Intn(len(keys))] validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -179,8 +121,8 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMs } // SimulateMsgBeginUnbonding -func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg { - return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { +func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := keys[r.Intn(len(keys))] validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -211,8 +153,8 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) mock.TestAn } // SimulateMsgCompleteUnbonding -func SimulateMsgCompleteUnbonding(k stake.Keeper) mock.TestAndRunMsg { - return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { +func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { validatorKey := keys[r.Intn(len(keys))] validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) delegatorKey := keys[r.Intn(len(keys))] @@ -234,8 +176,8 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) mock.TestAndRunMsg { } // SimulateMsgBeginRedelegate -func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg { - return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { +func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom sourceValidatorKey := keys[r.Intn(len(keys))] sourceValidatorAddress := sdk.AccAddress(sourceValidatorKey.PubKey().Address()) @@ -270,8 +212,8 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) mock.TestA } // SimulateMsgCompleteRedelegate -func SimulateMsgCompleteRedelegate(k stake.Keeper) mock.TestAndRunMsg { - return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { +func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { validatorSrcKey := keys[r.Intn(len(keys))] validatorSrcAddress := sdk.AccAddress(validatorSrcKey.PubKey().Address()) validatorDstKey := keys[r.Intn(len(keys))] @@ -296,7 +238,7 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) mock.TestAndRunMsg { } // SimulationSetup -func SimulationSetup(mapp *mock.App, k stake.Keeper) mock.RandSetup { +func SimulationSetup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { return func(r *rand.Rand, privKeys []crypto.PrivKey) { ctx := mapp.NewContext(false, abci.Header{}) stake.InitGenesis(ctx, k, stake.DefaultGenesisState()) @@ -338,8 +280,9 @@ func TestStakeWithRandomMessages(t *testing.T) { panic(err) } - mapp.SimpleRandomizedTestingFromSeed( - t, 20, []mock.TestAndRunMsg{ + simulation.Simulate( + t, mapp.BaseApp, json.RawMessage("{}"), + []simulation.TestAndRunTx{ SimulateMsgCreateValidator(mapper, stakeKeeper), SimulateMsgEditValidator(stakeKeeper), SimulateMsgDelegate(mapper, stakeKeeper), @@ -348,10 +291,10 @@ func TestStakeWithRandomMessages(t *testing.T) { SimulateMsgCompleteUnbonding(stakeKeeper), SimulateMsgBeginRedelegate(mapper, stakeKeeper), SimulateMsgCompleteRedelegate(stakeKeeper), - }, []mock.RandSetup{ + }, []simulation.RandSetup{ SimulationSetup(mapp, stakeKeeper), - }, []mock.Invariant{ - ModuleInvariants(coinKeeper, stakeKeeper), + }, []simulation.Invariant{ + AllInvariants(coinKeeper, stakeKeeper, mapp.AccountMapper), }, 10, 100, 500, )