mirror of
				https://github.com/0glabs/0g-chain.git
				synced 2025-11-04 14:47:26 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			377 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			377 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package main
 | 
						|
 | 
						|
// DONTCOVER
 | 
						|
 | 
						|
import (
 | 
						|
	"bufio"
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"net"
 | 
						|
	"os"
 | 
						|
	"path/filepath"
 | 
						|
 | 
						|
	"github.com/cosmos/cosmos-sdk/client/flags"
 | 
						|
	clientkeys "github.com/cosmos/cosmos-sdk/client/keys"
 | 
						|
	"github.com/cosmos/cosmos-sdk/codec"
 | 
						|
	"github.com/cosmos/cosmos-sdk/crypto/keys"
 | 
						|
	"github.com/cosmos/cosmos-sdk/server"
 | 
						|
	srvconfig "github.com/cosmos/cosmos-sdk/server/config"
 | 
						|
	sdk "github.com/cosmos/cosmos-sdk/types"
 | 
						|
	"github.com/cosmos/cosmos-sdk/types/module"
 | 
						|
	"github.com/cosmos/cosmos-sdk/x/auth"
 | 
						|
	authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
 | 
						|
	"github.com/cosmos/cosmos-sdk/x/genutil"
 | 
						|
	genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
 | 
						|
	"github.com/cosmos/cosmos-sdk/x/staking"
 | 
						|
	"github.com/spf13/cobra"
 | 
						|
	"github.com/spf13/viper"
 | 
						|
	tmconfig "github.com/tendermint/tendermint/config"
 | 
						|
	"github.com/tendermint/tendermint/crypto"
 | 
						|
	tmos "github.com/tendermint/tendermint/libs/os"
 | 
						|
	tmrand "github.com/tendermint/tendermint/libs/rand"
 | 
						|
	"github.com/tendermint/tendermint/types"
 | 
						|
	tmtime "github.com/tendermint/tendermint/types/time"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	flagNodeDirPrefix     = "node-dir-prefix"
 | 
						|
	flagNumValidators     = "v"
 | 
						|
	flagOutputDir         = "output-dir"
 | 
						|
	flagNodeDaemonHome    = "node-daemon-home"
 | 
						|
	flagNodeCLIHome       = "node-cli-home"
 | 
						|
	flagStartingIPAddress = "starting-ip-address"
 | 
						|
)
 | 
						|
 | 
						|
