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