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