perf: Replace runsim with Go stdlib testing (#24045)

Co-authored-by: Alex | Interchain Labs <alex@interchainlabs.io>
This commit is contained in:
Alexander Peters 2025-03-21 16:48:00 +01:00 committed by GitHub
parent b7c35f2568
commit bc57ce3ba7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 1041 additions and 788 deletions

View File

@ -63,9 +63,9 @@ jobs:
with:
go-version: "1.23"
check-latest: true
- name: test-sim-nondeterminism
- name: test-sim-nondeterminism-streaming
run: |
make test-sim-nondeterminism
make test-sim-nondeterminism-streaming
test-sim-multi-seed-short:
runs-on: depot-ubuntu-22.04-16

View File

@ -6,6 +6,7 @@ run:
- e2e
- ledger
- test_ledger_mock
- sims
linters:
disable-all: true

View File

@ -40,6 +40,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### Features
* (perf)[#24045](https://github.com/cosmos/cosmos-sdk/pull/24045) Sims: Replace runsim command with Go stdlib testing. CLI: `Commit` default true, `Lean`, `SimulateEveryOperation`, `PrintAllInvariants`, `DBBackend` params removed
* (crypto/keyring) [#24040](https://github.com/cosmos/cosmos-sdk/pull/24040) Expose the db keyring used in the keystore.
* (types) [#23919](https://github.com/cosmos/cosmos-sdk/pull/23919) Add MustValAddressFromBech32 function.
* (all) [#23708](https://github.com/cosmos/cosmos-sdk/pull/23708) Add unordered transaction support.

View File

@ -108,9 +108,6 @@ endif
all: tools build lint test vulncheck
# The below include contains the tools and runsim targets.
include contrib/devtools/Makefile
###############################################################################
### Build ###
###############################################################################
@ -267,8 +264,8 @@ endif
test-sim-nondeterminism:
@echo "Running non-determinism test..."
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -run TestAppStateDeterminism -Enabled=true \
-NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \
-NumBlocks=100 -BlockSize=200 -Period=0
# Requires an exported plugin. See store/streaming/README.md for documentation.
#
@ -281,41 +278,40 @@ test-sim-nondeterminism:
# make test-sim-nondeterminism-streaming
test-sim-nondeterminism-streaming:
@echo "Running non-determinism-streaming test..."
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -run TestAppStateDeterminism -Enabled=true \
-NumBlocks=100 -BlockSize=200 -Commit=true -Period=0 -v -timeout 24h -EnableStreaming=true
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=30m -tags='sims' -run TestAppStateDeterminism \
-NumBlocks=100 -BlockSize=200 -Period=0 -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 -mod=readonly -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \
-Enabled=true -NumBlocks=100 -BlockSize=200 -Commit=true -Seed=99 -Period=5 -SigverifyTx=false -v -timeout 24h
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \
-NumBlocks=100 -BlockSize=200 -Seed=99 -Period=5 -SigverifyTx=false
test-sim-import-export: runsim
test-sim-import-export:
@echo "Running application import/export simulation. This may take several minutes..."
@cd ${CURRENT_DIR}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 50 5 TestAppImportExport
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 20m -tags='sims' -run TestAppImportExport \
-NumBlocks=50 -Period=5
test-sim-after-import: runsim
test-sim-after-import:
@echo "Running application simulation-after-import. This may take several minutes..."
@cd ${CURRENT_DIR}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 50 5 TestAppSimulationAfterImport
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 30m -tags='sims' -run TestAppSimulationAfterImport \
-NumBlocks=50 -Period=5
test-sim-custom-genesis-multi-seed: runsim
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 && $(BINDIR)/runsim -Genesis=${HOME}/.simapp/config/genesis.json -SigverifyTx=false -SimAppPkg=. -ExitOnFail 400 5 TestFullAppSimulation
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation -Genesis=${HOME}/.simapp/config/genesis.json \
-NumBlocks=400 -Period=5
test-sim-multi-seed-long: runsim
test-sim-multi-seed-long:
@echo "Running long multi-seed application simulation. This may take awhile!"
@cd ${CURRENT_DIR}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 500 50 TestFullAppSimulation
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout=1h -tags='sims' -run TestFullAppSimulation \
-NumBlocks=500 -Period=50
test-sim-multi-seed-short: runsim
test-sim-multi-seed-short:
@echo "Running short multi-seed application simulation. This may take awhile!"
@cd ${CURRENT_DIR}/simapp && $(BINDIR)/runsim -Jobs=4 -SimAppPkg=. -ExitOnFail 50 10 TestFullAppSimulation
test-sim-benchmark-invariants:
@echo "Running simulation invariant benchmarks..."
cd ${CURRENT_DIR}/simapp && @go test -mod=readonly -benchmem -bench=BenchmarkInvariants -run=^$ \
-Enabled=true -NumBlocks=1000 -BlockSize=200 \
-Period=1 -Commit=true -Seed=57 -v -timeout 24h
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation \
-NumBlocks=50 -Period=10
.PHONY: \
test-sim-nondeterminism \
@ -325,17 +321,23 @@ test-sim-import-export \
test-sim-after-import \
test-sim-custom-genesis-multi-seed \
test-sim-multi-seed-short \
test-sim-multi-seed-long \
test-sim-benchmark-invariants
test-sim-multi-seed-long
SIM_NUM_BLOCKS ?= 500
SIM_BLOCK_SIZE ?= 200
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!"
#ld flags are a quick fix to make it work on current osx
@cd ${CURRENT_DIR}/simapp && go test -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 -mod=readonly -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -tags='sims' -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \
-NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -Seed=57 -timeout 30m
# Requires an exported plugin. See store/streaming/README.md for documentation.
#
@ -349,12 +351,12 @@ test-sim-benchmark:
test-sim-benchmark-streaming:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -run=^$$ $(.) -bench ^BenchmarkFullAppSimulation$$ \
-Enabled=true -NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -EnableStreaming=true
-NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -EnableStreaming=true
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 -mod=readonly -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
-NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out
# Requires an exported plugin. See store/streaming/README.md for documentation.
#
@ -368,9 +370,9 @@ test-sim-profile:
test-sim-profile-streaming:
@echo "Running application benchmark for numBlocks=$(SIM_NUM_BLOCKS), blockSize=$(SIM_BLOCK_SIZE). This may take awhile!"
@cd ${CURRENT_DIR}/simapp && go test -mod=readonly -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 -EnableStreaming=true
-NumBlocks=$(SIM_NUM_BLOCKS) -BlockSize=$(SIM_BLOCK_SIZE) -Commit=$(SIM_COMMIT) -timeout 24h -cpuprofile cpu.out -memprofile mem.out -EnableStreaming=true
.PHONY: test-sim-profile test-sim-benchmark
.PHONY: test-sim-profile test-sim-benchmark test-sim-fuzz
benchmark:
@go test -mod=readonly -bench=. $(PACKAGES_NOSIMULATION)

View File

@ -1175,3 +1175,8 @@ func (app *BaseApp) Close() error {
return errors.Join(errs...)
}
// GetBaseApp returns the pointer to itself.
func (app *BaseApp) GetBaseApp() *BaseApp {
return app
}

View File

@ -1,69 +0,0 @@
###
# Find OS and Go environment
# GO contains the Go binary
# FS contains the OS file separator
###
ifeq ($(OS),Windows_NT)
GO := $(shell where go.exe 2> NUL)
FS := "\\"
else
GO := $(shell command -v go 2> /dev/null)
FS := "/"
endif
ifeq ($(GO),)
$(error could not find go. Is it in PATH? $(GO))
endif
###############################################################################
### Functions ###
###############################################################################
go_get = $(if $(findstring Windows_NT,$(OS)),\
IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS) ( mkdir $(GITHUBDIR)$(FS)$(1) ) else (cd .) &\
IF NOT EXIST $(GITHUBDIR)$(FS)$(1)$(FS)$(2)$(FS) ( cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2) ) else (cd .) &\
,\
mkdir -p $(GITHUBDIR)$(FS)$(1) &&\
(test ! -d $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && cd $(GITHUBDIR)$(FS)$(1) && git clone https://github.com/$(1)/$(2)) || true &&\
)\
cd $(GITHUBDIR)$(FS)$(1)$(FS)$(2) && git fetch origin && git checkout -q $(3)
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
mkfile_dir := $(shell cd $(shell dirname $(mkfile_path)); pwd)
###############################################################################
### Tools ###
###############################################################################
PREFIX ?= /usr/local
BIN ?= $(PREFIX)/bin
UNAME_S ?= $(shell uname -s)
UNAME_M ?= $(shell uname -m)
GOPATH ?= $(shell $(GO) env GOPATH)
GITHUBDIR := $(GOPATH)$(FS)src$(FS)github.com
BUF_VERSION ?= 0.11.0
TOOLS_DESTDIR ?= $(GOPATH)/bin
RUNSIM = $(TOOLS_DESTDIR)/runsim
tools: tools-stamp
tools-stamp: runsim
# Create dummy file to satisfy dependency and avoid
# rebuilding when this Makefile target is hit twice
# in a row.
touch $@
# Install the runsim binary
runsim: $(RUNSIM)
$(RUNSIM):
@echo "Installing runsim..."
@go install github.com/cosmos/tools/cmd/runsim@v1.0.0
tools-clean:
rm -f $(GOLANGCI_LINT) $(RUNSIM)
rm -f tools-stamp
.PHONY: tools-clean runsim

View File

@ -2,7 +2,7 @@
set -e -o pipefail
LINT_TAGS="e2e,ledger,test_ledger_mock,system_test"
LINT_TAGS="e2e,ledger,test_ledger_mock,system_test,sims"
export LINT_TAGS
REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )"

View File

@ -3,7 +3,7 @@
set -e -o pipefail
LINT_TAGS="e2e,ledger,test_ledger_mock,system_test"
LINT_TAGS="e2e,ledger,test_ledger_mock,system_test,sims"
export LINT_TAGS
REPO_ROOT="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )"

View File

@ -1,38 +1,35 @@
//go:build sims
package simapp
import (
"os"
"testing"
flag "github.com/spf13/pflag"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
"github.com/cosmos/cosmos-sdk/testutils/sims"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/simulation"
simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli"
)
var FlagEnableBenchStreamingValue bool
// Get flags every time the simulator is run
func init() {
flag.BoolVar(&FlagEnableBenchStreamingValue, "EnableStreaming", false, "Enable streaming service")
}
// Profile with:
// /usr/local/go/bin/go test -benchmem -run=^$ cosmossdk.io/simapp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out
func BenchmarkFullAppSimulation(b *testing.B) {
b.ReportAllocs()
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID
config.ChainID = sims.SimAppChainID
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "goleveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "goleveldb-app-sim", "Simulation", simcli.FlagVerboseValue, true)
if err != nil {
b.Fatalf("simulation setup failed: %s", err.Error())
}
@ -47,86 +44,24 @@ func BenchmarkFullAppSimulation(b *testing.B) {
}()
appOptions := viper.New()
if FlagEnableStreamingValue {
m := make(map[string]interface{})
m["streaming.abci.keys"] = []string{"*"}
m["streaming.abci.plugin"] = "abci_v1"
m["streaming.abci.stop-node-on-err"] = true
for key, value := range m {
appOptions.SetDefault(key, value)
}
}
appOptions.SetDefault(flags.FlagHome, DefaultNodeHome)
appOptions.SetDefault(server.FlagInvCheckPeriod, simcli.FlagPeriodValue)
app := NewSimApp(logger, db, nil, true, appOptions, interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID))
app := NewSimApp(logger, db, nil, true, appOptions, interBlockCacheOpt(), baseapp.SetChainID(sims.SimAppChainID))
// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
simParams, simErr := simulation.SimulateFromSeedX(
b,
log.NewNopLogger(),
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
// export state and simParams before the simulation error is checked
if err = simtestutil.CheckExportSimulation(app, config, simParams); err != nil {
b.Fatal(err)
}
if simErr != nil {
b.Fatal(simErr)
}
if config.Commit {
simtestutil.PrintStats(db)
}
}
func BenchmarkInvariants(b *testing.B) {
b.ReportAllocs()
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-invariant-bench", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
if err != nil {
b.Fatalf("simulation setup failed: %s", err.Error())
}
if skip {
b.Skip("skipping benchmark application simulation")
}
config.AllInvariants = false
defer func() {
require.NoError(b, db.Close())
require.NoError(b, os.RemoveAll(dir))
}()
appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue
app := NewSimApp(logger, db, nil, true, appOptions, interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID))
// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
b,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
simtypes.RandomAccounts,
simtestutil.BuildSimulationOperations(app, app.AppCodec(), config, app.txConfig),
BlockedAddresses(),
config,
app.AppCodec(),
&simulation.DummyLogWriter{},
)
// export state and simParams before the simulation error is checked

View File

@ -1,19 +1,21 @@
//go:build sims
package simapp
import (
"encoding/binary"
"encoding/json"
"flag"
"fmt"
"io"
"math/rand"
"os"
"runtime/debug"
"strings"
"sync"
"testing"
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
dbm "github.com/cosmos/cosmos-db"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"cosmossdk.io/log"
@ -22,9 +24,10 @@ import (
"cosmossdk.io/x/feegrant"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
"github.com/cosmos/cosmos-sdk/testutils/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
"github.com/cosmos/cosmos-sdk/x/simulation"
@ -33,9 +36,6 @@ import (
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
// SimAppChainID hardcoded chainID for simulation
const SimAppChainID = "simulation-app"
var FlagEnableStreamingValue bool
// Get flags every time the simulator is run
@ -44,12 +44,6 @@ func init() {
flag.BoolVar(&FlagEnableStreamingValue, "EnableStreaming", false, "Enable streaming service")
}
// fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
// an IAVLStore for faster simulation speed.
func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode()
}
// interBlockCacheOpt returns a BaseApp option function that sets the persistent
// inter-block write-through cache.
func interBlockCacheOpt() func(*baseapp.BaseApp) {
@ -57,150 +51,188 @@ func interBlockCacheOpt() func(*baseapp.BaseApp) {
}
func TestFullAppSimulation(t *testing.T) {
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID
sims.Run(t, NewSimApp, setupStateFactory)
}
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
if skip {
t.Skip("skipping application simulation")
}
require.NoError(t, err, "simulation setup failed")
defer func() {
require.NoError(t, db.Close())
require.NoError(t, os.RemoveAll(dir))
}()
appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue
app := NewSimApp(logger, db, nil, true, appOptions, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
if !simcli.FlagSigverifyTxValue {
app.SetNotSigverifyTx()
}
require.Equal(t, "SimApp", app.Name())
// run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
// export state and simParams before the simulation error is checked
err = simtestutil.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)
if config.Commit {
simtestutil.PrintStats(db)
func setupStateFactory(app *SimApp) sims.SimStateFactory {
return sims.SimStateFactory{
Codec: app.AppCodec(),
AppStateFn: simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
BlockedAddr: BlockedAddresses(),
}
}
var (
exportAllModules = []string{}
exportWithValidatorSet = []string{}
)
func TestAppImportExport(t *testing.T) {
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID
sims.Run(t, NewSimApp, setupStateFactory, func(t *testing.T, ti sims.TestInstance[*SimApp]) {
t.Helper()
app := ti.App
t.Log("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules)
require.NoError(t, err)
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
if skip {
t.Skip("skipping application import/export simulation")
}
require.NoError(t, err, "simulation setup failed")
defer func() {
require.NoError(t, db.Close())
require.NoError(t, os.RemoveAll(dir))
}()
appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue
app := NewSimApp(logger, db, nil, true, appOptions, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
if !simcli.FlagSigverifyTxValue {
app.SetNotSigverifyTx()
}
require.Equal(t, "SimApp", app.Name())
// Run randomized simulation
_, simParams, simErr := simulation.SimulateFromSeed(
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
// export state and simParams before the simulation error is checked
err = simtestutil.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)
if config.Commit {
simtestutil.PrintStats(db)
}
fmt.Printf("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(false, []string{}, []string{})
require.NoError(t, err)
fmt.Printf("importing genesis...\n")
newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
require.NoError(t, err, "simulation setup failed")
defer func() {
require.NoError(t, newDB.Close())
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewSimApp(log.NewNopLogger(), newDB, nil, true, appOptions, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
require.Equal(t, "SimApp", newApp.Name())
var genesisState GenesisState
err = json.Unmarshal(exported.AppState, &genesisState)
require.NoError(t, err)
ctxA := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
_, err = newApp.ModuleManager.InitGenesis(ctxB, app.AppCodec(), genesisState)
if err != nil {
if strings.Contains(err.Error(), "validator set is empty after InitGenesis") {
logger.Info("Skipping simulation as all validators have been unbonded")
logger.Info("err", err, "stacktrace", string(debug.Stack()))
t.Log("importing genesis...\n")
newTestInstance := sims.NewSimulationAppInstance(t, ti.Cfg, NewSimApp)
newApp := newTestInstance.App
var genesisState GenesisState
require.NoError(t, json.Unmarshal(exported.AppState, &genesisState))
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
_, err = newApp.ModuleManager.InitGenesis(ctxB, newApp.appCodec, genesisState)
if IsEmptyValidatorSetErr(err) {
t.Skip("Skipping simulation as all validators have been unbonded")
return
}
}
require.NoError(t, err)
err = newApp.StoreConsensusParams(ctxB, exported.ConsensusParams)
require.NoError(t, err)
require.NoError(t, err)
err = newApp.StoreConsensusParams(ctxB, exported.ConsensusParams)
require.NoError(t, err)
fmt.Printf("comparing stores...\n")
t.Log("comparing stores...")
// skip certain prefixes
skipPrefixes := map[string][][]byte{
stakingtypes.StoreKey: {
stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey,
stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey,
stakingtypes.UnbondingTypeKey,
stakingtypes.ValidatorUpdatesKey, // todo (Alex): double check why there is a diff with test-sim-import-export
},
authzkeeper.StoreKey: {authzkeeper.GrantQueuePrefix},
feegrant.StoreKey: {feegrant.FeeAllowanceQueueKeyPrefix},
slashingtypes.StoreKey: {slashingtypes.ValidatorMissedBlockBitmapKeyPrefix},
}
AssertEqualStores(t, app, newApp, app.SimulationManager().StoreDecoders, skipPrefixes)
})
}
// skip certain prefixes
skipPrefixes := map[string][][]byte{
stakingtypes.StoreKey: {
stakingtypes.UnbondingQueueKey, stakingtypes.RedelegationQueueKey, stakingtypes.ValidatorQueueKey,
stakingtypes.HistoricalInfoKey, stakingtypes.UnbondingIDKey, stakingtypes.UnbondingIndexKey,
stakingtypes.UnbondingTypeKey, stakingtypes.ValidatorUpdatesKey,
},
authzkeeper.StoreKey: {authzkeeper.GrantQueuePrefix},
feegrant.StoreKey: {feegrant.FeeAllowanceQueueKeyPrefix},
slashingtypes.StoreKey: {slashingtypes.ValidatorMissedBlockBitmapKeyPrefix},
// 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) {
sims.Run(t, NewSimApp, setupStateFactory, func(t *testing.T, ti sims.TestInstance[*SimApp]) {
t.Helper()
app := ti.App
t.Log("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules)
require.NoError(t, err)
t.Log("importing genesis...\n")
newTestInstance := sims.NewSimulationAppInstance(t, ti.Cfg, NewSimApp)
newApp := newTestInstance.App
_, err = newApp.InitChain(&abci.RequestInitChain{
AppStateBytes: exported.AppState,
ChainId: sims.SimAppChainID,
})
if IsEmptyValidatorSetErr(err) {
t.Skip("Skipping simulation as all validators have been unbonded")
return
}
require.NoError(t, err)
newStateFactory := setupStateFactory(newApp)
_, err = simulation.SimulateFromSeedX(
t,
newTestInstance.AppLogger,
sims.WriteToDebugLog(newTestInstance.AppLogger),
newApp.BaseApp,
newStateFactory.AppStateFn,
simtypes.RandomAccounts,
simtestutil.BuildSimulationOperations(newApp, newApp.AppCodec(), newTestInstance.Cfg, newApp.TxConfig()),
newStateFactory.BlockedAddr,
newTestInstance.Cfg,
newStateFactory.Codec,
ti.ExecLogWriter,
)
require.NoError(t, err)
})
}
func IsEmptyValidatorSetErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "validator set is empty after InitGenesis")
}
func TestAppStateDeterminism(t *testing.T) {
const numTimesToRunPerSeed = 3
var seeds []int64
if s := simcli.NewConfigFromFlags().Seed; s != simcli.DefaultSeedValue {
// We will be overriding the random seed and just run a single simulation on the provided seed value
for j := 0; j < numTimesToRunPerSeed; j++ { // multiple rounds
seeds = append(seeds, s)
}
} else {
// setup with 3 random seeds
for i := 0; i < 3; i++ {
seed := rand.Int63()
for j := 0; j < numTimesToRunPerSeed; j++ { // multiple rounds
seeds = append(seeds, seed)
}
}
}
// overwrite default app config
interBlockCachingAppFactory := func(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp)) *SimApp {
if FlagEnableStreamingValue {
m := map[string]any{
"streaming.abci.keys": []string{"*"},
"streaming.abci.plugin": "abci_v1",
"streaming.abci.stop-node-on-err": true,
}
others := appOpts
appOpts = sims.AppOptionsFn(func(k string) any {
if v, ok := m[k]; ok {
return v
}
return others.Get(k)
})
}
return NewSimApp(logger, db, nil, true, appOpts, append(baseAppOptions, interBlockCacheOpt())...)
}
var mx sync.Mutex
appHashResults := make(map[int64][][]byte)
appSimLogger := make(map[int64][]simulation.LogWriter)
captureAndCheckHash := func(t *testing.T, ti sims.TestInstance[*SimApp]) {
t.Helper()
seed, appHash := ti.Cfg.Seed, ti.App.LastCommitID().Hash
mx.Lock()
otherHashes, execWriters := appHashResults[seed], appSimLogger[seed]
if len(otherHashes) < numTimesToRunPerSeed-1 {
appHashResults[seed], appSimLogger[seed] = append(otherHashes, appHash), append(execWriters, ti.ExecLogWriter)
} else { // cleanup
delete(appHashResults, seed)
delete(appSimLogger, seed)
}
mx.Unlock()
var failNow bool
// and check that all app hashes per seed are equal for each iteration
for i := 0; i < len(otherHashes); i++ {
if !assert.Equal(t, otherHashes[i], appHash) {
execWriters[i].PrintLogs()
failNow = true
}
}
if failNow {
ti.ExecLogWriter.PrintLogs()
t.Fatalf("non-determinism in seed %d", seed)
}
}
// run simulations
sims.RunWithSeeds(t, interBlockCachingAppFactory, setupStateFactory, seeds, []byte{}, captureAndCheckHash)
}
type ComparableStoreApp interface {
LastBlockHeight() int64
NewContextLegacy(isCheckTx bool, header cmtproto.Header) sdk.Context
GetKey(storeKey string) *storetypes.KVStoreKey
GetStoreKeys() []storetypes.StoreKey
}
func AssertEqualStores(t *testing.T, app, newApp ComparableStoreApp, storeDecoders simtypes.StoreDecoderRegistry, skipPrefixes map[string][][]byte) {
t.Helper()
ctxA := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
storeKeys := app.GetStoreKeys()
require.NotEmpty(t, storeKeys)
@ -218,195 +250,30 @@ func TestAppImportExport(t *testing.T) {
storeB := ctxB.KVStore(appKeyB)
failedKVAs, failedKVBs := simtestutil.DiffKVStores(storeA, storeB, skipPrefixes[keyName])
require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare %s", keyName)
require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare %s, key stores %s and %s", keyName, appKeyA, appKeyB)
fmt.Printf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), appKeyA, appKeyB)
require.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(keyName, app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs))
t.Logf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), appKeyA, appKeyB)
if !assert.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(keyName, storeDecoders, failedKVAs, failedKVBs)) {
for _, v := range failedKVAs {
t.Logf("store mismatch: %q\n", v)
}
t.FailNow()
}
}
}
func TestAppSimulationAfterImport(t *testing.T) {
config := simcli.NewConfigFromFlags()
config.ChainID = SimAppChainID
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
if skip {
t.Skip("skipping application simulation after import")
}
require.NoError(t, err, "simulation setup failed")
defer func() {
require.NoError(t, db.Close())
require.NoError(t, os.RemoveAll(dir))
}()
appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue
app := NewSimApp(logger, db, nil, true, appOptions, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
if !simcli.FlagSigverifyTxValue {
app.SetNotSigverifyTx()
}
require.Equal(t, "SimApp", app.Name())
// Run randomized simulation
stopEarly, simParams, simErr := simulation.SimulateFromSeed(
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
// export state and simParams before the simulation error is checked
err = simtestutil.CheckExportSimulation(app, config, simParams)
require.NoError(t, err)
require.NoError(t, simErr)
if config.Commit {
simtestutil.PrintStats(db)
}
if stopEarly {
fmt.Println("can't export or import a zero-validator genesis, exiting test...")
return
}
fmt.Printf("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(true, []string{}, []string{})
require.NoError(t, err)
fmt.Printf("importing genesis...\n")
newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
require.NoError(t, err, "simulation setup failed")
defer func() {
require.NoError(t, newDB.Close())
require.NoError(t, os.RemoveAll(newDir))
}()
newApp := NewSimApp(log.NewNopLogger(), newDB, nil, true, appOptions, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
require.Equal(t, "SimApp", newApp.Name())
_, err = newApp.InitChain(&abci.RequestInitChain{
AppStateBytes: exported.AppState,
ChainId: SimAppChainID,
func FuzzFullAppSimulation(f *testing.F) {
f.Fuzz(func(t *testing.T, rawSeed []byte) {
if len(rawSeed) < 8 {
t.Skip()
return
}
sims.RunWithSeeds(
t,
NewSimApp,
setupStateFactory,
[]int64{int64(binary.BigEndian.Uint64(rawSeed))},
rawSeed[8:],
)
})
require.NoError(t, err)
_, _, err = simulation.SimulateFromSeed(
t,
os.Stdout,
newApp.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(newApp, newApp.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
require.NoError(t, err)
}
// TODO: Make another test for the fuzzer itself, which just has noOp txs
// and doesn't depend on the application.
func TestAppStateDeterminism(t *testing.T) {
if !simcli.FlagEnabledValue {
t.Skip("skipping application simulation")
}
config := simcli.NewConfigFromFlags()
config.InitialBlockHeight = 1
config.ExportParamsPath = ""
config.OnOperation = false
config.AllInvariants = false
config.ChainID = SimAppChainID
numSeeds := 3
numTimesToRunPerSeed := 3 // This used to be set to 5, but we've temporarily reduced it to 3 for the sake of faster CI.
appHashList := make([]json.RawMessage, numTimesToRunPerSeed)
// We will be overriding the random seed and just run a single simulation on the provided seed value
if config.Seed != simcli.DefaultSeedValue {
numSeeds = 1
}
appOptions := viper.New()
if FlagEnableStreamingValue {
m := make(map[string]interface{})
m["streaming.abci.keys"] = []string{"*"}
m["streaming.abci.plugin"] = "abci_v1"
m["streaming.abci.stop-node-on-err"] = true
for key, value := range m {
appOptions.SetDefault(key, value)
}
}
appOptions.SetDefault(flags.FlagHome, DefaultNodeHome)
appOptions.SetDefault(server.FlagInvCheckPeriod, simcli.FlagPeriodValue)
if simcli.FlagVerboseValue {
appOptions.SetDefault(flags.FlagLogLevel, "debug")
}
for i := 0; i < numSeeds; i++ {
if config.Seed == simcli.DefaultSeedValue {
config.Seed = rand.Int63()
}
fmt.Println("config.Seed: ", config.Seed)
for j := 0; j < numTimesToRunPerSeed; j++ {
var logger log.Logger
if simcli.FlagVerboseValue {
logger = log.NewTestLogger(t)
} else {
logger = log.NewNopLogger()
}
db := dbm.NewMemDB()
app := NewSimApp(logger, db, nil, true, appOptions, interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID))
if !simcli.FlagSigverifyTxValue {
app.SetNotSigverifyTx()
}
fmt.Printf(
"running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n",
config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
)
_, _, err := simulation.SimulateFromSeed(
t,
os.Stdout,
app.BaseApp,
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.SimulationOperations(app, app.AppCodec(), config),
BlockedAddresses(),
config,
app.AppCodec(),
)
require.NoError(t, err)
if config.Commit {
simtestutil.PrintStats(db)
}
appHash := app.LastCommitID().Hash
appHashList[j] = appHash
if j != 0 {
require.Equal(
t, string(appHashList[0]), string(appHashList[j]),
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
)
}
}
}
}

View File

@ -19,7 +19,6 @@ import (
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/server"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/testutil/mock"
"github.com/cosmos/cosmos-sdk/testutil/network"
@ -43,7 +42,6 @@ func setup(withGenesis bool, invCheckPeriod uint) (*SimApp, GenesisState) {
appOptions := make(simtestutil.AppOptionsMap, 0)
appOptions[flags.FlagHome] = DefaultNodeHome
appOptions[server.FlagInvCheckPeriod] = invCheckPeriod
app := NewSimApp(log.NewNopLogger(), db, nil, true, appOptions)
if withGenesis {

View File

@ -12,6 +12,7 @@ import (
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -23,7 +24,7 @@ import (
// SetupSimulation creates the config, db (levelDB), temporary directory and logger for the simulation tests.
// If `skip` is false it skips the current test. `skip` should be set using the `FlagEnabledValue` flag.
// Returns error on an invalid db intantiation or temp dir creation.
// Returns error on an invalid db instantiation or temp dir creation.
func SetupSimulation(config simtypes.Config, dirPrefix, dbName string, verbose, skip bool) (dbm.DB, string, log.Logger, bool, error) {
if !skip {
return nil, "", nil, true, nil
@ -31,7 +32,7 @@ func SetupSimulation(config simtypes.Config, dirPrefix, dbName string, verbose,
var logger log.Logger
if verbose {
logger = log.NewLogger(os.Stdout) // TODO(mr): enable selection of log destination.
logger = log.NewLogger(os.Stdout)
} else {
logger = log.NewNopLogger()
}
@ -51,11 +52,18 @@ func SetupSimulation(config simtypes.Config, dirPrefix, dbName string, verbose,
// SimulationOperations retrieves the simulation params from the provided file path
// and returns all the modules weighted operations
// Deprecated: use BuildSimulationOperations with TxConfig
func SimulationOperations(app runtime.AppI, cdc codec.JSONCodec, config simtypes.Config) []simtypes.WeightedOperation {
return BuildSimulationOperations(app, cdc, config, moduletestutil.MakeTestTxConfig())
}
// BuildSimulationOperations retrieves the simulation params from the provided file path
// and returns all the modules weighted operations
func BuildSimulationOperations(app runtime.AppI, cdc codec.JSONCodec, config simtypes.Config, txConfig client.TxConfig) []simtypes.WeightedOperation {
simState := module.SimulationState{
AppParams: make(simtypes.AppParams),
Cdc: cdc,
TxConfig: moduletestutil.MakeTestTxConfig(),
TxConfig: txConfig,
BondDenom: sdk.DefaultBondDenom,
}
@ -71,8 +79,7 @@ func SimulationOperations(app runtime.AppI, cdc codec.JSONCodec, config simtypes
}
}
//nolint:staticcheck // used for legacy testing
simState.LegacyProposalContents = app.SimulationManager().GetProposalContents(simState)
simState.LegacyProposalContents = app.SimulationManager().GetProposalContents(simState) //nolint:staticcheck // we're testing the old way here
simState.ProposalMsgs = app.SimulationManager().GetProposalMsgs(simState)
return app.SimulationManager().WeightedOperations(simState)
}

View File

@ -17,6 +17,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/testutil"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
@ -35,7 +36,11 @@ const (
// AppStateFn returns the initial application state using a genesis or the simulation parameters.
// It calls AppStateFnWithExtendedCb with nil rawStateCb.
func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager, genesisState map[string]json.RawMessage) simtypes.AppStateFn {
func AppStateFn(
cdc codec.JSONCodec,
simManager *module.SimulationManager,
genesisState map[string]json.RawMessage,
) simtypes.AppStateFn {
return AppStateFnWithExtendedCb(cdc, simManager, genesisState, nil)
}
@ -68,13 +73,9 @@ func AppStateFnWithExtendedCbs(
accs []simtypes.Account,
config simtypes.Config,
) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) {
if simcli.FlagGenesisTimeValue == 0 {
genesisTimestamp = simtypes.RandTimestamp(r)
} else {
genesisTimestamp = time.Unix(simcli.FlagGenesisTimeValue, 0)
}
genesisTimestamp = time.Unix(config.GenesisTime, 0)
chainID = config.ChainID
switch {
case config.ParamsFile != "" && config.GenesisFile != "":
panic("cannot provide both a genesis file and a params file")
@ -138,8 +139,7 @@ func AppStateFnWithExtendedCbs(
}
notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens)
// edit bank state to make it have the not bonded pool tokens
bankStateBz, ok := rawState[banktypes.ModuleName]
// TODO(fdymylja/jonathan): should we panic in this case
bankStateBz, ok := rawState[testutil.BankModuleName]
if !ok {
panic("bank genesis state is missing")
}
@ -166,7 +166,7 @@ func AppStateFnWithExtendedCbs(
// change appState back
for name, state := range map[string]proto.Message{
stakingtypes.ModuleName: stakingState,
banktypes.ModuleName: bankState,
testutil.BankModuleName: bankState,
} {
if moduleStateCb != nil {
moduleStateCb(name, state)
@ -219,15 +219,6 @@ func AppStateRandomizedFn(
numInitiallyBonded = numAccs
}
fmt.Printf(
`Selected randomly generated parameters for simulated genesis:
{
stake_per_account: "%d",
initially_bonded_validators: "%d"
}
`, initialStake.Uint64(), numInitiallyBonded,
)
simState := &module.SimulationState{
AppParams: appParams,
Cdc: cdc,
@ -273,8 +264,8 @@ func AppStateFromGenesisFileFn(r io.Reader, cdc codec.JSONCodec, genesisFile str
}
var authGenesis authtypes.GenesisState
if appState[authtypes.ModuleName] != nil {
cdc.MustUnmarshalJSON(appState[authtypes.ModuleName], &authGenesis)
if appState[testutil.AuthModuleName] != nil {
cdc.MustUnmarshalJSON(appState[testutil.AuthModuleName], &authGenesis)
}
newAccs := make([]simtypes.Account, len(authGenesis.Accounts))

235
testutils/sims/runner.go Normal file
View File

@ -0,0 +1,235 @@
package sims
import (
"fmt"
"io"
"path/filepath"
"strings"
"testing"
dbm "github.com/cosmos/cosmos-db"
"github.com/stretchr/testify/require"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/runtime"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/simulation"
"github.com/cosmos/cosmos-sdk/x/simulation/client/cli"
)
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{
1, 2, 4, 7,
32, 123, 124, 582, 1893, 2989,
3012, 4728, 37827, 981928, 87821, 891823782,
989182, 89182391, 11, 22, 44, 77, 99, 2020,
3232, 123123, 124124, 582582, 18931893,
29892989, 30123012, 47284728, 7601778, 8090485,
977367484, 491163361, 424254581, 673398983,
}
type SimStateFactory struct {
Codec codec.Codec
AppStateFn simtypes.AppStateFn
BlockedAddr map[string]bool
}
// SimulationApp abstract app that is used by sims
type SimulationApp interface {
runtime.AppI
SetNotSigverifyTx()
GetBaseApp() *baseapp.BaseApp
TxConfig() client.TxConfig
}
// Run is a helper function that runs a simulation test with the given parameters.
// It calls the RunWithSeeds function with the default seeds and parameters.
//
// This is the entrypoint to run simulation tests that used to run with the runsim binary.
func Run[T SimulationApp](
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) T,
setupStateFactory func(app T) SimStateFactory,
postRunActions ...func(t *testing.T, app TestInstance[T]),
) {
t.Helper()
RunWithSeeds(t, appFactory, setupStateFactory, defaultSeeds, nil, postRunActions...)
}
// RunWithSeeds is a helper function that runs a simulation test with the given parameters.
// It iterates over the provided seeds and runs the simulation test for each seed in parallel.
//
// It sets up the environment, creates an instance of the simulation app,
// calls the simulation.SimulateFromSeed function to run the simulation, and performs post-run actions for each seed.
// The execution is deterministic and can be used for fuzz tests as well.
//
// The system under test is isolated for each run but unlike the old runsim command, there is no Process separation.
// This means, global caches may be reused for example. This implementation build upon the vanialla Go stdlib test framework.
func RunWithSeeds[T SimulationApp](
t *testing.T,
appFactory func(
logger log.Logger,
db dbm.DB,
traceStore io.Writer,
loadLatest bool,
appOpts servertypes.AppOptions,
baseAppOptions ...func(*baseapp.BaseApp),
) T,
setupStateFactory func(app T) SimStateFactory,
seeds []int64,
fuzzSeed []byte,
postRunActions ...func(t *testing.T, app TestInstance[T]),
) {
t.Helper()
cfg := cli.NewConfigFromFlags()
cfg.ChainID = SimAppChainID
if deprecatedParams := cli.GetDeprecatedFlagUsed(); len(deprecatedParams) != 0 {
fmt.Printf("Warning: Deprecated flag are used: %s", strings.Join(deprecatedParams, ","))
}
for i := range seeds {
seed := seeds[i]
t.Run(fmt.Sprintf("seed: %d", seed), func(t *testing.T) {
t.Parallel()
// setup environment
tCfg := cfg.With(t, seed, fuzzSeed)
testInstance := NewSimulationAppInstance(t, tCfg, appFactory)
var runLogger log.Logger
if cli.FlagVerboseValue {
runLogger = log.NewTestLogger(t)
} else {
runLogger = log.NewTestLoggerInfo(t)
}
runLogger = runLogger.With("seed", tCfg.Seed)
app := testInstance.App
stateFactory := setupStateFactory(app)
simParams, err := simulation.SimulateFromSeedX(
t,
runLogger,
WriteToDebugLog(runLogger),
app.GetBaseApp(),
stateFactory.AppStateFn,
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
simtestutil.BuildSimulationOperations(app, stateFactory.Codec, tCfg, testInstance.App.TxConfig()),
stateFactory.BlockedAddr,
tCfg,
stateFactory.Codec,
testInstance.ExecLogWriter,
)
require.NoError(t, err)
err = simtestutil.CheckExportSimulation(app, tCfg, simParams)
require.NoError(t, err)
if tCfg.Commit {
simtestutil.PrintStats(testInstance.DB)
}
for _, step := range postRunActions {
step(t, testInstance)
}
})
}
}
// TestInstance is a generic type that represents an instance of a SimulationApp used for testing simulations.
// It contains the following fields:
// - App: The instance of the SimulationApp under test.
// - DB: The LevelDB database for the simulation app.
// - WorkDir: The temporary working directory for the simulation app.
// - Cfg: The configuration flags for the simulator.
// - AppLogger: The logger used for logging in the app during the simulation, with seed value attached.
// - ExecLogWriter: Captures block and operation data coming from the simulation
type TestInstance[T SimulationApp] struct {
App T
DB dbm.DB
WorkDir string
Cfg simtypes.Config
AppLogger log.Logger
ExecLogWriter simulation.LogWriter
}
// NewSimulationAppInstance initializes and returns a TestInstance of a SimulationApp.
// The function takes a testing.T instance, a simtypes.Config instance, and an appFactory function as parameters.
// It creates a temporary working directory and a LevelDB database for the simulation app.
// The function then initializes a logger based on the verbosity flag and sets the logger's seed to the test configuration's seed.
// The database is closed and cleaned up on test completion.
func NewSimulationAppInstance[T SimulationApp](
t *testing.T,
tCfg simtypes.Config,
appFactory func(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool, appOpts servertypes.AppOptions, baseAppOptions ...func(*baseapp.BaseApp)) T,
) TestInstance[T] {
t.Helper()
workDir := t.TempDir()
dbDir := filepath.Join(workDir, "leveldb-app-sim")
var logger log.Logger
if cli.FlagVerboseValue {
logger = log.NewTestLogger(t)
} else {
logger = log.NewTestLoggerError(t)
}
logger = logger.With("seed", tCfg.Seed)
db, err := dbm.NewDB("Simulation", dbm.BackendType(tCfg.DBBackend), dbDir)
require.NoError(t, err)
t.Cleanup(func() {
require.NoError(t, db.Close())
})
appOptions := make(simtestutil.AppOptionsMap)
appOptions[flags.FlagHome] = workDir
app := appFactory(logger, db, nil, true, appOptions, baseapp.SetChainID(SimAppChainID))
if !cli.FlagSigverifyTxValue {
app.SetNotSigverifyTx()
}
return TestInstance[T]{
App: app,
DB: db,
WorkDir: workDir,
Cfg: tCfg,
AppLogger: logger,
ExecLogWriter: &simulation.StandardLogWriter{Seed: tCfg.Seed},
}
}
var _ io.Writer = writerFn(nil)
type writerFn func(p []byte) (n int, err error)
func (w writerFn) Write(p []byte) (n int, err error) {
return w(p)
}
// WriteToDebugLog is an adapter to io.Writer interface
func WriteToDebugLog(logger log.Logger) io.Writer {
return writerFn(func(p []byte) (n int, err error) {
logger.Debug(string(p))
return len(p), nil
})
}
// AppOptionsFn is an adapter to the single method AppOptions interface
type AppOptionsFn func(string) any
func (f AppOptionsFn) Get(k string) any {
return f(k)
}
// FauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
// an IAVLStore for faster simulation speed.
func FauxMerkleModeOpt(bapp *baseapp.BaseApp) {
bapp.SetFauxMerkleMode()
}

View File

@ -31,22 +31,32 @@ func RandomAcc(r *rand.Rand, accs []Account) (Account, int) {
return accs[idx], idx
}
// RandomAccounts generates n random accounts
// RandomAccounts deterministically generates n random accounts without duplicates.
func RandomAccounts(r *rand.Rand, n int) []Account {
accs := make([]Account, n)
for i := 0; i < n; i++ {
idx := make(map[string]struct{}, n)
var i int
for i < n {
// don't need that much entropy for simulation
privkeySeed := make([]byte, 15)
r.Read(privkeySeed)
accs[i].PrivKey = secp256k1.GenPrivKeyFromSecret(privkeySeed)
accs[i].PubKey = accs[i].PrivKey.PubKey()
accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address())
accs[i].ConsKey = ed25519.GenPrivKeyFromSecret(privkeySeed)
if _, err := r.Read(privkeySeed); err != nil {
panic(err)
}
privKey := secp256k1.GenPrivKeyFromSecret(privkeySeed)
pubKey := privKey.PubKey()
addr := sdk.AccAddress(pubKey.Address())
if _, exists := idx[string(addr.Bytes())]; exists {
continue
}
idx[string(addr.Bytes())] = struct{}{}
accs[i] = Account{
Address: addr,
PrivKey: privKey,
PubKey: pubKey,
ConsKey: ed25519.GenPrivKeyFromSecret(privkeySeed),
}
i++
}
return accs
}

View File

@ -1,5 +1,7 @@
package simulation
import "testing"
// Config contains the necessary configuration flags for the simulator
type Config struct {
GenesisFile string // custom simulation genesis file; cannot be used with params file
@ -12,6 +14,7 @@ type Config struct {
Seed int64 // simulation random seed
InitialBlockHeight int // 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
BlockSize int // operations per block
ChainID string // chain-id used on the simulation
@ -19,9 +22,28 @@ type Config struct {
Lean bool // lean simulation log output
Commit bool // have the simulation commit
OnOperation bool // run slow invariants every operation
AllInvariants bool // print all failed invariants if a broken invariant is found
DBBackend string // custom db backend type
BlockMaxGas int64 // custom max gas for block
FuzzSeed []byte
TB testing.TB
FauxMerkle bool
// Deprecated: unused and will be removed
OnOperation bool // run slow invariants every operation
// Deprecated: unused and will be removed
AllInvariants bool // print all failed invariants if a broken invariant is found
}
func (c Config) shallowCopy() Config {
return c
}
// With sets the values of t, seed, and fuzzSeed in a copy of the Config and returns the copy.
func (c Config) With(tb testing.TB, seed int64, fuzzSeed []byte) Config {
tb.Helper()
r := c.shallowCopy()
r.TB = tb
r.Seed = seed
r.FuzzSeed = fuzzSeed
return r
}

View File

@ -147,31 +147,8 @@ func RandSubsetCoins(r *rand.Rand, coins sdk.Coins) sdk.Coins {
}
// DeriveRand derives a new Rand deterministically from another random source.
// Unlike rand.New(rand.NewSource(seed)), the result is "more random"
// depending on the source and state of r.
//
// NOTE: not crypto safe.
func DeriveRand(r *rand.Rand) *rand.Rand {
const num = 8 // TODO what's a good number? Too large is too slow.
ms := multiSource(make([]rand.Source, num))
for i := 0; i < num; i++ {
ms[i] = rand.NewSource(r.Int63())
}
return rand.New(ms)
}
type multiSource []rand.Source
func (ms multiSource) Int63() (r int64) {
for _, source := range ms {
r ^= source.Int63()
}
return r
}
func (ms multiSource) Seed(_ int64) {
panic("multiSource Seed should not be called")
return rand.New(rand.NewSource(r.Int63()))
}

View File

@ -3,6 +3,7 @@ package simulation
import (
"math"
"math/rand"
"sync/atomic"
"time"
sdkmath "cosmossdk.io/math"
@ -18,7 +19,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/simulation"
)
var initialProposalID = uint64(100000000000000)
const unsetProposalID = 100000000000000
// Governance message types and routes
var (
@ -43,6 +44,26 @@ const (
DefaultWeightMsgCancelProposal = 5
)
// sharedState shared state between message invocations
type sharedState struct {
minProposalID atomic.Uint64
}
// newSharedState constructor
func newSharedState() *sharedState {
r := &sharedState{}
r.setMinProposalID(unsetProposalID)
return r
}
func (s *sharedState) getMinProposalID() uint64 {
return s.minProposalID.Load()
}
func (s *sharedState) setMinProposalID(id uint64) {
s.minProposalID.Store(id)
}
// WeightedOperations returns all the operations from the module with their respective weights
func WeightedOperations(
appParams simtypes.AppParams,
@ -119,19 +140,19 @@ func WeightedOperations(
),
)
}
state := newSharedState()
wGovOps := simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgDeposit,
SimulateMsgDeposit(txGen, ak, bk, k),
simulateMsgDeposit(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgVote,
SimulateMsgVote(txGen, ak, bk, k),
simulateMsgVote(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgVoteWeighted,
SimulateMsgVoteWeighted(txGen, ak, bk, k),
simulateMsgVoteWeighted(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgCancelProposal,
@ -205,7 +226,7 @@ func simulateMsgSubmitProposal(
// column 3: 75% vote
// column 4: 40% vote
// column 5: 15% vote
// column 6: noone votes
// column 6: no one votes
// All columns sum to 100 for simplicity, values chosen by @valardragon semi-arbitrarily,
// feel free to change.
numVotesTransitionMatrix, _ := simulation.CreateTransitionMatrix([][]int{
@ -291,13 +312,13 @@ func simulateMsgSubmitProposal(
whoVotes = whoVotes[:numVotes]
params, _ := k.Params.Get(ctx)
votingPeriod := params.VotingPeriod
s := newSharedState()
fops := make([]simtypes.FutureOperation, numVotes+1)
for i := 0; i < numVotes; i++ {
whenVote := ctx.BlockHeader().Time.Add(time.Duration(r.Int63n(int64(votingPeriod.Seconds()))) * time.Second)
fops[i] = simtypes.FutureOperation{
BlockTime: whenVote,
Op: operationSimulateMsgVote(txGen, ak, bk, k, accs[whoVotes[i]], int64(proposalID)),
Op: operationSimulateMsgVote(txGen, ak, bk, k, accs[whoVotes[i]], int64(proposalID), s),
}
}
@ -306,18 +327,29 @@ func simulateMsgSubmitProposal(
}
// SimulateMsgDeposit generates a MsgDeposit with random values.
// Deprecated: this is an internal method and will be removed
func SimulateMsgDeposit(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
) simtypes.Operation {
return simulateMsgDeposit(txGen, ak, bk, k, newSharedState())
}
func simulateMsgDeposit(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
accs []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
simAccount, _ := simtypes.RandomAcc(r, accs)
proposalID, ok := randomProposalID(r, k, ctx, v1.StatusDepositPeriod)
proposalID, ok := randomProposalID(r, k, ctx, v1.StatusDepositPeriod, s)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgDeposit, "unable to generate proposalID"), nil, nil
}
@ -366,13 +398,24 @@ func SimulateMsgDeposit(
}
// SimulateMsgVote generates a MsgVote with random values.
// Deprecated: this is an internal method and will be removed
func SimulateMsgVote(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
) simtypes.Operation {
return operationSimulateMsgVote(txGen, ak, bk, k, simtypes.Account{}, -1)
return simulateMsgVote(txGen, ak, bk, k, newSharedState())
}
func simulateMsgVote(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return operationSimulateMsgVote(txGen, ak, bk, k, simtypes.Account{}, -1, s)
}
func operationSimulateMsgVote(
@ -382,6 +425,7 @@ func operationSimulateMsgVote(
k *keeper.Keeper,
simAccount simtypes.Account,
proposalIDInt int64,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
@ -396,7 +440,7 @@ func operationSimulateMsgVote(
switch {
case proposalIDInt < 0:
var ok bool
proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod)
proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod, s)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgVote, "unable to generate proposalID"), nil, nil
}
@ -435,7 +479,17 @@ func SimulateMsgVoteWeighted(
bk types.BankKeeper,
k *keeper.Keeper,
) simtypes.Operation {
return operationSimulateMsgVoteWeighted(txGen, ak, bk, k, simtypes.Account{}, -1)
return simulateMsgVoteWeighted(txGen, ak, bk, k, newSharedState())
}
func simulateMsgVoteWeighted(
txGen client.TxConfig,
ak types.AccountKeeper,
bk types.BankKeeper,
k *keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return operationSimulateMsgVoteWeighted(txGen, ak, bk, k, simtypes.Account{}, -1, s)
}
func operationSimulateMsgVoteWeighted(
@ -445,6 +499,7 @@ func operationSimulateMsgVoteWeighted(
k *keeper.Keeper,
simAccount simtypes.Account,
proposalIDInt int64,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context,
@ -459,7 +514,7 @@ func operationSimulateMsgVoteWeighted(
switch {
case proposalIDInt < 0:
var ok bool
proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod)
proposalID, ok = randomProposalID(r, k, ctx, v1.StatusVotingPeriod, s)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgVoteWeighted, "unable to generate proposalID"), nil, nil
}
@ -626,20 +681,13 @@ func randomProposal(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context) *v1.Proposa
// (defined in gov GenesisState) and the latest proposal ID
// that matches a given Status.
// It does not provide a default ID.
func randomProposalID(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context, status v1.ProposalStatus) (proposalID uint64, found bool) {
func randomProposalID(r *rand.Rand, k *keeper.Keeper, ctx sdk.Context, status v1.ProposalStatus, s *sharedState) (proposalID uint64, found bool) {
proposalID, _ = k.ProposalID.Peek(ctx)
switch {
case proposalID > initialProposalID:
// select a random ID between [initialProposalID, proposalID]
if initialProposalID := s.getMinProposalID(); initialProposalID == unsetProposalID {
s.setMinProposalID(proposalID)
} else if initialProposalID < proposalID {
proposalID = uint64(simtypes.RandIntBetween(r, int(initialProposalID), int(proposalID)))
default:
// This is called on the first call to this funcion
// in order to update the global variable
initialProposalID = proposalID
}
proposal, err := k.Proposals.Get(ctx, proposalID)
if err != nil || proposal.Status != status {
return proposalID, false

View File

@ -325,13 +325,13 @@ func TestSimulateMsgVote(t *testing.T) {
proposal, err := v1.NewProposal([]sdk.Msg{contentMsg}, 1, submitTime, submitTime.Add(*depositPeriod), "", "text proposal", "description", sdk.AccAddress("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r"), false)
require.NoError(t, err)
suite.GovKeeper.ActivateVotingPeriod(ctx, proposal)
require.NoError(t, suite.GovKeeper.ActivateVotingPeriod(ctx, proposal))
app.FinalizeBlock(&abci.RequestFinalizeBlock{
_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{
Height: app.LastBlockHeight() + 1,
Hash: app.LastCommitID().Hash,
})
require.NoError(t, err)
// execute operation
op := simulation.SimulateMsgVote(suite.TxConfig, suite.AccountKeeper, suite.BankKeeper, suite.GovKeeper)
operationMsg, _, err := op(r, app.BaseApp, ctx, accounts, "")

View File

@ -5,6 +5,7 @@ import (
"fmt"
"math/rand"
"strings"
"sync/atomic"
"time"
"github.com/cosmos/cosmos-sdk/baseapp"
@ -19,7 +20,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/simulation"
)
var initialGroupID = uint64(100000000000000)
const unsetGroupID = 100000000000000
// group message types
var (
@ -76,12 +77,32 @@ const (
WeightMsgCreateGroupWithPolicy = 50
)
// sharedState shared state between message invocations
type sharedState struct {
minGroupID atomic.Uint64
}
// newSharedState constructor
func newSharedState() *sharedState {
r := &sharedState{}
r.setMinGroupID(unsetGroupID)
return r
}
func (s *sharedState) getMinGroupID() uint64 {
return s.minGroupID.Load()
}
func (s *sharedState) setMinGroupID(id uint64) {
s.minGroupID.Store(id)
}
// WeightedOperations returns all the operations from the module with their respective weights
func WeightedOperations(
registry cdctypes.InterfaceRegistry,
appParams simtypes.AppParams, cdc codec.JSONCodec, txGen client.TxConfig,
appParams simtypes.AppParams, _ codec.JSONCodec, txGen client.TxConfig,
ak group.AccountKeeper, bk group.BankKeeper, k keeper.Keeper,
appCdc cdctypes.AnyUnpacker,
_ cdctypes.AnyUnpacker,
) simulation.WeightedOperations {
var (
weightMsgCreateGroup int
@ -145,12 +166,14 @@ func WeightedOperations(
pCdc := codec.NewProtoCodec(registry)
state := newSharedState()
// create two proposals for weightedOperations
var createProposalOps simulation.WeightedOperations
for i := 0; i < 2; i++ {
createProposalOps = append(createProposalOps, simulation.NewWeightedOperation(
weightMsgSubmitProposal,
SimulateMsgSubmitProposal(pCdc, txGen, ak, bk, k),
simulateMsgSubmitProposal(pCdc, txGen, ak, bk, k, state),
))
}
@ -161,7 +184,7 @@ func WeightedOperations(
),
simulation.NewWeightedOperation(
weightMsgCreateGroupPolicy,
SimulateMsgCreateGroupPolicy(pCdc, txGen, ak, bk, k),
simulateMsgCreateGroupPolicy(pCdc, txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgCreateGroupWithPolicy,
@ -172,43 +195,43 @@ func WeightedOperations(
wPostCreateProposalOps := simulation.WeightedOperations{
simulation.NewWeightedOperation(
WeightMsgWithdrawProposal,
SimulateMsgWithdrawProposal(pCdc, txGen, ak, bk, k),
simulateMsgWithdrawProposal(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgVote,
SimulateMsgVote(pCdc, txGen, ak, bk, k),
simulateMsgVote(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgExec,
SimulateMsgExec(pCdc, txGen, ak, bk, k),
simulateMsgExec(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgUpdateGroupMetadata,
SimulateMsgUpdateGroupMetadata(pCdc, txGen, ak, bk, k),
simulateMsgUpdateGroupMetadata(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgUpdateGroupAdmin,
SimulateMsgUpdateGroupAdmin(pCdc, txGen, ak, bk, k),
simulateMsgUpdateGroupAdmin(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgUpdateGroupMembers,
SimulateMsgUpdateGroupMembers(pCdc, txGen, ak, bk, k),
simulateMsgUpdateGroupMembers(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgUpdateGroupPolicyAdmin,
SimulateMsgUpdateGroupPolicyAdmin(pCdc, txGen, ak, bk, k),
simulateMsgUpdateGroupPolicyAdmin(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgUpdateGroupPolicyDecisionPolicy,
SimulateMsgUpdateGroupPolicyDecisionPolicy(pCdc, txGen, ak, bk, k),
simulateMsgUpdateGroupPolicyDecisionPolicy(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgUpdateGroupPolicyMetadata,
SimulateMsgUpdateGroupPolicyMetadata(pCdc, txGen, ak, bk, k),
simulateMsgUpdateGroupPolicyMetadata(txGen, ak, bk, k, state),
),
simulation.NewWeightedOperation(
weightMsgLeaveGroup,
SimulateMsgLeaveGroup(pCdc, txGen, k, ak, bk),
simulateMsgLeaveGroup(txGen, k, ak, bk, state),
),
}
@ -216,8 +239,9 @@ func WeightedOperations(
}
// SimulateMsgCreateGroup generates a MsgCreateGroup with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgCreateGroup(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
@ -263,8 +287,9 @@ func SimulateMsgCreateGroup(
}
// SimulateMsgCreateGroupWithPolicy generates a MsgCreateGroupWithPolicy with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgCreateGroupWithPolicy(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
@ -297,7 +322,7 @@ func SimulateMsgCreateGroupWithPolicy(
GroupPolicyMetadata: simtypes.RandStringOfLength(r, 10),
GroupPolicyAsAdmin: r.Float32() < 0.5,
}
msg.SetDecisionPolicy(decisionPolicy)
err = msg.SetDecisionPolicy(decisionPolicy)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "unable to set decision policy"), nil, err
}
@ -327,17 +352,29 @@ func SimulateMsgCreateGroupWithPolicy(
}
// SimulateMsgCreateGroupPolicy generates a NewMsgCreateGroupPolicy with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgCreateGroupPolicy(
cdc *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgCreateGroupPolicy(cdc, txGen, ak, bk, k, newSharedState())
}
func simulateMsgCreateGroupPolicy(
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts)
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgCreateGroupPolicy, ""), nil, err
}
@ -393,17 +430,29 @@ func SimulateMsgCreateGroupPolicy(
}
// SimulateMsgSubmitProposal generates a NewMsgSubmitProposal with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgSubmitProposal(
cdc *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgSubmitProposal(cdc, txGen, ak, bk, k, newSharedState())
}
func simulateMsgSubmitProposal(
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgSubmitProposal, ""), nil, err
}
@ -474,17 +523,28 @@ func SimulateMsgSubmitProposal(
}
// SimulateMsgUpdateGroupAdmin generates a MsgUpdateGroupAdmin with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgUpdateGroupAdmin(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgUpdateGroupAdmin(txGen, ak, bk, k, newSharedState())
}
func simulateMsgUpdateGroupAdmin(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts)
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupAdmin, ""), nil, err
}
@ -539,17 +599,28 @@ func SimulateMsgUpdateGroupAdmin(
}
// SimulateMsgUpdateGroupMetadata generates a MsgUpdateGroupMetadata with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgUpdateGroupMetadata(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgUpdateGroupMetadata(txGen, ak, bk, k, newSharedState())
}
func simulateMsgUpdateGroupMetadata(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts)
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMetadata, ""), nil, err
}
@ -595,17 +666,28 @@ func SimulateMsgUpdateGroupMetadata(
}
// SimulateMsgUpdateGroupMembers generates a MsgUpdateGroupMembers with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgUpdateGroupMembers(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgUpdateGroupMembers(txGen, ak, bk, k, newSharedState())
}
func simulateMsgUpdateGroupMembers(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts)
groupInfo, acc, account, err := randomGroup(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupMembers, ""), nil, err
}
@ -678,17 +760,28 @@ func SimulateMsgUpdateGroupMembers(
}
// SimulateMsgUpdateGroupPolicyAdmin generates a MsgUpdateGroupPolicyAdmin with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgUpdateGroupPolicyAdmin(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgUpdateGroupPolicyAdmin(txGen, ak, bk, k, newSharedState())
}
func simulateMsgUpdateGroupPolicyAdmin(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyAdmin, ""), nil, err
}
@ -742,18 +835,29 @@ func SimulateMsgUpdateGroupPolicyAdmin(
}
}
// // SimulateMsgUpdateGroupPolicyDecisionPolicy generates a NewMsgUpdateGroupPolicyDecisionPolicy with random values
// SimulateMsgUpdateGroupPolicyDecisionPolicy generates a NewMsgUpdateGroupPolicyDecisionPolicy with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgUpdateGroupPolicyDecisionPolicy(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgUpdateGroupPolicyDecisionPolicy(txGen, ak, bk, k, newSharedState())
}
func simulateMsgUpdateGroupPolicyDecisionPolicy(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyDecisionPolicy, ""), nil, err
}
@ -806,18 +910,29 @@ func SimulateMsgUpdateGroupPolicyDecisionPolicy(
}
}
// // SimulateMsgUpdateGroupPolicyMetadata generates a MsgUpdateGroupPolicyMetadata with random values
// SimulateMsgUpdateGroupPolicyMetadata generates a MsgUpdateGroupPolicyMetadata with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgUpdateGroupPolicyMetadata(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgUpdateGroupPolicyMetadata(txGen, ak, bk, k, newSharedState())
}
func simulateMsgUpdateGroupPolicyMetadata(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgUpdateGroupPolicyMetadata, ""), nil, err
}
@ -863,17 +978,29 @@ func SimulateMsgUpdateGroupPolicyMetadata(
}
// SimulateMsgWithdrawProposal generates a MsgWithdrawProposal with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgWithdrawProposal(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgWithdrawProposal(txGen, ak, bk, k, newSharedState())
}
// simulateMsgWithdrawProposal generates a MsgWithdrawProposal with random values
func simulateMsgWithdrawProposal(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgWithdrawProposal, ""), nil, err
}
@ -958,7 +1085,6 @@ func SimulateMsgWithdrawProposal(
}
_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
if err != nil {
if strings.Contains(err.Error(), "group was modified") || strings.Contains(err.Error(), "group policy was modified") {
return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "no-op:group/group-policy was modified"), nil, nil
@ -971,17 +1097,28 @@ func SimulateMsgWithdrawProposal(
}
// SimulateMsgVote generates a MsgVote with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgVote(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgVote(txGen, ak, bk, k, newSharedState())
}
func simulateMsgVote(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
g, groupPolicy, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgVote, ""), nil, err
}
@ -1066,7 +1203,6 @@ func SimulateMsgVote(
}
_, _, err = app.SimDeliver(txGen.TxEncoder(), tx)
if err != nil {
if strings.Contains(err.Error(), "group was modified") || strings.Contains(err.Error(), "group policy was modified") {
return simtypes.NoOpMsg(group.ModuleName, sdk.MsgTypeURL(msg), "no-op:group/group-policy was modified"), nil, nil
@ -1078,18 +1214,29 @@ func SimulateMsgVote(
}
}
// // SimulateMsgExec generates a MsgExec with random values
// SimulateMsgExec generates a MsgExec with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgExec(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
) simtypes.Operation {
return simulateMsgExec(txGen, ak, bk, k, newSharedState())
}
func simulateMsgExec(
txGen client.TxConfig,
ak group.AccountKeeper,
bk group.BankKeeper,
k keeper.Keeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
_, groupPolicy, acc, account, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgExec, ""), nil, err
}
@ -1159,17 +1306,28 @@ func SimulateMsgExec(
}
// SimulateMsgLeaveGroup generates a MsgLeaveGroup with random values
// Deprecated: this is an internal method and will be removed
func SimulateMsgLeaveGroup(
cdc *codec.ProtoCodec,
_ *codec.ProtoCodec,
txGen client.TxConfig,
k keeper.Keeper,
ak group.AccountKeeper,
bk group.BankKeeper,
) simtypes.Operation {
return simulateMsgLeaveGroup(txGen, k, ak, bk, newSharedState())
}
func simulateMsgLeaveGroup(
txGen client.TxConfig,
k keeper.Keeper,
ak group.AccountKeeper,
bk group.BankKeeper,
s *sharedState,
) simtypes.Operation {
return func(
r *rand.Rand, app *baseapp.BaseApp, sdkCtx sdk.Context, accounts []simtypes.Account, chainID string,
) (simtypes.OperationMsg, []simtypes.FutureOperation, error) {
groupInfo, policyInfo, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts)
groupInfo, policyInfo, _, _, err := randomGroupPolicy(r, k, ak, sdkCtx, accounts, s)
if err != nil {
return simtypes.NoOpMsg(group.ModuleName, TypeMsgLeaveGroup, ""), nil, err
}
@ -1223,20 +1381,14 @@ func SimulateMsgLeaveGroup(
}
func randomGroup(r *rand.Rand, k keeper.Keeper, ak group.AccountKeeper,
ctx sdk.Context, accounts []simtypes.Account,
ctx sdk.Context, accounts []simtypes.Account, s *sharedState,
) (groupInfo *group.GroupInfo, acc simtypes.Account, account sdk.AccountI, err error) {
groupID := k.GetGroupSequence(ctx)
switch {
case groupID > initialGroupID:
// select a random ID between (initialGroupID, groupID]
// if there is at least one group information, then the groupID at this time must be greater than or equal to 1
if initialGroupID := s.getMinGroupID(); initialGroupID == unsetGroupID {
s.setMinGroupID(groupID)
} else if initialGroupID < groupID {
groupID = uint64(simtypes.RandIntBetween(r, int(initialGroupID+1), int(groupID+1)))
default:
// This is called on the first call to this function
// in order to update the global variable
initialGroupID = groupID
}
// when groupID is 0, it proves that SimulateMsgCreateGroup has never been called. that is, no group exists in the chain
@ -1267,9 +1419,9 @@ func randomGroup(r *rand.Rand, k keeper.Keeper, ak group.AccountKeeper,
}
func randomGroupPolicy(r *rand.Rand, k keeper.Keeper, ak group.AccountKeeper,
ctx sdk.Context, accounts []simtypes.Account,
ctx sdk.Context, accounts []simtypes.Account, s *sharedState,
) (groupInfo *group.GroupInfo, groupPolicyInfo *group.GroupPolicyInfo, acc simtypes.Account, account sdk.AccountI, err error) {
groupInfo, _, _, err = randomGroup(r, k, ak, ctx, accounts)
groupInfo, _, _, err = randomGroup(r, k, ak, ctx, accounts, s)
if err != nil {
return nil, nil, simtypes.Account{}, nil, err
}
@ -1352,12 +1504,8 @@ func genGroupMembers(r *rand.Rand, accounts []simtypes.Account) []group.MemberRe
}
}
max := 5
if len(accounts) < max {
max = len(accounts)
}
membersLen := simtypes.RandIntBetween(r, 1, max)
maxMembers := min(len(accounts), 5)
membersLen := simtypes.RandIntBetween(r, 1, maxMembers)
members := make([]group.MemberRequest, membersLen)
for i := 0; i < membersLen; i++ {

View File

@ -782,7 +782,7 @@ func (suite *SimTestSuite) TestSimulateLeaveGroup() {
})
// execute operation
op := simulation.SimulateMsgLeaveGroup(codec.NewProtoCodec(suite.interfaceRegistry), suite.txConfig, suite.groupKeeper, suite.accountKeeper, suite.bankKeeper)
op := simulation.SimulateMsgLeaveGroup(nil, suite.txConfig, suite.groupKeeper, suite.accountKeeper, suite.bankKeeper)
operationMsg, futureOperations, err := op(r, suite.app.BaseApp, suite.ctx, accounts, "")
suite.Require().NoError(err)

View File

@ -2,6 +2,7 @@ package cli
import (
"flag"
"time"
"github.com/cosmos/cosmos-sdk/types/simulation"
)
@ -22,15 +23,21 @@ var (
FlagBlockSizeValue int
FlagLeanValue bool
FlagCommitValue bool
FlagOnOperationValue bool // TODO: Remove in favor of binary search for invariant violation
FlagAllInvariantsValue bool
FlagDBBackendValue string
FlagEnabledValue bool
FlagVerboseValue bool
FlagPeriodValue uint
FlagGenesisTimeValue int64
FlagSigverifyTxValue bool
FlagFauxMerkle bool
// Deprecated: This flag is unused and will be removed in a future release.
FlagPeriodValue uint
// Deprecated: This flag is unused and will be removed in a future release.
FlagEnabledValue bool
// Deprecated: This flag is unused and will be removed in a future release.
FlagOnOperationValue bool
// Deprecated: This flag is unused and will be removed in a future release.
FlagAllInvariantsValue bool
)
// GetSimulatorFlags gets the values of all the available simulation flags
@ -47,17 +54,19 @@ func GetSimulatorFlags() {
flag.IntVar(&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", false, "have the simulation commit")
flag.BoolVar(&FlagOnOperationValue, "SimulateEveryOperation", false, "run slow invariants every operation")
flag.BoolVar(&FlagAllInvariantsValue, "PrintAllInvariants", false, "print all invariants if a broken invariant is found")
flag.StringVar(&FlagDBBackendValue, "DBBackend", "goleveldb", "custom db backend type")
flag.BoolVar(&FlagCommitValue, "Commit", true, "have the simulation commit")
flag.StringVar(&FlagDBBackendValue, "DBBackend", "memdb", "custom db backend type: goleveldb, pebbledb, memdb")
// 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", 0, "override genesis UNIX time instead of using a random UNIX time")
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")
flag.UintVar(&FlagPeriodValue, "Period", 0, "This parameter is unused and will be removed")
flag.BoolVar(&FlagEnabledValue, "Enabled", false, "This parameter is unused and will be removed")
flag.BoolVar(&FlagOnOperationValue, "SimulateEveryOperation", false, "This parameter is unused and will be removed")
flag.BoolVar(&FlagAllInvariantsValue, "PrintAllInvariants", false, "This parameter is unused and will be removed")
}
// NewConfigFromFlags creates a simulation from the retrieved values of the flags.
@ -71,12 +80,28 @@ func NewConfigFromFlags() simulation.Config {
ExportStatsPath: FlagExportStatsPathValue,
Seed: FlagSeedValue,
InitialBlockHeight: FlagInitialBlockHeightValue,
GenesisTime: FlagGenesisTimeValue,
NumBlocks: FlagNumBlocksValue,
BlockSize: FlagBlockSizeValue,
Lean: FlagLeanValue,
Commit: FlagCommitValue,
OnOperation: FlagOnOperationValue,
AllInvariants: FlagAllInvariantsValue,
DBBackend: FlagDBBackendValue,
}
}
// GetDeprecatedFlagUsed return list of deprecated flag names that are being used.
// This function is for internal usage only and may be removed with the deprecated fields.
func GetDeprecatedFlagUsed() []string {
var usedFlags []string
for _, flagName := range []string{
"Enabled",
"SimulateEveryOperation",
"PrintAllInvariants",
"Period",
} {
if flag.Lookup(flagName) != nil {
usedFlags = append(usedFlags, flagName)
}
}
return usedFlags
}

View File

@ -46,70 +46,65 @@ others state execution outcome.
# Usage
Switch to `simapp/` directory:
$ cd simapp/
To execute a completely pseudo-random simulation:
$ go test -mod=readonly github.com/cosmos/cosmos-sdk/simapp \
$ go test -mod=readonly . \
-tags='sims' \
-run=TestFullAppSimulation \
-Enabled=true \
-NumBlocks=100 \
-BlockSize=200 \
-Commit=true \
-Seed=99 \
-Period=5 \
-v -timeout 24h
To execute simulation from a genesis file:
$ go test -mod=readonly github.com/cosmos/cosmos-sdk/simapp \
$ go test -mod=readonly . \
-tags='sims' \
-run=TestFullAppSimulation \
-Enabled=true \
-NumBlocks=100 \
-BlockSize=200 \
-Commit=true \
-Seed=99 \
-Period=5 \
-Genesis=/path/to/genesis.json \
-v -timeout 24h
To execute simulation from a simulation params file:
$ go test -mod=readonly github.com/cosmos/cosmos-sdk/simapp \
$ go test -mod=readonly . \
-tags='sims' \
-run=TestFullAppSimulation \
-Enabled=true \
-NumBlocks=100 \
-BlockSize=200 \
-Commit=true \
-Seed=99 \
-Period=5 \
-Params=/path/to/params.json \
-v -timeout 24h
To export the simulation params to a file at a given block height:
$ go test -mod=readonly github.com/cosmos/cosmos-sdk/simapp \
$ go test -mod=readonly . \
-tags='sims' \
-run=TestFullAppSimulation \
-Enabled=true \
-NumBlocks=100 \
-BlockSize=200 \
-Commit=true \
-Seed=99 \
-Period=5 \
-ExportParamsPath=/path/to/params.json \
-ExportParamsHeight=50 \
-v -timeout 24h
-v -timeout 24h
To export the simulation app state (i.e genesis) to a file:
$ go test -mod=readonly github.com/cosmos/cosmos-sdk/simapp \
$ go test -mod=readonly . \
-tags='sims' \
-run=TestFullAppSimulation \
-Enabled=true \
-NumBlocks=100 \
-BlockSize=200 \
-Commit=true \
-Seed=99 \
-Period=5 \
-ExportStatePath=/path/to/genesis.json \
v -timeout 24h
-v -timeout 24h
# Params

View File

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path"
"sync"
"time"
)
@ -24,7 +25,11 @@ func NewLogWriter(testingmode bool) LogWriter {
// log writter
type StandardLogWriter struct {
Seed int64
OpEntries []OperationEntry `json:"op_entries" yaml:"op_entries"`
wMtx sync.Mutex
written bool
}
// add an entry to the log writter
@ -34,7 +39,12 @@ func (lw *StandardLogWriter) AddEntry(opEntry OperationEntry) {
// PrintLogs - print the logs to a simulation file
func (lw *StandardLogWriter) PrintLogs() {
f := createLogFile()
lw.wMtx.Lock()
defer lw.wMtx.Unlock()
if lw.written { // print once only
return
}
f := createLogFile(lw.Seed)
defer f.Close()
for i := 0; i < len(lw.OpEntries); i++ {
@ -44,12 +54,16 @@ func (lw *StandardLogWriter) PrintLogs() {
panic("Failed to write logs to file")
}
}
lw.written = true
}
func createLogFile() *os.File {
func createLogFile(seed int64) *os.File {
var f *os.File
fileName := fmt.Sprintf("%d.log", time.Now().UnixMilli())
var prefix string
if seed != 0 {
prefix = fmt.Sprintf("seed_%10d", seed)
}
fileName := fmt.Sprintf("%s--%d.log", prefix, time.Now().UnixNano())
folderPath := path.Join(os.ExpandEnv("$HOME"), ".simapp", "simulations")
filePath := path.Join(folderPath, fileName)

View File

@ -2,7 +2,6 @@ package simulation
import (
"encoding/json"
"fmt"
"math/rand"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
@ -197,12 +196,5 @@ func randomConsensusParams(r *rand.Rand, appState json.RawMessage, cdc codec.JSO
MaxAgeDuration: stakingGenesisState.Params.UnbondingTime,
},
}
bz, err := json.MarshalIndent(&consensusParams, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("Selected randomly generated consensus parameters:\n%s\n", bz)
return consensusParams
}

View File

@ -1,18 +1,21 @@
package simulation
import (
"bytes"
"encoding/binary"
"encoding/hex"
"fmt"
"io"
"math/rand"
"os"
"os/signal"
"syscall"
"testing"
"time"
abci "github.com/cometbft/cometbft/abci/types"
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
"cosmossdk.io/core/header"
"cosmossdk.io/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -65,14 +68,37 @@ func SimulateFromSeed(
config simulation.Config,
cdc codec.JSONCodec,
) (stopEarly bool, exportedParams Params, err error) {
tb.Helper()
mode, _, _ := getTestingMode(tb)
expParams, err := SimulateFromSeedX(tb, log.NewTestLogger(tb), w, app, appStateFn, randAccFn, ops, blockedAddrs, config, cdc, NewLogWriter(mode))
return false, expParams, err
}
// SimulateFromSeedX tests an application by running the provided
// operations, testing the provided invariants, but using the provided config.Seed.
func SimulateFromSeedX(
tb testing.TB,
logger log.Logger,
w io.Writer,
app *baseapp.BaseApp,
appStateFn simulation.AppStateFn,
randAccFn simulation.RandomAccountFn,
ops WeightedOperations,
blockedAddrs map[string]bool,
config simulation.Config,
cdc codec.JSONCodec,
logWriter LogWriter,
) (exportedParams Params, err error) {
tb.Helper()
// in case we have to end early, don't os.Exit so that we can run cleanup code.
testingMode, _, b := getTestingMode(tb)
r := rand.New(rand.NewSource(config.Seed))
r := rand.New(newByteSource(config.FuzzSeed, config.Seed))
params := RandomParams(r)
fmt.Fprintf(w, "Starting SimulateFromSeed with randomness created with seed %d\n", int(config.Seed))
fmt.Fprintf(w, "Randomized simulation params: \n%s\n", mustMarshalJSONIndent(params))
startTime := time.Now()
logger.Info("Starting SimulateFromSeed with randomness", "time", startTime)
logger.Debug("Randomized simulation setup", "params", mustMarshalJSONIndent(params))
timeDiff := maxTimePerBlock - minTimePerBlock
accs := randAccFn(r, params.NumKeys())
@ -84,16 +110,11 @@ func SimulateFromSeed(
// At least 2 accounts must be added here, otherwise when executing SimulateMsgSend
// two accounts will be selected to meet the conditions from != to and it will fall into an infinite loop.
if len(accs) <= 1 {
return true, params, fmt.Errorf("at least two genesis accounts are required")
return params, fmt.Errorf("at least two genesis accounts are required")
}
config.ChainID = chainID
fmt.Printf(
"Starting the simulation from time %v (unixtime %v)\n",
blockTime.UTC().Format(time.UnixDate), blockTime.Unix(),
)
// remove module account address if they exist in accs
var tmpAccs []simulation.Account
@ -107,7 +128,7 @@ func SimulateFromSeed(
nextValidators := validators
if len(nextValidators) == 0 {
tb.Skip("skipping: empty validator set in genesis")
return true, params, nil
return params, nil
}
var (
@ -120,17 +141,6 @@ func SimulateFromSeed(
opCount = 0
)
// Setup code to catch SIGTERM's
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
go func() {
receivedSignal := <-c
fmt.Fprintf(w, "\nExiting early due to %s, on block %d, operation %d\n", receivedSignal, blockHeight, opCount)
err = fmt.Errorf("exited due to %s", receivedSignal)
stopEarly = true
}()
finalizeBlockReq := RandomRequestFinalizeBlock(
r,
params,
@ -145,11 +155,10 @@ func SimulateFromSeed(
// These are operations which have been queued by previous operations
operationQueue := NewOperationQueue()
logWriter := NewLogWriter(testingMode)
blockSimulator := createBlockSimulator(
testingMode,
tb,
testingMode,
w,
params,
eventStats.Tally,
@ -166,7 +175,7 @@ func SimulateFromSeed(
// recover logs in case of panic
defer func() {
if r := recover(); r != nil {
_, _ = fmt.Fprintf(w, "simulation halted due to panic on block %d\n", blockHeight)
logger.Error("simulation halted due to panic", "height", blockHeight)
logWriter.PrintLogs()
panic(r)
}
@ -178,7 +187,7 @@ func SimulateFromSeed(
exportedParams = params
}
for blockHeight < int64(config.NumBlocks+config.InitialBlockHeight) && !stopEarly {
for blockHeight < int64(config.NumBlocks+config.InitialBlockHeight) {
pastTimes = append(pastTimes, blockTime)
pastVoteInfos = append(pastVoteInfos, finalizeBlockReq.DecidedLastCommit.Votes)
@ -187,7 +196,7 @@ func SimulateFromSeed(
res, err := app.FinalizeBlock(finalizeBlockReq)
if err != nil {
return true, params, err
return params, fmt.Errorf("block finalization failed at height %d: %w", blockHeight, err)
}
ctx := app.NewContextLegacy(false, cmtproto.Header{
@ -195,17 +204,21 @@ func SimulateFromSeed(
Time: blockTime,
ProposerAddress: proposerAddress,
ChainID: config.ChainID,
}).WithHeaderInfo(header.Info{
Height: blockHeight,
Time: blockTime,
ChainID: config.ChainID,
})
// run queued operations; ignores block size if block size is too small
numQueuedOpsRan, futureOps := runQueuedOperations(
operationQueue, int(blockHeight), tb, r, app, ctx, accs, logWriter,
tb, operationQueue, blockTime, int(blockHeight), r, app, ctx, accs, logWriter,
eventStats.Tally, config.Lean, config.ChainID,
)
numQueuedTimeOpsRan, timeFutureOps := runQueuedTimeOperations(
numQueuedTimeOpsRan, timeFutureOps := runQueuedTimeOperations(tb,
timeOperationQueue, int(blockHeight), blockTime,
tb, r, app, ctx, accs, logWriter, eventStats.Tally,
r, app, ctx, accs, logWriter, eventStats.Tally,
config.Lean, config.ChainID,
)
@ -223,19 +236,20 @@ func SimulateFromSeed(
blockHeight++
logWriter.AddEntry(EndBlockEntry(blockHeight))
blockTime = blockTime.Add(time.Duration(minTimePerBlock) * time.Second)
blockTime = blockTime.Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second)
proposerAddress = validators.randomProposer(r)
logWriter.AddEntry(EndBlockEntry(blockHeight))
if config.Commit {
app.Commit()
if _, err := app.Commit(); err != nil {
return params, fmt.Errorf("commit failed at height %d: %w", blockHeight, err)
}
}
if proposerAddress == nil {
fmt.Fprintf(w, "\nSimulation stopped early as all validators have been unbonded; nobody left to propose a block!\n")
stopEarly = true
logger.Info("Simulation stopped early as all validators have been unbonded; nobody left to propose a block", "height", blockHeight)
break
}
@ -249,7 +263,7 @@ func SimulateFromSeed(
nextValidators = updateValidators(tb, r, params, validators, res.ValidatorUpdates, eventStats.Tally)
if len(nextValidators) == 0 {
tb.Skip("skipping: empty validator set")
return true, params, nil
return params, nil
}
// update the exported params
@ -258,22 +272,8 @@ func SimulateFromSeed(
}
}
if stopEarly {
if config.ExportStatsPath != "" {
fmt.Println("Exporting simulation statistics...")
eventStats.ExportJSON(config.ExportStatsPath)
} else {
eventStats.Print(w)
}
return true, exportedParams, err
}
fmt.Fprintf(
w,
"\nSimulation complete; Final height (blocks): %d, final time (seconds): %v, operations ran: %d\n",
blockHeight, blockTime, opCount,
)
logger.Info("Simulation complete", "height", blockHeight, "block-time", blockTime, "opsCount", opCount,
"run-time", time.Since(startTime), "app-hash", hex.EncodeToString(app.LastCommitID().Hash))
if config.ExportStatsPath != "" {
fmt.Println("Exporting simulation statistics...")
@ -281,8 +281,7 @@ func SimulateFromSeed(
} else {
eventStats.Print(w)
}
return false, exportedParams, nil
return exportedParams, err
}
type blockSimFn func(
@ -294,12 +293,13 @@ type blockSimFn func(
) (opCount int)
// Returns a function to simulate blocks. Written like this to avoid constant
// parameters being passed everytime, to minimize memory overhead.
func createBlockSimulator(testingMode bool, tb testing.TB, w io.Writer, params Params,
// parameters being passed every time, to minimize memory overhead.
func createBlockSimulator(tb testing.TB, printProgress bool, w io.Writer, params Params,
event func(route, op, evResult string), ops WeightedOperations,
operationQueue OperationQueue, timeOperationQueue []simulation.FutureOperation,
logWriter LogWriter, config simulation.Config,
) blockSimFn {
tb.Helper()
lastBlockSizeState := 0 // state for [4 * uniform distribution]
blocksize := 0
selectOp := ops.getSelectOpFn()
@ -325,7 +325,7 @@ func createBlockSimulator(testingMode bool, tb testing.TB, w io.Writer, params P
for i := 0; i < blocksize; i++ {
opAndRz = append(opAndRz, opAndR{
op: selectOp(r),
rand: simulation.DeriveRand(r),
rand: r,
})
}
@ -350,8 +350,8 @@ Comment: %s`,
queueOperations(operationQueue, timeOperationQueue, futureOps)
if testingMode && opCount%50 == 0 {
fmt.Fprintf(w, "\rSimulating... block %d/%d, operation %d/%d. ",
if printProgress && opCount%50 == 0 {
_, _ = fmt.Fprintf(w, "\rSimulating... block %d/%d, operation %d/%d. ",
header.Height, config.NumBlocks, opCount, blocksize)
}
@ -362,11 +362,21 @@ Comment: %s`,
}
}
func runQueuedOperations(queueOps map[int][]simulation.Operation,
height int, tb testing.TB, r *rand.Rand, app *baseapp.BaseApp,
ctx sdk.Context, accounts []simulation.Account, logWriter LogWriter,
event func(route, op, evResult string), lean bool, chainID string,
func runQueuedOperations(
tb testing.TB,
queueOps map[int][]simulation.Operation,
blockTime time.Time,
height int,
r *rand.Rand,
app *baseapp.BaseApp,
ctx sdk.Context,
accounts []simulation.Account,
logWriter LogWriter,
event func(route, op, evResult string),
lean bool,
chainID string,
) (numOpsRan int, allFutureOps []simulation.FutureOperation) {
tb.Helper()
queuedOp, ok := queueOps[height]
if !ok {
return 0, nil
@ -398,12 +408,13 @@ func runQueuedOperations(queueOps map[int][]simulation.Operation,
return numOpsRan, allFutureOps
}
func runQueuedTimeOperations(queueOps []simulation.FutureOperation,
height int, currentTime time.Time, tb testing.TB, r *rand.Rand,
func runQueuedTimeOperations(tb testing.TB, queueOps []simulation.FutureOperation,
height int, currentTime time.Time, r *rand.Rand,
app *baseapp.BaseApp, ctx sdk.Context, accounts []simulation.Account,
logWriter LogWriter, event func(route, op, evResult string),
lean bool, chainID string,
) (numOpsRan int, allFutureOps []simulation.FutureOperation) {
tb.Helper()
// Keep all future operations
allFutureOps = make([]simulation.FutureOperation, 0)
@ -432,3 +443,41 @@ func runQueuedTimeOperations(queueOps []simulation.FutureOperation,
return numOpsRan, allFutureOps
}
const (
rngMax = 1 << 63
rngMask = rngMax - 1
)
// byteSource offers deterministic pseudo-random numbers for math.Rand with fuzzer support.
// The 'seed' data is read in big endian to uint64. When exhausted,
// it falls back to a standard random number generator initialized with a specific 'seed' value.
type byteSource struct {
seed *bytes.Reader
fallback *rand.Rand
}
// newByteSource creates a new byteSource with a specified byte slice and seed. This gives a fixed sequence of pseudo-random numbers.
// Initially, it utilizes the byte slice. Once that's exhausted, it continues generating numbers using the provided seed.
func newByteSource(fuzzSeed []byte, seed int64) *byteSource {
return &byteSource{
seed: bytes.NewReader(fuzzSeed),
fallback: rand.New(rand.NewSource(seed)),
}
}
func (s *byteSource) Uint64() uint64 {
if s.seed.Len() < 8 {
return s.fallback.Uint64()
}
var b [8]byte
if _, err := s.seed.Read(b[:]); err != nil && err != io.EOF {
panic(err) // Should not happen.
}
return binary.BigEndian.Uint64(b[:])
}
func (s *byteSource) Int63() int64 {
return int64(s.Uint64() & rngMask)
}
func (s *byteSource) Seed(seed int64) {}