github.com/Finschia/finschia-sdk@v0.49.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  	kmultisig "github.com/Finschia/finschia-sdk/crypto/keys/multisig"
    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  			// Bech32 decode error, maybe it's a name, we try to fetch from keyring
   262  			multisigAddr, multisigName, _, err := client.GetFromFields(txFactory.Keybase(), multisig, clientCtx.GenerateOnly)
   263  			if err != nil {
   264  				return fmt.Errorf("error getting account from keybase: %w", err)
   265  			}
   266  			multisigkey, err := getMultisigRecord(clientCtx, multisigName)
   267  			if err != nil {
   268  				return err
   269  			}
   270  			multisigPubKey := multisigkey.GetPubKey()
   271  			multisigLegacyPub := multisigPubKey.(*kmultisig.LegacyAminoPubKey)
   272  
   273  			fromRecord, err := clientCtx.Keyring.Key(fromName)
   274  			if err != nil {
   275  				return fmt.Errorf("error getting account from keybase: %w", err)
   276  			}
   277  			fromPubKey := fromRecord.GetPubKey()
   278  
   279  			var found bool
   280  			for _, pubkey := range multisigLegacyPub.GetPubKeys() {
   281  				if pubkey.Equals(fromPubKey) {
   282  					found = true
   283  				}
   284  			}
   285  			if !found {
   286  				return fmt.Errorf("signing key is not a part of multisig key")
   287  			}
   288  			err = authclient.SignTxWithSignerAddress(
   289  				txF, clientCtx, multisigAddr, fromName, txBuilder, clientCtx.Offline, overwrite)
   290  			if err != nil {
   291  				return err
   292  			}
   293  			printSignatureOnly = true
   294  		} else {
   295  			err = authclient.SignTx(txF, clientCtx, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, overwrite)
   296  		}
   297  		if err != nil {
   298  			return err
   299  		}
   300  
   301  		aminoJSON, err := f.GetBool(flagAmino)
   302  		if err != nil {
   303  			return err
   304  		}
   305  
   306  		var json []byte
   307  		if aminoJSON {
   308  			stdTx, err := tx.ConvertTxToStdTx(clientCtx.LegacyAmino, txBuilder.GetTx())
   309  			if err != nil {
   310  				return err
   311  			}
   312  			req := BroadcastReq{
   313  				Tx:   stdTx,
   314  				Mode: "block|sync|async",
   315  			}
   316  			json, err = clientCtx.LegacyAmino.MarshalJSON(req)
   317  			if err != nil {
   318  				return err
   319  			}
   320  		} else {
   321  			json, err = marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
   322  			if err != nil {
   323  				return err
   324  			}
   325  		}
   326  
   327  		outputDoc, _ := cmd.Flags().GetString(flags.FlagOutputDocument)
   328  		if outputDoc == "" {
   329  			cmd.Printf("%s\n", json)
   330  			return nil
   331  		}
   332  
   333  		fp, err := os.OpenFile(outputDoc, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o644)
   334  		if err != nil {
   335  			return err
   336  		}
   337  		defer func() {
   338  			err2 := fp.Close()
   339  			if err == nil {
   340  				err = err2
   341  			}
   342  		}()
   343  
   344  		_, err = fp.Write(append(json, '\n'))
   345  		return err
   346  	}
   347  }
   348  
   349  func marshalSignatureJSON(txConfig client.TxConfig, txBldr client.TxBuilder, signatureOnly bool) ([]byte, error) {
   350  	parsedTx := txBldr.GetTx()
   351  	if signatureOnly {
   352  		sigs, err := parsedTx.GetSignaturesV2()
   353  		if err != nil {
   354  			return nil, err
   355  		}
   356  		return txConfig.MarshalSignatureJSON(sigs)
   357  	}
   358  
   359  	return txConfig.TxJSONEncoder()(parsedTx)
   360  }