diff --git a/Gopkg.lock b/Gopkg.lock index 0e9681ed39eb..8dd4be708335 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -689,6 +689,7 @@ "github.com/tendermint/tendermint/rpc/lib/client", "github.com/tendermint/tendermint/rpc/lib/server", "github.com/tendermint/tendermint/types", + "github.com/tendermint/tendermint/types/time", "github.com/tendermint/tendermint/version", "github.com/zondax/ledger-goclient", "golang.org/x/crypto/bcrypt", diff --git a/PENDING.md b/PENDING.md index c399a5099809..b3885942b9bc 100644 --- a/PENDING.md +++ b/PENDING.md @@ -64,7 +64,8 @@ BUG FIXES * Gaia CLI (`gaiacli`) * Gaia - - \#2670 [x/stake] fixed incorrent `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators` + - \#2670 [x/stake] fixed incorrect `IterateBondedValidators` and split into two functions: `IterateBondedValidators` and `IterateLastBlockConsValidators` + - \#2691 Fix local testnet creation by using a single canonical genesis time * SDK - \#2625 [x/gov] fix AppendTag function usage error diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 6c331856c345..579ad93a85d8 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -2,9 +2,10 @@ package app import ( "encoding/json" + "testing" + "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" @@ -91,8 +92,10 @@ 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(bondDenom, - 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, "") } diff --git a/cmd/gaia/init/collect.go b/cmd/gaia/init/collect.go index 34b65e560bce..97d9743a06c7 100644 --- a/cmd/gaia/init/collect.go +++ b/cmd/gaia/init/collect.go @@ -2,7 +2,6 @@ package init import ( "encoding/json" - "io/ioutil" "path/filepath" "github.com/cosmos/cosmos-sdk/client" @@ -12,7 +11,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" "github.com/spf13/cobra" "github.com/spf13/viper" - "github.com/tendermint/go-amino" cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/cli" @@ -35,12 +33,13 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config config.SetRoot(viper.GetString(cli.HomeFlag)) - name := viper.GetString(client.FlagName) + nodeID, valPubKey, err := InitializeNodeValidatorFiles(config) if err != nil { return err } + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) if err != nil { return err @@ -59,12 +58,14 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { NodeID: nodeID, ValPubKey: valPubKey, } + appMessage, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) if err != nil { return err } toPrint.AppMessage = appMessage + // print out some key information return displayInfo(cdc, toPrint) }, @@ -74,23 +75,29 @@ func CollectGenTxsCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { return cmd } -func genAppStateFromConfig(cdc *codec.Codec, config *cfg.Config, initCfg initConfig, - genDoc types.GenesisDoc) (appState json.RawMessage, err error) { +func genAppStateFromConfig( + cdc *codec.Codec, config *cfg.Config, initCfg initConfig, genDoc types.GenesisDoc, +) (appState json.RawMessage, err error) { genFile := config.GenesisFile() - // process genesis transactions, else create default genesis.json - var appGenTxs []auth.StdTx - var persistentPeers string - var genTxs []json.RawMessage - var jsonRawTx json.RawMessage + var ( + appGenTxs []auth.StdTx + persistentPeers string + genTxs []json.RawMessage + jsonRawTx json.RawMessage + ) + // process genesis transactions, else create default genesis.json appGenTxs, persistentPeers, err = app.CollectStdTxs( - cdc, config.Moniker, initCfg.GenTxsDir, genDoc) + cdc, config.Moniker, initCfg.GenTxsDir, genDoc, + ) if err != nil { return } + genTxs = make([]json.RawMessage, len(appGenTxs)) config.P2P.PersistentPeers = persistentPeers + for i, stdTx := range appGenTxs { jsonRawTx, err = cdc.MarshalJSON(stdTx) if err != nil { @@ -100,6 +107,7 @@ func genAppStateFromConfig(cdc *codec.Codec, config *cfg.Config, initCfg initCon } cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) + appState, err = app.GaiaAppGenStateJSON(cdc, genDoc, genTxs) if err != nil { return @@ -108,12 +116,3 @@ func genAppStateFromConfig(cdc *codec.Codec, config *cfg.Config, initCfg initCon err = WriteGenesisFile(genFile, initCfg.ChainID, nil, appState) return } - -func loadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { - genContents, err := ioutil.ReadFile(genFile) - if err != nil { - return - } - err = cdc.UnmarshalJSON(genContents, &genDoc) - return -} diff --git a/cmd/gaia/init/init.go b/cmd/gaia/init/init.go index 0290ae4fb2bb..f2a815a5c30a 100644 --- a/cmd/gaia/init/init.go +++ b/cmd/gaia/init/init.go @@ -3,7 +3,6 @@ package init import ( "encoding/json" "fmt" - "github.com/tendermint/tendermint/privval" "os" "path/filepath" @@ -14,11 +13,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" cfg "github.com/tendermint/tendermint/config" - "github.com/tendermint/tendermint/crypto" "github.com/tendermint/tendermint/libs/cli" "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/p2p" - "github.com/tendermint/tendermint/types" ) const ( @@ -97,54 +93,3 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec, appInit server.AppInit) *cob cmd.Flags().String(flagMoniker, "", "set the validator's moniker") return cmd } - -// InitializeNodeValidatorFiles creates private validator and p2p configuration files. -func InitializeNodeValidatorFiles(config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error) { - nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) - if err != nil { - return - } - nodeID = string(nodeKey.ID()) - valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile()) - return -} - -// WriteGenesisFile creates and writes the genesis configuration to disk. An -// error is returned if building or writing the configuration to file fails. -// nolint: unparam -func WriteGenesisFile(genesisFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage) error { - genDoc := types.GenesisDoc{ - ChainID: chainID, - Validators: validators, - AppState: appState, - } - - if err := genDoc.ValidateAndComplete(); err != nil { - return err - } - - return genDoc.SaveAs(genesisFile) -} - -// read of create the private key file for this config -func ReadOrCreatePrivValidator(privValFile string) crypto.PubKey { - // private validator - var privValidator *privval.FilePV - if common.FileExists(privValFile) { - privValidator = privval.LoadFilePV(privValFile) - } else { - privValidator = privval.GenFilePV(privValFile) - privValidator.Save() - } - return privValidator.GetPubKey() -} - -func initializeEmptyGenesis(cdc *codec.Codec, genFile string, chainID string, - overwrite bool) (appState json.RawMessage, err error) { - if !overwrite && common.FileExists(genFile) { - err = fmt.Errorf("genesis.json file already exists: %v", genFile) - return - } - - return codec.MarshalJSONIndent(cdc, app.NewDefaultGenesisState()) -} diff --git a/cmd/gaia/init/testnet.go b/cmd/gaia/init/testnet.go index 5f729b0a4b7e..42d8332d27fb 100644 --- a/cmd/gaia/init/testnet.go +++ b/cmd/gaia/init/testnet.go @@ -3,6 +3,10 @@ package init import ( "encoding/json" "fmt" + "net" + "os" + "path/filepath" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/codec" @@ -10,10 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" "github.com/cosmos/cosmos-sdk/x/stake" - "github.com/tendermint/tendermint/types" - "net" - "os" - "path/filepath" "github.com/cosmos/cosmos-sdk/server" "github.com/spf13/cobra" @@ -21,16 +21,17 @@ import ( cfg "github.com/tendermint/tendermint/config" "github.com/tendermint/tendermint/crypto" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" + tmtime "github.com/tendermint/tendermint/types/time" ) var ( - nodeDirPrefix = "node-dir-prefix" - nValidators = "v" - outputDir = "output-dir" - nodeDaemonHome = "node-daemon-home" - nodeCliHome = "node-cli-home" - - startingIPAddress = "starting-ip-address" + flagNodeDirPrefix = "node-dir-prefix" + flagNumValidators = "v" + flagOutputDir = "output-dir" + flagNodeDaemonHome = "node-daemon-home" + flagNodeCliHome = "node-cli-home" + flagStartingIPAddress = "starting-ip-address" ) const nodeDirPerm = 0755 @@ -48,50 +49,59 @@ necessary files (private validator, genesis, config, etc.). Note, strict routability for addresses is turned off in the config file. Example: - - gaiad testnet --v 4 --o ./output --starting-ip-address 192.168.10.2 + gaiad testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2 `, RunE: func(_ *cobra.Command, _ []string) error { config := ctx.Config - return testnetWithConfig(config, cdc) + return initTestnet(config, cdc) }, } - cmd.Flags().Int(nValidators, 4, - "Number of validators to initialize the testnet with") - cmd.Flags().StringP(outputDir, "o", "./mytestnet", - "Directory to store initialization data for the testnet") - cmd.Flags().String(nodeDirPrefix, "node", - "Prefix the directory name for each node with (node results in node0, node1, ...)") - cmd.Flags().String(nodeDaemonHome, "gaiad", - "Home directory of the node's daemon configuration") - cmd.Flags().String(nodeCliHome, "gaiacli", - "Home directory of the node's cli configuration") - - cmd.Flags().String(startingIPAddress, "192.168.0.1", + + cmd.Flags().Int(flagNumValidators, 4, + "Number of validators to initialize the testnet with", + ) + cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", + "Directory to store initialization data for the testnet", + ) + cmd.Flags().String(flagNodeDirPrefix, "node", + "Prefix the directory name for each node with (node results in node0, node1, ...)", + ) + cmd.Flags().String(flagNodeDaemonHome, "gaiad", + "Home directory of the node's daemon configuration", + ) + cmd.Flags().String(flagNodeCliHome, "gaiacli", + "Home directory of the node's cli configuration", + ) + cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)") + return cmd } -func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { - outDir := viper.GetString(outputDir) - numValidators := viper.GetInt(nValidators) +func initTestnet(config *cfg.Config, cdc *codec.Codec) error { + outDir := viper.GetString(flagOutputDir) + numValidators := viper.GetInt(flagNumValidators) - // Generate genesis.json and config.toml chainID := "chain-" + cmn.RandStr(6) + monikers := make([]string, numValidators) nodeIDs := make([]string, numValidators) valPubKeys := make([]crypto.PubKey, numValidators) - // Generate private key, node ID, initial transaction - var accs []app.GenesisAccount - var genFiles []string + var ( + accs []app.GenesisAccount + genFiles []string + ) + + // generate private keys, node IDs, and initial transactions for i := 0; i < numValidators; i++ { - nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDaemonHomeName := viper.GetString(nodeDaemonHome) - nodeCliHomeName := viper.GetString(nodeCliHome) + nodeDirName := fmt.Sprintf("%s%d", viper.GetString(flagNodeDirPrefix), i) + nodeDaemonHomeName := viper.GetString(flagNodeDaemonHome) + nodeCliHomeName := viper.GetString(flagNodeCliHome) nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) clientDir := filepath.Join(outDir, nodeDirName, nodeCliHomeName) gentxsDir := filepath.Join(outDir, "gentxs") + config.SetRoot(nodeDir) err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm) @@ -108,24 +118,27 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { monikers = append(monikers, nodeDirName) config.Moniker = nodeDirName - ip, err := getIP(i) + + ip, err := getIP(i, viper.GetString(flagStartingIPAddress)) if err != nil { _ = os.RemoveAll(outDir) return err } + nodeIDs[i], valPubKeys[i], err = InitializeNodeValidatorFiles(config) if err != nil { _ = os.RemoveAll(outDir) return err } - memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) - // write genesis + memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip) genFiles = append(genFiles, config.GenesisFile()) buf := client.BufferStdin() prompt := fmt.Sprintf( - "Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass) + "Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass, + ) + keyPass, err := client.GetPassword(prompt, buf) if err != nil && keyPass != "" { // An error was returned that either failed to read the password from @@ -133,6 +146,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { // length requirements. return err } + if keyPass == "" { keyPass = app.DefaultKeyPass } @@ -142,11 +156,14 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { _ = os.RemoveAll(outDir) return err } + info := map[string]string{"secret": secret} + cliPrint, err := json.Marshal(info) if err != nil { return err } + // save private key seed words err = writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint) if err != nil { @@ -170,6 +187,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { ) tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo) txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo) + signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false) if err != nil { _ = os.RemoveAll(outDir) @@ -182,7 +200,7 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { return err } - // Gather gentxs folder + // gather gentxs folder err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes) if err != nil { _ = os.RemoveAll(outDir) @@ -190,37 +208,70 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { } } - // Generate empty genesis.json + if err := initGenFiles(cdc, chainID, accs, genFiles, numValidators); err != nil { + return err + } + + err := collectGenFiles( + cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators, + outDir, viper.GetString(flagNodeDirPrefix), viper.GetString(flagNodeDaemonHome), + ) + if err != nil { + return err + } + + fmt.Printf("Successfully initialized %d node directories\n", numValidators) + return nil +} + +func initGenFiles( + cdc *codec.Codec, chainID string, accs []app.GenesisAccount, + genFiles []string, numValidators int, +) error { + appGenState := app.NewDefaultGenesisState() appGenState.Accounts = accs + appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState) if err != nil { return err } + genDoc := types.GenesisDoc{ ChainID: chainID, AppState: appGenStateJSON, Validators: nil, } - // Save all genesis.json files + // generate empty genesis files for each validator and save for i := 0; i < numValidators; i++ { if err := genDoc.SaveAs(genFiles[i]); err != nil { return err } } + return nil +} + +func collectGenFiles( + cdc *codec.Codec, config *cfg.Config, chainID string, + monikers, nodeIDs []string, valPubKeys []crypto.PubKey, + numValidators int, outDir, nodeDirPrefix, nodeDaemonHomeName string, +) error { + + var appState json.RawMessage + genTime := tmtime.Now() + for i := 0; i < numValidators; i++ { - nodeDirName := fmt.Sprintf("%s%d", viper.GetString(nodeDirPrefix), i) - nodeDaemonHomeName := viper.GetString(nodeDaemonHome) + nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i) nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName) gentxsDir := filepath.Join(outDir, "gentxs") moniker := monikers[i] config.Moniker = nodeDirName + config.SetRoot(nodeDir) nodeID, valPubKey := nodeIDs[i], valPubKeys[i] - // Run `init` and generate genesis.json and config.toml initCfg := initConfig{ ChainID: chainID, GenTxsDir: gentxsDir, @@ -228,46 +279,69 @@ func testnetWithConfig(config *cfg.Config, cdc *codec.Codec) error { NodeID: nodeID, ValPubKey: valPubKey, } + genDoc, err := loadGenesisDoc(cdc, config.GenesisFile()) if err != nil { return err } - if _, err := genAppStateFromConfig(cdc, config, initCfg, genDoc); err != nil { + + nodeAppState, err := genAppStateFromConfig(cdc, config, initCfg, genDoc) + if err != nil { + return err + } + + if appState == nil { + // set the canonical application state (they should not differ) + appState = nodeAppState + } + + genFile := config.GenesisFile() + + // overwrite each validator's genesis file to have a canonical genesis time + err = WriteGenesisFileWithTime(genFile, chainID, nil, appState, genTime) + if err != nil { return err } } - fmt.Printf("Successfully initialized %v node directories\n", viper.GetInt(nValidators)) return nil } -func getIP(i int) (ip string, err error) { - ip = viper.GetString(startingIPAddress) - if len(ip) == 0 { +func getIP(i int, startingIPAddr string) (string, error) { + var ( + ip string + err error + ) + + if len(startingIPAddr) == 0 { ip, err = server.ExternalIP() if err != nil { return "", err } } else { - ip, err = calculateIP(ip, i) + ip, err = calculateIP(startingIPAddr, i) if err != nil { return "", err } } + return ip, nil } func writeFile(name string, dir string, contents []byte) error { writePath := filepath.Join(dir) file := filepath.Join(writePath, name) + err := cmn.EnsureDir(writePath, 0700) if err != nil { return err } + err = cmn.WriteFile(file, contents, 0600) if err != nil { return err } + return nil } @@ -280,5 +354,6 @@ func calculateIP(ip string, i int) (string, error) { for j := 0; j < i; j++ { ipv4[3]++ } + return ipv4.String(), nil } diff --git a/cmd/gaia/init/utils.go b/cmd/gaia/init/utils.go new file mode 100644 index 000000000000..1738433705e7 --- /dev/null +++ b/cmd/gaia/init/utils.go @@ -0,0 +1,112 @@ +package init + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "time" + + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" + "github.com/cosmos/cosmos-sdk/codec" + amino "github.com/tendermint/go-amino" + cfg "github.com/tendermint/tendermint/config" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" + "github.com/tendermint/tendermint/privval" + "github.com/tendermint/tendermint/types" +) + +// WriteGenesisFile creates and writes the genesis configuration to disk. An +// error is returned if building or writing the configuration to file fails. +func WriteGenesisFile( + genFile, chainID string, validators []types.GenesisValidator, appState json.RawMessage, +) error { + + genDoc := types.GenesisDoc{ + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + return genDoc.SaveAs(genFile) +} + +// WriteGenesisFileWithTime creates and writes the genesis configuration to disk. +// An error is returned if building or writing the configuration to file fails. +func WriteGenesisFileWithTime( + genFile, chainID string, validators []types.GenesisValidator, + appState json.RawMessage, genTime time.Time, +) error { + + genDoc := types.GenesisDoc{ + GenesisTime: genTime, + ChainID: chainID, + Validators: validators, + AppState: appState, + } + + if err := genDoc.ValidateAndComplete(); err != nil { + return err + } + + return genDoc.SaveAs(genFile) +} + +// read of create the private key file for this config +func ReadOrCreatePrivValidator(privValFile string) crypto.PubKey { + var privValidator *privval.FilePV + + if common.FileExists(privValFile) { + privValidator = privval.LoadFilePV(privValFile) + } else { + privValidator = privval.GenFilePV(privValFile) + privValidator.Save() + } + + return privValidator.GetPubKey() +} + +// InitializeNodeValidatorFiles creates private validator and p2p configuration files. +func InitializeNodeValidatorFiles( + config *cfg.Config) (nodeID string, valPubKey crypto.PubKey, err error, +) { + + nodeKey, err := p2p.LoadOrGenNodeKey(config.NodeKeyFile()) + if err != nil { + return nodeID, valPubKey, err + } + + nodeID = string(nodeKey.ID()) + valPubKey = ReadOrCreatePrivValidator(config.PrivValidatorFile()) + + return nodeID, valPubKey, nil +} + +func loadGenesisDoc(cdc *amino.Codec, genFile string) (genDoc types.GenesisDoc, err error) { + genContents, err := ioutil.ReadFile(genFile) + if err != nil { + return genDoc, err + } + + if err := cdc.UnmarshalJSON(genContents, &genDoc); err != nil { + return genDoc, err + } + + return genDoc, err +} + +func initializeEmptyGenesis( + cdc *codec.Codec, genFile, chainID string, overwrite bool, +) (appState json.RawMessage, err error) { + + if !overwrite && common.FileExists(genFile) { + return nil, fmt.Errorf("genesis.json file already exists: %v", genFile) + } + + return codec.MarshalJSONIndent(cdc, app.NewDefaultGenesisState()) +}