Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

R4R: New genesis workflow #2602

Merged
merged 24 commits into from
Nov 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions PENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ BREAKING CHANGES
* Gaia CLI (`gaiacli`)

* Gaia
* [gaiad init] \#2602 New genesis workflow

* SDK
* [simulation] \#2665 only argument to simulation.Invariant is now app
Expand Down
11 changes: 10 additions & 1 deletion client/lcd/test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ func InitializeTestLCD(
genTxs := []json.RawMessage{}

// append any additional (non-proposing) validators
var accs []gapp.GenesisAccount
for i := 0; i < nValidators; i++ {
operPrivKey := secp256k1.GenPrivKey()
operAddr := operPrivKey.PubKey().Address()
Expand Down Expand Up @@ -242,9 +243,17 @@ func InitializeTestLCD(
genTxs = append(genTxs, txBytes)
valConsPubKeys = append(valConsPubKeys, pubKey)
valOperAddrs = append(valOperAddrs, sdk.ValAddress(operAddr))

accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(operAddr))
accAuth.Coins = sdk.Coins{sdk.NewInt64Coin("steak", 150)}
accs = append(accs, gapp.NewGenesisAccount(&accAuth))
}

genesisState, err := gapp.GaiaAppGenState(cdc, genTxs)
appGenState := gapp.NewDefaultGenesisState()
appGenState.Accounts = accs
genDoc.AppState, err = cdc.MarshalJSON(appGenState)
require.NoError(t, err)
genesisState, err := gapp.GaiaAppGenState(cdc, *genDoc, genTxs)
require.NoError(t, err)

// add some tokens to init accounts
Expand Down
3 changes: 2 additions & 1 deletion cmd/gaia/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,8 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci
// sanity check
if len(req.Validators) > 0 {
if len(req.Validators) != len(validators) {
panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d) ", len(req.Validators), len(validators)))
panic(fmt.Errorf("len(RequestInitChain.Validators) != len(validators) (%d != %d)",
len(req.Validators), len(validators)))
}
sort.Sort(abci.ValidatorUpdates(req.Validators))
sort.Sort(abci.ValidatorUpdates(validators))
Expand Down
162 changes: 94 additions & 68 deletions cmd/gaia/app/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var (
// bonded tokens given to genesis validators/accounts
freeFermionVal = int64(100)
freeFermionsAcc = sdk.NewInt(150)
bondDenom = "steak"
)

// State to Unmarshal
Expand Down Expand Up @@ -90,58 +91,60 @@ func GaiaAppInit() server.AppInit {

// Create the core parameters for genesis initialization for gaia
// note that the pubkey input is this machines pubkey
func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisState GenesisState, err error) {
if len(appGenTxs) == 0 {
err = errors.New("must provide at least genesis transaction")
return
}
func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []json.RawMessage) (
genesisState GenesisState, err error) {

// start with the default staking genesis state
stakeData := stake.DefaultGenesisState()
slashingData := slashing.DefaultGenesisState()
if err = cdc.UnmarshalJSON(genDoc.AppState, &genesisState); err != nil {
return genesisState, err
}

// get genesis flag account information
genaccs := make([]GenesisAccount, len(appGenTxs))
// 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")
}

stakeData := genesisState.StakeData
for i, genTx := range appGenTxs {
var tx auth.StdTx
err = cdc.UnmarshalJSON(genTx, &tx)
if err != nil {
return
if err := cdc.UnmarshalJSON(genTx, &tx); err != nil {
return genesisState, err
}
msgs := tx.GetMsgs()
if len(msgs) != 1 {
err = errors.New("must provide genesis StdTx with exactly 1 CreateValidator message")
return
return genesisState, errors.New(
"must provide genesis StdTx with exactly 1 CreateValidator message")
}
msg := msgs[0].(stake.MsgCreateValidator)
if _, ok := msgs[0].(stake.MsgCreateValidator); !ok {
return genesisState, fmt.Errorf(
"Genesis transaction %v does not contain a MsgCreateValidator", i)
}
}

for _, acc := range genesisState.Accounts {
// create the genesis account, give'm few steaks and a buncha token with there name
genaccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc)
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply
for _, coin := range acc.Coins {
if coin.Denom == bondDenom {
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.
Add(sdk.NewDecFromInt(coin.Amount)) // increase the supply
}
}
}
genesisState.StakeData = stakeData
genesisState.GenTxs = appGenTxs
return genesisState, nil
}

