cosmos-sdk/x/stake/simulation/sim_test.go

360 lines
13 KiB
Go
Raw Normal View History

2018-07-11 10:43:25 -07:00
package simulation
2018-07-10 11:46:28 -07:00
import (
"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"
2018-07-11 10:43:25 -07:00
"github.com/cosmos/cosmos-sdk/x/stake"
2018-07-10 11:46:28 -07:00
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
)
2018-07-10 19:36:12 -07:00
var (
stats = make(map[string]int)
)
2018-07-10 11:46:28 -07:00
// ModuleInvariants runs all invariants of the stake module.
// Currently: total supply, positive power
2018-07-11 10:43:25 -07:00
func ModuleInvariants(ck bank.Keeper, k stake.Keeper) mock.Invariant {
2018-07-10 11:46:28 -07:00
return func(t *testing.T, app *mock.App, log string) {
2018-07-10 17:36:50 -07:00
SupplyInvariants(ck, k)(t, app, log)
2018-07-10 11:46:28 -07:00
PositivePowerInvariant(k)(t, app, log)
2018-07-10 17:36:50 -07:00
ValidatorSetInvariant(k)(t, app, log)
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// SupplyInvariants checks that the total supply reflects all held loose tokens, bonded tokens, and unbonding delegations
2018-07-11 10:43:25 -07:00
func SupplyInvariants(ck bank.Keeper, k stake.Keeper) mock.Invariant {
2018-07-10 11:46:28 -07:00
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
})
2018-07-16 17:41:36 -07:00
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",
2018-07-10 19:36:12 -07:00
pool.LooseTokens, loose, log)
stats["stake/invariant/looseTokens"] += 1
2018-07-10 11:46:28 -07:00
// Bonded tokens should equal sum of tokens with bonded validators
2018-07-10 17:36:50 -07:00
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
})
2018-07-16 17:41:36 -07:00
require.True(t, pool.BondedTokens.Equal(bonded), "expected bonded tokens to equal total steak held by bonded validators\nlog: %s", log)
2018-07-10 19:36:12 -07:00
stats["stake/invariant/bondedTokens"] += 1
2018-07-10 17:36:50 -07:00
// TODO Inflation check on total supply
2018-07-10 11:46:28 -07:00
}
}
// PositivePowerInvariant checks that all stored validators have > 0 power
2018-07-11 10:43:25 -07:00
func PositivePowerInvariant(k stake.Keeper) mock.Invariant {
2018-07-10 11:46:28 -07:00
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
})
2018-07-10 19:36:12 -07:00
stats["stake/invariant/positivePower"] += 1
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// ValidatorSetInvariant checks equivalence of Tendermint validator set and SDK validator set
2018-07-11 10:43:25 -07:00
func ValidatorSetInvariant(k stake.Keeper) mock.Invariant {
2018-07-10 17:36:50 -07:00
return func(t *testing.T, app *mock.App, log string) {
// TODO
}
}
// SimulateMsgCreateValidator
2018-07-11 10:43:25 -07:00
func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg {
2018-07-10 17:36:50 -07:00
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
2018-07-11 10:43:25 -07:00
description := stake.Description{
2018-07-10 17:36:50 -07:00
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()))))
}
2018-07-10 19:36:12 -07:00
if amount.Equal(sdk.ZeroInt()) {
return "nop", nil
}
2018-07-11 10:43:25 -07:00
msg := stake.MsgCreateValidator{
2018-07-11 10:17:09 -07:00
Description: description,
ValidatorAddr: address,
DelegatorAddr: address,
PubKey: pubkey,
Delegation: sdk.NewIntCoin(denom, amount),
2018-07-10 17:36:50 -07:00
}
2018-07-10 19:36:12 -07:00
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
2018-07-10 19:55:57 -07:00
ctx, write := ctx.CacheContext()
2018-07-11 10:43:25 -07:00
result := stake.NewHandler(k)(ctx, msg)
2018-07-10 19:55:57 -07:00
if result.IsOK() {
write()
}
2018-07-10 19:36:12 -07:00
stats[fmt.Sprintf("stake/createvalidator/%v", result.IsOK())] += 1
// require.True(t, result.IsOK(), "expected OK result but instead got %v", result)
2018-07-10 17:36:50 -07:00
action = fmt.Sprintf("TestMsgCreateValidator: %s", msg.GetSignBytes())
return action, nil
}
}
// SimulateMsgEditValidator
2018-07-11 10:43:25 -07:00
func SimulateMsgEditValidator(k stake.Keeper) mock.TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
2018-07-11 10:43:25 -07:00
description := stake.Description{
2018-07-10 17:36:50 -07:00
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())
2018-07-11 10:43:25 -07:00
msg := stake.MsgEditValidator{
2018-07-10 17:36:50 -07:00
Description: description,
ValidatorAddr: address,
}
2018-07-10 19:36:12 -07:00
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
2018-07-10 19:55:57 -07:00
ctx, write := ctx.CacheContext()
2018-07-11 10:43:25 -07:00
result := stake.NewHandler(k)(ctx, msg)
2018-07-10 19:55:57 -07:00
if result.IsOK() {
write()
}
2018-07-10 19:36:12 -07:00
stats[fmt.Sprintf("stake/editvalidator/%v", result.IsOK())] += 1
2018-07-10 17:36:50 -07:00
action = fmt.Sprintf("TestMsgEditValidator: %s", msg.GetSignBytes())
return action, nil
}
}
2018-07-10 11:46:28 -07:00
// SimulateMsgDelegate
2018-07-11 10:43:25 -07:00
func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg {
2018-07-10 11:46:28 -07:00
return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
2018-07-10 19:36:12 -07:00
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
}
2018-07-11 10:43:25 -07:00
msg := stake.MsgDelegate{
2018-07-10 19:36:12 -07:00
DelegatorAddr: delegatorAddress,
ValidatorAddr: validatorAddress,
2018-07-11 10:17:09 -07:00
Delegation: sdk.NewIntCoin(denom, amount),
2018-07-10 19:36:12 -07:00
}
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
2018-07-10 19:55:57 -07:00
ctx, write := ctx.CacheContext()
2018-07-11 10:43:25 -07:00
result := stake.NewHandler(k)(ctx, msg)
2018-07-10 19:55:57 -07:00
if result.IsOK() {
write()
}
2018-07-10 19:36:12 -07:00
stats[fmt.Sprintf("stake/delegate/%v", result.IsOK())] += 1
action = fmt.Sprintf("TestMsgDelegate: %s", msg.GetSignBytes())
return action, nil
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// SimulateMsgBeginUnbonding
2018-07-11 10:43:25 -07:00
func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
2018-07-11 10:51:04 -07:00
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
2018-07-10 17:36:50 -07:00
}
}
// SimulateMsgCompleteUnbonding
2018-07-11 10:43:25 -07:00
func SimulateMsgCompleteUnbonding(k stake.Keeper) mock.TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
2018-07-11 13:44:21 -07:00
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
2018-07-10 17:36:50 -07:00
}
}
// SimulateMsgBeginRedelegate
2018-07-11 10:43:25 -07:00
func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) mock.TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
2018-07-10 19:36:12 -07:00
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
}
2018-07-11 10:43:25 -07:00
msg := stake.MsgBeginRedelegate{
2018-07-10 19:36:12 -07:00
DelegatorAddr: delegatorAddress,
ValidatorSrcAddr: sourceValidatorAddress,
ValidatorDstAddr: destValidatorAddress,
SharesAmount: sdk.NewRatFromInt(amount),
}
require.Nil(t, msg.ValidateBasic(), "expected msg to pass ValidateBasic: %s", msg.GetSignBytes())
2018-07-10 19:55:57 -07:00
ctx, write := ctx.CacheContext()
2018-07-11 10:43:25 -07:00
result := stake.NewHandler(k)(ctx, msg)
2018-07-10 19:55:57 -07:00
if result.IsOK() {
write()
}
2018-07-10 19:36:12 -07:00
stats[fmt.Sprintf("stake/beginredelegate/%v", result.IsOK())] += 1
action = fmt.Sprintf("TestMsgBeginRedelegate: %s", msg.GetSignBytes())
return action, nil
2018-07-10 17:36:50 -07:00
}
}
// SimulateMsgCompleteRedelegate
2018-07-11 10:43:25 -07:00
func SimulateMsgCompleteRedelegate(k stake.Keeper) mock.TestAndRunMsg {
2018-07-10 17:36:50 -07:00
return func(t *testing.T, r *rand.Rand, ctx sdk.Context, keys []crypto.PrivKey, log string) (action string, err sdk.Error) {
2018-07-11 13:44:21 -07:00
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
2018-07-10 17:36:50 -07:00
}
}
2018-07-10 11:46:28 -07:00
// SimulationSetup
2018-07-11 10:43:25 -07:00
func SimulationSetup(mapp *mock.App, k stake.Keeper) mock.RandSetup {
2018-07-10 11:46:28 -07:00
return func(r *rand.Rand, privKeys []crypto.PrivKey) {
ctx := mapp.NewContext(false, abci.Header{})
2018-07-11 10:43:25 -07:00
stake.InitGenesis(ctx, k, stake.DefaultGenesisState())
2018-07-10 19:36:12 -07:00
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)
2018-07-16 17:41:36 -07:00
pool.LooseTokens = pool.LooseTokens.Add(sdk.NewRat(loose.Int64(), 1))
2018-07-10 19:36:12 -07:00
k.SetPool(ctx, pool)
2018-07-10 11:46:28 -07:00
}
}
2018-07-10 17:36:50 -07:00
// TestStakeWithRandomMessages
2018-07-10 11:46:28 -07:00
func TestStakeWithRandomMessages(t *testing.T) {
mapp := mock.NewApp()
bank.RegisterWire(mapp.Cdc)
2018-07-10 17:36:50 -07:00
mapper := mapp.AccountMapper
coinKeeper := bank.NewKeeper(mapper)
2018-07-10 11:46:28 -07:00
stakeKey := sdk.NewKVStoreKey("stake")
2018-07-11 10:43:25 -07:00
stakeKeeper := stake.NewKeeper(mapp.Cdc, stakeKey, coinKeeper, stake.DefaultCodespace)
mapp.Router().AddRoute("stake", stake.NewHandler(stakeKeeper))
2018-07-10 19:36:12 -07:00
mapp.SetEndBlocker(func(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock {
2018-07-11 10:43:25 -07:00
validatorUpdates := stake.EndBlocker(ctx, stakeKeeper)
2018-07-10 19:36:12 -07:00
return abci.ResponseEndBlock{
ValidatorUpdates: validatorUpdates,
}
})
2018-07-10 11:46:28 -07:00
err := mapp.CompleteSetup([]*sdk.KVStoreKey{stakeKey})
if err != nil {
panic(err)
}
mapp.SimpleRandomizedTestingFromSeed(
t, 20, []mock.TestAndRunMsg{
2018-07-10 17:36:50 -07:00
SimulateMsgCreateValidator(mapper, stakeKeeper),
SimulateMsgEditValidator(stakeKeeper),
2018-07-10 19:36:12 -07:00
SimulateMsgDelegate(mapper, stakeKeeper),
2018-07-11 13:44:21 -07:00
// XXX TODO
// SimulateMsgBeginUnbonding(mapper, stakeKeeper),
2018-07-10 17:36:50 -07:00
SimulateMsgCompleteUnbonding(stakeKeeper),
2018-07-12 16:54:07 -07:00
SimulateMsgBeginRedelegate(mapper, stakeKeeper),
2018-07-10 17:36:50 -07:00
SimulateMsgCompleteRedelegate(stakeKeeper),
2018-07-10 11:46:28 -07:00
}, []mock.RandSetup{
SimulationSetup(mapp, stakeKeeper),
}, []mock.Invariant{
ModuleInvariants(coinKeeper, stakeKeeper),
}, 10, 100, 500,
)
2018-07-10 19:36:12 -07:00
fmt.Printf("Stats: %v\n", stats)
2018-07-10 11:46:28 -07:00
}