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 #9750

Merged
merged 25 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
dafbe1f
WIP for auth query
amaury1093 Jul 21, 2021
d801fbd
Make query by addr and seq work
amaury1093 Jul 22, 2021
3628100
Add tests for sigs
amaury1093 Jul 22, 2021
51f4cf2
Merge branch 'master' into am/9741-tx-query
amaury1093 Jul 22, 2021
ea3dc2e
Make query by sig work
amaury1093 Jul 22, 2021
96d357c
Fix lint
amaury1093 Jul 22, 2021
069e823
Improve err message
amaury1093 Jul 22, 2021
f319d4a
Move HasExtensionOptionsTx to authtx?
amaury1093 Jul 22, 2021
eb06533
Switch to bez's CLI UX
amaury1093 Jul 22, 2021
b7218b1
Fix cycle dep
amaury1093 Jul 22, 2021
56c331a
Cleanups
amaury1093 Jul 22, 2021
620b3ea
Update CHANGELOG.md
amaury1093 Jul 22, 2021
76f6fdd
Emit all nested sigs
amaury1093 Jul 23, 2021
acefab9
Merge branch 'am/9741-tx-query' of ssh://github.com/cosmos/cosmos-sdk…
amaury1093 Jul 23, 2021
209ffb3
Merge branch 'master' into am/9741-tx-query
amaury1093 Jul 23, 2021
f6e61e1
Index by addr++seq
amaury1093 Jul 23, 2021
ef80915
Merge branch 'master' into am/9741-tx-query
amaury1093 Jul 26, 2021
1d0c8c5
Use '/' delimiter
amaury1093 Jul 26, 2021
1eb8abd
Merge branch 'master' into am/9741-tx-query
alexanderbez Jul 26, 2021
fade9f7
Update x/auth/client/cli/query.go
amaury1093 Jul 27, 2021
10f846e
Update x/auth/client/cli/query.go
amaury1093 Jul 27, 2021
2e9a636
Update x/auth/client/cli/query.go
amaury1093 Jul 27, 2021
4e19f3d
Update x/auth/client/cli/query.go
amaury1093 Jul 27, 2021
91314fe
Address review comments
amaury1093 Jul 27, 2021
78b7526
Merge branch 'master' into am/9741-tx-query
amaury1093 Jul 27, 2021
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 @@ -41,6 +41,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

