github.com/cosmos/cosmos-sdk@v0.50.10/x/genutil/client/cli/gentx.go (about)

     1  package cli
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  
    12  	"github.com/spf13/cobra"
    13  
    14  	address "cosmossdk.io/core/address"
    15  	"cosmossdk.io/errors"
    16  
    17  	"github.com/cosmos/cosmos-sdk/client"
    18  	"github.com/cosmos/cosmos-sdk/client/flags"
    19  	"github.com/cosmos/cosmos-sdk/client/tx"
    20  	"github.com/cosmos/cosmos-sdk/crypto/keyring"
    21  	"github.com/cosmos/cosmos-sdk/server"
    22  	sdk "github.com/cosmos/cosmos-sdk/types"
    23  	"github.com/cosmos/cosmos-sdk/types/module"
    24  	"github.com/cosmos/cosmos-sdk/version"
    25  	authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
    26  	"github.com/cosmos/cosmos-sdk/x/genutil"
    27  	"github.com/cosmos/cosmos-sdk/x/genutil/types"
    28  	"github.com/cosmos/cosmos-sdk/x/staking/client/cli"
    29  )
    30  
    31  // GenTxCmd builds the application's gentx command.
    32  func GenTxCmd(mbm module.BasicManager, txEncCfg client.TxEncodingConfig, genBalIterator types.GenesisBalancesIterator, defaultNodeHome string, valAdddressCodec address.Codec) *cobra.Command {
    33  	ipDefault, _ := server.ExternalIP()
    34  	fsCreateValidator, defaultsDesc := cli.CreateValidatorMsgFlagSet(ipDefault)
    35  
    36  	cmd := &cobra.Command{
    37  		Use:   "gentx [key_name] [amount]",
    38  		Short: "Generate a genesis tx carrying a self delegation",
    39  		Args:  cobra.ExactArgs(2),
    40  		Long: fmt.Sprintf(`Generate a genesis transaction that creates a validator with a self-delegation,
    41  that is signed by the key in the Keyring referenced by a given name. A node ID and consensus
    42  pubkey may optionally be provided. If they are omitted, they will be retrieved from the priv_validator.json
    43  file. The following default parameters are included:
    44      %s
    45  
    46  Example:
    47  $ %s gentx my-key-name 1000000stake --home=/path/to/home/dir --keyring-backend=os --chain-id=test-chain-1 \
    48      --moniker="myvalidator" \
    49      --commission-max-change-rate=0.01 \
    50      --commission-max-rate=1.0 \
    51      --commission-rate=0.07 \
    52      --details="..." \
    53      --security-contact="..." \
    54      --website="..."
    55  `, defaultsDesc, version.AppName,
    56  		),
    57  		RunE: func(cmd *cobra.Command, args []string) error {
    58  			serverCtx := server.GetServerContextFromCmd(cmd)
    59  			clientCtx, err := client.GetClientTxContext(cmd)
    60  			if err != nil {
    61  				return err
    62  			}
    63  			cdc := clientCtx.Codec
    64  
    65  			config := serverCtx.Config
    66  			config.SetRoot(clientCtx.HomeDir)
    67  
    68  			nodeID, valPubKey, err := genutil.InitializeNodeValidatorFiles(serverCtx.Config)
    69  			if err != nil {
    70  				return errors.Wrap(err, "failed to initialize node validator files")
    71  			}
    72  
    73  			// read --nodeID, if empty take it from priv_validator.json
    74  			if nodeIDString, _ := cmd.Flags().GetString(cli.FlagNodeID); nodeIDString != "" {
    75  				nodeID = nodeIDString
    76  			}
    77  
    78  			// read --pubkey, if empty take it from priv_validator.json
    79  			if pkStr, _ := cmd.Flags().GetString(cli.FlagPubKey); pkStr != "" {
    80  				if err := clientCtx.Codec.UnmarshalInterfaceJSON([]byte(pkStr), &valPubKey); err != nil {
    81  					return errors.Wrap(err, "failed to unmarshal validator public key")
    82  				}
    83  			}
    84  
    85  			appGenesis, err := types.AppGenesisFromFile(config.GenesisFile())
    86  			if err != nil {
    87  				return errors.Wrapf(err, "failed to read genesis doc file %s", config.GenesisFile())
    88  			}
    89  
    90  			var genesisState map[string]json.RawMessage
    91  			if err = json.Unmarshal(appGenesis.AppState, &genesisState); err != nil {
    92  				return errors.Wrap(err, "failed to unmarshal genesis state")
    93  			}
    94  
    95  			if err = mbm.ValidateGenesis(cdc, txEncCfg, genesisState); err != nil {
    96  				return errors.Wrap(err, "failed to validate genesis state")
    97  			}
    98  
    99  			inBuf := bufio.NewReader(cmd.InOrStdin())
   100  
   101  			name := args[0]
   102  			key, err := clientCtx.Keyring.Key(name)
   103  			if err != nil {
   104  				return errors.Wrapf(err, "failed to fetch '%s' from the keyring", name)
   105  			}
   106  
   107  			moniker := config.Moniker
   108  			if m, _ := cmd.Flags().GetString(cli.FlagMoniker); m != "" {
   109  				moniker = m
   110  			}
   111  
   112  			// set flags for creating a gentx
   113  			createValCfg, err := cli.PrepareConfigForTxCreateValidator(cmd.Flags(), moniker, nodeID, appGenesis.ChainID, valPubKey)
   114  			if err != nil {
   115  				return errors.Wrap(err, "error creating configuration to create validator msg")
   116  			}
   117  
   118  			amount := args[1]
   119  			coins, err := sdk.ParseCoinsNormalized(amount)
   120  			if err != nil {
   121  				return errors.Wrap(err, "failed to parse coins")
   122  			}
   123  			addr, err := key.GetAddress()
   124  			if err != nil {
   125  				return err
   126  			}
   127  			err = genutil.ValidateAccountInGenesis(genesisState, genBalIterator, addr, coins, cdc)
   128  			if err != nil {
   129  				return errors.Wrap(err, "failed to validate account in genesis")
   130  			}
   131  
   132  			txFactory, err := tx.NewFactoryCLI(clientCtx, cmd.Flags())
   133  			if err != nil {
   134  				return err
   135  			}
   136  
   137  			pub, err := key.GetAddress()
   138  			if err != nil {
   139  				return err
   140  			}
   141  			clientCtx = clientCtx.WithInput(inBuf).WithFromAddress(pub)
   142  
   143  			// The following line comes from a discrepancy between the `gentx`
   144  			// and `create-validator` commands:
   145  			// - `gentx` expects amount as an arg,
   146  			// - `create-validator` expects amount as a required flag.
   147  			// ref: https://github.com/cosmos/cosmos-sdk/issues/8251
   148  			// Since gentx doesn't set the amount flag (which `create-validator`
   149  			// reads from), we copy the amount arg into the valCfg directly.
   150  			//
   151  			// Ideally, the `create-validator` command should take a validator
   152  			// config file instead of so many flags.
   153  			// ref: https://github.com/cosmos/cosmos-sdk/issues/8177
   154  			createValCfg.Amount = amount
   155  
   156  			// create a 'create-validator' message
   157  			txBldr, msg, err := cli.BuildCreateValidatorMsg(clientCtx, createValCfg, txFactory, true, valAdddressCodec)
   158  			if err != nil {
   159  				return errors.Wrap(err, "failed to build create-validator message")
   160  			}
   161  
   162  			if key.GetType() == keyring.TypeOffline || key.GetType() == keyring.TypeMulti {
   163  				cmd.PrintErrln("Offline key passed in. Use `tx sign` command to sign.")
   164  				return txBldr.PrintUnsignedTx(clientCtx, msg)
   165  			}
   166  
   167  			// write the unsigned transaction to the buffer
   168  			w := bytes.NewBuffer([]byte{})
   169  			clientCtx = clientCtx.WithOutput(w)
   170  
   171  			if m, ok := msg.(sdk.HasValidateBasic); ok {
   172  				if err := m.ValidateBasic(); err != nil {
   173  					return err
   174  				}
   175  			}
   176  
   177  			if err = txBldr.PrintUnsignedTx(clientCtx, msg); err != nil {
   178  				return errors.Wrap(err, "failed to print unsigned std tx")
   179  			}
   180  
   181  			// read the transaction
   182  			stdTx, err := readUnsignedGenTxFile(clientCtx, w)
   183  			if err != nil {
   184  				return errors.Wrap(err, "failed to read unsigned gen tx file")
   185  			}
   186  
   187  			// sign the transaction and write it to the output file
   188  			txBuilder, err := clientCtx.TxConfig.WrapTxBuilder(stdTx)
   189  			if err != nil {
   190  				return fmt.Errorf("error creating tx builder: %w", err)
   191  			}
   192  
   193  			err = authclient.SignTx(txFactory, clientCtx, name, txBuilder, true, true)
   194  			if err != nil {
   195  				return errors.Wrap(err, "failed to sign std tx")
   196  			}
   197  
   198  			outputDocument, _ := cmd.Flags().GetString(flags.FlagOutputDocument)
   199  			if outputDocument == "" {
   200  				outputDocument, err = makeOutputFilepath(config.RootDir, nodeID)
   201  				if err != nil {
   202  					return errors.Wrap(err, "failed to create output file path")
   203  				}
   204  			}
   205  
   206  			if err := writeSignedGenTx(clientCtx, outputDocument, stdTx); err != nil {
   207  				return errors.Wrap(err, "failed to write signed gen tx")
   208  			}
   209  
   210  			cmd.PrintErrf("Genesis transaction written to %q\n", outputDocument)
   211  			return nil
   212  		},
   213  	}
   214  
   215  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   216  	cmd.Flags().String(flags.FlagOutputDocument, "", "Write the genesis transaction JSON document to the given file instead of the default location")
   217  	cmd.Flags().AddFlagSet(fsCreateValidator)
   218  	flags.AddTxFlagsToCmd(cmd)
   219  	_ = cmd.Flags().MarkHidden(flags.FlagOutput) // signing makes sense to output only json
   220  
   221  	return cmd
   222  }
   223  
   224  func makeOutputFilepath(rootDir, nodeID string) (string, error) {
   225  	writePath := filepath.Join(rootDir, "config", "gentx")
   226  	if err := os.MkdirAll(writePath, 0o700); err != nil {
   227  		return "", fmt.Errorf("could not create directory %q: %w", writePath, err)
   228  	}
   229  
   230  	return filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)), nil
   231  }
   232  
   233  func readUnsignedGenTxFile(clientCtx client.Context, r io.Reader) (sdk.Tx, error) {
   234  	bz, err := io.ReadAll(r)
   235  	if err != nil {
   236  		return nil, err
   237  	}
   238  
   239  	aTx, err := clientCtx.TxConfig.TxJSONDecoder()(bz)
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  
   244  	return aTx, err
   245  }
   246  
   247  func writeSignedGenTx(clientCtx client.Context, outputDocument string, tx sdk.Tx) error {
   248  	outputFile, err := os.OpenFile(outputDocument, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
   249  	if err != nil {
   250  		return err
   251  	}
   252  	defer outputFile.Close()
   253  
   254  	json, err := clientCtx.TxConfig.TxJSONEncoder()(tx)
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	_, err = fmt.Fprintf(outputFile, "%s\n", json)
   260  
   261  	return err
   262  }