2019-06-20 13:37:57 +00:00
|
|
|
package init
|
2019-06-07 11:59:19 +00:00
|
|
|
|
|
|
|
// DONTCOVER
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
"github.com/cosmos/cosmos-sdk/client/keys"
|
|
|
|
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
|
|
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
|
|
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
|
|
"github.com/cosmos/cosmos-sdk/x/auth"
|
|
|
|
authtx "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder"
|
|
|
|
"github.com/cosmos/cosmos-sdk/x/staking"
|
2019-06-20 17:08:58 +00:00
|
|
|
"github.com/kava-labs/kava/app"
|
2019-06-20 13:37:57 +00:00
|
|
|
|
2019-06-07 11:59:19 +00:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
tmconfig "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"
|
|
|
|
|
|
|
|
"github.com/cosmos/cosmos-sdk/server"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
flagNodeDirPrefix = "node-dir-prefix"
|
|
|
|
flagNumValidators = "v"
|
|
|
|
flagOutputDir = "output-dir"
|
|
|
|
flagNodeDaemonHome = "node-daemon-home"
|
2019-06-20 13:37:57 +00:00
|
|
|
flagNodeCliHome = "node-cli-home"
|
2019-06-07 11:59:19 +00:00
|
|
|
flagStartingIPAddress = "starting-ip-address"
|
|
|
|
)
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
const nodeDirPerm = 0755
|
|
|
|
|
2019-06-07 11:59:19 +00:00
|
|
|
// get cmd to initialize all files for tendermint testnet and application
|
2019-06-20 13:37:57 +00:00
|
|
|
func TestnetFilesCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command {
|
2019-06-07 11:59:19 +00:00
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
Use: "testnet",
|
2019-06-20 17:08:58 +00:00
|
|
|
Short: "Initialize files for a testnet",
|
2019-06-07 11:59:19 +00:00
|
|
|
Long: `testnet will create "v" number of directories and populate each with
|
|
|
|
necessary files (private validator, genesis, config, etc.).
|
|
|
|
|
|
|
|
Note, strict routability for addresses is turned off in the config file.
|
|
|
|
|
|
|
|
Example:
|
2019-06-20 17:08:58 +00:00
|
|
|
kvd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2
|
2019-06-07 11:59:19 +00:00
|
|
|
`,
|
|
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
|
|
config := ctx.Config
|
2019-06-20 13:37:57 +00:00
|
|
|
return initTestnet(config, cdc)
|
2019-06-07 11:59:19 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd.Flags().Int(flagNumValidators, 4,
|
2019-06-20 13:37:57 +00:00
|
|
|
"Number of validators to initialize the testnet with",
|
|
|
|
)
|
2019-06-07 11:59:19 +00:00
|
|
|
cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet",
|
2019-06-20 13:37:57 +00:00
|
|
|
"Directory to store initialization data for the testnet",
|
|
|
|
)
|
2019-06-07 11:59:19 +00:00
|
|
|
cmd.Flags().String(flagNodeDirPrefix, "node",
|
2019-06-20 13:37:57 +00:00
|
|
|
"Prefix the directory name for each node with (node results in node0, node1, ...)",
|
|
|
|
)
|
2019-06-20 17:08:58 +00:00
|
|
|
cmd.Flags().String(flagNodeDaemonHome, "kvd",
|
2019-06-20 13:37:57 +00:00
|
|
|
"Home directory of the node's daemon configuration",
|
|
|
|
)
|
2019-06-20 17:08:58 +00:00
|
|
|
cmd.Flags().String(flagNodeCliHome, "kvcli",
|
2019-06-20 13:37:57 +00:00
|
|
|
"Home directory of the node's cli configuration",
|
|
|
|
)
|
2019-06-07 11:59:19 +00:00
|
|
|
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, ...)")
|
2019-06-20 13:37:57 +00:00
|
|
|
|
2019-06-07 11:59:19 +00:00
|
|
|
cmd.Flags().String(
|
2019-06-20 13:37:57 +00:00
|
|
|
client.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created",
|
|
|
|
)
|
2019-06-07 11:59:19 +00:00
|
|
|
cmd.Flags().String(
|
|
|
|
server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom),
|
2019-06-20 13:37:57 +00:00
|
|
|
"Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)",
|
|
|
|
)
|
|
|
|
|
2019-06-07 11:59:19 +00:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
func initTestnet(config *tmconfig.Config, cdc *codec.Codec) error {
|
|
|
|
var chainID string
|
2019-06-07 11:59:19 +00:00
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
outDir := viper.GetString(flagOutputDir)
|
|
|
|
numValidators := viper.GetInt(flagNumValidators)
|
2019-06-07 11:59:19 +00:00
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
chainID = viper.GetString(client.FlagChainID)
|
2019-06-07 11:59:19 +00:00
|
|
|
if chainID == "" {
|
|
|
|
chainID = "chain-" + cmn.RandStr(6)
|
|
|
|
}
|
|
|
|
|
|
|
|
monikers := make([]string, numValidators)
|
|
|
|
nodeIDs := make([]string, numValidators)
|
|
|
|
valPubKeys := make([]crypto.PubKey, numValidators)
|
|
|
|
|
2019-06-20 17:28:21 +00:00
|
|
|
appConfig := srvconfig.DefaultConfig()
|
|
|
|
appConfig.MinGasPrices = viper.GetString(server.FlagMinGasPrices)
|
2019-06-07 11:59:19 +00:00
|
|
|
|
|
|
|
var (
|
2019-06-20 13:37:57 +00:00
|
|
|
accs []app.GenesisAccount
|
2019-06-07 11:59:19 +00:00
|
|
|
genFiles []string
|
|
|
|
)
|
|
|
|
|
|
|
|
// generate private keys, node IDs, and initial transactions
|
|
|
|
for i := 0; i < numValidators; i++ {
|
2019-06-20 13:37:57 +00:00
|
|
|
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")
|
2019-06-07 11:59:19 +00:00
|
|
|
|
|
|
|
config.SetRoot(nodeDir)
|
|
|
|
|
|
|
|
err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
|
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err = os.MkdirAll(clientDir, nodeDirPerm)
|
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
monikers = append(monikers, nodeDirName)
|
|
|
|
config.Moniker = nodeDirName
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
ip, err := getIP(i, viper.GetString(flagStartingIPAddress))
|
2019-06-07 11:59:19 +00:00
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
nodeIDs[i], valPubKeys[i], err = InitializeNodeValidatorFiles(config)
|
2019-06-07 11:59:19 +00:00
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
|
|
|
|
genFiles = append(genFiles, config.GenesisFile())
|
|
|
|
|
|
|
|
buf := client.BufferStdin()
|
|
|
|
prompt := fmt.Sprintf(
|
2019-06-20 13:37:57 +00:00
|
|
|
"Password for account '%s' (default %s):", nodeDirName, app.DefaultKeyPass,
|
2019-06-07 11:59:19 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
keyPass, err := client.GetPassword(prompt, buf)
|
|
|
|
if err != nil && keyPass != "" {
|
|
|
|
// An error was returned that either failed to read the password from
|
|
|
|
// STDIN or the given password is not empty but failed to meet minimum
|
|
|
|
// length requirements.
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if keyPass == "" {
|
2019-06-20 13:37:57 +00:00
|
|
|
keyPass = app.DefaultKeyPass
|
2019-06-07 11:59:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
addr, secret, err := server.GenerateSaveCoinKey(clientDir, nodeDirName, keyPass, true)
|
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
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 {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
accTokens := sdk.TokensFromTendermintPower(1000)
|
|
|
|
accStakingTokens := sdk.TokensFromTendermintPower(500)
|
2019-06-20 13:37:57 +00:00
|
|
|
accs = append(accs, app.GenesisAccount{
|
2019-06-07 11:59:19 +00:00
|
|
|
Address: addr,
|
|
|
|
Coins: sdk.Coins{
|
|
|
|
sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens),
|
|
|
|
sdk.NewCoin(sdk.DefaultBondDenom, accStakingTokens),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
valTokens := sdk.TokensFromTendermintPower(100)
|
|
|
|
msg := staking.NewMsgCreateValidator(
|
|
|
|
sdk.ValAddress(addr),
|
|
|
|
valPubKeys[i],
|
|
|
|
sdk.NewCoin(sdk.DefaultBondDenom, valTokens),
|
|
|
|
staking.NewDescription(nodeDirName, "", "", ""),
|
2019-06-20 13:37:57 +00:00
|
|
|
staking.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
|
2019-06-07 11:59:19 +00:00
|
|
|
sdk.OneInt(),
|
|
|
|
)
|
|
|
|
kb, err := keys.NewKeyBaseFromDir(clientDir)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
|
2019-06-20 13:37:57 +00:00
|
|
|
txBldr := authtx.NewTxBuilderFromCLI().WithChainID(chainID).WithMemo(memo).WithKeybase(kb)
|
2019-06-07 11:59:19 +00:00
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
signedTx, err := txBldr.SignStdTx(nodeDirName, app.DefaultKeyPass, tx, false)
|
2019-06-07 11:59:19 +00:00
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
txBytes, err := cdc.MarshalJSON(signedTx)
|
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// gather gentxs folder
|
|
|
|
err = writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes)
|
|
|
|
if err != nil {
|
2019-06-20 13:37:57 +00:00
|
|
|
_ = os.RemoveAll(outDir)
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-06-20 17:28:21 +00:00
|
|
|
appConfigFilePath := filepath.Join(nodeDir, "config/kvd.toml")
|
|
|
|
srvconfig.WriteConfigFile(appConfigFilePath, appConfig)
|
2019-06-07 11:59:19 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
if err := initGenFiles(cdc, chainID, accs, genFiles, numValidators); err != nil {
|
2019-06-07 11:59:19 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
err := collectGenFiles(
|
|
|
|
cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators,
|
2019-06-20 13:37:57 +00:00
|
|
|
outDir, viper.GetString(flagNodeDirPrefix), viper.GetString(flagNodeDaemonHome),
|
2019-06-07 11:59:19 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
fmt.Printf("Successfully initialized %d node directories\n", numValidators)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
func initGenFiles(
|
|
|
|
cdc *codec.Codec, chainID string, accs []app.GenesisAccount,
|
|
|
|
genFiles []string, numValidators int,
|
|
|
|
) error {
|
2019-06-07 11:59:19 +00:00
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
appGenState := app.NewDefaultGenesisState()
|
|
|
|
appGenState.Accounts = accs
|
2019-06-07 11:59:19 +00:00
|
|
|
|
|
|
|
appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
genDoc := types.GenesisDoc{
|
|
|
|
ChainID: chainID,
|
|
|
|
AppState: appGenStateJSON,
|
|
|
|
Validators: nil,
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
}
|
2019-06-20 13:37:57 +00:00
|
|
|
|
2019-06-07 11:59:19 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func collectGenFiles(
|
|
|
|
cdc *codec.Codec, config *tmconfig.Config, chainID string,
|
|
|
|
monikers, nodeIDs []string, valPubKeys []crypto.PubKey,
|
2019-06-20 13:37:57 +00:00
|
|
|
numValidators int, outDir, nodeDirPrefix, nodeDaemonHomeName string,
|
|
|
|
) error {
|
2019-06-07 11:59:19 +00:00
|
|
|
|
|
|
|
var appState json.RawMessage
|
|
|
|
genTime := tmtime.Now()
|
|
|
|
|
|
|
|
for i := 0; i < numValidators; i++ {
|
|
|
|
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
|
2019-06-20 13:37:57 +00:00
|
|
|
nodeDir := filepath.Join(outDir, nodeDirName, nodeDaemonHomeName)
|
|
|
|
gentxsDir := filepath.Join(outDir, "gentxs")
|
2019-06-07 11:59:19 +00:00
|
|
|
moniker := monikers[i]
|
|
|
|
config.Moniker = nodeDirName
|
|
|
|
|
|
|
|
config.SetRoot(nodeDir)
|
|
|
|
|
|
|
|
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
|
2019-06-20 13:37:57 +00:00
|
|
|
initCfg := newInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
|
2019-06-07 11:59:19 +00:00
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
genDoc, err := LoadGenesisDoc(cdc, config.GenesisFile())
|
2019-06-07 11:59:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
nodeAppState, err := genAppStateFromConfig(cdc, config, initCfg, genDoc)
|
2019-06-07 11:59:19 +00:00
|
|
|
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
|
2019-06-20 13:37:57 +00:00
|
|
|
err = ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime)
|
2019-06-07 11:59:19 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
func getIP(i int, startingIPAddr string) (string, error) {
|
|
|
|
var (
|
|
|
|
ip string
|
|
|
|
err error
|
|
|
|
)
|
|
|
|
|
2019-06-07 11:59:19 +00:00
|
|
|
if len(startingIPAddr) == 0 {
|
|
|
|
ip, err = server.ExternalIP()
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-06-20 13:37:57 +00:00
|
|
|
} else {
|
|
|
|
ip, err = calculateIP(startingIPAddr, i)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
2019-06-07 11:59:19 +00:00
|
|
|
}
|
|
|
|
|
2019-06-20 13:37:57 +00:00
|
|
|
return ip, nil
|
2019-06-07 11:59:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2019-06-20 13:37:57 +00:00
|
|
|
|
|
|
|
func calculateIP(ip string, i int) (string, error) {
|
|
|
|
ipv4 := net.ParseIP(ip).To4()
|
|
|
|
if ipv4 == nil {
|
|
|
|
return "", fmt.Errorf("%v: non ipv4 address", ip)
|
|
|
|
}
|
|
|
|
|
|
|
|
for j := 0; j < i; j++ {
|
|
|
|
ipv4[3]++
|
|
|
|
}
|
|
|
|
|
|
|
|
return ipv4.String(), nil
|
|
|
|
}
|