Skip to content

Commit

Permalink
feat(sim): implement the beginning of a replacement simulator
Browse files Browse the repository at this point in the history
This change implements a replacement for the current simulator based
on testutil/network. Most of the changes are porting the module specific
message generators to no longer rely on SimulationState, and to generate
"real" messages, not simulator messages. The simulator driver is in
simapp, as part of the IntegationTestSuite.

The new approach aims to improve simulation in two important ways:

- Simulation should more closely mimic a real network. The current
simulator message delivery is implemented parallel to non-simulator
message delivery, leading to loss of fidelity and higher maintenance.
One symptom is cosmos#13843.
- Simulation should be layered on top of modules, not part of modules.
This means that modules should not import simulation packages, nor refer
to its generator package (x/module/simulation). This should eventually
fix cosmos#7622.

There are also downsides, however. Where the current simulator is too
high level, testutil/network is too low level: it runs a real network
of validators which is difficult to control. For example:

- AppHashes differ between runs, because modules may depend on non-
deterministic state such as block header timestamps.
- The validators runs in separate goroutines, which makes it hard to
query app state without introducing race conditions.
- Blocks are produced according tot time, and not under control by the
test driver. This makes it hard to trigger processing of messages in
particular blocks, which ruins determinism.

Some of the issues may be worked around, for example by forcing the
block headers to be deterministic; however, the real fix is to make
testutil/network itself deterministic, providing the goldilock level
of simulation: close enough to a real network, yet deterministic enough
to generate the same chain state for a given random seed.

A deterministic testutil/network is part of cosmos#18145.

Future work includes:

- Porting of the remaining module message generators.
- Generating (and verifying) deterministic AppHashes, allowing reliable
replay when a problematic message is detected. Depends on cosmos#18145.
- Save/reload of state for faster debugging cycles.
- Removal of the old simulator, most importantly the reference to it from
 module code.

 Updates cosmos#14753 (Simulator rewrite epic)
 Updates cosmos#7622 (reducing imports from modules to simulator)
 Updates cosmos#13843 (using real message delivery for simulation)
  • Loading branch information
elias-orijtech committed Nov 10, 2023
1 parent 9411747 commit dc967dd
Show file tree
Hide file tree
Showing 20 changed files with 516 additions and 1,073 deletions.
12 changes: 2 additions & 10 deletions simapp/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
authcodec "cosmossdk.io/x/auth/codec"
authkeeper "cosmossdk.io/x/auth/keeper"
"cosmossdk.io/x/auth/posthandler"
authsims "cosmossdk.io/x/auth/simulation"
authtx "cosmossdk.io/x/auth/tx"
txmodule "cosmossdk.io/x/auth/tx/config"
authtypes "cosmossdk.io/x/auth/types"
Expand Down Expand Up @@ -405,7 +404,7 @@ func NewSimApp(
txConfig,
),
accounts.NewAppModule(app.AccountsKeeper),
auth.NewAppModule(appCodec, app.AuthKeeper, authsims.RandomGenesisAccounts),
auth.NewAppModule(appCodec, app.AuthKeeper),
vesting.NewAppModule(app.AuthKeeper, app.BankKeeper),
bank.NewAppModule(appCodec, app.BankKeeper, app.AuthKeeper),
feegrantmodule.NewAppModule(appCodec, app.AuthKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry),
Expand Down Expand Up @@ -502,14 +501,7 @@ func NewSimApp(
// add test gRPC service for testing gRPC queries in isolation
testdata_pulsar.RegisterQueryServer(app.GRPCQueryRouter(), testdata_pulsar.QueryImpl{})

// create the simulation manager and define the order of the modules for deterministic simulations
//
// NOTE: this is not required apps that don't use the simulator for fuzz testing
// transactions
overrideModules := map[string]module.AppModuleSimulation{
authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AuthKeeper, authsims.RandomGenesisAccounts),
}
app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules)
app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, nil)

app.sm.RegisterStoreDecoders()

