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 5 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
67 changes: 67 additions & 0 deletions baseapp/baseapp.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package baseapp

import (
"encoding/base64"
"errors"
"fmt"
"reflect"
"strconv"
"strings"

"github.com/gogo/protobuf/proto"
Expand All @@ -14,13 +16,16 @@ import (
dbm "github.com/tendermint/tm-db"

"github.com/cosmos/cosmos-sdk/codec/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/snapshots"
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/rootmulti"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
sdktx "github.com/cosmos/cosmos-sdk/types/tx"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
"github.com/cosmos/cosmos-sdk/x/auth/migrations/legacytx"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
)

const (
Expand Down Expand Up @@ -676,6 +681,31 @@ func (app *BaseApp) runTx(mode runTxMode, txBytes []byte) (gInfo sdk.GasInfo, re
// is a branch of a branch.
runMsgCtx, msCache := app.cacheTxContext(ctx, txBytes)

// Also emit the following events, so that txs can be indexed by these
// indices:
// - signature (via `tx.signature='<sig_as_base64>'`),
// - address and sequence (via `message.sender=<addr> AND tx.sequence='<seq>' `).
sigTx, ok := tx.(authsigning.SigVerifiableTx)
amaury1093 marked this conversation as resolved.
Show resolved Hide resolved
if !ok {
return sdk.GasInfo{}, nil, sdkerrors.ErrInvalidType.Wrapf("expected %T, got %T", (*authsigning.SigVerifiableTx)(nil), tx)
}
sigs, err := sigTx.GetSignaturesV2()
if err != nil {
return sdk.GasInfo{}, nil, err
}

for _, sig := range sigs {
sigBz, err := signatureDataToBz(sig.Data)
if err != nil {
return sdk.GasInfo{}, nil, err
}

events = append(events, sdk.NewEvent(sdk.EventTypeTx,
sdk.NewAttribute(sdk.AttributeKeySequence, strconv.FormatUint(sig.Sequence, 10)),
sdk.NewAttribute(sdk.AttributeKeySignature, base64.StdEncoding.EncodeToString(sigBz)),
))
}

// Attempt to execute all messages and only update state if all messages pass
// and we're in DeliverTx. Note, runMsgs will never return a reference to a
// Result if any single message fails or does not have a registered Handler.
Expand Down Expand Up @@ -769,3 +799,40 @@ func (app *BaseApp) runMsgs(ctx sdk.Context, msgs []sdk.Msg, mode runTxMode) (*s
Events: events.ToABCIEvents(),
}, nil
}

// signatureDataToBz converts a SignatureData into raw bytes signature. It is
// the same function as in auth/tx/sigs.go, but copied here because of import
// cycles.
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 data.Signature, nil
case *signing.MultiSignatureData:
n := len(data.Signatures)
sigs := make([][]byte, n)
var err error

for i, d := range data.Signatures {
sigs[i], err = signatureDataToBz(d)
if err != nil {
return nil, err
}
}

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

return sig, nil
default:
return nil, fmt.Errorf("unexpected signature data type %T", data)
}
}
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"

AttributeKeySequence = "sequence"
amaury1093 marked this conversation as resolved.
Show resolved Hide resolved
AttributeKeySignature = "signature"

EventTypeMessage = "message"

AttributeKeyAction = "action"
Expand Down
84 changes: 74 additions & 10 deletions x/auth/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ import (
)

const (
flagEvents = "events"
flagEvents = "events"
flagSignatures = "signatures"
flagAddress = "address"
flagSequence = "sequence"

eventFormat = "{eventType}.{eventAttribute}={value}"
)
Expand Down Expand Up @@ -212,26 +215,87 @@ 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),
Args: cobra.MaximumNArgs(1),
amaury1093 marked this conversation as resolved.
Show resolved Hide resolved
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])
addr, _ := cmd.Flags().GetString(flagAddress)
seq, _ := cmd.Flags().GetInt(flagSequence)
sigs, _ := cmd.Flags().GetString(flagSignatures)

