github.com/devwanda/aphelion-staking@v0.33.9/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/devwanda/aphelion-staking/config"
    15  	"github.com/devwanda/aphelion-staking/libs/bytes"
    16  	tmrand "github.com/devwanda/aphelion-staking/libs/rand"
    17  	"github.com/devwanda/aphelion-staking/p2p"
    18  	"github.com/devwanda/aphelion-staking/privval"
    19  	"github.com/devwanda/aphelion-staking/types"
    20  	tmtime "github.com/devwanda/aphelion-staking/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.LoadFilePV(pvKeyFile, pvStateFile)
   142  
   143  		pubKey, err := pv.GetPubKey()
   144  		if err != nil {
   145  			return errors.Wrap(err, "can't get pubkey")
   146  		}
   147  		genVals[i] = types.GenesisValidator{
   148  			Address: pubKey.Address(),
   149  			PubKey:  pubKey,
   150  			Power:   1,
   151  			Name:    nodeDirName,
   152  		}
   153  	}
   154  
   155  	for i := 0; i < nNonValidators; i++ {
   156  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i+nValidators))
   157  		config.SetRoot(nodeDir)
   158  
   159  		err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm)
   160  		if err != nil {
   161  			_ = os.RemoveAll(outputDir)
   162  			return err
   163  		}
   164  
   165  		err = os.MkdirAll(filepath.Join(nodeDir, "data"), nodeDirPerm)
   166  		if err != nil {
   167  			_ = os.RemoveAll(outputDir)
   168  			return err
   169  		}
   170  
   171  		initFilesWithConfig(config)
   172  	}
   173  
   174  	// Generate genesis doc from generated validators
   175  	genDoc := &types.GenesisDoc{
   176  		ChainID:         "chain-" + tmrand.Str(6),
   177  		ConsensusParams: types.DefaultConsensusParams(),
   178  		GenesisTime:     tmtime.Now(),
   179  		Validators:      genVals,
   180  	}
   181  
   182  	// Write genesis file.
   183  	for i := 0; i < nValidators+nNonValidators; i++ {
   184  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
   185  		if err := genDoc.SaveAs(filepath.Join(nodeDir, config.BaseConfig.Genesis)); err != nil {
   186  			_ = os.RemoveAll(outputDir)
   187  			return err
   188  		}
   189  	}
   190  
   191  	// Gather persistent peer addresses.
   192  	var (
   193  		persistentPeers string
   194  		err             error
   195  	)
   196  	if populatePersistentPeers {
   197  		persistentPeers, err = persistentPeersString(config)
   198  		if err != nil {
   199  			_ = os.RemoveAll(outputDir)
   200  			return err
   201  		}
   202  	}
   203  
   204  	// Overwrite default config.
   205  	for i := 0; i < nValidators+nNonValidators; i++ {
   206  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
   207  		config.SetRoot(nodeDir)
   208  		config.P2P.AddrBookStrict = false
   209  		config.P2P.AllowDuplicateIP = true
   210  		if populatePersistentPeers {
   211  			config.P2P.PersistentPeers = persistentPeers
   212  		}
   213  		config.Moniker = moniker(i)
   214  
   215  		cfg.WriteConfigFile(filepath.Join(nodeDir, "config", "config.toml"), config)
   216  	}
   217  
   218  	fmt.Printf("Successfully initialized %v node directories\n", nValidators+nNonValidators)
   219  	return nil
   220  }
   221  
   222  func hostnameOrIP(i int) string {
   223  	if len(hostnames) > 0 && i < len(hostnames) {
   224  		return hostnames[i]
   225  	}
   226  	if startingIPAddress == "" {
   227  		return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
   228  	}
   229  	ip := net.ParseIP(startingIPAddress)
   230  	ip = ip.To4()
   231  	if ip == nil {
   232  		fmt.Printf("%v: non ipv4 address\n", startingIPAddress)
   233  		os.Exit(1)
   234  	}
   235  
   236  	for j := 0; j < i; j++ {
   237  		ip[3]++
   238  	}
   239  	return ip.String()
   240  }
   241  
   242  func persistentPeersString(config *cfg.Config) (string, error) {
   243  	persistentPeers := make([]string, nValidators+nNonValidators)
   244  	for i := 0; i < nValidators+nNonValidators; i++ {
   245  		nodeDir := filepath.Join(outputDir, fmt.Sprintf("%s%d", nodeDirPrefix, i))
   246  		config.SetRoot(nodeDir)
   247  		nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
   248  		if err != nil {
   249  			return "", err
   250  		}
   251  		persistentPeers[i] = p2p.IDAddressString(nodeKey.ID(), fmt.Sprintf("%s:%d", hostnameOrIP(i), p2pPort))
   252  	}
   253  	return strings.Join(persistentPeers, ","), nil
   254  }
   255  
   256  func moniker(i int) string {
   257  	if randomMonikers {
   258  		return randomMoniker()
   259  	}
   260  	if len(hostnames) > 0 && i < len(hostnames) {
   261  		return hostnames[i]
   262  	}
   263  	if startingIPAddress == "" {
   264  		return fmt.Sprintf("%s%d%s", hostnamePrefix, i, hostnameSuffix)
   265  	}
   266  	return randomMoniker()
   267  }
   268  
   269  func randomMoniker() string {
   270  	return bytes.HexBytes(tmrand.Bytes(8)).String()
   271  }