// create the final app state
genesisState = GenesisState{
Accounts: genaccs,
StakeData: stakeData,
// NewDefaultGenesisState generates the default state for gaia.
func NewDefaultGenesisState() GenesisState {
return GenesisState{
alessio marked this conversation as resolved.
Show resolved Hide resolved
Accounts: nil,
StakeData: stake.DefaultGenesisState(),
MintData: mint.DefaultGenesisState(),
DistrData: distr.DefaultGenesisState(),
GovData: gov.DefaultGenesisState(),
SlashingData: slashingData,
GenTxs: appGenTxs,
}

return
}

func genesisAccountFromMsgCreateValidator(msg stake.MsgCreateValidator, amount sdk.Int) GenesisAccount {
accAuth := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr))
accAuth.Coins = []sdk.Coin{
{msg.Description.Moniker + "Token", sdk.NewInt(1000)},
{"steak", amount},
SlashingData: slashing.DefaultGenesisState(),
GenTxs: nil,
}
return NewGenesisAccount(&accAuth)
}

// GaiaValidateGenesisState ensures that the genesis state obeys the expected invariants
Expand Down Expand Up @@ -175,27 +178,43 @@ func validateGenesisStateAccounts(accs []GenesisAccount) (err error) {
}

// GaiaAppGenState but with JSON
func GaiaAppGenStateJSON(cdc *codec.Codec, appGenTxs []json.RawMessage) (appState json.RawMessage, err error) {
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, appGenTxs)
genesisState, err := GaiaAppGenState(cdc, genDoc, appGenTxs)
if err != nil {
return nil, err
}
appState, err = codec.MarshalJSONIndent(cdc, genesisState)
return
return codec.MarshalJSONIndent(cdc, genesisState)
}

