Skip to content

Commit

Permalink
feat: let sign-batch read multiple files (cosmos#13454)
Browse files Browse the repository at this point in the history
* feat: let `sign-batch` read multiple files

* add changelog

* fix gosec issue

* simplify
  • Loading branch information
julienrbrt authored and Wryhder committed Oct 26, 2022
1 parent fbff3c5 commit 1eb2705
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 67 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -52,6 +52,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/auth) [#13210](https://github.com/cosmos/cosmos-sdk/pull/13210) Add `Query/AccountInfo` endpoint for simplified access to basic account info.
* (cli) [#13147](https://github.com/cosmos/cosmos-sdk/pull/13147) Add the `--append` flag to the `sign-batch` CLI cmd to combine the messages and sign those txs which are created with `--generate-only`.
* (x/consensus) [#12905](https://github.com/cosmos/cosmos-sdk/pull/12905) Create a new `x/consensus` module that is now responsible for maintaining Tendermint consensus parameters instead of `x/param`. Legacy types remain in order to facilitate parameter migration from the deprecated `x/params`. App developers should ensure that they execute `baseapp.MigrateParams` during their chain upgrade. These legacy types will be removed in a future release.
* (cli) [#13454](https://github.com/cosmos/cosmos-sdk/pull/13454) `sign-batch` CLI can now read multiple transaction files.

### Improvements

Expand Down
20 changes: 5 additions & 15 deletions x/auth/client/cli/tx_multisign.go
Expand Up @@ -29,7 +29,7 @@ type BroadcastReq struct {
Mode string `json:"mode" yaml:"mode"`
}

// GetSignCommand returns the sign command
// GetMultiSignCommand returns the multi-sign command
func GetMultiSignCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "multi-sign [file] [name] [[signature]...]",
Expand Down Expand Up @@ -258,21 +258,11 @@ func makeBatchMultisignCmd() func(cmd *cobra.Command, args []string) error {
txFactory = txFactory.WithSignMode(signingtypes.SignMode_SIGN_MODE_LEGACY_AMINO_JSON)
}

infile := os.Stdin
if args[0] != "-" {
infile, err = os.Open(args[0])
defer func() {
err2 := infile.Close()
if err == nil {
err = err2
}
}()

if err != nil {
return fmt.Errorf("couldn't open %s: %w", args[0], err)
}
// reads tx from args[0]
scanner, err := authclient.ReadTxsFromInput(txCfg, args[0])
if err != nil {
return err
}
scanner := authclient.NewBatchScanner(txCfg, infile)

k, err := getMultisigRecord(clientCtx, args[1])
if err != nil {
Expand Down
103 changes: 52 additions & 51 deletions x/auth/client/cli/tx_sign.go
Expand Up @@ -27,11 +27,11 @@ const (
// GetSignBatchCommand returns the transaction sign-batch command.
func GetSignBatchCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "sign-batch [file]",
Use: "sign-batch [file] ([file2]...)",
Short: "Sign transaction batch files",
Long: `Sign batch files of transactions generated with --generate-only.
The command processes list of transactions from file (one StdTx each line), generate
signed transactions or signatures and print their JSON encoding, delimited by '\n'.
The command processes list of transactions from a file (one StdTx each line), or multiple files.
Then generates signed transactions or signatures and print their JSON encoding, delimited by '\n'.
As the signatures are generated, the command updates the account and sequence number accordingly.
If the --signature-only flag is set, it will output the signature parts only.
Expand All @@ -50,7 +50,7 @@ account key. It implies --signature-only.
`,
PreRun: preSignCmd,
RunE: makeSignBatchCmd(),
Args: cobra.ExactArgs(1),
Args: cobra.MinimumNArgs(1),
}

cmd.Flags().String(flagMultisig, "", "Address or key name of the multisig account on behalf of which the transaction shall be signed")
Expand All @@ -74,7 +74,6 @@ func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
txFactory := tx.NewFactoryCLI(clientCtx, cmd.Flags())
txCfg := clientCtx.TxConfig
printSignatureOnly, _ := cmd.Flags().GetBool(flagSigOnly)
infile := os.Stdin

ms, err := cmd.Flags().GetString(flagMultisig)
if err != nil {
Expand All @@ -86,17 +85,14 @@ func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}

defer closeFunc()
clientCtx.WithOutput(cmd.OutOrStdout())

if args[0] != "-" {
infile, err = os.Open(args[0])
if err != nil {
return err
}
// reads tx from args
scanner, err := authclient.ReadTxsFromInput(txCfg, args...)
if err != nil {
return err
}
scanner := authclient.NewBatchScanner(txCfg, infile)

if !clientCtx.Offline {
if ms == "" {
Expand All @@ -121,9 +117,9 @@ func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
}
}

appendMessagesToSingleMsg, _ := cmd.Flags().GetBool(flagAppend)
if appendMessagesToSingleMsg {
// It will combine all tx msgs and create single signed transaction
appendMessagesToSingleTx, _ := cmd.Flags().GetBool(flagAppend)
// Combines all tx msgs and create single signed transaction
if appendMessagesToSingleTx {
txBuilder := clientCtx.TxConfig.NewTxBuilder()
msgs := make([]sdk.Msg, 0)
newGasLimit := uint64(0)
Expand Down Expand Up @@ -151,39 +147,24 @@ func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
// set the gasLimit
txBuilder.SetGasLimit(newGasLimit)

// sign the txs
if ms == "" {
from, _ := cmd.Flags().GetString(flags.FlagFrom)
_, fromName, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), from)
if err != nil {
return fmt.Errorf("error getting account from keybase: %w", err)
}
err = authclient.SignTx(txFactory, clientCtx, fromName, txBuilder, true, true)
if err != nil {
if err := sign(clientCtx, txBuilder, txFactory, from); err != nil {
return err
}
} else {
multisigAddr, _, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), ms)
if err != nil {
return fmt.Errorf("error getting account from keybase: %w", err)
}
err = authclient.SignTxWithSignerAddress(
txFactory, clientCtx, multisigAddr, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, true)
if err != nil {
if err := multisigSign(clientCtx, txBuilder, txFactory, ms); err != nil {
return err
}
}

if err != nil {
return err
}

json, err := marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
if err != nil {
return err
}

cmd.Printf("%s\n", json)

} else {
// It will generate signed tx for each tx
for sequence := txFactory.Sequence(); scanner.Scan(); sequence++ {
Expand All @@ -193,37 +174,23 @@ func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}

// sign the txs
if ms == "" {
from, _ := cmd.Flags().GetString(flags.FlagFrom)
_, fromName, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), from)
if err != nil {
return fmt.Errorf("error getting account from keybase: %w", err)
}
err = authclient.SignTx(txFactory, clientCtx, fromName, txBuilder, true, true)
if err != nil {
if err := sign(clientCtx, txBuilder, txFactory, from); err != nil {
return err
}
} else {
multisigAddr, _, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), ms)
if err != nil {
return fmt.Errorf("error getting account from keybase: %w", err)
}
err = authclient.SignTxWithSignerAddress(
txFactory, clientCtx, multisigAddr, clientCtx.GetFromName(), txBuilder, clientCtx.Offline, true)
if err != nil {
if err := multisigSign(clientCtx, txBuilder, txFactory, ms); err != nil {
return err
}
}

if err != nil {
return err
}

json, err := marshalSignatureJSON(txCfg, txBuilder, printSignatureOnly)
if err != nil {
return err
}

cmd.Printf("%s\n", json)
}
}
Expand All @@ -236,6 +203,40 @@ func makeSignBatchCmd() func(cmd *cobra.Command, args []string) error {
}
}

func sign(clientCtx client.Context, txBuilder client.TxBuilder, txFactory tx.Factory, from string) error {
_, fromName, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), from)
if err != nil {
return fmt.Errorf("error getting account from keybase: %w", err)
}

if err = authclient.SignTx(txFactory, clientCtx, fromName, txBuilder, true, true); err != nil {
return err
}

return nil
}

