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

feat: Query txs by signature and by address+seq (backport #9750) #9783

Merged
merged 5 commits into from
Jul 28, 2021
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### Features

* [\#9750](https://github.com/cosmos/cosmos-sdk/pull/9750) Emit events for tx signature and sequence, so clients can now query txs by signature (`tx.signature='<base64_sig>'`) or by address and sequence combo (`tx.acc_seq='<addr>/<seq>'`).

### Improvements

* (cli) [\#9717](https://github.com/cosmos/cosmos-sdk/pull/9717) Added CLI flag `--output json/text` to `tx` cli commands.
Expand Down
5 changes: 5 additions & 0 deletions types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,11 @@ func toBytes(i interface{}) []byte {

// Common event types and attribute keys
var (
EventTypeTx = "tx"

AttributeKeyAccountSequence = "acc_seq"
AttributeKeySignature = "signature"

EventTypeMessage = "message"

AttributeKeyAction = "action"
Expand Down
68 changes: 68 additions & 0 deletions x/auth/ante/sigverify.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package ante

import (
"bytes"
"encoding/base64"
"encoding/hex"
"fmt"

Expand Down Expand Up @@ -90,6 +91,34 @@ func (spkd SetPubKeyDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate b
spkd.ak.SetAccount(ctx, acc)
}

// Also emit the following events, so that txs can be indexed by these
// indices:
// - signature (via `tx.signature='<sig_as_base64>'`),
// - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`).
sigs, err := sigTx.GetSignaturesV2()
if err != nil {
return ctx, err
}

var events sdk.Events
for i, sig := range sigs {
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signers[i], sig.Sequence)),
))

sigBzs, err := signatureDataToBz(sig.Data)
if err != nil {
return ctx, err
}
for _, sigBz := range sigBzs {
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)),
))
}
}

ctx.EventManager().EmitEvents(events)

return next(ctx, tx, simulate)
}

Expand Down Expand Up @@ -436,3 +465,42 @@ func CountSubKeys(pub cryptotypes.PubKey) int {

return numKeys
}

// signatureDataToBz converts a SignatureData into raw bytes signature.
// For SingleSignatureData, it returns the signature raw bytes.
// For MultiSignatureData, it returns an array of all individual signatures,
// as well as the aggregated signature.
func signatureDataToBz(data signing.SignatureData) ([][]byte, error) {
if data == nil {
return nil, fmt.Errorf("got empty SignatureData")
}

switch data := data.(type) {
case *signing.SingleSignatureData:
return [][]byte{data.Signature}, nil
case *signing.MultiSignatureData:
sigs := [][]byte{}
var err error

for _, d := range data.Signatures {
nestedSigs, err := signatureDataToBz(d)
if err != nil {
return nil, err
}
sigs = append(sigs, nestedSigs...)
}

multisig := cryptotypes.MultiSignature{
Signatures: sigs,
}
aggregatedSig, err := multisig.Marshal()
if err != nil {
return nil, err
}
sigs = append(sigs, aggregatedSig)

return sigs, nil
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unexpected signature data type %T", data)
}
}
181 changes: 156 additions & 25 deletions x/auth/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package cli_test

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -78,11 +79,17 @@ func (s *IntegrationTestSuite) TearDownSuite() {

func (s *IntegrationTestSuite) TestCLIValidateSignatures() {
val := s.network.Validators[0]
res := s.createBankMsg(val, val.Address)
sendTokens := sdk.NewCoins(
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)))

