github.com/adoriasoft/tendermint@v0.34.0-dev1.0.20200722151356-96d84601a75a/cmd/tendermint/commands/testnet.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"net"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/spf13/cobra"
    11  	"github.com/spf13/viper"
    12  
    13  	cfg "github.com/tendermint/tendermint/config"
    14  	"github.com/tendermint/tendermint/libs/bytes"
    15  	tmrand "github.com/tendermint/tendermint/libs/rand"
    16  	"github.com/tendermint/tendermint/p2p"
    17  	"github.com/tendermint/tendermint/privval"
    18  	"github.com/tendermint/tendermint/types"
    19  	tmtime "github.com/tendermint/tendermint/types/time"
    20  )
    21  
    22  var (
    23  	nValidators    int
    24  	nNonValidators int
    25  	configFile     string
    26  	outputDir      string
    27  	nodeDirPrefix  string
    28  
    29  	populatePersistentPeers bool
    30  	hostnamePrefix          string
    31  	hostnameSuffix          string
    32  	startingIPAddress       string
    33  	hostnames               []string
    34  	p2pPort                 int
    35  	randomMonikers          bool
    36  )
    37  
    38  const (
    39  	nodeDirPerm = 0755
    40  )
    41  
    42  func init() {
    43  	TestnetFilesCmd.Flags().IntVar(&nValidators, "v", 4,
    44  		"Number of validators to initialize the testnet with")
    45  	TestnetFilesCmd.Flags().StringVar(&configFile, "config", "",
    46  		"Config file to use (note some options may be overwritten)")
    47  	TestnetFilesCmd.Flags().IntVar(&nNonValidators, "n", 0,
    48  		"Number of non-validators to initialize the testnet with")
    49  	TestnetFilesCmd.Flags().StringVar(&outputDir, "o", "./mytestnet",
    50  		"Directory to store initialization data for the testnet")
    51  	TestnetFilesCmd.Flags().StringVar(&nodeDirPrefix, "node-dir-prefix", "node",
    52  		"Prefix the directory name for each node with (node results in node0, node1, ...)")
    53  
    54  	TestnetFilesCmd.Flags().BoolVar(&populatePersistentPeers, "populate-persistent-peers", true,
    55  		"Update config of each node with the list of persistent peers build using either"+
    56  			" hostname-prefix or"+
    57  			" starting-ip-address")
    58  	TestnetFilesCmd.Flags().StringVar(&hostnamePrefix, "hostname-prefix", "node",
    59  		"Hostname prefix (\"node\" results in persistent peers list ID0@node0:26656, ID1@node1:26656, ...)")
    60  	TestnetFilesCmd.Flags().StringVar(&hostnameSuffix, "hostname-suffix", "",
    61  		"Hostname suffix ("+
    62  			"\".xyz.com\""+
    63  			" results in persistent peers list ID0@node0.xyz.com:26656, ID1@node1.xyz.com:26656, ...)")
    64  	TestnetFilesCmd.Flags().StringVar(&startingIPAddress, "starting-ip-address", "",
    65  		"Starting IP address ("+
    66  			"\"192.168.0.1\""+
    67  			" results in persistent peers list ID0@192.168.0.1:26656, ID1@192.168.0.2:26656, ...)")
    68  	TestnetFilesCmd.Flags().StringArrayVar(&hostnames, "hostname", []string{},
    69  		"Manually override all hostnames of validators and non-validators (use --hostname multiple times for multiple hosts)")
    70  	TestnetFilesCmd.Flags().IntVar(&p2pPort, "p2p-port", 26656,
    71  		"P2P Port")
    72  	TestnetFilesCmd.Flags().BoolVar(&randomMonikers, "random-monikers", false,
    73  		"Randomize the moniker for each generated node")
    74  }
    75  
    76  // TestnetFilesCmd allows initialisation of files for a Tendermint testnet.
    77  var TestnetFilesCmd = &cobra.Command{
    78  	Use:   "testnet",
    79  	Short: "Initialize files for a Tendermint testnet",
    80  	Long: `testnet will create "v" + "n" number of directories and populate each with
    81  necessary files (private validator, genesis, config, etc.).
    82  
    83  Note, strict routability for addresses is turned off in the config file.
    84  
    85  Optionally, it will fill in persistent_peers list in config file using either hostnames or IPs.
    86  
    87  Example:
    88  
    89  	tendermint testnet --v 4 --o ./output --populate-persistent-peers --starting-ip-address 192.168.10.2
    90  	`,
    91  	RunE: testnetFiles,
    92  }
    93  
    94  func testnetFiles(cmd *cobra.Command, args []string) error {
    95  	if len(hostnames) > 0 && len(hostnames) != (nValidators+nNonValidators) {
    96  		return fmt.Errorf(
    97  			"testnet needs precisely %d hostnames (number of validators plus non-validators) if --hostname parameter is used",
    98  			nValidators+nNonValidators,
    99  		)
   100  	}
   101  
   102  	config := cfg.DefaultConfig()
   103  
   104  	// overwrite default config if set and valid
   105  	if configFile != "" {
   106  		viper.SetConfigFile(configFile)
   107  		if err := viper.ReadInConfig(); err != nil {
   108  			return err
   109  		}
   110  		if err := viper.Unmarshal(config); err != nil {
   111  			return err
   112  		}
   113  		if err := config.ValidateBasic(); err != nil {
   114  			return err
   115  		}
   116  	}
   117  
   118  	genVals := make([]types.GenesisValidator, nValidators)
   119  
   120  	for i := 0; i < nValidators; i++ {
   121  		nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
   122  		nodeDir := filepath.Join(outputDir, nodeDirName)
   123  		config.SetRoot(nodeDir)
   124  
   125  		err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
   126  		if err != nil {
   127  			_ = os.RemoveAll(outputDir)
   128  			return err
   129  		}
   130  		err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
   131  		if err != nil {
   132  			_ = os.RemoveAll(outputDir)
   133  			return err
   134  		}
   135  
   136  		initFilesWithConfig(config)
   137  
   138  		pvKeyFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorKey)
   139  		pvStateFile := filepath.Join(nodeDir, config.BaseConfig.PrivValidatorState)
   140  		pv := privval.LoadFilePV(pvKeyFile, pvStateFile)
   141  
   142  		pubKey, err := pv.GetPubKey()
   143  		if err != nil {
   144  			return fmt.Errorf("can't get pubkey: %w", err)
   145  		}
   146  		genVals[i] = types.GenesisValidator{
   147  			Address: pubKey.Address(),
   148  			PubKey:  pubKey,
   149  			Power:   1,
   150  			Name:    nodeDirName,
   151  		}
   152  	}
   153  
   154  	for i := 0; i < nNonValidators; i++ {
   155  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i+nValidators))
   156  		config.SetRoot(nodeDir)
   157  
   158  		err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
   159  		if err != nil {
   160  			_ = os.RemoveAll(outputDir)
   161  			return err
   162  		}
   163  
   164  		err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
   165  		if err != nil {
   166  			_ = os.RemoveAll(outputDir)
   167  			return err
   168  		}
   169  
   170  		initFilesWithConfig(config)
   171  	}
   172  
   173  	// Generate genesis doc from generated validators
   174  	genDoc := &types.GenesisDoc{
   175  		ChainID:         "chain-" + tmrand.Str(6),
   176  		ConsensusParams: types.DefaultConsensusParams(),
   177  		GenesisTime:     tmtime.Now(),
   178  		Validators:      genVals,
   179  	}
   180  
   181  	// Write genesis file.
   182  	for i := 0; i < nValidators+nNonValidators; i++ {
   183  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
   184  		if err := genDoc.SaveAs(filepath.Join(nodeDir, config.BaseConfig.Genesis)); err != nil {
   185  			_ = os.RemoveAll(outputDir)
   186  			return err
   187  		}
   188  	}
   189  
   190  	// Gather persistent peer addresses.
   191  	var (
   192  		persistentPeers string
   193  		err             error
   194  	)
   195  	if populatePersistentPeers {
   196  		persistentPeers, err = persistentPeersString(config)
   197  		if err != nil {
   198  			_ = os.RemoveAll(outputDir)
   199  			return err
   200  		}
   201  	}
   202  
   203  	// Overwrite default config.
   204  	for i := 0; i < nValidators+nNonValidators; i++ {
   205  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
   206  		config.SetRoot(nodeDir)
   207  		config.P2P.AddrBookStrict = false
   208  		config.P2P.AllowDuplicateIP = true
   209  		if populatePersistentPeers {
   210  			config.P2P.PersistentPeers = persistentPeers
   211  		}
   212  		config.Moniker = moniker(i)
   213  
   214  		cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config)
   215  	}
   216  
   217  	fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators)
   218  	return nil
   219  }
   220  
   221  func hostnameOrIP(i int) string {
   222  	if len(hostnames) > 0 && i < len(hostnames) {
   223  		return hostnames[i]
   224  	}
   225  	if startingIPAddress == "" {
   226  		return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
   227  	}
   228  	ip := net.ParseIP(startingIPAddress)
   229  	ip = ip.To4()
   230  	if ip == nil {
   231  		fmt.Printf("%v: non ipv4 address\n", startingIPAddress)
   232  		os.Exit(1)
   233  	}
   234  
   235  	for j := 0; j < i; j++ {
   236  		ip[3]++
   237  	}
   238  	return ip.String()
   239  }
   240  
   241  func persistentPeersString(config *cfg.Config) (string, error) {
   242  	persistentPeers := make([]string, nValidators+nNonValidators)
   243  	for i := 0; i < nValidators+nNonValidators; i++ {
   244  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
   245  		config.SetRoot(nodeDir)
   246  		nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
   247  		if err != nil {
   248  			return "", err
   249  		}
   250  		persistentPeers[i] = p2p.IDAddressString(nodeKey.ID(), fmt.Sprintf("%s:%d", hostnameOrIP(i), p2pPort))
   251  	}
   252  	return strings.Join(persistentPeers, ","), nil
   253  }
   254  
   255  func moniker(i int) string {
   256  	if randomMonikers {
   257  		return randomMoniker()
   258  	}
   259  	if len(hostnames) > 0 && i < len(hostnames) {
   260  		return hostnames[i]
   261  	}
   262  	if startingIPAddress == "" {
   263  		return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
   264  	}
   265  	return randomMoniker()
   266  }
   267  
   268  func randomMoniker() string {
   269  	return bytes.HexBytes(tmrand.Bytes(8)).String()
   270  }