Work-in-progress staking invariants

This commit is contained in:
Christopher Goes 2018-07-10 20:46:28 +02:00
parent 37640d6c00
commit 6a119f6934
4 changed files with 151 additions and 3 deletions

View File

@ -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.

View File

@ -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)

View File

@ -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)

View File

@ -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,
)
}