Skip to content

Commit 9db413b

Browse files
feat: Query txs by signature and by address+seq (backport #9750) (#9783)
* 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 <[email protected]>
1 parent c26244c commit 9db413b

File tree

7 files changed

+367
-38
lines changed

7 files changed

+367
-38
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ Ref: https://keepachangelog.com/en/1.0.0/
3636

3737
## Unreleased
3838

39+
### Features
40+
41+
* [\#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>'`).
42+
3943
### Improvements
4044

4145
* (cli) [\#9717](https://github.com/cosmos/cosmos-sdk/pull/9717) Added CLI flag `--output json/text` to `tx` cli commands.

types/events.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,11 @@ func toBytes(i interface{}) []byte {
223223

224224
// Common event types and attribute keys
225225
var (
226+
EventTypeTx = "tx"
227+
228+
AttributeKeyAccountSequence = "acc_seq"
229+
AttributeKeySignature = "signature"
230+
226231
EventTypeMessage = "message"
227232

228233
AttributeKeyAction = "action"

x/auth/ante/sigverify.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package ante
22

33
import (
44
"bytes"
5+
"encoding/base64"
56
"encoding/hex"
67
"fmt"
78

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

94+
// Also emit the following events, so that txs can be indexed by these
95+
// indices:
96+
// - signature (via `tx.signature='<sig_as_base64>'`),
97+
// - concat(address,"/",sequence) (via `tx.acc_seq='cosmos1abc...def/42'`).
98+
sigs, err := sigTx.GetSignaturesV2()
99+
if err != nil {
100+
return ctx, err
101+
}
102+
103+
var events sdk.Events
104+
for i, sig := range sigs {
105+
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
106+
sdk.NewAttribute(sdk.AttributeKeyAccountSequence, fmt.Sprintf("%s/%d", signers[i], sig.Sequence)),
107+
))
108+
109+
sigBzs, err := signatureDataToBz(sig.Data)
110+
if err != nil {
111+
return ctx, err
112+
}
113+
for _, sigBz := range sigBzs {
114+
events = append(events, sdk.NewEvent(sdk.EventTypeTx,
115+
sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)),
116+
))
117+
}
118+
}
119+
120+
ctx.EventManager().EmitEvents(events)
121+
93122
return next(ctx, tx, simulate)
94123
}
95124

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

437466
return numKeys
438467
}
468+
469+
// signatureDataToBz converts a SignatureData into raw bytes signature.
470+
// For SingleSignatureData, it returns the signature raw bytes.
471+
// For MultiSignatureData, it returns an array of all individual signatures,
472+
// as well as the aggregated signature.
473+
func signatureDataToBz(data signing.SignatureData) ([][]byte, error) {
474+
if data == nil {
475+
return nil, fmt.Errorf("got empty SignatureData")
476+
}
477+
478+
switch data := data.(type) {
479+
case *signing.SingleSignatureData:
480+
return [][]byte{data.Signature}, nil
481+
case *signing.MultiSignatureData:
482+
sigs := [][]byte{}
483+
var err error
484+
485+
for _, d := range data.Signatures {
486+
nestedSigs, err := signatureDataToBz(d)
487+
if err != nil {
488+
return nil, err
489+
}
490+
sigs = append(sigs, nestedSigs...)
491+
}
492+
493+
multisig := cryptotypes.MultiSignature{
494+
Signatures: sigs,
495+
}
496+
aggregatedSig, err := multisig.Marshal()
497+
if err != nil {
498+
return nil, err
499+
}
500+
sigs = append(sigs, aggregatedSig)
501+
502+
return sigs, nil
503+
default:
504+
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "unexpected signature data type %T", data)
505+
}
506+
}

x/auth/client/cli/cli_test.go

Lines changed: 156 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ package cli_test
44

