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