2020-08-04 18:52:32 +00:00
package client
2020-07-31 21:42:04 +00:00
// DONTCOVER
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/spf13/cobra"
tmconfig "github.com/tendermint/tendermint/config"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmos "github.com/tendermint/tendermint/libs/os"
tmrand "github.com/tendermint/tendermint/libs/rand"
tmtypes "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"
2020-08-23 21:41:54 +00:00
"github.com/cosmos/cosmos-sdk/crypto/keys"
2020-07-31 21:42:04 +00:00
"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"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
2020-08-23 21:41:54 +00:00
"github.com/cosmos/cosmos-sdk/x/crisis"
2020-07-31 21:42:04 +00:00
"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"
2020-08-23 21:41:54 +00:00
"github.com/cosmos/cosmos-sdk/x/mint"
2020-07-31 21:42:04 +00:00
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
2020-10-06 18:57:55 +00:00
"github.com/cosmos/ethermint/crypto/hd"
2020-09-24 17:50:47 +00:00
ethermint "github.com/cosmos/ethermint/types"
2020-09-08 14:39:48 +00:00
evmtypes "github.com/cosmos/ethermint/x/evm/types"
2020-07-31 21:42:04 +00:00
)
var (
flagNodeDirPrefix = "node-dir-prefix"
flagNumValidators = "v"
flagOutputDir = "output-dir"
flagNodeDaemonHome = "node-daemon-home"
flagNodeCLIHome = "node-cli-home"
flagStartingIPAddress = "starting-ip-address"
2020-09-08 14:39:48 +00:00
flagCoinDenom = "coin-denom"
2020-08-12 14:25:57 +00:00
flagKeyAlgo = "algo"
2020-09-25 23:00:49 +00:00
flagIPAddrs = "ip-addrs"
2020-07-31 21:42:04 +00:00
)
const nodeDirPerm = 0755
2020-08-04 18:52:32 +00:00
// TestnetCmd initializes all files for tendermint testnet and application
func TestnetCmd ( ctx * server . Context , cdc * codec . Codec ,
2020-08-23 21:41:54 +00:00
mbm module . BasicManager , genAccIterator authtypes . GenesisAccountIterator ,
2020-07-31 21:42:04 +00:00
) * 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 . ` ,
2020-08-05 15:17:50 +00:00
Example : "ethermintd testnet --v 4 --keyring-backend test --output-dir ./output --starting-ip-address 192.168.10.2" ,
2020-07-31 21:42:04 +00:00
RunE : func ( cmd * cobra . Command , _ [ ] string ) error {
config := ctx . 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 )
nodeCLIHome , _ := cmd . Flags ( ) . GetString ( flagNodeCLIHome )
startingIPAddress , _ := cmd . Flags ( ) . GetString ( flagStartingIPAddress )
2020-09-25 23:00:49 +00:00
ipAddresses , _ := cmd . Flags ( ) . GetStringSlice ( flagIPAddrs )
2020-07-31 21:42:04 +00:00
numValidators , _ := cmd . Flags ( ) . GetInt ( flagNumValidators )
2020-09-08 14:39:48 +00:00
coinDenom , _ := cmd . Flags ( ) . GetString ( flagCoinDenom )
2020-08-12 14:25:57 +00:00
algo , _ := cmd . Flags ( ) . GetString ( flagKeyAlgo )
2020-07-31 21:42:04 +00:00
return InitTestnet (
2020-09-08 14:39:48 +00:00
cmd , config , cdc , mbm , genAccIterator , outputDir , chainID , coinDenom , minGasPrices ,
2020-09-25 23:00:49 +00:00
nodeDirPrefix , nodeDaemonHome , nodeCLIHome , startingIPAddress , ipAddresses , keyringBackend , algo , numValidators ,
2020-07-31 21:42:04 +00:00
)
} ,
}
cmd . Flags ( ) . Int ( flagNumValidators , 4 , "Number of validators to initialize the testnet with" )
cmd . Flags ( ) . StringP ( flagOutputDir , "o" , "./build" , "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, ...)" )
2020-08-05 15:17:50 +00:00
cmd . Flags ( ) . String ( flagNodeDaemonHome , "ethermintd" , "Home directory of the node's daemon configuration" )
cmd . Flags ( ) . String ( flagNodeCLIHome , "ethermintcli" , "Home directory of the node's cli configuration" )
2020-07-31 21:42:04 +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, ...)" )
2020-09-25 23:00:49 +00:00
cmd . Flags ( ) . StringSlice ( flagIPAddrs , [ ] string { } , "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)" )
2020-07-31 21:42:04 +00:00
cmd . Flags ( ) . String ( flags . FlagChainID , "" , "genesis file chain-id, if left blank will be randomly created" )
2020-09-24 17:50:47 +00:00
cmd . Flags ( ) . String ( flagCoinDenom , ethermint . AttoPhoton , "Coin denomination used for staking, governance, mint, crisis and evm parameters" )
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)" )
2020-07-31 21:42:04 +00:00
cmd . Flags ( ) . String ( flags . FlagKeyringBackend , flags . DefaultKeyringBackend , "Select keyring's backend (os|file|test)" )
2020-10-06 18:57:55 +00:00
cmd . Flags ( ) . String ( flagKeyAlgo , string ( hd . EthSecp256k1 ) , "Key signing algorithm to generate keys for" )
2020-07-31 21:42:04 +00:00
return cmd
}
// InitTestnet initializes the testnet configuration
func InitTestnet (
2020-08-12 14:25:57 +00:00
cmd * cobra . Command ,
config * tmconfig . Config ,
cdc * codec . Codec ,
mbm module . BasicManager ,
2020-08-23 21:41:54 +00:00
genAccIterator authtypes . GenesisAccountIterator ,
2020-08-12 14:25:57 +00:00
outputDir ,
chainID ,
2020-09-08 14:39:48 +00:00
coinDenom ,
2020-08-12 14:25:57 +00:00
minGasPrices ,
nodeDirPrefix ,
nodeDaemonHome ,
nodeCLIHome ,
2020-09-25 23:00:49 +00:00
startingIPAddress string ,
ipAddresses [ ] string ,
2020-08-12 14:25:57 +00:00
keyringBackend ,
algo string ,
numValidators int ,
2020-07-31 21:42:04 +00:00
) error {
if chainID == "" {
2020-09-24 17:50:47 +00:00
chainID = fmt . Sprintf ( "ethermint-%d" , tmrand . Int63n ( 9999999999999 ) + 1 )
}
if ! ethermint . IsValidChainID ( chainID ) {
return fmt . Errorf ( "invalid chain-id: %s" , chainID )
2020-07-31 21:42:04 +00:00
}
2020-09-08 14:39:48 +00:00
if err := sdk . ValidateDenom ( coinDenom ) ; err != nil {
return err
}
2020-09-25 23:00:49 +00:00
if len ( ipAddresses ) != 0 {
numValidators = len ( ipAddresses )
}
2020-07-31 21:42:04 +00:00
nodeIDs := make ( [ ] string , numValidators )
valPubKeys := make ( [ ] tmcrypto . PubKey , numValidators )
simappConfig := srvconfig . DefaultConfig ( )
simappConfig . MinGasPrices = minGasPrices
var (
genAccounts [ ] authexported . GenesisAccount
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
}
config . Moniker = nodeDirName
2020-09-25 23:00:49 +00:00
var ip string
var err error
if len ( ipAddresses ) == 0 {
ip , err = getIP ( i , startingIPAddress )
if err != nil {
_ = os . RemoveAll ( outputDir )
return err
}
} else {
ip = ipAddresses [ i ]
2020-07-31 21:42:04 +00:00
}
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 ( ) )
2020-08-23 21:41:54 +00:00
kb , err := keys . NewKeyring (
2020-07-31 21:42:04 +00:00
sdk . KeyringServiceName ( ) ,
keyringBackend ,
clientDir ,
inBuf ,
2020-10-06 18:57:55 +00:00
hd . EthSecp256k1Options ( ) ... ,
2020-07-31 21:42:04 +00:00
)
if err != nil {
return err
}
cmd . Printf (
"Password for account '%s' :\n" , nodeDirName ,
)
keyPass := clientkeys . DefaultKeyPass
2020-08-23 21:41:54 +00:00
addr , secret , err := GenerateSaveCoinKey ( kb , nodeDirName , keyPass , true , keys . SigningAlgo ( algo ) )
2020-07-31 21:42:04 +00:00
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
}
accStakingTokens := sdk . TokensFromConsensusPower ( 5000 )
coins := sdk . NewCoins (
2020-09-08 14:39:48 +00:00
sdk . NewCoin ( coinDenom , accStakingTokens ) ,
2020-07-31 21:42:04 +00:00
)
2020-09-24 17:50:47 +00:00
genAccounts = append ( genAccounts , ethermint . EthAccount {
2020-08-23 21:41:54 +00:00
BaseAccount : authtypes . NewBaseAccount ( addr , coins , nil , 0 , 0 ) ,
2020-07-31 21:42:04 +00:00
CodeHash : ethcrypto . Keccak256 ( nil ) ,
} )
valTokens := sdk . TokensFromConsensusPower ( 100 )
msg := stakingtypes . NewMsgCreateValidator (
sdk . ValAddress ( addr ) ,
valPubKeys [ i ] ,
2020-09-08 14:39:48 +00:00
sdk . NewCoin ( coinDenom , valTokens ) ,
2020-07-31 21:42:04 +00:00
stakingtypes . NewDescription ( nodeDirName , "" , "" , "" , "" ) ,
stakingtypes . NewCommissionRates ( sdk . OneDec ( ) , sdk . OneDec ( ) , sdk . OneDec ( ) ) ,
sdk . OneInt ( ) ,
)
tx := authtypes . NewStdTx ( [ ] sdk . Msg { msg } , authtypes . StdFee { } , [ ] authtypes . StdSignature { } , memo ) //nolint:staticcheck // SA1019: authtypes.StdFee is deprecated
txBldr := authtypes . NewTxBuilderFromCLI ( inBuf ) . WithChainID ( chainID ) . WithMemo ( memo ) . WithKeybase ( kb )
signedTx , err := txBldr . SignStdTx ( nodeDirName , clientkeys . DefaultKeyPass , 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 )
}
2020-09-08 14:39:48 +00:00
if err := initGenFiles ( cdc , mbm , chainID , coinDenom , genAccounts , genFiles , numValidators ) ; err != nil {
2020-07-31 21:42:04 +00:00
return err
}
err := collectGenFiles (
cdc , config , chainID , nodeIDs , valPubKeys , numValidators ,
2020-08-23 21:41:54 +00:00
outputDir , nodeDirPrefix , nodeDaemonHome , genAccIterator ,
2020-07-31 21:42:04 +00:00
)
if err != nil {
return err
}
cmd . PrintErrf ( "Successfully initialized %d node directories\n" , numValidators )
return nil
}
func initGenFiles (
2020-09-08 14:39:48 +00:00
cdc * codec . Codec , mbm module . BasicManager ,
chainID , coinDenom string ,
2020-08-23 21:41:54 +00:00
genAccounts [ ] authexported . GenesisAccount ,
2020-07-31 21:42:04 +00:00
genFiles [ ] string , numValidators int ,
) error {
2020-08-23 21:41:54 +00:00
appGenState := mbm . DefaultGenesis ( )
2020-07-31 21:42:04 +00:00
// set the accounts in the genesis state
var authGenState authtypes . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ authtypes . ModuleName ] , & authGenState )
authGenState . Accounts = genAccounts
appGenState [ authtypes . ModuleName ] = cdc . MustMarshalJSON ( authGenState )
var stakingGenState stakingtypes . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ stakingtypes . ModuleName ] , & stakingGenState )
2020-09-08 14:39:48 +00:00
stakingGenState . Params . BondDenom = coinDenom
2020-07-31 21:42:04 +00:00
appGenState [ stakingtypes . ModuleName ] = cdc . MustMarshalJSON ( stakingGenState )
var govGenState govtypes . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ govtypes . ModuleName ] , & govGenState )
2020-09-08 14:39:48 +00:00
govGenState . DepositParams . MinDeposit [ 0 ] . Denom = coinDenom
2020-07-31 21:42:04 +00:00
appGenState [ govtypes . ModuleName ] = cdc . MustMarshalJSON ( govGenState )
2020-08-23 21:41:54 +00:00
var mintGenState mint . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ mint . ModuleName ] , & mintGenState )
2020-07-31 21:42:04 +00:00
2020-09-08 14:39:48 +00:00
mintGenState . Params . MintDenom = coinDenom
2020-08-23 21:41:54 +00:00
appGenState [ mint . ModuleName ] = cdc . MustMarshalJSON ( mintGenState )
2020-07-31 21:42:04 +00:00
2020-08-23 21:41:54 +00:00
var crisisGenState crisis . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ crisis . ModuleName ] , & crisisGenState )
2020-07-31 21:42:04 +00:00
2020-09-08 14:39:48 +00:00
crisisGenState . ConstantFee . Denom = coinDenom
2020-08-23 21:41:54 +00:00
appGenState [ crisis . ModuleName ] = cdc . MustMarshalJSON ( crisisGenState )
2020-07-31 21:42:04 +00:00
2020-09-08 14:39:48 +00:00
var evmGenState evmtypes . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ evmtypes . ModuleName ] , & evmGenState )
evmGenState . Params . EvmDenom = coinDenom
appGenState [ evmtypes . ModuleName ] = cdc . MustMarshalJSON ( evmGenState )
2020-07-31 21:42:04 +00:00
appGenStateJSON , err := codec . MarshalJSONIndent ( cdc , appGenState )
if err != nil {
return err
}
genDoc := tmtypes . 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
}
2020-08-12 14:25:57 +00:00
// GenerateSaveCoinKey returns the address of a public key, along with the secret
// phrase to recover the private key.
2020-08-23 21:41:54 +00:00
func GenerateSaveCoinKey ( keybase keys . Keybase , keyName , keyPass string , overwrite bool , algo keys . SigningAlgo ) ( sdk . AccAddress , string , error ) {
2020-08-12 14:25:57 +00:00
// ensure no overwrite
if ! overwrite {
_ , err := keybase . Get ( keyName )
if err == nil {
return sdk . AccAddress ( [ ] byte { } ) , "" , fmt . Errorf (
"key already exists, overwrite is disabled" )
}
}
// generate a private key, with recovery phrase
2020-08-23 21:41:54 +00:00
info , secret , err := keybase . CreateMnemonic ( keyName , keys . English , keyPass , algo )
2020-08-12 14:25:57 +00:00
if err != nil {
return sdk . AccAddress ( [ ] byte { } ) , "" , err
}
return sdk . AccAddress ( info . GetPubKey ( ) . Address ( ) ) , secret , nil
}
2020-07-31 21:42:04 +00:00
func collectGenFiles (
cdc * codec . Codec , config * tmconfig . Config , chainID string ,
nodeIDs [ ] string , valPubKeys [ ] tmcrypto . PubKey ,
numValidators int , outputDir , nodeDirPrefix , nodeDaemonHome string ,
2020-08-23 21:41:54 +00:00
genAccIterator authtypes . GenesisAccountIterator ,
2020-07-31 21:42:04 +00:00
) 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" )
config . Moniker = nodeDirName
config . SetRoot ( nodeDir )
nodeID , valPubKey := nodeIDs [ i ] , valPubKeys [ i ]
initCfg := genutiltypes . NewInitConfig ( chainID , gentxsDir , nodeID , nodeID , valPubKey )
genDoc , err := tmtypes . GenesisDocFromFile ( config . GenesisFile ( ) )
if err != nil {
return err
}
2020-08-23 21:41:54 +00:00
nodeAppState , err := genutil . GenAppStateFromConfig ( cdc , config , initCfg , * genDoc , genAccIterator )
2020-07-31 21:42:04 +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
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
}