2020-06-12 09:23:08 -07:00
package main
// DONTCOVER
import (
"bufio"
"encoding/json"
"fmt"
"net"
"os"
"path/filepath"
"github.com/spf13/cobra"
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"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
2020-06-14 16:06:16 -07:00
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
2020-06-12 09:23:08 -07:00
"github.com/cosmos/cosmos-sdk/x/genutil"
2020-06-13 00:41:45 -07:00
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
2020-06-12 09:23:08 -07:00
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 ,
2020-06-14 16:06:16 -07:00
mbm module . BasicManager , genBalIterator banktypes . GenesisBalancesIterator ,
2020-06-12 09:23:08 -07:00
) * 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
2020-07-05 09:56:17 -07:00
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 )
numValidators , _ := cmd . Flags ( ) . GetInt ( flagNumValidators )
2020-06-12 09:23:08 -07:00
return InitTestnet (
cmd , config , cdc , mbm , genBalIterator , outputDir , chainID , minGasPrices ,
2020-07-05 09:56:17 -07:00
nodeDirPrefix , nodeDaemonHome , nodeCLIHome , startingIPAddress , keyringBackend , numValidators ,
2020-06-12 09:23:08 -07:00
)
} ,
}
2020-07-05 09:56:17 -07:00
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)" )
2020-06-12 09:23:08 -07:00
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 ,
2020-06-14 16:06:16 -07:00
mbm module . BasicManager , genBalIterator banktypes . GenesisBalancesIterator ,
2020-06-12 09:23:08 -07:00
outputDir , chainID , minGasPrices , nodeDirPrefix , nodeDaemonHome ,
2020-07-05 09:56:17 -07:00
nodeCLIHome , startingIPAddress , keyringBackend string , numValidators int ,
2020-06-12 09:23:08 -07:00
) error {
if chainID == "" {
chainID = "chain-" + tmrand . NewRand ( ) . Str ( 6 )
}
nodeIDs := make ( [ ] string , numValidators )
valPubKeys := make ( [ ] crypto . PubKey , numValidators )
simappConfig := srvconfig . DefaultConfig ( )
simappConfig . MinGasPrices = minGasPrices
2020-06-15 10:39:09 -07:00
simappConfig . API . Enable = true
2020-06-16 08:11:02 -07:00
simappConfig . Telemetry . Enabled = true
simappConfig . Telemetry . PrometheusRetentionTime = 60
simappConfig . Telemetry . EnableHostnameLabel = false
2020-06-18 11:12:44 -07:00
simappConfig . Telemetry . GlobalLabels = [ ] [ ] string { { "chain_id" , chainID } }
2020-06-12 09:23:08 -07:00
var (
genAccounts [ ] authtypes . GenesisAccount
2020-06-14 16:06:16 -07:00
genBalances [ ] banktypes . Balance
2020-06-12 09:23:08 -07:00
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
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 ( ) )
2020-07-05 09:56:17 -07:00
kb , err := keyring . New ( sdk . KeyringServiceName ( ) , keyringBackend , clientDir , inBuf )
2020-06-12 09:23:08 -07:00
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 ) ,
}
2020-06-14 16:06:16 -07:00
genBalances = append ( genBalances , banktypes . Balance { Address : addr , Coins : coins . Sort ( ) } )
2020-06-17 11:42:27 -07:00
genAccounts = append ( genAccounts , authtypes . NewBaseAccount ( addr , nil , 0 , 0 ) )
2020-06-12 09:23:08 -07:00
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 ( ) ,
)
2020-06-17 11:42:27 -07:00
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 )
2020-06-12 09:23:08 -07:00
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 (
2020-07-06 14:48:54 -07:00
cdc , config , chainID , nodeIDs , valPubKeys , numValidators ,
2020-06-12 09:23:08 -07:00
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 ,
2020-06-14 16:06:16 -07:00
genAccounts [ ] authtypes . GenesisAccount , genBalances [ ] banktypes . Balance ,
2020-06-12 09:23:08 -07:00
genFiles [ ] string , numValidators int ,
) error {
appGenState := mbm . DefaultGenesis ( cdc )
// set the accounts in the genesis state
2020-06-17 11:42:27 -07:00
var authGenState authtypes . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ authtypes . ModuleName ] , & authGenState )
2020-06-12 09:23:08 -07:00
authGenState . Accounts = genAccounts
2020-06-17 11:42:27 -07:00
appGenState [ authtypes . ModuleName ] = cdc . MustMarshalJSON ( authGenState )
2020-06-12 09:23:08 -07:00
// set the balances in the genesis state
2020-06-14 16:06:16 -07:00
var bankGenState banktypes . GenesisState
cdc . MustUnmarshalJSON ( appGenState [ banktypes . ModuleName ] , & bankGenState )
2020-06-12 09:23:08 -07:00
bankGenState . Balances = genBalances
2020-06-14 16:06:16 -07:00
appGenState [ banktypes . ModuleName ] = cdc . MustMarshalJSON ( bankGenState )
2020-06-12 09:23:08 -07: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
}
}
return nil
}
func collectGenFiles (
cdc codec . JSONMarshaler , config * tmconfig . Config , chainID string ,
2020-07-06 14:48:54 -07:00
nodeIDs [ ] string , valPubKeys [ ] crypto . PubKey ,
2020-06-12 09:23:08 -07:00
numValidators int , outputDir , nodeDirPrefix , nodeDaemonHome string ,
2020-06-14 16:06:16 -07:00
genBalIterator banktypes . GenesisBalancesIterator ,
2020-06-12 09:23:08 -07: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 ]
2020-07-06 14:48:54 -07:00
initCfg := genutiltypes . NewInitConfig ( chainID , gentxsDir , nodeID , valPubKey )
2020-06-12 09:23:08 -07:00
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
}