Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/auth: sign-batch command #6350

Merged
merged 3 commits into from
Jun 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ be used to retrieve the actual proposal `Content`. Also the `NewMsgSubmitProposa
* (x/capability) [\#5828](https://github.com/cosmos/cosmos-sdk/pull/5828) Capability module integration as outlined in [ADR 3 - Dynamic Capability Store](https://github.com/cosmos/tree/master/docs/architecture/adr-003-dynamic-capability-store.md).
* (x/params) [\#6005](https://github.com/cosmos/cosmos-sdk/pull/6005) Add new CLI command for querying raw x/params parameters by subspace and key.
* (x/ibc) [\#5769](https://github.com/cosmos/cosmos-sdk/pull/5769) [ICS 009 - Loopback Client](https://github.com/cosmos/ics/tree/master/spec/ics-009-loopback-client) subpackage
* (x/auth) [\6350](https://github.com/cosmos/cosmos-sdk/pull/6350) New sign-batch command to sign StdTx batch files.

### Bug Fixes

Expand Down
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@ mocks: $(MOCKS_DIR)
$(MOCKS_DIR):
mkdir -p $(MOCKS_DIR)

distclean:
distclean: clean
rm -rf \
gitian-build-darwin/ \
gitian-build-linux/ \
gitian-build-windows/ \
.gitian-builder-cache/
.PHONY: distclean

clean:
rm -rf $(BUILDDIR)/

.PHONY: distclean clean

###############################################################################
### Tools & Dependencies ###
Expand Down
1 change: 1 addition & 0 deletions simapp/cmd/simcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func txCmd(cdc *codec.Codec) *cobra.Command {
bankcmd.NewSendTxCmd(clientCtx),
flags.LineBreak,
authcmd.GetSignCommand(cdc),
authcmd.GetSignBatchCommand(cdc),
authcmd.GetMultiSignCommand(cdc),
authcmd.GetValidateSignaturesCommand(cdc),
flags.LineBreak,
Expand Down
51 changes: 51 additions & 0 deletions x/auth/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,57 @@ func TestCLIValidateSignatures(t *testing.T) {
f.Cleanup()
}

func TestCLISignBatch(t *testing.T) {
t.Parallel()
f := cli.InitFixtures(t)

fooAddr := f.KeyAddress(cli.KeyFoo)
barAddr := f.KeyAddress(cli.KeyBar)

sendTokens := sdk.TokensFromConsensusPower(10)
success, generatedStdTx, stderr := bankcli.TxSend(f, fooAddr.String(), barAddr, sdk.NewCoin(cli.Denom, sendTokens), "--generate-only")

require.True(t, success)
require.Empty(t, stderr)

// Write the output to disk
batchfile, cleanup1 := tests.WriteToNewTempFile(t, strings.Repeat(generatedStdTx, 3))
t.Cleanup(cleanup1)

// sign-batch file - offline is set but account-number and sequence are not
success, _, stderr = testutil.TxSignBatch(f, cli.KeyFoo, batchfile.Name(), "--offline")
require.Contains(t, stderr, "required flag(s) \"account-number\", \"sequence\" not set")
require.False(t, success)

// sign-batch file
success, stdout, stderr := testutil.TxSignBatch(f, cli.KeyFoo, batchfile.Name())
require.True(t, success)
require.Empty(t, stderr)
require.Equal(t, 3, len(strings.Split(strings.Trim(stdout, "\n"), "\n")))

// sign-batch file
success, stdout, stderr = testutil.TxSignBatch(f, cli.KeyFoo, batchfile.Name(), "--signature-only")
require.True(t, success)
require.Empty(t, stderr)
require.Equal(t, 3, len(strings.Split(strings.Trim(stdout, "\n"), "\n")))

malformedFile, cleanup2 := tests.WriteToNewTempFile(t, fmt.Sprintf("%smalformed", generatedStdTx))
t.Cleanup(cleanup2)

// sign-batch file
success, stdout, stderr = testutil.TxSignBatch(f, cli.KeyFoo, malformedFile.Name())
require.False(t, success)
require.Equal(t, 1, len(strings.Split(strings.Trim(stdout, "\n"), "\n")))
require.Equal(t, "ERROR: cannot parse disfix JSON wrapper: invalid character 'm' looking for beginning of value\n", stderr)

// sign-batch file
success, stdout, _ = testutil.TxSignBatch(f, cli.KeyFoo, malformedFile.Name(), "--signature-only")
require.False(t, success)
require.Equal(t, 1, len(strings.Split(strings.Trim(stdout, "\n"), "\n")))

f.Cleanup()
}

func TestCLISendGenerateSignAndBroadcast(t *testing.T) {
t.Parallel()
f := cli.InitFixtures(t)
Expand Down
1 change: 1 addition & 0 deletions x/auth/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func GetTxCmd(cdc *codec.Codec) *cobra.Command {
GetMultiSignCommand(cdc),
GetSignCommand(cdc),
GetValidateSignaturesCommand(cdc),
GetSignBatchCommand(cdc),
)
return txCmd
}
135 changes: 132 additions & 3 deletions x/auth/client/cli/tx_sign.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package cli

import (
"bufio"
"fmt"
"os"

"github.com/spf13/cobra"
"github.com/spf13/viper"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/client"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
"github.com/cosmos/cosmos-sdk/x/auth/types"
)

Expand All @@ -20,6 +22,133 @@ const (
flagSigOnly = "signature-only"
)

// GetSignBatchCommand returns the transaction sign-batch command.
func GetSignBatchCommand(codec *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "sign-batch [file]",
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'.
As the signatures are generated, the command updates the sequence number accordingly.

If the flag --signature-only flag is set, it will output a JSON representation
of the generated signature only.

The --offline flag makes sure that the client will not reach out to full node.
As a result, the account and the sequence number queries will not be performed and
it is required to set such parameters manually. Note, invalid values will cause
the transaction to fail. The sequence will be incremented automatically for each
transaction that is signed.

The --multisig=<multisig_key> flag generates a signature on behalf of a multisig
account key. It implies --signature-only.
`,
PreRun: preSignCmd,
RunE: makeSignBatchCmd(codec),
Args: cobra.ExactArgs(1),
}

