github.com/Finschia/finschia-sdk@v0.48.1/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  	ostos "github.com/Finschia/ostracon/libs/os"
    13  	octypes "github.com/Finschia/ostracon/types"
    14  	"github.com/pkg/errors"
    15  	"github.com/spf13/cobra"
    16  
    17  	"github.com/Finschia/finschia-sdk/client"
    18  	"github.com/Finschia/finschia-sdk/client/flags"
    19  	"github.com/Finschia/finschia-sdk/client/tx"
    20  	"github.com/Finschia/finschia-sdk/crypto/keyring"
    21  	"github.com/Finschia/finschia-sdk/server"
    22  	sdk "github.com/Finschia/finschia-sdk/types"
    23  	"github.com/Finschia/finschia-sdk/types/module"
    24  	"github.com/Finschia/finschia-sdk/version"
    25  	authclient "github.com/Finschia/finschia-sdk/x/auth/client"
    26  	"github.com/Finschia/finschia-sdk/x/genutil"
    27  	"github.com/Finschia/finschia-sdk/x/genutil/types"
    28  	"github.com/Finschia/finschia-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) *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 Bech32 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.GetClientQueryContext(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  			genDoc, err := octypes.GenesisDocFromFile(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(genDoc.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, genDoc.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  
   124  			err = genutil.ValidateAccountInGenesis(genesisState, genBalIterator, key.GetAddress(), coins, cdc)
   125  			if err != nil {
   126  				return errors.Wrap(err, "failed to validate account in genesis")
   127  			}
   128  
   129  			txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags())
   130  			if err != nil {
   131  				return errors.Wrap(err, "error creating tx builder")
   132  			}
   133  
   134  			clientCtx = clientCtx.WithInput(inBuf).WithFromAddress(key.GetAddress())
   135  
   136  			// The following line comes from a discrepancy between the `gentx`
   137  			// and `create-validator` commands:
   138  			// - `gentx` expects amount as an arg,
   139  			// - `create-validator` expects amount as a required flag.
   140  			// ref: https://github.com/cosmos/cosmos-sdk/issues/8251
   141  			// Since gentx doesn't set the amount flag (which `create-validator`
   142  			// reads from), we copy the amount arg into the valCfg directly.
   143  			//
   144  			// Ideally, the `create-validator` command should take a validator
   145  			// config file instead of so many flags.
   146  			// ref: https://github.com/cosmos/cosmos-sdk/issues/8177
   147  			createValCfg.Amount = amount
   148  
   149  			// create a 'create-validator' message
   150  			txBldr, msg, err := cli.BuildCreateValidatorMsg(clientCtx, createValCfg, txFactory, true)
   151  			if err != nil {
   152  				return errors.Wrap(err, "failed to build create-validator message")
   153  			}
   154  
   155  			if key.GetType() == keyring.TypeOffline || key.GetType() == keyring.TypeMulti {
   156  				cmd.PrintErrln("Offline key passed in. Use `tx sign` command to sign.")
   157  				return authclient.PrintUnsignedStdTx(txBldr, clientCtx, []sdk.Msg{msg})
   158  			}
   159  
   160  			// write the unsigned transaction to the buffer
   161  			w := bytes.NewBuffer([]byte{})
   162  			clientCtx = clientCtx.WithOutput(w)
   163  
   164  			if err = msg.ValidateBasic(); err != nil {
   165  				return err
   166  			}
   167  
   168  			if err = authclient.PrintUnsignedStdTx(txBldr, clientCtx, []sdk.Msg{msg}); err != nil {
   169  				return errors.Wrap(err, "failed to print unsigned std tx")
   170  			}
   171  
   172  			// read the transaction
   173  			stdTx, err := readUnsignedGenTxFile(clientCtx, w)
   174  			if err != nil {
   175  				return errors.Wrap(err, "failed to read unsigned gen tx file")
   176  			}
   177  
   178  			// sign the transaction and write it to the output file
   179  			txBuilder, err := clientCtx.TxConfig.WrapTxBuilder(stdTx)
   180  			if err != nil {
   181  				return fmt.Errorf("error creating tx builder: %w", err)
   182  			}
   183  
   184  			err = authclient.SignTx(txFactory, clientCtx, name, txBuilder, true, true)
   185  			if err != nil {
   186  				return errors.Wrap(err, "failed to sign std tx")
   187  			}
   188  
   189  			outputDocument, _ := cmd.Flags().GetString(flags.FlagOutputDocument)
   190  			if outputDocument == "" {
   191  				outputDocument, err = makeOutputFilepath(config.RootDir, nodeID)
   192  				if err != nil {
   193  					return errors.Wrap(err, "failed to create output file path")
   194  				}
   195  			}
   196  
   197  			if err := writeSignedGenTx(clientCtx, outputDocument, stdTx); err != nil {
   198  				return errors.Wrap(err, "failed to write signed gen tx")
   199  			}
   200  
   201  			cmd.PrintErrf("Genesis transaction written to %q\n", outputDocument)
   202  			return nil
   203  		},
   204  	}
   205  
   206  	cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
   207  	cmd.Flags().String(flags.FlagOutputDocument, "", "Write the genesis transaction JSON document to the given file instead of the default location")
   208  	cmd.Flags().String(flags.FlagChainID, "", "The network chain ID")
   209  	cmd.Flags().AddFlagSet(fsCreateValidator)
   210  	flags.AddTxFlagsToCmd(cmd)
   211  
   212  	return cmd
   213  }
   214  
   215  func makeOutputFilepath(rootDir, nodeID string) (string, error) {
   216  	writePath := filepath.Join(rootDir, "config", "gentx")
   217  	if err := ostos.EnsureDir(writePath, 0700); err != nil {
   218  		return "", err
   219  	}
   220  
   221  	return filepath.Join(writePath, fmt.Sprintf("gentx-%v.json", nodeID)), nil
   222  }
   223  
   224  func readUnsignedGenTxFile(clientCtx client.Context, r io.Reader) (sdk.Tx, error) {
   225  	bz, err := io.ReadAll(r)
   226  	if err != nil {
   227  		return nil, err
   228  	}
   229  
   230  	aTx, err := clientCtx.TxConfig.TxJSONDecoder()(bz)
   231  	if err != nil {
   232  		return nil, err
   233  	}
   234  
   235  	return aTx, err
   236  }
   237  
   238  func writeSignedGenTx(clientCtx client.Context, outputDocument string, tx sdk.Tx) error {
   239  	outputFile, err := os.OpenFile(outputDocument, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644)
   240  	if err != nil {
   241  		return err
   242  	}
   243  	defer outputFile.Close()
   244  
   245  	json, err := clientCtx.TxConfig.TxJSONEncoder()(tx)
   246  	if err != nil {
   247  		return err
   248  	}
   249  
   250  	_, err = fmt.Fprintf(outputFile, "%s\n", json)
   251  
   252  	return err
   253  }