Skip to content

Commit

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

* feat: Query txs by signature and by address+seq (cosmos#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: cosmos#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/query.go

* Fix conflicts

* Fix cl

* Fix conflicts

Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com>
  • Loading branch information
2 people authored and JeancarloBarrios committed Sep 28, 2024
1 parent 69755b7 commit a3da810
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 42 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,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
4 changes: 1 addition & 3 deletions types/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,13 +282,11 @@ func (e Events) GetAttributes(key string) ([]Attribute, bool) {
}

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

AttributeKeyAccountSequence = "acc_seq"
AttributeKeySignature = "signature"
AttributeKeyFee = "fee"
AttributeKeyFeePayer = "fee_payer"

EventTypeMessage = "message"

Expand Down
61 changes: 34 additions & 27 deletions x/auth/ante/sigverify.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ante

import (
"context"
"bytes"
"encoding/base64"
"encoding/hex"
"errors"
Expand Down Expand Up @@ -143,10 +143,37 @@ func verifyIsOnCurve(pubKey cryptotypes.PubKey) (err error) {
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "some keys are not on curve")
}

default:
return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "unsupported key type: %T", typedPubKey)
// 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)
}

return nil
}

Expand Down Expand Up @@ -632,13 +659,12 @@ func CountSubKeys(pub cryptotypes.PubKey) int {
// as well as the aggregated signature.
func signatureDataToBz(data signing.SignatureData) ([][]byte, error) {
if data == nil {
return nil, errors.New("got empty SignatureData")
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
Expand All @@ -648,39 +674,20 @@ func signatureDataToBz(data signing.SignatureData) ([][]byte, error) {
if err != nil {
return nil, err
}

sigs = append(sigs, nestedSigs...)
}

multiSignature := cryptotypes.MultiSignature{
multisig := cryptotypes.MultiSignature{
Signatures: sigs,
}

aggregatedSig, err := multiSignature.Marshal()
aggregatedSig, err := multisig.Marshal()
if err != nil {
return nil, err
}

sigs = append(sigs, aggregatedSig)
return sigs, nil

return sigs, nil
default:
return nil, sdkerrors.ErrInvalidType.Wrapf("unexpected signature data type %T", data)
}
}

// isSigverifyTx will always return true, unless the context is a sdk.Context, in which case we will return the
// value of IsSigverifyTx.
func isSigverifyTx(ctx context.Context) bool {
if sdkCtx, ok := sdk.TryUnwrapSDKContext(ctx); ok {
return sdkCtx.IsSigverifyTx()
}
return true
}

func isRecheckTx(ctx context.Context, txSvc transaction.Service) bool {
if sdkCtx, ok := sdk.TryUnwrapSDKContext(ctx); ok {
return sdkCtx.IsReCheckTx()
}
return txSvc.ExecMode(ctx) == transaction.ExecModeReCheck
}
122 changes: 114 additions & 8 deletions x/auth/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
querytypes "github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/types/rest"
"github.com/cosmos/cosmos-sdk/version"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
)

const (
FlagQuery = "query"
FlagType = "type"
FlagOrderBy = "order_by"
flagEvents = "events"
flagType = "type"

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

TypeHash = "hash"
TypeAccSeq = "acc_seq"
Expand Down Expand Up @@ -180,10 +184,112 @@ $ %s query tx --%s=%s <sig1_base64>,<sig2_base64...>
return cmd
}

// ParseSigArgs parses comma-separated signatures from the CLI arguments.
func ParseSigArgs(args []string) ([]string, error) {
// QueryTxCmd implements the default command for a tx query.
func QueryTxCmd() *cobra.Command {
cmd := &cobra.Command{
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
}

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
}

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, rest.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, rest.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, errors.New("argument should be comma-separated signatures")
return nil, fmt.Errorf("argument should be comma-separated signatures")
}

return strings.Split(args[0], ","), nil
Expand Down
6 changes: 2 additions & 4 deletions x/auth/client/cli/query_test.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package cli_test
package cli

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/cosmos/cosmos-sdk/x/auth/client/cli"
)

func TestParseSigs(t *testing.T) {
Expand All @@ -23,7 +21,7 @@ func TestParseSigs(t *testing.T) {
}

for _, tc := range cases {
sigs, err := cli.ParseSigArgs(tc.args)
sigs, err := parseSigArgs(tc.args)
if tc.expErr {
require.Error(t, err)
} else {
Expand Down

0 comments on commit a3da810

Please sign in to comment.