mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-11-10 10:05:18 +00:00
418 lines
12 KiB
Go
418 lines
12 KiB
Go
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 AppGenState(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,
|
|
}
|
|
}
|
|
|
|
// ValidateGenesisState 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 ValidateGenesisState(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
|
|
}
|
|
|
|
// AppGenState but with JSON
|
|
func AppGenStateJSON(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) (
|
|
appState json.RawMessage, err error) {
|
|
// create the final app state
|
|
genesisState, err := AppGenState(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)
|
|
}
|