174 lines
5.3 KiB
Go
174 lines
5.3 KiB
Go
//go:build sims
|
|
|
|
package simapp
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
authzkeeper "cosmossdk.io/x/authz/keeper"
|
|
"cosmossdk.io/x/feegrant"
|
|
slashingtypes "cosmossdk.io/x/slashing/types"
|
|
stakingtypes "cosmossdk.io/x/staking/types"
|
|
"maps"
|
|
"math/rand"
|
|
"slices"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
|
|
genutil "github.com/cosmos/cosmos-sdk/x/genutil/v2"
|
|
simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli"
|
|
)
|
|
|
|
func init() {
|
|
simcli.GetSimulatorFlags()
|
|
}
|
|
|
|
func TestFullAppSimulation(t *testing.T) {
|
|
RunWithSeeds[Tx](t, NewSimApp[Tx], AppConfig, DefaultSeeds)
|
|
}
|
|
|
|
// Scenario:
|
|
//
|
|
// Run 3 times a fresh node with the same seed,
|
|
// then the app hash should always be the same after n blocks
|
|
func TestAppStateDeterminism(t *testing.T) {
|
|
var seeds []int64
|
|
if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue {
|
|
// override defaults with user data
|
|
seeds = []int64{s, s, s} // run same simulation 3 times
|
|
} else {
|
|
seeds = []int64{ // some random seeds, tripled to ensure same app-hash on all runs
|
|
1, 1, 1,
|
|
3, 3, 3,
|
|
5, 5, 5,
|
|
}
|
|
}
|
|
|
|
var mx sync.Mutex
|
|
appHashResults := make(map[int64][]byte)
|
|
captureAndCheckHash := func(tb testing.TB, cs ChainState[Tx], ti TestInstance[Tx], _ []simtypes.Account) {
|
|
tb.Helper()
|
|
mx.Lock()
|
|
defer mx.Unlock()
|
|
seed := ti.RandSource.GetSeed()
|
|
otherHashes, ok := appHashResults[seed]
|
|
if !ok {
|
|
appHashResults[seed] = cs.AppHash
|
|
return
|
|
}
|
|
if !bytes.Equal(otherHashes, cs.AppHash) {
|
|
tb.Fatalf("non-determinism in seed %d", seed)
|
|
}
|
|
}
|
|
// run simulations
|
|
RunWithSeeds(t, NewSimApp[Tx], AppConfig, seeds, captureAndCheckHash)
|
|
}
|
|
|
|
// ExportableApp defines an interface for exporting application state and validator set.
|
|
type ExportableApp interface {
|
|
ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs []string) (genutil.ExportedApp, error)
|
|
}
|
|
|
|
// Scenario:
|
|
//
|
|
// Start a fresh node and run n blocks, export state
|
|
// set up a new node instance, Init chain from exported genesis
|
|
// run new instance for n blocks
|
|
func TestAppSimulationAfterImport(t *testing.T) {
|
|
appFactory := NewSimApp[Tx]
|
|
cfg := simcli.NewConfigFromFlags()
|
|
cfg.ChainID = SimAppChainID
|
|
|
|
exportAndStartChainFromGenesisPostAction := func(tb testing.TB, cs ChainState[Tx], ti TestInstance[Tx], accs []simtypes.Account) {
|
|
tb.Helper()
|
|
tb.Log("exporting genesis...\n")
|
|
app, ok := ti.App.(ExportableApp)
|
|
require.True(tb, ok)
|
|
exported, err := app.ExportAppStateAndValidators(false, []string{})
|
|
require.NoError(tb, err)
|
|
|
|
genesisTimestamp := cs.BlockTime.Add(24 * time.Hour)
|
|
startHeight := uint64(exported.Height + 1)
|
|
chainID := SimAppChainID + "_2"
|
|
|
|
importGenesisChainStateFactory := func(ctx context.Context, r *rand.Rand) (TestInstance[Tx], ChainState[Tx], []simtypes.Account) {
|
|
testInstance := SetupTestInstance(tb, appFactory, AppConfig, ti.RandSource, cfg.DBBackend)
|
|
newCs := testInstance.InitializeChain(
|
|
tb,
|
|
ctx,
|
|
chainID,
|
|
genesisTimestamp,
|
|
startHeight,
|
|
exported.AppState,
|
|
)
|
|
return testInstance, newCs, accs
|
|
}
|
|
// run sims with new app setup from exported genesis
|
|
RunWithRandSourceX[Tx](tb, cfg, importGenesisChainStateFactory, ti.RandSource)
|
|
}
|
|
RunWithSeeds[Tx, *SimApp[Tx]](t, appFactory, AppConfig, DefaultSeeds, exportAndStartChainFromGenesisPostAction)
|
|
}
|
|
|
|
// Scenario:
|
|
//
|
|
// Start a fresh node and run n blocks, export state
|
|
// set up a new node instance, Init chain from exported genesis
|
|
// then the stored data should be the same
|
|
func TestAppImportExport(t *testing.T) {
|
|
appFactory := NewSimApp[Tx]
|
|
cfg := simcli.NewConfigFromFlags()
|
|
cfg.ChainID = SimAppChainID
|
|
|
|
exportAndStartChainFromGenesisPostAction := func(tb testing.TB, cs ChainState[Tx], ti TestInstance[Tx], accs []simtypes.Account) {
|
|
tb.Helper()
|
|
tb.Log("exporting genesis...\n")
|
|
app, ok := ti.App.(ExportableApp)
|
|
require.True(tb, ok)
|
|
exported, err := app.ExportAppStateAndValidators(false, []string{})
|
|
require.NoError(tb, err)
|
|
|
|
genesisTimestamp := cs.BlockTime
|
|
startHeight := uint64(exported.Height) + 1
|
|
chainID := SimAppChainID
|
|
tb.Log("importing genesis...\n")
|
|
|
|
newTestInstance := SetupTestInstance(tb, appFactory, AppConfig, ti.RandSource, cfg.DBBackend)
|
|
newTestInstance.InitializeChain(
|
|
tb,
|
|
context.Background(),
|
|
chainID,
|
|
genesisTimestamp,
|
|
startHeight,
|
|
exported.AppState,
|
|
)
|
|
t.Log("comparing stores...")
|
|
// skip certain prefixes
|
|
skipPrefixes := map[string][][]byte{
|
|
stakingtypes.StoreKey: {
|
|
stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey,
|
|
},
|
|
authzkeeper.StoreKey: {authzkeeper.GrantQueuePrefix},
|
|
feegrant.StoreKey: {feegrant.FeeAllowanceQueueKeyPrefix},
|
|
slashingtypes.StoreKey: {slashingtypes.ValidatorMissedBlockBitmapKeyPrefix},
|
|
}
|
|
type decodeable interface {
|
|
RegisterStoreDecoder(sdr simtypes.StoreDecoderRegistry)
|
|
}
|
|
storeDecoders := make(simtypes.StoreDecoderRegistry)
|
|
for _, m := range ti.ModuleManager.Modules() {
|
|
if v, ok := m.(decodeable); ok {
|
|
v.RegisterStoreDecoder(storeDecoders)
|
|
}
|
|
}
|
|
storeKeys := slices.Collect(maps.Values(ti.ModuleManager.StoreKeys()))
|
|
slices.Sort(storeKeys)
|
|
|
|
AssertEqualStores(tb, ti.App.Store(), newTestInstance.App.Store(), storeKeys, storeDecoders, skipPrefixes)
|
|
}
|
|
RunWithSeeds[Tx, *SimApp[Tx]](t, appFactory, AppConfig, DefaultSeeds, exportAndStartChainFromGenesisPostAction)
|
|
}
|