55
import (
66
"context"
7+
"encoding/base64"
78
"encoding/json"
89
"fmt"
910
"io/ioutil"
@@ -78,11 +79,17 @@ func (s *IntegrationTestSuite) TearDownSuite() {
7879

7980
func (s *IntegrationTestSuite) TestCLIValidateSignatures() {
8081
val := s.network.Validators[0]
81-
res := s.createBankMsg(val, val.Address)
82+
sendTokens := sdk.NewCoins(
83+
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)),
84+
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)))
85+
86+
res, err := s.createBankMsg(val, val.Address, sendTokens,
87+
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
88+
s.Require().NoError(err)
8289

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

105112
func (s *IntegrationTestSuite) TestCLISignBatch() {
106113
val := s.network.Validators[0]
107-
generatedStd := s.createBankMsg(val, val.Address)
114+
var sendTokens = sdk.NewCoins(
115+
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)),
116+
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)),
117+
)
118+
119+
generatedStd, err := s.createBankMsg(val, val.Address,
120+
sendTokens, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
121+
s.Require().NoError(err)
122+
108123
outputFile := testutil.WriteToNewTempFile(s.T(), strings.Repeat(generatedStd.String(), 3))
109124
val.ClientCtx.HomeDir = strings.Replace(val.ClientCtx.HomeDir, "simd", "simcli", 1)
110125

@@ -136,7 +151,13 @@ func (s *IntegrationTestSuite) TestCLISign_AminoJSON() {
136151
require := s.Require()
137152
val1 := s.network.Validators[0]
138153
txCfg := val1.ClientCtx.TxConfig
139-
txBz := s.createBankMsg(val1, val1.Address)
154+
var sendTokens = sdk.NewCoins(
155+
sdk.NewCoin(fmt.Sprintf("%stoken", val1.Moniker), sdk.NewInt(10)),
156+
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)),
157+
)
158+
txBz, err := s.createBankMsg(val1, val1.Address,
159+
sendTokens, fmt.Sprintf("--%s=true", flags.FlagGenerateOnly))
160+
require.NoError(err)
140161
fileUnsigned := testutil.WriteToNewTempFile(s.T(), txBz.String())
141162
chainFlag := fmt.Sprintf("--%s=%s", flags.FlagChainID, val1.ClientCtx.ChainID)
142163
sigOnlyFlag := "--signature-only"
@@ -224,7 +245,7 @@ func checkSignatures(require *require.Assertions, txCfg client.TxConfig, output
224245
}
225246
}
226247