func multisigSign(clientCtx client.Context, txBuilder client.TxBuilder, txFactory tx.Factory, multisig string) error {
multisigAddr, _, _, err := client.GetFromFields(clientCtx, txFactory.Keybase(), multisig)
if err != nil {
return fmt.Errorf("error getting account from keybase: %w", err)
}

if err = authclient.SignTxWithSignerAddress(
txFactory,
clientCtx,
multisigAddr,
clientCtx.GetFromName(),
txBuilder,
clientCtx.Offline,
true,
); err != nil {
return err
}

return nil
}

func setOutputFile(cmd *cobra.Command) (func(), error) {
outputDoc, _ := cmd.Flags().GetString(flags.FlagOutputDocument)
if outputDoc == "" {
Expand Down
30 changes: 29 additions & 1 deletion x/auth/client/tx.go
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"path/filepath"
"strings"

"github.com/cosmos/gogoproto/jsonpb"
Expand Down Expand Up @@ -90,7 +91,7 @@ func SignTxWithSignerAddress(txFactory tx.Factory, clientCtx client.Context, add
return tx.Sign(txFactory, name, txBuilder, overwrite)
}

// Read and decode a StdTx from the given filename. Can pass "-" to read from stdin.
// Read and decode a StdTx from the given filename. Can pass "-" to read from stdin.
func ReadTxFromFile(ctx client.Context, filename string) (tx sdk.Tx, err error) {
var bytes []byte

Expand All @@ -107,6 +108,33 @@ func ReadTxFromFile(ctx client.Context, filename string) (tx sdk.Tx, err error)
return ctx.TxConfig.TxJSONDecoder()(bytes)
}

// ReadTxsFromInput reads multiples txs from the given filename(s). Can pass "-" to read from stdin.
// Unlike ReadTxFromFile, this function does not decode the txs.
func ReadTxsFromInput(txCfg client.TxConfig, filenames ...string) (scanner *BatchScanner, err error) {
if len(filenames) == 0 {
return nil, fmt.Errorf("no file name provided")
}

var infile io.Reader = os.Stdin
if filenames[0] != "-" {
buf := new(bytes.Buffer)
for _, f := range filenames {
bytes, err := os.ReadFile(filepath.Clean(f))
if err != nil {
return nil, fmt.Errorf("couldn't read %s: %w", f, err)
}

if _, err := buf.WriteString(string(bytes)); err != nil {
return nil, fmt.Errorf("couldn't write to merged file: %w", err)
}
}

infile = buf
}

return NewBatchScanner(txCfg, infile), nil
}

// NewBatchScanner returns a new BatchScanner to read newline-delimited StdTx transactions from r.
func NewBatchScanner(cfg client.TxConfig, r io.Reader) *BatchScanner {
return &BatchScanner{Scanner: bufio.NewScanner(r), cfg: cfg}
Expand Down

0 comments on commit 1eb2705

Please sign in to comment.