From 1a656e70235b72840a158fc235ac5db72fe8e376 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Tue, 29 Jan 2019 13:11:33 -0500 Subject: [PATCH] Merge PR #3425: Add Vesting Account Genesis Validation --- PENDING.md | 2 ++ cmd/gaia/app/genesis.go | 40 ++++++++++++++++++------ cmd/gaia/app/genesis_test.go | 32 +++++++++++++------ docs/gaia/genesis.md | 60 ++++++++++++++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 docs/gaia/genesis.md diff --git a/PENDING.md b/PENDING.md index ad915ada61..7f34780cb1 100644 --- a/PENDING.md +++ b/PENDING.md @@ -36,6 +36,8 @@ IMPROVEMENTS * Gaia CLI (`gaiacli`) * Gaia + * [\#3418](https://github.com/cosmos/cosmos-sdk/issues/3418) Add vesting account + genesis validation checks to `GaiaValidateGenesisState`. * SDK diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index b8b9aa1227..3eae8af875 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -9,6 +9,7 @@ import ( "path/filepath" "sort" "strings" + "time" "github.com/cosmos/cosmos-sdk/x/bank" @@ -85,8 +86,8 @@ type GenesisAccount struct { 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 - EndTime int64 `json:"end_time"` // vesting end time + 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 { @@ -252,17 +253,38 @@ func GaiaValidateGenesisState(genesisState GenesisState) error { return slashing.ValidateGenesis(genesisState.SlashingData) } -// Ensures that there are no duplicate accounts in the genesis state, +// 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 i := 0; i < len(accs); i++ { - acc := accs[i] - strAddr := string(acc.Address) - if _, ok := addrMap[strAddr]; ok { - return fmt.Errorf("Duplicate account in genesis state: Address %v", acc.Address) + 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) } - addrMap[strAddr] = true + + // 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 } diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index be0c9dba4c..82829b6994 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -111,29 +111,41 @@ func makeMsg(name string, pk crypto.PubKey) auth.StdTx { } func TestGaiaGenesisValidation(t *testing.T) { - genTxs := make([]auth.StdTx, 2) - // Test duplicate accounts fails - genTxs[0] = makeMsg("test-0", pk1) - genTxs[1] = makeMsg("test-1", pk1) - genesisState := makeGenesisState(t, genTxs) + genTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk2)} + dupGenTxs := []auth.StdTx{makeMsg("test-0", pk1), makeMsg("test-1", pk1)} + + // require duplicate accounts fails validation + genesisState := makeGenesisState(t, dupGenTxs) err := GaiaValidateGenesisState(genesisState) - require.NotNil(t, err) - // Test bonded + jailed validator fails + require.Error(t, err) + + // require invalid vesting account fails validation (invalid end time) + genesisState = makeGenesisState(t, genTxs) + genesisState.Accounts[0].OriginalVesting = genesisState.Accounts[0].Coins + err = GaiaValidateGenesisState(genesisState) + require.Error(t, err) + genesisState.Accounts[0].StartTime = 1548888000 + genesisState.Accounts[0].EndTime = 1548775410 + err = GaiaValidateGenesisState(genesisState) + require.Error(t, err) + + // require bonded + jailed validator fails validation genesisState = makeGenesisState(t, genTxs) val1 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #2"}) val1.Jailed = true val1.Status = sdk.Bonded genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) err = GaiaValidateGenesisState(genesisState) - require.NotNil(t, err) - // Test duplicate validator fails + require.Error(t, err) + + // require duplicate validator fails validation val1.Jailed = false genesisState = makeGenesisState(t, genTxs) val2 := stakingTypes.NewValidator(addr1, pk1, stakingTypes.Description{Moniker: "test #3"}) genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val1) genesisState.StakingData.Validators = append(genesisState.StakingData.Validators, val2) err = GaiaValidateGenesisState(genesisState) - require.NotNil(t, err) + require.Error(t, err) } func TestNewDefaultGenesisAccount(t *testing.T) { diff --git a/docs/gaia/genesis.md b/docs/gaia/genesis.md new file mode 100644 index 0000000000..727944fbc9 --- /dev/null +++ b/docs/gaia/genesis.md @@ -0,0 +1,60 @@ +# Gaia Genesis State + +Gaia genesis state, `GenesisState`, is composed of accounts, various module +states and metadata such as genesis transactions. Each module may specify its +own `GenesisState`. In addition, each module may specify its own genesis state +validation, import and export functionality. + +The Gaia genesis state is defined as follows: + +```go +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"` + SlashingData slashing.GenesisState `json:"slashing"` + GenTxs []json.RawMessage `json:"gentxs"` +} +``` + +In the ABCI `initChainer` definition of Gaia the `initFromGenesisState` is called +which internally calls each module's `InitGenesis` providing its own respective +`GenesisState` as a parameter. + +## Accounts + +Genesis accounts defined in the `GenesisState` are defined as follows: + +```go +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) +} +``` + +Each account must have a valid and unique account number in addition to a +sequence number (nonce) and address. + +Accounts may also be vesting, in which case they must provide the necessary vesting +information. Vesting accounts must provide at a minimum `OriginalVesting` and +`EndTime`. If `StartTime` is also provided, the account will be treated as a +"continuous" vesting account in which it vests coins at a predefined schedule. +Providing a `StartTime` must be less than `EndTime` but may be in the future. +In other words, it does not have to be equal to the genesis time. In a new chain +starting from a fresh state (not exported), `OriginalVesting` must be less than +or equal to `Coins.` + +