github.com/cosmos/cosmos-sdk@v0.50.10/testutil/sims/state_helpers.go (about)

     1  package sims
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"math/rand"
     9  	"os"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"github.com/cosmos/gogoproto/proto"
    14  
    15  	"cosmossdk.io/math"
    16  
    17  	"github.com/cosmos/cosmos-sdk/codec"
    18  	"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
    19  	"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
    20  	sdk "github.com/cosmos/cosmos-sdk/types"
    21  	"github.com/cosmos/cosmos-sdk/types/module"
    22  	simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
    23  	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
    24  	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
    25  	genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
    26  	simcli "github.com/cosmos/cosmos-sdk/x/simulation/client/cli"
    27  	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
    28  )
    29  
    30  // Simulation parameter constants
    31  const (
    32  	StakePerAccount           = "stake_per_account"
    33  	InitiallyBondedValidators = "initially_bonded_validators"
    34  )
    35  
    36  // AppStateFn returns the initial application state using a genesis or the simulation parameters.
    37  // It calls AppStateFnWithExtendedCb with nil rawStateCb.
    38  func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager, genesisState map[string]json.RawMessage) simtypes.AppStateFn {
    39  	return AppStateFnWithExtendedCb(cdc, simManager, genesisState, nil)
    40  }
    41  
    42  // AppStateFnWithExtendedCb returns the initial application state using a genesis or the simulation parameters.
    43  // It calls AppStateFnWithExtendedCbs with nil moduleStateCb.
    44  func AppStateFnWithExtendedCb(
    45  	cdc codec.JSONCodec,
    46  	simManager *module.SimulationManager,
    47  	genesisState map[string]json.RawMessage,
    48  	rawStateCb func(rawState map[string]json.RawMessage),
    49  ) simtypes.AppStateFn {
    50  	return AppStateFnWithExtendedCbs(cdc, simManager, genesisState, nil, rawStateCb)
    51  }
    52  
    53  // AppStateFnWithExtendedCbs returns the initial application state using a genesis or the simulation parameters.
    54  // It panics if the user provides files for both of them.
    55  // If a file is not given for the genesis or the sim params, it creates a randomized one.
    56  // genesisState is the default genesis state of the whole app.
    57  // moduleStateCb is the callback function to access moduleState.
    58  // rawStateCb is the callback function to extend rawState.
    59  func AppStateFnWithExtendedCbs(
    60  	cdc codec.JSONCodec,
    61  	simManager *module.SimulationManager,
    62  	genesisState map[string]json.RawMessage,
    63  	moduleStateCb func(moduleName string, genesisState interface{}),
    64  	rawStateCb func(rawState map[string]json.RawMessage),
    65  ) simtypes.AppStateFn {
    66  	return func(
    67  		r *rand.Rand,
    68  		accs []simtypes.Account,
    69  		config simtypes.Config,
    70  	) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) {
    71  		if simcli.FlagGenesisTimeValue == 0 {
    72  			genesisTimestamp = simtypes.RandTimestamp(r)
    73  		} else {
    74  			genesisTimestamp = time.Unix(simcli.FlagGenesisTimeValue, 0)
    75  		}
    76  
    77  		chainID = config.ChainID
    78  		switch {
    79  		case config.ParamsFile != "" && config.GenesisFile != "":
    80  			panic("cannot provide both a genesis file and a params file")
    81  
    82  		case config.GenesisFile != "":
    83  			// override the default chain-id from simapp to set it later to the config
    84  			genesisDoc, accounts, err := AppStateFromGenesisFileFn(r, cdc, config.GenesisFile)
    85  			if err != nil {
    86  				panic(err)
    87  			}
    88  
    89  			if simcli.FlagGenesisTimeValue == 0 {
    90  				// use genesis timestamp if no custom timestamp is provided (i.e no random timestamp)
    91  				genesisTimestamp = genesisDoc.GenesisTime
    92  			}
    93  
    94  			appState = genesisDoc.AppState
    95  			chainID = genesisDoc.ChainID
    96  			simAccs = accounts
    97  
    98  		case config.ParamsFile != "":
    99  			appParams := make(simtypes.AppParams)
   100  			bz, err := os.ReadFile(config.ParamsFile)
   101  			if err != nil {
   102  				panic(err)
   103  			}
   104  
   105  			err = json.Unmarshal(bz, &appParams)
   106  			if err != nil {
   107  				panic(err)
   108  			}
   109  			appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams, genesisState)
   110  
   111  		default:
   112  			appParams := make(simtypes.AppParams)
   113  			appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams, genesisState)
   114  		}
   115  
   116  		rawState := make(map[string]json.RawMessage)
   117  		err := json.Unmarshal(appState, &rawState)
   118  		if err != nil {
   119  			panic(err)
   120  		}
   121  
   122  		stakingStateBz, ok := rawState[stakingtypes.ModuleName]
   123  		if !ok {
   124  			panic("staking genesis state is missing")
   125  		}
   126  
   127  		stakingState := new(stakingtypes.GenesisState)
   128  		if err = cdc.UnmarshalJSON(stakingStateBz, stakingState); err != nil {
   129  			panic(err)
   130  		}
   131  		// compute not bonded balance
   132  		notBondedTokens := math.ZeroInt()
   133  		for _, val := range stakingState.Validators {
   134  			if val.Status != stakingtypes.Unbonded {
   135  				continue
   136  			}
   137  			notBondedTokens = notBondedTokens.Add(val.GetTokens())
   138  		}
   139  		notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens)
   140  		// edit bank state to make it have the not bonded pool tokens
   141  		bankStateBz, ok := rawState[banktypes.ModuleName]
   142  		// TODO(fdymylja/jonathan): should we panic in this case
   143  		if !ok {
   144  			panic("bank genesis state is missing")
   145  		}
   146  		bankState := new(banktypes.GenesisState)
   147  		if err = cdc.UnmarshalJSON(bankStateBz, bankState); err != nil {
   148  			panic(err)
   149  		}
   150  
   151  		stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String()
   152  		var found bool
   153  		for _, balance := range bankState.Balances {
   154  			if balance.Address == stakingAddr {
   155  				found = true
   156  				break
   157  			}
   158  		}
   159  		if !found {
   160  			bankState.Balances = append(bankState.Balances, banktypes.Balance{
   161  				Address: stakingAddr,
   162  				Coins:   sdk.NewCoins(notBondedCoins),
   163  			})
   164  		}
   165  
   166  		// change appState back
   167  		for name, state := range map[string]proto.Message{
   168  			stakingtypes.ModuleName: stakingState,
   169  			banktypes.ModuleName:    bankState,
   170  		} {
   171  			if moduleStateCb != nil {
   172  				moduleStateCb(name, state)
   173  			}
   174  			rawState[name] = cdc.MustMarshalJSON(state)
   175  		}
   176  
   177  		// extend state from callback function
   178  		if rawStateCb != nil {
   179  			rawStateCb(rawState)
   180  		}
   181  
   182  		// replace appstate
   183  		appState, err = json.Marshal(rawState)
   184  		if err != nil {
   185  			panic(err)
   186  		}
   187  		return appState, simAccs, chainID, genesisTimestamp
   188  	}
   189  }
   190  
   191  // AppStateRandomizedFn creates calls each module's GenesisState generator function
   192  // and creates the simulation params
   193  func AppStateRandomizedFn(
   194  	simManager *module.SimulationManager,
   195  	r *rand.Rand,
   196  	cdc codec.JSONCodec,
   197  	accs []simtypes.Account,
   198  	genesisTimestamp time.Time,
   199  	appParams simtypes.AppParams,
   200  	genesisState map[string]json.RawMessage,
   201  ) (json.RawMessage, []simtypes.Account) {
   202  	numAccs := int64(len(accs))
   203  	// generate a random amount of initial stake coins and a random initial
   204  	// number of bonded accounts
   205  	var (
   206  		numInitiallyBonded int64
   207  		initialStake       math.Int
   208  	)
   209  	appParams.GetOrGenerate(
   210  		StakePerAccount, &initialStake, r,
   211  		func(r *rand.Rand) { initialStake = sdk.DefaultPowerReduction.AddRaw(r.Int63n(1e12)) },
   212  	)
   213  	appParams.GetOrGenerate(
   214  		InitiallyBondedValidators, &numInitiallyBonded, r,
   215  		func(r *rand.Rand) { numInitiallyBonded = int64(r.Intn(299) + 1) },
   216  	)
   217  
   218  	if numInitiallyBonded > numAccs {
   219  		numInitiallyBonded = numAccs
   220  	}
   221  
   222  	fmt.Printf(
   223  		`Selected randomly generated parameters for simulated genesis:
   224  {
   225    stake_per_account: "%d",
   226    initially_bonded_validators: "%d"
   227  }
   228  `, initialStake.Uint64(), numInitiallyBonded,
   229  	)
   230  
   231  	simState := &module.SimulationState{
   232  		AppParams:    appParams,
   233  		Cdc:          cdc,
   234  		Rand:         r,
   235  		GenState:     genesisState,
   236  		Accounts:     accs,
   237  		InitialStake: initialStake,
   238  		NumBonded:    numInitiallyBonded,
   239  		BondDenom:    sdk.DefaultBondDenom,
   240  		GenTimestamp: genesisTimestamp,
   241  	}
   242  
   243  	simManager.GenerateGenesisStates(simState)
   244  
   245  	appState, err := json.Marshal(genesisState)
   246  	if err != nil {
   247  		panic(err)
   248  	}
   249  
   250  	return appState, accs
   251  }
   252  
   253  // AppStateFromGenesisFileFn util function to generate the genesis AppState
   254  // from a genesis.json file.
   255  func AppStateFromGenesisFileFn(r io.Reader, cdc codec.JSONCodec, genesisFile string) (genutiltypes.AppGenesis, []simtypes.Account, error) {
   256  	file, err := os.Open(filepath.Clean(genesisFile))
   257  	if err != nil {
   258  		panic(err)
   259  	}
   260  
   261  	genesis, err := genutiltypes.AppGenesisFromReader(bufio.NewReader(file))
   262  	if err != nil {
   263  		return *genesis, nil, err
   264  	}
   265  
   266  	if err := file.Close(); err != nil {
   267  		return *genesis, nil, err
   268  	}
   269  
   270  	var appState map[string]json.RawMessage
   271  	if err = json.Unmarshal(genesis.AppState, &appState); err != nil {
   272  		return *genesis, nil, err
   273  	}
   274  
   275  	var authGenesis authtypes.GenesisState
   276  	if appState[authtypes.ModuleName] != nil {
   277  		cdc.MustUnmarshalJSON(appState[authtypes.ModuleName], &authGenesis)
   278  	}
   279  
   280  	newAccs := make([]simtypes.Account, len(authGenesis.Accounts))
   281  	for i, acc := range authGenesis.Accounts {
   282  		// Pick a random private key, since we don't know the actual key
   283  		// This should be fine as it's only used for mock CometBFT validators
   284  		// and these keys are never actually used to sign by mock CometBFT.
   285  		privkeySeed := make([]byte, 15)
   286  		if _, err := r.Read(privkeySeed); err != nil {
   287  			panic(err)
   288  		}
   289  
   290  		privKey := secp256k1.GenPrivKeyFromSecret(privkeySeed)
   291  
   292  		a, ok := acc.GetCachedValue().(sdk.AccountI)
   293  		if !ok {
   294  			return *genesis, nil, fmt.Errorf("expected account")
   295  		}
   296  
   297  		// create simulator accounts
   298  		simAcc := simtypes.Account{PrivKey: privKey, PubKey: privKey.PubKey(), Address: a.GetAddress(), ConsKey: ed25519.GenPrivKeyFromSecret(privkeySeed)}
   299  		newAccs[i] = simAcc
   300  	}
   301  
   302  	return *genesis, newAccs, nil
   303  }