cosmos-sdk/simapp/cmd/simd/testnet.go

390 lines
11 KiB
Go

package main
// DONTCOVER
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/spf13/viper"
tmconfig "github.com/tendermint/tendermint/config"
"github.com/tendermint/tendermint/crypto"
tmos "github.com/tendermint/tendermint/libs/os"
tmrand "github.com/tendermint/tendermint/libs/rand"
"github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/cosmos/cosmos-sdk/client/flags"
clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/server"
srvconfig "github.com/cosmos/cosmos-sdk/server/config"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/x/auth"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
var (
flagNodeDirPrefix = "node-dir-prefix"
flagNumValidators = "v"
flagOutputDir = "output-dir"
flagNodeDaemonHome = "node-daemon-home"
flagNodeCLIHome = "node-cli-home"
flagStartingIPAddress = "starting-ip-address"
)
// get cmd to initialize all files for tendermint testnet and application
func testnetCmd(ctx *server.Context, cdc codec.JSONMarshaler,
mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator,
) *cobra.Command {
cmd := &cobra.Command{
Use: "testnet",
Short: "Initialize files for a simapp testnet",
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:
simd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2
`,
RunE: func(cmd *cobra.Command, _ []string) error {
config := ctx.Config
outputDir := viper.GetString(flagOutputDir)
chainID := viper.GetString(flags.FlagChainID)
minGasPrices := viper.GetString(server.FlagMinGasPrices)
nodeDirPrefix := viper.GetString(flagNodeDirPrefix)
nodeDaemonHome := viper.GetString(flagNodeDaemonHome)
nodeCLIHome := viper.GetString(flagNodeCLIHome)
startingIPAddress := viper.GetString(flagStartingIPAddress)
numValidators := viper.GetInt(flagNumValidators)
return InitTestnet(
cmd, config, cdc, mbm, genBalIterator, outputDir, chainID, minGasPrices,
nodeDirPrefix, nodeDaemonHome, nodeCLIHome, startingIPAddress, numValidators,
)
},
}
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, "simd",
"Home directory of the node's daemon configuration")
cmd.Flags().String(flagNodeCLIHome, "simcli",
"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, ...)")
cmd.Flags().String(
flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().String(
server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom),
"Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
return cmd
}
const nodeDirPerm = 0755
// Initialize the testnet
func InitTestnet(
cmd *cobra.Command, config *tmconfig.Config, cdc codec.JSONMarshaler,
mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator,
outputDir, chainID, minGasPrices, nodeDirPrefix, nodeDaemonHome,
nodeCLIHome, startingIPAddress string, numValidators int,
) error {
if chainID == "" {
chainID = "chain-" + tmrand.NewRand().Str(6)
}
monikers := make([]string, numValidators)
nodeIDs := make([]string, numValidators)
valPubKeys := make([]crypto.PubKey, numValidators)
simappConfig := srvconfig.DefaultConfig()
simappConfig.MinGasPrices = minGasPrices
simappConfig.API.Enable = true
var (
genAccounts []authtypes.GenesisAccount
genBalances []banktypes.Balance
genFiles []string
)
inBuf := bufio.NewReader(cmd.InOrStdin())
// generate private keys, node IDs, and initial transactions
for i := 0; i < numValidators; i++ {
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
clientDir := filepath.Join(outputDir, nodeDirName, nodeCLIHome)
gentxsDir := filepath.Join(outputDir, "gentxs")
config.SetRoot(nodeDir)
config.RPC.ListenAddress = "tcp://0.0.0.0:26657"
if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil {
_ = os.RemoveAll(outputDir)
return err
}
if err := os.MkdirAll(clientDir, nodeDirPerm); err != nil {
_ = os.RemoveAll(outputDir)
return err
}
monikers = append(monikers, nodeDirName)
config.Moniker = nodeDirName
ip, err := getIP(i, startingIPAddress)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(config)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
genFiles = append(genFiles, config.GenesisFile())
kb, err := keyring.New(
sdk.KeyringServiceName(),
viper.GetString(flags.FlagKeyringBackend),
clientDir,
inBuf,
)
if err != nil {
return err
}
keyPass := clientkeys.DefaultKeyPass
addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, keyPass, true)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
info := map[string]string{"secret": secret}
cliPrint, err := json.Marshal(info)
if err != nil {
return err
}
// save private key seed words
if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint); err != nil {
return err
}
accTokens := sdk.TokensFromConsensusPower(1000)
accStakingTokens := sdk.TokensFromConsensusPower(500)
coins := sdk.Coins{
sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens),
sdk.NewCoin(sdk.DefaultBondDenom, accStakingTokens),
}
genBalances = append(genBalances, banktypes.Balance{Address: addr, Coins: coins.Sort()})
genAccounts = append(genAccounts, auth.NewBaseAccount(addr, nil, 0, 0))
valTokens := sdk.TokensFromConsensusPower(100)
msg := stakingtypes.NewMsgCreateValidator(
sdk.ValAddress(addr),
valPubKeys[i],
sdk.NewCoin(sdk.DefaultBondDenom, valTokens),
stakingtypes.NewDescription(nodeDirName, "", "", "", ""),
stakingtypes.NewCommissionRates(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()),
sdk.OneInt(),
)
tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
txBldr := auth.NewTxBuilderFromCLI(inBuf).WithChainID(chainID).WithMemo(memo).WithKeybase(kb)
signedTx, err := txBldr.SignStdTx(nodeDirName, tx, false)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
txBytes, err := cdc.MarshalJSON(signedTx)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
// gather gentxs folder
if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes); err != nil {
_ = os.RemoveAll(outputDir)
return err
}
srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), simappConfig)
}
if err := initGenFiles(cdc, mbm, chainID, genAccounts, genBalances, genFiles, numValidators); err != nil {
return err
}
err := collectGenFiles(
cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators,
outputDir, nodeDirPrefix, nodeDaemonHome, genBalIterator,
)
if err != nil {
return err
}
cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators)
return nil
}
func initGenFiles(
cdc codec.JSONMarshaler, mbm module.BasicManager, chainID string,
genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance,
genFiles []string, numValidators int,
) error {
appGenState := mbm.DefaultGenesis(cdc)
// set the accounts in the genesis state
var authGenState auth.GenesisState
cdc.MustUnmarshalJSON(appGenState[auth.ModuleName], &authGenState)
authGenState.Accounts = genAccounts
appGenState[auth.ModuleName] = cdc.MustMarshalJSON(authGenState)
// set the balances in the genesis state
var bankGenState banktypes.GenesisState
cdc.MustUnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState)
bankGenState.Balances = genBalances
appGenState[banktypes.ModuleName] = cdc.MustMarshalJSON(bankGenState)
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
}
}
return nil
}
func collectGenFiles(
cdc codec.JSONMarshaler, config *tmconfig.Config, chainID string,
monikers, nodeIDs []string, valPubKeys []crypto.PubKey,
numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string,
genBalIterator banktypes.GenesisBalancesIterator,
) error {
var appState json.RawMessage
genTime := tmtime.Now()
for i := 0; i < numValidators; i++ {
nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
gentxsDir := filepath.Join(outputDir, "gentxs")
moniker := monikers[i]
config.Moniker = nodeDirName
config.SetRoot(nodeDir)
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
if err != nil {
return err
}
nodeAppState, err := genutil.GenAppStateFromConfig(cdc, config, initCfg, *genDoc, genBalIterator)
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
if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil {
return err
}
}
return nil
}
func getIP(i int, startingIPAddr string) (ip string, err error) {
if len(startingIPAddr) == 0 {
ip, err = server.ExternalIP()
if err != nil {
return "", err
}
return ip, nil
}
return calculateIP(startingIPAddr, i)
}
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
}
func writeFile(name string, dir string, contents []byte) error {
writePath := filepath.Join(dir)
file := filepath.Join(writePath, name)
err := tmos.EnsureDir(writePath, 0755)
if err != nil {
return err
}
err = tmos.WriteFile(file, contents, 0644)
if err != nil {
return err
}
return nil
}