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 }