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