// CollectStdTxs processes and validates application's genesis StdTxs and returns the list of validators,
// appGenTxs, and persistent peers required to generate genesis.json.
func CollectStdTxs(moniker string, genTxsDir string, cdc *codec.Codec) (
validators []tmtypes.GenesisValidator, appGenTxs []auth.StdTx, persistentPeers string, err error) {
// 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
alessio marked this conversation as resolved.
Show resolved Hide resolved
fos, err = ioutil.ReadDir(genTxsDir)
if err != nil {
return
return appGenTxs, persistentPeers, err
}

var addresses []string
// 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]
strAddr := string(acc.Address)
addrMap[strAddr] = 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") {
Expand All @@ -204,48 +223,55 @@ func CollectStdTxs(moniker string, genTxsDir string, cdc *codec.Codec) (

// get the genStdTx
var jsonRawTx []byte
jsonRawTx, err = ioutil.ReadFile(filename)
if err != nil {
return
if jsonRawTx, err = ioutil.ReadFile(filename); err != nil {
return appGenTxs, persistentPeers, err
}
var genStdTx auth.StdTx
err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx)
if err != nil {
return
if err = cdc.UnmarshalJSON(jsonRawTx, &genStdTx); err != nil {
return appGenTxs, persistentPeers, err
}
appGenTxs = append(appGenTxs, genStdTx)

nodeAddr := genStdTx.GetMemo()
if len(nodeAddr) == 0 {
err = fmt.Errorf("couldn't find node's address in %s", fo.Name())
return
// 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 {
err = errors.New("each genesis transaction must provide a single genesis message")
return

return appGenTxs, persistentPeers, errors.New(
"each genesis transaction must provide a single genesis message")
}

// TODO: this could be decoupled from stake.MsgCreateValidator
// TODO: and we likely want to do it for real world Gaia
// validate the validator address and funds against the accounts in the state
msg := msgs[0].(stake.MsgCreateValidator)
validators = append(validators, tmtypes.GenesisValidator{
PubKey: msg.PubKey,
Power: freeFermionVal,
Name: msg.Description.Moniker,
})
addr := string(sdk.AccAddress(msg.ValidatorAddr))
acc, ok := addrMap[addr]
if !ok {
return appGenTxs, persistentPeers, fmt.Errorf(
"account %v not in genesis.json: %+v", addr, addrMap)
}
if acc.Coins.AmountOf(msg.Delegation.Denom).LT(msg.Delegation.Amount) {
err = fmt.Errorf("insufficient fund for the delegation: %s < %s",
acc.Coins.AmountOf(msg.Delegation.Denom), msg.Delegation.Amount)
}

// exclude itself from persistent peers
if msg.Description.Moniker != moniker {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary as per @rigelrozanski

addresses = append(addresses, nodeAddr)
addressesIPs = append(addressesIPs, nodeAddrIP)
}
}

sort.Strings(addresses)
persistentPeers = strings.Join(addresses, ",")
sort.Strings(addressesIPs)
persistentPeers = strings.Join(addressesIPs, ",")

return
return appGenTxs, persistentPeers, nil
}

func NewDefaultGenesisAccount(addr sdk.AccAddress) GenesisAccount {
Expand Down
44 changes: 33 additions & 11 deletions cmd/gaia/app/genesis_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package app

import (
"encoding/json"
"github.com/tendermint/tendermint/crypto/secp256k1"
tmtypes "github.com/tendermint/tendermint/types"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/stake"
stakeTypes "github.com/cosmos/cosmos-sdk/x/stake/types"
"github.com/stretchr/testify/require"
Expand All @@ -27,25 +29,24 @@ var (

func makeGenesisState(t *testing.T, genTxs []auth.StdTx) GenesisState {
// start with the default staking genesis state
stakeData := stake.DefaultGenesisState()
appState := NewDefaultGenesisState()
stakeData := appState.StakeData
genAccs := make([]GenesisAccount, len(genTxs))

for i, genTx := range genTxs {
msgs := genTx.GetMsgs()
require.Equal(t, 1, len(msgs))
msg := msgs[0].(stake.MsgCreateValidator)

// get genesis flag account information
genAccs[i] = genesisAccountFromMsgCreateValidator(msg, freeFermionsAcc)
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDecFromInt(freeFermionsAcc)) // increase the supply
acc := auth.NewBaseAccountWithAddress(sdk.AccAddress(msg.ValidatorAddr))
acc.Coins = sdk.Coins{sdk.NewInt64Coin(bondDenom, 150)}
genAccs[i] = NewGenesisAccount(&acc)
stakeData.Pool.LooseTokens = stakeData.Pool.LooseTokens.Add(sdk.NewDec(150)) // increase the supply
}

// create the final app state
return GenesisState{
Accounts: genAccs,
StakeData: stakeData,
GovData: gov.DefaultGenesisState(),
}
appState.Accounts = genAccs
return appState
}

func TestToAccount(t *testing.T) {
Expand All @@ -68,6 +69,19 @@ func TestGaiaAppGenTx(t *testing.T) {
func TestGaiaAppGenState(t *testing.T) {
cdc := MakeCodec()
_ = cdc
var genDoc tmtypes.GenesisDoc

// test unmarshalling error
_, err := GaiaAppGenState(cdc, genDoc, []json.RawMessage{})
require.Error(t, err)

appState := makeGenesisState(t, []auth.StdTx{})
genDoc.AppState, err = json.Marshal(appState)
require.NoError(t, err)

// test validation error
_, err = GaiaAppGenState(cdc, genDoc, []json.RawMessage{})
require.Error(t, err)

// TODO test must provide at least genesis transaction
// TODO test with both one and two genesis transactions:
Expand All @@ -77,7 +91,8 @@ func TestGaiaAppGenState(t *testing.T) {
func makeMsg(name string, pk crypto.PubKey) auth.StdTx {
desc := stake.NewDescription(name, "", "", "")
comm := stakeTypes.CommissionMsg{}
msg := stake.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin("steak", 50), desc, comm)
msg := stake.NewMsgCreateValidator(sdk.ValAddress(pk.Address()), pk, sdk.NewInt64Coin(bondDenom,
50), desc, comm)
return auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, nil, "")
}

Expand Down Expand Up @@ -106,3 +121,10 @@ func TestGaiaGenesisValidation(t *testing.T) {
err = GaiaValidateGenesisState(genesisState)
require.NotNil(t, err)
}

func TestNewDefaultGenesisAccount(t *testing.T) {
addr := secp256k1.GenPrivKeySecp256k1([]byte("")).PubKey().Address()
acc := NewDefaultGenesisAccount(sdk.AccAddress(addr))
require.Equal(t, sdk.NewInt(1000), acc.Coins.AmountOf("fooToken"))
require.Equal(t, sdk.NewInt(150), acc.Coins.AmountOf(bondDenom))
}
Loading