github.com/Finschia/finschia-sdk@v0.48.1/simapp/simd/cmd/testnet.go (about)

     1  package cmd
     2  
     3  // DONTCOVER
     4  
     5  import (
     6  	"bufio"
     7  	"encoding/json"
     8  	"fmt"
     9  	"net"
    10  	"os"
    11  	"path/filepath"
    12  
    13  	"github.com/spf13/cobra"
    14  
    15  	ostconfig "github.com/Finschia/ostracon/config"
    16  	ostos "github.com/Finschia/ostracon/libs/os"
    17  	ostrand "github.com/Finschia/ostracon/libs/rand"
    18  	"github.com/Finschia/ostracon/types"
    19  	osttime "github.com/Finschia/ostracon/types/time"
    20  
    21  	"github.com/Finschia/finschia-sdk/client"
    22  	"github.com/Finschia/finschia-sdk/client/flags"
    23  	"github.com/Finschia/finschia-sdk/client/tx"
    24  	"github.com/Finschia/finschia-sdk/crypto/hd"
    25  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    26  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    27  	"github.com/Finschia/finschia-sdk/server"
    28  	srvconfig "github.com/Finschia/finschia-sdk/server/config"
    29  	"github.com/Finschia/finschia-sdk/testutil"
    30  	sdk "github.com/Finschia/finschia-sdk/types"
    31  	"github.com/Finschia/finschia-sdk/types/module"
    32  	authtypes "github.com/Finschia/finschia-sdk/x/auth/types"
    33  	banktypes "github.com/Finschia/finschia-sdk/x/bank/types"
    34  	"github.com/Finschia/finschia-sdk/x/genutil"
    35  	genutiltypes "github.com/Finschia/finschia-sdk/x/genutil/types"
    36  	stakingtypes "github.com/Finschia/finschia-sdk/x/staking/types"
    37  )
    38  
    39  var (
    40  	flagNodeDirPrefix     = "node-dir-prefix"
    41  	flagNumValidators     = "v"
    42  	flagOutputDir         = "output-dir"
    43  	flagNodeDaemonHome    = "node-daemon-home"
    44  	flagStartingIPAddress = "starting-ip-address"
    45  )
    46  
    47  // get cmd to initialize all files for ostracon testnet and application
    48  func testnetCmd(mbm module.BasicManager, genBalIterator banktypes.GenesisBalancesIterator) *cobra.Command {
    49  	cmd := &cobra.Command{
    50  		Use:   "testnet",
    51  		Short: "Initialize files for a simapp testnet",
    52  		Long: `testnet will create "v" number of directories and populate each with
    53  necessary files (private validator, genesis, config, etc.).
    54  
    55  Note, strict routability for addresses is turned off in the config file.
    56  
    57  Example:
    58  	simd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2
    59  	`,
    60  		RunE: func(cmd *cobra.Command, _ []string) error {
    61  			clientCtx, err := client.GetClientQueryContext(cmd)
    62  			if err != nil {
    63  				return err
    64  			}
    65  
    66  			serverCtx := server.GetServerContextFromCmd(cmd)
    67  			config := serverCtx.Config
    68  
    69  			outputDir, _ := cmd.Flags().GetString(flagOutputDir)
    70  			keyringBackend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend)
    71  			chainID, _ := cmd.Flags().GetString(flags.FlagChainID)
    72  			minGasPrices, _ := cmd.Flags().GetString(server.FlagMinGasPrices)
    73  			nodeDirPrefix, _ := cmd.Flags().GetString(flagNodeDirPrefix)
    74  			nodeDaemonHome, _ := cmd.Flags().GetString(flagNodeDaemonHome)
    75  			startingIPAddress, _ := cmd.Flags().GetString(flagStartingIPAddress)
    76  			numValidators, _ := cmd.Flags().GetInt(flagNumValidators)
    77  			algo, _ := cmd.Flags().GetString(flags.FlagKeyAlgorithm)
    78  
    79  			return InitTestnet(
    80  				clientCtx, cmd, config, mbm, genBalIterator, outputDir, chainID, minGasPrices,
    81  				nodeDirPrefix, nodeDaemonHome, startingIPAddress, keyringBackend, algo, numValidators,
    82  			)
    83  		},
    84  	}
    85  
    86  	cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with")
    87  	cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", "Directory to store initialization data for the testnet")
    88  	cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)")
    89  	cmd.Flags().String(flagNodeDaemonHome, "simd", "Home directory of the node's daemon configuration")
    90  	cmd.Flags().String(flagStartingIPAddress, "192.168.0.1", "Starting IP address (192.168.0.1 results in persistent peers list ID0@192.168.0.1:46656, ID1@192.168.0.2:46656, ...)")
    91  	cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
    92  	cmd.Flags().String(server.FlagMinGasPrices, fmt.Sprintf("0.000006%s", sdk.DefaultBondDenom), "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01photino,0.001stake)")
    93  	cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
    94  	cmd.Flags().String(flags.FlagKeyAlgorithm, string(hd.Secp256k1Type), "Key signing algorithm to generate keys for")
    95  
    96  	return cmd
    97  }
    98  
    99  const nodeDirPerm = 0o755
   100  
   101  // Initialize the testnet
   102  func InitTestnet(
   103  	clientCtx client.Context,
   104  	cmd *cobra.Command,
   105  	nodeConfig *ostconfig.Config,
   106  	mbm module.BasicManager,
   107  	genBalIterator banktypes.GenesisBalancesIterator,
   108  	outputDir,
   109  	chainID,
   110  	minGasPrices,
   111  	nodeDirPrefix,
   112  	nodeDaemonHome,
   113  	startingIPAddress,
   114  	keyringBackend,
   115  	algoStr string,
   116  	numValidators int,
   117  ) error {
   118  	if chainID == "" {
   119  		chainID = "chain-" + ostrand.NewRand().Str(6)
   120  	}
   121  
   122  	nodeIDs := make([]string, numValidators)
   123  	valPubKeys := make([]cryptotypes.PubKey, numValidators)
   124  
   125  	simappConfig := srvconfig.DefaultConfig()
   126  	simappConfig.MinGasPrices = minGasPrices
   127  	simappConfig.API.Enable = true
   128  	simappConfig.Telemetry.Enabled = true
   129  	simappConfig.Telemetry.PrometheusRetentionTime = 60
   130  	simappConfig.Telemetry.EnableHostnameLabel = false
   131  	simappConfig.Telemetry.GlobalLabels = [][]string{{"chain_id", chainID}}
   132  
   133  	var (
   134  		genAccounts []authtypes.GenesisAccount
   135  		genBalances []banktypes.Balance
   136  		genFiles    []string
   137  	)
   138  
   139  	inBuf := bufio.NewReader(cmd.InOrStdin())
   140  	// generate private keys, node IDs, and initial transactions
   141  	for i := 0; i < numValidators; i++ {
   142  		nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
   143  		nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
   144  		gentxsDir := filepath.Join(outputDir, "gentxs")
   145  
   146  		nodeConfig.SetRoot(nodeDir)
   147  		nodeConfig.RPC.ListenAddress = "tcp://0.0.0.0:26657"
   148  
   149  		if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil {
   150  			_ = os.RemoveAll(outputDir)
   151  			return err
   152  		}
   153  
   154  		nodeConfig.Moniker = nodeDirName
   155  
   156  		ip, err := getIP(i, startingIPAddress)
   157  		if err != nil {
   158  			_ = os.RemoveAll(outputDir)
   159  			return err
   160  		}
   161  
   162  		nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(nodeConfig)
   163  		if err != nil {
   164  			_ = os.RemoveAll(outputDir)
   165  			return err
   166  		}
   167  
   168  		memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
   169  		genFiles = append(genFiles, nodeConfig.GenesisFile())
   170  
   171  		kb, err := keyring.New(sdk.KeyringServiceName(), keyringBackend, nodeDir, inBuf)
   172  		if err != nil {
   173  			return err
   174  		}
   175  
   176  		keyringAlgos, _ := kb.SupportedAlgorithms()
   177  		algo, err := keyring.NewSigningAlgoFromString(algoStr, keyringAlgos)
   178  		if err != nil {
   179  			return err
   180  		}
   181  
   182  		addr, secret, err := testutil.GenerateSaveCoinKey(kb, nodeDirName, "", true, algo)
   183  		if err != nil {
   184  			_ = os.RemoveAll(outputDir)
   185  			return err
   186  		}
   187  
   188  		info := map[string]string{"secret": secret}
   189  
   190  		cliPrint, err := json.Marshal(info)
   191  		if err != nil {
   192  			return err
   193  		}
   194  
   195  		// save private key seed words
   196  		if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), nodeDir, cliPrint); err != nil {
   197  			return err
   198  		}
   199  
   200  		accTokens := sdk.TokensFromConsensusPower(1000, sdk.DefaultPowerReduction)
   201  		accStakingTokens := sdk.TokensFromConsensusPower(500, sdk.DefaultPowerReduction)
   202  		coins := sdk.Coins{
   203  			sdk.NewCoin("testtoken", accTokens),
   204  			sdk.NewCoin(sdk.DefaultBondDenom, accStakingTokens),
   205  		}
   206  
   207  		genBalances = append(genBalances, banktypes.Balance{Address: addr.String(), Coins: coins.Sort()})
   208  		genAccounts = append(genAccounts, authtypes.NewBaseAccount(addr, nil, 0, 0))
   209  
   210  		valTokens := sdk.TokensFromConsensusPower(100, sdk.DefaultPowerReduction)
   211  		createValMsg, err := stakingtypes.NewMsgCreateValidator(
   212  			sdk.ValAddress(addr),
   213  			valPubKeys[i],
   214  			sdk.NewCoin(sdk.DefaultBondDenom, valTokens),
   215  			stakingtypes.NewDescription(nodeDirName, "", "", "", ""),
   216  			stakingtypes.NewCommissionRates(sdk.OneDec(), sdk.OneDec(), sdk.OneDec()),
   217  			sdk.OneInt(),
   218  		)
   219  		if err != nil {
   220  			return err
   221  		}
   222  
   223  		txBuilder := clientCtx.TxConfig.NewTxBuilder()
   224  		if err := txBuilder.SetMsgs(createValMsg); err != nil {
   225  			return err
   226  		}
   227  
   228  		txBuilder.SetMemo(memo)
   229  
   230  		txFactory := tx.Factory{}
   231  		txFactory = txFactory.
   232  			WithChainID(chainID).
   233  			WithMemo(memo).
   234  			WithKeybase(kb).
   235  			WithTxConfig(clientCtx.TxConfig)
   236  
   237  		if err := tx.Sign(txFactory, nodeDirName, txBuilder, true); err != nil {
   238  			return err
   239  		}
   240  
   241  		txBz, err := clientCtx.TxConfig.TxJSONEncoder()(txBuilder.GetTx())
   242  		if err != nil {
   243  			return err
   244  		}
   245  
   246  		if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBz); err != nil {
   247  			return err
   248  		}
   249  
   250  		srvconfig.WriteConfigFile(filepath.Join(nodeDir, "config/app.toml"), simappConfig)
   251  	}
   252  
   253  	if err := initGenFiles(clientCtx, mbm, chainID, genAccounts, genBalances, genFiles, numValidators); err != nil {
   254  		return err
   255  	}
   256  
   257  	err := collectGenFiles(
   258  		clientCtx, nodeConfig, chainID, nodeIDs, valPubKeys, numValidators,
   259  		outputDir, nodeDirPrefix, nodeDaemonHome, genBalIterator,
   260  	)
   261  	if err != nil {
   262  		return err
   263  	}
   264  
   265  	cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators)
   266  	return nil
   267  }
   268  
   269  func initGenFiles(
   270  	clientCtx client.Context, mbm module.BasicManager, chainID string,
   271  	genAccounts []authtypes.GenesisAccount, genBalances []banktypes.Balance,
   272  	genFiles []string, numValidators int,
   273  ) error {
   274  	appGenState := mbm.DefaultGenesis(clientCtx.Codec)
   275  
   276  	// set the accounts in the genesis state
   277  	var authGenState authtypes.GenesisState
   278  	clientCtx.Codec.MustUnmarshalJSON(appGenState[authtypes.ModuleName], &authGenState)
   279  
   280  	accounts, err := authtypes.PackAccounts(genAccounts)
   281  	if err != nil {
   282  		return err
   283  	}
   284  
   285  	authGenState.Accounts = accounts
   286  	appGenState[authtypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&authGenState)
   287  
   288  	// set the balances in the genesis state
   289  	var bankGenState banktypes.GenesisState
   290  	clientCtx.Codec.MustUnmarshalJSON(appGenState[banktypes.ModuleName], &bankGenState)
   291  
   292  	bankGenState.Balances = banktypes.SanitizeGenesisBalances(genBalances)
   293  	for _, bal := range bankGenState.Balances {
   294  		bankGenState.Supply = bankGenState.Supply.Add(bal.Coins...)
   295  	}
   296  	appGenState[banktypes.ModuleName] = clientCtx.Codec.MustMarshalJSON(&bankGenState)
   297  
   298  	appGenStateJSON, err := json.MarshalIndent(appGenState, "", "  ")
   299  	if err != nil {
   300  		return err
   301  	}
   302  
   303  	genDoc := types.GenesisDoc{
   304  		ChainID:    chainID,
   305  		AppState:   appGenStateJSON,
   306  		Validators: nil,
   307  	}
   308  
   309  	// generate empty genesis files for each validator and save
   310  	for i := 0; i < numValidators; i++ {
   311  		if err := genDoc.SaveAs(genFiles[i]); err != nil {
   312  			return err
   313  		}
   314  	}
   315  	return nil
   316  }
   317  
   318  func collectGenFiles(
   319  	clientCtx client.Context, nodeConfig *ostconfig.Config, chainID string,
   320  	nodeIDs []string, valPubKeys []cryptotypes.PubKey, numValidators int,
   321  	outputDir, nodeDirPrefix, nodeDaemonHome string, genBalIterator banktypes.GenesisBalancesIterator,
   322  ) error {
   323  	var appState json.RawMessage
   324  	genTime := osttime.Now()
   325  
   326  	for i := 0; i < numValidators; i++ {
   327  		nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
   328  		nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
   329  		gentxsDir := filepath.Join(outputDir, "gentxs")
   330  		nodeConfig.Moniker = nodeDirName
   331  
   332  		nodeConfig.SetRoot(nodeDir)
   333  
   334  		nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
   335  		initCfg := genutiltypes.NewInitConfig(chainID, gentxsDir, nodeID, valPubKey)
   336  
   337  		genDoc, err := types.GenesisDocFromFile(nodeConfig.GenesisFile())
   338  		if err != nil {
   339  			return err
   340  		}
   341  
   342  		nodeAppState, err := genutil.GenAppStateFromConfig(clientCtx.Codec, clientCtx.TxConfig, nodeConfig, initCfg, *genDoc, genBalIterator)
   343  		if err != nil {
   344  			return err
   345  		}
   346  
   347  		if appState == nil {
   348  			// set the canonical application state (they should not differ)
   349  			appState = nodeAppState
   350  		}
   351  
   352  		genFile := nodeConfig.GenesisFile()
   353  
   354  		// overwrite each validator's genesis file to have a canonical genesis time
   355  		if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil {
   356  			return err
   357  		}
   358  	}
   359  
   360  	return nil
   361  }
   362  
   363  func getIP(i int, startingIPAddr string) (ip string, err error) {
   364  	if len(startingIPAddr) == 0 {
   365  		ip, err = server.ExternalIP()
   366  		if err != nil {
   367  			return "", err
   368  		}
   369  		return ip, nil
   370  	}
   371  	return calculateIP(startingIPAddr, i)
   372  }
   373  
   374  func calculateIP(ip string, i int) (string, error) {
   375  	ipv4 := net.ParseIP(ip).To4()
   376  	if ipv4 == nil {
   377  		return "", fmt.Errorf("%v: non ipv4 address", ip)
   378  	}
   379  
   380  	for j := 0; j < i; j++ {
   381  		ipv4[3]++
   382  	}
   383  
   384  	return ipv4.String(), nil
   385  }
   386  
   387  func writeFile(name string, dir string, contents []byte) error {
   388  	writePath := filepath.Join(dir) //nolint:gocritic
   389  	file := filepath.Join(writePath, name)
   390  
   391  	err := ostos.EnsureDir(writePath, 0755)
   392  	if err != nil {
   393  		return err
   394  	}
   395  
   396  	err = ostos.WriteFile(file, contents, 0644)
   397  	if err != nil {
   398  		return err
   399  	}
   400  
   401  	return nil
   402  }