* [\#9533](https://github.com/cosmos/cosmos-sdk/pull/9533) Added a new gRPC method, `DenomOwners`, in `x/bank` to query for all account holders of a specific denomination.
* (bank) [\#9618](https://github.com/cosmos/cosmos-sdk/pull/9618) Update bank.Metadata: add URI and URIHash attributes.
* [\#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>'`).

### API Breaking Changes

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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not adding it under other AttributeKey*?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These attributes are for tx.*, the attributes below are for message.*


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 @@ -94,6 +95,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 @@ -447,3 +476,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.ErrInvalidType.Wrapf("unexpected signature data type %T", data)
}
}
110 changes: 99 additions & 11 deletions x/auth/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/version"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
Expand All @@ -18,6 +19,11 @@ import (

const (
flagEvents = "events"
flagType = "type"

typeHash = "hash"
typeAccSeq = "acc_seq"
typeSig = "signature"

eventFormat = "{eventType}.{eventAttribute}={value}"
)
Expand Down Expand Up @@ -210,28 +216,110 @@ $ %s query txs --%s 'message.sender=cosmos1...&message.action=withdraw_delegator
// QueryTxCmd implements the default command for a tx query.
func QueryTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "tx [hash]",
Short: "Query for a transaction by hash in a committed block",
Args: cobra.ExactArgs(1),
Use: "tx --type=[hash|acc_seq|signature] [hash|acc_seq|signature]",
Short: "Query for a transaction by hash, addr++seq combination or signature in a committed block",
Long: strings.TrimSpace(fmt.Sprintf(`
Example:
$ %s query tx <hash>
$ %s query tx --%s=%s <addr>:<sequence>
$ %s query tx --%s=%s <sig1_base64,sig2_base64...>
`,
version.AppName,
version.AppName, flagType, typeAccSeq,
version.AppName, flagType, typeSig)),
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
output, err := authtx.QueryTx(clientCtx, args[0])
if err != nil {
return err
}

if output.Empty() {
return fmt.Errorf("no transaction found with hash %s", args[0])
}
typ, _ := cmd.Flags().GetString(flagType)

switch typ {
case typeHash:
{
if args[0] == "" {
return fmt.Errorf("argument should be a tx hash")
}

// If hash is given, then query the tx by hash.
output, err := authtx.QueryTx(clientCtx, args[0])
if err != nil {
return err
}

return clientCtx.PrintProto(output)
if output.Empty() {
return fmt.Errorf("no transaction found with hash %s", args[0])
}

return clientCtx.PrintProto(output)
}
case typeSig:
{
sigParts, err := parseSigArgs(args)
if err != nil {
return err
}
tmEvents := make([]string, len(sigParts))
for i, sig := range sigParts {
tmEvents[i] = fmt.Sprintf("%s.%s='%s'", sdk.EventTypeTx, sdk.AttributeKeySignature, sig)
}

txs, err := authtx.QueryTxsByEvents(clientCtx, tmEvents, query.DefaultPage, query.DefaultLimit, "")
if err != nil {
return err
}
if len(txs.Txs) == 0 {
return fmt.Errorf("found no txs matching given signatures")
}
if len(txs.Txs) > 1 {
// This case means there's a bug somewhere else in the code. Should not happen.
return errors.ErrLogic.Wrapf("found %d txs matching given signatures", len(txs.Txs))
}

return clientCtx.PrintProto(txs.Txs[0])
}
case typeAccSeq:
{
if args[0] == "" {
return fmt.Errorf("`acc_seq` type takes an argument '<addr>/<seq>'")
}

tmEvents := []string{
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeTx, sdk.AttributeKeyAccountSequence, args[0]),
}
txs, err := authtx.QueryTxsByEvents(clientCtx, tmEvents, query.DefaultPage, query.DefaultLimit, "")
if err != nil {
return err
}
if len(txs.Txs) == 0 {
return fmt.Errorf("found no txs matching given address and sequence combination")
}
if len(txs.Txs) > 1 {
// This case means there's a bug somewhere else in the code. Should not happen.
return fmt.Errorf("found %d txs matching given address and sequence combination", len(txs.Txs))
}

return clientCtx.PrintProto(txs.Txs[0])
}
default:
return fmt.Errorf("unknown --%s value %s", flagType, typ)
}
},
}

flags.AddQueryFlagsToCmd(cmd)
cmd.Flags().String(flagType, typeHash, fmt.Sprintf("The type to be used when querying tx, can be one of \"%s\", \"%s\", \"%s\"", typeHash, typeAccSeq, typeSig))

return cmd
}

// parseSigArgs parses comma-separated signatures from the CLI arguments.
func parseSigArgs(args []string) ([]string, error) {
if len(args) != 1 || args[0] == "" {
return nil, fmt.Errorf("argument should be comma-separated signatures")
}

return strings.Split(args[0], ","), nil
}
32 changes: 32 additions & 0 deletions x/auth/client/cli/query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package cli

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestParseSigs(t *testing.T) {
cases := []struct {
name string
args []string
expErr bool
expNumSigs int
}{
{"no args", []string{}, true, 0},
{"empty args", []string{""}, true, 0},
{"too many args", []string{"foo", "bar"}, true, 0},
{"1 sig", []string{"foo"}, false, 1},
{"3 sigs", []string{"foo,bar,baz"}, false, 3},
}

for _, tc := range cases {
sigs, err := parseSigArgs(tc.args)
if tc.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tc.expNumSigs, len(sigs))
}
}
}
Loading