Expand Down
12 changes: 1 addition & 11 deletions simapp/app_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ import (
"cosmossdk.io/depinject"
"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
authtypes "cosmossdk.io/x/auth/types"
authzkeeper "cosmossdk.io/x/authz/keeper"
bankkeeper "cosmossdk.io/x/bank/keeper"
circuitkeeper "cosmossdk.io/x/circuit/keeper"
Expand Down Expand Up @@ -234,14 +231,7 @@ func NewSimApp(
// add test gRPC service for testing gRPC queries in isolation
testdata_pulsar.RegisterQueryServer(app.GRPCQueryRouter(), testdata_pulsar.QueryImpl{})

// create the simulation manager and define the order of the modules for deterministic simulations
//
// NOTE: this is not required apps that don't use the simulator for fuzz testing
// transactions
overrideModules := map[string]module.AppModuleSimulation{
authtypes.ModuleName: auth.NewAppModule(app.appCodec, app.AuthKeeper, authsims.RandomGenesisAccounts),
}
app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, overrideModules)
app.sm = module.NewSimulationManagerFromAppModules(app.ModuleManager.Modules, nil)

app.sm.RegisterStoreDecoders()

Expand Down
142 changes: 140 additions & 2 deletions simapp/testutil_network_test.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,78 @@
package simapp_test

import (
"fmt"
"math/rand"
"testing"
"time"

cmtcfg "github.com/cometbft/cometbft/config"
"github.com/cosmos/go-bip39"
"github.com/stretchr/testify/suite"

sdkmath "cosmossdk.io/math"
"cosmossdk.io/simapp"
authsim "cosmossdk.io/x/auth/simulation"
banksim "cosmossdk.io/x/bank/simulation"
govsim "cosmossdk.io/x/gov/simulation"

"github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
xsim "github.com/cosmos/cosmos-sdk/x/simulation"
)

type IntegrationTestSuite struct {
suite.Suite

network network.NetworkI
network network.NetworkI
cfg network.Config
v0app *simapp.SimApp
rand *rand.Rand
accounts []simtypes.Account
}

func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")

s.rand = rand.New(rand.NewSource(42))
params := xsim.RandomParams(s.rand)
s.accounts = simtypes.RandomAccounts(s.rand, params.NumKeys())
// TODO: improve this hack to get at the bank keeper, or replace
// it with RPC queries.
s.cfg = network.DefaultConfig(func() network.TestFixture {
fixture := simapp.NewTestNetworkFixture()
appCtr := fixture.AppConstructor
fixture.AppConstructor = func(val network.ValidatorI) servertypes.Application {
app := appCtr(val)
s.v0app = app.(*simapp.SimApp)
return app
}
return fixture
})
s.cfg.NumValidators = s.rand.Intn(10)
s.cfg.ChainID = fmt.Sprintf("chainid-simapp-%x", s.rand.Uint64())
s.cfg.GenesisTime = time.Unix(0, 0)
s.cfg.FuzzConnConfig = cmtcfg.DefaultFuzzConnConfig()
s.cfg.FuzzConnConfig.ProbDropConn = .1
for i := 0; i < s.cfg.NumValidators; i++ {
entropy := make([]byte, 256/8)
n, _ := s.rand.Read(entropy)
mnemonic, err := bip39.NewMnemonic(entropy[:n])
if err != nil {
s.T().Fatal(err)
}
s.cfg.Mnemonics = append(s.cfg.Mnemonics, mnemonic)
}
initialStake := sdkmath.NewInt(s.rand.Int63n(1e12))
const numBonded = 3
banksim.RandomizedGenState(s.rand, s.cfg.GenesisState, s.cfg.Codec, s.cfg.BondDenom, s.accounts)
authsim.RandomizedGenState(s.rand, s.cfg.GenesisState, s.cfg.Codec, s.cfg.BondDenom, s.accounts, initialStake, numBonded, s.cfg.GenesisTime)
govsim.RandomizedGenState(s.rand, s.cfg.GenesisState, s.cfg.Codec, s.cfg.BondDenom)
var err error
s.network, err = network.New(s.T(), s.T().TempDir(), network.DefaultConfig(simapp.NewTestNetworkFixture))
s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg)
s.Require().NoError(err)

h, err := s.network.WaitForHeight(1)
Expand All @@ -38,6 +89,93 @@ func (s *IntegrationTestSuite) TestNetwork_Liveness() {
s.Require().NoError(err, "expected to reach 10 blocks; got %d", h)
}

