Skip to content

Commit

Permalink
feat: Query txs by signature and by address+seq (backport #9750) (#9783)
Browse files Browse the repository at this point in the history
* feat: Query txs by signature and by address+seq (#9750)

<!--
The default pull request template is for types feat, fix, or refactor.
For other templates, add one of the following parameters to the url:
- template=docs.md
- template=other.md
-->

## Description

Closes: #9741

<!-- Add a description of the changes that this PR introduces and the files that
are the most critical to review. -->

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [ ] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] added `!` to the type prefix if API or client breaking change
- [ ] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting))
- [ ] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [ ] reviewed "Files changed" and left comments if necessary
- [ ] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)

(cherry picked from commit 7c19434)

# Conflicts:
#	CHANGELOG.md
#	x/auth/client/cli/cli_test.go
#	x/auth/client/cli/query.go

* Fix conflicts

* Fix build

* Fix IBC test

* use createBankMsg from master

Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com>
  • Loading branch information
mergify[bot] and amaury1093 committed Jul 28, 2021
1 parent c26244c commit 9db413b
Show file tree
Hide file tree
Showing 7 changed files with 367 additions and 38 deletions.
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

0 comments on commit 9db413b

Please sign in to comment.