package app import ( "encoding/json" "errors" "fmt" "io/ioutil" "os" "path/filepath" "sort" "strings" "time" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/crisis" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/staking" ) var ( // bonded tokens given to genesis validators/accounts freeTokensPerAcc = sdk.TokensFromTendermintPower(150) defaultBondDenom = sdk.DefaultBondDenom ) // State to Unmarshal type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` AuthData auth.GenesisState `json:"auth"` BankData bank.GenesisState `json:"bank"` StakingData staking.GenesisState `json:"staking"` MintData mint.GenesisState `json:"mint"` DistrData distr.GenesisState `json:"distr"` GovData gov.GenesisState `json:"gov"` CrisisData crisis.GenesisState `json:"crisis"` SlashingData slashing.GenesisState `json:"slashing"` GenTxs []json.RawMessage `json:"gentxs"` } func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, bankData bank.GenesisState, stakingData staking.GenesisState, mintData mint.GenesisState, distrData distr.GenesisState, govData gov.GenesisState, crisisData crisis.GenesisState, slashingData slashing.GenesisState) GenesisState { return GenesisState{ Accounts: accounts, AuthData: authData, BankData: bankData, StakingData: stakingData, MintData: mintData, DistrData: distrData, GovData: govData, CrisisData: crisisData, SlashingData: slashingData, } } // Sanitize sorts accounts and coin sets. func (gs GenesisState) Sanitize() { sort.Slice(gs.Accounts, func(i, j int) bool { return gs.Accounts[i].AccountNumber < gs.Accounts[j].AccountNumber }) for _, acc := range gs.Accounts { acc.Coins = acc.Coins.Sort() } } // GenesisAccount defines an account initialized at genesis. type GenesisAccount struct { Address sdk.AccAddress `json:"address"` Coins sdk.Coins `json:"coins"` Sequence uint64 `json:"sequence_number"` AccountNumber uint64 `json:"account_number"` // vesting account fields OriginalVesting sdk.Coins `json:"original_vesting"` // total vesting coins upon initialization DelegatedFree sdk.Coins `json:"delegated_free"` // delegated vested coins at time of delegation DelegatedVesting sdk.Coins `json:"delegated_vesting"` // delegated vesting coins at time of delegation StartTime int64 `json:"start_time"` // vesting start time (UNIX Epoch time) EndTime int64 `json:"end_time"` // vesting end time (UNIX Epoch time) } func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { return GenesisAccount{ Address: acc.Address, Coins: acc.Coins, AccountNumber: acc.AccountNumber, Sequence: acc.Sequence, } } func NewGenesisAccountI(acc auth.Account) GenesisAccount { gacc := GenesisAccount{ Address: acc.GetAddress(), Coins: acc.GetCoins(), AccountNumber: acc.GetAccountNumber(), Sequence: acc.GetSequence(), } vacc, ok := acc.(auth.VestingAccount) if ok { gacc.OriginalVesting = vacc.GetOriginalVesting() gacc.DelegatedFree = vacc.GetDelegatedFree() gacc.DelegatedVesting = vacc.GetDelegatedVesting() gacc.StartTime = vacc.GetStartTime() gacc.EndTime = vacc.GetEndTime() } return gacc } // convert GenesisAccount to auth.BaseAccount func (ga *GenesisAccount) ToAccount() auth.Account { bacc := &auth.BaseAccount{ Address: ga.Address, Coins: ga.Coins.Sort(), AccountNumber: ga.AccountNumber, Sequence: ga.Sequence, } if !ga.OriginalVesting.IsZero() { baseVestingAcc := &auth.BaseVestingAccount{ BaseAccount: bacc, OriginalVesting: ga.OriginalVesting, DelegatedFree: ga.DelegatedFree, DelegatedVesting: ga.DelegatedVesting, EndTime: ga.EndTime, } if ga.StartTime != 0 && ga.EndTime != 0 { return &auth.ContinuousVestingAccount{ BaseVestingAccount: baseVestingAcc, StartTime: ga.StartTime, } } else if ga.EndTime != 0 { return &auth.DelayedVestingAccount{ BaseVestingAccount: baseVestingAcc, } } else { panic(fmt.Sprintf("invalid genesis vesting account: %+v", ga)) } } return bacc } // Create the core parameters for genesis initialization for gaia // note that the pubkey input is this machines pubkey func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( genesisState GenesisState, err error) { if err = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil { return genesisState, err } // if there are no gen txs to be processed, return the default empty state if len(appGenTxs) == 0 { return genesisState, errors.New("there must be at least one genesis tx") } stakingData := genesisState.StakingData for i, genTx := range appGenTxs { var tx auth.StdTx if err := cdc.UnmarshalJSON(genTx, &tx); err != nil { return genesisState, err } msgs := tx.GetMsgs() if len(msgs) != 1 { return genesisState, errors.New( "must provide genesis StdTx with exactly 1 CreateValidator message") } if _, ok := msgs[0].(staking.MsgCreateValidator); !ok { return genesisState, fmt.Errorf( "Genesis transaction %v does not contain a MsgCreateValidator", i) } } for _, acc := range genesisState.Accounts { for _, coin := range acc.Coins { if coin.Denom == genesisState.StakingData.Params.BondDenom { stakingData.Pool.NotBondedTokens = stakingData.Pool.NotBondedTokens. Add(coin.Amount) // increase the supply } } } genesisState.StakingData = stakingData genesisState.GenTxs = appGenTxs return genesisState, nil } // NewDefaultGenesisState generates the default state for gaia. func NewDefaultGenesisState() GenesisState { return GenesisState{ Accounts: nil, AuthData: auth.DefaultGenesisState(), BankData: bank.DefaultGenesisState(), StakingData: staking.DefaultGenesisState(), MintData: mint.DefaultGenesisState(), DistrData: distr.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), CrisisData: crisis.DefaultGenesisState(), SlashingData: slashing.DefaultGenesisState(), GenTxs: nil, } } // GaiaValidateGenesisState ensures that the genesis state obeys the expected invariants // TODO: No validators are both bonded and jailed (#2088) // TODO: Error if there is a duplicate validator (#1708) // TODO: Ensure all state machine parameters are in genesis (#1704) func GaiaValidateGenesisState(genesisState GenesisState) error { if err := validateGenesisStateAccounts(genesisState.Accounts); err != nil { return err } // skip stakingData validation as genesis is created from txs if len(genesisState.GenTxs) > 0 { return nil } if err := auth.ValidateGenesis(genesisState.AuthData); err != nil { return err } if err := bank.ValidateGenesis(genesisState.BankData); err != nil { return err } if err := staking.ValidateGenesis(genesisState.StakingData); err != nil { return err } if err := mint.ValidateGenesis(genesisState.MintData); err != nil { return err } if err := distr.ValidateGenesis(genesisState.DistrData); err != nil { return err } if err := gov.ValidateGenesis(genesisState.GovData); err != nil { return err } if err := crisis.ValidateGenesis(genesisState.CrisisData); err != nil { return err } return slashing.ValidateGenesis(genesisState.SlashingData) } // validateGenesisStateAccounts performs validation of genesis accounts. It // ensures that there are no duplicate accounts in the genesis state and any // provided vesting accounts are valid. func validateGenesisStateAccounts(accs []GenesisAccount) error { addrMap := make(map[string]bool, len(accs)) for _, acc := range accs { addrStr := acc.Address.String() // disallow any duplicate accounts if _, ok := addrMap[addrStr]; ok { return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr) } // validate any vesting fields if !acc.OriginalVesting.IsZero() { if acc.EndTime == 0 { return fmt.Errorf("missing end time for vesting account; address: %s", addrStr) } if acc.StartTime >= acc.EndTime { return fmt.Errorf( "vesting start time must before end time; address: %s, start: %s, end: %s", addrStr, time.Unix(acc.StartTime, 0).UTC().Format(time.RFC3339), time.Unix(acc.EndTime, 0).UTC().Format(time.RFC3339), ) } } addrMap[addrStr] = true } return nil } // GaiaAppGenState but with JSON func GaiaAppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) ( appState json.RawMessage, err error) { // create the final app state genesisState, err := GaiaAppGenState(cdc, genDoc, appGenTxs) if err != nil { return nil, err } return codec.MarshalJSONIndent(cdc, genesisState) } // CollectStdTxs processes and validates application's genesis StdTxs and returns // the list of appGenTxs, and persistent peers required to generate genesis.json. func CollectStdTxs(cdc *codec.Codec, moniker string, genTxsDir string, genDoc tmtypes.GenesisDoc) ( appGenTxs []auth.StdTx, persistentPeers string, err error) { var fos []os.FileInfo fos, err = ioutil.ReadDir(genTxsDir) if err != nil { return appGenTxs, persistentPeers, err } // prepare a map of all accounts in genesis state to then validate // against the validators addresses var appState GenesisState if err := cdc.UnmarshalJSON(genDoc.AppState, &appState); err != nil { return appGenTxs, persistentPeers, err } addrMap := make(map[string]GenesisAccount, len(appState.Accounts)) for i := 0; i < len(appState.Accounts); i++ { acc := appState.Accounts[i] addrMap[acc.Address.String()] = acc } // addresses and IPs (and port) validator server info var addressesIPs []string for _, fo := range fos { filename := filepath.Join(genTxsDir, fo.Name()) if !fo.IsDir() && (filepath.Ext(filename) != ".json") { continue } // get the genStdTx var jsonRawTx []byte if jsonRawTx, err = ioutil.ReadFile(filename); err != nil { return appGenTxs, persistentPeers, err } var genStdTx auth.StdTx if err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx); err != nil { return appGenTxs, persistentPeers, err } appGenTxs = append(appGenTxs, genStdTx) // the memo flag is used to store // the ip and node-id, for example this may be: // "528fd3df22b31f4969b05652bfe8f0fe921321d5@192.168.2.37:26656" nodeAddrIP := genStdTx.GetMemo() if len(nodeAddrIP) == 0 { return appGenTxs, persistentPeers, fmt.Errorf( "couldn't find node's address and IP in %s", fo.Name()) } // genesis transactions must be single-message msgs := genStdTx.GetMsgs() if len(msgs) != 1 { return appGenTxs, persistentPeers, errors.New( "each genesis transaction must provide a single genesis message") } msg := msgs[0].(staking.MsgCreateValidator) // validate delegator and validator addresses and funds against the accounts in the state delAddr := msg.DelegatorAddress.String() valAddr := sdk.AccAddress(msg.ValidatorAddress).String() delAcc, delOk := addrMap[delAddr] _, valOk := addrMap[valAddr] accsNotInGenesis := []string{} if !delOk { accsNotInGenesis = append(accsNotInGenesis, delAddr) } if !valOk { accsNotInGenesis = append(accsNotInGenesis, valAddr) } if len(accsNotInGenesis) != 0 { return appGenTxs, persistentPeers, fmt.Errorf( "account(s) %v not in genesis.json: %+v", strings.Join(accsNotInGenesis, " "), addrMap) } if delAcc.Coins.AmountOf(msg.Value.Denom).LT(msg.Value.Amount) { return appGenTxs, persistentPeers, fmt.Errorf( "insufficient fund for delegation %v: %v < %v", delAcc.Address, delAcc.Coins.AmountOf(msg.Value.Denom), msg.Value.Amount, ) } // exclude itself from persistent peers if msg.Description.Moniker != moniker { addressesIPs = append(addressesIPs, nodeAddrIP) } } sort.Strings(addressesIPs) persistentPeers = strings.Join(addressesIPs, ",") return appGenTxs, persistentPeers, nil } func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount { accAuth := auth.NewBaseAccountWithAddress(addr) coins := sdk.Coins{ sdk.NewCoin("footoken", sdk.NewInt(1000)), sdk.NewCoin(defaultBondDenom, freeTokensPerAcc), } coins.Sort() accAuth.Coins = coins return NewGenesisAccount(&accAuth) }