cmd.Flags().String(
flagMultisig, "",
"Address of the multisig account on behalf of which the transaction shall be signed",
)
cmd.Flags().String(flags.FlagOutputDocument, "", "The document will be written to the given file instead of STDOUT")
cmd.Flags().Bool(flagSigOnly, true, "Print only the generated signature, then exit")
cmd = flags.PostCommands(cmd)[0]
cmd.MarkFlagRequired(flags.FlagFrom)

return cmd
}

func makeSignBatchCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
inBuf := bufio.NewReader(cmd.InOrStdin())
clientCtx := client.NewContextWithInput(inBuf).WithCodec(cdc)
txBldr := types.NewTxBuilderFromCLI(inBuf)
generateSignatureOnly := viper.GetBool(flagSigOnly)

var (
err error
multisigAddr sdk.AccAddress
infile = os.Stdin
)

// validate multisig address if there's any
if viper.GetString(flagMultisig) != "" {
multisigAddr, err = sdk.AccAddressFromBech32(viper.GetString(flagMultisig))
if err != nil {
return err
}
}

// prepare output document
closeFunc, err := setOutputFile(cmd)
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
}
}

scanner := authclient.NewBatchScanner(cdc, infile)

for sequence := txBldr.Sequence(); scanner.Scan(); sequence++ {
var stdTx types.StdTx

unsignedStdTx := scanner.StdTx()
txBldr = txBldr.WithSequence(sequence)

if multisigAddr.Empty() {
stdTx, err = authclient.SignStdTx(txBldr, clientCtx, viper.GetString(flags.FlagFrom), unsignedStdTx, false, true)
} else {
stdTx, err = authclient.SignStdTxWithSignerAddress(txBldr, clientCtx, multisigAddr, clientCtx.GetFromName(), unsignedStdTx, true)
}

if err != nil {
return err
}

json, err := getSignatureJSON(cdc, stdTx, clientCtx.Indent, generateSignatureOnly)
if err != nil {
return err
}

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

if err := scanner.UnmarshalErr(); err != nil {
return err
}

return scanner.Err()
}
}

