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 }