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