func setOutputFile(cmd *cobra.Command) (func(), error) {
outputDoc := viper.GetString(flags.FlagOutputDocument)
if outputDoc == "" {
cmd.SetOut(cmd.OutOrStdout())
return func() {}, nil
}

fp, err := os.OpenFile(outputDoc, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
return func() {}, err
}

cmd.SetOut(fp)

return func() { fp.Close() }, nil
}

// GetSignCommand returns the transaction sign command.
func GetSignCommand(codec *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Expand Down Expand Up @@ -89,13 +218,13 @@ func makeSignCmd(cdc *codec.Codec) func(cmd *cobra.Command, args []string) error
if err != nil {
return err
}
newTx, err = client.SignStdTxWithSignerAddress(
newTx, err = authclient.SignStdTxWithSignerAddress(
txBldr, clientCtx, multisigAddr, clientCtx.GetFromName(), stdTx, clientCtx.Offline,
)
generateSignatureOnly = true
} else {
appendSig := viper.GetBool(flagAppend) && !generateSignatureOnly
newTx, err = client.SignStdTx(txBldr, clientCtx, clientCtx.GetFromName(), stdTx, appendSig, clientCtx.Offline)
newTx, err = authclient.SignStdTx(txBldr, clientCtx, clientCtx.GetFromName(), stdTx, appendSig, clientCtx.Offline)
}

if err != nil {
Expand Down
8 changes: 8 additions & 0 deletions x/auth/client/testutil/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,11 @@ func TxMultisign(f *cli.Fixtures, fileName, name string, signaturesFiles []strin
)
return cli.ExecuteWriteRetStdStreams(f.T, cli.AddFlags(cmd, flags))
}

func TxSignBatch(f *cli.Fixtures, signer, fileName string, flags ...string) (bool, string, string) {
cmd := fmt.Sprintf("%s tx sign-batch %v --keyring-backend=test --from=%s %v", f.SimcliBinary, f.Flags(), signer, fileName)

return cli.ExecuteWriteRetStdStreams(f.T, cli.AddFlags(cmd, flags), clientkeys.DefaultKeyPass)
}

// DONTCOVER
35 changes: 35 additions & 0 deletions x/auth/client/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bufio"
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"strings"
Expand Down Expand Up @@ -250,6 +251,40 @@ func ReadStdTxFromFile(cdc *codec.Codec, filename string) (stdTx authtypes.StdTx
return
}

// NewBatchScanner returns a new BatchScanner to read newline-delimited StdTx transactions from r.
func NewBatchScanner(cdc *codec.Codec, r io.Reader) *BatchScanner {
return &BatchScanner{Scanner: bufio.NewScanner(r), cdc: cdc}
}

// BatchScanner provides a convenient interface for reading batch data such as a file
// of newline-delimited JSON encoded StdTx.
type BatchScanner struct {
*bufio.Scanner
stdTx authtypes.StdTx
cdc *codec.Codec
unmarshalErr error
}

// StdTx returns the most recent StdTx unmarshalled by a call to Scan.
func (bs BatchScanner) StdTx() authtypes.StdTx { return bs.stdTx }

// UnmarshalErr returns the first unmarshalling error that was encountered by the scanner.
func (bs BatchScanner) UnmarshalErr() error { return bs.unmarshalErr }

// Scan advances the Scanner to the next line.
func (bs *BatchScanner) Scan() bool {
if !bs.Scanner.Scan() {
return false
}

if err := bs.cdc.UnmarshalJSON(bs.Bytes(), &bs.stdTx); err != nil && bs.unmarshalErr == nil {
bs.unmarshalErr = err
return false
}

return true
}

func populateAccountFromState(
txBldr authtypes.TxBuilder, clientCtx client.Context, addr sdk.AccAddress,
) (authtypes.TxBuilder, error) {
Expand Down
Loading