res, err := s.createBankMsg(val, val.Address, sendTokens,
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
s.Require().NoError(err)

// write unsigned tx to file
unsignedTx := testutil.WriteToNewTempFile(s.T(), res.String())
res, err := authtest.TxSignExec(val.ClientCtx, val.Address, unsignedTx.Name())
res, err = authtest.TxSignExec(val.ClientCtx, val.Address, unsignedTx.Name())
s.Require().NoError(err)
signedTx, err := val.ClientCtx.TxConfig.TxJSONDecoder()(res.Bytes())
s.Require().NoError(err)
Expand All @@ -104,7 +111,15 @@ func (s *IntegrationTestSuite) TestCLIValidateSignatures() {

func (s *IntegrationTestSuite) TestCLISignBatch() {
val := s.network.Validators[0]
generatedStd := s.createBankMsg(val, val.Address)
var sendTokens = sdk.NewCoins(
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)),
)

generatedStd, err := s.createBankMsg(val, val.Address,
sendTokens, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
s.Require().NoError(err)

outputFile := testutil.WriteToNewTempFile(s.T(), strings.Repeat(generatedStd.String(), 3))
val.ClientCtx.HomeDir = strings.Replace(val.ClientCtx.HomeDir, "simd", "simcli", 1)

Expand Down Expand Up @@ -136,7 +151,13 @@ func (s *IntegrationTestSuite) TestCLISign_AminoJSON() {
require := s.Require()
val1 := s.network.Validators[0]
txCfg := val1.ClientCtx.TxConfig
txBz := s.createBankMsg(val1, val1.Address)
var sendTokens = sdk.NewCoins(
sdk.NewCoin(fmt.Sprintf("%stoken", val1.Moniker), sdk.NewInt(10)),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)),
)
txBz, err := s.createBankMsg(val1, val1.Address,
sendTokens, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
require.NoError(err)
fileUnsigned := testutil.WriteToNewTempFile(s.T(), txBz.String())
chainFlag := fmt.Sprintf("--%s=%s", flags.FlagChainID, val1.ClientCtx.ChainID)
sigOnlyFlag := "--signature-only"
Expand Down Expand Up @@ -224,7 +245,7 @@ func checkSignatures(require *require.Assertions, txCfg client.TxConfig, output
}
}

func (s *IntegrationTestSuite) TestCLIQueryTxCmd() {
func (s *IntegrationTestSuite) TestCLIQueryTxCmdByHash() {
val := s.network.Validators[0]

account2, err := val.ClientCtx.Keyring.Key("newAccount2")
Expand Down Expand Up @@ -271,17 +292,20 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() {
expectErr bool
rawLogContains string
}{
{
"not enough args",
[]string{},
true, "",
},
{
"with invalid hash",
[]string{"somethinginvalid", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
true,
"",
true, "",
},
{
"with valid and not existing hash",
[]string{"C7E7D3A86A17AB3A321172239F3B61357937AF0F25D9FA4D2F4DCCAD9B0D7747", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
true,
"",
true, "",
},
{
"happy case (legacy Msg)",
Expand Down Expand Up @@ -318,6 +342,120 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() {
}
}

func (s *IntegrationTestSuite) TestCLIQueryTxCmdByEvents() {
val := s.network.Validators[0]

account2, err := val.ClientCtx.Keyring.Key("newAccount2")
s.Require().NoError(err)

sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10)

// Send coins.
out, err := s.createBankMsg(
val, account2.GetAddress(),
sdk.NewCoins(sendTokens),
)
s.Require().NoError(err)
var txRes sdk.TxResponse
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes))
s.Require().NoError(s.network.WaitForNextBlock())

// Query the tx by hash to get the inner tx.
out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.QueryTxCmd(), []string{txRes.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)})
s.Require().NoError(err)
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes))
protoTx := txRes.GetTx().(*tx.Tx)

testCases := []struct {
name string
args []string
expectErr bool
expectErrStr string
}{
{
"invalid --type",
[]string{
fmt.Sprintf("--type=%s", "foo"),
"bar",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "unknown --type value foo",
},
{
"--type=acc_seq with no addr+seq",
[]string{
"--type=acc_seq",
"",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "`acc_seq` type takes an argument '<addr>/<seq>'",
},
{
"non-existing addr+seq combo",
[]string{
"--type=acc_seq",
"foobar",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "found no txs matching given address and sequence combination",
},
{
"addr+seq happy case",
[]string{
"--type=acc_seq",
fmt.Sprintf("%s/%d", val.Address, protoTx.AuthInfo.SignerInfos[0].Sequence),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, "",
},
{
"--type=signature with no signature",
[]string{
"--type=signature",
"",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "argument should be comma-separated signatures",
},
{
"non-existing signatures",
[]string{
"--type=signature",
"foo",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "found no txs matching given signatures",
},
{
"with --signatures happy case",
[]string{
"--type=signature",
base64.StdEncoding.EncodeToString(protoTx.Signatures[0]),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, "",
},
}

for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
cmd := authcli.QueryTxCmd()
clientCtx := val.ClientCtx

out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
s.Require().Contains(err.Error(), tc.expectErrStr)
} else {
var result sdk.TxResponse
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &result))
s.Require().NotNil(result.Height)
}
})
}
}

func (s *IntegrationTestSuite) TestCLISendGenerateSignAndBroadcast() {
val1 := s.network.Validators[0]

Expand Down Expand Up @@ -732,7 +870,7 @@ func (s *IntegrationTestSuite) TestCLIMultisign() {

sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String())

// Sign with account1
// Sign with account2
account2Signature, err := authtest.TxSignExec(val1.ClientCtx, account2.GetAddress(), multiGeneratedTxFile.Name(), "--multisig", multisigInfo.GetAddress().String())
s.Require().NoError(err)

Expand Down Expand Up @@ -1136,22 +1274,15 @@ func (s *IntegrationTestSuite) TestSignWithMultiSigners_AminoJSON() {
require.Equal(sdk.NewCoins(val0Coin, val1Coin), queryRes.Balances)
}

func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.AccAddress) testutil.BufferWriter {
res, err := bankcli.MsgSendExec(
val.ClientCtx,
val.Address,
toAddr,
sdk.NewCoins(
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)),
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)),
),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.AccAddress, amount sdk.Coins, extraFlags ...string) (testutil.BufferWriter, error) {
flags := []string{fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly),
)
s.Require().NoError(err)
return res
fmt.Sprintf("--%s=%s", flags.FlagFees,
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
}

flags = append(flags, extraFlags...)
return bankcli.MsgSendExec(val.ClientCtx, val.Address, toAddr, amount, flags...)
}

func TestIntegrationTestSuite(t *testing.T) {
Expand Down
Loading