cosmos-sdk/simapp/state.go

244 lines
7.1 KiB
Go

package simapp
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math/rand"
"time"
tmjson "github.com/tendermint/tendermint/libs/json"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
// AppStateFn returns the initial application state using a genesis or the simulation parameters.
// It panics if the user provides files for both of them.
// If a file is not given for the genesis or the sim params, it creates a randomized one.
func AppStateFn(cdc codec.JSONCodec, simManager *module.SimulationManager) simtypes.AppStateFn {
return func(r *rand.Rand, accs []simtypes.Account, config simtypes.Config,
) (appState json.RawMessage, simAccs []simtypes.Account, chainID string, genesisTimestamp time.Time) {
if FlagGenesisTimeValue == 0 {
genesisTimestamp = simtypes.RandTimestamp(r)
} else {
genesisTimestamp = time.Unix(FlagGenesisTimeValue, 0)
}
chainID = config.ChainID
switch {
case config.ParamsFile != "" && config.GenesisFile != "":
panic("cannot provide both a genesis file and a params file")
case config.GenesisFile != "":
// override the default chain-id from simapp to set it later to the config
genesisDoc, accounts := AppStateFromGenesisFileFn(r, cdc, config.GenesisFile)
if FlagGenesisTimeValue == 0 {
// use genesis timestamp if no custom timestamp is provided (i.e no random timestamp)
genesisTimestamp = genesisDoc.GenesisTime
}
appState = genesisDoc.AppState
chainID = genesisDoc.ChainID
simAccs = accounts
case config.ParamsFile != "":
appParams := make(simtypes.AppParams)
bz, err := ioutil.ReadFile(config.ParamsFile)
if err != nil {
panic(err)
}
err = json.Unmarshal(bz, &appParams)
if err != nil {
panic(err)
}
appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams)
default:
appParams := make(simtypes.AppParams)
appState, simAccs = AppStateRandomizedFn(simManager, r, cdc, accs, genesisTimestamp, appParams)
}
rawState := make(map[string]json.RawMessage)
err := json.Unmarshal(appState, &rawState)
if err != nil {
panic(err)
}
stakingStateBz, ok := rawState[stakingtypes.ModuleName]
if !ok {
panic("staking genesis state is missing")
}
stakingState := new(stakingtypes.GenesisState)
err = cdc.UnmarshalJSON(stakingStateBz, stakingState)
if err != nil {
panic(err)
}
// compute not bonded balance
notBondedTokens := sdk.ZeroInt()
for _, val := range stakingState.Validators {
if val.Status != stakingtypes.Unbonded {
continue
}
notBondedTokens = notBondedTokens.Add(val.GetTokens())
}
notBondedCoins := sdk.NewCoin(stakingState.Params.BondDenom, notBondedTokens)
// edit bank state to make it have the not bonded pool tokens
bankStateBz, ok := rawState[banktypes.ModuleName]
// TODO(fdymylja/jonathan): should we panic in this case
if !ok {
panic("bank genesis state is missing")
}
bankState := new(banktypes.GenesisState)
err = cdc.UnmarshalJSON(bankStateBz, bankState)
if err != nil {
panic(err)
}
stakingAddr := authtypes.NewModuleAddress(stakingtypes.NotBondedPoolName).String()
var found bool
for _, balance := range bankState.Balances {
if balance.Address == stakingAddr {
found = true
break
}
}
if !found {
bankState.Balances = append(bankState.Balances, banktypes.Balance{
Address: stakingAddr,
Coins: sdk.NewCoins(notBondedCoins),
})
}
// change appState back
rawState[stakingtypes.ModuleName] = cdc.MustMarshalJSON(stakingState)
rawState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankState)
// replace appstate
appState, err = json.Marshal(rawState)
if err != nil {
panic(err)
}
return appState, simAccs, chainID, genesisTimestamp
}
}
// AppStateRandomizedFn creates calls each module's GenesisState generator function
// and creates the simulation params
func AppStateRandomizedFn(
simManager *module.SimulationManager, r *rand.Rand, cdc codec.JSONCodec,
accs []simtypes.Account, genesisTimestamp time.Time, appParams simtypes.AppParams,
) (json.RawMessage, []simtypes.Account) {
numAccs := int64(len(accs))
genesisState := NewDefaultGenesisState(cdc)
// generate a random amount of initial stake coins and a random initial
// number of bonded accounts
var initialStake, numInitiallyBonded int64
appParams.GetOrGenerate(
cdc, simappparams.StakePerAccount, &initialStake, r,
func(r *rand.Rand) { initialStake = r.Int63n(1e12) },
)
appParams.GetOrGenerate(
cdc, simappparams.InitiallyBondedValidators, &numInitiallyBonded, r,
func(r *rand.Rand) { numInitiallyBonded = int64(r.Intn(300)) },
)
if numInitiallyBonded > numAccs {
numInitiallyBonded = numAccs
}
fmt.Printf(
`Selected randomly generated parameters for simulated genesis:
{
stake_per_account: "%d",
initially_bonded_validators: "%d"
}
`, initialStake, numInitiallyBonded,
)
simState := &module.SimulationState{
AppParams: appParams,
Cdc: cdc,
Rand: r,
GenState: genesisState,
Accounts: accs,
InitialStake: initialStake,
NumBonded: numInitiallyBonded,
GenTimestamp: genesisTimestamp,
}
simManager.GenerateGenesisStates(simState)
appState, err := json.Marshal(genesisState)
if err != nil {
panic(err)
}
return appState, accs
}
// AppStateFromGenesisFileFn util function to generate the genesis AppState
// from a genesis.json file.
func AppStateFromGenesisFileFn(r io.Reader, cdc codec.JSONCodec, genesisFile string) (tmtypes.GenesisDoc, []simtypes.Account) {
bytes, err := ioutil.ReadFile(genesisFile)
if err != nil {
panic(err)
}
var genesis tmtypes.GenesisDoc
// NOTE: Tendermint uses a custom JSON decoder for GenesisDoc
err = tmjson.Unmarshal(bytes, &genesis)
if err != nil {
panic(err)
}
var appState GenesisState
err = json.Unmarshal(genesis.AppState, &appState)
if err != nil {
panic(err)
}
var authGenesis authtypes.GenesisState
if appState[authtypes.ModuleName] != nil {
cdc.MustUnmarshalJSON(appState[authtypes.ModuleName], &authGenesis)
}
newAccs := make([]simtypes.Account, len(authGenesis.Accounts))
for i, acc := range authGenesis.Accounts {
// Pick a random private key, since we don't know the actual key
// This should be fine as it's only used for mock Tendermint validators
// and these keys are never actually used to sign by mock Tendermint.
privkeySeed := make([]byte, 15)
if _, err := r.Read(privkeySeed); err != nil {
panic(err)
}
privKey := secp256k1.GenPrivKeyFromSecret(privkeySeed)
a, ok := acc.GetCachedValue().(authtypes.AccountI)
if !ok {
panic("expected account")
}
// create simulator accounts
simAcc := simtypes.Account{PrivKey: privKey, PubKey: privKey.PubKey(), Address: a.GetAddress()}
newAccs[i] = simAcc
}
return genesis, newAccs
}