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