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