From 1a700206b8ce1c90290daa228d2bc56ecbc0fea1 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Sat, 8 Sep 2018 02:29:34 -0700 Subject: [PATCH] Merge PR #2275: genesis: Ensure there are no duplicate accounts in genesis file This also contains a light refactor of genesis state parsing, so that the tests file didn't duplicate a lot of this code. --- PENDING.md | 1 + cmd/gaia/app/app.go | 5 ++ cmd/gaia/app/genesis.go | 90 +++++++++++++++++++++++++----------- cmd/gaia/app/genesis_test.go | 39 ++++++++++++++++ 4 files changed, 107 insertions(+), 28 deletions(-) diff --git a/PENDING.md b/PENDING.md index 9254f69b9a0b..3b6033b9e489 100644 --- a/PENDING.md +++ b/PENDING.md @@ -95,6 +95,7 @@ IMPROVEMENTS * [x/auth] Signature verification's gas cost now accounts for pubkey type. [#2046](https://github.com/tendermint/tendermint/pull/2046) * [x/stake] [x/slashing] Ensure delegation invariants to jailed validators [#1883](https://github.com/cosmos/cosmos-sdk/issues/1883). * [x/stake] Improve speed of GetValidator, which was shown to be a performance bottleneck. [#2046](https://github.com/tendermint/tendermint/pull/2200) + * [genesis] \#2229 Ensure that there are no duplicate accounts in the genesis state. * SDK * [tools] Make get_vendor_deps deletes `.vendor-new` directories, in case scratch files are present. * [spec] Added simple piggy bank distribution spec diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 36a02899b5d5..14ed1b2c1749 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -190,6 +190,11 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.StakeData) gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) + err = GaiaValidateGenesisState(genesisState) + if err != nil { + // TODO find a way to do this w/o panics + panic(err) + } return abci.ResponseInitChain{ Validators: validators, diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 16c29190373c..4967361b14d5 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -180,38 +180,12 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState } // create the genesis account, give'm few steaks and a buncha token with there name - accAuth := auth.NewBaseAccountWithAddress(genTx.Address) - accAuth.Coins = sdk.Coins{ - {genTx.Name + "Token", sdk.NewInt(1000)}, - {"steak", freeFermionsAcc}, - } - acc := NewGenesisAccount(&accAuth) - genaccs[i] = acc + genaccs[i] = genesisAccountFromGenTx(genTx) stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply // add the validator if len(genTx.Name) > 0 { - desc := stake.NewDescription(genTx.Name, "", "", "") - validator := stake.NewValidator( - sdk.ValAddress(genTx.Address), sdk.MustGetConsPubKeyBech32(genTx.PubKey), desc, - ) - - stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDec(freeFermionVal)) // increase the supply - - // add some new shares to the validator - var issuedDelShares sdk.Dec - validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, sdk.NewInt(freeFermionVal)) - stakeData.Validators = append(stakeData.Validators, validator) - - // create the self-delegation from the issuedDelShares - delegation := stake.Delegation{ - DelegatorAddr: sdk.AccAddress(validator.OperatorAddr), - ValidatorAddr: validator.OperatorAddr, - Shares: issuedDelShares, - Height: 0, - } - - stakeData.Bonds = append(stakeData.Bonds, delegation) + stakeData = addValidatorToStakeData(genTx, stakeData) } } @@ -224,6 +198,66 @@ func GaiaAppGenState(cdc *wire.Codec, appGenTxs []json.RawMessage) (genesisState return } +func addValidatorToStakeData(genTx GaiaGenTx, stakeData stake.GenesisState) stake.GenesisState { + desc := stake.NewDescription(genTx.Name, "", "", "") + validator := stake.NewValidator( + sdk.ValAddress(genTx.Address), sdk.MustGetConsPubKeyBech32(genTx.PubKey), desc, + ) + + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDec(freeFermionVal)) // increase the supply + + // add some new shares to the validator + var issuedDelShares sdk.Dec + validator, stakeData.Pool, issuedDelShares = validator.AddTokensFromDel(stakeData.Pool, sdk.NewInt(freeFermionVal)) + stakeData.Validators = append(stakeData.Validators, validator) + + // create the self-delegation from the issuedDelShares + delegation := stake.Delegation{ + DelegatorAddr: sdk.AccAddress(validator.OperatorAddr), + ValidatorAddr: validator.OperatorAddr, + Shares: issuedDelShares, + Height: 0, + } + + stakeData.Bonds = append(stakeData.Bonds, delegation) + return stakeData +} + +func genesisAccountFromGenTx(genTx GaiaGenTx) GenesisAccount { + accAuth := auth.NewBaseAccountWithAddress(genTx.Address) + accAuth.Coins = sdk.Coins{ + {genTx.Name + "Token", sdk.NewInt(1000)}, + {"steak", freeFermionsAcc}, + } + return NewGenesisAccount(&accAuth) +} + +// GaiaValidateGenesisState ensures that the genesis state obeys the expected invariants +// TODO: No validators are both bonded and revoked (#2088) +// TODO: Error if there is a duplicate validator (#1708) +// TODO: Ensure all state machine parameters are in genesis (#1704) +func GaiaValidateGenesisState(genesisState GenesisState) (err error) { + err = validateGenesisStateAccounts(genesisState.Accounts) + if err != nil { + return + } + return +} + +// Ensures that there are no duplicate accounts in the genesis state, +func validateGenesisStateAccounts(accs []GenesisAccount) (err 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) + } + addrMap[strAddr] = true + } + return +} + // GaiaAppGenState but with JSON func GaiaAppGenStateJSON(cdc *wire.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) { diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 85b5b0d9b46d..755a90ebe790 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -5,10 +5,36 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/gov" + stake "github.com/cosmos/cosmos-sdk/x/stake" "github.com/stretchr/testify/require" "github.com/tendermint/tendermint/crypto/ed25519" ) +func makeGenesisState(genTxs []GaiaGenTx) GenesisState { + // start with the default staking genesis state + stakeData := stake.DefaultGenesisState() + + // get genesis flag account information + genaccs := make([]GenesisAccount, len(genTxs)) + for i, genTx := range genTxs { + genaccs[i] = genesisAccountFromGenTx(genTx) + stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply + + // add the validator + if len(genTx.Name) > 0 { + stakeData = addValidatorToStakeData(genTx, stakeData) + } + } + + // create the final app state + return GenesisState{ + Accounts: genaccs, + StakeData: stakeData, + GovData: gov.DefaultGenesisState(), + } +} + func TestToAccount(t *testing.T) { priv := ed25519.GenPrivKey() addr := sdk.AccAddress(priv.PubKey().Address()) @@ -34,3 +60,16 @@ func TestGaiaAppGenState(t *testing.T) { // TODO test with both one and two genesis transactions: // TODO correct: genesis account created, canididates created, pool token variance } + +func TestGaiaGenesisValidation(t *testing.T) { + genTxs := make([]GaiaGenTx, 2) + privKey := ed25519.GenPrivKey() + pubKey := privKey.PubKey() + addr := pubKey.Address() + // Test duplicate accounts fails + genTxs[0] = GaiaGenTx{"", sdk.AccAddress(addr), ""} + genTxs[1] = GaiaGenTx{"", sdk.AccAddress(addr), ""} + genesisState := makeGenesisState(genTxs) + err := GaiaValidateGenesisState(genesisState) + require.NotNil(t, err) +}