test: Add more sims test scenarios (#23278)

Co-authored-by: Alex | Interchain Labs <alex@skip.money>
This commit is contained in:
Alexander Peters 2025-01-13 10:50:55 +01:00 committed by GitHub
parent bc9ce394cb
commit 8cdae287f7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 319 additions and 172 deletions

View File

@ -2,9 +2,10 @@
#? test-sim-nondeterminism: Run non-determinism test for simapp
test-sim-nondeterminism:
# @echo "Running non-determinism test..."
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \
# -NumBlocks=100 -BlockSize=200 -Period=0
@echo "Running non-determinism test..."
@cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \
-NumBlocks=100 -BlockSize=200
# Requires an exported plugin. See store/streaming/README.md for documentation.
#
@ -18,51 +19,45 @@ test-sim-nondeterminism:
test-sim-nondeterminism-streaming:
# @echo "Running non-determinism-streaming test..."
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \
# -NumBlocks=100 -BlockSize=200 -Period=0 -EnableStreaming=true
# -NumBlocks=100 -BlockSize=200 -EnableStreaming=true
test-sim-custom-genesis-fast:
# @echo "Running custom genesis simulation..."
# @echo "By default, ${HOME}/.simapp/config/genesis.json will be used."
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \
# -NumBlocks=100 -BlockSize=200 -Seed=99 -Period=5 -SigverifyTx=false
# -NumBlocks=100 -BlockSize=200 -Seed=99 -SigverifyTx=false
test-sim-import-export:
# @echo "Running application import/export simulation. This may take several minutes..."
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 20m -tags='sims' -run TestAppImportExport \
# -NumBlocks=50 -Period=5
# @echo "Running application import/export simulation. This may take several minutes..."
# @cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout 20m -tags='sims' -run TestAppImportExport \
# -NumBlocks=50
test-sim-after-import:
# @echo "Running application simulation-after-import. This may take several minutes..."
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestAppSimulationAfterImport \
# -NumBlocks=50 -Period=5
@echo "Running application simulation-after-import. This may take several minutes..."
@cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestAppSimulationAfterImport \
-NumBlocks=50
test-sim-custom-genesis-multi-seed:
# @echo "Running multi-seed custom genesis simulation..."
# @echo "By default, ${HOME}/.simapp/config/genesis.json will be used."
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \
# -NumBlocks=400 -Period=5
# @cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \
# -NumBlocks=400
test-sim-multi-seed-long:
# @echo "Running long multi-seed application simulation. This may take awhile!"
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout=2h -tags='sims' -run TestFullAppSimulation \
# -NumBlocks=150 -Period=50
@echo "Running long multi-seed application simulation. This may take awhile!"
@cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout=2h -tags='sims' -run TestFullAppSimulation \
-NumBlocks=150
test-sim-multi-seed-short: test-v2-sim
# @echo "Running short multi-seed application simulation. This may take awhile!"
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation \
# -NumBlocks=50 -Period=10 -FauxMerkle=true
.Phony: test-v2-sim
test-v2-sim:
@echo "Running short multi-seed application simulation. This may take awhile!"
@cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestSimsAppV2 \
# -NumBlocks=50 -Period=10 -FauxMerkle=true
test-sim-multi-seed-short:
@echo "Running short multi-seed application simulation. This may take awhile!"
@cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation \
-NumBlocks=50
test-sim-benchmark-invariants:
# @echo "Running simulation invariant benchmarks..."
# cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -benchmem -bench=BenchmarkInvariants -tags='sims' -run=^$ \
# -Enabled=true -NumBlocks=1000 -BlockSize=200 \
# -Period=1 -Commit=true -Seed=57 -v -timeout 24h
# -Commit=true -Seed=57 -v -timeout 24h
.PHONY: \
test-sim-nondeterminism \
@ -81,20 +76,20 @@ SIM_COMMIT ?= true
#? test-sim-fuzz: Run fuzz test for simapp
test-sim-fuzz:
@echo "Running application fuzz for numBlocks=2, blockSize=20. This may take awhile!"
# @echo "Running application fuzz for numBlocks=2, blockSize=20. This may take awhile!"
#ld flags are a quick fix to make it work on current osx
@cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -json -tags='sims' -ldflags="-extldflags=-Wl,-ld_classic" -timeout=60m -fuzztime=60m -run=^$$ -fuzz=FuzzFullAppSimulation -GenesisTime=1714720615 -NumBlocks=2 -BlockSize=20
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -json -tags='sims' -ldflags="-extldflags=-Wl,-ld_classic" -timeout=60m -fuzztime=60m -run=^$$ -fuzz=FuzzFullAppSimulation -GenesisTime=1714720615 -NumBlocks=2 -BlockSize=20
#? test-sim-benchmark: Run benchmark test for simapp
test-sim-benchmark:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -tags='sims' -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \
# @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -tags='sims' -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h
test-sim-profile:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -tags='sims' -benchmem -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \
# @echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
# @cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -tags='sims' -benchmem -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
.PHONY: test-sim-profile test-sim-benchmark test-sim-fuzz

View File

@ -27,7 +27,6 @@ import (
"cosmossdk.io/log"
"cosmossdk.io/runtime/v2"
"cosmossdk.io/server/v2/appmanager"
cometbfttypes "cosmossdk.io/server/v2/cometbft/types"
storev2 "cosmossdk.io/store/v2"
consensustypes "cosmossdk.io/x/consensus/types"
@ -51,8 +50,8 @@ type (
const SimAppChainID = "simulation-app"
// this list of seeds was imported from the original simulation runner: https://github.com/cosmos/tools/blob/v1.0.0/cmd/runsim/main.go#L32
var defaultSeeds = []int64{
// DefaultSeeds list of seeds was imported from the original simulation runner: https://github.com/cosmos/tools/blob/v1.0.0/cmd/runsim/main.go#L32
var DefaultSeeds = []int64{
1, 2, 4, 7,
32, 123, 124, 582, 1893, 2989,
3012, 4728, 37827, 981928, 87821, 891823782,
@ -89,6 +88,14 @@ type (
// SimulationApp abstract blockchain app
SimulationApp[T Tx] interface {
appmanager.TransactionFuzzer[T]
InitGenesis(
ctx context.Context,
blockRequest *server.BlockRequest[T],
initGenesisJSON []byte,
txDecoder transaction.Codec[T],
) (*server.BlockResponse, store.WriterMap, error)
GetApp() *runtime.App[T]
TxConfig() client.TxConfig
AppCodec() codec.Codec
@ -99,6 +106,7 @@ type (
// TestInstance system under test
TestInstance[T Tx] struct {
Seed int64
App SimulationApp[T]
TxDecoder transaction.Codec[T]
BankKeeper BankKeeper
@ -110,18 +118,24 @@ type (
}
AppFactory[T Tx, V SimulationApp[T]] func(config depinject.Config, outputs ...any) (V, error)
AppConfigFactory func() depinject.Config
)
// SetupTestInstance initializes and returns the system under test.
func SetupTestInstance[T Tx, V SimulationApp[T]](t *testing.T, factory AppFactory[T, V], appConfig depinject.Config) TestInstance[T] {
t.Helper()
func SetupTestInstance[T Tx, V SimulationApp[T]](
tb testing.TB,
appFactory AppFactory[T, V],
appConfigFactory AppConfigFactory,
seed int64,
) TestInstance[T] {
tb.Helper()
vp := viper.New()
vp.Set("store.app-db-backend", "memdb")
vp.Set("home", t.TempDir())
vp.Set("home", tb.TempDir())
depInjCfg := depinject.Configs(
depinject.Supply(log.NewNopLogger(), runtime.GlobalConfig(vp.AllSettings())),
appConfig,
appConfigFactory(),
)
var (
bankKeeper BankKeeper
@ -134,11 +148,12 @@ func SetupTestInstance[T Tx, V SimulationApp[T]](t *testing.T, factory AppFactor
&bankKeeper,
&stKeeper,
)
require.NoError(t, err)
require.NoError(tb, err)
xapp, err := factory(depinject.Configs(depinject.Supply(log.NewNopLogger(), runtime.GlobalConfig(vp.AllSettings()))))
require.NoError(t, err)
xapp, err := appFactory(depinject.Configs(depinject.Supply(log.NewNopLogger(), runtime.GlobalConfig(vp.AllSettings()))))
require.NoError(tb, err)
return TestInstance[T]{
Seed: seed,
App: xapp,
BankKeeper: bankKeeper,
AuthKeeper: authKeeper,
@ -150,80 +165,129 @@ func SetupTestInstance[T Tx, V SimulationApp[T]](t *testing.T, factory AppFactor
}
}
// InitializeChain sets up the blockchain with an initial state, validator set, and history using the provided genesis data.
func (ti TestInstance[T]) InitializeChain(
tb testing.TB,
ctx context.Context,
chainID string,
genesisTimestamp time.Time,
initialHeight uint64,
genesisAppState json.RawMessage,
) ChainState[T] {
tb.Helper()
initRsp, stateRoot := doChainInitWithGenesis(
tb,
ctx,
chainID,
genesisTimestamp,
initialHeight,
genesisAppState,
ti,
)
activeValidatorSet := simsxv2.NewValSet().Update(initRsp.ValidatorUpdates)
valsetHistory := simsxv2.NewValSetHistory(initialHeight)
valsetHistory.Add(genesisTimestamp, activeValidatorSet)
return ChainState[T]{
ChainID: chainID,
BlockTime: genesisTimestamp,
BlockHeight: initialHeight,
ActiveValidatorSet: activeValidatorSet,
ValsetHistory: valsetHistory,
AppHash: stateRoot,
}
}
// RunWithSeeds runs a series of subtests using the default set of random seeds for deterministic simulation testing.
func RunWithSeeds[T Tx](
func RunWithSeeds[T Tx, V SimulationApp[T]](
t *testing.T,
appFactory AppFactory[T, V],
appConfigFactory AppConfigFactory,
seeds []int64,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
postRunActions ...func(t testing.TB, cs ChainState[T], app TestInstance[T], accs []simtypes.Account),
) {
t.Helper()
cfg := cli.NewConfigFromFlags()
cfg.ChainID = SimAppChainID
for i := range seeds {
seed := seeds[i]
for _, seed := range seeds {
t.Run(fmt.Sprintf("seed: %d", seed), func(t *testing.T) {
t.Parallel()
RunWithSeed(t, NewSimApp[T], AppConfig(), cfg, seed, postRunActions...)
RunWithSeed(t, appFactory, appConfigFactory, cfg, seed, postRunActions...)
})
}
}
// RunWithSeed initializes and executes a simulation run with the given seed, generating blocks and transactions.
func RunWithSeed[T Tx, V SimulationApp[T]](
t *testing.T,
tb testing.TB,
appFactory AppFactory[T, V],
appConfig depinject.Config,
appConfigFactory AppConfigFactory,
tCfg simtypes.Config,
seed int64,
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
postRunActions ...func(t testing.TB, cs ChainState[T], app TestInstance[T], accs []simtypes.Account),
) {
t.Helper()
r := rand.New(rand.NewSource(seed))
testInstance := SetupTestInstance[T, V](t, appFactory, appConfig)
accounts, genesisAppState, chainID, genesisTimestamp := prepareInitialGenesisState(testInstance.App, r, testInstance.BankKeeper, tCfg, testInstance.ModuleManager)
tb.Helper()
initialBlockHeight := tCfg.InitialBlockHeight
require.NotEmpty(tb, initialBlockHeight, "initial block height must not be 0")
appManager := testInstance.AppManager
appStore := testInstance.App.Store()
txConfig := testInstance.App.TxConfig()
setupFn := func(ctx context.Context, r *rand.Rand) (TestInstance[T], ChainState[T], []simtypes.Account) {
testInstance := SetupTestInstance[T, V](tb, appFactory, appConfigFactory, seed)
accounts, genesisAppState, chainID, genesisTimestamp := prepareInitialGenesisState(
testInstance.App,
r,
testInstance.BankKeeper,
tCfg,
testInstance.ModuleManager,
)
cs := testInstance.InitializeChain(
tb,
ctx,
chainID,
genesisTimestamp,
initialBlockHeight,
genesisAppState,
)
return testInstance, cs, accounts
}
RunWithSeedX(tb, tCfg, setupFn, seed, postRunActions...)
}
// RunWithSeedX entrypoint for custom chain setups.
// The function runs the full simulation test circle for the specified seed and setup function, followed by optional post-run actions.
func RunWithSeedX[T Tx](
tb testing.TB,
tCfg simtypes.Config,
setupChainStateFn func(ctx context.Context, r *rand.Rand) (TestInstance[T], ChainState[T], []simtypes.Account),
seed int64,
postRunActions ...func(t testing.TB, cs ChainState[T], app TestInstance[T], accs []simtypes.Account),
) {
tb.Helper()
r := rand.New(rand.NewSource(seed))
rootCtx, done := context.WithCancel(context.Background())
defer done()
initRsp, stateRoot := doChainInitWithGenesis(t, rootCtx, chainID, genesisTimestamp, appManager, testInstance.TxDecoder, genesisAppState, appStore)
activeValidatorSet := simsxv2.NewValSet().Update(initRsp.ValidatorUpdates)
valsetHistory := simsxv2.NewValSetHistory(1)
valsetHistory.Add(genesisTimestamp, activeValidatorSet)
testInstance, chainState, accounts := setupChainStateFn(rootCtx, r)
emptySimParams := make(map[string]json.RawMessage) // todo read sims params from disk as before
modules := testInstance.ModuleManager.Modules()
msgFactoriesFn := prepareSimsMsgFactories(r, modules, simsx.ParamWeightSource(emptySimParams))
cs := chainState[T]{
chainID: chainID,
blockTime: genesisTimestamp,
activeValidatorSet: activeValidatorSet,
valsetHistory: valsetHistory,
stateRoot: stateRoot,
app: appManager,
appStore: appStore,
txConfig: txConfig,
}
doMainLoop(
t,
tb,
rootCtx,
cs,
testInstance,
&chainState,
msgFactoriesFn,
r,
testInstance.AuthKeeper,
testInstance.BankKeeper,
tCfg,
accounts,
testInstance.TXBuilder,
testInstance.StakingKeeper,
)
require.NoError(t, testInstance.App.Close(), "closing app")
for _, step := range postRunActions {
step(t, testInstance, accounts)
step(tb, chainState, testInstance, accounts)
}
require.NoError(tb, testInstance.App.Close(), "closing app")
}
// prepareInitialGenesisState initializes the genesis state for simulation by generating accounts, app state, chain ID, and timestamp.
@ -257,18 +321,20 @@ func prepareInitialGenesisState[T Tx](
// doChainInitWithGenesis initializes the blockchain state with the provided genesis data and returns the initial block response and state root.
func doChainInitWithGenesis[T Tx](
t *testing.T,
tb testing.TB,
ctx context.Context,
chainID string,
genesisTimestamp time.Time,
app appmanager.AppManager[T],
txDecoder transaction.Codec[T],
initialHeight uint64,
genesisAppState json.RawMessage,
appStore cometbfttypes.Store,
testInstance TestInstance[T],
) (*server.BlockResponse, store.Hash) {
t.Helper()
tb.Helper()
app := testInstance.App
txDecoder := testInstance.TxDecoder
appStore := testInstance.App.Store()
genesisReq := &server.BlockRequest[T]{
Height: 0,
Height: initialHeight,
Time: genesisTimestamp,
Hash: make([]byte, 32),
ChainId: chainID,
@ -290,27 +356,25 @@ func doChainInitWithGenesis[T Tx](
}
genesisCtx := context.WithValue(ctx, corecontext.CometParamsInitInfoKey, initialConsensusParams)
initRsp, genesisStateChanges, err := app.InitGenesis(genesisCtx, genesisReq, genesisAppState, txDecoder)
require.NoError(t, err)
require.NoError(tb, err)
require.NoError(t, appStore.SetInitialVersion(0))
require.NoError(tb, appStore.SetInitialVersion(initialHeight-1))
changeSet, err := genesisStateChanges.GetStateChanges()
require.NoError(t, err)
require.NoError(tb, err)
stateRoot, err := appStore.Commit(&store.Changeset{Changes: changeSet})
require.NoError(t, err)
stateRoot, err := appStore.Commit(&store.Changeset{Changes: changeSet, Version: initialHeight - 1})
require.NoError(tb, err)
return initRsp, stateRoot
}
// chainState represents the state of a blockchain during a simulation run.
type chainState[T Tx] struct {
chainID string
blockTime time.Time
activeValidatorSet simsxv2.WeightedValidators
valsetHistory *simsxv2.ValSetHistory
stateRoot store.Hash
app appmanager.TransactionFuzzer[T]
appStore storev2.RootStore
txConfig client.TxConfig
// ChainState represents the state of a blockchain during a simulation run.
type ChainState[T Tx] struct {
ChainID string
BlockTime time.Time
BlockHeight uint64
ActiveValidatorSet simsxv2.WeightedValidators
ValsetHistory *simsxv2.ValSetHistory
AppHash store.Hash
}
// doMainLoop executes the main simulation loop after chain setup with genesis block.
@ -318,34 +382,23 @@ type chainState[T Tx] struct {
// and executed. Events like validators missing votes or double signing are included in this
// process. The runtime tracks the validator's state and history.
func doMainLoop[T Tx](
t *testing.T,
tb testing.TB,
rootCtx context.Context,
cs chainState[T],
testInstance TestInstance[T],
cs *ChainState[T],
nextMsgFactory func() simsx.SimMsgFactoryX,
r *rand.Rand,
authKeeper AuthKeeper,
bankKeeper simsx.BalanceSource,
tCfg simtypes.Config,
accounts []simtypes.Account,
txBuilder simsxv2.TXBuilder[T],
stakingKeeper StakingKeeper,
) {
t.Helper()
blockTime := cs.blockTime
activeValidatorSet := cs.activeValidatorSet
if len(activeValidatorSet) == 0 {
t.Fatal("no active validators in chain setup")
tb.Helper()
if len(cs.ActiveValidatorSet) == 0 {
tb.Fatal("no active validators in chain setup")
return
}
valsetHistory := cs.valsetHistory
stateRoot := cs.stateRoot
chainID := cs.chainID
app := cs.app
appStore := cs.appStore
const ( // todo: read from CLI instead
numBlocks = 100 // 500 default
maxTXPerBlock = 200 // 200 default
)
numBlocks := tCfg.NumBlocks
maxTXPerBlock := tCfg.BlockSize
var (
txSkippedCounter int
@ -354,39 +407,39 @@ func doMainLoop[T Tx](
rootReporter := simsx.NewBasicSimulationReporter()
futureOpsReg := simsxv2.NewFutureOpsRegistry()
for i := 0; i < numBlocks; i++ {
if len(activeValidatorSet) == 0 {
t.Skipf("run out of validators in block: %d\n", i+1)
for end := cs.BlockHeight + numBlocks; cs.BlockHeight < end; cs.BlockHeight++ {
if len(cs.ActiveValidatorSet) == 0 {
tb.Skipf("run out of validators in block: %d\n", cs.BlockHeight)
return
}
blockTime = blockTime.Add(minTimePerBlock)
blockTime = blockTime.Add(time.Duration(int64(r.Intn(int(timeRangePerBlock/time.Second)))) * time.Second)
valsetHistory.Add(blockTime, activeValidatorSet)
cs.BlockTime = cs.BlockTime.Add(minTimePerBlock).
Add(time.Duration(int64(r.Intn(int(timeRangePerBlock/time.Second)))) * time.Second)
cs.ValsetHistory.Add(cs.BlockTime, cs.ActiveValidatorSet)
blockReqN := &server.BlockRequest[T]{
Height: uint64(1 + i),
Time: blockTime,
Hash: stateRoot,
AppHash: stateRoot,
ChainId: chainID,
Height: cs.BlockHeight,
Time: cs.BlockTime,
Hash: cs.AppHash,
AppHash: cs.AppHash,
ChainId: cs.ChainID,
}
cometInfo := comet.Info{
ValidatorsHash: nil,
Evidence: valsetHistory.MissBehaviour(r),
ProposerAddress: activeValidatorSet[0].Address,
LastCommit: activeValidatorSet.NewCommitInfo(r),
Evidence: cs.ValsetHistory.MissBehaviour(r),
ProposerAddress: cs.ActiveValidatorSet[0].Address, // todo: pick random one
LastCommit: cs.ActiveValidatorSet.NewCommitInfo(r),
}
fOps, pos := futureOpsReg.PopScheduledFor(blockTime), 0
addressCodec := cs.txConfig.SigningContext().AddressCodec()
fOps, pos := futureOpsReg.PopScheduledFor(cs.BlockTime), 0
addressCodec := testInstance.App.TxConfig().SigningContext().AddressCodec()
simsCtx := context.WithValue(rootCtx, corecontext.CometInfoKey, cometInfo) // required for ContextAwareCometInfoService
resultHandlers := make([]simsx.SimDeliveryResultHandler, 0, maxTXPerBlock)
var txPerBlockCounter int
blockRsp, updates, err := app.DeliverSims(simsCtx, blockReqN, func(ctx context.Context) iter.Seq[T] {
blockRsp, updates, err := testInstance.App.DeliverSims(simsCtx, blockReqN, func(ctx context.Context) iter.Seq[T] {
return func(yield func(T) bool) {
unbondingTime, err := stakingKeeper.UnbondingTime(ctx)
require.NoError(t, err)
valsetHistory.SetMaxHistory(minBlocksInUnbondingPeriod(unbondingTime))
testData := simsx.NewChainDataSource(ctx, r, authKeeper, bankKeeper, addressCodec, accounts...)
unbondingTime, err := testInstance.StakingKeeper.UnbondingTime(ctx)
require.NoError(tb, err)
cs.ValsetHistory.SetMaxHistory(minBlocksInUnbondingPeriod(unbondingTime))
testData := simsx.NewChainDataSource(ctx, r, testInstance.AuthKeeper, testInstance.BankKeeper, addressCodec, accounts...)
for txPerBlockCounter < maxTXPerBlock {
txPerBlockCounter++
@ -407,36 +460,36 @@ func doMainLoop[T Tx](
signers, msg := mergedMsgFactory.Create()(ctx, testData, reporter)
if reporter.IsSkipped() {
txSkippedCounter++
require.NoError(t, reporter.Close())
require.NoError(tb, reporter.Close())
continue
}
resultHandlers = append(resultHandlers, mergedMsgFactory.DeliveryResultHandler())
reporter.Success(msg)
require.NoError(t, reporter.Close())
require.NoError(tb, reporter.Close())
tx, err := txBuilder.Build(ctx, authKeeper, signers, msg, r, chainID)
require.NoError(t, err)
tx, err := testInstance.TXBuilder.Build(ctx, testInstance.AuthKeeper, signers, msg, r, cs.ChainID)
require.NoError(tb, err)
if !yield(tx) {
return
}
}
}
})
require.NoError(t, err, "%d, %s", blockReqN.Height, blockReqN.Time)
require.NoError(tb, err, "%d, %s", blockReqN.Height, blockReqN.Time)
changeSet, err := updates.GetStateChanges()
require.NoError(t, err)
stateRoot, err = appStore.Commit(&store.Changeset{
require.NoError(tb, err)
cs.AppHash, err = testInstance.App.Store().Commit(&store.Changeset{
Version: blockReqN.Height,
Changes: changeSet,
})
require.NoError(t, err)
require.Equal(t, len(resultHandlers), len(blockRsp.TxResults), "txPerBlockCounter: %d, totalSkipped: %d", txPerBlockCounter, txSkippedCounter)
require.NoError(tb, err)
require.Equal(tb, len(resultHandlers), len(blockRsp.TxResults), "txPerBlockCounter: %d, totalSkipped: %d", txPerBlockCounter, txSkippedCounter)
for i, v := range blockRsp.TxResults {
require.NoError(t, resultHandlers[i](v.Error))
require.NoError(tb, resultHandlers[i](v.Error))
}
txTotalCounter += txPerBlockCounter
activeValidatorSet = activeValidatorSet.Update(blockRsp.ValidatorUpdates)
cs.ActiveValidatorSet = cs.ActiveValidatorSet.Update(blockRsp.ValidatorUpdates)
}
fmt.Println("+++ reporter:\n" + rootReporter.Summary().String())
fmt.Printf("Tx total: %d skipped: %d\n", txTotalCounter, txSkippedCounter)

View File

@ -1,7 +1,103 @@
//go:build sims
package simapp
import "testing"
import (
"bytes"
"context"
"math/rand"
"sync"
"testing"
"time"
func TestSimsAppV2(t *testing.T) {
RunWithSeeds[Tx](t, defaultSeeds)
"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)
}
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()
otherHashes, ok := appHashResults[ti.Seed]
if !ok {
appHashResults[ti.Seed] = cs.AppHash
return
}
if !bytes.Equal(otherHashes, cs.AppHash) {
tb.Fatalf("non-determinism in seed %d", ti.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.Seed)
newCs := testInstance.InitializeChain(
tb,
ctx,
chainID,
genesisTimestamp,
startHeight,
exported.AppState,
)
return testInstance, newCs, accs
}
// run sims with new app setup from exported genesis
RunWithSeedX[Tx](tb, cfg, importGenesisChainStateFactory, ti.Seed)
}
RunWithSeeds[Tx, *SimApp[Tx]](t, appFactory, AppConfig, DefaultSeeds, exportAndStartChainFromGenesisPostAction)
}

View File

@ -263,6 +263,9 @@ func NewChainDataSource(
codec address.Codec,
oldSimAcc ...simtypes.Account,
) *ChainDataSource {
if len(oldSimAcc) == 0 {
panic("empty accounts")
}
acc := make([]SimAccount, len(oldSimAcc))
index := make(map[string]int, len(oldSimAcc))
bank := contextAwareBalanceSource{ctx: ctx, bank: bk}

View File

@ -349,7 +349,6 @@ func NewSimulationAppInstance[T SimulationApp](
})
appOptions := make(simtestutil.AppOptionsMap)
appOptions[flags.FlagHome] = workDir
appOptions[flags.FlagInvCheckPeriod] = cli.FlagPeriodValue
opts := []func(*baseapp.BaseApp){baseapp.SetChainID(tCfg.ChainID)}
if tCfg.FauxMerkle {
opts = append(opts, FauxMerkleModeOpt)

View File

@ -16,15 +16,17 @@ type historicValSet struct {
}
type ValSetHistory struct {
maxElements int
blockOffset int
blockOffset uint64
vals []historicValSet
}
func NewValSetHistory(maxElements int) *ValSetHistory {
// NewValSetHistory constructor. The maximum of historic valsets must not exceed the block or time limit for
// valid evidence.
func NewValSetHistory(initialHeight uint64) *ValSetHistory {
return &ValSetHistory{
maxElements: maxElements,
blockOffset: 1, // start at height 1
vals: make([]historicValSet, 0, maxElements),
maxElements: 1,
blockOffset: initialHeight,
vals: make([]historicValSet, 0, 1),
}
}
@ -58,7 +60,7 @@ func (h *ValSetHistory) MissBehaviour(r *rand.Rand) []comet.Evidence {
evidence := comet.Evidence{
Type: comet.DuplicateVote,
Validator: comet.Validator{Address: badVal.Address, Power: badVal.Power},
Height: int64(h.blockOffset + n),
Height: int64(h.blockOffset) + int64(n),
Time: h.vals[n].blockTime,
TotalVotingPower: h.vals[n].vals.TotalPower(),
}
@ -68,11 +70,12 @@ func (h *ValSetHistory) MissBehaviour(r *rand.Rand) []comet.Evidence {
return []comet.Evidence{evidence}
}
// SetMaxHistory sets the maximum number of historical validator sets to retain. Reduces retained history if it exceeds the limit.
func (h *ValSetHistory) SetMaxHistory(v int) {
h.maxElements = v
if len(h.vals) > h.maxElements {
diff := len(h.vals) - h.maxElements
h.vals = h.vals[diff:]
h.blockOffset += diff
h.blockOffset += uint64(diff)
}
}

View File

@ -13,9 +13,9 @@ type Config struct {
ExportStatsPath string // custom file path to save the exported simulation statistics JSON
Seed int64 // simulation random seed
InitialBlockHeight int // initial block to start the simulation
InitialBlockHeight uint64 // initial block to start the simulation
GenesisTime int64 // genesis time to start the simulation
NumBlocks int // number of new blocks to simulate from the initial block height
NumBlocks uint64 // number of new blocks to simulate from the initial block height
BlockSize int // operations per block
ChainID string // chain-id used on the simulation

View File

@ -18,8 +18,8 @@ var (
FlagExportStatePathValue string
FlagExportStatsPathValue string
FlagSeedValue int64
FlagInitialBlockHeightValue int
FlagNumBlocksValue int
FlagInitialBlockHeightValue uint64
FlagNumBlocksValue uint64
FlagBlockSizeValue int
FlagLeanValue bool
FlagCommitValue bool
@ -27,7 +27,6 @@ var (
FlagEnabledValue bool
FlagVerboseValue bool
FlagPeriodValue uint
FlagGenesisTimeValue int64
FlagSigverifyTxValue bool
FlagFauxMerkle bool
@ -42,8 +41,8 @@ func GetSimulatorFlags() {
flag.IntVar(&FlagExportParamsHeightValue, "ExportParamsHeight", 0, "height to which export the randomly generated params")
flag.StringVar(&FlagExportStatePathValue, "ExportStatePath", "", "custom file path to save the exported app state JSON")
flag.Int64Var(&FlagSeedValue, "Seed", DefaultSeedValue, "simulation random seed")
flag.IntVar(&FlagInitialBlockHeightValue, "InitialBlockHeight", 1, "initial block to start the simulation")
flag.IntVar(&FlagNumBlocksValue, "NumBlocks", 500, "number of new blocks to simulate from the initial block height")
flag.Uint64Var(&FlagInitialBlockHeightValue, "InitialBlockHeight", 1, "initial block to start the simulation")
flag.Uint64Var(&FlagNumBlocksValue, "NumBlocks", 500, "number of new blocks to simulate from the initial block height")
flag.IntVar(&FlagBlockSizeValue, "BlockSize", 200, "operations per block")
flag.BoolVar(&FlagLeanValue, "Lean", false, "lean simulation log output")
flag.BoolVar(&FlagCommitValue, "Commit", true, "have the simulation commit")
@ -52,7 +51,6 @@ func GetSimulatorFlags() {
// simulation flags
flag.BoolVar(&FlagEnabledValue, "Enabled", false, "enable the simulation")
flag.BoolVar(&FlagVerboseValue, "Verbose", false, "verbose log output")
flag.UintVar(&FlagPeriodValue, "Period", 0, "run slow invariants only once every period assertions")
flag.Int64Var(&FlagGenesisTimeValue, "GenesisTime", time.Now().Unix(), "use current time as genesis UNIX time for default")
flag.BoolVar(&FlagSigverifyTxValue, "SigverifyTx", true, "whether to sigverify check for transaction ")
flag.BoolVar(&FlagFauxMerkle, "FauxMerkle", false, "use faux merkle instead of iavl")