From 6a119f69349c202d8ffefe0ae2f1d1bf47392882 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 10 Jul 2018 20:46:28 +0200 Subject: [PATCH 01/34] Work-in-progress staking invariants --- x/mock/app.go | 3 +- x/mock/random_simulate_blocks.go | 43 +++++++++++++- x/mock/types.go | 9 +++ x/stake/simulation_test.go | 99 ++++++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 x/stake/simulation_test.go diff --git a/x/mock/app.go b/x/mock/app.go index dd8edb102f8a..45df31dfd77e 100644 --- a/x/mock/app.go +++ b/x/mock/app.go @@ -202,8 +202,7 @@ func RandomSetGenesis(r *rand.Rand, app *App, addrs []sdk.Address, denoms []stri (&baseAcc).SetCoins(coins) accts[i] = &baseAcc } - - SetGenesis(app, accts) + app.GenesisAccounts = accts } // GetAllAccounts returns all accounts in the accountMapper. diff --git a/x/mock/random_simulate_blocks.go b/x/mock/random_simulate_blocks.go index a37913065d1e..8d167f45e0a7 100644 --- a/x/mock/random_simulate_blocks.go +++ b/x/mock/random_simulate_blocks.go @@ -31,11 +31,13 @@ func (app *App) RandomizedTestingFromSeed( keys, addrs := GeneratePrivKeyAddressPairs(numKeys) r := rand.New(rand.NewSource(seed)) + RandomSetGenesis(r, app, addrs, []string{"foocoin"}) + app.InitChain(abci.RequestInitChain{}) for i := 0; i < len(setups); i++ { setups[i](r, keys) } + app.Commit() - RandomSetGenesis(r, app, addrs, []string{"foocoin"}) header := abci.Header{Height: 0} for i := 0; i < numBlocks; i++ { @@ -62,6 +64,45 @@ func (app *App) RandomizedTestingFromSeed( } } +func (app *App) SimpleRandomizedTestingFromSeed( + t *testing.T, seed int64, ops []TestAndRunMsg, setups []RandSetup, + invariants []Invariant, numKeys int, numBlocks int, blockSize int, +) { + log := fmt.Sprintf("Starting SimpleSingleModuleTest with randomness created with seed %d", int(seed)) + keys, addrs := GeneratePrivKeyAddressPairs(numKeys) + r := rand.New(rand.NewSource(seed)) + + RandomSetGenesis(r, app, addrs, []string{"foocoin"}) + app.InitChain(abci.RequestInitChain{}) + for i := 0; i < len(setups); i++ { + setups[i](r, keys) + } + app.Commit() + + header := abci.Header{Height: 0} + + for i := 0; i < numBlocks; i++ { + app.BeginBlock(abci.RequestBeginBlock{}) + + app.assertAllInvariants(t, invariants, log) + + ctx := app.NewContext(false, header) + + // TODO: Add modes to simulate "no load", "medium load", and + // "high load" blocks. + for j := 0; j < blockSize; j++ { + logUpdate, err := ops[r.Intn(len(ops))](t, r, ctx, keys, log) + log += "\n" + logUpdate + + require.Nil(t, err, log) + app.assertAllInvariants(t, invariants, log) + } + + app.EndBlock(abci.RequestEndBlock{}) + header.Height++ + } +} + func (app *App) assertAllInvariants(t *testing.T, tests []Invariant, log string) { for i := 0; i < len(tests); i++ { tests[i](t, app, log) diff --git a/x/mock/types.go b/x/mock/types.go index 50957e1c4669..8a1c3d3e88de 100644 --- a/x/mock/types.go +++ b/x/mock/types.go @@ -17,6 +17,15 @@ type ( privKeys []crypto.PrivKey, log string, ) (action string, err sdk.Error) + // TestAndRunMsg produces a fuzzed message, calls the appropriate handler + // (bypassing all ante handler checks), and ensures that the state + // transition was as expected. It returns a descriptive message "action" + // about what this fuzzed msg actually did for ease of debugging. + TestAndRunMsg func( + t *testing.T, r *rand.Rand, ctx sdk.Context, + privKey []crypto.PrivKey, log string, + ) (action string, err sdk.Error) + // RandSetup performs the random setup the mock module needs. RandSetup func(r *rand.Rand, privKeys []crypto.PrivKey) diff --git a/x/stake/simulation_test.go b/x/stake/simulation_test.go new file mode 100644 index 000000000000..8a59d9aa03c5 --- /dev/null +++ b/x/stake/simulation_test.go @@ -0,0 +1,99 @@ +package stake + +import ( + // "errors" + "fmt" + "math/rand" + "testing" + + "github.com/stretchr/testify/require" + + 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" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" +) + +// ModuleInvariants runs all invariants of the stake module. +// Currently: total supply, positive power +func ModuleInvariants(ck bank.Keeper, k Keeper) mock.Invariant { + return func(t *testing.T, app *mock.App, log string) { + TotalSupplyInvariant(ck, k)(t, app, log) + PositivePowerInvariant(k)(t, app, log) + } +} + +// TotalSupplyInvariant checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations +func TotalSupplyInvariant(ck bank.Keeper, k 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, sdk.NewInt(pool.LooseTokens).Equal(loose), "expected loose tokens to equal total steak held by accounts") + + // Bonded tokens should equal sum of tokens with bonded validators + + // Unbonded tokens should equal sum of tokens with unbonded validators + } +} + +// PositivePowerInvariant checks that all stored validators have > 0 power +func PositivePowerInvariant(k 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 + }) + } +} + +// SimulateMsgDelegate +func SimulateMsgDelegate(k Keeper) mock.TestAndRunMsg { + return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + msg := fmt.Sprintf("TestMsgDelegate with %s", "ok") + return msg, nil + } +} + +// SimulationSetup +func SimulationSetup(mapp *mock.App, k Keeper) mock.RandSetup { + return func(r *rand.Rand, privKeys []crypto.PrivKey) { + ctx := mapp.NewContext(false, abci.Header{}) + InitGenesis(ctx, k, DefaultGenesisState()) + } +} + +// Test random messages +func TestStakeWithRandomMessages(t *testing.T) { + mapp := mock.NewApp() + + bank.RegisterWire(mapp.Cdc) + coinKeeper := bank.NewKeeper(mapp.AccountMapper) + stakeKey := sdk.NewKVStoreKey("stake") + stakeKeeper := NewKeeper(mapp.Cdc, stakeKey, coinKeeper, DefaultCodespace) + mapp.Router().AddRoute("stake", NewHandler(stakeKeeper)) + + err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey}) + if err != nil { + panic(err) + } + + mapp.SimpleRandomizedTestingFromSeed( + t, 20, []mock.TestAndRunMsg{ + SimulateMsgDelegate(stakeKeeper), + }, []mock.RandSetup{ + SimulationSetup(mapp, stakeKeeper), + }, []mock.Invariant{ + ModuleInvariants(coinKeeper, stakeKeeper), + }, 10, 100, 500, + ) +} From 940cfa98af3cc5adac7cebe2644398f0f2d1206f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 11 Jul 2018 02:36:50 +0200 Subject: [PATCH 02/34] Invariants & random Msgs in progress --- types/coin.go | 6 +- x/mock/random_simulate_blocks.go | 26 +++++++ x/stake/simulation_test.go | 123 +++++++++++++++++++++++++++++-- 3 files changed, 148 insertions(+), 7 deletions(-) diff --git a/types/coin.go b/types/coin.go index eba645932146..862614ca0753 100644 --- a/types/coin.go +++ b/types/coin.go @@ -15,9 +15,13 @@ type Coin struct { } func NewCoin(denom string, amount int64) Coin { + return NewIntCoin(denom, NewInt(amount)) +} + +func NewIntCoin(denom string, amount Int) Coin { return Coin{ Denom: denom, - Amount: NewInt(amount), + Amount: amount, } } diff --git a/x/mock/random_simulate_blocks.go b/x/mock/random_simulate_blocks.go index 4883cb1800ca..452b8f6bf592 100644 --- a/x/mock/random_simulate_blocks.go +++ b/x/mock/random_simulate_blocks.go @@ -135,3 +135,29 @@ func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { return result } + +// shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = r.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + return string(b) +} diff --git a/x/stake/simulation_test.go b/x/stake/simulation_test.go index 8a59d9aa03c5..f9aac426ce23 100644 --- a/x/stake/simulation_test.go +++ b/x/stake/simulation_test.go @@ -20,13 +20,14 @@ import ( // Currently: total supply, positive power func ModuleInvariants(ck bank.Keeper, k Keeper) mock.Invariant { return func(t *testing.T, app *mock.App, log string) { - TotalSupplyInvariant(ck, k)(t, app, log) + SupplyInvariants(ck, k)(t, app, log) PositivePowerInvariant(k)(t, app, log) + ValidatorSetInvariant(k)(t, app, log) } } -// TotalSupplyInvariant checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations -func TotalSupplyInvariant(ck bank.Keeper, k Keeper) mock.Invariant { +// SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations +func SupplyInvariants(ck bank.Keeper, k Keeper) mock.Invariant { return func(t *testing.T, app *mock.App, log string) { ctx := app.NewContext(false, abci.Header{}) pool := k.GetPool(ctx) @@ -40,8 +41,26 @@ func TotalSupplyInvariant(ck bank.Keeper, k Keeper) mock.Invariant { require.True(t, sdk.NewInt(pool.LooseTokens).Equal(loose), "expected loose tokens to equal total steak held by accounts") // Bonded tokens should equal sum of tokens with bonded validators - // Unbonded tokens should equal sum of tokens with unbonded validators + bonded := sdk.ZeroRat() + unbonded := sdk.ZeroRat() + k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { + switch validator.GetStatus() { + case sdk.Bonded: + bonded = bonded.Add(validator.GetPower()) + case sdk.Unbonding: + // TODO + case sdk.Unbonded: + unbonded = unbonded.Add(validator.GetPower()) + } + return false + }) + require.True(t, sdk.NewRat(pool.BondedTokens).Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators") + require.True(t, sdk.NewRat(pool.UnbondedTokens).Equal(unbonded), "expected unbonded tokens to equal total steak held by unbonded validators") + + // TODO Unbonding tokens + + // TODO Inflation check on total supply } } @@ -56,6 +75,59 @@ func PositivePowerInvariant(k Keeper) mock.Invariant { } } +// ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set +func ValidatorSetInvariant(k Keeper) mock.Invariant { + return func(t *testing.T, app *mock.App, log string) { + // TODO + } +} + +// SimulateMsgCreateValidator +func SimulateMsgCreateValidator(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { + return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + denom := k.GetParams(ctx).BondDenom + description := Description{ + Moniker: mock.RandStringOfLength(r, 10), + } + key := keys[r.Intn(len(keys))] + pubkey := key.PubKey() + address := sdk.AccAddress(pubkey.Address()) + amount := m.GetAccount(ctx, address).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + msg := MsgCreateValidator{ + Description: description, + ValidatorAddr: address, + PubKey: pubkey, + SelfDelegation: sdk.NewIntCoin(denom, amount), + } + action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulateMsgEditValidator +func SimulateMsgEditValidator(k Keeper) mock.TestAndRunMsg { + return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + description := Description{ + Moniker: mock.RandStringOfLength(r, 10), + Identity: mock.RandStringOfLength(r, 10), + Website: mock.RandStringOfLength(r, 10), + Details: mock.RandStringOfLength(r, 10), + } + key := keys[r.Intn(len(keys))] + pubkey := key.PubKey() + address := sdk.AccAddress(pubkey.Address()) + msg := MsgEditValidator{ + Description: description, + ValidatorAddr: address, + } + action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes()) + return action, nil + } +} + // SimulateMsgDelegate func SimulateMsgDelegate(k Keeper) mock.TestAndRunMsg { return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { @@ -64,6 +136,38 @@ func SimulateMsgDelegate(k Keeper) mock.TestAndRunMsg { } } +// SimulateMsgBeginUnbonding +func SimulateMsgBeginUnbonding(k Keeper) mock.TestAndRunMsg { + return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + msg := fmt.Sprintf("TestMsgBeginUnbonding with %s", "ok") + return msg, nil + } +} + +// SimulateMsgCompleteUnbonding +func SimulateMsgCompleteUnbonding(k Keeper) mock.TestAndRunMsg { + return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + msg := fmt.Sprintf("TestMsgCompleteUnbonding with %s", "ok") + return msg, nil + } +} + +// SimulateMsgBeginRedelegate +func SimulateMsgBeginRedelegate(k Keeper) mock.TestAndRunMsg { + return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + msg := fmt.Sprintf("TestMsgBeginRedelegate with %s", "ok") + return msg, nil + } +} + +// SimulateMsgCompleteRedelegate +func SimulateMsgCompleteRedelegate(k Keeper) mock.TestAndRunMsg { + return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { + msg := fmt.Sprintf("TestMsgCompleteRedelegate with %s", "ok") + return msg, nil + } +} + // SimulationSetup func SimulationSetup(mapp *mock.App, k Keeper) mock.RandSetup { return func(r *rand.Rand, privKeys []crypto.PrivKey) { @@ -72,12 +176,13 @@ func SimulationSetup(mapp *mock.App, k Keeper) mock.RandSetup { } } -// Test random messages +// TestStakeWithRandomMessages func TestStakeWithRandomMessages(t *testing.T) { mapp := mock.NewApp() bank.RegisterWire(mapp.Cdc) - coinKeeper := bank.NewKeeper(mapp.AccountMapper) + mapper := mapp.AccountMapper + coinKeeper := bank.NewKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") stakeKeeper := NewKeeper(mapp.Cdc, stakeKey, coinKeeper, DefaultCodespace) mapp.Router().AddRoute("stake", NewHandler(stakeKeeper)) @@ -89,7 +194,13 @@ func TestStakeWithRandomMessages(t *testing.T) { mapp.SimpleRandomizedTestingFromSeed( t, 20, []mock.TestAndRunMsg{ + SimulateMsgCreateValidator(mapper, stakeKeeper), + SimulateMsgEditValidator(stakeKeeper), SimulateMsgDelegate(stakeKeeper), + SimulateMsgBeginUnbonding(stakeKeeper), + SimulateMsgCompleteUnbonding(stakeKeeper), + SimulateMsgBeginRedelegate(stakeKeeper), + SimulateMsgCompleteRedelegate(stakeKeeper), }, []mock.RandSetup{ SimulationSetup(mapp, stakeKeeper), }, []mock.Invariant{ From a3d8b38d47c5d01f90030d4b1f9a6ce70a2746fc Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 11 Jul 2018 04:36:12 +0200 Subject: [PATCH 03/34] Add several simulated Msgs --- x/stake/simulation_test.go | 110 ++++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 13 deletions(-) diff --git a/x/stake/simulation_test.go b/x/stake/simulation_test.go index f9aac426ce23..7dacf02b2671 100644 --- a/x/stake/simulation_test.go +++ b/x/stake/simulation_test.go @@ -16,6 +16,10 @@ import ( "github.com/tendermint/tendermint/crypto" ) +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 Keeper) mock.Invariant { @@ -38,7 +42,9 @@ func SupplyInvariants(ck bank.Keeper, k Keeper) mock.Invariant { loose = loose.Add(acc.GetCoins().AmountOf("steak")) return false }) - require.True(t, sdk.NewInt(pool.LooseTokens).Equal(loose), "expected loose tokens to equal total steak held by accounts") + require.True(t, sdk.NewInt(pool.LooseTokens).Equal(loose), "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 // Unbonded tokens should equal sum of tokens with unbonded validators @@ -55,8 +61,10 @@ func SupplyInvariants(ck bank.Keeper, k Keeper) mock.Invariant { } return false }) - require.True(t, sdk.NewRat(pool.BondedTokens).Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators") - require.True(t, sdk.NewRat(pool.UnbondedTokens).Equal(unbonded), "expected unbonded tokens to equal total steak held by unbonded validators") + require.True(t, sdk.NewRat(pool.BondedTokens).Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators\nlog: %s", log) + stats["stake/invariant/bondedTokens"] += 1 + require.True(t, sdk.NewRat(pool.UnbondedTokens).Equal(unbonded), "expected unbonded tokens to equal total steak held by unbonded validators\n log: %s", log) + stats["stake/invariant/unbondedTokens"] += 1 // TODO Unbonding tokens @@ -72,6 +80,7 @@ func PositivePowerInvariant(k Keeper) mock.Invariant { require.True(t, validator.GetPower().GT(sdk.ZeroRat()), "validator with non-positive power stored") return false }) + stats["stake/invariant/positivePower"] += 1 } } @@ -96,12 +105,19 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k Keeper) mock.TestAndRunM if amount.GT(sdk.ZeroInt()) { amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } msg := MsgCreateValidator{ Description: description, ValidatorAddr: address, PubKey: pubkey, SelfDelegation: sdk.NewIntCoin(denom, amount), } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + result := handleMsgCreateValidator(ctx, msg, k) + stats[fmt.Sprintf("stake/createvalidator/%v", result.IsOK())] += 1 + // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes()) return action, nil } @@ -123,21 +139,44 @@ func SimulateMsgEditValidator(k Keeper) mock.TestAndRunMsg { Description: description, ValidatorAddr: address, } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + result := handleMsgEditValidator(ctx, msg, k) + stats[fmt.Sprintf("stake/editvalidator/%v", result.IsOK())] += 1 action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes()) return action, nil } } // SimulateMsgDelegate -func SimulateMsgDelegate(k Keeper) mock.TestAndRunMsg { +func SimulateMsgDelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { - msg := fmt.Sprintf("TestMsgDelegate with %s", "ok") - return msg, nil + denom := k.GetParams(ctx).BondDenom + validatorKey := keys[r.Intn(len(keys))] + validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } + msg := MsgDelegate{ + DelegatorAddr: delegatorAddress, + ValidatorAddr: validatorAddress, + Bond: sdk.NewIntCoin(denom, amount), + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + result := handleMsgDelegate(ctx, msg, k) + stats[fmt.Sprintf("stake/delegate/%v", result.IsOK())] += 1 + action = fmt.Sprintf("TestMsgDelegate: %s", msg.GetSignBytes()) + return action, nil } } // SimulateMsgBeginUnbonding -func SimulateMsgBeginUnbonding(k Keeper) mock.TestAndRunMsg { +func SimulateMsgBeginUnbonding(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { msg := fmt.Sprintf("TestMsgBeginUnbonding with %s", "ok") return msg, nil @@ -153,10 +192,34 @@ func SimulateMsgCompleteUnbonding(k Keeper) mock.TestAndRunMsg { } // SimulateMsgBeginRedelegate -func SimulateMsgBeginRedelegate(k Keeper) mock.TestAndRunMsg { +func SimulateMsgBeginRedelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { - msg := fmt.Sprintf("TestMsgBeginRedelegate with %s", "ok") - return msg, nil + denom := k.GetParams(ctx).BondDenom + sourceValidatorKey := keys[r.Intn(len(keys))] + sourceValidatorAddress := sdk.AccAddress(sourceValidatorKey.PubKey().Address()) + destValidatorKey := keys[r.Intn(len(keys))] + destValidatorAddress := sdk.AccAddress(destValidatorKey.PubKey().Address()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + // TODO + amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } + msg := MsgBeginRedelegate{ + DelegatorAddr: delegatorAddress, + ValidatorSrcAddr: sourceValidatorAddress, + ValidatorDstAddr: destValidatorAddress, + SharesAmount: sdk.NewRatFromInt(amount), + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + result := handleMsgBeginRedelegate(ctx, msg, k) + stats[fmt.Sprintf("stake/beginredelegate/%v", result.IsOK())] += 1 + action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes()) + return action, nil } } @@ -173,6 +236,19 @@ func SimulationSetup(mapp *mock.App, k Keeper) mock.RandSetup { return func(r *rand.Rand, privKeys []crypto.PrivKey) { ctx := mapp.NewContext(false, abci.Header{}) InitGenesis(ctx, k, DefaultGenesisState()) + params := k.GetParams(ctx) + denom := params.BondDenom + loose := sdk.ZeroInt() + mapp.AccountMapper.IterateAccounts(ctx, func(acc auth.Account) bool { + balance := sdk.NewInt(int64(r.Intn(1000000))) + acc.SetCoins(acc.GetCoins().Plus(sdk.Coins{sdk.NewIntCoin(denom, balance)})) + mapp.AccountMapper.SetAccount(ctx, acc) + loose = loose.Add(balance) + return false + }) + pool := k.GetPool(ctx) + pool.LooseTokens += loose.Int64() + k.SetPool(ctx, pool) } } @@ -186,6 +262,12 @@ func TestStakeWithRandomMessages(t *testing.T) { stakeKey := sdk.NewKVStoreKey("stake") stakeKeeper := NewKeeper(mapp.Cdc, stakeKey, coinKeeper, DefaultCodespace) mapp.Router().AddRoute("stake", NewHandler(stakeKeeper)) + mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + validatorUpdates := EndBlocker(ctx, stakeKeeper) + return abci.ResponseEndBlock{ + ValidatorUpdates: validatorUpdates, + } + }) err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey}) if err != nil { @@ -196,10 +278,10 @@ func TestStakeWithRandomMessages(t *testing.T) { t, 20, []mock.TestAndRunMsg{ SimulateMsgCreateValidator(mapper, stakeKeeper), SimulateMsgEditValidator(stakeKeeper), - SimulateMsgDelegate(stakeKeeper), - SimulateMsgBeginUnbonding(stakeKeeper), + SimulateMsgDelegate(mapper, stakeKeeper), + SimulateMsgBeginUnbonding(mapper, stakeKeeper), SimulateMsgCompleteUnbonding(stakeKeeper), - SimulateMsgBeginRedelegate(stakeKeeper), + SimulateMsgBeginRedelegate(mapper, stakeKeeper), SimulateMsgCompleteRedelegate(stakeKeeper), }, []mock.RandSetup{ SimulationSetup(mapp, stakeKeeper), @@ -207,4 +289,6 @@ func TestStakeWithRandomMessages(t *testing.T) { ModuleInvariants(coinKeeper, stakeKeeper), }, 10, 100, 500, ) + + fmt.Printf("Stats: %v\n", stats) } From 27f157a3e213daa55da59b5ae932d595b853fbcc Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 11 Jul 2018 04:55:57 +0200 Subject: [PATCH 04/34] CacheContext() --- x/stake/simulation_test.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/x/stake/simulation_test.go b/x/stake/simulation_test.go index 7dacf02b2671..f9abc606401f 100644 --- a/x/stake/simulation_test.go +++ b/x/stake/simulation_test.go @@ -115,7 +115,11 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k Keeper) mock.TestAndRunM SelfDelegation: sdk.NewIntCoin(denom, amount), } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() result := handleMsgCreateValidator(ctx, msg, k) + if result.IsOK() { + write() + } stats[fmt.Sprintf("stake/createvalidator/%v", result.IsOK())] += 1 // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes()) @@ -140,7 +144,11 @@ func SimulateMsgEditValidator(k Keeper) mock.TestAndRunMsg { ValidatorAddr: address, } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() result := handleMsgEditValidator(ctx, msg, k) + if result.IsOK() { + write() + } stats[fmt.Sprintf("stake/editvalidator/%v", result.IsOK())] += 1 action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes()) return action, nil @@ -168,7 +176,11 @@ func SimulateMsgDelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { Bond: sdk.NewIntCoin(denom, amount), } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() result := handleMsgDelegate(ctx, msg, k) + if result.IsOK() { + write() + } stats[fmt.Sprintf("stake/delegate/%v", result.IsOK())] += 1 action = fmt.Sprintf("TestMsgDelegate: %s", msg.GetSignBytes()) return action, nil @@ -216,7 +228,11 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunM SharesAmount: sdk.NewRatFromInt(amount), } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() result := handleMsgBeginRedelegate(ctx, msg, k) + if result.IsOK() { + write() + } stats[fmt.Sprintf("stake/beginredelegate/%v", result.IsOK())] += 1 action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes()) return action, nil From 0572a2743e2de71b03bafc3df58a96ed0c953fdf Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 11 Jul 2018 19:17:09 +0200 Subject: [PATCH 05/34] Changes from merge --- x/stake/simulation_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/x/stake/simulation_test.go b/x/stake/simulation_test.go index f9abc606401f..b7fe42e3828c 100644 --- a/x/stake/simulation_test.go +++ b/x/stake/simulation_test.go @@ -109,10 +109,11 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k Keeper) mock.TestAndRunM return "nop", nil } msg := MsgCreateValidator{ - Description: description, - ValidatorAddr: address, - PubKey: pubkey, - SelfDelegation: sdk.NewIntCoin(denom, amount), + Description: description, + ValidatorAddr: address, + DelegatorAddr: address, + PubKey: pubkey, + Delegation: sdk.NewIntCoin(denom, amount), } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) ctx, write := ctx.CacheContext() @@ -173,7 +174,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { msg := MsgDelegate{ DelegatorAddr: delegatorAddress, ValidatorAddr: validatorAddress, - Bond: sdk.NewIntCoin(denom, amount), + Delegation: sdk.NewIntCoin(denom, amount), } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) ctx, write := ctx.CacheContext() From 53138fb36f5854466c85a19f587fa9b54361b179 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 11 Jul 2018 19:43:25 +0200 Subject: [PATCH 06/34] 'make test_sim', simulation folder --- Makefile | 8 ++- x/stake/{ => simulation}/simulation_test.go | 59 +++++++++++---------- 2 files changed, 36 insertions(+), 31 deletions(-) rename x/stake/{ => simulation}/simulation_test.go (85%) diff --git a/Makefile b/Makefile index 4d761ede14ce..ce753afefd3d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/') -PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) +PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v '/simulation' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) +PACKAGES_SIMTEST=$(shell go list ./... | grep -v '/vendor/' | grep '/simulation') COMMIT_HASH := $(shell git rev-parse --short HEAD) BUILD_FLAGS = -tags netgo -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" @@ -98,6 +99,9 @@ test_unit: test_race: @go test -race $(PACKAGES_NOCLITEST) +test_sim: + @go test $(PACKAGES_SIMTEST) -v + test_cover: @bash tests/test_cover.sh @@ -181,4 +185,4 @@ remotenet-status: # To avoid unintended conflicts with file names, always add to .PHONY # unless there is a reason not to. # https://www.gnu.org/software/make/manual/html_node/Phony-Targets.html -.PHONY: build build_examples install install_examples install_debug dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update build-linux build-docker-gaiadnode localnet-start localnet-stop remotenet-start remotenet-stop remotenet-status format +.PHONY: build build_examples install install_examples install_debug dist check_tools get_tools get_vendor_deps draw_deps test test_cli test_unit test_cover test_sim test_lint benchmark devdoc_init devdoc devdoc_save devdoc_update build-linux build-docker-gaiadnode localnet-start localnet-stop remotenet-start remotenet-stop remotenet-status format diff --git a/x/stake/simulation_test.go b/x/stake/simulation/simulation_test.go similarity index 85% rename from x/stake/simulation_test.go rename to x/stake/simulation/simulation_test.go index b7fe42e3828c..fd951b2b7482 100644 --- a/x/stake/simulation_test.go +++ b/x/stake/simulation/simulation_test.go @@ -1,7 +1,6 @@ -package stake +package simulation import ( - // "errors" "fmt" "math/rand" "testing" @@ -12,6 +11,7 @@ import ( "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/stake" abci "github.com/tendermint/tendermint/abci/types" "github.com/tendermint/tendermint/crypto" ) @@ -22,7 +22,7 @@ var ( // ModuleInvariants runs all invariants of the stake module. // Currently: total supply, positive power -func ModuleInvariants(ck bank.Keeper, k Keeper) mock.Invariant { +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) @@ -31,7 +31,7 @@ func ModuleInvariants(ck bank.Keeper, k Keeper) mock.Invariant { } // SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations -func SupplyInvariants(ck bank.Keeper, k Keeper) mock.Invariant { +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) @@ -73,7 +73,7 @@ func SupplyInvariants(ck bank.Keeper, k Keeper) mock.Invariant { } // PositivePowerInvariant checks that all stored validators have > 0 power -func PositivePowerInvariant(k Keeper) mock.Invariant { +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 { @@ -85,17 +85,17 @@ func PositivePowerInvariant(k Keeper) mock.Invariant { } // ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set -func ValidatorSetInvariant(k Keeper) mock.Invariant { +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 Keeper) mock.TestAndRunMsg { +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) { denom := k.GetParams(ctx).BondDenom - description := Description{ + description := stake.Description{ Moniker: mock.RandStringOfLength(r, 10), } key := keys[r.Intn(len(keys))] @@ -108,7 +108,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k Keeper) mock.TestAndRunM if amount.Equal(sdk.ZeroInt()) { return "nop", nil } - msg := MsgCreateValidator{ + msg := stake.MsgCreateValidator{ Description: description, ValidatorAddr: address, DelegatorAddr: address, @@ -117,7 +117,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k Keeper) mock.TestAndRunM } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) ctx, write := ctx.CacheContext() - result := handleMsgCreateValidator(ctx, msg, k) + result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { write() } @@ -129,9 +129,9 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k Keeper) mock.TestAndRunM } // SimulateMsgEditValidator -func SimulateMsgEditValidator(k Keeper) mock.TestAndRunMsg { +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) { - description := Description{ + description := stake.Description{ Moniker: mock.RandStringOfLength(r, 10), Identity: mock.RandStringOfLength(r, 10), Website: mock.RandStringOfLength(r, 10), @@ -140,13 +140,13 @@ func SimulateMsgEditValidator(k Keeper) mock.TestAndRunMsg { key := keys[r.Intn(len(keys))] pubkey := key.PubKey() address := sdk.AccAddress(pubkey.Address()) - msg := MsgEditValidator{ + msg := stake.MsgEditValidator{ Description: description, ValidatorAddr: address, } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) ctx, write := ctx.CacheContext() - result := handleMsgEditValidator(ctx, msg, k) + result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { write() } @@ -157,7 +157,7 @@ func SimulateMsgEditValidator(k Keeper) mock.TestAndRunMsg { } // SimulateMsgDelegate -func SimulateMsgDelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { +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) { denom := k.GetParams(ctx).BondDenom validatorKey := keys[r.Intn(len(keys))] @@ -171,14 +171,14 @@ func SimulateMsgDelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { if amount.Equal(sdk.ZeroInt()) { return "nop", nil } - msg := MsgDelegate{ + msg := stake.MsgDelegate{ DelegatorAddr: delegatorAddress, ValidatorAddr: validatorAddress, Delegation: sdk.NewIntCoin(denom, amount), } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) ctx, write := ctx.CacheContext() - result := handleMsgDelegate(ctx, msg, k) + result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { write() } @@ -189,7 +189,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { } // SimulateMsgBeginUnbonding -func SimulateMsgBeginUnbonding(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { +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) { msg := fmt.Sprintf("TestMsgBeginUnbonding with %s", "ok") return msg, nil @@ -197,7 +197,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k Keeper) mock.TestAndRunMs } // SimulateMsgCompleteUnbonding -func SimulateMsgCompleteUnbonding(k Keeper) mock.TestAndRunMsg { +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) { msg := fmt.Sprintf("TestMsgCompleteUnbonding with %s", "ok") return msg, nil @@ -205,7 +205,7 @@ func SimulateMsgCompleteUnbonding(k Keeper) mock.TestAndRunMsg { } // SimulateMsgBeginRedelegate -func SimulateMsgBeginRedelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunMsg { +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) { denom := k.GetParams(ctx).BondDenom sourceValidatorKey := keys[r.Intn(len(keys))] @@ -222,7 +222,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunM if amount.Equal(sdk.ZeroInt()) { return "nop", nil } - msg := MsgBeginRedelegate{ + msg := stake.MsgBeginRedelegate{ DelegatorAddr: delegatorAddress, ValidatorSrcAddr: sourceValidatorAddress, ValidatorDstAddr: destValidatorAddress, @@ -230,7 +230,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunM } require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) ctx, write := ctx.CacheContext() - result := handleMsgBeginRedelegate(ctx, msg, k) + result := stake.NewHandler(k)(ctx, msg) if result.IsOK() { write() } @@ -241,7 +241,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k Keeper) mock.TestAndRunM } // SimulateMsgCompleteRedelegate -func SimulateMsgCompleteRedelegate(k Keeper) mock.TestAndRunMsg { +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) { msg := fmt.Sprintf("TestMsgCompleteRedelegate with %s", "ok") return msg, nil @@ -249,10 +249,10 @@ func SimulateMsgCompleteRedelegate(k Keeper) mock.TestAndRunMsg { } // SimulationSetup -func SimulationSetup(mapp *mock.App, k Keeper) mock.RandSetup { +func SimulationSetup(mapp *mock.App, k stake.Keeper) mock.RandSetup { return func(r *rand.Rand, privKeys []crypto.PrivKey) { ctx := mapp.NewContext(false, abci.Header{}) - InitGenesis(ctx, k, DefaultGenesisState()) + stake.InitGenesis(ctx, k, stake.DefaultGenesisState()) params := k.GetParams(ctx) denom := params.BondDenom loose := sdk.ZeroInt() @@ -277,10 +277,10 @@ func TestStakeWithRandomMessages(t *testing.T) { mapper := mapp.AccountMapper coinKeeper := bank.NewKeeper(mapper) stakeKey := sdk.NewKVStoreKey("stake") - stakeKeeper := NewKeeper(mapp.Cdc, stakeKey, coinKeeper, DefaultCodespace) - mapp.Router().AddRoute("stake", NewHandler(stakeKeeper)) + stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, coinKeeper, stake.DefaultCodespace) + mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper)) mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { - validatorUpdates := EndBlocker(ctx, stakeKeeper) + validatorUpdates := stake.EndBlocker(ctx, stakeKeeper) return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, } @@ -298,7 +298,8 @@ func TestStakeWithRandomMessages(t *testing.T) { SimulateMsgDelegate(mapper, stakeKeeper), SimulateMsgBeginUnbonding(mapper, stakeKeeper), SimulateMsgCompleteUnbonding(stakeKeeper), - SimulateMsgBeginRedelegate(mapper, stakeKeeper), + // XXX TODO Bug found! + // SimulateMsgBeginRedelegate(mapper, stakeKeeper), SimulateMsgCompleteRedelegate(stakeKeeper), }, []mock.RandSetup{ SimulationSetup(mapp, stakeKeeper), From 88364c838eaf14f171df403dcd2c6c37e4431ef1 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 11 Jul 2018 19:51:04 +0200 Subject: [PATCH 07/34] TestMsgBeginUnbonding --- x/stake/simulation/simulation_test.go | 28 +++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/x/stake/simulation/simulation_test.go b/x/stake/simulation/simulation_test.go index fd951b2b7482..626108236642 100644 --- a/x/stake/simulation/simulation_test.go +++ b/x/stake/simulation/simulation_test.go @@ -191,8 +191,32 @@ 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) { - msg := fmt.Sprintf("TestMsgBeginUnbonding with %s", "ok") - return msg, nil + denom := k.GetParams(ctx).BondDenom + validatorKey := keys[r.Intn(len(keys))] + validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } + msg := stake.MsgBeginUnbonding{ + DelegatorAddr: delegatorAddress, + ValidatorAddr: validatorAddress, + SharesAmount: sdk.NewRatFromInt(amount), + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + stats[fmt.Sprintf("stake/beginunbonding/%v", result.IsOK())] += 1 + action = fmt.Sprintf("TestMsgBeginUnbonding: %s", msg.GetSignBytes()) + return action, nil } } From 4623923fb877def1d7995841b92895a0aca30cbc Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 11 Jul 2018 22:44:21 +0200 Subject: [PATCH 08/34] Add 'test_sim' to CircleCI --- .circleci/config.yml | 19 ++++++++++++ x/stake/simulation/simulation_test.go | 44 ++++++++++++++++++++++++--- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 155945ae6917..b12fd8a3a81e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -85,6 +85,22 @@ jobs: export PATH="$GOBIN:$PATH" make test_cli + test_sim: + <<: *defaults + parallelism: 1 + steps: + - attach_workspace: + at: /tmp/workspace + - restore_cache: + key: v1-pkg-cache + - restore_cache: + key: v1-tree-{{ .Environment.CIRCLE_SHA1 }} + - run: + name: Test simulation + command: | + export PATH="$GOBIN:$PATH" + make test_sim + test_cover: <<: *defaults parallelism: 2 @@ -145,6 +161,9 @@ workflows: - test_cli: requires: - setup_dependencies + - test_sim: + requires: + - setup_dependencies - test_cover: requires: - setup_dependencies diff --git a/x/stake/simulation/simulation_test.go b/x/stake/simulation/simulation_test.go index 626108236642..621e2c5b2fee 100644 --- a/x/stake/simulation/simulation_test.go +++ b/x/stake/simulation/simulation_test.go @@ -223,8 +223,23 @@ 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) { - msg := fmt.Sprintf("TestMsgCompleteUnbonding with %s", "ok") - return msg, nil + validatorKey := keys[r.Intn(len(keys))] + validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + msg := stake.MsgCompleteUnbonding{ + DelegatorAddr: delegatorAddress, + ValidatorAddr: validatorAddress, + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + stats[fmt.Sprintf("stake/completeunbonding/%v", result.IsOK())] += 1 + action = fmt.Sprintf("TestMsgCompleteUnbonding with %s", msg.GetSignBytes()) + return action, nil } } @@ -267,8 +282,26 @@ 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) { - msg := fmt.Sprintf("TestMsgCompleteRedelegate with %s", "ok") - return msg, nil + validatorSrcKey := keys[r.Intn(len(keys))] + validatorSrcAddress := sdk.AccAddress(validatorSrcKey.PubKey().Address()) + validatorDstKey := keys[r.Intn(len(keys))] + validatorDstAddress := sdk.AccAddress(validatorDstKey.PubKey().Address()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + msg := stake.MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddress, + ValidatorSrcAddr: validatorSrcAddress, + ValidatorDstAddr: validatorDstAddress, + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + stats[fmt.Sprintf("stake/completeredelegate/%v", result.IsOK())] += 1 + action = fmt.Sprintf("TestMsgCompleteRedelegate with %s", msg.GetSignBytes()) + return action, nil } } @@ -320,7 +353,8 @@ func TestStakeWithRandomMessages(t *testing.T) { SimulateMsgCreateValidator(mapper, stakeKeeper), SimulateMsgEditValidator(stakeKeeper), SimulateMsgDelegate(mapper, stakeKeeper), - SimulateMsgBeginUnbonding(mapper, stakeKeeper), + // XXX TODO + // SimulateMsgBeginUnbonding(mapper, stakeKeeper), SimulateMsgCompleteUnbonding(stakeKeeper), // XXX TODO Bug found! // SimulateMsgBeginRedelegate(mapper, stakeKeeper), From f9f326cefb18cb0fa9689121a8bc6890616ffe18 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 12 Jul 2018 00:14:37 +0200 Subject: [PATCH 09/34] Move files --- cmd/gaia/simulation/sim_test.go | 11 +++++++++++ .../simulation/{simulation_test.go => sim_test.go} | 0 2 files changed, 11 insertions(+) create mode 100644 cmd/gaia/simulation/sim_test.go rename x/stake/simulation/{simulation_test.go => sim_test.go} (100%) diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/simulation/sim_test.go new file mode 100644 index 000000000000..77940fba7818 --- /dev/null +++ b/cmd/gaia/simulation/sim_test.go @@ -0,0 +1,11 @@ +package simulation + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestFullGaiaSimulation(t *testing.T) { + require.True(t, true, "should not happen") +} diff --git a/x/stake/simulation/simulation_test.go b/x/stake/simulation/sim_test.go similarity index 100% rename from x/stake/simulation/simulation_test.go rename to x/stake/simulation/sim_test.go From 601251d9b8e2307b5dd22e9c00689d8bb545326b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 12 Jul 2018 21:31:24 +0200 Subject: [PATCH 10/34] Update Gopkg.lock & Makefile --- Gopkg.lock | 181 ++++++++++++++++++++++++++++++++++++++++++++++++----- Makefile | 2 +- 2 files changed, 165 insertions(+), 18 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index a73a2009a52b..eaf4d82dd6fa 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,59 +3,78 @@ [[projects]] branch = "master" + digest = "1:09a7f74eb6bb3c0f14d8926610c87f569c5cff68e978d30e9a3540aeb626fdf0" name = "github.com/bartekn/go-bip39" packages = ["."] + pruneopts = "UT" revision = "a05967ea095d81c8fe4833776774cfaff8e5036c" [[projects]] branch = "master" + digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d" name = "github.com/beorn7/perks" packages = ["quantile"] + pruneopts = "UT" revision = "3a771d992973f24aa725d07868b467d1ddfceafb" [[projects]] + digest = "1:1343a2963481a305ca4d051e84bc2abd16b601ee22ed324f8d605de1adb291b0" name = "github.com/bgentry/speakeasy" packages = ["."] + pruneopts = "UT" revision = "4aabc24848ce5fd31929f7d1e4ea74d3709c14cd" version = "v0.1.0" [[projects]] branch = "master" + digest = "1:70f6b224a59b2fa453debffa85c77f71063d8754b90c8c4fbad5794e2c382b0f" name = "github.com/brejski/hid" packages = ["."] + pruneopts = "UT" revision = "06112dcfcc50a7e0e4fd06e17f9791e788fdaafc" [[projects]] branch = "master" + digest = "1:6aabc1566d6351115d561d038da82a4c19b46c3b6e17f4a0a2fa60260663dc79" name = "github.com/btcsuite/btcd" packages = ["btcec"] + pruneopts = "UT" revision = "fdfc19097e7ac6b57035062056f5b7b4638b8898" [[projects]] branch = "master" + digest = "1:386de157f7d19259a7f9c81f26ce011223ce0f090353c1152ffdf730d7d10ac2" name = "github.com/btcsuite/btcutil" packages = ["bech32"] + pruneopts = "UT" revision = "ab6388e0c60ae4834a1f57511e20c17b5f78be4b" [[projects]] + digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39" name = "github.com/davecgh/go-spew" packages = ["spew"] + pruneopts = "UT" revision = "346938d642f2ec3594ed81d874461961cd0faa76" version = "v1.1.0" [[projects]] branch = "master" + digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b" name = "github.com/ebuchman/fail-test" packages = ["."] + pruneopts = "UT" revision = "95f809107225be108efcf10a3509e4ea6ceef3c4" [[projects]] + digest = "1:abeb38ade3f32a92943e5be54f55ed6d6e3b6602761d74b4aab4c9dd45c18abd" name = "github.com/fsnotify/fsnotify" packages = ["."] + pruneopts = "UT" revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9" version = "v1.4.7" [[projects]] + digest = "1:fa30c0652956e159cdb97dcb2ef8b8db63ed668c02a5c3a40961c8f0641252fe" name = "github.com/go-kit/kit" packages = [ "log", @@ -64,24 +83,30 @@ "metrics", "metrics/discard", "metrics/internal/lv", - "metrics/prometheus" + "metrics/prometheus", ] + pruneopts = "UT" revision = "4dc7be5d2d12881735283bcab7352178e190fc71" version = "v0.6.0" [[projects]] + digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659" name = "github.com/go-logfmt/logfmt" packages = ["."] + pruneopts = "UT" revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5" version = "v0.3.0" [[projects]] + digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406" name = "github.com/go-stack/stack" packages = ["."] + pruneopts = "UT" revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc" version = "v1.7.0" [[projects]] + digest = "1:af1306bff89268721ea2550d504413c9487ebfca11e2ff8f39ae79b99a720ff5" name = "github.com/gogo/protobuf" packages = [ "gogoproto", @@ -89,49 +114,61 @@ "proto", "protoc-gen-gogo/descriptor", "sortkeys", - "types" + "types", ] + pruneopts = "UT" revision = "1adfc126b41513cc696b209667c8656ea7aac67c" version = "v1.0.0" [[projects]] + digest = "1:cb22af0ed7c72d495d8be1106233ee553898950f15fd3f5404406d44c2e86888" name = "github.com/golang/protobuf" packages = [ "proto", "ptypes", "ptypes/any", "ptypes/duration", - "ptypes/timestamp" + "ptypes/timestamp", ] + pruneopts = "UT" revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" version = "v1.1.0" [[projects]] branch = "master" + digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009" name = "github.com/golang/snappy" packages = ["."] + pruneopts = "UT" revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a" [[projects]] + digest = "1:c79fb010be38a59d657c48c6ba1d003a8aa651fa56b579d959d74573b7dff8e1" name = "github.com/gorilla/context" packages = ["."] + pruneopts = "UT" revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42" version = "v1.1.1" [[projects]] + digest = "1:e73f5b0152105f18bc131fba127d9949305c8693f8a762588a82a48f61756f5f" name = "github.com/gorilla/mux" packages = ["."] + pruneopts = "UT" revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf" version = "v1.6.2" [[projects]] + digest = "1:43dd08a10854b2056e615d1b1d22ac94559d822e1f8b6fcc92c1a1057e85188e" name = "github.com/gorilla/websocket" packages = ["."] + pruneopts = "UT" revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" version = "v1.2.0" [[projects]] branch = "master" + digest = "1:8951fe6e358876736d8fa1f3992624fdbb2dec6bc49401c1381d1ef8abbb544f" name = "github.com/hashicorp/hcl" packages = [ ".", @@ -142,162 +179,208 @@ "hcl/token", "json/parser", "json/scanner", - "json/token" + "json/token", ] + pruneopts = "UT" revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168" [[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" name = "github.com/inconshreveable/mousetrap" packages = ["."] + pruneopts = "UT" revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" version = "v1.0" [[projects]] branch = "master" + digest = "1:39b27d1381a30421f9813967a5866fba35dc1d4df43a6eefe3b7a5444cb07214" name = "github.com/jmhodges/levigo" packages = ["."] + pruneopts = "UT" revision = "c42d9e0ca023e2198120196f842701bb4c55d7b9" [[projects]] branch = "master" + digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72" name = "github.com/kr/logfmt" packages = ["."] + pruneopts = "UT" revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0" [[projects]] + digest = "1:c568d7727aa262c32bdf8a3f7db83614f7af0ed661474b24588de635c20024c7" name = "github.com/magiconair/properties" packages = ["."] + pruneopts = "UT" revision = "c2353362d570a7bfa228149c62842019201cfb71" version = "v1.8.0" [[projects]] + digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb" name = "github.com/mattn/go-isatty" packages = ["."] + pruneopts = "UT" revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" [[projects]] + digest = "1:ff5ebae34cfbf047d505ee150de27e60570e8c394b3b8fdbb720ff6ac71985fc" name = "github.com/matttproud/golang_protobuf_extensions" packages = ["pbutil"] + pruneopts = "UT" revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" version = "v1.0.1" [[projects]] branch = "master" + digest = "1:e730597b38a4d56e2361e0b6236cb800e52c73cace2ff91396f4ff35792ddfa7" name = "github.com/mitchellh/mapstructure" packages = ["."] + pruneopts = "UT" revision = "bb74f1db0675b241733089d5a1faa5dd8b0ef57b" [[projects]] + digest = "1:95741de3af260a92cc5c7f3f3061e85273f5a81b5db20d4bd68da74bd521675e" name = "github.com/pelletier/go-toml" packages = ["."] + pruneopts = "UT" revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194" version = "v1.2.0" [[projects]] + digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "UT" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] + digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe" name = "github.com/pmezard/go-difflib" packages = ["difflib"] + pruneopts = "UT" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] branch = "master" + digest = "1:98225904b7abff96c052b669b25788f18225a36673fba022fb93514bb9a2a64e" name = "github.com/prometheus/client_golang" packages = [ "prometheus", - "prometheus/promhttp" + "prometheus/promhttp", ] + pruneopts = "UT" revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632" [[projects]] branch = "master" + digest = "1:53a76eb11bdc815fcf0c757a9648fda0ab6887da13f07587181ff2223b67956c" name = "github.com/prometheus/client_model" packages = ["go"] + pruneopts = "UT" revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" [[projects]] branch = "master" + digest = "1:4d291d51042ed9de40eef61a3c1b56e969d6e0f8aa5fd3da5e958ec66bee68e4" name = "github.com/prometheus/common" packages = [ "expfmt", "internal/bitbucket.org/ww/goautoneg", - "model" + "model", ] + pruneopts = "UT" revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" [[projects]] branch = "master" + digest = "1:55d7449d6987dabf272b4e81b2f9c449f05b17415c939b68d1e82f57e3374b7f" name = "github.com/prometheus/procfs" packages = [ ".", "internal/util", "nfs", - "xfs" + "xfs", ] + pruneopts = "UT" revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" [[projects]] branch = "master" + digest = "1:c4556a44e350b50a490544d9b06e9fba9c286c21d6c0e47f54f3a9214597298c" name = "github.com/rcrowley/go-metrics" packages = ["."] + pruneopts = "UT" revision = "e2704e165165ec55d062f5919b4b29494e9fa790" [[projects]] + digest = "1:37ace7f35375adec11634126944bdc45a673415e2fcc07382d03b75ec76ea94c" name = "github.com/spf13/afero" packages = [ ".", - "mem" + "mem", ] + pruneopts = "UT" revision = "787d034dfe70e44075ccc060d346146ef53270ad" version = "v1.1.1" [[projects]] + digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f" name = "github.com/spf13/cast" packages = ["."] + pruneopts = "UT" revision = "8965335b8c7107321228e3e3702cab9832751bac" version = "v1.2.0" [[projects]] + digest = "1:627ab2f549a6a55c44f46fa24a4307f4d0da81bfc7934ed0473bf38b24051d26" name = "github.com/spf13/cobra" packages = ["."] + pruneopts = "UT" revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b" version = "v0.0.1" [[projects]] branch = "master" + digest = "1:080e5f630945ad754f4b920e60b4d3095ba0237ebf88dc462eb28002932e3805" name = "github.com/spf13/jwalterweatherman" packages = ["."] + pruneopts = "UT" revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394" [[projects]] + digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7" name = "github.com/spf13/pflag" packages = ["."] + pruneopts = "UT" revision = "583c0c0531f06d5278b7d917446061adc344b5cd" version = "v1.0.1" [[projects]] + digest = "1:f8e1a678a2571e265f4bf91a3e5e32aa6b1474a55cb0ea849750cc177b664d96" name = "github.com/spf13/viper" packages = ["."] + pruneopts = "UT" revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7" version = "v1.0.0" [[projects]] + digest = "1:73697231b93fb74a73ebd8384b68b9a60c57ea6b13c56d2425414566a72c8e6d" name = "github.com/stretchr/testify" packages = [ "assert", - "require" + "require", ] + pruneopts = "UT" revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71" version = "v1.2.1" [[projects]] branch = "master" + digest = "1:922191411ad8f61bcd8018ac127589bb489712c1d1a0ab2497aca4b16de417d2" name = "github.com/syndtr/goleveldb" packages = [ "leveldb", @@ -311,33 +394,41 @@ "leveldb/opt", "leveldb/storage", "leveldb/table", - "leveldb/util" + "leveldb/util", ] + pruneopts = "UT" revision = "c4c61651e9e37fa117f53c5a906d3b63090d8445" [[projects]] branch = "master" + digest = "1:203b409c21115233a576f99e8f13d8e07ad82b25500491f7e1cca12588fb3232" name = "github.com/tendermint/ed25519" packages = [ ".", "edwards25519", - "extra25519" + "extra25519", ] + pruneopts = "UT" revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057" [[projects]] + digest = "1:e9113641c839c21d8eaeb2c907c7276af1eddeed988df8322168c56b7e06e0e1" name = "github.com/tendermint/go-amino" packages = ["."] + pruneopts = "UT" revision = "2106ca61d91029c931fd54968c2bb02dc96b1412" version = "0.10.1" [[projects]] + digest = "1:d4a15d404afbf591e8be16fcda7f5ac87948d5c7531f9d909fd84cc730ab16e2" name = "github.com/tendermint/iavl" packages = ["."] + pruneopts = "UT" revision = "35f66e53d9b01e83b30de68b931f54b2477a94c9" version = "v0.9.2" [[projects]] + digest = "1:2511fa7bc2725251a1a48a923c8f01cd41de29b00a21224092d448a9e4627c21" name = "github.com/tendermint/tendermint" packages = [ "abci/client", @@ -392,18 +483,22 @@ "state/txindex/kv", "state/txindex/null", "types", - "version" + "version", ] + pruneopts = "UT" revision = "5ff65274b84ea905787a48512cc3124385bddf2f" version = "v0.22.2" [[projects]] + digest = "1:9d2f5bccd058537986ef2862b8c6daff855f293d924ae8ebc18974143ae9191e" name = "github.com/zondax/ledger-goclient" packages = ["."] + pruneopts = "UT" revision = "065cbf938a16f20335c40cfe180f9cd4955c6a5a" [[projects]] branch = "master" + digest = "1:e8206c1653e050116ec8c9a823a86413fc9f9ee3c2f3ae977c96d6a1747f7325" name = "golang.org/x/crypto" packages = [ "blowfish", @@ -416,12 +511,14 @@ "pbkdf2", "poly1305", "ripemd160", - "salsa20/salsa" + "salsa20/salsa", ] + pruneopts = "UT" revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" [[projects]] branch = "master" + digest = "1:04dda8391c3e2397daf254ac68003f30141c069b228d06baec8324a5f81dc1e9" name = "golang.org/x/net" packages = [ "context", @@ -431,17 +528,21 @@ "idna", "internal/timeseries", "netutil", - "trace" + "trace", ] + pruneopts = "UT" revision = "292b43bbf7cb8d35ddf40f8d5100ef3837cced3f" [[projects]] branch = "master" + digest = "1:d773e525476aefa22ea944a5425a9bfb99819b2e67eeb9b1966454fd57522bbf" name = "golang.org/x/sys" packages = ["unix"] + pruneopts = "UT" revision = "1b2967e3c290b7c545b3db0deeda16e9be4f98a2" [[projects]] + digest = "1:7509ba4347d1f8de6ae9be8818b0cd1abc3deeffe28aeaf4be6d4b6b5178d9ca" name = "golang.org/x/text" packages = [ "collate", @@ -457,18 +558,22 @@ "unicode/bidi", "unicode/cldr", "unicode/norm", - "unicode/rangetable" + "unicode/rangetable", ] + pruneopts = "UT" revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" version = "v0.3.0" [[projects]] branch = "master" + digest = "1:601e63e7d4577f907118bec825902505291918859d223bce015539e79f1160e3" name = "google.golang.org/genproto" packages = ["googleapis/rpc/status"] + pruneopts = "UT" revision = "e92b116572682a5b432ddd840aeaba2a559eeff1" [[projects]] + digest = "1:4d7b5d9746840266938cdb21a40f8eba7137d9153c4ed404d6bb2a450d06f690" name = "google.golang.org/grpc" packages = [ ".", @@ -493,20 +598,62 @@ "stats", "status", "tap", - "transport" + "transport", ] + pruneopts = "UT" revision = "d11072e7ca9811b1100b80ca0269ac831f06d024" version = "v1.11.3" [[projects]] + digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202" name = "gopkg.in/yaml.v2" packages = ["."] + pruneopts = "UT" revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" version = "v2.2.1" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "37c54ed9bde68bad33be02f5e09b17a42397fb2fc9a10fa582e66b3852a99370" + input-imports = [ + "github.com/bartekn/go-bip39", + "github.com/bgentry/speakeasy", + "github.com/btcsuite/btcd/btcec", + "github.com/golang/protobuf/proto", + "github.com/gorilla/mux", + "github.com/mattn/go-isatty", + "github.com/pkg/errors", + "github.com/spf13/cobra", + "github.com/spf13/pflag", + "github.com/spf13/viper", + "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/require", + "github.com/tendermint/go-amino", + "github.com/tendermint/iavl", + "github.com/tendermint/tendermint/abci/server", + "github.com/tendermint/tendermint/abci/types", + "github.com/tendermint/tendermint/cmd/tendermint/commands", + "github.com/tendermint/tendermint/config", + "github.com/tendermint/tendermint/crypto", + "github.com/tendermint/tendermint/crypto/merkle", + "github.com/tendermint/tendermint/libs/bech32", + "github.com/tendermint/tendermint/libs/cli", + "github.com/tendermint/tendermint/libs/cli/flags", + "github.com/tendermint/tendermint/libs/common", + "github.com/tendermint/tendermint/libs/db", + "github.com/tendermint/tendermint/libs/log", + "github.com/tendermint/tendermint/node", + "github.com/tendermint/tendermint/p2p", + "github.com/tendermint/tendermint/privval", + "github.com/tendermint/tendermint/proxy", + "github.com/tendermint/tendermint/rpc/client", + "github.com/tendermint/tendermint/rpc/core/types", + "github.com/tendermint/tendermint/rpc/lib/client", + "github.com/tendermint/tendermint/rpc/lib/server", + "github.com/tendermint/tendermint/types", + "github.com/zondax/ledger-goclient", + "golang.org/x/crypto/blowfish", + "golang.org/x/crypto/ripemd160", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Makefile b/Makefile index ce753afefd3d..900219d37a61 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ PACKAGES_SIMTEST=$(shell go list ./... | grep -v '/vendor/' | grep '/simulation' COMMIT_HASH := $(shell git rev-parse --short HEAD) BUILD_FLAGS = -tags netgo -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" -all: get_tools get_vendor_deps install install_examples test_lint test +all: get_tools get_vendor_deps install install_examples test_lint test test_sim ######################################## ### CI From bb217b91a428cd63853e43bd5372a39a10c28a1d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 12 Jul 2018 22:01:43 +0200 Subject: [PATCH 11/34] Actual Gaia app --- cmd/gaia/simulation/sim_test.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/simulation/sim_test.go index 77940fba7818..077ed9d67408 100644 --- a/cmd/gaia/simulation/sim_test.go +++ b/cmd/gaia/simulation/sim_test.go @@ -4,8 +4,18 @@ import ( "testing" "github.com/stretchr/testify/require" + + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + // stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" ) func TestFullGaiaSimulation(t *testing.T) { - require.True(t, true, "should not happen") + // Setup Gaia application + logger := log.NewNopLogger() + db := dbm.NewMemDB() + app := gaia.NewGaiaApp(logger, db) + require.Equal(t, "GaiaApp", app.Name()) } From c272db06b82859f07efa963b723b90fa054865c5 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Fri, 13 Jul 2018 01:54:07 +0200 Subject: [PATCH 12/34] Delegation bug fixed! --- x/stake/simulation/sim_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 621e2c5b2fee..5252158d8642 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -356,8 +356,7 @@ func TestStakeWithRandomMessages(t *testing.T) { // XXX TODO // SimulateMsgBeginUnbonding(mapper, stakeKeeper), SimulateMsgCompleteUnbonding(stakeKeeper), - // XXX TODO Bug found! - // SimulateMsgBeginRedelegate(mapper, stakeKeeper), + SimulateMsgBeginRedelegate(mapper, stakeKeeper), SimulateMsgCompleteRedelegate(stakeKeeper), }, []mock.RandSetup{ SimulationSetup(mapp, stakeKeeper), From a4e7216b36eeb9fe5e79e582f4f27eed15085ac0 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 02:29:46 +0200 Subject: [PATCH 13/34] Fix gocyclo check --- tools/Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/Makefile b/tools/Makefile index d58f52d1b184..426600e0cbfc 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -120,10 +120,10 @@ else @echo "Installing unparam" go get -v $(UNPARAM) endif -ifdef GOYCLO_CHECK - @echo "goyclo is already installed. Run 'make update_tools' to update." +ifdef GOCYCLO_CHECK + @echo "gocyclo is already installed. Run 'make update_tools' to update." else - @echo "Installing goyclo" + @echo "Installing gocyclo" go get -v $(GOCYCLO) endif From 9ad3d62e491869433e569802d78aad6586cec85f Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 02:41:36 +0200 Subject: [PATCH 14/34] Updates from merge --- cmd/gaia/simulation/sim_test.go | 2 +- x/stake/simulation/sim_test.go | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/simulation/sim_test.go index 077ed9d67408..d00e56bab0d9 100644 --- a/cmd/gaia/simulation/sim_test.go +++ b/cmd/gaia/simulation/sim_test.go @@ -16,6 +16,6 @@ func TestFullGaiaSimulation(t *testing.T) { // Setup Gaia application logger := log.NewNopLogger() db := dbm.NewMemDB() - app := gaia.NewGaiaApp(logger, db) + app := gaia.NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) } diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 5252158d8642..0280bab706e3 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -42,31 +42,21 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper) mock.Invariant { loose = loose.Add(acc.GetCoins().AmountOf("steak")) return false }) - require.True(t, sdk.NewInt(pool.LooseTokens).Equal(loose), "expected loose tokens to equal total steak held by accounts - pool.LooseTokens: %v, sum of account tokens: %v\nlog: %s", + 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 - // Unbonded tokens should equal sum of tokens with unbonded validators bonded := sdk.ZeroRat() - unbonded := sdk.ZeroRat() k.IterateValidators(ctx, func(_ int64, validator sdk.Validator) bool { switch validator.GetStatus() { case sdk.Bonded: bonded = bonded.Add(validator.GetPower()) - case sdk.Unbonding: - // TODO - case sdk.Unbonded: - unbonded = unbonded.Add(validator.GetPower()) } return false }) - require.True(t, sdk.NewRat(pool.BondedTokens).Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators\nlog: %s", log) + 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 - require.True(t, sdk.NewRat(pool.UnbondedTokens).Equal(unbonded), "expected unbonded tokens to equal total steak held by unbonded validators\n log: %s", log) - stats["stake/invariant/unbondedTokens"] += 1 - - // TODO Unbonding tokens // TODO Inflation check on total supply } @@ -321,7 +311,7 @@ func SimulationSetup(mapp *mock.App, k stake.Keeper) mock.RandSetup { return false }) pool := k.GetPool(ctx) - pool.LooseTokens += loose.Int64() + pool.LooseTokens = pool.LooseTokens.Add(sdk.NewRat(loose.Int64(), 1)) k.SetPool(ctx, pool) } } From a49f9d6314d103deb359abcdc36911e5392075a9 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 03:15:50 +0200 Subject: [PATCH 15/34] Restructure --- cmd/gaia/simulation/sim_test.go | 20 +++++- x/mock/random_simulate_blocks.go | 116 ++++--------------------------- x/mock/types.go | 16 ++--- x/mock/util.go | 60 ++++++++++++++++ 4 files changed, 95 insertions(+), 117 deletions(-) create mode 100644 x/mock/util.go diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/simulation/sim_test.go index d00e56bab0d9..3cbbd68c80c4 100644 --- a/cmd/gaia/simulation/sim_test.go +++ b/cmd/gaia/simulation/sim_test.go @@ -9,7 +9,13 @@ import ( "github.com/tendermint/tendermint/libs/log" gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - // stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" + "github.com/cosmos/cosmos-sdk/x/mock" +) + +const ( + NumKeys = 10 + NumBlocks = 1000 + BlockSize = 1000 ) func TestFullGaiaSimulation(t *testing.T) { @@ -18,4 +24,16 @@ func TestFullGaiaSimulation(t *testing.T) { db := dbm.NewMemDB() app := gaia.NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) + + // Run randomized simulation + mock.RandomizedTesting( + t, app.BaseApp, + []mock.TestAndRunTx{}, + []mock.RandSetup{}, + []mock.Invariant{}, + NumKeys, + NumBlocks, + BlockSize, + ) + } diff --git a/x/mock/random_simulate_blocks.go b/x/mock/random_simulate_blocks.go index 452b8f6bf592..5021d2785fde 100644 --- a/x/mock/random_simulate_blocks.go +++ b/x/mock/random_simulate_blocks.go @@ -2,36 +2,36 @@ package mock import ( "fmt" - "math/big" "math/rand" "testing" "time" - sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/baseapp" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) // RandomizedTesting tests application by sending random messages. -func (app *App) RandomizedTesting( - t *testing.T, ops []TestAndRunTx, setups []RandSetup, +func RandomizedTesting( + t *testing.T, app *baseapp.BaseApp, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { time := time.Now().UnixNano() - app.RandomizedTestingFromSeed(t, time, ops, setups, invariants, numKeys, numBlocks, blockSize) + RandomizedTestingFromSeed(t, app, time, ops, setups, invariants, numKeys, numBlocks, blockSize) } // RandomizedTestingFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. -func (app *App) RandomizedTestingFromSeed( - t *testing.T, seed int64, ops []TestAndRunTx, setups []RandSetup, +func RandomizedTestingFromSeed( + t *testing.T, app *baseapp.BaseApp, seed int64, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { log := fmt.Sprintf("Starting SingleModuleTest with randomness created with seed %d", int(seed)) - keys, addrs := GeneratePrivKeyAddressPairs(numKeys) + keys, _ := GeneratePrivKeyAddressPairs(numKeys) r := rand.New(rand.NewSource(seed)) - RandomSetGenesis(r, app, addrs, []string{"foocoin"}) + // XXX TODO + // RandomSetGenesis(r, app, addrs, []string{"foocoin"}) app.InitChain(abci.RequestInitChain{}) for i := 0; i < len(setups); i++ { setups[i](r, keys) @@ -45,7 +45,7 @@ func (app *App) RandomizedTestingFromSeed( // Make sure invariants hold at beginning of block and when nothing was // done. - app.assertAllInvariants(t, invariants, log) + AssertAllInvariants(t, app, invariants, log) ctx := app.NewContext(false, header) @@ -56,7 +56,7 @@ func (app *App) RandomizedTestingFromSeed( log += "\n" + logUpdate require.Nil(t, err, log) - app.assertAllInvariants(t, invariants, log) + AssertAllInvariants(t, app, invariants, log) } app.EndBlock(abci.RequestEndBlock{}) @@ -64,100 +64,8 @@ func (app *App) RandomizedTestingFromSeed( } } -// SimpleRandomizedTestingFromSeed -func (app *App) SimpleRandomizedTestingFromSeed( - t *testing.T, seed int64, ops []TestAndRunMsg, setups []RandSetup, - invariants []Invariant, numKeys int, numBlocks int, blockSize int, -) { - log := fmt.Sprintf("Starting SimpleSingleModuleTest with randomness created with seed %d", int(seed)) - keys, addrs := GeneratePrivKeyAddressPairs(numKeys) - r := rand.New(rand.NewSource(seed)) - - RandomSetGenesis(r, app, addrs, []string{"foocoin"}) - app.InitChain(abci.RequestInitChain{}) - for i := 0; i < len(setups); i++ { - setups[i](r, keys) - } - app.Commit() - - header := abci.Header{Height: 0} - - for i := 0; i < numBlocks; i++ { - app.BeginBlock(abci.RequestBeginBlock{}) - - app.assertAllInvariants(t, invariants, log) - - ctx := app.NewContext(false, header) - - // TODO: Add modes to simulate "no load", "medium load", and - // "high load" blocks. - for j := 0; j < blockSize; j++ { - logUpdate, err := ops[r.Intn(len(ops))](t, r, ctx, keys, log) - log += "\n" + logUpdate - - require.Nil(t, err, log) - app.assertAllInvariants(t, invariants, log) - } - - app.EndBlock(abci.RequestEndBlock{}) - header.Height++ - } -} - -func (app *App) assertAllInvariants(t *testing.T, tests []Invariant, log string) { +func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant, log string) { for i := 0; i < len(tests); i++ { tests[i](t, app, log) } } - -// BigInterval is a representation of the interval [lo, hi), where -// lo and hi are both of type sdk.Int -type BigInterval struct { - lo sdk.Int - hi sdk.Int -} - -// RandFromBigInterval chooses an interval uniformly from the provided list of -// BigIntervals, and then chooses an element from an interval uniformly at random. -func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { - if len(intervals) == 0 { - return sdk.ZeroInt() - } - - interval := intervals[r.Intn(len(intervals))] - - lo := interval.lo - hi := interval.hi - - diff := hi.Sub(lo) - result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) - result = result.Add(lo) - - return result -} - -// shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 - -const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" -const ( - letterIdxBits = 6 // 6 bits to represent a letter index - letterIdxMask = 1<= 0; { - if remain == 0 { - cache, remain = r.Int63(), letterIdxMax - } - if idx := int(cache & letterIdxMask); idx < len(letterBytes) { - b[i] = letterBytes[idx] - i-- - } - cache >>= letterIdxBits - remain-- - } - return string(b) -} diff --git a/x/mock/types.go b/x/mock/types.go index 8a1c3d3e88de..3a68e2422e1e 100644 --- a/x/mock/types.go +++ b/x/mock/types.go @@ -4,6 +4,7 @@ import ( "math/rand" "testing" + "github.com/cosmos/cosmos-sdk/baseapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/tendermint/tendermint/crypto" ) @@ -13,33 +14,24 @@ type ( // transition was as expected. It returns a descriptive message "action" // about what this fuzzed tx actually did, for ease of debugging. TestAndRunTx func( - t *testing.T, r *rand.Rand, app *App, ctx sdk.Context, + t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, privKeys []crypto.PrivKey, log string, ) (action string, err sdk.Error) - // TestAndRunMsg produces a fuzzed message, calls the appropriate handler - // (bypassing all ante handler checks), and ensures that the state - // transition was as expected. It returns a descriptive message "action" - // about what this fuzzed msg actually did for ease of debugging. - TestAndRunMsg func( - t *testing.T, r *rand.Rand, ctx sdk.Context, - privKey []crypto.PrivKey, log string, - ) (action string, err sdk.Error) - // RandSetup performs the random setup the mock module needs. RandSetup func(r *rand.Rand, privKeys []crypto.PrivKey) // An Invariant is a function which tests a particular invariant. // If the invariant has been broken, the function should halt the // test and output the log. - Invariant func(t *testing.T, app *App, log string) + Invariant func(t *testing.T, app *baseapp.BaseApp, log string) ) // PeriodicInvariant returns an Invariant function closure that asserts // a given invariant if the mock application's last block modulo the given // period is congruent to the given offset. func PeriodicInvariant(invariant Invariant, period int, offset int) Invariant { - return func(t *testing.T, app *App, log string) { + return func(t *testing.T, app *baseapp.BaseApp, log string) { if int(app.LastBlockHeight())%period == offset { invariant(t, app, log) } diff --git a/x/mock/util.go b/x/mock/util.go new file mode 100644 index 000000000000..7b86cc69e755 --- /dev/null +++ b/x/mock/util.go @@ -0,0 +1,60 @@ +package mock + +import ( + "math/big" + "math/rand" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// BigInterval is a representation of the interval [lo, hi), where +// lo and hi are both of type sdk.Int +type BigInterval struct { + lo sdk.Int + hi sdk.Int +} + +// RandFromBigInterval chooses an interval uniformly from the provided list of +// BigIntervals, and then chooses an element from an interval uniformly at random. +func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { + if len(intervals) == 0 { + return sdk.ZeroInt() + } + + interval := intervals[r.Intn(len(intervals))] + + lo := interval.lo + hi := interval.hi + + diff := hi.Sub(lo) + result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) + result = result.Add(lo) + + return result +} + +// shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = r.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + return string(b) +} From cbcd0f08284a709e5298df3333437fb47b3d1a2e Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 03:25:15 +0200 Subject: [PATCH 16/34] Restructure contd. --- cmd/gaia/simulation/sim_test.go | 10 +++--- .../random_simulate_blocks.go | 5 +-- x/mock/{ => simulation}/types.go | 2 +- x/mock/{ => simulation}/util.go | 31 +------------------ x/mock/test_utils.go | 28 +++++++++++++++++ 5 files changed, 38 insertions(+), 38 deletions(-) rename x/mock/{ => simulation}/random_simulate_blocks.go (94%) rename x/mock/{ => simulation}/types.go (98%) rename x/mock/{ => simulation}/util.go (56%) diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/simulation/sim_test.go index 3cbbd68c80c4..b1382deb0b1d 100644 --- a/cmd/gaia/simulation/sim_test.go +++ b/cmd/gaia/simulation/sim_test.go @@ -9,7 +9,7 @@ import ( "github.com/tendermint/tendermint/libs/log" gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" ) const ( @@ -26,11 +26,11 @@ func TestFullGaiaSimulation(t *testing.T) { require.Equal(t, "GaiaApp", app.Name()) // Run randomized simulation - mock.RandomizedTesting( + simulation.RandomizedTesting( t, app.BaseApp, - []mock.TestAndRunTx{}, - []mock.RandSetup{}, - []mock.Invariant{}, + []simulation.TestAndRunTx{}, + []simulation.RandSetup{}, + []simulation.Invariant{}, NumKeys, NumBlocks, BlockSize, diff --git a/x/mock/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go similarity index 94% rename from x/mock/random_simulate_blocks.go rename to x/mock/simulation/random_simulate_blocks.go index 5021d2785fde..d663732580c7 100644 --- a/x/mock/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -1,4 +1,4 @@ -package mock +package simulation import ( "fmt" @@ -7,6 +7,7 @@ import ( "time" "github.com/cosmos/cosmos-sdk/baseapp" + "github.com/cosmos/cosmos-sdk/x/mock" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" ) @@ -27,7 +28,7 @@ func RandomizedTestingFromSeed( invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { log := fmt.Sprintf("Starting SingleModuleTest with randomness created with seed %d", int(seed)) - keys, _ := GeneratePrivKeyAddressPairs(numKeys) + keys, _ := mock.GeneratePrivKeyAddressPairs(numKeys) r := rand.New(rand.NewSource(seed)) // XXX TODO diff --git a/x/mock/types.go b/x/mock/simulation/types.go similarity index 98% rename from x/mock/types.go rename to x/mock/simulation/types.go index 3a68e2422e1e..41736ce2ce35 100644 --- a/x/mock/types.go +++ b/x/mock/simulation/types.go @@ -1,4 +1,4 @@ -package mock +package simulation import ( "math/rand" diff --git a/x/mock/util.go b/x/mock/simulation/util.go similarity index 56% rename from x/mock/util.go rename to x/mock/simulation/util.go index 7b86cc69e755..97307dc3e045 100644 --- a/x/mock/util.go +++ b/x/mock/simulation/util.go @@ -1,38 +1,9 @@ -package mock +package simulation import ( - "math/big" "math/rand" - - sdk "github.com/cosmos/cosmos-sdk/types" ) -// BigInterval is a representation of the interval [lo, hi), where -// lo and hi are both of type sdk.Int -type BigInterval struct { - lo sdk.Int - hi sdk.Int -} - -// RandFromBigInterval chooses an interval uniformly from the provided list of -// BigIntervals, and then chooses an element from an interval uniformly at random. -func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { - if len(intervals) == 0 { - return sdk.ZeroInt() - } - - interval := intervals[r.Intn(len(intervals))] - - lo := interval.lo - hi := interval.hi - - diff := hi.Sub(lo) - result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) - result = result.Add(lo) - - return result -} - // shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" diff --git a/x/mock/test_utils.go b/x/mock/test_utils.go index f9e1e8f5e9e6..fa7d9875f3b1 100644 --- a/x/mock/test_utils.go +++ b/x/mock/test_utils.go @@ -1,6 +1,8 @@ package mock import ( + "math/big" + "math/rand" "testing" "github.com/cosmos/cosmos-sdk/baseapp" @@ -10,6 +12,32 @@ import ( "github.com/tendermint/tendermint/crypto" ) +// BigInterval is a representation of the interval [lo, hi), where +// lo and hi are both of type sdk.Int +type BigInterval struct { + lo sdk.Int + hi sdk.Int +} + +// RandFromBigInterval chooses an interval uniformly from the provided list of +// BigIntervals, and then chooses an element from an interval uniformly at random. +func RandFromBigInterval(r *rand.Rand, intervals []BigInterval) sdk.Int { + if len(intervals) == 0 { + return sdk.ZeroInt() + } + + interval := intervals[r.Intn(len(intervals))] + + lo := interval.lo + hi := interval.hi + + diff := hi.Sub(lo) + result := sdk.NewIntFromBigInt(new(big.Int).Rand(r, diff.BigInt())) + result = result.Add(lo) + + return result +} + // CheckBalance checks the balance of an account. func CheckBalance(t *testing.T, app *App, addr sdk.AccAddress, exp sdk.Coins) { ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) From cbc9d7d1da92ff671fdddd54ba8361ee91871e67 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 20:33:53 +0200 Subject: [PATCH 17/34] Genesis state --- cmd/gaia/simulation/sim_test.go | 18 ++++++++++++++++-- x/mock/simulation/random_simulate_blocks.go | 17 +++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/simulation/sim_test.go index b1382deb0b1d..2d36e0fa207d 100644 --- a/cmd/gaia/simulation/sim_test.go +++ b/cmd/gaia/simulation/sim_test.go @@ -10,6 +10,7 @@ import ( gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/x/mock/simulation" + stake "github.com/cosmos/cosmos-sdk/x/stake" ) const ( @@ -19,15 +20,28 @@ const ( ) func TestFullGaiaSimulation(t *testing.T) { + // Setup Gaia application logger := log.NewNopLogger() db := dbm.NewMemDB() app := gaia.NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) + // Default genesis state + genesis := gaia.GenesisState{ + Accounts: []gaia.GenesisAccount{}, + StakeData: stake.DefaultGenesisState(), + } + + // Marshal genesis + appState, err := gaia.MakeCodec().MarshalJSON(genesis) + if err != nil { + panic(err) + } + // Run randomized simulation - simulation.RandomizedTesting( - t, app.BaseApp, + simulation.Simulate( + t, app.BaseApp, appState, []simulation.TestAndRunTx{}, []simulation.RandSetup{}, []simulation.Invariant{}, diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index d663732580c7..fdb1c9d42275 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -1,6 +1,7 @@ package simulation import ( + "encoding/json" "fmt" "math/rand" "testing" @@ -12,19 +13,19 @@ import ( abci "github.com/tendermint/tendermint/abci/types" ) -// RandomizedTesting tests application by sending random messages. -func RandomizedTesting( - t *testing.T, app *baseapp.BaseApp, ops []TestAndRunTx, setups []RandSetup, +// Simulate tests application by sending random messages. +func Simulate( + t *testing.T, app *baseapp.BaseApp, appState json.RawMessage, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { time := time.Now().UnixNano() - RandomizedTestingFromSeed(t, app, time, ops, setups, invariants, numKeys, numBlocks, blockSize) + SimulateFromSeed(t, app, appState, time, ops, setups, invariants, numKeys, numBlocks, blockSize) } -// RandomizedTestingFromSeed tests an application by running the provided +// SimulateFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. -func RandomizedTestingFromSeed( - t *testing.T, app *baseapp.BaseApp, seed int64, ops []TestAndRunTx, setups []RandSetup, +func SimulateFromSeed( + t *testing.T, app *baseapp.BaseApp, appState json.RawMessage, seed int64, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { log := fmt.Sprintf("Starting SingleModuleTest with randomness created with seed %d", int(seed)) @@ -33,7 +34,7 @@ func RandomizedTestingFromSeed( // XXX TODO // RandomSetGenesis(r, app, addrs, []string{"foocoin"}) - app.InitChain(abci.RequestInitChain{}) + app.InitChain(abci.RequestInitChain{AppStateBytes: appState}) for i := 0; i < len(setups); i++ { setups[i](r, keys) } From af206bd0ed9450675a50fd0a21dc5fa0d697f638 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 20:50:30 +0200 Subject: [PATCH 18/34] 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 000000000000..08567bb4e51d --- /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 0280bab706e3..8e5a48a21310 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, ) From eda7eb48cd0a68afa7adb605eec8d1ac23815be8 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Tue, 17 Jul 2018 23:06:30 +0200 Subject: [PATCH 19/34] Gaia simulation needs internal field access --- Makefile | 6 +++--- cmd/gaia/{simulation => app}/sim_test.go | 17 +++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) rename cmd/gaia/{simulation => app}/sim_test.go (74%) diff --git a/Makefile b/Makefile index 6f5b55e5526f..5d2cb8493105 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/') -PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v '/simulation' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) -PACKAGES_SIMTEST=$(shell go list ./... | grep -v '/vendor/' | grep '/simulation') +PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) +PACKAGES_SIMTEST=$(shell go list ./... | grep -v '/vendor/') COMMIT_HASH := $(shell git rev-parse --short HEAD) BUILD_TAGS = netgo ledger BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" @@ -114,7 +114,7 @@ test_race: @go test -race $(PACKAGES_NOCLITEST) test_sim: - @go test $(PACKAGES_SIMTEST) -v + @ENABLE_GAIA_SIMULATION=1 go test ./cmd/gaia/app -run TestFullGaiaSimulation -v test_cover: @bash tests/test_cover.sh diff --git a/cmd/gaia/simulation/sim_test.go b/cmd/gaia/app/sim_test.go similarity index 74% rename from cmd/gaia/simulation/sim_test.go rename to cmd/gaia/app/sim_test.go index 2d36e0fa207d..a42a34bc6dfa 100644 --- a/cmd/gaia/simulation/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -1,6 +1,7 @@ -package simulation +package app import ( + "os" "testing" "github.com/stretchr/testify/require" @@ -8,7 +9,6 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" - gaia "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/x/mock/simulation" stake "github.com/cosmos/cosmos-sdk/x/stake" ) @@ -17,24 +17,29 @@ const ( NumKeys = 10 NumBlocks = 1000 BlockSize = 1000 + + simulationEnv = "ENABLE_GAIA_SIMULATION" ) func TestFullGaiaSimulation(t *testing.T) { + if os.Getenv(simulationEnv) == "" { + t.Skip("Skipping Gaia simulation") + } // Setup Gaia application logger := log.NewNopLogger() db := dbm.NewMemDB() - app := gaia.NewGaiaApp(logger, db, nil) + app := NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) // Default genesis state - genesis := gaia.GenesisState{ - Accounts: []gaia.GenesisAccount{}, + genesis := GenesisState{ + Accounts: []GenesisAccount{}, StakeData: stake.DefaultGenesisState(), } // Marshal genesis - appState, err := gaia.MakeCodec().MarshalJSON(genesis) + appState, err := MakeCodec().MarshalJSON(genesis) if err != nil { panic(err) } From 253b82f92a0c4957ac08d63bb347eff5ae02826b Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 00:04:10 +0200 Subject: [PATCH 20/34] Makefile changes --- Makefile | 5 +- cmd/gaia/app/sim_test.go | 49 +++- x/mock/simulation/random_simulate_blocks.go | 16 +- x/stake/simulation/msgs.go | 246 +++++++++++++++++++ x/stake/simulation/sim_test.go | 248 -------------------- 5 files changed, 293 insertions(+), 271 deletions(-) create mode 100644 x/stake/simulation/msgs.go diff --git a/Makefile b/Makefile index 5d2cb8493105..6459734726fe 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ PACKAGES=$(shell go list ./... | grep -v '/vendor/') -PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) -PACKAGES_SIMTEST=$(shell go list ./... | grep -v '/vendor/') +PACKAGES_NOCLITEST=$(shell go list ./... | grep -v '/vendor/' | grep -v '/simulation' | grep -v github.com/cosmos/cosmos-sdk/cmd/gaia/cli_test) +PACKAGES_SIMTEST=$(shell go list ./... | grep -v '/vendor/' | grep '/simulation') COMMIT_HASH := $(shell git rev-parse --short HEAD) BUILD_TAGS = netgo ledger BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" @@ -115,6 +115,7 @@ test_race: test_sim: @ENABLE_GAIA_SIMULATION=1 go test ./cmd/gaia/app -run TestFullGaiaSimulation -v + @go test $(PACKAGES_SIMTEST) test_cover: @bash tests/test_cover.sh diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index a42a34bc6dfa..2fd2a55b6b49 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -1,6 +1,8 @@ package app import ( + "encoding/json" + "math/rand" "os" "testing" @@ -9,8 +11,10 @@ import ( dbm "github.com/tendermint/tendermint/libs/db" "github.com/tendermint/tendermint/libs/log" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock/simulation" stake "github.com/cosmos/cosmos-sdk/x/stake" + stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" ) const ( @@ -21,20 +25,21 @@ const ( simulationEnv = "ENABLE_GAIA_SIMULATION" ) -func TestFullGaiaSimulation(t *testing.T) { - if os.Getenv(simulationEnv) == "" { - t.Skip("Skipping Gaia simulation") - } +func appStateFn(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { + var genesisAccounts []GenesisAccount - // Setup Gaia application - logger := log.NewNopLogger() - db := dbm.NewMemDB() - app := NewGaiaApp(logger, db, nil) - require.Equal(t, "GaiaApp", app.Name()) + // Randomly generate some genesis accounts + for _, addr := range accs { + coins := sdk.Coins{sdk.Coin{"steak", sdk.NewInt(100)}} + genesisAccounts = append(genesisAccounts, GenesisAccount{ + Address: addr, + Coins: coins, + }) + } // Default genesis state genesis := GenesisState{ - Accounts: []GenesisAccount{}, + Accounts: genesisAccounts, StakeData: stake.DefaultGenesisState(), } @@ -44,12 +49,30 @@ func TestFullGaiaSimulation(t *testing.T) { panic(err) } + return appState +} + +func TestFullGaiaSimulation(t *testing.T) { + if os.Getenv(simulationEnv) == "" { + t.Skip("Skipping Gaia simulation") + } + + // Setup Gaia application + logger := log.NewNopLogger() + db := dbm.NewMemDB() + app := NewGaiaApp(logger, db, nil) + require.Equal(t, "GaiaApp", app.Name()) + // Run randomized simulation simulation.Simulate( - t, app.BaseApp, appState, - []simulation.TestAndRunTx{}, + t, app.BaseApp, appStateFn, + []simulation.TestAndRunTx{ + stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper), + }, []simulation.RandSetup{}, - []simulation.Invariant{}, + []simulation.Invariant{ + stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper), + }, NumKeys, NumBlocks, BlockSize, diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index fdb1c9d42275..eca9757c5733 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -8,6 +8,7 @@ import ( "time" "github.com/cosmos/cosmos-sdk/baseapp" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/mock" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" @@ -15,26 +16,24 @@ import ( // Simulate tests application by sending random messages. func Simulate( - t *testing.T, app *baseapp.BaseApp, appState json.RawMessage, ops []TestAndRunTx, setups []RandSetup, + t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { time := time.Now().UnixNano() - SimulateFromSeed(t, app, appState, time, ops, setups, invariants, numKeys, numBlocks, blockSize) + SimulateFromSeed(t, app, appStateFn, time, ops, setups, invariants, numKeys, numBlocks, blockSize) } // SimulateFromSeed tests an application by running the provided // operations, testing the provided invariants, but using the provided seed. func SimulateFromSeed( - t *testing.T, app *baseapp.BaseApp, appState json.RawMessage, seed int64, ops []TestAndRunTx, setups []RandSetup, + t *testing.T, app *baseapp.BaseApp, appStateFn func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage, seed int64, ops []TestAndRunTx, setups []RandSetup, invariants []Invariant, numKeys int, numBlocks int, blockSize int, ) { - log := fmt.Sprintf("Starting SingleModuleTest with randomness created with seed %d", int(seed)) - keys, _ := mock.GeneratePrivKeyAddressPairs(numKeys) + log := fmt.Sprintf("Starting SimulateFromSeed with randomness created with seed %d", int(seed)) + keys, addrs := mock.GeneratePrivKeyAddressPairs(numKeys) r := rand.New(rand.NewSource(seed)) - // XXX TODO - // RandomSetGenesis(r, app, addrs, []string{"foocoin"}) - app.InitChain(abci.RequestInitChain{AppStateBytes: appState}) + app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, addrs)}) for i := 0; i < len(setups); i++ { setups[i](r, keys) } @@ -66,6 +65,7 @@ func SimulateFromSeed( } } +// AssertAllInvariants asserts a list of provided invariants against application state func AssertAllInvariants(t *testing.T, app *baseapp.BaseApp, tests []Invariant, log string) { for i := 0; i < len(tests); i++ { tests[i](t, app, log) diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go new file mode 100644 index 000000000000..2c7bf80f428d --- /dev/null +++ b/x/stake/simulation/msgs.go @@ -0,0 +1,246 @@ +package simulation + +import ( + "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/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" +) + +// SimulateMsgCreateValidator +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: simulation.RandStringOfLength(r, 10), + } + key := keys[r.Intn(len(keys))] + pubkey := key.PubKey() + address := sdk.AccAddress(pubkey.Address()) + amount := m.GetAccount(ctx, address).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } + msg := stake.MsgCreateValidator{ + Description: description, + ValidatorAddr: address, + DelegatorAddr: address, + PubKey: pubkey, + Delegation: sdk.NewIntCoin(denom, amount), + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) + action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulateMsgEditValidator +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: 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() + address := sdk.AccAddress(pubkey.Address()) + msg := stake.MsgEditValidator{ + Description: description, + ValidatorAddr: address, + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulateMsgDelegate +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()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } + msg := stake.MsgDelegate{ + DelegatorAddr: delegatorAddress, + ValidatorAddr: validatorAddress, + Delegation: sdk.NewIntCoin(denom, amount), + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + action = fmt.Sprintf("TestMsgDelegate: %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulateMsgBeginUnbonding +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()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } + msg := stake.MsgBeginUnbonding{ + DelegatorAddr: delegatorAddress, + ValidatorAddr: validatorAddress, + SharesAmount: sdk.NewRatFromInt(amount), + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + action = fmt.Sprintf("TestMsgBeginUnbonding: %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulateMsgCompleteUnbonding +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))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + msg := stake.MsgCompleteUnbonding{ + DelegatorAddr: delegatorAddress, + ValidatorAddr: validatorAddress, + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + action = fmt.Sprintf("TestMsgCompleteUnbonding with %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulateMsgBeginRedelegate +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()) + destValidatorKey := keys[r.Intn(len(keys))] + destValidatorAddress := sdk.AccAddress(destValidatorKey.PubKey().Address()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + // TODO + amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) + if amount.GT(sdk.ZeroInt()) { + amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + } + if amount.Equal(sdk.ZeroInt()) { + return "nop", nil + } + msg := stake.MsgBeginRedelegate{ + DelegatorAddr: delegatorAddress, + ValidatorSrcAddr: sourceValidatorAddress, + ValidatorDstAddr: destValidatorAddress, + SharesAmount: sdk.NewRatFromInt(amount), + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulateMsgCompleteRedelegate +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))] + validatorDstAddress := sdk.AccAddress(validatorDstKey.PubKey().Address()) + delegatorKey := keys[r.Intn(len(keys))] + delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) + msg := stake.MsgCompleteRedelegate{ + DelegatorAddr: delegatorAddress, + ValidatorSrcAddr: validatorSrcAddress, + ValidatorDstAddr: validatorDstAddress, + } + require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) + ctx, write := ctx.CacheContext() + result := stake.NewHandler(k)(ctx, msg) + if result.IsOK() { + write() + } + action = fmt.Sprintf("TestMsgCompleteRedelegate with %s", msg.GetSignBytes()) + return action, nil + } +} + +// SimulationSetup +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()) + params := k.GetParams(ctx) + denom := params.BondDenom + loose := sdk.ZeroInt() + mapp.AccountMapper.IterateAccounts(ctx, func(acc auth.Account) bool { + balance := sdk.NewInt(int64(r.Intn(1000000))) + acc.SetCoins(acc.GetCoins().Plus(sdk.Coins{sdk.NewIntCoin(denom, balance)})) + mapp.AccountMapper.SetAccount(ctx, acc) + loose = loose.Add(balance) + return false + }) + pool := k.GetPool(ctx) + pool.LooseTokens = pool.LooseTokens.Add(sdk.NewRat(loose.Int64(), 1)) + k.SetPool(ctx, pool) + } +} diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index 8e5a48a21310..c26f6c82c0ad 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -2,262 +2,16 @@ 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" -) - -var ( - stats = make(map[string]int) ) -// SimulateMsgCreateValidator -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: simulation.RandStringOfLength(r, 10), - } - key := keys[r.Intn(len(keys))] - pubkey := key.PubKey() - address := sdk.AccAddress(pubkey.Address()) - amount := m.GetAccount(ctx, address).GetCoins().AmountOf(denom) - if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) - } - if amount.Equal(sdk.ZeroInt()) { - return "nop", nil - } - msg := stake.MsgCreateValidator{ - Description: description, - ValidatorAddr: address, - DelegatorAddr: address, - PubKey: pubkey, - Delegation: sdk.NewIntCoin(denom, amount), - } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) - if result.IsOK() { - write() - } - stats[fmt.Sprintf("stake/createvalidator/%v", result.IsOK())] += 1 - // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) - action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes()) - return action, nil - } -} - -// SimulateMsgEditValidator -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: 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() - address := sdk.AccAddress(pubkey.Address()) - msg := stake.MsgEditValidator{ - Description: description, - ValidatorAddr: address, - } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) - if result.IsOK() { - write() - } - stats[fmt.Sprintf("stake/editvalidator/%v", result.IsOK())] += 1 - action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes()) - return action, nil - } -} - -// SimulateMsgDelegate -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()) - delegatorKey := keys[r.Intn(len(keys))] - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) - amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) - if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) - } - if amount.Equal(sdk.ZeroInt()) { - return "nop", nil - } - msg := stake.MsgDelegate{ - DelegatorAddr: delegatorAddress, - ValidatorAddr: validatorAddress, - Delegation: sdk.NewIntCoin(denom, amount), - } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) - if result.IsOK() { - write() - } - stats[fmt.Sprintf("stake/delegate/%v", result.IsOK())] += 1 - action = fmt.Sprintf("TestMsgDelegate: %s", msg.GetSignBytes()) - return action, nil - } -} - -// SimulateMsgBeginUnbonding -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()) - delegatorKey := keys[r.Intn(len(keys))] - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) - amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) - if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) - } - if amount.Equal(sdk.ZeroInt()) { - return "nop", nil - } - msg := stake.MsgBeginUnbonding{ - DelegatorAddr: delegatorAddress, - ValidatorAddr: validatorAddress, - SharesAmount: sdk.NewRatFromInt(amount), - } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) - if result.IsOK() { - write() - } - stats[fmt.Sprintf("stake/beginunbonding/%v", result.IsOK())] += 1 - action = fmt.Sprintf("TestMsgBeginUnbonding: %s", msg.GetSignBytes()) - return action, nil - } -} - -// SimulateMsgCompleteUnbonding -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))] - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) - msg := stake.MsgCompleteUnbonding{ - DelegatorAddr: delegatorAddress, - ValidatorAddr: validatorAddress, - } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) - if result.IsOK() { - write() - } - stats[fmt.Sprintf("stake/completeunbonding/%v", result.IsOK())] += 1 - action = fmt.Sprintf("TestMsgCompleteUnbonding with %s", msg.GetSignBytes()) - return action, nil - } -} - -// SimulateMsgBeginRedelegate -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()) - destValidatorKey := keys[r.Intn(len(keys))] - destValidatorAddress := sdk.AccAddress(destValidatorKey.PubKey().Address()) - delegatorKey := keys[r.Intn(len(keys))] - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) - // TODO - amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) - if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) - } - if amount.Equal(sdk.ZeroInt()) { - return "nop", nil - } - msg := stake.MsgBeginRedelegate{ - DelegatorAddr: delegatorAddress, - ValidatorSrcAddr: sourceValidatorAddress, - ValidatorDstAddr: destValidatorAddress, - SharesAmount: sdk.NewRatFromInt(amount), - } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) - if result.IsOK() { - write() - } - stats[fmt.Sprintf("stake/beginredelegate/%v", result.IsOK())] += 1 - action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes()) - return action, nil - } -} - -// SimulateMsgCompleteRedelegate -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))] - validatorDstAddress := sdk.AccAddress(validatorDstKey.PubKey().Address()) - delegatorKey := keys[r.Intn(len(keys))] - delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) - msg := stake.MsgCompleteRedelegate{ - DelegatorAddr: delegatorAddress, - ValidatorSrcAddr: validatorSrcAddress, - ValidatorDstAddr: validatorDstAddress, - } - require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes()) - ctx, write := ctx.CacheContext() - result := stake.NewHandler(k)(ctx, msg) - if result.IsOK() { - write() - } - stats[fmt.Sprintf("stake/completeredelegate/%v", result.IsOK())] += 1 - action = fmt.Sprintf("TestMsgCompleteRedelegate with %s", msg.GetSignBytes()) - return action, nil - } -} - -// SimulationSetup -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()) - params := k.GetParams(ctx) - denom := params.BondDenom - loose := sdk.ZeroInt() - mapp.AccountMapper.IterateAccounts(ctx, func(acc auth.Account) bool { - balance := sdk.NewInt(int64(r.Intn(1000000))) - acc.SetCoins(acc.GetCoins().Plus(sdk.Coins{sdk.NewIntCoin(denom, balance)})) - mapp.AccountMapper.SetAccount(ctx, acc) - loose = loose.Add(balance) - return false - }) - pool := k.GetPool(ctx) - pool.LooseTokens = pool.LooseTokens.Add(sdk.NewRat(loose.Int64(), 1)) - k.SetPool(ctx, pool) - } -} - // TestStakeWithRandomMessages func TestStakeWithRandomMessages(t *testing.T) { mapp := mock.NewApp() @@ -297,6 +51,4 @@ func TestStakeWithRandomMessages(t *testing.T) { AllInvariants(coinKeeper, stakeKeeper, mapp.AccountMapper), }, 10, 100, 500, ) - - fmt.Printf("Stats: %v\n", stats) } From 5918ab18fdbc2fbb20b317e6fc03c588d8849b6d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 01:01:36 +0200 Subject: [PATCH 21/34] Restructure (probably) complete --- cmd/gaia/app/sim_test.go | 8 +++++--- x/stake/simulation/sim_test.go | 8 +++++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 2fd2a55b6b49..86c0de5942ed 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -19,8 +19,8 @@ import ( const ( NumKeys = 10 - NumBlocks = 1000 - BlockSize = 1000 + NumBlocks = 100 + BlockSize = 500 simulationEnv = "ENABLE_GAIA_SIMULATION" ) @@ -38,9 +38,11 @@ func appStateFn(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { } // Default genesis state + stakeGenesis := stake.DefaultGenesisState() + stakeGenesis.Pool.LooseTokens = sdk.NewRat(1000) genesis := GenesisState{ Accounts: genesisAccounts, - StakeData: stake.DefaultGenesisState(), + StakeData: stakeGenesis, } // Marshal genesis diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index c26f6c82c0ad..a009dafe11a2 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -2,6 +2,7 @@ package simulation import ( "encoding/json" + "math/rand" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -34,8 +35,13 @@ func TestStakeWithRandomMessages(t *testing.T) { panic(err) } + appStateFn := func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { + mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + return json.RawMessage("{}") + } + simulation.Simulate( - t, mapp.BaseApp, json.RawMessage("{}"), + t, mapp.BaseApp, appStateFn, []simulation.TestAndRunTx{ SimulateMsgCreateValidator(mapper, stakeKeeper), SimulateMsgEditValidator(stakeKeeper), From c61b1aa591da31a1d2943444edd70637c5ac5c74 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 01:27:51 +0200 Subject: [PATCH 22/34] Event stats --- Makefile | 4 +++- x/mock/simulation/random_simulate_blocks.go | 10 +++++++++- x/mock/simulation/types.go | 2 +- x/mock/simulation/util.go | 6 ++++++ x/stake/simulation/msgs.go | 21 ++++++++++++++------- 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 6459734726fe..830aed42eda0 100644 --- a/Makefile +++ b/Makefile @@ -114,8 +114,10 @@ test_race: @go test -race $(PACKAGES_NOCLITEST) test_sim: + @echo "Running individual module simulations..." + @go test $(PACKAGES_SIMTEST) -v + @echo "Running full Gaia simulation..." @ENABLE_GAIA_SIMULATION=1 go test ./cmd/gaia/app -run TestFullGaiaSimulation -v - @go test $(PACKAGES_SIMTEST) test_cover: @bash tests/test_cover.sh diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index eca9757c5733..e952ce07f771 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -33,6 +33,12 @@ func SimulateFromSeed( keys, addrs := mock.GeneratePrivKeyAddressPairs(numKeys) r := rand.New(rand.NewSource(seed)) + // Setup event stats + events := make(map[string]uint) + event := func(what string) { + events[what] += 1 + } + app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, addrs)}) for i := 0; i < len(setups); i++ { setups[i](r, keys) @@ -53,7 +59,7 @@ func SimulateFromSeed( // TODO: Add modes to simulate "no load", "medium load", and // "high load" blocks. for j := 0; j < blockSize; j++ { - logUpdate, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log) + logUpdate, err := ops[r.Intn(len(ops))](t, r, app, ctx, keys, log, event) log += "\n" + logUpdate require.Nil(t, err, log) @@ -63,6 +69,8 @@ func SimulateFromSeed( app.EndBlock(abci.RequestEndBlock{}) header.Height++ } + + DisplayEvents(events) } // AssertAllInvariants asserts a list of provided invariants against application state diff --git a/x/mock/simulation/types.go b/x/mock/simulation/types.go index 41736ce2ce35..6e1d9f198ffb 100644 --- a/x/mock/simulation/types.go +++ b/x/mock/simulation/types.go @@ -15,7 +15,7 @@ type ( // about what this fuzzed tx actually did, for ease of debugging. TestAndRunTx func( t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, - privKeys []crypto.PrivKey, log string, + privKeys []crypto.PrivKey, log string, event func(string), ) (action string, err sdk.Error) // RandSetup performs the random setup the mock module needs. diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index 97307dc3e045..c6f8ed54be00 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -1,6 +1,7 @@ package simulation import ( + "fmt" "math/rand" ) @@ -29,3 +30,8 @@ func RandStringOfLength(r *rand.Rand, n int) string { } return string(b) } + +func DisplayEvents(events map[string]uint) { + // TODO + fmt.Printf("Events: %v\n", events) +} diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 2c7bf80f428d..c3060190fe9a 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -19,7 +19,7 @@ import ( // SimulateMsgCreateValidator 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) { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), @@ -47,6 +47,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgCreateValidator/%v", result.IsOK())) // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes()) return action, nil @@ -55,7 +56,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator 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) { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), Identity: simulation.RandStringOfLength(r, 10), @@ -75,6 +76,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgEditValidator/%v", result.IsOK())) action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes()) return action, nil } @@ -82,7 +84,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { // SimulateMsgDelegate 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) { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := keys[r.Intn(len(keys))] validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -106,6 +108,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAn if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgDelegate/%v", result.IsOK())) action = fmt.Sprintf("TestMsgDelegate: %s", msg.GetSignBytes()) return action, nil } @@ -113,7 +116,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAn // SimulateMsgBeginUnbonding 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) { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom validatorKey := keys[r.Intn(len(keys))] validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) @@ -137,6 +140,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgBeginUnbonding/%v", result.IsOK())) action = fmt.Sprintf("TestMsgBeginUnbonding: %s", msg.GetSignBytes()) return action, nil } @@ -144,7 +148,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgCompleteUnbonding 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) { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { validatorKey := keys[r.Intn(len(keys))] validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) delegatorKey := keys[r.Intn(len(keys))] @@ -159,6 +163,7 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.TestAndRunTx { if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgCompleteUnbonding/%v", result.IsOK())) action = fmt.Sprintf("TestMsgCompleteUnbonding with %s", msg.GetSignBytes()) return action, nil } @@ -166,7 +171,7 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.TestAndRunTx { // SimulateMsgBeginRedelegate 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) { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom sourceValidatorKey := keys[r.Intn(len(keys))] sourceValidatorAddress := sdk.AccAddress(sourceValidatorKey.PubKey().Address()) @@ -194,6 +199,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgBeginRedelegate/%v", result.IsOK())) action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes()) return action, nil } @@ -201,7 +207,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgCompleteRedelegate 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) { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { validatorSrcKey := keys[r.Intn(len(keys))] validatorSrcAddress := sdk.AccAddress(validatorSrcKey.PubKey().Address()) validatorDstKey := keys[r.Intn(len(keys))] @@ -219,6 +225,7 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.TestAndRunTx { if result.IsOK() { write() } + event(fmt.Sprintf("stake/MsgCompleteRedelegate/%v", result.IsOK())) action = fmt.Sprintf("TestMsgCompleteRedelegate with %s", msg.GetSignBytes()) return action, nil } From 6c61577b0b40d034d35bdff02e1effce3c5fc38d Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 07:37:38 +0200 Subject: [PATCH 23/34] Misc, environment variables --- Makefile | 8 ++++---- cmd/gaia/app/sim_test.go | 29 ++++++++++++++++++++++++----- x/mock/simulation/util.go | 1 + x/stake/keeper/delegation.go | 16 ++++++++++++++++ x/stake/simulation/invariants.go | 8 +++++++- x/stake/simulation/sim_test.go | 5 ++--- 6 files changed, 54 insertions(+), 13 deletions(-) diff --git a/Makefile b/Makefile index 830aed42eda0..02fb21715e58 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ BUILD_TAGS = netgo ledger BUILD_FLAGS = -tags "${BUILD_TAGS}" -ldflags "-X github.com/cosmos/cosmos-sdk/version.GitCommit=${COMMIT_HASH}" GCC := $(shell command -v gcc 2> /dev/null) LEDGER_ENABLED ?= true -all: get_tools get_vendor_deps install install_examples test_lint test test_sim +all: get_tools get_vendor_deps install install_examples test_lint test ######################################## ### CI @@ -114,10 +114,10 @@ test_race: @go test -race $(PACKAGES_NOCLITEST) test_sim: - @echo "Running individual module simulations..." + @echo "Running individual module simulations." @go test $(PACKAGES_SIMTEST) -v - @echo "Running full Gaia simulation..." - @ENABLE_GAIA_SIMULATION=1 go test ./cmd/gaia/app -run TestFullGaiaSimulation -v + @echo "Running full Gaia simulation. This may take several minutes. Set the environment variable 'GAIA_SIMULATION_SEED' to run with a constant seed." + @GAIA_SIMULATION_ENABLED=1 go test ./cmd/gaia/app -run TestFullGaiaSimulation -v test_cover: @bash tests/test_cover.sh diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 86c0de5942ed..a7b20586cac8 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -20,9 +20,13 @@ import ( const ( NumKeys = 10 NumBlocks = 100 - BlockSize = 500 + BlockSize = 100 - simulationEnv = "ENABLE_GAIA_SIMULATION" + simulationEnvEnable = "GAIA_SIMULATION_ENABLED" + simulationEnvSeed = "GAIA_SIMULATION_SEED" + simulationEnvKeys = "GAIA_SIMULATION_KEYS" + simulationEnvBlocks = "GAIA_SIMULATION_BLOCKS" + simulationEnvBlockSize = "GAIA_SIMULATION_BLOCK_SIZE" ) func appStateFn(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { @@ -55,7 +59,7 @@ func appStateFn(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { } func TestFullGaiaSimulation(t *testing.T) { - if os.Getenv(simulationEnv) == "" { + if os.Getenv(simulationEnvEnable) == "" { t.Skip("Skipping Gaia simulation") } @@ -65,11 +69,26 @@ func TestFullGaiaSimulation(t *testing.T) { app := NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) + var seed int64 + envSeed := os.Getenv(simulationEnvSeed) + if envSeed != "" { + seed, err = strconv.ParseInt(envSeed, 10, 64) + require.Nil(t, err) + } else { + seed = time.Now().UnixNano() + } + // Run randomized simulation - simulation.Simulate( - t, app.BaseApp, appStateFn, + simulation.SimulateFromSeed( + t, app.BaseApp, appStateFn, seed, []simulation.TestAndRunTx{ stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgEditValidator(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper), + stakesim.SimulateMsgBeginRedelegate(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgCompleteRedelegate(app.stakeKeeper), }, []simulation.RandSetup{}, []simulation.Invariant{ diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index c6f8ed54be00..35dcc1925756 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -6,6 +6,7 @@ import ( ) // shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 +// TODO we should probably move this to tendermint/libs/common/random.go const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" const ( diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index e7168109a66b..4c7dab739df0 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -110,6 +110,22 @@ func (k Keeper) GetUnbondingDelegationsFromValidator(ctx sdk.Context, valAddr sd return ubds } +// iterate through all of the unbonding delegations +func (k Keeper) IterateUnbondingDelegations(ctx sdk.Context, fn func(index int64, ubd types.UnbondingDelegation) (stop bool)) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, UnbondingDelegationKey) + i := int64(0) + for ; iterator.Valid(); iterator.Next() { + ubd := types.MustUnmarshalUBD(k.cdc, iterator.Key(), iterator.Value()) + stop := fn(i, ubd) + if stop { + break + } + i++ + } + iterator.Close() +} + // set the unbonding delegation and associated index func (k Keeper) SetUnbondingDelegation(ctx sdk.Context, ubd types.UnbondingDelegation) { store := ctx.KVStore(k.storeKey) diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 08567bb4e51d..e5f042a6e938 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -1,6 +1,7 @@ package simulation import ( + "fmt" "testing" "github.com/stretchr/testify/require" @@ -30,12 +31,17 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim ctx := app.NewContext(false, abci.Header{}) pool := k.GetPool(ctx) - // Loose tokens should equal coin supply + // Loose tokens should equal coin supply plus unbonding delegations loose := sdk.ZeroInt() am.IterateAccounts(ctx, func(acc auth.Account) bool { loose = loose.Add(acc.GetCoins().AmountOf("steak")) return false }) + k.IterateUnbondingDelegations(ctx, func(_ int64, ubd stake.UnbondingDelegation) bool { + fmt.Printf("found ubd with balance: %v\n", ubd.Balance) + loose = loose.Add(ubd.Balance.Amount) + 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 diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index a009dafe11a2..a2afeb665ae7 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -46,8 +46,7 @@ func TestStakeWithRandomMessages(t *testing.T) { SimulateMsgCreateValidator(mapper, stakeKeeper), SimulateMsgEditValidator(stakeKeeper), SimulateMsgDelegate(mapper, stakeKeeper), - // XXX TODO - // SimulateMsgBeginUnbonding(mapper, stakeKeeper), + SimulateMsgBeginUnbonding(mapper, stakeKeeper), SimulateMsgCompleteUnbonding(stakeKeeper), SimulateMsgBeginRedelegate(mapper, stakeKeeper), SimulateMsgCompleteRedelegate(stakeKeeper), @@ -55,6 +54,6 @@ func TestStakeWithRandomMessages(t *testing.T) { SimulationSetup(mapp, stakeKeeper), }, []simulation.Invariant{ AllInvariants(coinKeeper, stakeKeeper, mapp.AccountMapper), - }, 10, 100, 500, + }, 10, 100, 100, ) } From 966f26dfb23c532481972fa59ffd9cd395e24120 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 07:44:40 +0200 Subject: [PATCH 24/34] Remove print, quickfix --- cmd/gaia/app/sim_test.go | 5 ++++- x/stake/keeper/delegation.go | 6 ++++++ x/stake/simulation/invariants.go | 2 -- x/stake/types/errors.go | 4 ++++ 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index a7b20586cac8..246d03a0fa0e 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -4,7 +4,9 @@ import ( "encoding/json" "math/rand" "os" + "strconv" "testing" + "time" "github.com/stretchr/testify/require" @@ -70,6 +72,7 @@ func TestFullGaiaSimulation(t *testing.T) { require.Equal(t, "GaiaApp", app.Name()) var seed int64 + var err error envSeed := os.Getenv(simulationEnvSeed) if envSeed != "" { seed, err = strconv.ParseInt(envSeed, 10, 64) @@ -83,7 +86,7 @@ func TestFullGaiaSimulation(t *testing.T) { t, app.BaseApp, appStateFn, seed, []simulation.TestAndRunTx{ stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper), - stakesim.SimulateMsgEditValidator(app.accountMapper, app.stakeKeeper), + stakesim.SimulateMsgEditValidator(app.stakeKeeper), stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper), stakesim.SimulateMsgBeginUnbonding(app.accountMapper, app.stakeKeeper), stakesim.SimulateMsgCompleteUnbonding(app.stakeKeeper), diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 4c7dab739df0..23b58108f85c 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -314,6 +314,12 @@ func (k Keeper) unbond(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddr // complete unbonding an unbonding record func (k Keeper) BeginUnbonding(ctx sdk.Context, delegatorAddr, validatorAddr sdk.AccAddress, sharesAmount sdk.Rat) sdk.Error { + // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 + _, found := k.GetUnbondingDelegation(ctx, delegatorAddr, validatorAddr) + if found { + return types.ErrExistingUnbondingDelegation(k.Codespace()) + } + returnAmount, err := k.unbond(ctx, delegatorAddr, validatorAddr, sharesAmount) if err != nil { return err diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index e5f042a6e938..776c8d1e7de8 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -1,7 +1,6 @@ package simulation import ( - "fmt" "testing" "github.com/stretchr/testify/require" @@ -38,7 +37,6 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim return false }) k.IterateUnbondingDelegations(ctx, func(_ int64, ubd stake.UnbondingDelegation) bool { - fmt.Printf("found ubd with balance: %v\n", ubd.Balance) loose = loose.Add(ubd.Balance.Amount) return false }) diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 237340b89d5b..2ef747bae63e 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -120,6 +120,10 @@ func ErrNoUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no unbonding delegation found") } +func ErrExistingUnbondingDelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, "existing unbonding delegation found") +} + func ErrNoRedelegation(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidDelegation, "no redelegation found") } From ad410c1e2ebdc571da323c95c8b4ce90dacc0793 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 07:50:04 +0200 Subject: [PATCH 25/34] Linter fixes --- x/mock/simulation/random_simulate_blocks.go | 2 +- x/mock/simulation/util.go | 2 ++ x/stake/simulation/msgs.go | 4 ++-- x/stake/simulation/sim_test.go | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index e952ce07f771..5f507b89c1fd 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -36,7 +36,7 @@ func SimulateFromSeed( // Setup event stats events := make(map[string]uint) event := func(what string) { - events[what] += 1 + events[what]++ } app.InitChain(abci.RequestInitChain{AppStateBytes: appStateFn(r, addrs)}) diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index 35dcc1925756..8fdf5021e0ab 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -15,6 +15,7 @@ const ( letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) +// Generate a random string of a particular length func RandStringOfLength(r *rand.Rand, n int) string { b := make([]byte, n) // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! @@ -32,6 +33,7 @@ func RandStringOfLength(r *rand.Rand, n int) string { return string(b) } +// Pretty-print events as a table func DisplayEvents(events map[string]uint) { // TODO fmt.Printf("Events: %v\n", events) diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index c3060190fe9a..f46881e307b2 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -231,8 +231,8 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.TestAndRunTx { } } -// SimulationSetup -func SimulationSetup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { +// Setup +func Setup(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()) diff --git a/x/stake/simulation/sim_test.go b/x/stake/simulation/sim_test.go index a2afeb665ae7..391ca1996e7d 100644 --- a/x/stake/simulation/sim_test.go +++ b/x/stake/simulation/sim_test.go @@ -51,7 +51,7 @@ func TestStakeWithRandomMessages(t *testing.T) { SimulateMsgBeginRedelegate(mapper, stakeKeeper), SimulateMsgCompleteRedelegate(stakeKeeper), }, []simulation.RandSetup{ - SimulationSetup(mapp, stakeKeeper), + Setup(mapp, stakeKeeper), }, []simulation.Invariant{ AllInvariants(coinKeeper, stakeKeeper, mapp.AccountMapper), }, 10, 100, 100, From 8bd54f0701ea515d6faa4b307c0dbaa0f8f099f7 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 09:05:48 +0200 Subject: [PATCH 26/34] Refactor bank tests --- cmd/gaia/app/sim_test.go | 3 + x/bank/app_test.go | 15 ---- x/bank/simulation/invariants.go | 50 +++++++++++ x/bank/simulation/msgs.go | 117 +++++++++++++++++++++++++ x/bank/simulation/sim_test.go | 44 ++++++++++ x/bank/test_helpers.go | 150 -------------------------------- 6 files changed, 214 insertions(+), 165 deletions(-) create mode 100644 x/bank/simulation/invariants.go create mode 100644 x/bank/simulation/msgs.go create mode 100644 x/bank/simulation/sim_test.go delete mode 100644 x/bank/test_helpers.go diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 246d03a0fa0e..745824f00830 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -14,6 +14,7 @@ import ( "github.com/tendermint/tendermint/libs/log" sdk "github.com/cosmos/cosmos-sdk/types" + banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation" stake "github.com/cosmos/cosmos-sdk/x/stake" stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" @@ -85,6 +86,7 @@ func TestFullGaiaSimulation(t *testing.T) { simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, []simulation.TestAndRunTx{ + banksim.TestAndRunSingleInputMsgSend(app.accountMapper), stakesim.SimulateMsgCreateValidator(app.accountMapper, app.stakeKeeper), stakesim.SimulateMsgEditValidator(app.stakeKeeper), stakesim.SimulateMsgDelegate(app.accountMapper, app.stakeKeeper), @@ -95,6 +97,7 @@ func TestFullGaiaSimulation(t *testing.T) { }, []simulation.RandSetup{}, []simulation.Invariant{ + banksim.NonnegativeBalanceInvariant(app.accountMapper), stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper), }, NumKeys, diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 74a421bd7edf..8c82cd4752a9 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -83,21 +83,6 @@ func getMockApp(t *testing.T) *mock.App { return mapp } -func TestBankWithRandomMessages(t *testing.T) { - mapp := getMockApp(t) - setup := func(r *rand.Rand, keys []crypto.PrivKey) { - return - } - - mapp.RandomizedTesting( - t, - []mock.TestAndRunTx{TestAndRunSingleInputMsgSend}, - []mock.RandSetup{setup}, - []mock.Invariant{ModuleInvariants}, - 100, 30, 30, - ) -} - func TestMsgSendWithAccounts(t *testing.T) { mapp := getMockApp(t) diff --git a/x/bank/simulation/invariants.go b/x/bank/simulation/invariants.go new file mode 100644 index 000000000000..847288e1f16e --- /dev/null +++ b/x/bank/simulation/invariants.go @@ -0,0 +1,50 @@ +package simulation + +import ( + "fmt" + "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/mock" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" + abci "github.com/tendermint/tendermint/abci/types" +) + +// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances +func NonnegativeBalanceInvariant(mapper auth.AccountMapper) simulation.Invariant { + return func(t *testing.T, app *baseapp.BaseApp, log string) { + ctx := app.NewContext(false, abci.Header{}) + accts := mock.GetAllAccounts(mapper, ctx) + for _, acc := range accts { + coins := acc.GetCoins() + require.True(t, coins.IsNotNegative(), + fmt.Sprintf("%s has a negative denomination of %s\n%s", + acc.GetAddress().String(), + coins.String(), + log), + ) + } + } +} + +// TotalCoinsInvariant checks that the sum of the coins across all accounts +// is what is expected +func TotalCoinsInvariant(mapper auth.AccountMapper, totalSupplyFn func() sdk.Coins) simulation.Invariant { + return func(t *testing.T, app *baseapp.BaseApp, log string) { + ctx := app.NewContext(false, abci.Header{}) + totalCoins := sdk.Coins{} + + chkAccount := func(acc auth.Account) bool { + coins := acc.GetCoins() + totalCoins = totalCoins.Plus(coins) + return false + } + + mapper.IterateAccounts(ctx, chkAccount) + require.Equal(t, totalSupplyFn(), totalCoins, log) + } +} diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go new file mode 100644 index 000000000000..553d000db475 --- /dev/null +++ b/x/bank/simulation/msgs.go @@ -0,0 +1,117 @@ +package simulation + +import ( + "errors" + "fmt" + "math/big" + "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/tendermint/tendermint/crypto" +) + +// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both +// accounts already exist. +func TestAndRunSingleInputMsgSend(mapper auth.AccountMapper) simulation.TestAndRunTx { + return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { + fromKey := keys[r.Intn(len(keys))] + fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) + toKey := keys[r.Intn(len(keys))] + // Disallow sending money to yourself + for { + if !fromKey.Equals(toKey) { + break + } + toKey = keys[r.Intn(len(keys))] + } + toAddr := sdk.AccAddress(toKey.PubKey().Address()) + initFromCoins := mapper.GetAccount(ctx, fromAddr).GetCoins() + + denomIndex := r.Intn(len(initFromCoins)) + amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount) + if goErr != nil { + return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil + } + + action = fmt.Sprintf("%s is sending %s %s to %s", + fromAddr.String(), + amt.String(), + initFromCoins[denomIndex].Denom, + toAddr.String(), + ) + log = fmt.Sprintf("%s\n%s", log, action) + + coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}} + var msg = bank.MsgSend{ + Inputs: []bank.Input{bank.NewInput(fromAddr, coins)}, + Outputs: []bank.Output{bank.NewOutput(toAddr, coins)}, + } + sendAndVerifyMsgSend(t, app, mapper, msg, ctx, log, []crypto.PrivKey{fromKey}) + event("bank/sendAndVerifyMsgSend/ok") + + return action, nil + } +} + +// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs +func sendAndVerifyMsgSend(t *testing.T, app *baseapp.BaseApp, mapper auth.AccountMapper, msg bank.MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { + initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) + initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) + AccountNumbers := make([]int64, len(msg.Inputs)) + SequenceNumbers := make([]int64, len(msg.Inputs)) + + for i := 0; i < len(msg.Inputs); i++ { + acc := mapper.GetAccount(ctx, msg.Inputs[i].Address) + AccountNumbers[i] = acc.GetAccountNumber() + SequenceNumbers[i] = acc.GetSequence() + initialInputAddrCoins[i] = acc.GetCoins() + } + for i := 0; i < len(msg.Outputs); i++ { + acc := mapper.GetAccount(ctx, msg.Outputs[i].Address) + initialOutputAddrCoins[i] = acc.GetCoins() + } + tx := mock.GenTx([]sdk.Msg{msg}, + AccountNumbers, + SequenceNumbers, + privkeys...) + res := app.Deliver(tx) + if !res.IsOK() { + // TODO: Do this in a more 'canonical' way + fmt.Println(res) + fmt.Println(log) + t.FailNow() + } + + for i := 0; i < len(msg.Inputs); i++ { + terminalInputCoins := mapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() + require.Equal(t, + initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins), + terminalInputCoins, + fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log), + ) + } + for i := 0; i < len(msg.Outputs); i++ { + terminalOutputCoins := mapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() + require.Equal(t, + initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins), + terminalOutputCoins, + fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log), + ) + } +} + +func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) { + if !max.GT(sdk.OneInt()) { + return sdk.Int{}, errors.New("max too small") + } + max = max.Sub(sdk.OneInt()) + return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil +} diff --git a/x/bank/simulation/sim_test.go b/x/bank/simulation/sim_test.go new file mode 100644 index 000000000000..5d76dd0589a6 --- /dev/null +++ b/x/bank/simulation/sim_test.go @@ -0,0 +1,44 @@ +package simulation + +import ( + "encoding/json" + "math/rand" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/bank" + "github.com/cosmos/cosmos-sdk/x/mock" + "github.com/cosmos/cosmos-sdk/x/mock/simulation" +) + +func TestBankWithRandomMessages(t *testing.T) { + mapp := mock.NewApp() + + bank.RegisterWire(mapp.Cdc) + mapper := mapp.AccountMapper + coinKeeper := bank.NewKeeper(mapper) + mapp.Router().AddRoute("bank", bank.NewHandler(coinKeeper)) + + err := mapp.CompleteSetup([]*sdk.KVStoreKey{}) + if err != nil { + panic(err) + } + + appStateFn := func(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { + mock.RandomSetGenesis(r, mapp, accs, []string{"stake"}) + return json.RawMessage("{}") + } + + simulation.Simulate( + t, mapp.BaseApp, appStateFn, + []simulation.TestAndRunTx{ + TestAndRunSingleInputMsgSend(mapper), + }, + []simulation.RandSetup{}, + []simulation.Invariant{ + NonnegativeBalanceInvariant(mapper), + TotalCoinsInvariant(mapper, func() sdk.Coins { return mapp.TotalCoinsSupply }), + }, + 100, 30, 30, + ) +} diff --git a/x/bank/test_helpers.go b/x/bank/test_helpers.go deleted file mode 100644 index 1dad0ba267b4..000000000000 --- a/x/bank/test_helpers.go +++ /dev/null @@ -1,150 +0,0 @@ -package bank - -import ( - "errors" - "fmt" - "math/big" - "math/rand" - "testing" - - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/mock" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto" -) - -// ModuleInvariants runs all invariants of the bank module. -// Currently runs non-negative balance invariant and TotalCoinsInvariant -func ModuleInvariants(t *testing.T, app *mock.App, log string) { - NonnegativeBalanceInvariant(t, app, log) - TotalCoinsInvariant(t, app, log) -} - -// NonnegativeBalanceInvariant checks that all accounts in the application have non-negative balances -func NonnegativeBalanceInvariant(t *testing.T, app *mock.App, log string) { - ctx := app.NewContext(false, abci.Header{}) - accts := mock.GetAllAccounts(app.AccountMapper, ctx) - for _, acc := range accts { - coins := acc.GetCoins() - assert.True(t, coins.IsNotNegative(), - fmt.Sprintf("%s has a negative denomination of %s\n%s", - acc.GetAddress().String(), - coins.String(), - log), - ) - } -} - -// TotalCoinsInvariant checks that the sum of the coins across all accounts -// is what is expected -func TotalCoinsInvariant(t *testing.T, app *mock.App, log string) { - ctx := app.BaseApp.NewContext(false, abci.Header{}) - totalCoins := sdk.Coins{} - - chkAccount := func(acc auth.Account) bool { - coins := acc.GetCoins() - totalCoins = totalCoins.Plus(coins) - return false - } - - app.AccountMapper.IterateAccounts(ctx, chkAccount) - require.Equal(t, app.TotalCoinsSupply, totalCoins, log) -} - -// TestAndRunSingleInputMsgSend tests and runs a single msg send, with one input and one output, where both -// accounts already exist. -func TestAndRunSingleInputMsgSend(t *testing.T, r *rand.Rand, app *mock.App, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) { - fromKey := keys[r.Intn(len(keys))] - fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) - toKey := keys[r.Intn(len(keys))] - // Disallow sending money to yourself - for { - if !fromKey.Equals(toKey) { - break - } - toKey = keys[r.Intn(len(keys))] - } - toAddr := sdk.AccAddress(toKey.PubKey().Address()) - initFromCoins := app.AccountMapper.GetAccount(ctx, fromAddr).GetCoins() - - denomIndex := r.Intn(len(initFromCoins)) - amt, goErr := randPositiveInt(r, initFromCoins[denomIndex].Amount) - if goErr != nil { - return "skipping bank send due to account having no coins of denomination " + initFromCoins[denomIndex].Denom, nil - } - - action = fmt.Sprintf("%s is sending %s %s to %s", - fromAddr.String(), - amt.String(), - initFromCoins[denomIndex].Denom, - toAddr.String(), - ) - log = fmt.Sprintf("%s\n%s", log, action) - - coins := sdk.Coins{{initFromCoins[denomIndex].Denom, amt}} - var msg = MsgSend{ - Inputs: []Input{NewInput(fromAddr, coins)}, - Outputs: []Output{NewOutput(toAddr, coins)}, - } - sendAndVerifyMsgSend(t, app, msg, ctx, log, []crypto.PrivKey{fromKey}) - - return action, nil -} - -// Sends and verifies the transition of a msg send. This fails if there are repeated inputs or outputs -func sendAndVerifyMsgSend(t *testing.T, app *mock.App, msg MsgSend, ctx sdk.Context, log string, privkeys []crypto.PrivKey) { - initialInputAddrCoins := make([]sdk.Coins, len(msg.Inputs)) - initialOutputAddrCoins := make([]sdk.Coins, len(msg.Outputs)) - AccountNumbers := make([]int64, len(msg.Inputs)) - SequenceNumbers := make([]int64, len(msg.Inputs)) - - for i := 0; i < len(msg.Inputs); i++ { - acc := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address) - AccountNumbers[i] = acc.GetAccountNumber() - SequenceNumbers[i] = acc.GetSequence() - initialInputAddrCoins[i] = acc.GetCoins() - } - for i := 0; i < len(msg.Outputs); i++ { - acc := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address) - initialOutputAddrCoins[i] = acc.GetCoins() - } - tx := mock.GenTx([]sdk.Msg{msg}, - AccountNumbers, - SequenceNumbers, - privkeys...) - res := app.Deliver(tx) - if !res.IsOK() { - // TODO: Do this in a more 'canonical' way - fmt.Println(res) - fmt.Println(log) - t.FailNow() - } - - for i := 0; i < len(msg.Inputs); i++ { - terminalInputCoins := app.AccountMapper.GetAccount(ctx, msg.Inputs[i].Address).GetCoins() - require.Equal(t, - initialInputAddrCoins[i].Minus(msg.Inputs[i].Coins), - terminalInputCoins, - fmt.Sprintf("Input #%d had an incorrect amount of coins\n%s", i, log), - ) - } - for i := 0; i < len(msg.Outputs); i++ { - terminalOutputCoins := app.AccountMapper.GetAccount(ctx, msg.Outputs[i].Address).GetCoins() - require.Equal(t, - initialOutputAddrCoins[i].Plus(msg.Outputs[i].Coins), - terminalOutputCoins, - fmt.Sprintf("Output #%d had an incorrect amount of coins\n%s", i, log), - ) - } -} - -func randPositiveInt(r *rand.Rand, max sdk.Int) (sdk.Int, error) { - if !max.GT(sdk.OneInt()) { - return sdk.Int{}, errors.New("max too small") - } - max = max.Sub(sdk.OneInt()) - return sdk.NewIntFromBigInt(new(big.Int).Rand(r, max.BigInt())).Add(sdk.OneInt()), nil -} From 05ceff521283705be9d95b29849f0e32e7719014 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 09:37:14 +0200 Subject: [PATCH 27/34] Deterministic 'make test_sim' on CircleCI; bank test fix --- .circleci/config.yml | 1 + x/bank/app_test.go | 2 -- x/stake/simulation/invariants.go | 5 +---- x/stake/simulation/msgs.go | 12 ++++++------ 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index bba1031fd721..ff9395c526f7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,6 +99,7 @@ jobs: name: Test simulation command: | export PATH="$GOBIN:$PATH" + export GAIA_SIMULATION_SEED=1531897442166404087 make test_sim test_cover: diff --git a/x/bank/app_test.go b/x/bank/app_test.go index 8c82cd4752a9..2deb5de38f24 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -5,8 +5,6 @@ import ( "github.com/stretchr/testify/require" - "math/rand" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/mock" diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 776c8d1e7de8..92dd317d0233 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -41,8 +41,7 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim 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 + pool.LooseTokens.RoundInt64(), loose.Int64(), log) // Bonded tokens should equal sum of tokens with bonded validators bonded := sdk.ZeroRat() @@ -54,7 +53,6 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim 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 } @@ -68,7 +66,6 @@ func PositivePowerInvariant(k stake.Keeper) simulation.Invariant { require.True(t, validator.GetPower().GT(sdk.ZeroRat()), "validator with non-positive power stored") return false }) - // stats["stake/invariant/positivePower"] += 1 } } diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index f46881e307b2..597046107c47 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -49,7 +49,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation } event(fmt.Sprintf("stake/MsgCreateValidator/%v", result.IsOK())) // require.True(t, result.IsOK(), "expected OK result but instead got %v", result) - action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgCreateValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil } } @@ -77,7 +77,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { write() } event(fmt.Sprintf("stake/MsgEditValidator/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgEditValidator: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil } } @@ -109,7 +109,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAn write() } event(fmt.Sprintf("stake/MsgDelegate/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgDelegate: %s", msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgDelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil } } @@ -141,7 +141,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. write() } event(fmt.Sprintf("stake/MsgBeginUnbonding/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgBeginUnbonding: %s", msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgBeginUnbonding: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil } } @@ -164,7 +164,7 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.TestAndRunTx { write() } event(fmt.Sprintf("stake/MsgCompleteUnbonding/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgCompleteUnbonding with %s", msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgCompleteUnbonding: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil } } @@ -226,7 +226,7 @@ func SimulateMsgCompleteRedelegate(k stake.Keeper) simulation.TestAndRunTx { write() } event(fmt.Sprintf("stake/MsgCompleteRedelegate/%v", result.IsOK())) - action = fmt.Sprintf("TestMsgCompleteRedelegate with %s", msg.GetSignBytes()) + action = fmt.Sprintf("TestMsgCompleteRedelegate: ok %v, msg %s", result.IsOK(), msg.GetSignBytes()) return action, nil } } From cea2be6107abbf9b277e44d16a91c654c3b3ddc8 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 09:42:18 +0200 Subject: [PATCH 28/34] Fix loose tokens invariant --- types/stake.go | 1 + x/stake/simulation/invariants.go | 16 ++++++++++------ x/stake/types/validator.go | 1 + 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/types/stake.go b/types/stake.go index eb3f660820f3..e48577c0bf16 100644 --- a/types/stake.go +++ b/types/stake.go @@ -43,6 +43,7 @@ type Validator interface { GetOwner() AccAddress // owner AccAddress to receive/return validators coins GetPubKey() crypto.PubKey // validation pubkey GetPower() Rat // validation power + GetTokens() Rat // validation tokens GetDelegatorShares() Rat // Total out standing delegator shares GetBondHeight() int64 // height in which the validator became active } diff --git a/x/stake/simulation/invariants.go b/x/stake/simulation/invariants.go index 92dd317d0233..e4869693cfa0 100644 --- a/x/stake/simulation/invariants.go +++ b/x/stake/simulation/invariants.go @@ -30,8 +30,8 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim ctx := app.NewContext(false, abci.Header{}) pool := k.GetPool(ctx) - // Loose tokens should equal coin supply plus unbonding delegations loose := sdk.ZeroInt() + bonded := sdk.ZeroRat() am.IterateAccounts(ctx, func(acc auth.Account) bool { loose = loose.Add(acc.GetCoins().AmountOf("steak")) return false @@ -40,18 +40,22 @@ func SupplyInvariants(ck bank.Keeper, k stake.Keeper, am auth.AccountMapper) sim loose = loose.Add(ubd.Balance.Amount) 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.RoundInt64(), loose.Int64(), log) - - // 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()) + case sdk.Unbonding: + case sdk.Unbonded: + loose = loose.Add(validator.GetTokens().RoundInt()) } return false }) + + // Loose tokens should equal coin supply plus unbonding delegations plus tokens on unbonded validators + 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.RoundInt64(), loose.Int64(), log) + + // Bonded tokens should equal sum of tokens with bonded validators require.True(t, pool.BondedTokens.Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators\nlog: %s", log) // TODO Inflation check on total supply diff --git a/x/stake/types/validator.go b/x/stake/types/validator.go index ed109830f035..ee1cb5a4427f 100644 --- a/x/stake/types/validator.go +++ b/x/stake/types/validator.go @@ -406,6 +406,7 @@ func (v Validator) GetStatus() sdk.BondStatus { return v.Status } func (v Validator) GetOwner() sdk.AccAddress { return v.Owner } func (v Validator) GetPubKey() crypto.PubKey { return v.PubKey } func (v Validator) GetPower() sdk.Rat { return v.BondedTokens() } +func (v Validator) GetTokens() sdk.Rat { return v.Tokens } func (v Validator) GetDelegatorShares() sdk.Rat { return v.DelegatorShares } func (v Validator) GetBondHeight() int64 { return v.BondHeight } From dc14eef639a0bfc61b113dba6d36a2b1aecc3892 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 09:47:55 +0200 Subject: [PATCH 29/34] Clarify 'nop', linter fix --- examples/democoin/mock/validator.go | 5 +++++ x/stake/simulation/msgs.go | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go index 84d41d4880f2..c3d01b170353 100644 --- a/examples/democoin/mock/validator.go +++ b/examples/democoin/mock/validator.go @@ -28,6 +28,11 @@ func (v Validator) GetPubKey() crypto.PubKey { return nil } +// Implements sdk.Validator +func (v Validator) GetTokens() sdk.Rat { + return sdk.ZeroRat() +} + // Implements sdk.Validator func (v Validator) GetPower() sdk.Rat { return v.Power diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 597046107c47..41bda5dd6f72 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -32,7 +32,7 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) } if amount.Equal(sdk.ZeroInt()) { - return "nop", nil + return "no-operation", nil } msg := stake.MsgCreateValidator{ Description: description, @@ -95,7 +95,7 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAn amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) } if amount.Equal(sdk.ZeroInt()) { - return "nop", nil + return "no-operation", nil } msg := stake.MsgDelegate{ DelegatorAddr: delegatorAddress, @@ -127,7 +127,7 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) } if amount.Equal(sdk.ZeroInt()) { - return "nop", nil + return "no-operation", nil } msg := stake.MsgBeginUnbonding{ DelegatorAddr: delegatorAddress, @@ -185,7 +185,7 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) } if amount.Equal(sdk.ZeroInt()) { - return "nop", nil + return "no-operation", nil } msg := stake.MsgBeginRedelegate{ DelegatorAddr: delegatorAddress, From dcbd13c392245538ec0602e75963657e6883ef7c Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 09:54:41 +0200 Subject: [PATCH 30/34] Environment variables --- Makefile | 6 +++++- cmd/gaia/app/sim_test.go | 33 +++++++++++++++++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/Makefile b/Makefile index 02fb21715e58..b3b5851d6f4b 100644 --- a/Makefile +++ b/Makefile @@ -116,7 +116,11 @@ test_race: test_sim: @echo "Running individual module simulations." @go test $(PACKAGES_SIMTEST) -v - @echo "Running full Gaia simulation. This may take several minutes. Set the environment variable 'GAIA_SIMULATION_SEED' to run with a constant seed." + @echo "Running full Gaia simulation. This may take several minutes." + @echo "Set the environment variable 'GAIA_SIMULATION_SEED' to run with a constant seed." + @echo "Set the environment variable 'GAIA_SIMULATION_KEYS' to run with the specified number of keys." + @echo "Set the environment variable 'GAIA_SIMULATION_BLOCKS' to run with the specified number of blocks." + @echo "Set the environment variable 'GAIA_SIMULATION_BLOCK_SIZE' to run with the specified block size (operations per block)." @GAIA_SIMULATION_ENABLED=1 go test ./cmd/gaia/app -run TestFullGaiaSimulation -v test_cover: diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 745824f00830..89b1b54aa92a 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -21,9 +21,9 @@ import ( ) const ( - NumKeys = 10 - NumBlocks = 100 - BlockSize = 100 + defaultNumKeys = 10 + defaultNumBlocks = 100 + defaultBlockSize = 100 simulationEnvEnable = "GAIA_SIMULATION_ENABLED" simulationEnvSeed = "GAIA_SIMULATION_SEED" @@ -82,6 +82,27 @@ func TestFullGaiaSimulation(t *testing.T) { seed = time.Now().UnixNano() } + keys := defaultNumKeys + envKeys := os.Getenv(simulationEnvKeys) + if envKeys != "" { + keys, err = strconv.Atoi(envKeys) + require.Nil(t, err) + } + + blocks := defaultNumBlocks + envBlocks := os.Getenv(simulationEnvBlocks) + if envBlocks != "" { + blocks, err = strconv.Atoi(envBlocks) + require.Nil(t, err) + } + + blockSize := defaultBlockSize + envBlockSize := os.Getenv(simulationEnvBlockSize) + if envBlockSize != "" { + blockSize, err = strconv.Atoi(envBlockSize) + require.Nil(t, err) + } + // Run randomized simulation simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, @@ -100,9 +121,9 @@ func TestFullGaiaSimulation(t *testing.T) { banksim.NonnegativeBalanceInvariant(app.accountMapper), stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper), }, - NumKeys, - NumBlocks, - BlockSize, + keys, + blocks, + blockSize, ) } From 2c0cd73fc59fd66119239f6a44507383ae9a6d41 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 09:58:17 +0200 Subject: [PATCH 31/34] Remove old randomized pool testing, redundant --- x/stake/types/test_utils.go | 173 -------------------------------- x/stake/types/validator_test.go | 62 ------------ 2 files changed, 235 deletions(-) diff --git a/x/stake/types/test_utils.go b/x/stake/types/test_utils.go index 104eae3d315f..1ab72119e856 100644 --- a/x/stake/types/test_utils.go +++ b/x/stake/types/test_utils.go @@ -1,12 +1,7 @@ package types import ( - "fmt" - "math/rand" - "testing" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto" ) @@ -21,171 +16,3 @@ var ( emptyAddr sdk.AccAddress emptyPubkey crypto.PubKey ) - -// Operation reflects any operation that transforms staking state. It takes in -// a RNG instance, pool, validator and returns an updated pool, updated -// validator, delta tokens, and descriptive message. -type Operation func(r *rand.Rand, pool Pool, c Validator) (Pool, Validator, sdk.Rat, string) - -// OpBondOrUnbond implements an operation that bonds or unbonds a validator -// depending on current status. -// nolint: unparam -// TODO split up into multiple operations -func OpBondOrUnbond(r *rand.Rand, pool Pool, validator Validator) (Pool, Validator, sdk.Rat, string) { - var ( - msg string - newStatus sdk.BondStatus - ) - - if validator.Status == sdk.Bonded { - msg = fmt.Sprintf("sdk.Unbonded previously bonded validator %#v", validator) - newStatus = sdk.Unbonded - - } else if validator.Status == sdk.Unbonded { - msg = fmt.Sprintf("sdk.Bonded previously bonded validator %#v", validator) - newStatus = sdk.Bonded - } - - validator, pool = validator.UpdateStatus(pool, newStatus) - return pool, validator, sdk.ZeroRat(), msg -} - -// OpAddTokens implements an operation that adds a random number of tokens to a -// validator. -func OpAddTokens(r *rand.Rand, pool Pool, validator Validator) (Pool, Validator, sdk.Rat, string) { - msg := fmt.Sprintf("validator %#v", validator) - - tokens := int64(r.Int31n(1000)) - validator, pool, _ = validator.AddTokensFromDel(pool, tokens) - msg = fmt.Sprintf("Added %d tokens to %s", tokens, msg) - - // Tokens are removed so for accounting must be negative - return pool, validator, sdk.NewRat(-1 * tokens), msg -} - -// OpRemoveShares implements an operation that removes a random number of -// delegatorshares from a validator. -func OpRemoveShares(r *rand.Rand, pool Pool, validator Validator) (Pool, Validator, sdk.Rat, string) { - var shares sdk.Rat - for { - shares = sdk.NewRat(int64(r.Int31n(1000))) - if shares.LT(validator.DelegatorShares) { - break - } - } - - msg := fmt.Sprintf("Removed %v shares from validator %#v", shares, validator) - - validator, pool, tokens := validator.RemoveDelShares(pool, shares) - return pool, validator, tokens, msg -} - -// RandomOperation returns a random staking operation. -func RandomOperation(r *rand.Rand) Operation { - operations := []Operation{ - OpBondOrUnbond, - OpAddTokens, - OpRemoveShares, - } - r.Shuffle(len(operations), func(i, j int) { - operations[i], operations[j] = operations[j], operations[i] - }) - - return operations[0] -} - -// AssertInvariants ensures invariants that should always be true are true. -// nolint: unparam -func AssertInvariants(t *testing.T, msg string, - pOrig Pool, cOrig []Validator, pMod Pool, vMods []Validator) { - - // total tokens conserved - require.True(t, - pOrig.LooseTokens.Add(pOrig.BondedTokens).Equal( - pMod.LooseTokens.Add(pMod.BondedTokens)), - "Tokens not conserved - msg: %v\n, pOrig.BondedTokens: %v, pOrig.LooseTokens: %v, pMod.BondedTokens: %v, pMod.LooseTokens: %v", - msg, - pOrig.BondedTokens, pOrig.LooseTokens, - pMod.BondedTokens, pMod.LooseTokens) - - // Nonnegative bonded tokens - require.False(t, pMod.BondedTokens.LT(sdk.ZeroRat()), - "Negative bonded shares - msg: %v\npOrig: %v\npMod: %v\n", - msg, pOrig, pMod) - - // Nonnegative loose tokens - require.False(t, pMod.LooseTokens.LT(sdk.ZeroRat()), - "Negative unbonded shares - msg: %v\npOrig: %v\npMod: %v\n", - msg, pOrig, pMod) - - for _, vMod := range vMods { - // Nonnegative ex rate - require.False(t, vMod.DelegatorShareExRate().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShareExRate(): %v (validator.Owner: %s)", - msg, - vMod.DelegatorShareExRate(), - vMod.Owner, - ) - - // Nonnegative poolShares - require.False(t, vMod.BondedTokens().LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.BondedTokens(): %#v", - msg, - vMod, - ) - - // Nonnegative delShares - require.False(t, vMod.DelegatorShares.LT(sdk.ZeroRat()), - "Applying operation \"%s\" resulted in negative validator.DelegatorShares: %#v", - msg, - vMod, - ) - } -} - -// TODO: refactor this random setup - -// randomValidator generates a random validator. -// nolint: unparam -func randomValidator(r *rand.Rand, i int) Validator { - - tokens := sdk.NewRat(int64(r.Int31n(10000))) - delShares := sdk.NewRat(int64(r.Int31n(10000))) - - // TODO add more options here - status := sdk.Bonded - if r.Float64() > float64(0.5) { - status = sdk.Unbonded - } - - validator := NewValidator(addr1, pk1, Description{}) - validator.Status = status - validator.Tokens = tokens - validator.DelegatorShares = delShares - - return validator -} - -// RandomSetup generates a random staking state. -func RandomSetup(r *rand.Rand, numValidators int) (Pool, []Validator) { - pool := InitialPool() - pool.LooseTokens = sdk.NewRat(100000) - - validators := make([]Validator, numValidators) - for i := 0; i < numValidators; i++ { - validator := randomValidator(r, i) - - switch validator.Status { - case sdk.Bonded: - pool.BondedTokens = pool.BondedTokens.Add(validator.Tokens) - case sdk.Unbonded, sdk.Unbonding: - pool.LooseTokens = pool.LooseTokens.Add(validator.Tokens) - default: - panic("improper use of RandomSetup") - } - - validators[i] = validator - } - - return pool, validators -} diff --git a/x/stake/types/validator_test.go b/x/stake/types/validator_test.go index 8d97cbce749e..5e025201585e 100644 --- a/x/stake/types/validator_test.go +++ b/x/stake/types/validator_test.go @@ -2,7 +2,6 @@ package types import ( "fmt" - "math/rand" "testing" sdk "github.com/cosmos/cosmos-sdk/types" @@ -234,67 +233,6 @@ func TestPossibleOverflow(t *testing.T) { msg, newValidator.DelegatorShareExRate()) } -// run random operations in a random order on a random single-validator state, assert invariants hold -func TestSingleValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(41)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := RandomSetup(r, 1) - require.Equal(t, 1, len(validatorsOrig)) - - // sanity check - AssertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig) - - for j := 0; j < 5; j++ { - poolMod, validatorMod, _, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[0]) - - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - require.Equal(t, 1, len(validatorsOrig), "j %v", j) - require.Equal(t, 1, len(validatorsMod), "j %v", j) - validatorsMod[0] = validatorMod - - AssertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod) - - poolOrig = poolMod - validatorsOrig = validatorsMod - } - } -} - -// run random operations in a random order on a random multi-validator state, assert invariants hold -func TestMultiValidatorIntegrationInvariants(t *testing.T) { - r := rand.New(rand.NewSource(42)) - - for i := 0; i < 10; i++ { - poolOrig, validatorsOrig := RandomSetup(r, 100) - - AssertInvariants(t, "no operation", - poolOrig, validatorsOrig, - poolOrig, validatorsOrig) - - for j := 0; j < 5; j++ { - index := int(r.Int31n(int32(len(validatorsOrig)))) - poolMod, validatorMod, _, msg := RandomOperation(r)(r, poolOrig, validatorsOrig[index]) - validatorsMod := make([]Validator, len(validatorsOrig)) - copy(validatorsMod[:], validatorsOrig[:]) - validatorsMod[index] = validatorMod - - AssertInvariants(t, msg, - poolOrig, validatorsOrig, - poolMod, validatorsMod) - - poolOrig = poolMod - validatorsOrig = validatorsMod - - } - } -} - func TestHumanReadableString(t *testing.T) { validator := NewValidator(addr1, pk1, Description{}) From 16d4e0773d16aad388b0191ca3597f62102a83f8 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Wed, 18 Jul 2018 10:04:53 +0200 Subject: [PATCH 32/34] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef203bbd07f8..63a539f9b3e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ BREAKING CHANGES FEATURES * [lcd] Can now query governance proposals by ProposalStatus +* [x/mock/simulation] Randomized simulation framework + * Modules specify invariants and operations, preferably in an x/[module]/simulation package + * Modules can test random combinations of their own operations + * Applications can integrate operations and invariants from modules together for an integrated simulation IMPROVEMENTS * [baseapp] Allow any alphanumeric character in route From 1e5a7993ed6cb73c4b903f9c010d711b47f76611 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 19 Jul 2018 08:40:46 +0200 Subject: [PATCH 33/34] Environment variables => flags --- Makefile | 10 +++---- cmd/gaia/app/sim_test.go | 65 +++++++++++----------------------------- 2 files changed, 23 insertions(+), 52 deletions(-) diff --git a/Makefile b/Makefile index 9cba9cb61ad3..6459f08e8974 100644 --- a/Makefile +++ b/Makefile @@ -132,11 +132,11 @@ test_sim: @echo "Running individual module simulations." @go test $(PACKAGES_SIMTEST) -v @echo "Running full Gaia simulation. This may take several minutes." - @echo "Set the environment variable 'GAIA_SIMULATION_SEED' to run with a constant seed." - @echo "Set the environment variable 'GAIA_SIMULATION_KEYS' to run with the specified number of keys." - @echo "Set the environment variable 'GAIA_SIMULATION_BLOCKS' to run with the specified number of blocks." - @echo "Set the environment variable 'GAIA_SIMULATION_BLOCK_SIZE' to run with the specified block size (operations per block)." - @GAIA_SIMULATION_ENABLED=1 go test ./cmd/gaia/app -run TestFullGaiaSimulation -v + @echo "Pass the flag 'SimulationSeed' to run with a constant seed." + @echo "Pass the flag 'SimulationNumKeys' to run with the specified number of keys." + @echo "Pass the flag 'SimulationNumBlocks' to run with the specified number of blocks." + @echo "Pass the flag 'SimulationBlockSize' to run with the specified block size (operations per block)." + @go test ./cmd/gaia/app -run TestFullGaiaSimulation -SimulationEnabled=true -SimulationBlockSize=200 -v test_cover: @bash tests/test_cover.sh diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 89b1b54aa92a..f0bea1e177fc 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -2,11 +2,9 @@ package app import ( "encoding/json" + "flag" "math/rand" - "os" - "strconv" "testing" - "time" "github.com/stretchr/testify/require" @@ -20,18 +18,22 @@ import ( stakesim "github.com/cosmos/cosmos-sdk/x/stake/simulation" ) -const ( - defaultNumKeys = 10 - defaultNumBlocks = 100 - defaultBlockSize = 100 - - simulationEnvEnable = "GAIA_SIMULATION_ENABLED" - simulationEnvSeed = "GAIA_SIMULATION_SEED" - simulationEnvKeys = "GAIA_SIMULATION_KEYS" - simulationEnvBlocks = "GAIA_SIMULATION_BLOCKS" - simulationEnvBlockSize = "GAIA_SIMULATION_BLOCK_SIZE" +var ( + seed int64 + numKeys int + numBlocks int + blockSize int + enabled bool ) +func init() { + flag.Int64Var(&seed, "SimulationSeed", 42, "Simulation random seed") + flag.IntVar(&numKeys, "SimulationNumKeys", 10, "Number of keys (accounts)") + flag.IntVar(&numBlocks, "SimulationNumBlocks", 100, "Number of blocks") + flag.IntVar(&blockSize, "SimulationBlockSize", 100, "Operations per block") + flag.BoolVar(&enabled, "SimulationEnabled", false, "Enable the simulation") +} + func appStateFn(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { var genesisAccounts []GenesisAccount @@ -62,7 +64,7 @@ func appStateFn(r *rand.Rand, accs []sdk.AccAddress) json.RawMessage { } func TestFullGaiaSimulation(t *testing.T) { - if os.Getenv(simulationEnvEnable) == "" { + if !enabled { t.Skip("Skipping Gaia simulation") } @@ -72,37 +74,6 @@ func TestFullGaiaSimulation(t *testing.T) { app := NewGaiaApp(logger, db, nil) require.Equal(t, "GaiaApp", app.Name()) - var seed int64 - var err error - envSeed := os.Getenv(simulationEnvSeed) - if envSeed != "" { - seed, err = strconv.ParseInt(envSeed, 10, 64) - require.Nil(t, err) - } else { - seed = time.Now().UnixNano() - } - - keys := defaultNumKeys - envKeys := os.Getenv(simulationEnvKeys) - if envKeys != "" { - keys, err = strconv.Atoi(envKeys) - require.Nil(t, err) - } - - blocks := defaultNumBlocks - envBlocks := os.Getenv(simulationEnvBlocks) - if envBlocks != "" { - blocks, err = strconv.Atoi(envBlocks) - require.Nil(t, err) - } - - blockSize := defaultBlockSize - envBlockSize := os.Getenv(simulationEnvBlockSize) - if envBlockSize != "" { - blockSize, err = strconv.Atoi(envBlockSize) - require.Nil(t, err) - } - // Run randomized simulation simulation.SimulateFromSeed( t, app.BaseApp, appStateFn, seed, @@ -121,8 +92,8 @@ func TestFullGaiaSimulation(t *testing.T) { banksim.NonnegativeBalanceInvariant(app.accountMapper), stakesim.AllInvariants(app.coinKeeper, app.stakeKeeper, app.accountMapper), }, - keys, - blocks, + numKeys, + numBlocks, blockSize, ) From ee29e10068e6bdf19c9b6e7cf9001794574a7954 Mon Sep 17 00:00:00 2001 From: Christopher Goes Date: Thu, 19 Jul 2018 08:47:54 +0200 Subject: [PATCH 34/34] RandomKey, RandomAmount --- x/bank/simulation/msgs.go | 6 +++--- x/mock/simulation/util.go | 16 ++++++++++++++++ x/stake/simulation/msgs.go | 38 +++++++++++++++++++------------------- 3 files changed, 38 insertions(+), 22 deletions(-) diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index 553d000db475..3a72488757ed 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -22,15 +22,15 @@ import ( // accounts already exist. func TestAndRunSingleInputMsgSend(mapper auth.AccountMapper) simulation.TestAndRunTx { return func(t *testing.T, r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, keys []crypto.PrivKey, log string, event func(string)) (action string, err sdk.Error) { - fromKey := keys[r.Intn(len(keys))] + fromKey := simulation.RandomKey(r, keys) fromAddr := sdk.AccAddress(fromKey.PubKey().Address()) - toKey := keys[r.Intn(len(keys))] + toKey := simulation.RandomKey(r, keys) // Disallow sending money to yourself for { if !fromKey.Equals(toKey) { break } - toKey = keys[r.Intn(len(keys))] + toKey = simulation.RandomKey(r, keys) } toAddr := sdk.AccAddress(toKey.PubKey().Address()) initFromCoins := mapper.GetAccount(ctx, fromAddr).GetCoins() diff --git a/x/mock/simulation/util.go b/x/mock/simulation/util.go index 8fdf5021e0ab..14227a1aef70 100644 --- a/x/mock/simulation/util.go +++ b/x/mock/simulation/util.go @@ -3,6 +3,10 @@ package simulation import ( "fmt" "math/rand" + + crypto "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" ) // shamelessly copied from https://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang#31832326 @@ -38,3 +42,15 @@ func DisplayEvents(events map[string]uint) { // TODO fmt.Printf("Events: %v\n", events) } + +// Pick a random key from an array +func RandomKey(r *rand.Rand, keys []crypto.PrivKey) crypto.PrivKey { + return keys[r.Intn( + len(keys), + )] +} + +// Generate a random amount +func RandomAmount(r *rand.Rand, max sdk.Int) sdk.Int { + return sdk.NewInt(int64(r.Intn(int(max.Int64())))) +} diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 41bda5dd6f72..87324eed720e 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -24,12 +24,12 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), } - key := keys[r.Intn(len(keys))] + key := simulation.RandomKey(r, keys) pubkey := key.PubKey() address := sdk.AccAddress(pubkey.Address()) amount := m.GetAccount(ctx, address).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + amount = simulation.RandomAmount(r, amount) } if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil @@ -63,7 +63,7 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { Website: simulation.RandStringOfLength(r, 10), Details: simulation.RandStringOfLength(r, 10), } - key := keys[r.Intn(len(keys))] + key := simulation.RandomKey(r, keys) pubkey := key.PubKey() address := sdk.AccAddress(pubkey.Address()) msg := stake.MsgEditValidator{ @@ -86,13 +86,13 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.TestAndRunTx { 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, event func(string)) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom - validatorKey := keys[r.Intn(len(keys))] + validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) - delegatorKey := keys[r.Intn(len(keys))] + delegatorKey := simulation.RandomKey(r, keys) delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + amount = simulation.RandomAmount(r, amount) } if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil @@ -118,13 +118,13 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.TestAn 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, event func(string)) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom - validatorKey := keys[r.Intn(len(keys))] + validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) - delegatorKey := keys[r.Intn(len(keys))] + delegatorKey := simulation.RandomKey(r, keys) delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + amount = simulation.RandomAmount(r, amount) } if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil @@ -149,9 +149,9 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgCompleteUnbonding 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, event func(string)) (action string, err sdk.Error) { - validatorKey := keys[r.Intn(len(keys))] + validatorKey := simulation.RandomKey(r, keys) validatorAddress := sdk.AccAddress(validatorKey.PubKey().Address()) - delegatorKey := keys[r.Intn(len(keys))] + delegatorKey := simulation.RandomKey(r, keys) delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) msg := stake.MsgCompleteUnbonding{ DelegatorAddr: delegatorAddress, @@ -173,16 +173,16 @@ func SimulateMsgCompleteUnbonding(k stake.Keeper) simulation.TestAndRunTx { 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, event func(string)) (action string, err sdk.Error) { denom := k.GetParams(ctx).BondDenom - sourceValidatorKey := keys[r.Intn(len(keys))] + sourceValidatorKey := simulation.RandomKey(r, keys) sourceValidatorAddress := sdk.AccAddress(sourceValidatorKey.PubKey().Address()) - destValidatorKey := keys[r.Intn(len(keys))] + destValidatorKey := simulation.RandomKey(r, keys) destValidatorAddress := sdk.AccAddress(destValidatorKey.PubKey().Address()) - delegatorKey := keys[r.Intn(len(keys))] + delegatorKey := simulation.RandomKey(r, keys) delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) // TODO amount := m.GetAccount(ctx, delegatorAddress).GetCoins().AmountOf(denom) if amount.GT(sdk.ZeroInt()) { - amount = sdk.NewInt(int64(r.Intn(int(amount.Int64())))) + amount = simulation.RandomAmount(r, amount) } if amount.Equal(sdk.ZeroInt()) { return "no-operation", nil @@ -208,11 +208,11 @@ func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgCompleteRedelegate 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, event func(string)) (action string, err sdk.Error) { - validatorSrcKey := keys[r.Intn(len(keys))] + validatorSrcKey := simulation.RandomKey(r, keys) validatorSrcAddress := sdk.AccAddress(validatorSrcKey.PubKey().Address()) - validatorDstKey := keys[r.Intn(len(keys))] + validatorDstKey := simulation.RandomKey(r, keys) validatorDstAddress := sdk.AccAddress(validatorDstKey.PubKey().Address()) - delegatorKey := keys[r.Intn(len(keys))] + delegatorKey := simulation.RandomKey(r, keys) delegatorAddress := sdk.AccAddress(delegatorKey.PubKey().Address()) msg := stake.MsgCompleteRedelegate{ DelegatorAddr: delegatorAddress, @@ -240,7 +240,7 @@ func Setup(mapp *mock.App, k stake.Keeper) simulation.RandSetup { denom := params.BondDenom loose := sdk.ZeroInt() mapp.AccountMapper.IterateAccounts(ctx, func(acc auth.Account) bool { - balance := sdk.NewInt(int64(r.Intn(1000000))) + balance := simulation.RandomAmount(r, sdk.NewInt(1000000)) acc.SetCoins(acc.GetCoins().Plus(sdk.Coins{sdk.NewIntCoin(denom, balance)})) mapp.AccountMapper.SetAccount(ctx, acc) loose = loose.Add(balance)