func (s *IntegrationTestSuite) TestSimulation() {
v0 := s.network.GetValidators()[0]
v0app := s.v0app
clientCtx := v0.GetClientCtx()
ctx := v0app.NewContext(true)

voteGen := govsim.NewVoteGenerator()
var futureVotes []govsim.Vote
txEnc := clientCtx.TxConfig.TxEncoder()
queryClient := cmtservice.NewServiceClient(clientCtx)
generate := func(r *rand.Rand) sdk.Tx {
bankSend := func(to, from simtypes.Account) sdk.Tx {
return banksim.GenerateMsgSend(s.rand, ctx, clientCtx, v0.GetMoniker(), to, from, v0app.BankKeeper, v0app.AuthKeeper)
}
generators := []func() sdk.Tx{
func() sdk.Tx {
from, _ := simtypes.RandomAcc(s.rand, s.accounts)
to, _ := simtypes.RandomAcc(s.rand, s.accounts)
// disallow sending money to yourself.
for from.PubKey.Equals(to.PubKey) {
to, _ = simtypes.RandomAcc(s.rand, s.accounts)
}
return bankSend(to, from)
},
func() sdk.Tx {
macc := v0app.AuthKeeper.GetModuleAccount(ctx, banksim.DistributionModuleName)
to := simtypes.Account{
PubKey: macc.GetPubKey(),
Address: macc.GetAddress(),
}
from, _ := simtypes.RandomAcc(s.rand, s.accounts)
return bankSend(to, from)
},
func() sdk.Tx {
const numModuleAccs = 2
return banksim.GenerateMsgMultiSendToModuleAccount(s.rand, ctx, clientCtx, v0.GetMoniker(), s.accounts, v0app.BankKeeper, v0app.AuthKeeper, numModuleAccs)
},
func() sdk.Tx {
proposal := authsim.GenerateMsgUpdateParams(s.rand)
tx := govsim.GenerateMsgSubmitProposal(s.rand, ctx, clientCtx.TxConfig, s.accounts, v0app.AuthKeeper, v0app.BankKeeper, v0app.GovKeeper, []sdk.Msg{proposal})
votes := voteGen.GenerateVotes(s.rand, ctx, clientCtx.TxConfig, s.accounts, v0app.AuthKeeper, v0app.BankKeeper, v0app.GovKeeper)
futureVotes = append(futureVotes, votes...)
return tx
},
func() sdk.Tx {
return govsim.GenerateMsgDeposit(s.rand, ctx, clientCtx.TxConfig, s.accounts, v0app.AuthKeeper, v0app.BankKeeper, v0app.GovKeeper)
},
func() sdk.Tx {
return banksim.GenerateMsgMultiSend(s.rand, ctx, clientCtx, v0.GetMoniker(), s.accounts, v0app.BankKeeper, v0app.AuthKeeper)
},
func() sdk.Tx {
return govsim.GenerateMsgVoteWeighted(s.rand, ctx, clientCtx.TxConfig, s.accounts, v0app.AuthKeeper, v0app.BankKeeper, v0app.GovKeeper)
},
func() sdk.Tx {
return govsim.GenerateMsgCancelProposal(s.rand, ctx, clientCtx.TxConfig, s.accounts, v0app.AuthKeeper, v0app.BankKeeper, v0app.GovKeeper)
},
}
idx := s.rand.Intn(len(generators))
return generators[idx]()
}
for i := 0; i < 100; i++ {
tx := generate(s.rand)
txBytes, err := txEnc(tx)
s.Require().NoError(err)
_, err = clientCtx.BroadcastTxAsync(txBytes)
s.Require().NoError(err)
res, err := queryClient.GetLatestBlock(ctx, &cmtservice.GetLatestBlockRequest{})
s.Require().NoError(err)
latestTime := res.SdkBlock.Header.Time
for j := len(futureVotes) - 1; j >= 0; j-- {
v := futureVotes[j]
if v.BlockTime.Before(latestTime) {
continue
}
futureVotes = append(futureVotes[:j], futureVotes[j+1:]...)
txBytes, err := txEnc(v.Vote)
s.Require().NoError(err)
_, err = clientCtx.BroadcastTxAsync(txBytes)
s.Require().NoError(err)
}
if i%10 == 0 {
err := s.network.WaitForNextBlock()
s.Require().NoError(err)
}
}
}

func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}
3 changes: 1 addition & 2 deletions tests/integration/bank/keeper/deterministic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
_ "cosmossdk.io/x/auth/tx/config"
authtypes "cosmossdk.io/x/auth/types"
"cosmossdk.io/x/bank"
Expand Down Expand Up @@ -98,7 +97,7 @@ func initDeterministicFixture(t *testing.T) *deterministicFixture {
log.NewNopLogger(),
)

authModule := auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(cdc, accountKeeper)
bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper)

