github.com/number571/tendermint@v0.34.11-gost/cmd/tendermint/commands/testnet.go (about)

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