laconicd/client/testnet.go

554 lines
16 KiB
Go
Raw Normal View History

package client
// DONTCOVER
import (
"bufio"
2021-04-18 15:54:18 +00:00
"bytes"
"encoding/json"
2021-04-17 10:00:07 +00:00
"errors"
"fmt"
2021-04-18 15:54:18 +00:00
"io/ioutil"
"log"
"net"
"os"
"path/filepath"
2021-04-18 15:54:18 +00:00
"strings"
2021-04-18 15:54:18 +00:00
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/spf13/cobra"
2021-04-17 10:00:07 +00:00
tmconfig "github.com/tendermint/tendermint/config"
tmos "github.com/tendermint/tendermint/libs/os"
tmrand "github.com/tendermint/tendermint/libs/rand"
2021-04-17 10:00:07 +00:00
"github.com/tendermint/tendermint/types"
tmtime "github.com/tendermint/tendermint/types/time"
2021-04-17 10:00:07 +00:00
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
2021-04-17 10:00:07 +00:00
"github.com/cosmos/cosmos-sdk/client/tx"
"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"
2021-04-17 10:00:07 +00:00
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"
2021-04-17 10:00:07 +00:00
mintypes "github.com/cosmos/cosmos-sdk/x/mint/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
2021-04-18 15:54:18 +00:00
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/ethermint/crypto/hd"
2021-04-18 15:54:18 +00:00
chaintypes "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
2021-04-18 15:54:18 +00:00
"github.com/cosmos/ethermint/cmd/injectived/config"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
"github.com/ethereum/go-ethereum/common"
)
var (
2021-04-17 10:00:07 +00:00
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
2021-04-17 10:00:07 +00:00
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.`,
2021-04-18 15:54:18 +00:00
Example: "injectived testnet --v 4 --keyring-backend test --output-dir ./output --ip-addresses 192.168.10.2",
RunE: func(cmd *cobra.Command, _ []string) error {
2021-04-17 10:00:07 +00:00
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)
2021-04-17 10:00:07 +00:00
algo, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm)
coinDenom, _ := cmd.Flags().GetString(flagCoinDenom)
2021-04-17 10:00:07 +00:00
if len(ipAddresses) == 0 {
return errors.New("IP address list cannot be empty")
}
return InitTestnet(
2021-04-17 10:00:07 +00:00
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")
2021-04-17 10:00:07 +00:00
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, ...)")
2021-04-18 15:54:18 +00:00
cmd.Flags().String(flagNodeDaemonHome, "injectived", "Home directory of the node's daemon configuration")
2021-04-17 10:00:07 +00:00
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")
2021-04-18 15:54:18 +00:00
cmd.Flags().String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01inj,0.001stake)")
cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
2021-04-17 10:00:07 +00:00
cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.EthSecp256k1Type), "Key signing algorithm to generate keys for")
2021-04-18 15:54:18 +00:00
cmd.Flags().String(flagCoinDenom, chaintypes.InjectiveCoin, "Coin denomination used for staking, governance, mint, crisis and evm parameters")
return cmd
}
// InitTestnet initializes the testnet configuration
func InitTestnet(
2021-04-17 10:00:07 +00:00
clientCtx client.Context,
cmd *cobra.Command,
2021-04-17 10:00:07 +00:00
nodeConfig *tmconfig.Config,
mbm module.BasicManager,
2021-04-17 10:00:07 +00:00
genBalIterator banktypes.GenesisBalancesIterator,
outputDir,
chainID,
coinDenom,
minGasPrices,
nodeDirPrefix,
nodeDaemonHome,
keyringBackend,
2021-04-17 10:00:07 +00:00
algoStr string,
ipAddresses []string,
numValidators int,
) error {
if chainID == "" {
2021-04-18 15:54:18 +00:00
chainID = fmt.Sprintf("injective-%d", tmrand.Int63n(9999999999999)+1)
}
2021-04-18 15:54:18 +00:00
if !chaintypes.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)
2021-04-17 10:00:07 +00:00
valPubKeys := make([]cryptotypes.PubKey, numValidators)
2021-04-18 15:54:18 +00:00
appConfig := config.DefaultConfig()
2021-04-17 10:00:07 +00:00
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 (
2021-04-17 10:00:07 +00:00
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")
2021-04-17 10:00:07 +00:00
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
}
2021-04-17 10:00:07 +00:00
nodeConfig.Moniker = nodeDirName
2021-04-17 10:00:07 +00:00
var (
ip string
err error
)
2021-04-17 10:00:07 +00:00
if len(ipAddresses) == 1 {
ip, err = getIP(i, ipAddresses[0])
if err != nil {
_ = os.RemoveAll(outputDir)
return err
}
} else {
ip = ipAddresses[i]
}
2021-04-17 10:00:07 +00:00
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)
2021-04-17 10:00:07 +00:00
genFiles = append(genFiles, nodeConfig.GenesisFile())
2021-04-17 10:00:07 +00:00
kb, err := keyring.New(
sdk.KeyringServiceName(),
keyringBackend,
2021-04-17 10:00:07 +00:00
nodeDir,
inBuf,
2021-04-17 10:00:07 +00:00
hd.EthSecp256k1Option(),
)
if err != nil {
return err
}
2021-04-17 10:00:07 +00:00
keyringAlgos, _ := kb.SupportedAlgorithms()
algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos)
if err != nil {
return err
}
2021-04-17 10:00:07 +00:00
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
2021-04-17 10:00:07 +00:00
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),
)
2021-04-17 10:00:07 +00:00
genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins})
2021-04-18 15:54:18 +00:00
genAccounts = append(genAccounts, &chaintypes.EthAccount{
2021-04-17 10:00:07 +00:00
BaseAccount: authtypes.NewBaseAccount(addr, nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),
})
valTokens := sdk.TokensFromConsensusPower(100)
2021-04-17 10:00:07 +00:00
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
}
2021-04-17 10:00:07 +00:00
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
}
2021-04-17 10:00:07 +00:00
if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil {
return err
}
2021-04-18 15:54:18 +00:00
config.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), appConfig)
ethPrivKey, err := keyring.NewUnsafe(kb).UnsafeExportPrivKeyHex(nodeDirName)
initPeggo(outputDir, nodeDirName, []byte(strings.ToUpper(ethPrivKey)))
}
2021-04-17 10:00:07 +00:00
if err := initGenFiles(clientCtx, mbm, chainID, coinDenom, genAccounts, genBalances, genFiles, numValidators); err != nil {
return err
}
err := collectGenFiles(
2021-04-17 10:00:07 +00:00
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
}
2021-04-18 15:54:18 +00:00
func initPeggo(outputDir string, nodeDirName string, privKey []byte) {
peggoDir := filepath.Join(outputDir, nodeDirName, "peggo")
if envdata, _ := ioutil.ReadFile("./templates/peggo_config.template"); len(envdata) > 0 {
s := bufio.NewScanner(bytes.NewReader(envdata))
for s.Scan() {
parts := strings.Split(s.Text(), "=")
if len(parts) != 2 {
continue
} else {
content := []byte(s.Text())
if parts[0] == "PEGGY_COSMOS_PRIVKEY" {
content = append([]byte(parts[0]+"="), privKey...)
} else if parts[0] == "PEGGY_ETH_PRIVATE_KEY" {
newPrivkey, _ := ethsecp256k1.GenerateKey()
privKeyStr := common.Bytes2Hex(newPrivkey.GetKey())
privKeyBytes := []byte(strings.ToUpper(privKeyStr))
content = append([]byte(parts[0]+"="), privKeyBytes...)
}
if err := appendToFile(fmt.Sprintf("config.env"), peggoDir, content); err != nil {
fmt.Println("Error writing peggo config", "error", err)
}
}
}
}
}
func initGenFiles(
2021-04-17 10:00:07 +00:00
clientCtx client.Context,
mbm module.BasicManager,
chainID,
coinDenom string,
genAccounts []authtypes.GenesisAccount,
genBalances []banktypes.Balance,
genFiles []string,
numValidators int,
) error {
2021-04-17 10:00:07 +00:00
appGenState := mbm.DefaultGenesis(clientCtx.JSONMarshaler)
// set the accounts in the genesis state
var authGenState authtypes.GenesisState
2021-04-17 10:00:07 +00:00
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)
2021-04-17 10:00:07 +00:00
// 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
2021-04-17 10:00:07 +00:00
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[stakingtypes.ModuleName], &stakingGenState)
stakingGenState.Params.BondDenom = coinDenom
2021-04-17 10:00:07 +00:00
appGenState[stakingtypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&stakingGenState)
var govGenState govtypes.GenesisState
2021-04-17 10:00:07 +00:00
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[govtypes.ModuleName], &govGenState)
govGenState.DepositParams.MinDeposit[0].Denom = coinDenom
2021-04-17 10:00:07 +00:00
appGenState[govtypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&govGenState)
2021-04-17 10:00:07 +00:00
var mintGenState mintypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[mintypes.ModuleName], &mintGenState)
mintGenState.Params.MintDenom = coinDenom
2021-04-17 10:00:07 +00:00
appGenState[mintypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&mintGenState)
2021-04-17 10:00:07 +00:00
var crisisGenState crisistypes.GenesisState
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[crisistypes.ModuleName], &crisisGenState)
crisisGenState.ConstantFee.Denom = coinDenom
2021-04-17 10:00:07 +00:00
appGenState[crisistypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&crisisGenState)
var evmGenState evmtypes.GenesisState
2021-04-17 10:00:07 +00:00
clientCtx.JSONMarshaler.MustUnmarshalJSON(appGenState[evmtypes.ModuleName], &evmGenState)
evmGenState.Params.EvmDenom = coinDenom
2021-04-17 10:00:07 +00:00
appGenState[evmtypes.ModuleName] = clientCtx.JSONMarshaler.MustMarshalJSON(&evmGenState)
2021-04-17 10:00:07 +00:00
appGenStateJSON, err := json.MarshalIndent(appGenState, "", " ")
if err != nil {
return err
}
2021-04-17 10:00:07 +00:00
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(
2021-04-17 10:00:07 +00:00
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")
2021-04-17 10:00:07 +00:00
nodeConfig.Moniker = nodeDirName
2021-04-17 10:00:07 +00:00
nodeConfig.SetRoot(nodeDir)
nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
2021-04-17 10:00:07 +00:00
initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, valPubKey)
2021-04-17 10:00:07 +00:00
genDoc, err := types.GenesisDocFromFile(nodeConfig.GenesisFile())
if err != nil {
return err
}
2021-04-17 10:00:07 +00:00
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
}
2021-04-17 10:00:07 +00:00
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
}
2021-04-18 15:54:18 +00:00
func appendToFile(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
}
if _, err = os.Stat(file); err == nil {
err = os.Chmod(file, 0777)
if err != nil {
fmt.Println(err)
return err
}
}
f, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
return err
}
defer f.Close()
_, err = f.Write(contents)
if err != nil {
log.Println(err)
return err
}
f.Write([]byte("\n"))
if err != nil {
log.Println(err)
return err
}
return nil
}