integrationApp := integration.NewIntegrationApp(newCtx, logger, keys, cdc, map[string]appmodule.AppModule{
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/distribution/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import (
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
authtypes "cosmossdk.io/x/auth/types"
"cosmossdk.io/x/bank"
bankkeeper "cosmossdk.io/x/bank/keeper"
Expand Down Expand Up @@ -115,7 +114,7 @@ func initFixture(t *testing.T) *fixture {
cdc, runtime.NewKVStoreService(keys[distrtypes.StoreKey]), accountKeeper, bankKeeper, stakingKeeper, poolKeeper, distrtypes.ModuleName, authority.String(),
)

authModule := auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(cdc, accountKeeper)
bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper)
stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper)
distrModule := distribution.NewAppModule(cdc, distrKeeper, accountKeeper, bankKeeper, stakingKeeper, poolKeeper)
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/evidence/keeper/infraction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
authtypes "cosmossdk.io/x/auth/types"
"cosmossdk.io/x/bank"
bankkeeper "cosmossdk.io/x/bank/keeper"
Expand Down Expand Up @@ -130,7 +129,7 @@ func initFixture(tb testing.TB) *fixture {
router = router.AddRoute(evidencetypes.RouteEquivocation, testEquivocationHandler(evidenceKeeper))
evidenceKeeper.SetRouter(router)

authModule := auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(cdc, accountKeeper)
bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper)
stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper)
slashingModule := slashing.NewAppModule(cdc, slashingKeeper, accountKeeper, bankKeeper, stakingKeeper, cdc.InterfaceRegistry())
Expand Down
5 changes: 2 additions & 3 deletions tests/integration/example/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
authtypes "cosmossdk.io/x/auth/types"
"cosmossdk.io/x/mint"
mintkeeper "cosmossdk.io/x/mint/keeper"
Expand Down Expand Up @@ -50,7 +49,7 @@ func Example() {
)

// subspace is nil because we don't test params (which is legacy anyway)
authModule := auth.NewAppModule(encodingCfg.Codec, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(encodingCfg.Codec, accountKeeper)

// here bankkeeper and staking keeper is nil because we are not testing them
// subspace is nil because we don't test params (which is legacy anyway)
Expand Down Expand Up @@ -139,7 +138,7 @@ func Example_oneModule() {
)

// subspace is nil because we don't test params (which is legacy anyway)
authModule := auth.NewAppModule(encodingCfg.Codec, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(encodingCfg.Codec, accountKeeper)

// create the application and register all the modules from the previous step
integrationApp := integration.NewIntegrationApp(
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/gov/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
authtypes "cosmossdk.io/x/auth/types"
"cosmossdk.io/x/bank"
bankkeeper "cosmossdk.io/x/bank/keeper"
Expand Down Expand Up @@ -121,7 +120,7 @@ func initFixture(tb testing.TB) *fixture {
err = govKeeper.Params.Set(newCtx, v1.DefaultParams())
assert.NilError(tb, err)

authModule := auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(cdc, accountKeeper)
bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper)
stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper)
govModule := gov.NewAppModule(cdc, govKeeper, accountKeeper, bankKeeper, poolKeeper)
Expand Down
3 changes: 1 addition & 2 deletions tests/integration/staking/keeper/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
authtypes "cosmossdk.io/x/auth/types"
"cosmossdk.io/x/bank"
bankkeeper "cosmossdk.io/x/bank/keeper"
Expand Down Expand Up @@ -137,7 +136,7 @@ func initFixture(tb testing.TB) *fixture {

stakingKeeper := stakingkeeper.NewKeeper(cdc, runtime.NewKVStoreService(keys[types.StoreKey]), accountKeeper, bankKeeper, authority.String(), addresscodec.NewBech32Codec(sdk.Bech32PrefixValAddr), addresscodec.NewBech32Codec(sdk.Bech32PrefixConsAddr))

authModule := auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(cdc, accountKeeper)
bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper)
stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper)

Expand Down
3 changes: 1 addition & 2 deletions tests/integration/staking/keeper/deterministic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
storetypes "cosmossdk.io/store/types"
"cosmossdk.io/x/auth"
authkeeper "cosmossdk.io/x/auth/keeper"
authsims "cosmossdk.io/x/auth/simulation"
authtypes "cosmossdk.io/x/auth/types"
"cosmossdk.io/x/bank"
bankkeeper "cosmossdk.io/x/bank/keeper"
Expand Down Expand Up @@ -108,7 +107,7 @@ func initDeterministicFixture(t *testing.T) *deterministicFixture {

stakingKeeper := stakingkeeper.NewKeeper(cdc, runtime.NewKVStoreService(keys[stakingtypes.StoreKey]), accountKeeper, bankKeeper, authority.String(), addresscodec.NewBech32Codec(sdk.Bech32PrefixValAddr), addresscodec.NewBech32Codec(sdk.Bech32PrefixConsAddr))

authModule := auth.NewAppModule(cdc, accountKeeper, authsims.RandomGenesisAccounts)
authModule := auth.NewAppModule(cdc, accountKeeper)
bankModule := bank.NewAppModule(cdc, bankKeeper, accountKeeper)
stakingModule := staking.NewAppModule(cdc, stakingKeeper, accountKeeper, bankKeeper)

Expand Down
Loading

0 comments on commit dc967dd

Please sign in to comment.