switch {
// Query tx by hash.
case len(args) > 0 && args[0] != "":
{
// 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)
}
// Query tx by signature.
case sigs != "":
{
sigParts := strings.Split(sigs, ",")
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 fmt.Errorf("found %d txs matching given signatures", len(txs.Txs))
amaury1093 marked this conversation as resolved.
Show resolved Hide resolved
}

return clientCtx.PrintProto(txs.Txs[0])
}
// Query tx by addr+seq combo.
case addr != "" && seq >= 0:
{
tmEvents := []string{
fmt.Sprintf("%s.%s='%s'", sdk.EventTypeMessage, sdk.AttributeKeySender, addr),
fmt.Sprintf("%s.%s='%d'", sdk.EventTypeTx, sdk.AttributeKeySequence, seq),
}
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("either pass a tx hash, OR a --signature flag, OR both --address and --sequence flags")
}

return clientCtx.PrintProto(output)
},
}

flags.AddQueryFlagsToCmd(cmd)
cmd.Flags().String(flagAddress, "", fmt.Sprintf("Query the tx by signer, to be used in conjunction with --%s", flagSequence))
cmd.Flags().Int(flagSequence, -1, fmt.Sprintf("Query the tx by sequence, to be used in conjunction with --%s", flagAddress))
cmd.Flags().String(flagSignatures, "", "Query the tx by comma-separated signatures in base64 encoding")

return cmd
}
124 changes: 117 additions & 7 deletions x/auth/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package testutil

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -245,7 +246,7 @@ func checkSignatures(require *require.Assertions, txCfg client.TxConfig, output
}
}

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

account2, err := val.ClientCtx.Keyring.Key("newAccount2")
Expand All @@ -269,23 +270,26 @@ 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",
[]string{txRes.TxHash, fmt.Sprintf("--%s=json", tmcli.OutputFlag)},
false,
"/cosmos.bank.v1beta1.MsgSend",
sdk.MsgTypeURL(&banktypes.MsgSend{}),
},
}

Expand All @@ -310,6 +314,112 @@ func (s *IntegrationTestSuite) TestCLIQueryTxCmd() {
}
}

func (s *IntegrationTestSuite) TestCLIQueryTxCmd_ByEvents() {
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.Codec.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.Codec.UnmarshalJSON(out.Bytes(), &txRes))
Copy link
Collaborator

Choose a reason for hiding this comment

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

the lines above a repeated in this file, can we move it to another function and reuse it?
Eg:

recipient, txRes, err := sendCoins(val, "newAccount2", 10)  // maybe we can add coins  to the return

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A function for sending coins already exists (createBankMsg()). I think putting the ExecTestCLICmd for authcli.QueryTxCmd() separately is okay.

protoTx := txRes.GetTx().(*tx.Tx)

testCases := []struct {
name string
args []string
expectErr bool
expectErrStr string
}{
{
"with no flags",
[]string{},
true, "either pass a tx hash, OR a --signature flag, OR both --address and --sequence flags",
},
{
"with --address only",
[]string{
fmt.Sprintf("--address=%s", val.Address.String()),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "either pass a tx hash, OR a --signature flag, OR both --address and --sequence flags",
},
{
"with --sequence only",
[]string{
fmt.Sprintf("--sequence=%d", protoTx.AuthInfo.SignerInfos[0].Sequence),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "either pass a tx hash, OR a --signature flag, OR both --address and --sequence flags",
},
{
"non-existing --address and --sequence combo",
[]string{
fmt.Sprintf("--address=%s", val.Address.String()),
fmt.Sprintf("--sequence=%d", 42),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "found no txs matching given address and sequence combination",
},
{
"with --address and --sequence happy case",
[]string{
fmt.Sprintf("--address=%s", val.Address.String()),
fmt.Sprintf("--sequence=%d", protoTx.AuthInfo.SignerInfos[0].Sequence),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, "",
},
{
"non-existing --signatures",
[]string{
fmt.Sprintf("--signatures=%s", "foo"),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, "found no txs matching given signatures",
},
{
"with --signatures happy case",
[]string{
fmt.Sprintf("--signatures=%s", 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.Codec.UnmarshalJSON(out.Bytes(), &result))
s.Require().NotNil(result.Height)
}
})
}
}

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

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

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

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

Expand Down