laconicd/client/testnet.go
Federico Kunze 5a3d514ba0
conflicts
2021-04-17 12:00:07 +02:00

479 lines
14 KiB
Go

package client
// DONTCOVER
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"net"
"os"
"path/filepath"
"github.com/spf13/cobra"
tmconfig "github.com/tendermint/tendermint/config"
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"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/server"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
crisistypes "github.com/cosmos/cosmos-sdk/x/crisis/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
mintypes "github.com/cosmos/cosmos-sdk/x/mint/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/cosmos/ethermint/crypto/hd"
srvconfig "github.com/cosmos/ethermint/server/config"
ethermint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
var (
flagNodeDirPrefix = "node-dir-prefix"
flagNumValidators = "v"
flagOutputDir = "output-dir"
flagNodeDaemonHome = "node-daemon-home"
flagCoinDenom = "coin-denom"
flagIPAddrs = "ip-addresses"
)
const nodeDirPerm = 0755
// TestnetCmd initializes all files for tendermint testnet and application
func TestnetCmd(
mbm module.BasicManager, genBalancesIterator banktypes.GenesisBalancesIterator,
) *cobra.Command {
cmd := &cobra.Command{
Use: "testnet",
Short: "Initialize files for a Ethermint 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: "ethermintd testnet --v 4 --keyring-backend test --output-dir ./output --ip-addresses 192.168.10.2",
RunE: func(cmd *cobra.Command, _ []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
serverCtx := server.GetServerContextFromCmd(cmd)
config := serverCtx.Config
outputDir, _ := cmd.Flags().GetString(flagOutputDir)
keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend)
chainID, _ := cmd.Flags().GetString(flags.FlagChainID)
minGasPrices, _ := cmd.Flags().GetString(server.FlagMinGasPrices)
nodeDirPrefix, _ := cmd.Flags().GetString(flagNodeDirPrefix)
nodeDaemonHome, _ := cmd.Flags().GetString(flagNodeDaemonHome)
ipAddresses, _ := cmd.Flags().GetStringSlice(flagIPAddrs)
numValidators, _ := cmd.Flags().GetInt(flagNumValidators)
algo, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm)
coinDenom, _ := cmd.Flags().GetString(flagCoinDenom)
if len(ipAddresses) == 0 {
return errors.New("IP address list cannot be empty")
}
return InitTestnet(
clientCtx, cmd, config, mbm, genBalancesIterator, outputDir, chainID, coinDenom, minGasPrices,
nodeDirPrefix, nodeDaemonHome, keyringBackend, algo, ipAddresses, 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().StringSlice(flagIPAddrs, []string{"192.168.0.1"}, "List of IP addresses to use (i.e. `192.168.0.1,172.168.0.1` results in persistent peers list ID0@192.168.0.1:46656, ID1@172.168.0.1)")
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", ethermint.AttoPhoton), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01aphoton,0.001stake)")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.EthSecp256k1Type), "Key signing algorithm to generate keys for")
cmd.Flags().String(flagCoinDenom, ethermint.AttoPhoton, "Coin denomination used for staking, governance, mint, crisis and evm parameters")
return cmd
}
// InitTestnet initializes the testnet configuration
func InitTestnet(
clientCtx client.Context,
cmd *cobra.Command,
nodeConfig *tmconfig.Config,
mbm module.BasicManager,
genBalIterator banktypes.GenesisBalancesIterator,
outputDir,
chainID,
coinDenom,
minGasPrices,
nodeDirPrefix,
nodeDaemonHome,
keyringBackend,
algoStr string,
ipAddresses []string,
numValidators int,
) error {
if chainID == "" {
chainID = fmt.Sprintf("ethermint-%d", tmrand.Int63n(9999999999999)+1)
}
if !ethermint.IsValidChainID(chainID) {
return fmt.Errorf("invalid chain-id: %s", chainID)
}
if err := sdk.ValidateDenom(coinDenom); err != nil {
return err
}
if len(ipAddresses) != 0 {
numValidators = len(ipAddresses)
}
nodeIDs := make([]string, numValidators)
valPubKeys := make([]cryptotypes.PubKey, numValidators)
appConfig := srvconfig.DefaultConfig()
appConfig.MinGasPrices = minGasPrices
appConfig.API.Enable = true
appConfig.Telemetry.Enabled = true
appConfig.Telemetry.PrometheusRetentionTime = 60
appConfig.Telemetry.EnableHostnameLabel = false
appConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", chainID}}
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)
gentxsDir := filepath.Join(outputDir, "gentxs")
nodeConfig.SetRoot(nodeDir)
nodeConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657"
if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil {
_ = os.RemoveAll(outputDir)
return err
}
nodeConfig.Moniker = nodeDirName
var (
ip string
err error
)
if len(ipAddresses) == 1 {
ip, err = getIP(i, ipAddresses[0])
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
} else {
ip = ipAddresses[i]
}
nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig)
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
genFiles = append(genFiles, nodeConfig.GenesisFile())
kb, err := keyring.New(
sdk.KeyringServiceName(),
keyringBackend,
nodeDir,
inBuf,
hd.EthSecp256k1Option(),
)
if err != nil {
return err
}
keyringAlgos, _ := kb.SupportedAlgorithms()
algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos)
if err != nil {
return err
}
addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, true, algo)
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"), nodeDir, cliPrint); err != nil {
return err
}
accStakingTokens := sdk.TokensFromConsensusPower(5000)
coins := sdk.NewCoins(
sdk.NewCoin(coinDenom, accStakingTokens),
)
genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins})
genAccounts = append(genAccounts, &ethermint.EthAccount{
BaseAccount: authtypes.NewBaseAccount(addr, nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),
})
valTokens := sdk.TokensFromConsensusPower(100)
createValMsg, err := stakingtypes.NewMsgCreateValidator(
sdk.ValAddress(addr),
valPubKeys[i],
sdk.NewCoin(coinDenom, valTokens),
stakingtypes.NewDescription(nodeDirName, "", "", "", ""),
stakingtypes.NewCommissionRates(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()),
sdk.OneInt(),
)
if err != nil {
return err
}
txBuilder := clientCtx.TxConfig.NewTxBuilder()
if err := txBuilder.SetMsgs(createValMsg); err != nil {
return err
}
txBuilder.SetMemo(memo)
txFactory := tx.Factory{}
txFactory = txFactory.
WithChainID(chainID).
WithMemo(memo).
WithKeybase(kb).
WithTxConfig(clientCtx.TxConfig)
if err := tx.Sign(txFactory, nodeDirName, txBuilder, false); err != nil {
return err
}
txBz, err := clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
if err != nil {
return err
}
if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil {
return err
}
srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), appConfig)
}
if err := initGenFiles(clientCtx, mbm, chainID, coinDenom, genAccounts, genBalances, genFiles, numValidators); err != nil {
return err
}
err := collectGenFiles(
clientCtx, nodeConfig, chainID, 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(
clientCtx client.Context,
mbm module.BasicManager,
chainID,
coinDenom string,
genAccounts []authtypes.GenesisAccount,
genBalances []banktypes.Balance,
genFiles []string,
numValidators int,
) error {
appGenState := mbm.DefaultGenesis(clientCtx.JSONMarshaler)
// set the accounts in the genesis state
var authGenState authtypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[authtypes.ModuleName], &authGenState)
accounts, err := authtypes.PackAccounts(genAccounts)
if err != nil {
return err
}
authGenState.Accounts = accounts
appGenState[authtypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&authGenState)
// set the balances in the genesis state
var bankGenState banktypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState)
bankGenState.Balances = genBalances
appGenState[banktypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&bankGenState)
var stakingGenState stakingtypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[stakingtypes.ModuleName], &stakingGenState)
stakingGenState.Params.BondDenom = coinDenom
appGenState[stakingtypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&stakingGenState)
var govGenState govtypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[govtypes.ModuleName], &govGenState)
govGenState.DepositParams.MinDeposit[0].Denom = coinDenom
appGenState[govtypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&govGenState)
var mintGenState mintypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[mintypes.ModuleName], &mintGenState)
mintGenState.Params.MintDenom = coinDenom
appGenState[mintypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&mintGenState)
var crisisGenState crisistypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[crisistypes.ModuleName], &crisisGenState)
crisisGenState.ConstantFee.Denom = coinDenom
appGenState[crisistypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&crisisGenState)
var evmGenState evmtypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[evmtypes.ModuleName], &evmGenState)
evmGenState.Params.EvmDenom = coinDenom
appGenState[evmtypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&evmGenState)
appGenStateJSON, err := json.MarshalIndent(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(
clientCtx client.Context, nodeConfig *tmconfig.Config, chainID string,
nodeIDs []string, valPubKeys []cryptotypes.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")
nodeConfig.Moniker = nodeDirName
nodeConfig.SetRoot(nodeDir)
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, valPubKey)
genDoc, err := types.GenesisDocFromFile(nodeConfig.GenesisFile())
if err != nil {
return err
}
nodeAppState, err := genutil.GenAppStateFromConfig(clientCtx.JSONMarshaler, clientCtx.TxConfig, nodeConfig, initCfg, *genDoc, genBalIterator)
if err != nil {
return err
}
if appState == nil {
// set the canonical application state (they should not differ)
appState = nodeAppState
}
genFile := nodeConfig.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
}