227-
func (s *IntegrationTestSuite) TestCLIQueryTxCmd() {
248+
func (s *IntegrationTestSuite) TestCLIQueryTxCmdByHash() {
228249
val := s.network.Validators[0]
229250

230251
account2, err := val.ClientCtx.Keyring.Key("newAccount2")
@@ -271,17 +292,20 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() {
271292
expectErr bool
272293
rawLogContains string
273294
}{
295+
{
296+
"not enough args",
297+
[]string{},
298+
true, "",
299+
},
274300
{
275301
"with invalid hash",
276302
[]string{"somethinginvalid", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
277-
true,
278-
"",
303+
true, "",
279304
},
280305
{
281306
"with valid and not existing hash",
282307
[]string{"C7E7D3A86A17AB3A321172239F3B61357937AF0F25D9FA4D2F4DCCAD9B0D7747", fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
283-
true,
284-
"",
308+
true, "",
285309
},
286310
{
287311
"happy case (legacy Msg)",
@@ -318,6 +342,120 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() {
318342
}
319343
}
320344

345+
func (s *IntegrationTestSuite) TestCLIQueryTxCmdByEvents() {
346+
val := s.network.Validators[0]
347+
348+
account2, err := val.ClientCtx.Keyring.Key("newAccount2")
349+
s.Require().NoError(err)
350+
351+
sendTokens := sdk.NewInt64Coin(s.cfg.BondDenom, 10)
352+
353+
// Send coins.
354+
out, err := s.createBankMsg(
355+
val, account2.GetAddress(),
356+
sdk.NewCoins(sendTokens),
357+
)
358+
s.Require().NoError(err)
359+
var txRes sdk.TxResponse
360+
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes))
361+
s.Require().NoError(s.network.WaitForNextBlock())
362+
363+
// Query the tx by hash to get the inner tx.
364+
out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, authcli.QueryTxCmd(), []string{txRes.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)})
365+
s.Require().NoError(err)
366+
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes))
367+
protoTx := txRes.GetTx().(*tx.Tx)
368+
369+
testCases := []struct {
370+
name string
371+
args []string
372+
expectErr bool
373+
expectErrStr string
374+
}{
375+
{
376+
"invalid --type",
377+
[]string{
378+
fmt.Sprintf("--type=%s", "foo"),
379+
"bar",
380+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
381+
},
382+
true, "unknown --type value foo",
383+
},
384+
{
385+
"--type=acc_seq with no addr+seq",
386+
[]string{
387+
"--type=acc_seq",
388+
"",
389+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
390+
},
391+
true, "`acc_seq` type takes an argument '<addr>/<seq>'",
392+
},
393+
{
394+
"non-existing addr+seq combo",
395+
[]string{
396+
"--type=acc_seq",
397+
"foobar",
398+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
399+
},
400+
true, "found no txs matching given address and sequence combination",
401+
},
402+
{
403+
"addr+seq happy case",
404+
[]string{
405+
"--type=acc_seq",
406+
fmt.Sprintf("%s/%d", val.Address, protoTx.AuthInfo.SignerInfos[0].Sequence),
407+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
408+
},
409+
false, "",
410+
},
411+
{
412+
"--type=signature with no signature",
413+
[]string{
414+
"--type=signature",
415+
"",
416+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
417+
},
418+
true, "argument should be comma-separated signatures",
419+
},
420+
{
421+
"non-existing signatures",
422+
[]string{
423+
"--type=signature",
424+
"foo",
425+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
426+
},
427+
true, "found no txs matching given signatures",
428+
},
429+
{
430+
"with --signatures happy case",
431+
[]string{
432+
"--type=signature",
433+
base64.StdEncoding.EncodeToString(protoTx.Signatures[0]),
434+
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
435+
},
436+
false, "",
437+
},
438+
}
439+
440+
for _, tc := range testCases {
441+
tc := tc
442+
s.Run(tc.name, func() {
443+
cmd := authcli.QueryTxCmd()
444+
clientCtx := val.ClientCtx
445+
446+
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
447+
if tc.expectErr {
448+
s.Require().Error(err)
449+
s.Require().Contains(err.Error(), tc.expectErrStr)
450+
} else {
451+
var result sdk.TxResponse
452+
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &result))
453+
s.Require().NotNil(result.Height)
454+
}
455+
})
456+
}
457+
}
458+
321459
func (s *IntegrationTestSuite) TestCLISendGenerateSignAndBroadcast() {
322460
val1 := s.network.Validators[0]
323461

@@ -732,7 +870,7 @@ func (s *IntegrationTestSuite) TestCLIMultisign() {
732870

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

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

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

1139-
func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.AccAddress) testutil.BufferWriter {
1140-
res, err := bankcli.MsgSendExec(
1141-
val.ClientCtx,
1142-
val.Address,
1143-
toAddr,
1144-
sdk.NewCoins(
1145-
sdk.NewCoin(fmt.Sprintf("%stoken", val.Moniker), sdk.NewInt(10)),
1146-
sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)),
1147-
),
1148-
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
1277+
func (s *IntegrationTestSuite) createBankMsg(val *network.Validator, toAddr sdk.AccAddress, amount sdk.Coins, extraFlags ...string) (testutil.BufferWriter, error) {
1278+
flags := []string{fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
11491279
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
1150-
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
1151-
fmt.Sprintf("--%s=true", flags.FlagGenerateOnly),
1152-
)
1153-
s.Require().NoError(err)
1154-
return res
1280+
fmt.Sprintf("--%s=%s", flags.FlagFees,
1281+
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
1282+
}
1283+
1284+
flags = append(flags, extraFlags...)
1285+
return bankcli.MsgSendExec(val.ClientCtx, val.Address, toAddr, amount, flags...)
11551286
}
11561287

11571288
func TestIntegrationTestSuite(t *testing.T) {

0 commit comments

Comments
 (0)