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