func testnetCmd(
 | 
						|
	ctx *server.Context, cdc *codec.Codec, mbm module.BasicManager, genAccIterator genutiltypes.GenesisAccountsIterator,
 | 
						|
) *cobra.Command {
 | 
						|
 | 
						|
	cmd := &cobra.Command{
 | 
						|
		Use:   "testnet",
 | 
						|
		Short: "Initialize files for a local kava testnet",
 | 
						|
		Long: `testnet will create "v" number of directories and populate each with
 | 
						|
necessary files (private validator, genesis, config, etc.).
 | 
						|
 | 
						|
Note, strict routability for addresses is turned off in the config file.
 | 
						|
 | 
						|
Example:
 | 
						|
	$ kvd testnet --v 4 --output-dir ./output --starting-ip-address 192.168.10.2
 | 
						|
	`,
 | 
						|
		RunE: func(cmd *cobra.Command, _ []string) error {
 | 
						|
			config := ctx.Config
 | 
						|
 | 
						|
			outputDir := viper.GetString(flagOutputDir)
 | 
						|
			chainID := viper.GetString(flags.FlagChainID)
 | 
						|
			minGasPrices := viper.GetString(server.FlagMinGasPrices)
 | 
						|
			nodeDirPrefix := viper.GetString(flagNodeDirPrefix)
 | 
						|
			nodeDaemonHome := viper.GetString(flagNodeDaemonHome)
 | 
						|
			nodeCLIHome := viper.GetString(flagNodeCLIHome)
 | 
						|
			startingIPAddress := viper.GetString(flagStartingIPAddress)
 | 
						|
			numValidators := viper.GetInt(flagNumValidators)
 | 
						|
 | 
						|
			return InitTestnet(
 | 
						|
				cmd, config, cdc, mbm, genAccIterator, outputDir, chainID,
 | 
						|
				minGasPrices, nodeDirPrefix, nodeDaemonHome, nodeCLIHome, startingIPAddress, numValidators,
 | 
						|
			)
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	cmd.Flags().Int(flagNumValidators, 4,
 | 
						|
		"Number of validators to initialize the testnet with")
 | 
						|
	cmd.Flags().StringP(flagOutputDir, "o", "./kavatestnet",
 | 
						|
		"Directory to store initialization data for the testnet")
 | 
						|
	cmd.Flags().String(flagNodeDirPrefix, "node",
 | 
						|
		"Prefix the directory name for each node with (node results in node0, node1, ...)")
 | 
						|
	cmd.Flags().String(flagNodeDaemonHome, "kvd",
 | 
						|
		"Home directory of the node's daemon configuration")
 | 
						|
	cmd.Flags().String(flagNodeCLIHome, "kvcli",
 | 
						|
		"Home directory of the node's cli configuration")
 | 
						|
	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, ...)")
 | 
						|
	cmd.Flags().String(
 | 
						|
		flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
 | 
						|
	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)")
 | 
						|
	cmd.Flags().String(flags.FlagKeyringBackend, flags.DefaultKeyringBackend, "Select keyring's backend (os|file|test)")
 | 
						|
 | 
						|
	return cmd
 | 
						|
}
 | 
						|
 | 
						|
const nodeDirPerm = 0755
 | 
						|
 | 
						|
// Initialize the testnet
 | 
						|
func InitTestnet(
 | 
						|
	cmd *cobra.Command, config *tmconfig.Config, cdc *codec.Codec,
 | 
						|
	mbm module.BasicManager, genAccIterator genutiltypes.GenesisAccountsIterator,
 | 
						|
	outputDir, chainID, minGasPrices, nodeDirPrefix, nodeDaemonHome,
 | 
						|
	nodeCLIHome, startingIPAddress string, numValidators int,
 | 
						|
) error {
 | 
						|
 | 
						|
	if chainID == "" {
 | 
						|
		chainID = "chain-" + tmrand.NewRand().Str(6)
 | 
						|
	}
 | 
						|
 | 
						|
	monikers := make([]string, numValidators)
 | 
						|
	nodeIDs := make([]string, numValidators)
 | 
						|
	valPubKeys := make([]crypto.PubKey, numValidators)
 | 
						|
 | 
						|
	kavaConfig := srvconfig.DefaultConfig()
 | 
						|
	kavaConfig.MinGasPrices = minGasPrices
 | 
						|
 | 
						|
	//nolint:prealloc
 | 
						|
	var (
 | 
						|
		genAccounts []authexported.GenesisAccount
 | 
						|
		genFiles    []string
 | 
						|
	)
 | 
						|
 | 
						|
	inBuf := bufio.NewReader(cmd.InOrStdin())
 | 
						|
	// generate private keys, node IDs, and initial transactions
 | 
						|
	for i := 0; i < numValidators; i++ {
 | 
						|
		nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
 | 
						|
		nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
 | 
						|
		clientDir := filepath.Join(outputDir, nodeDirName, nodeCLIHome)
 | 
						|
		gentxsDir := filepath.Join(outputDir, "gentxs")
 | 
						|
 | 
						|
		config.SetRoot(nodeDir)
 | 
						|
		config.RPC.ListenAddress = "tcp://0.0.0.0:26657"
 | 
						|
 | 
						|
		if err := os.MkdirAll(filepath.Join(nodeDir, "config"), nodeDirPerm); err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		if err := os.MkdirAll(clientDir, nodeDirPerm); err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		monikers = append(monikers, nodeDirName)
 | 
						|
		config.Moniker = nodeDirName
 | 
						|
 | 
						|
		ip, err := getIP(i, startingIPAddress)
 | 
						|
		if err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		nodeIDs[i], valPubKeys[i], err = genutil.InitializeNodeValidatorFiles(config)
 | 
						|
		if err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		memo := fmt.Sprintf("%s@%s:26656", nodeIDs[i], ip)
 | 
						|
		genFiles = append(genFiles, config.GenesisFile())
 | 
						|
 | 
						|
		kb, err := keys.NewKeyring(
 | 
						|
			sdk.KeyringServiceName(),
 | 
						|
			viper.GetString(flags.FlagKeyringBackend),
 | 
						|
			clientDir,
 | 
						|
			inBuf,
 | 
						|
		)
 | 
						|
 | 
						|
		keyPass := clientkeys.DefaultKeyPass
 | 
						|
		addr, secret, err := server.GenerateSaveCoinKey(kb, nodeDirName, keyPass, true)
 | 
						|
		if err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		info := map[string]string{"secret": secret}
 | 
						|
 | 
						|
		cliPrint, err := json.Marshal(info)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// save private key seed words
 | 
						|
		if err := writeFile(fmt.Sprintf("%v.json", "key_seed"), clientDir, cliPrint); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		accTokens := sdk.TokensFromConsensusPower(1000)
 | 
						|
		accStakingTokens := sdk.TokensFromConsensusPower(500)
 | 
						|
		coins := sdk.Coins{
 | 
						|
			sdk.NewCoin(fmt.Sprintf("%stoken", nodeDirName), accTokens),
 | 
						|
			sdk.NewCoin(sdk.DefaultBondDenom, accStakingTokens),
 | 
						|
		}
 | 
						|
		genAccounts = append(genAccounts, auth.NewBaseAccount(addr, coins.Sort(), nil, 0, 0))
 | 
						|
 | 
						|
		valTokens := sdk.TokensFromConsensusPower(100)
 | 
						|
		msg := staking.NewMsgCreateValidator(
 | 
						|
			sdk.ValAddress(addr),
 | 
						|
			valPubKeys[i],
 | 
						|
			sdk.NewCoin(sdk.DefaultBondDenom, valTokens),
 | 
						|
			staking.NewDescription(nodeDirName, "", "", "", ""),
 | 
						|
			staking.NewCommissionRates(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()),
 | 
						|
			sdk.OneInt(),
 | 
						|
		)
 | 
						|
 | 
						|
		tx := auth.NewStdTx([]sdk.Msg{msg}, auth.StdFee{}, []auth.StdSignature{}, memo)
 | 
						|
		txBldr := auth.NewTxBuilderFromCLI(inBuf).WithChainID(chainID).WithMemo(memo).WithKeybase(kb)
 | 
						|
 | 
						|
		signedTx, err := txBldr.SignStdTx(nodeDirName, clientkeys.DefaultKeyPass, tx, false)
 | 
						|
		if err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		txBytes, err := cdc.MarshalJSON(signedTx)
 | 
						|
		if err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		// gather gentxs folder
 | 
						|
		if err := writeFile(fmt.Sprintf("%v.json", nodeDirName), gentxsDir, txBytes); err != nil {
 | 
						|
			_ = os.RemoveAll(outputDir)
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		appConfigFilePath := filepath.Join(nodeDir, "config/app.toml")
 | 
						|
		srvconfig.WriteConfigFile(appConfigFilePath, kavaConfig)
 | 
						|
	}
 | 
						|
 | 
						|
	if err := initGenFiles(cdc, mbm, chainID, genAccounts, genFiles, numValidators); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err := collectGenFiles(
 | 
						|
		cdc, config, chainID, monikers, nodeIDs, valPubKeys, numValidators,
 | 
						|
		outputDir, nodeDirPrefix, nodeDaemonHome, genAccIterator,
 | 
						|
	)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	cmd.PrintErrf("Successfully initialized %d node directories\n", numValidators)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func initGenFiles(
 | 
						|
	cdc *codec.Codec, mbm module.BasicManager, chainID string,
 | 
						|
	genAccounts []authexported.GenesisAccount, genFiles []string, numValidators int,
 | 
						|
) error {
 | 
						|
 | 
						|
	appGenState := mbm.DefaultGenesis()
 | 
						|
 | 
						|
	// set the accounts in the genesis state
 | 
						|
	authDataBz := appGenState[auth.ModuleName]
 | 
						|
	var authGenState auth.GenesisState
 | 
						|
	cdc.MustUnmarshalJSON(authDataBz, &authGenState)
 | 
						|
	authGenState.Accounts = genAccounts
 | 
						|
	appGenState[auth.ModuleName] = cdc.MustMarshalJSON(authGenState)
 | 
						|
 | 
						|
	appGenStateJSON, err := codec.MarshalJSONIndent(cdc, appGenState)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	genDoc := types.GenesisDoc{
 | 
						|
		ChainID:    chainID,
 | 
						|
		AppState:   appGenStateJSON,
 | 
						|
		Validators: nil,
 | 
						|
	}
 | 
						|
 | 
						|
	// generate empty genesis files for each validator and save
 | 
						|
	for i := 0; i < numValidators; i++ {
 | 
						|
		if err := genDoc.SaveAs(genFiles[i]); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func collectGenFiles(
 | 
						|
	cdc *codec.Codec, config *tmconfig.Config, chainID string,
 | 
						|
	monikers, nodeIDs []string, valPubKeys []crypto.PubKey,
 | 
						|
	numValidators int, outputDir, nodeDirPrefix, nodeDaemonHome string,
 | 
						|
	genAccIterator genutiltypes.GenesisAccountsIterator,
 | 
						|
) error {
 | 
						|
 | 
						|
	var appState json.RawMessage
 | 
						|
	genTime := tmtime.Now()
 | 
						|
 | 
						|
	for i := 0; i < numValidators; i++ {
 | 
						|
		nodeDirName := fmt.Sprintf("%s%d", nodeDirPrefix, i)
 | 
						|
		nodeDir := filepath.Join(outputDir, nodeDirName, nodeDaemonHome)
 | 
						|
		gentxsDir := filepath.Join(outputDir, "gentxs")
 | 
						|
		moniker := monikers[i]
 | 
						|
		config.Moniker = nodeDirName
 | 
						|
 | 
						|
		config.SetRoot(nodeDir)
 | 
						|
 | 
						|
		nodeID, valPubKey := nodeIDs[i], valPubKeys[i]
 | 
						|
		initCfg := genutil.NewInitConfig(chainID, gentxsDir, moniker, nodeID, valPubKey)
 | 
						|
 | 
						|
		genDoc, err := types.GenesisDocFromFile(config.GenesisFile())
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		nodeAppState, err := genutil.GenAppStateFromConfig(cdc, config, initCfg, *genDoc, genAccIterator)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
 | 
						|
		if appState == nil {
 | 
						|
			// set the canonical application state (they should not differ)
 | 
						|
			appState = nodeAppState
 | 
						|
		}
 | 
						|
 | 
						|
		genFile := config.GenesisFile()
 | 
						|
 | 
						|
		// overwrite each validator's genesis file to have a canonical genesis time
 | 
						|
		if err := genutil.ExportGenesisFileWithTime(genFile, chainID, nil, appState, genTime); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
func getIP(i int, startingIPAddr string) (ip string, err error) {
 | 
						|
	if len(startingIPAddr) == 0 {
 | 
						|
		ip, err = server.ExternalIP()
 | 
						|
		if err != nil {
 | 
						|
			return "", err
 | 
						|
		}
 | 
						|
 | 
						|
		return ip, nil
 | 
						|
	}
 | 
						|
 | 
						|
	return calculateIP(startingIPAddr, i)
 | 
						|
}
 | 
						|
 | 
						|
func calculateIP(ip string, i int) (string, error) {
 | 
						|
	ipv4 := net.ParseIP(ip).To4()
 | 
						|
	if ipv4 == nil {
 | 
						|
		return "", fmt.Errorf("%v: non ipv4 address", ip)
 | 
						|
	}
 | 
						|
 | 
						|
	for j := 0; j < i; j++ {
 | 
						|
		ipv4[3]++
 | 
						|
	}
 | 
						|
 | 
						|
	return ipv4.String(), nil
 | 
						|
}
 | 
						|
 | 
						|
func writeFile(name string, dir string, contents []byte) error {
 | 
						|
	writePath := filepath.Join(dir)
 | 
						|
	file := filepath.Join(writePath, name)
 | 
						|
 | 
						|
	err := tmos.EnsureDir(writePath, 0700)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	err = tmos.WriteFile(file, contents, 0600)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 |