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