github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/app/simulation_test.go (about)

     1  package app
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"math/rand"
     7  	"os"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec"
    12  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/prefix"
    13  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types/module"
    14  	"github.com/fibonacci-chain/fbc/x/wasm"
    15  	wasmtypes "github.com/fibonacci-chain/fbc/x/wasm/types"
    16  
    17  	"github.com/stretchr/testify/require"
    18  
    19  	abci "github.com/fibonacci-chain/fbc/libs/tendermint/abci/types"
    20  	"github.com/fibonacci-chain/fbc/libs/tendermint/libs/log"
    21  	dbm "github.com/fibonacci-chain/fbc/libs/tm-db"
    22  
    23  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/baseapp"
    24  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/simapp"
    25  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/simapp/helpers"
    26  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store"
    27  	sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types"
    28  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/auth"
    29  	distr "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/distribution"
    30  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/mint"
    31  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/simulation"
    32  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/slashing"
    33  	"github.com/fibonacci-chain/fbc/libs/cosmos-sdk/x/supply"
    34  	"github.com/fibonacci-chain/fbc/x/gov"
    35  	"github.com/fibonacci-chain/fbc/x/params"
    36  	"github.com/fibonacci-chain/fbc/x/staking"
    37  )
    38  
    39  func init() {
    40  	simapp.GetSimulatorFlags()
    41  }
    42  
    43  type storeKeysPrefixes struct {
    44  	A        sdk.StoreKey
    45  	B        sdk.StoreKey
    46  	Prefixes [][]byte
    47  }
    48  
    49  // fauxMerkleModeOpt returns a BaseApp option to use a dbStoreAdapter instead of
    50  // an IAVLStore for faster simulation speed.
    51  func fauxMerkleModeOpt(bapp *baseapp.BaseApp) {
    52  	bapp.SetFauxMerkleMode()
    53  }
    54  
    55  // interBlockCacheOpt returns a BaseApp option function that sets the persistent
    56  // inter-block write-through cache.
    57  func interBlockCacheOpt() func(*baseapp.BaseApp) {
    58  	return baseapp.SetInterBlockCache(store.NewCommitKVStoreCacheManager())
    59  }
    60  
    61  func TestFullAppSimulation(t *testing.T) {
    62  	config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
    63  	if skip {
    64  		t.Skip("skipping application simulation")
    65  	}
    66  	require.NoError(t, err, "simulation setup failed")
    67  
    68  	defer func() {
    69  		db.Close()
    70  		require.NoError(t, os.RemoveAll(dir))
    71  	}()
    72  
    73  	app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
    74  	require.Equal(t, appName, app.Name())
    75  
    76  	// run randomized simulation
    77  	_, simParams, simErr := simulation.SimulateFromSeed(
    78  		t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()),
    79  		simapp.SimulationOperations(app, app.Codec(), config),
    80  		app.ModuleAccountAddrs(), config,
    81  	)
    82  
    83  	// export state and simParams before the simulation error is checked
    84  	err = simapp.CheckExportSimulation(app, config, simParams)
    85  	require.NoError(t, err)
    86  	require.NoError(t, simErr)
    87  
    88  	if config.Commit {
    89  		simapp.PrintStats(db)
    90  	}
    91  }
    92  
    93  func TestAppImportExport(t *testing.T) {
    94  	config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
    95  	if skip {
    96  		t.Skip("skipping application import/export simulation")
    97  	}
    98  	require.NoError(t, err, "simulation setup failed")
    99  
   100  	defer func() {
   101  		db.Close()
   102  		require.NoError(t, os.RemoveAll(dir))
   103  	}()
   104  
   105  	app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
   106  	require.Equal(t, appName, app.Name())
   107  
   108  	// Run randomized simulation
   109  	_, simParams, simErr := simulation.SimulateFromSeed(
   110  		t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()),
   111  		simapp.SimulationOperations(app, app.Codec(), config),
   112  		app.ModuleAccountAddrs(), config,
   113  	)
   114  
   115  	// export state and simParams before the simulation error is checked
   116  	err = simapp.CheckExportSimulation(app, config, simParams)
   117  	require.NoError(t, err)
   118  	require.NoError(t, simErr)
   119  
   120  	if config.Commit {
   121  		simapp.PrintStats(db)
   122  	}
   123  
   124  	fmt.Printf("exporting genesis...\n")
   125  
   126  	appState, _, err := app.ExportAppStateAndValidators(false, []string{})
   127  	require.NoError(t, err)
   128  
   129  	fmt.Printf("importing genesis...\n")
   130  
   131  	// nolint: dogsled
   132  	_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
   133  	require.NoError(t, err, "simulation setup failed")
   134  
   135  	defer func() {
   136  		newDB.Close()
   137  		require.NoError(t, os.RemoveAll(newDir))
   138  	}()
   139  
   140  	newApp := NewFBChainApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
   141  	require.Equal(t, appName, newApp.Name())
   142  
   143  	var genesisState map[string]json.RawMessage
   144  	err = app.Codec().UnmarshalJSON(appState, &genesisState)
   145  	require.NoError(t, err)
   146  
   147  	ctxA := app.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
   148  	ctxB := newApp.NewContext(true, abci.Header{Height: app.LastBlockHeight()})
   149  	newApp.mm.InitGenesis(ctxB, genesisState)
   150  
   151  	fmt.Printf("comparing stores...\n")
   152  
   153  	storeKeysPrefixes := []storeKeysPrefixes{
   154  		{app.keys[baseapp.MainStoreKey], newApp.keys[baseapp.MainStoreKey], [][]byte{}},
   155  		{app.keys[auth.StoreKey], newApp.keys[auth.StoreKey], [][]byte{}},
   156  		{app.keys[staking.StoreKey], newApp.keys[staking.StoreKey],
   157  			[][]byte{}}, // ordering may change but it doesn't matter
   158  		{app.keys[slashing.StoreKey], newApp.keys[slashing.StoreKey], [][]byte{}},
   159  		{app.keys[mint.StoreKey], newApp.keys[mint.StoreKey], [][]byte{}},
   160  		{app.keys[distr.StoreKey], newApp.keys[distr.StoreKey], [][]byte{}},
   161  		{app.keys[supply.StoreKey], newApp.keys[supply.StoreKey], [][]byte{}},
   162  		{app.keys[params.StoreKey], newApp.keys[params.StoreKey], [][]byte{}},
   163  		{app.keys[gov.StoreKey], newApp.keys[gov.StoreKey], [][]byte{}},
   164  	}
   165  
   166  	// reset contract code index in source DB for comparison with dest DB
   167  	dropContractHistory := func(s store.KVStore, keys ...[]byte) {
   168  		for _, key := range keys {
   169  			prefixStore := prefix.NewStore(s, key)
   170  			iter := prefixStore.Iterator(nil, nil)
   171  			for ; iter.Valid(); iter.Next() {
   172  				prefixStore.Delete(iter.Key())
   173  			}
   174  			iter.Close()
   175  		}
   176  	}
   177  	prefixes := [][]byte{wasmtypes.ContractCodeHistoryElementPrefix, wasmtypes.ContractByCodeIDAndCreatedSecondaryIndexPrefix}
   178  	dropContractHistory(ctxA.KVStore(app.keys[wasm.StoreKey]), prefixes...)
   179  	dropContractHistory(ctxB.KVStore(newApp.keys[wasm.StoreKey]), prefixes...)
   180  
   181  	normalizeContractInfo := func(ctx sdk.Context, app *FBChainApp) {
   182  		var index uint64
   183  		app.WasmKeeper.IterateContractInfo(ctx, func(address sdk.AccAddress, info wasmtypes.ContractInfo) bool {
   184  			created := &wasmtypes.AbsoluteTxPosition{
   185  				BlockHeight: uint64(0),
   186  				TxIndex:     index,
   187  			}
   188  			info.Created = created
   189  			store := ctx.KVStore(app.keys[wasm.StoreKey])
   190  			store.Set(wasmtypes.GetContractAddressKey(address), app.marshal.GetProtocMarshal().MustMarshal(&info))
   191  			index++
   192  			return false
   193  		})
   194  	}
   195  	normalizeContractInfo(ctxA, app)
   196  	normalizeContractInfo(ctxB, newApp)
   197  
   198  	for _, skp := range storeKeysPrefixes {
   199  		storeA := ctxA.KVStore(skp.A)
   200  		storeB := ctxB.KVStore(skp.B)
   201  
   202  		failedKVAs, failedKVBs := sdk.DiffKVStores(storeA, storeB, skp.Prefixes)
   203  		require.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare")
   204  
   205  		fmt.Printf("compared %d key/value pairs between %s and %s\n", len(failedKVAs), skp.A, skp.B)
   206  		require.Equal(t, len(failedKVAs), 0, simapp.GetSimulationLog(skp.A.Name(), app.SimulationManager().StoreDecoders, app.Codec(), failedKVAs, failedKVBs))
   207  	}
   208  }
   209  
   210  func TestAppSimulationAfterImport(t *testing.T) {
   211  	config, db, dir, logger, skip, err := simapp.SetupSimulation("leveldb-app-sim", "Simulation")
   212  	if skip {
   213  		t.Skip("skipping application simulation after import")
   214  	}
   215  	require.NoError(t, err, "simulation setup failed")
   216  
   217  	defer func() {
   218  		db.Close()
   219  		require.NoError(t, os.RemoveAll(dir))
   220  	}()
   221  
   222  	app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
   223  	require.Equal(t, appName, app.Name())
   224  
   225  	// Run randomized simulation
   226  	stopEarly, simParams, simErr := simulation.SimulateFromSeed(
   227  		t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()),
   228  		simapp.SimulationOperations(app, app.Codec(), config),
   229  		app.ModuleAccountAddrs(), config,
   230  	)
   231  
   232  	// export state and simParams before the simulation error is checked
   233  	err = simapp.CheckExportSimulation(app, config, simParams)
   234  	require.NoError(t, err)
   235  	require.NoError(t, simErr)
   236  
   237  	if config.Commit {
   238  		simapp.PrintStats(db)
   239  	}
   240  
   241  	if stopEarly {
   242  		fmt.Println("can't export or import a zero-validator genesis, exiting test...")
   243  		return
   244  	}
   245  
   246  	fmt.Printf("exporting genesis...\n")
   247  
   248  	appState, _, err := app.ExportAppStateAndValidators(true, []string{})
   249  	require.NoError(t, err)
   250  
   251  	fmt.Printf("importing genesis...\n")
   252  
   253  	// nolint: dosgsled
   254  	_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
   255  	require.NoError(t, err, "simulation setup failed")
   256  
   257  	defer func() {
   258  		newDB.Close()
   259  		require.NoError(t, os.RemoveAll(newDir))
   260  	}()
   261  
   262  	newApp := NewFBChainApp(log.NewNopLogger(), newDB, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, fauxMerkleModeOpt)
   263  	require.Equal(t, appName, newApp.Name())
   264  
   265  	newApp.InitChain(abci.RequestInitChain{
   266  		AppStateBytes: appState,
   267  	})
   268  
   269  	_, _, err = simulation.SimulateFromSeed(
   270  		t, os.Stdout, newApp.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()),
   271  		simapp.SimulationOperations(newApp, newApp.Codec(), config),
   272  		newApp.ModuleAccountAddrs(), config,
   273  	)
   274  	require.NoError(t, err)
   275  }
   276  
   277  func TestAppStateDeterminism(t *testing.T) {
   278  	if !simapp.FlagEnabledValue {
   279  		t.Skip("skipping application simulation")
   280  	}
   281  
   282  	config := simapp.NewConfigFromFlags()
   283  	config.InitialBlockHeight = 1
   284  	config.ExportParamsPath = ""
   285  	config.OnOperation = false
   286  	config.AllInvariants = false
   287  	config.ChainID = helpers.SimAppChainID
   288  
   289  	numTimesToRunPerSeed := 2
   290  	appHashList := make([]json.RawMessage, numTimesToRunPerSeed)
   291  
   292  	config.Seed = rand.Int63()
   293  
   294  	for i := 0; i < numTimesToRunPerSeed; i++ {
   295  		var logger log.Logger
   296  		if simapp.FlagVerboseValue {
   297  			logger = log.TestingLogger()
   298  		} else {
   299  			logger = log.NewNopLogger()
   300  		}
   301  
   302  		db := dbm.NewMemDB()
   303  
   304  		app := NewFBChainApp(logger, db, nil, true, map[int64]bool{}, simapp.FlagPeriodValue, interBlockCacheOpt())
   305  
   306  		fmt.Printf(
   307  			"running non-determinism simulation; seed %d: attempt: %d/%d\n",
   308  			config.Seed, i+1, numTimesToRunPerSeed,
   309  		)
   310  
   311  		_, _, err := simulation.SimulateFromSeed(
   312  			t, os.Stdout, app.BaseApp, AppStateFn(app.Codec(), app.SimulationManager()),
   313  			simapp.SimulationOperations(app, app.Codec(), config),
   314  			app.ModuleAccountAddrs(), config,
   315  		)
   316  		require.NoError(t, err)
   317  
   318  		if config.Commit {
   319  			simapp.PrintStats(db)
   320  		}
   321  
   322  		appHash := app.LastCommitID().Hash
   323  		appHashList[i] = appHash
   324  
   325  		if i != 0 {
   326  			require.Equal(
   327  				t, appHashList[0], appHashList[i],
   328  				"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numTimesToRunPerSeed,
   329  			)
   330  		}
   331  	}
   332  }
   333  
   334  // AppStateFn returns the initial application state using a genesis or the simulation parameters.
   335  // It panics if the user provides files for both of them.
   336  // If a file is not given for the genesis or the sim params, it creates a randomized one.
   337  func AppStateFn(codec *codec.Codec, manager *module.SimulationManager) simulation.AppStateFn {
   338  	// quick hack to setup app state genesis with our app modules
   339  	simapp.ModuleBasics = ModuleBasics
   340  	if simapp.FlagGenesisTimeValue == 0 { // always set to have a block time
   341  		simapp.FlagGenesisTimeValue = time.Now().Unix()
   342  	}
   343  	return simapp.AppStateFn(codec, manager)
   344  }