Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(sims): TestAppSimulationAfterImport and legacy proposal handling #21800

Merged
merged 3 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions scripts/build/simulations.mk
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ test-sim-multi-seed-short:
@cd ${CURRENT_DIR}/simapp && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestFullAppSimulation \
-NumBlocks=50 -Period=10 -FauxMerkle=true

test-v2-sim-wip:
@echo "Running v2 simapp. This may take awhile!"
@cd ${CURRENT_DIR}/simapp/v2 && go test -failfast -mod=readonly -timeout 30m -tags='sims' -run TestSimsAppV2

test-sim-benchmark-invariants:
@echo "Running simulation invariant benchmarks..."
Expand Down
20 changes: 12 additions & 8 deletions simapp/sim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ var (
)

func TestAppImportExport(t *testing.T) {
simsx.Run(t, NewSimApp, setupStateFactory, func(t testing.TB, ti simsx.TestInstance[*SimApp]) {
simsx.Run(t, NewSimApp, setupStateFactory, func(t testing.TB, ti simsx.TestInstance[*SimApp], _ []simtypes.Account) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the accs parameter to initialize the application state.

The accs parameter has been added to the function signature but is not being used within the function. Consider using it to initialize the application state to ensure consistency during simulation.

app := ti.App
t.Log("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules)
Expand Down Expand Up @@ -116,35 +116,39 @@ func TestAppImportExport(t *testing.T) {
// set up a new node instance, Init chain from exported genesis
// run new instance for n blocks
func TestAppSimulationAfterImport(t *testing.T) {
simsx.Run(t, NewSimApp, setupStateFactory, func(t testing.TB, ti simsx.TestInstance[*SimApp]) {
simsx.Run(t, NewSimApp, setupStateFactory, func(t testing.TB, ti simsx.TestInstance[*SimApp], accs []simtypes.Account) {
app := ti.App
t.Log("exporting genesis...\n")
exported, err := app.ExportAppStateAndValidators(false, exportWithValidatorSet, exportAllModules)
require.NoError(t, err)

t.Log("importing genesis...\n")
importGenesisStateFactory := func(app *SimApp) simsx.SimStateFactory {
return simsx.SimStateFactory{
Codec: app.AppCodec(),
AppStateFn: func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config) (json.RawMessage, []simtypes.Account, string, time.Time) {
AppStateFn: func(r *rand.Rand, _ []simtypes.Account, config simtypes.Config) (json.RawMessage, []simtypes.Account, string, time.Time) {
t.Log("importing genesis...\n")
genesisTimestamp := time.Unix(config.GenesisTime, 0)

_, err = app.InitChain(&abci.InitChainRequest{
AppStateBytes: exported.AppState,
ChainId: simsx.SimAppChainID,
InitialHeight: exported.Height,
Time: genesisTimestamp,
})
if IsEmptyValidatorSetErr(err) {
t.Skip("Skipping simulation as all validators have been unbonded")
return nil, nil, "", time.Time{}
}
acc, err := simtestutil.AccountsFromAppState(app.AppCodec(), exported.AppState)
require.NoError(t, err)
genesisTimestamp := time.Unix(config.GenesisTime, 0)
return exported.AppState, acc, config.ChainID, genesisTimestamp
// use accounts from initial run
return exported.AppState, accs, config.ChainID, genesisTimestamp
},
BlockedAddr: must(BlockedAddresses(app.AuthKeeper.AddressCodec())),
AccountSource: app.AuthKeeper,
BalanceSource: app.BankKeeper,
}
}
ti.Cfg.InitialBlockHeight = int(exported.Height)
simsx.RunWithSeed(t, ti.Cfg, NewSimApp, importGenesisStateFactory, ti.Cfg.Seed, ti.Cfg.FuzzSeed)
})
}
Expand Down Expand Up @@ -191,7 +195,7 @@ func TestAppStateDeterminism(t *testing.T) {
var mx sync.Mutex
appHashResults := make(map[int64][][]byte)
appSimLogger := make(map[int64][]simulation.LogWriter)
captureAndCheckHash := func(t testing.TB, ti simsx.TestInstance[*SimApp]) {
captureAndCheckHash := func(t testing.TB, ti simsx.TestInstance[*SimApp], _ []simtypes.Account) {
seed, appHash := ti.Cfg.Seed, ti.App.LastCommitID().Hash
mx.Lock()
otherHashes, execWriters := appHashResults[seed], appSimLogger[seed]
Expand Down
3 changes: 3 additions & 0 deletions simsx/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,9 @@ func NewChainDataSource(
for i, a := range oldSimAcc {
acc[i] = SimAccount{Account: a, r: r, bank: bank}
index[a.AddressBech32] = i
if a.AddressBech32 == "" {
panic("test account has empty bech32 address")
}
}
return &ChainDataSource{
r: r,
Expand Down
70 changes: 66 additions & 4 deletions simsx/registry.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package simsx

import (
"cmp"
"context"
"iter"
"maps"
Expand Down Expand Up @@ -36,7 +37,7 @@ type (
)

// WeightedProposalMsgIter iterator for weighted gov proposal payload messages
type WeightedProposalMsgIter = iter.Seq2[uint32, SimMsgFactoryX]
type WeightedProposalMsgIter = iter.Seq2[uint32, FactoryMethod]

var _ Registry = &WeightedOperationRegistryAdapter{}

Expand Down Expand Up @@ -108,6 +109,7 @@ type HasFutureOpsRegistry interface {
SetFutureOpsRegistry(FutureOpsRegistry)
}

// msg factory to legacy Operation type
func legacyOperationAdapter(l regCommon, fx SimMsgFactoryX) simtypes.Operation {
return func(
r *rand.Rand, app AppEntrypoint, ctx sdk.Context,
Expand Down Expand Up @@ -167,15 +169,15 @@ func (s UniqueTypeRegistry) Add(weight uint32, f SimMsgFactoryX) {
}

// Iterator returns an iterator function for a Go for loop sorted by weight desc.
func (s UniqueTypeRegistry) Iterator() iter.Seq2[uint32, SimMsgFactoryX] {
func (s UniqueTypeRegistry) Iterator() WeightedProposalMsgIter {
x := maps.Values(s)
sortedWeightedFactory := slices.SortedFunc(x, func(a, b WeightedFactory) int {
return a.Compare(b)
})

return func(yield func(uint32, SimMsgFactoryX) bool) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, but can we add godocs to SimMsgFactoryX

return func(yield func(uint32, FactoryMethod) bool) {
for _, v := range sortedWeightedFactory {
if !yield(v.Weight, v.Factory) {
if !yield(v.Weight, v.Factory.Create()) {
return
}
}
Expand Down Expand Up @@ -204,3 +206,63 @@ func (f WeightedFactory) Compare(b WeightedFactory) int {
return strings.Compare(sdk.MsgTypeURL(f.Factory.MsgType()), sdk.MsgTypeURL(b.Factory.MsgType()))
}
}

// WeightedFactoryMethod is a data tuple used for registering legacy proposal operations
type WeightedFactoryMethod struct {
Weight uint32
Factory FactoryMethod
}

type WeightedFactoryMethods []WeightedFactoryMethod

// NewWeightedFactoryMethods constructor
func NewWeightedFactoryMethods() WeightedFactoryMethods {
return make(WeightedFactoryMethods, 0)
}

// Add adds a new WeightedFactoryMethod to the WeightedFactoryMethods slice.
// If weight is zero or f is nil, it returns without making any changes.
func (s *WeightedFactoryMethods) Add(weight uint32, f FactoryMethod) {
if weight == 0 {
return
}
if f == nil {
panic("message factory must not be nil")
}
*s = append(*s, WeightedFactoryMethod{Weight: weight, Factory: f})
}

// Iterator returns an iterator function for a Go for loop sorted by weight desc.
func (s WeightedFactoryMethods) Iterator() WeightedProposalMsgIter {
slices.SortFunc(s, func(e, e2 WeightedFactoryMethod) int {
return cmp.Compare(e.Weight, e2.Weight)
})
return func(yield func(uint32, FactoryMethod) bool) {
for _, v := range s {
if !yield(v.Weight, v.Factory) {
return
}
}
}
}

// legacy operation to Msg factory type
func legacyToMsgFactoryAdapter(fn simtypes.MsgSimulatorFnX) FactoryMethod {
return func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg) {
msg, err := fn(ctx, testData.r, testData.AllAccounts(), testData.AddressCodec())
if err != nil {
reporter.Skip(err.Error())
return nil, nil
}
return []SimAccount{}, msg
}
}

// AppendIterators takes multiple WeightedProposalMsgIter and returns a single iterator that sequentially yields items after each one.
func AppendIterators(iterators ...WeightedProposalMsgIter) WeightedProposalMsgIter {
return func(yield func(uint32, FactoryMethod) bool) {
for _, it := range iterators {
it(yield)
}
}
}
64 changes: 55 additions & 9 deletions simsx/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,20 +116,21 @@ func TestSimsMsgRegistryAdapter(t *testing.T) {
}

func TestUniqueTypeRegistry(t *testing.T) {
f1 := SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) {
exampleFactory := SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) {
return []SimAccount{}, nil
})

specs := map[string]struct {
src []WeightedFactory
exp []WeightedFactory
exp []WeightedFactoryMethod
expErr bool
}{
"unique": {
src: []WeightedFactory{{Weight: 1, Factory: f1}},
exp: []WeightedFactory{{Weight: 1, Factory: f1}},
src: []WeightedFactory{{Weight: 1, Factory: exampleFactory}},
exp: []WeightedFactoryMethod{{Weight: 1, Factory: exampleFactory.Create()}},
},
"duplicate": {
src: []WeightedFactory{{Weight: 1, Factory: f1}, {Weight: 2, Factory: f1}},
src: []WeightedFactory{{Weight: 1, Factory: exampleFactory}, {Weight: 2, Factory: exampleFactory}},
expErr: true,
},
}
Expand All @@ -148,11 +149,56 @@ func TestUniqueTypeRegistry(t *testing.T) {
reg.Add(v.Weight, v.Factory)
}
// then
var got []WeightedFactory
for w, f := range reg.Iterator() {
got = append(got, WeightedFactory{Weight: w, Factory: f})
}
got := readAll(reg.Iterator())
require.Len(t, got, len(spec.exp))
})
}
}

func TestWeightedFactories(t *testing.T) {
r := NewWeightedFactoryMethods()
f1 := func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg) {
panic("not implemented")
}
f2 := func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg) {
panic("not implemented")
}
r.Add(1, f1)
r.Add(2, f2)
got := readAll(r.Iterator())
require.Len(t, got, 2)

assert.Equal(t, uint32(1), r[0].Weight)
assert.Equal(t, uint32(2), r[1].Weight)
}

func TestAppendIterators(t *testing.T) {
r1 := NewWeightedFactoryMethods()
r1.Add(2, func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg) {
panic("not implemented")
})
r1.Add(2, func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg) {
panic("not implemented")
})
r1.Add(3, func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg sdk.Msg) {
panic("not implemented")
})
r2 := NewUniqueTypeRegistry()
r2.Add(1, SimMsgFactoryFn[*testdata.TestMsg](func(ctx context.Context, testData *ChainDataSource, reporter SimulationReporter) (signer []SimAccount, msg *testdata.TestMsg) {
panic("not implemented")
}))
// when
all := readAll(AppendIterators(r1.Iterator(), r2.Iterator()))
// then
require.Len(t, all, 4)
gotWeights := Collect(all, func(a WeightedFactoryMethod) uint32 { return a.Weight })
assert.Equal(t, []uint32{2, 2, 3, 1}, gotWeights)
}

func readAll(iterator WeightedProposalMsgIter) []WeightedFactoryMethod {
var ret []WeightedFactoryMethod
for w, f := range iterator {
ret = append(ret, WeightedFactoryMethod{Weight: w, Factory: f})
}
return ret
}
3 changes: 3 additions & 0 deletions simsx/reporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,9 @@ func (s *ExecutionSummary) String() string {
for _, key := range keys {
sb.WriteString(fmt.Sprintf("%s: %d\n", key, s.counts[key]))
}
if len(s.skipReasons) != 0 {
sb.WriteString("\nSkip reasons:\n")
}
for m, c := range s.skipReasons {
values := maps.Values(c)
keys := maps.Keys(c)
Expand Down
29 changes: 12 additions & 17 deletions simsx/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"encoding/json"
"fmt"
"io"
"iter"
"os"
"path/filepath"
"testing"
Expand Down Expand Up @@ -82,7 +81,7 @@ func Run[T SimulationApp](
baseAppOptions ...func(*baseapp.BaseApp),
) T,
setupStateFactory func(app T) SimStateFactory,
postRunActions ...func(t testing.TB, app TestInstance[T]),
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
t.Helper()
RunWithSeeds(t, appFactory, setupStateFactory, defaultSeeds, nil, postRunActions...)
Expand Down Expand Up @@ -110,7 +109,7 @@ func RunWithSeeds[T SimulationApp](
setupStateFactory func(app T) SimStateFactory,
seeds []int64,
fuzzSeed []byte,
postRunActions ...func(t testing.TB, app TestInstance[T]),
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
t.Helper()
cfg := cli.NewConfigFromFlags()
Expand All @@ -137,7 +136,7 @@ func RunWithSeed[T SimulationApp](
setupStateFactory func(app T) SimStateFactory,
seed int64,
fuzzSeed []byte,
postRunActions ...func(t testing.TB, app TestInstance[T]),
postRunActions ...func(t testing.TB, app TestInstance[T], accs []simtypes.Account),
) {
tb.Helper()
// setup environment
Expand All @@ -154,7 +153,7 @@ func RunWithSeed[T SimulationApp](
app := testInstance.App
stateFactory := setupStateFactory(app)
ops, reporter := prepareWeightedOps(app.SimulationManager(), stateFactory, tCfg, testInstance.App.TxConfig(), runLogger)
simParams, err := simulation.SimulateFromSeedX(tb, runLogger, WriteToDebugLog(runLogger), app.GetBaseApp(), stateFactory.AppStateFn, simtypes.RandomAccounts, ops, stateFactory.BlockedAddr, tCfg, stateFactory.Codec, testInstance.ExecLogWriter)
simParams, accs, err := simulation.SimulateFromSeedX(tb, runLogger, WriteToDebugLog(runLogger), app.GetBaseApp(), stateFactory.AppStateFn, simtypes.RandomAccounts, ops, stateFactory.BlockedAddr, tCfg, stateFactory.Codec, testInstance.ExecLogWriter)
require.NoError(tb, err)
err = simtestutil.CheckExportSimulation(app, tCfg, simParams)
require.NoError(tb, err)
Expand All @@ -164,7 +163,7 @@ func RunWithSeed[T SimulationApp](
// not using tb.Log to always print the summary
fmt.Printf("+++ DONE (seed: %d): \n%s\n", seed, reporter.Summary().String())
for _, step := range postRunActions {
step(tb, testInstance)
step(tb, testInstance, accs)
}
require.NoError(tb, app.Close())
}
Expand All @@ -174,7 +173,7 @@ type (
WeightedOperationsX(weight WeightSource, reg Registry)
}
HasWeightedOperationsXWithProposals interface {
WeightedOperationsX(weights WeightSource, reg Registry, proposals iter.Seq2[uint32, SimMsgFactoryX],
WeightedOperationsX(weights WeightSource, reg Registry, proposals WeightedProposalMsgIter,
legacyProposals []simtypes.WeightedProposalContent) //nolint: staticcheck // used for legacy proposal types
}
HasProposalMsgsX interface {
Expand Down Expand Up @@ -254,26 +253,22 @@ func prepareWeightedOps(
reporter := NewBasicSimulationReporter()

pReg := make(UniqueTypeRegistry)
wProps := make([]simtypes.WeightedProposalMsg, 0, len(sm.Modules))
wContent := make([]simtypes.WeightedProposalContent, 0) //nolint:staticcheck // required for legacy type

legacyPReg := NewWeightedFactoryMethods()
// add gov proposals types
for _, m := range sm.Modules {
switch xm := m.(type) {
case HasProposalMsgsX:
xm.ProposalMsgsX(weights, pReg)
case HasLegacyProposalMsgs:
wProps = append(wProps, xm.ProposalMsgs(simState)...)
for _, p := range xm.ProposalMsgs(simState) {
weight := weights.Get(p.AppParamsKey(), uint32(p.DefaultWeight()))
legacyPReg.Add(weight, legacyToMsgFactoryAdapter(p.MsgSimulatorFn()))
}
case HasLegacyProposalContents:
wContent = append(wContent, xm.ProposalContents(simState)...)
}
}
if len(wProps) != 0 {
panic("legacy proposals are not empty")
}
if len(wContent) != 0 {
panic("legacy proposal contents are not empty")
}

oReg := NewSimsMsgRegistryAdapter(reporter, stateFact.AccountSource, stateFact.BalanceSource, txConfig, logger)
wOps := make([]simtypes.WeightedOperation, 0, len(sm.Modules))
Expand All @@ -283,7 +278,7 @@ func prepareWeightedOps(
case HasWeightedOperationsX:
xm.WeightedOperationsX(weights, oReg)
case HasWeightedOperationsXWithProposals:
xm.WeightedOperationsX(weights, oReg, pReg.Iterator(), nil)
xm.WeightedOperationsX(weights, oReg, AppendIterators(legacyPReg.Iterator(), pReg.Iterator()), wContent)
case HasLegacyWeightedOperations:
wOps = append(wOps, xm.WeightedOperations(simState)...)
}
Expand Down
Loading
Loading