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