github.com/Finschia/finschia-sdk@v0.48.1/x/auth/client/cli/tx_sign.go (about)

     1  package cli
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  
     7  	"github.com/spf13/cobra"
     8  
     9  	"github.com/Finschia/finschia-sdk/client"
    10  	"github.com/Finschia/finschia-sdk/client/flags"
    11  	"github.com/Finschia/finschia-sdk/client/tx"
    12  	sdk "github.com/Finschia/finschia-sdk/types"
    13  	authclient "github.com/Finschia/finschia-sdk/x/auth/client"
    14  )
    15  
    16  const (
    17  	flagMultisig        = "multisig"
    18  	flagOverwrite       = "overwrite"
    19  	flagSigOnly         = "signature-only"
    20  	flagAmino           = "amino"
    21  	flagNoAutoIncrement = "no-auto-increment"
    22  )
    23  
    24  // GetSignBatchCommand returns the transaction sign-batch command.
    25  func GetSignBatchCommand() *cobra.Command {
    26  	cmd := &cobra.Command{
    27  		Use:   "sign-batch [file]",
    28  		Short: "Sign transaction batch files",
    29  		Long: `Sign batch files of transactions generated with --generate-only.
    30  The command processes list of transactions from file (one StdTx each line), generate
    31  signed transactions or signatures and print their JSON encoding, delimited by '\n'.
    32  As the signatures are generated, the command updates the account sequence number accordingly.
    33  
    34  If the --signature-only flag is set, it will output the signature parts only.
    35  
    36  The --offline flag makes sure that the client will not reach out to full node.
    37  As a result, the account and the sequence number queries will not be performed and
    38  it is required to set such parameters manually. Note, invalid values will cause
    39  the transaction to fail. The sequence will be incremented automatically for each
    40  transaction that is signed.
    41  
    42  The --multisig=<multisig_key> flag generates a signature on behalf of a multisig
    43  account key. It implies --signature-only.
    44  `,
    45  		PreRun: preSignCmd,
    46  		RunE:   makeSignBatchCmd(),
    47  		Args:   cobra.ExactArgs(1),
    48  	}
    49  
    50  	cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed")
    51  	cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT")
    52  	cmd.Flags().Bool(flagSigOnly, true, "Print only the generated signature, then exit")
    53  	cmd.Flags().String(flags.FlagChainID, "", "network chain ID")
    54  	cmd.MarkFlagRequired(flags.FlagFrom)
    55  	flags.AddTxFlagsToCmd(cmd)
    56  
    57  	return cmd
    58  }
    59  
    60  func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
    61  	return func(cmd *cobra.Command, args []string) error {
    62  		clientCtx, err := client.GetClientTxContext(cmd)
    63  		if err != nil {
    64  			return err
    65  		}
    66  		txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags())
    67  		txCfg := clientCtx.TxConfig
    68  		printSignatureOnly, _ := cmd.Flags().GetBool(flagSigOnly)
    69  		infile := os.Stdin
    70  
    71  		ms, err := cmd.Flags().GetString(flagMultisig)
    72  		if err != nil {
    73  			return err
    74  		}
    75  
    76  		// prepare output document
    77  		closeFunc, err := setOutputFile(cmd)
    78  		if err != nil {
    79  			return err
    80  		}
    81  
    82  		defer closeFunc()
    83  		clientCtx.WithOutput(cmd.OutOrStdout())
    84  
    85  		if args[0] != "-" {
    86  			infile, err = os.Open(args[0])
    87  			if err != nil {
    88  				return err
    89  			}
    90  		}
    91  		scanner := authclient.NewBatchScanner(txCfg, infile)
    92  
    93  		if !clientCtx.Offline {
    94  			if ms == "" {
    95  				from, err := cmd.Flags().GetString(flags.FlagFrom)
    96  				if err != nil {
    97  					return err
    98  				}
    99  
   100  				addr, _, _, err := client.GetFromFields(txFactory.Keybase(), from, clientCtx.GenerateOnly)
   101  				if err != nil {
   102  					return err
   103  				}
   104  
   105  				acc, err := txFactory.AccountRetriever().GetAccount(clientCtx, addr)
   106  				if err != nil {
   107  					return err
   108  				}
   109  
   110  				txFactory = txFactory.WithAccountNumber(acc.GetAccountNumber()).WithSequence(acc.GetSequence())
   111  			} else {
   112  				txFactory = txFactory.WithAccountNumber(0).WithSequence(0)
   113  			}
   114  		}
   115  
   116  		for sequence := txFactory.Sequence(); scanner.Scan(); sequence++ {
   117  			unsignedStdTx := scanner.Tx()
   118  			txFactory = txFactory.WithSequence(sequence)
   119  			txBuilder, err := txCfg.WrapTxBuilder(unsignedStdTx)
   120  			if err != nil {
   121  				return err
   122  			}
   123  			if ms == "" {
   124  				from, _ := cmd.Flags().GetString(flags.FlagFrom)
   125  				_, fromName, _, err := client.GetFromFields(txFactory.Keybase(), from, clientCtx.GenerateOnly)
   126  				if err != nil {
   127  					return fmt.Errorf("error getting account from keybase: %w", err)
   128  				}
   129  				err = authclient.SignTx(txFactory, clientCtx, fromName, txBuilder, true, true)
   130  				if err != nil {
   131  					return err
   132  				}
   133  			} else {
   134  				multisigAddr, _, _, err := client.GetFromFields(txFactory.Keybase(), ms, clientCtx.GenerateOnly)
   135  				if err != nil {
   136  					return fmt.Errorf("error getting account from keybase: %w", err)
   137  				}
   138  				err = authclient.SignTxWithSignerAddress(
   139  					txFactory, clientCtx, multisigAddr, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, true)
   140  				if err != nil {
   141  					return err
   142  				}
   143  			}
   144  
   145  			if err != nil {
   146  				return err
   147  			}
   148  
   149  			json, err := marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
   150  			if err != nil {
   151  				return err
   152  			}
   153  
   154  			cmd.Printf("%s\n", json)
   155  		}
   156  
   157  		if err := scanner.UnmarshalErr(); err != nil {
   158  			return err
   159  		}
   160  
   161  		return scanner.UnmarshalErr()
   162  	}
   163  }
   164  
   165  func setOutputFile(cmd *cobra.Command) (func(), error) {
   166  	outputDoc, _ := cmd.Flags().GetString(flags.FlagOutputDocument)
   167  	if outputDoc == "" {
   168  		return func() {}, nil
   169  	}
   170  
   171  	fp, err := os.OpenFile(outputDoc, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
   172  	if err != nil {
   173  		return func() {}, err
   174  	}
   175  
   176  	cmd.SetOut(fp)
   177  
   178  	return func() { fp.Close() }, nil
   179  }
   180  
   181  // GetSignCommand returns the transaction sign command.
   182  func GetSignCommand() *cobra.Command {
   183  	cmd := &cobra.Command{
   184  		Use:   "sign [file]",
   185  		Short: "Sign a transaction generated offline",
   186  		Long: `Sign a transaction created with the --generate-only flag.
   187  It will read a transaction from [file], sign it, and print its JSON encoding.
   188  
   189  If the --signature-only flag is set, it will output the signature parts only.
   190  
   191  The --offline flag makes sure that the client will not reach out to full node.
   192  As a result, the account and sequence number queries will not be performed and
   193  it is required to set such parameters manually. Note, invalid values will cause
   194  the transaction to fail.
   195  
   196  The --multisig=<multisig_key> flag generates a signature on behalf of a multisig account
   197  key. It implies --signature-only. Full multisig signed transactions may eventually
   198  be generated via the 'multisign' command.
   199  `,
   200  		PreRun: preSignCmd,
   201  		RunE:   makeSignCmd(),
   202  		Args:   cobra.ExactArgs(1),
   203  	}
   204  
   205  	cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed")
   206  	cmd.Flags().Bool(flagOverwrite, false, "Overwrite existing signatures with a new one. If disabled, new signature will be appended")
   207  	cmd.Flags().Bool(flagSigOnly, false, "Print only the signatures")
   208  	cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT")
   209  	cmd.Flags().String(flags.FlagChainID, "", "The network chain ID")
   210  	cmd.Flags().Bool(flagAmino, false, "Generate Amino encoded JSON suitable for submiting to the txs REST endpoint")
   211  	cmd.MarkFlagRequired(flags.FlagFrom)
   212  	flags.AddTxFlagsToCmd(cmd)
   213  
   214  	return cmd
   215  }
   216  
   217  func preSignCmd(cmd *cobra.Command, _ []string) {
   218  	// Conditionally mark the account and sequence numbers required as no RPC
   219  	// query will be done.
   220  	if offline, _ := cmd.Flags().GetBool(flags.FlagOffline); offline {
   221  		cmd.MarkFlagRequired(flags.FlagAccountNumber)
   222  		cmd.MarkFlagRequired(flags.FlagSequence)
   223  	}
   224  }
   225  
   226  func makeSignCmd() func(cmd *cobra.Command, args []string) error {
   227  	return func(cmd *cobra.Command, args []string) (err error) {
   228  		var clientCtx client.Context
   229  
   230  		clientCtx, err = client.GetClientTxContext(cmd)
   231  		if err != nil {
   232  			return err
   233  		}
   234  		f := cmd.Flags()
   235  
   236  		clientCtx, txF, newTx, err := readTxAndInitContexts(clientCtx, cmd, args[0])
   237  		if err != nil {
   238  			return err
   239  		}
   240  
   241  		txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags())
   242  		txCfg := clientCtx.TxConfig
   243  		txBuilder, err := txCfg.WrapTxBuilder(newTx)
   244  		if err != nil {
   245  			return err
   246  		}
   247  
   248  		printSignatureOnly, _ := cmd.Flags().GetBool(flagSigOnly)
   249  		multisig, _ := cmd.Flags().GetString(flagMultisig)
   250  		if err != nil {
   251  			return err
   252  		}
   253  		from, _ := cmd.Flags().GetString(flags.FlagFrom)
   254  		_, fromName, _, err := client.GetFromFields(txF.Keybase(), from, clientCtx.GenerateOnly)
   255  		if err != nil {
   256  			return fmt.Errorf("error getting account from keybase: %w", err)
   257  		}
   258  
   259  		overwrite, _ := f.GetBool(flagOverwrite)
   260  		if multisig != "" {
   261  			multisigAddr, err := sdk.AccAddressFromBech32(multisig)
   262  			if err != nil {
   263  				// Bech32 decode error, maybe it's a name, we try to fetch from keyring
   264  				multisigAddr, _, _, err = client.GetFromFields(txFactory.Keybase(), multisig, clientCtx.GenerateOnly)
   265  				if err != nil {
   266  					return fmt.Errorf("error getting account from keybase: %w", err)
   267  				}
   268  			}
   269  			err = authclient.SignTxWithSignerAddress(
   270  				txF, clientCtx, multisigAddr, fromName, txBuilder, clientCtx.Offline, overwrite)
   271  			if err != nil {
   272  				return err
   273  			}
   274  			printSignatureOnly = true
   275  		} else {
   276  			err = authclient.SignTx(txF, clientCtx, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, overwrite)
   277  		}
   278  		if err != nil {
   279  			return err
   280  		}
   281  
   282  		aminoJSON, err := f.GetBool(flagAmino)
   283  		if err != nil {
   284  			return err
   285  		}
   286  
   287  		var json []byte
   288  		if aminoJSON {
   289  			stdTx, err := tx.ConvertTxToStdTx(clientCtx.LegacyAmino, txBuilder.GetTx())
   290  			if err != nil {
   291  				return err
   292  			}
   293  			req := BroadcastReq{
   294  				Tx:   stdTx,
   295  				Mode: "block|sync|async",
   296  			}
   297  			json, err = clientCtx.LegacyAmino.MarshalJSON(req)
   298  			if err != nil {
   299  				return err
   300  			}
   301  		} else {
   302  			json, err = marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
   303  			if err != nil {
   304  				return err
   305  			}
   306  		}
   307  
   308  		outputDoc, _ := cmd.Flags().GetString(flags.FlagOutputDocument)
   309  		if outputDoc == "" {
   310  			cmd.Printf("%s\n", json)
   311  			return nil
   312  		}
   313  
   314  		fp, err := os.OpenFile(outputDoc, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
   315  		if err != nil {
   316  			return err
   317  		}
   318  		defer func() {
   319  			err2 := fp.Close()
   320  			if err == nil {
   321  				err = err2
   322  			}
   323  		}()
   324  
   325  		_, err = fp.Write(append(json, '\n'))
   326  		return err
   327  	}
   328  }
   329  
   330  func marshalSignatureJSON(txConfig client.TxConfig, txBldr client.TxBuilder, signatureOnly bool) ([]byte, error) {
   331  	parsedTx := txBldr.GetTx()
   332  	if signatureOnly {
   333  		sigs, err := parsedTx.GetSignaturesV2()
   334  		if err != nil {
   335  			return nil, err
   336  		}
   337  		return txConfig.MarshalSignatureJSON(sigs)
   338  	}
   339  
   340  	return txConfig.TxJSONEncoder()(parsedTx)
   341  }