From 37b126051a0c9586bc07b8ddff8875428ac1d20e Mon Sep 17 00:00:00 2001 From: Aleksandr Bezobchuk Date: Mon, 14 Nov 2022 13:49:59 -0500 Subject: [PATCH 1/3] fix: Allow underscores in EventRegex (#13861) (cherry picked from commit 14c582f30ec1f6f0c661e916f4dccf46b7305701) # Conflicts: # CHANGELOG.md # tests/e2e/tx/service_test.go --- CHANGELOG.md | 13 + tests/e2e/tx/service_test.go | 828 +++++++++++++++++++++++++++++++++++ x/auth/tx/service.go | 2 +- 3 files changed, 842 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/tx/service_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 3db28ed33097..feb32fb1a14c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,7 +49,20 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/bank) [#13821](https://github.com/cosmos/cosmos-sdk/pull/13821) Fix bank store migration of coin metadata. * (x/group) [#13808](https://github.com/cosmos/cosmos-sdk/pull/13808) Fix propagation of message events to the current context in `EndBlocker`. * (x/gov) [#13728](https://github.com/cosmos/cosmos-sdk/pull/13728) Fix propagation of message events to the current context in `EndBlocker`. +<<<<<<< HEAD * (store) [#13803](https://github.com/cosmos/cosmos-sdk/pull/13803) Add an error log if iavl set operation failed. +======= +* (server) [#13778](https://github.com/cosmos/cosmos-sdk/pull/13778) Set Cosmos SDK default endpoints to localhost to avoid unknown exposure of endpoints. +* [#13861](https://github.com/cosmos/cosmos-sdk/pull/13861) Allow `_` characters in tx event queries, i.e. `GetTxsEvent`. + +### Deprecated + +* (x/evidence) [#13740](https://github.com/cosmos/cosmos-sdk/pull/13740) The `evidence_hash` field of `QueryEvidenceRequest` has been deprecated and now contains a new field `hash` with type `string`. +* (x/bank) [#11859](https://github.com/cosmos/cosmos-sdk/pull/11859) The Params.SendEnabled field is deprecated and unusable. + The information can now be accessed using the BankKeeper. + Setting can be done using MsgSetSendEnabled as a governance proposal. + A SendEnabled query has been added to both GRPC and CLI. +>>>>>>> 14c582f30 (fix: Allow underscores in EventRegex (#13861)) ## [v0.46.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.4) - 2022-11-01 diff --git a/tests/e2e/tx/service_test.go b/tests/e2e/tx/service_test.go new file mode 100644 index 000000000000..45c7ff3cde15 --- /dev/null +++ b/tests/e2e/tx/service_test.go @@ -0,0 +1,828 @@ +package tx_test + +import ( + "context" + "encoding/base64" + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "cosmossdk.io/simapp" + "github.com/cosmos/cosmos-sdk/client" + "github.com/cosmos/cosmos-sdk/client/flags" + clienttx "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/crypto/hd" + "github.com/cosmos/cosmos-sdk/crypto/keyring" + kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + "github.com/cosmos/cosmos-sdk/testutil" + "github.com/cosmos/cosmos-sdk/testutil/cli" + "github.com/cosmos/cosmos-sdk/testutil/network" + "github.com/cosmos/cosmos-sdk/testutil/testdata" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/query" + "github.com/cosmos/cosmos-sdk/types/tx" + "github.com/cosmos/cosmos-sdk/types/tx/signing" + authclient "github.com/cosmos/cosmos-sdk/x/auth/client" + authtest "github.com/cosmos/cosmos-sdk/x/auth/client/testutil" + authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" +) + +var bankMsgSendEventAction = fmt.Sprintf("message.action='%s'", sdk.MsgTypeURL(&banktypes.MsgSend{})) + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + + txHeight int64 + queryClient tx.ServiceClient + txRes sdk.TxResponse +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) + cfg.NumValidators = 1 + s.cfg = cfg + + var err error + s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) + s.Require().NoError(err) + + val := s.network.Validators[0] + s.Require().NoError(s.network.WaitForNextBlock()) + + s.queryClient = tx.NewServiceClient(val.ClientCtx) + + // Create a new MsgSend tx from val to itself. + out, err := cli.MsgSendExec( + val.ClientCtx, + val.Address, + val.Address, + sdk.NewCoins( + sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)), + ), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--gas=%d", flags.DefaultGasLimit), + fmt.Sprintf("--%s=foobar", flags.FlagNote), + ) + s.Require().NoError(err) + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &s.txRes)) + s.Require().Equal(uint32(0), s.txRes.Code, s.txRes) + + out, err = cli.MsgSendExec( + val.ClientCtx, + val.Address, + val.Address, + sdk.NewCoins( + sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(1)), + ), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=2", flags.FlagSequence), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--gas=%d", flags.DefaultGasLimit), + fmt.Sprintf("--%s=foobar", flags.FlagNote), + ) + s.Require().NoError(err) + var tr sdk.TxResponse + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &tr)) + s.Require().Equal(uint32(0), tr.Code) + + s.Require().NoError(s.network.WaitForNextBlock()) + height, err := s.network.LatestHeight() + s.Require().NoError(err) + s.txHeight = height +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *IntegrationTestSuite) TestQueryBySig() { + // broadcast tx + txb := s.mkTxBuilder() + txbz, err := s.cfg.TxConfig.TxEncoder()(txb.GetTx()) + s.Require().NoError(err) + resp, err := s.queryClient.BroadcastTx(context.Background(), &tx.BroadcastTxRequest{TxBytes: txbz, Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC}) + s.Require().NoError(err) + s.Require().NotEmpty(resp.TxResponse.TxHash) + + s.Require().NoError(s.network.WaitForNextBlock()) + s.Require().NoError(s.network.WaitForNextBlock()) + + // get the signature out of the builder + sigs, err := txb.GetTx().GetSignaturesV2() + s.Require().NoError(err) + s.Require().Len(sigs, 1) + sig, ok := sigs[0].Data.(*signing.SingleSignatureData) + s.Require().True(ok) + + // encode, format, query + b64Sig := base64.StdEncoding.EncodeToString(sig.Signature) + sigFormatted := fmt.Sprintf("%s.%s='%s'", sdk.EventTypeTx, sdk.AttributeKeySignature, b64Sig) + res, err := s.queryClient.GetTxsEvent(context.Background(), &tx.GetTxsEventRequest{ + Events: []string{sigFormatted}, + OrderBy: 0, + Page: 0, + Limit: 10, + }) + s.Require().NoError(err) + s.Require().Len(res.Txs, 1) + s.Require().Len(res.Txs[0].Signatures, 1) + s.Require().Equal(res.Txs[0].Signatures[0], sig.Signature) + + // bad format should error + _, err = s.queryClient.GetTxsEvent(context.Background(), &tx.GetTxsEventRequest{Events: []string{"tx.foo.bar='baz'"}}) + s.Require().ErrorContains(err, "invalid event;") +} + +func TestEventRegex(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + event string + match bool + }{ + { + name: "valid: with quotes", + event: "tx.message='something'", + match: true, + }, + { + name: "valid: with underscores", + event: "claim_reward.message_action='something'", + match: true, + }, + { + name: "valid: no quotes", + event: "tx.message=something", + match: true, + }, + { + name: "invalid: too many separators", + event: "tx.message.foo='bar'", + match: false, + }, + { + name: "valid: symbols ok", + event: "tx.signature='foobar/baz123=='", + match: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + match := authtx.EventRegex.Match([]byte(tc.event)) + require.Equal(t, tc.match, match) + }) + } +} + +func (s IntegrationTestSuite) TestSimulateTx_GRPC() { + val := s.network.Validators[0] + txBuilder := s.mkTxBuilder() + // Convert the txBuilder to a tx.Tx. + protoTx, err := txBuilderToProtoTx(txBuilder) + s.Require().NoError(err) + // Encode the txBuilder to txBytes. + txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + + testCases := []struct { + name string + req *tx.SimulateRequest + expErr bool + expErrMsg string + }{ + {"nil request", nil, true, "request cannot be nil"}, + {"empty request", &tx.SimulateRequest{}, true, "empty txBytes is not allowed"}, + {"valid request with proto tx (deprecated)", &tx.SimulateRequest{Tx: protoTx}, false, ""}, + {"valid request with tx_bytes", &tx.SimulateRequest{TxBytes: txBytes}, false, ""}, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + // Broadcast the tx via gRPC via the validator's clientCtx (which goes + // through Tendermint). + res, err := s.queryClient.Simulate(context.Background(), tc.req) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + // Check the result and gas used are correct. + // + // The 12 events are: + // - Sending Fee to the pool: coin_spent, coin_received, transfer and message.sender= + // - tx.* events: tx.fee, tx.acc_seq, tx.signature + // - Sending Amount to recipient: coin_spent, coin_received, transfer and message.sender= + // - Msg events: message.module=bank and message.action=/cosmos.bank.v1beta1.MsgSend (in one message) + s.Require().Equal(12, len(res.GetResult().GetEvents())) + s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. + } + }) + } +} + +func (s IntegrationTestSuite) TestSimulateTx_GRPCGateway() { + val := s.network.Validators[0] + txBuilder := s.mkTxBuilder() + // Convert the txBuilder to a tx.Tx. + protoTx, err := txBuilderToProtoTx(txBuilder) + s.Require().NoError(err) + // Encode the txBuilder to txBytes. + txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + + testCases := []struct { + name string + req *tx.SimulateRequest + expErr bool + expErrMsg string + }{ + {"empty request", &tx.SimulateRequest{}, true, "empty txBytes is not allowed"}, + {"valid request with proto tx (deprecated)", &tx.SimulateRequest{Tx: protoTx}, false, ""}, + {"valid request with tx_bytes", &tx.SimulateRequest{TxBytes: txBytes}, false, ""}, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + req, err := val.ClientCtx.Codec.MarshalJSON(tc.req) + s.Require().NoError(err) + res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/simulate", val.APIAddress), "application/json", req) + s.Require().NoError(err) + if tc.expErr { + s.Require().Contains(string(res), tc.expErrMsg) + } else { + var result tx.SimulateResponse + err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) + s.Require().NoError(err) + // Check the result and gas used are correct. + s.Require().Len(result.GetResult().MsgResponses, 1) + s.Require().Equal(12, len(result.GetResult().GetEvents())) // See TestSimulateTx_GRPC for the 12 events. + s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. + } + }) + } +} + +func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { + testCases := []struct { + name string + req *tx.GetTxsEventRequest + expErr bool + expErrMsg string + expLen int + }{ + { + "nil request", + nil, + true, "request cannot be nil", 0, + }, + { + "empty request", + &tx.GetTxsEventRequest{}, + true, "must declare at least one event to search", 0, + }, + { + "request with dummy event", + &tx.GetTxsEventRequest{Events: []string{"foobar"}}, + true, "event foobar should be of the format: {eventType}.{eventAttribute}={value}", 0, + }, + { + "request with order-by", + &tx.GetTxsEventRequest{ + Events: []string{bankMsgSendEventAction}, + OrderBy: tx.OrderBy_ORDER_BY_ASC, + }, + false, "", 3, + }, + { + "without pagination", + &tx.GetTxsEventRequest{ + Events: []string{bankMsgSendEventAction}, + }, + false, "", 3, + }, + { + "with pagination", + &tx.GetTxsEventRequest{ + Events: []string{bankMsgSendEventAction}, + Page: 2, + Limit: 2, + }, + false, "", 1, + }, + { + "with multi events", + &tx.GetTxsEventRequest{ + Events: []string{bankMsgSendEventAction, "message.module='bank'"}, + }, + false, "", 3, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + // Query the tx via gRPC. + grpcRes, err := s.queryClient.GetTxsEvent(context.Background(), tc.req) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + s.Require().GreaterOrEqual(len(grpcRes.Txs), 1) + s.Require().Equal("foobar", grpcRes.Txs[0].Body.Memo) + s.Require().Equal(len(grpcRes.Txs), tc.expLen) + // Make sure fields are populated. + // ref: https://github.com/cosmos/cosmos-sdk/issues/8680 + // ref: https://github.com/cosmos/cosmos-sdk/issues/8681 + s.Require().NotEmpty(grpcRes.TxResponses[0].Timestamp) + s.Require().NotEmpty(grpcRes.TxResponses[0].RawLog) + } + }) + } +} + +func (s IntegrationTestSuite) TestGetTxEvents_GRPCGateway() { + val := s.network.Validators[0] + testCases := []struct { + name string + url string + expErr bool + expErrMsg string + expLen int + }{ + { + "empty params", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", val.APIAddress), + true, + "must declare at least one event to search", 0, + }, + { + "without pagination", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, bankMsgSendEventAction), + false, + "", 3, + }, + { + "with pagination", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&page=%d&limit=%d", val.APIAddress, bankMsgSendEventAction, 2, 2), + false, + "", 1, + }, + { + "valid request: order by asc", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), + false, + "", 3, + }, + { + "valid request: order by desc", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), + false, + "", 3, + }, + { + "invalid request: invalid order by", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), + true, + "is not a valid tx.OrderBy", 0, + }, + { + "expect pass with multiple-events", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), + false, + "", 3, + }, + { + "expect pass with escape event", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action%3D'/cosmos.bank.v1beta1.MsgSend'"), + false, + "", 3, + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + res, err := testutil.GetRequest(tc.url) + s.Require().NoError(err) + if tc.expErr { + s.Require().Contains(string(res), tc.expErrMsg) + } else { + var result tx.GetTxsEventResponse + err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) + s.Require().NoError(err) + s.Require().GreaterOrEqual(len(result.Txs), 1) + s.Require().Equal("foobar", result.Txs[0].Body.Memo) + s.Require().NotZero(result.TxResponses[0].Height) + s.Require().Equal(len(result.Txs), tc.expLen) + } + }) + } +} + +func (s IntegrationTestSuite) TestGetTx_GRPC() { + testCases := []struct { + name string + req *tx.GetTxRequest + expErr bool + expErrMsg string + }{ + {"nil request", nil, true, "request cannot be nil"}, + {"empty request", &tx.GetTxRequest{}, true, "tx hash cannot be empty"}, + {"request with dummy hash", &tx.GetTxRequest{Hash: "deadbeef"}, true, "code = NotFound desc = tx not found: deadbeef"}, + {"good request", &tx.GetTxRequest{Hash: s.txRes.TxHash}, false, ""}, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + // Query the tx via gRPC. + grpcRes, err := s.queryClient.GetTx(context.Background(), tc.req) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + s.Require().Equal("foobar", grpcRes.Tx.Body.Memo) + } + }) + } +} + +func (s IntegrationTestSuite) TestGetTx_GRPCGateway() { + val := s.network.Validators[0] + testCases := []struct { + name string + url string + expErr bool + expErrMsg string + }{ + { + "empty params", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/", val.APIAddress), + true, "tx hash cannot be empty", + }, + { + "dummy hash", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, "deadbeef"), + true, "code = NotFound desc = tx not found: deadbeef", + }, + { + "good hash", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, s.txRes.TxHash), + false, "", + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + res, err := testutil.GetRequest(tc.url) + s.Require().NoError(err) + if tc.expErr { + s.Require().Contains(string(res), tc.expErrMsg) + } else { + var result tx.GetTxResponse + err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) + s.Require().NoError(err) + s.Require().Equal("foobar", result.Tx.Body.Memo) + s.Require().NotZero(result.TxResponse.Height) + + // Make sure fields are populated. + // ref: https://github.com/cosmos/cosmos-sdk/issues/8680 + // ref: https://github.com/cosmos/cosmos-sdk/issues/8681 + s.Require().NotEmpty(result.TxResponse.Timestamp) + s.Require().NotEmpty(result.TxResponse.RawLog) + } + }) + } +} + +func (s IntegrationTestSuite) TestBroadcastTx_GRPC() { + val := s.network.Validators[0] + txBuilder := s.mkTxBuilder() + txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + + testCases := []struct { + name string + req *tx.BroadcastTxRequest + expErr bool + expErrMsg string + }{ + {"nil request", nil, true, "request cannot be nil"}, + {"empty request", &tx.BroadcastTxRequest{}, true, "invalid empty tx"}, + {"no mode", &tx.BroadcastTxRequest{TxBytes: txBytes}, true, "supported types: sync, async"}, + {"valid request", &tx.BroadcastTxRequest{ + Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC, + TxBytes: txBytes, + }, false, ""}, + } + + for _, tc := range testCases { + tc := tc + s.Run(tc.name, func() { + // Broadcast the tx via gRPC via the validator's clientCtx (which goes + // through Tendermint). + grpcRes, err := s.queryClient.BroadcastTx(context.Background(), tc.req) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + s.Require().Equal(uint32(0), grpcRes.TxResponse.Code) + } + }) + } +} + +func (s IntegrationTestSuite) TestBroadcastTx_GRPCGateway() { + val := s.network.Validators[0] + txBuilder := s.mkTxBuilder() + txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) + s.Require().NoError(err) + + testCases := []struct { + name string + req *tx.BroadcastTxRequest + expErr bool + expErrMsg string + }{ + {"empty request", &tx.BroadcastTxRequest{}, true, "invalid empty tx"}, + {"no mode", &tx.BroadcastTxRequest{TxBytes: txBytes}, true, "supported types: sync, async"}, + {"valid request", &tx.BroadcastTxRequest{ + Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC, + TxBytes: txBytes, + }, false, ""}, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + req, err := val.ClientCtx.Codec.MarshalJSON(tc.req) + s.Require().NoError(err) + res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", val.APIAddress), "application/json", req) + s.Require().NoError(err) + if tc.expErr { + s.Require().Contains(string(res), tc.expErrMsg) + } else { + var result tx.BroadcastTxResponse + err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) + s.Require().NoError(err) + s.Require().Equal(uint32(0), result.TxResponse.Code, "rawlog", result.TxResponse.RawLog) + } + }) + } +} + +func (s *IntegrationTestSuite) TestSimMultiSigTx() { + val1 := *s.network.Validators[0] + + kr := val1.ClientCtx.Keyring + + account1, _, err := kr.NewMnemonic("newAccount1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + + account2, _, err := kr.NewMnemonic("newAccount2", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + + pub1, err := account1.GetPubKey() + s.Require().NoError(err) + + pub2, err := account2.GetPubKey() + s.Require().NoError(err) + + multi := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{pub1, pub2}) + _, err = kr.SaveMultisig("multi", multi) + s.Require().NoError(err) + + s.Require().NoError(s.network.WaitForNextBlock()) + + multisigRecord, err := val1.ClientCtx.Keyring.Key("multi") + s.Require().NoError(err) + + height, err := s.network.LatestHeight() + s.Require().NoError(err) + _, err = s.network.WaitForHeight(height + 1) + s.Require().NoError(err) + + addr, err := multisigRecord.GetAddress() + s.Require().NoError(err) + + // Send coins from validator to multisig. + coins := sdk.NewInt64Coin(s.cfg.BondDenom, 15) + _, err = cli.MsgSendExec( + val1.ClientCtx, + val1.Address, + addr, + sdk.NewCoins(coins), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--gas=%d", flags.DefaultGasLimit), + ) + s.Require().NoError(err) + + height, err = s.network.LatestHeight() + s.Require().NoError(err) + _, err = s.network.WaitForHeight(height + 1) + s.Require().NoError(err) + + // Generate multisig transaction. + multiGeneratedTx, err := cli.MsgSendExec( + val1.ClientCtx, + addr, + val1.Address, + sdk.NewCoins( + sdk.NewInt64Coin(s.cfg.BondDenom, 5), + ), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), + fmt.Sprintf("--%s=foobar", flags.FlagNote), + ) + s.Require().NoError(err) + + // Save tx to file + multiGeneratedTxFile := testutil.WriteToNewTempFile(s.T(), multiGeneratedTx.String()) + + // Sign with account1 + addr1, err := account1.GetAddress() + s.Require().NoError(err) + val1.ClientCtx.HomeDir = strings.Replace(val1.ClientCtx.HomeDir, "simd", "simcli", 1) + account1Signature, err := authtest.TxSignExec(val1.ClientCtx, addr1, multiGeneratedTxFile.Name(), "--multisig", addr.String()) + s.Require().NoError(err) + sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String()) + + // Sign with account2 + addr2, err := account2.GetAddress() + s.Require().NoError(err) + account2Signature, err := authtest.TxSignExec(val1.ClientCtx, addr2, multiGeneratedTxFile.Name(), "--multisig", addr.String()) + s.Require().NoError(err) + sign2File := testutil.WriteToNewTempFile(s.T(), account2Signature.String()) + + // multisign tx + val1.ClientCtx.Offline = false + multiSigWith2Signatures, err := authtest.TxMultiSignExec(val1.ClientCtx, multisigRecord.Name, multiGeneratedTxFile.Name(), sign1File.Name(), sign2File.Name()) + s.Require().NoError(err) + + // convert from protoJSON to protoBinary for sim + sdkTx, err := val1.ClientCtx.TxConfig.TxJSONDecoder()(multiSigWith2Signatures.Bytes()) + s.Require().NoError(err) + txBytes, err := val1.ClientCtx.TxConfig.TxEncoder()(sdkTx) + s.Require().NoError(err) + + // simulate tx + sim := &tx.SimulateRequest{TxBytes: txBytes} + res, err := s.queryClient.Simulate(context.Background(), sim) + s.Require().NoError(err) + + // make sure gas was used + s.Require().Greater(res.GasInfo.GasUsed, uint64(0)) +} + +func (s IntegrationTestSuite) TestGetBlockWithTxs_GRPC() { + testCases := []struct { + name string + req *tx.GetBlockWithTxsRequest + expErr bool + expErrMsg string + expTxsLen int + }{ + {"nil request", nil, true, "request cannot be nil", 0}, + {"empty request", &tx.GetBlockWithTxsRequest{}, true, "height must not be less than 1 or greater than the current height", 0}, + {"bad height", &tx.GetBlockWithTxsRequest{Height: 99999999}, true, "height must not be less than 1 or greater than the current height", 0}, + {"bad pagination", &tx.GetBlockWithTxsRequest{Height: s.txHeight, Pagination: &query.PageRequest{Offset: 1000, Limit: 100}}, true, "out of range", 0}, + {"good request", &tx.GetBlockWithTxsRequest{Height: s.txHeight}, false, "", 1}, + {"with pagination request", &tx.GetBlockWithTxsRequest{Height: s.txHeight, Pagination: &query.PageRequest{Offset: 0, Limit: 1}}, false, "", 1}, + {"page all request", &tx.GetBlockWithTxsRequest{Height: s.txHeight, Pagination: &query.PageRequest{Offset: 0, Limit: 100}}, false, "", 1}, + {"block with 0 tx", &tx.GetBlockWithTxsRequest{Height: s.txHeight - 1, Pagination: &query.PageRequest{Offset: 0, Limit: 100}}, false, "", 0}, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + // Query the tx via gRPC. + grpcRes, err := s.queryClient.GetBlockWithTxs(context.Background(), tc.req) + if tc.expErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expErrMsg) + } else { + s.Require().NoError(err) + if tc.expTxsLen > 0 { + s.Require().Equal("foobar", grpcRes.Txs[0].Body.Memo) + } + s.Require().Equal(grpcRes.Block.Header.Height, tc.req.Height) + if tc.req.Pagination != nil { + s.Require().LessOrEqual(len(grpcRes.Txs), int(tc.req.Pagination.Limit)) + } + } + }) + } +} + +func (s IntegrationTestSuite) TestGetBlockWithTxs_GRPCGateway() { + val := s.network.Validators[0] + testCases := []struct { + name string + url string + expErr bool + expErrMsg string + }{ + { + "empty params", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/0", val.APIAddress), + true, "height must not be less than 1 or greater than the current height", + }, + { + "bad height", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/%d", val.APIAddress, 9999999), + true, "height must not be less than 1 or greater than the current height", + }, + { + "good request", + fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/%d", val.APIAddress, s.txHeight), + false, "", + }, + } + for _, tc := range testCases { + s.Run(tc.name, func() { + res, err := testutil.GetRequest(tc.url) + s.Require().NoError(err) + if tc.expErr { + s.Require().Contains(string(res), tc.expErrMsg) + } else { + var result tx.GetBlockWithTxsResponse + err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) + s.Require().NoError(err) + s.Require().Equal("foobar", result.Txs[0].Body.Memo) + s.Require().Equal(result.Block.Header.Height, s.txHeight) + } + }) + } +} + +func TestIntegrationTestSuite(t *testing.T) { + suite.Run(t, new(IntegrationTestSuite)) +} + +func (s IntegrationTestSuite) mkTxBuilder() client.TxBuilder { + val := s.network.Validators[0] + s.Require().NoError(s.network.WaitForNextBlock()) + + // prepare txBuilder with msg + txBuilder := val.ClientCtx.TxConfig.NewTxBuilder() + feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)} + gasLimit := testdata.NewTestGasLimit() + s.Require().NoError( + txBuilder.SetMsgs(&banktypes.MsgSend{ + FromAddress: val.Address.String(), + ToAddress: val.Address.String(), + Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, + }), + ) + txBuilder.SetFeeAmount(feeAmount) + txBuilder.SetGasLimit(gasLimit) + txBuilder.SetMemo("foobar") + + // setup txFactory + txFactory := clienttx.Factory{}. + WithChainID(val.ClientCtx.ChainID). + WithKeybase(val.ClientCtx.Keyring). + WithTxConfig(val.ClientCtx.TxConfig). + WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) + + // Sign Tx. + err := authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, false, true) + s.Require().NoError(err) + + return txBuilder +} + +// protoTxProvider is a type which can provide a proto transaction. It is a +// workaround to get access to the wrapper TxBuilder's method GetProtoTx(). +// Deprecated: It's only used for testing the deprecated Simulate gRPC endpoint +// using a proto Tx field. +type protoTxProvider interface { + GetProtoTx() *tx.Tx +} + +// txBuilderToProtoTx converts a txBuilder into a proto tx.Tx. +// Deprecated: It's only used for testing the deprecated Simulate gRPC endpoint +// using a proto Tx field. +func txBuilderToProtoTx(txBuilder client.TxBuilder) (*tx.Tx, error) { // nolint + protoProvider, ok := txBuilder.(protoTxProvider) + if !ok { + return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expected proto tx builder, got %T", txBuilder) + } + + return protoProvider.GetProtoTx(), nil +} diff --git a/x/auth/tx/service.go b/x/auth/tx/service.go index 8c8e0a2347f9..6bc4c95120c0 100644 --- a/x/auth/tx/service.go +++ b/x/auth/tx/service.go @@ -45,7 +45,7 @@ var ( _ txtypes.ServiceServer = txServer{} // EventRegex checks that an event string is formatted with {alphabetic}.{alphabetic}={value} - EventRegex = regexp.MustCompile(`^[a-zA-Z]+\.[a-zA-Z]+=\S+$`) + EventRegex = regexp.MustCompile(`^[a-zA-Z_]+\.[a-zA-Z_]+=\S+$`) ) const ( From a7b29ec17f714e17f160b47049c63879ee6bc989 Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 14 Nov 2022 21:09:17 +0100 Subject: [PATCH 2/3] updates --- CHANGELOG.md | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index feb32fb1a14c..14027dea492c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] -## [v0.46.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.5) - 2022-11-14 +## [v0.46.5](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.5) - 2022-11-16 ### Improvements @@ -49,21 +49,9 @@ Ref: https://keepachangelog.com/en/1.0.0/ * (x/bank) [#13821](https://github.com/cosmos/cosmos-sdk/pull/13821) Fix bank store migration of coin metadata. * (x/group) [#13808](https://github.com/cosmos/cosmos-sdk/pull/13808) Fix propagation of message events to the current context in `EndBlocker`. * (x/gov) [#13728](https://github.com/cosmos/cosmos-sdk/pull/13728) Fix propagation of message events to the current context in `EndBlocker`. -<<<<<<< HEAD -* (store) [#13803](https://github.com/cosmos/cosmos-sdk/pull/13803) Add an error log if iavl set operation failed. -======= -* (server) [#13778](https://github.com/cosmos/cosmos-sdk/pull/13778) Set Cosmos SDK default endpoints to localhost to avoid unknown exposure of endpoints. +* (store) [#13803](https://github.com/cosmos/cosmos-sdk/pull/13803) Add an error log if IAVL set operation failed. * [#13861](https://github.com/cosmos/cosmos-sdk/pull/13861) Allow `_` characters in tx event queries, i.e. `GetTxsEvent`. -### Deprecated - -* (x/evidence) [#13740](https://github.com/cosmos/cosmos-sdk/pull/13740) The `evidence_hash` field of `QueryEvidenceRequest` has been deprecated and now contains a new field `hash` with type `string`. -* (x/bank) [#11859](https://github.com/cosmos/cosmos-sdk/pull/11859) The Params.SendEnabled field is deprecated and unusable. - The information can now be accessed using the BankKeeper. - Setting can be done using MsgSetSendEnabled as a governance proposal. - A SendEnabled query has been added to both GRPC and CLI. ->>>>>>> 14c582f30 (fix: Allow underscores in EventRegex (#13861)) - ## [v0.46.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.46.4) - 2022-11-01 ### Features From 601d7f7c84688a015aa7aad66a16fc7360edea2b Mon Sep 17 00:00:00 2001 From: Julien Robert Date: Mon, 14 Nov 2022 21:10:48 +0100 Subject: [PATCH 3/3] updates --- tests/e2e/tx/service_test.go | 828 ----------------------------------- x/auth/tx/service_test.go | 5 + 2 files changed, 5 insertions(+), 828 deletions(-) delete mode 100644 tests/e2e/tx/service_test.go diff --git a/tests/e2e/tx/service_test.go b/tests/e2e/tx/service_test.go deleted file mode 100644 index 45c7ff3cde15..000000000000 --- a/tests/e2e/tx/service_test.go +++ /dev/null @@ -1,828 +0,0 @@ -package tx_test - -import ( - "context" - "encoding/base64" - "fmt" - "strings" - "testing" - - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - - "cosmossdk.io/simapp" - "github.com/cosmos/cosmos-sdk/client" - "github.com/cosmos/cosmos-sdk/client/flags" - clienttx "github.com/cosmos/cosmos-sdk/client/tx" - "github.com/cosmos/cosmos-sdk/crypto/hd" - "github.com/cosmos/cosmos-sdk/crypto/keyring" - kmultisig "github.com/cosmos/cosmos-sdk/crypto/keys/multisig" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil" - "github.com/cosmos/cosmos-sdk/testutil/cli" - "github.com/cosmos/cosmos-sdk/testutil/network" - "github.com/cosmos/cosmos-sdk/testutil/testdata" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - "github.com/cosmos/cosmos-sdk/types/query" - "github.com/cosmos/cosmos-sdk/types/tx" - "github.com/cosmos/cosmos-sdk/types/tx/signing" - authclient "github.com/cosmos/cosmos-sdk/x/auth/client" - authtest "github.com/cosmos/cosmos-sdk/x/auth/client/testutil" - authtx "github.com/cosmos/cosmos-sdk/x/auth/tx" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" -) - -var bankMsgSendEventAction = fmt.Sprintf("message.action='%s'", sdk.MsgTypeURL(&banktypes.MsgSend{})) - -type IntegrationTestSuite struct { - suite.Suite - - cfg network.Config - network *network.Network - - txHeight int64 - queryClient tx.ServiceClient - txRes sdk.TxResponse -} - -func (s *IntegrationTestSuite) SetupSuite() { - s.T().Log("setting up integration test suite") - - cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) - cfg.NumValidators = 1 - s.cfg = cfg - - var err error - s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) - s.Require().NoError(err) - - val := s.network.Validators[0] - s.Require().NoError(s.network.WaitForNextBlock()) - - s.queryClient = tx.NewServiceClient(val.ClientCtx) - - // Create a new MsgSend tx from val to itself. - out, err := cli.MsgSendExec( - val.ClientCtx, - val.Address, - val.Address, - sdk.NewCoins( - sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10)), - ), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - fmt.Sprintf("--gas=%d", flags.DefaultGasLimit), - fmt.Sprintf("--%s=foobar", flags.FlagNote), - ) - s.Require().NoError(err) - s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &s.txRes)) - s.Require().Equal(uint32(0), s.txRes.Code, s.txRes) - - out, err = cli.MsgSendExec( - val.ClientCtx, - val.Address, - val.Address, - sdk.NewCoins( - sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(1)), - ), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=2", flags.FlagSequence), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - fmt.Sprintf("--gas=%d", flags.DefaultGasLimit), - fmt.Sprintf("--%s=foobar", flags.FlagNote), - ) - s.Require().NoError(err) - var tr sdk.TxResponse - s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &tr)) - s.Require().Equal(uint32(0), tr.Code) - - s.Require().NoError(s.network.WaitForNextBlock()) - height, err := s.network.LatestHeight() - s.Require().NoError(err) - s.txHeight = height -} - -func (s *IntegrationTestSuite) TearDownSuite() { - s.T().Log("tearing down integration test suite") - s.network.Cleanup() -} - -func (s *IntegrationTestSuite) TestQueryBySig() { - // broadcast tx - txb := s.mkTxBuilder() - txbz, err := s.cfg.TxConfig.TxEncoder()(txb.GetTx()) - s.Require().NoError(err) - resp, err := s.queryClient.BroadcastTx(context.Background(), &tx.BroadcastTxRequest{TxBytes: txbz, Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC}) - s.Require().NoError(err) - s.Require().NotEmpty(resp.TxResponse.TxHash) - - s.Require().NoError(s.network.WaitForNextBlock()) - s.Require().NoError(s.network.WaitForNextBlock()) - - // get the signature out of the builder - sigs, err := txb.GetTx().GetSignaturesV2() - s.Require().NoError(err) - s.Require().Len(sigs, 1) - sig, ok := sigs[0].Data.(*signing.SingleSignatureData) - s.Require().True(ok) - - // encode, format, query - b64Sig := base64.StdEncoding.EncodeToString(sig.Signature) - sigFormatted := fmt.Sprintf("%s.%s='%s'", sdk.EventTypeTx, sdk.AttributeKeySignature, b64Sig) - res, err := s.queryClient.GetTxsEvent(context.Background(), &tx.GetTxsEventRequest{ - Events: []string{sigFormatted}, - OrderBy: 0, - Page: 0, - Limit: 10, - }) - s.Require().NoError(err) - s.Require().Len(res.Txs, 1) - s.Require().Len(res.Txs[0].Signatures, 1) - s.Require().Equal(res.Txs[0].Signatures[0], sig.Signature) - - // bad format should error - _, err = s.queryClient.GetTxsEvent(context.Background(), &tx.GetTxsEventRequest{Events: []string{"tx.foo.bar='baz'"}}) - s.Require().ErrorContains(err, "invalid event;") -} - -func TestEventRegex(t *testing.T) { - t.Parallel() - - testCases := []struct { - name string - event string - match bool - }{ - { - name: "valid: with quotes", - event: "tx.message='something'", - match: true, - }, - { - name: "valid: with underscores", - event: "claim_reward.message_action='something'", - match: true, - }, - { - name: "valid: no quotes", - event: "tx.message=something", - match: true, - }, - { - name: "invalid: too many separators", - event: "tx.message.foo='bar'", - match: false, - }, - { - name: "valid: symbols ok", - event: "tx.signature='foobar/baz123=='", - match: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - match := authtx.EventRegex.Match([]byte(tc.event)) - require.Equal(t, tc.match, match) - }) - } -} - -func (s IntegrationTestSuite) TestSimulateTx_GRPC() { - val := s.network.Validators[0] - txBuilder := s.mkTxBuilder() - // Convert the txBuilder to a tx.Tx. - protoTx, err := txBuilderToProtoTx(txBuilder) - s.Require().NoError(err) - // Encode the txBuilder to txBytes. - txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) - s.Require().NoError(err) - - testCases := []struct { - name string - req *tx.SimulateRequest - expErr bool - expErrMsg string - }{ - {"nil request", nil, true, "request cannot be nil"}, - {"empty request", &tx.SimulateRequest{}, true, "empty txBytes is not allowed"}, - {"valid request with proto tx (deprecated)", &tx.SimulateRequest{Tx: protoTx}, false, ""}, - {"valid request with tx_bytes", &tx.SimulateRequest{TxBytes: txBytes}, false, ""}, - } - - for _, tc := range testCases { - tc := tc - s.Run(tc.name, func() { - // Broadcast the tx via gRPC via the validator's clientCtx (which goes - // through Tendermint). - res, err := s.queryClient.Simulate(context.Background(), tc.req) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrMsg) - } else { - s.Require().NoError(err) - // Check the result and gas used are correct. - // - // The 12 events are: - // - Sending Fee to the pool: coin_spent, coin_received, transfer and message.sender= - // - tx.* events: tx.fee, tx.acc_seq, tx.signature - // - Sending Amount to recipient: coin_spent, coin_received, transfer and message.sender= - // - Msg events: message.module=bank and message.action=/cosmos.bank.v1beta1.MsgSend (in one message) - s.Require().Equal(12, len(res.GetResult().GetEvents())) - s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. - } - }) - } -} - -func (s IntegrationTestSuite) TestSimulateTx_GRPCGateway() { - val := s.network.Validators[0] - txBuilder := s.mkTxBuilder() - // Convert the txBuilder to a tx.Tx. - protoTx, err := txBuilderToProtoTx(txBuilder) - s.Require().NoError(err) - // Encode the txBuilder to txBytes. - txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) - s.Require().NoError(err) - - testCases := []struct { - name string - req *tx.SimulateRequest - expErr bool - expErrMsg string - }{ - {"empty request", &tx.SimulateRequest{}, true, "empty txBytes is not allowed"}, - {"valid request with proto tx (deprecated)", &tx.SimulateRequest{Tx: protoTx}, false, ""}, - {"valid request with tx_bytes", &tx.SimulateRequest{TxBytes: txBytes}, false, ""}, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - req, err := val.ClientCtx.Codec.MarshalJSON(tc.req) - s.Require().NoError(err) - res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/simulate", val.APIAddress), "application/json", req) - s.Require().NoError(err) - if tc.expErr { - s.Require().Contains(string(res), tc.expErrMsg) - } else { - var result tx.SimulateResponse - err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) - s.Require().NoError(err) - // Check the result and gas used are correct. - s.Require().Len(result.GetResult().MsgResponses, 1) - s.Require().Equal(12, len(result.GetResult().GetEvents())) // See TestSimulateTx_GRPC for the 12 events. - s.Require().True(result.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty. - } - }) - } -} - -func (s IntegrationTestSuite) TestGetTxEvents_GRPC() { - testCases := []struct { - name string - req *tx.GetTxsEventRequest - expErr bool - expErrMsg string - expLen int - }{ - { - "nil request", - nil, - true, "request cannot be nil", 0, - }, - { - "empty request", - &tx.GetTxsEventRequest{}, - true, "must declare at least one event to search", 0, - }, - { - "request with dummy event", - &tx.GetTxsEventRequest{Events: []string{"foobar"}}, - true, "event foobar should be of the format: {eventType}.{eventAttribute}={value}", 0, - }, - { - "request with order-by", - &tx.GetTxsEventRequest{ - Events: []string{bankMsgSendEventAction}, - OrderBy: tx.OrderBy_ORDER_BY_ASC, - }, - false, "", 3, - }, - { - "without pagination", - &tx.GetTxsEventRequest{ - Events: []string{bankMsgSendEventAction}, - }, - false, "", 3, - }, - { - "with pagination", - &tx.GetTxsEventRequest{ - Events: []string{bankMsgSendEventAction}, - Page: 2, - Limit: 2, - }, - false, "", 1, - }, - { - "with multi events", - &tx.GetTxsEventRequest{ - Events: []string{bankMsgSendEventAction, "message.module='bank'"}, - }, - false, "", 3, - }, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - // Query the tx via gRPC. - grpcRes, err := s.queryClient.GetTxsEvent(context.Background(), tc.req) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrMsg) - } else { - s.Require().NoError(err) - s.Require().GreaterOrEqual(len(grpcRes.Txs), 1) - s.Require().Equal("foobar", grpcRes.Txs[0].Body.Memo) - s.Require().Equal(len(grpcRes.Txs), tc.expLen) - // Make sure fields are populated. - // ref: https://github.com/cosmos/cosmos-sdk/issues/8680 - // ref: https://github.com/cosmos/cosmos-sdk/issues/8681 - s.Require().NotEmpty(grpcRes.TxResponses[0].Timestamp) - s.Require().NotEmpty(grpcRes.TxResponses[0].RawLog) - } - }) - } -} - -func (s IntegrationTestSuite) TestGetTxEvents_GRPCGateway() { - val := s.network.Validators[0] - testCases := []struct { - name string - url string - expErr bool - expErrMsg string - expLen int - }{ - { - "empty params", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", val.APIAddress), - true, - "must declare at least one event to search", 0, - }, - { - "without pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, bankMsgSendEventAction), - false, - "", 3, - }, - { - "with pagination", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&page=%d&limit=%d", val.APIAddress, bankMsgSendEventAction, 2, 2), - false, - "", 1, - }, - { - "valid request: order by asc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_ASC", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), - false, - "", 3, - }, - { - "valid request: order by desc", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=ORDER_BY_DESC", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), - false, - "", 3, - }, - { - "invalid request: invalid order by", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s&order_by=invalid_order", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), - true, - "is not a valid tx.OrderBy", 0, - }, - { - "expect pass with multiple-events", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s&events=%s", val.APIAddress, bankMsgSendEventAction, "message.module='bank'"), - false, - "", 3, - }, - { - "expect pass with escape event", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs?events=%s", val.APIAddress, "message.action%3D'/cosmos.bank.v1beta1.MsgSend'"), - false, - "", 3, - }, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - res, err := testutil.GetRequest(tc.url) - s.Require().NoError(err) - if tc.expErr { - s.Require().Contains(string(res), tc.expErrMsg) - } else { - var result tx.GetTxsEventResponse - err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) - s.Require().NoError(err) - s.Require().GreaterOrEqual(len(result.Txs), 1) - s.Require().Equal("foobar", result.Txs[0].Body.Memo) - s.Require().NotZero(result.TxResponses[0].Height) - s.Require().Equal(len(result.Txs), tc.expLen) - } - }) - } -} - -func (s IntegrationTestSuite) TestGetTx_GRPC() { - testCases := []struct { - name string - req *tx.GetTxRequest - expErr bool - expErrMsg string - }{ - {"nil request", nil, true, "request cannot be nil"}, - {"empty request", &tx.GetTxRequest{}, true, "tx hash cannot be empty"}, - {"request with dummy hash", &tx.GetTxRequest{Hash: "deadbeef"}, true, "code = NotFound desc = tx not found: deadbeef"}, - {"good request", &tx.GetTxRequest{Hash: s.txRes.TxHash}, false, ""}, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - // Query the tx via gRPC. - grpcRes, err := s.queryClient.GetTx(context.Background(), tc.req) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrMsg) - } else { - s.Require().NoError(err) - s.Require().Equal("foobar", grpcRes.Tx.Body.Memo) - } - }) - } -} - -func (s IntegrationTestSuite) TestGetTx_GRPCGateway() { - val := s.network.Validators[0] - testCases := []struct { - name string - url string - expErr bool - expErrMsg string - }{ - { - "empty params", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/", val.APIAddress), - true, "tx hash cannot be empty", - }, - { - "dummy hash", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, "deadbeef"), - true, "code = NotFound desc = tx not found: deadbeef", - }, - { - "good hash", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, s.txRes.TxHash), - false, "", - }, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - res, err := testutil.GetRequest(tc.url) - s.Require().NoError(err) - if tc.expErr { - s.Require().Contains(string(res), tc.expErrMsg) - } else { - var result tx.GetTxResponse - err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) - s.Require().NoError(err) - s.Require().Equal("foobar", result.Tx.Body.Memo) - s.Require().NotZero(result.TxResponse.Height) - - // Make sure fields are populated. - // ref: https://github.com/cosmos/cosmos-sdk/issues/8680 - // ref: https://github.com/cosmos/cosmos-sdk/issues/8681 - s.Require().NotEmpty(result.TxResponse.Timestamp) - s.Require().NotEmpty(result.TxResponse.RawLog) - } - }) - } -} - -func (s IntegrationTestSuite) TestBroadcastTx_GRPC() { - val := s.network.Validators[0] - txBuilder := s.mkTxBuilder() - txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) - s.Require().NoError(err) - - testCases := []struct { - name string - req *tx.BroadcastTxRequest - expErr bool - expErrMsg string - }{ - {"nil request", nil, true, "request cannot be nil"}, - {"empty request", &tx.BroadcastTxRequest{}, true, "invalid empty tx"}, - {"no mode", &tx.BroadcastTxRequest{TxBytes: txBytes}, true, "supported types: sync, async"}, - {"valid request", &tx.BroadcastTxRequest{ - Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC, - TxBytes: txBytes, - }, false, ""}, - } - - for _, tc := range testCases { - tc := tc - s.Run(tc.name, func() { - // Broadcast the tx via gRPC via the validator's clientCtx (which goes - // through Tendermint). - grpcRes, err := s.queryClient.BroadcastTx(context.Background(), tc.req) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrMsg) - } else { - s.Require().NoError(err) - s.Require().Equal(uint32(0), grpcRes.TxResponse.Code) - } - }) - } -} - -func (s IntegrationTestSuite) TestBroadcastTx_GRPCGateway() { - val := s.network.Validators[0] - txBuilder := s.mkTxBuilder() - txBytes, err := val.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx()) - s.Require().NoError(err) - - testCases := []struct { - name string - req *tx.BroadcastTxRequest - expErr bool - expErrMsg string - }{ - {"empty request", &tx.BroadcastTxRequest{}, true, "invalid empty tx"}, - {"no mode", &tx.BroadcastTxRequest{TxBytes: txBytes}, true, "supported types: sync, async"}, - {"valid request", &tx.BroadcastTxRequest{ - Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC, - TxBytes: txBytes, - }, false, ""}, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - req, err := val.ClientCtx.Codec.MarshalJSON(tc.req) - s.Require().NoError(err) - res, err := testutil.PostRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs", val.APIAddress), "application/json", req) - s.Require().NoError(err) - if tc.expErr { - s.Require().Contains(string(res), tc.expErrMsg) - } else { - var result tx.BroadcastTxResponse - err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) - s.Require().NoError(err) - s.Require().Equal(uint32(0), result.TxResponse.Code, "rawlog", result.TxResponse.RawLog) - } - }) - } -} - -func (s *IntegrationTestSuite) TestSimMultiSigTx() { - val1 := *s.network.Validators[0] - - kr := val1.ClientCtx.Keyring - - account1, _, err := kr.NewMnemonic("newAccount1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) - s.Require().NoError(err) - - account2, _, err := kr.NewMnemonic("newAccount2", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) - s.Require().NoError(err) - - pub1, err := account1.GetPubKey() - s.Require().NoError(err) - - pub2, err := account2.GetPubKey() - s.Require().NoError(err) - - multi := kmultisig.NewLegacyAminoPubKey(2, []cryptotypes.PubKey{pub1, pub2}) - _, err = kr.SaveMultisig("multi", multi) - s.Require().NoError(err) - - s.Require().NoError(s.network.WaitForNextBlock()) - - multisigRecord, err := val1.ClientCtx.Keyring.Key("multi") - s.Require().NoError(err) - - height, err := s.network.LatestHeight() - s.Require().NoError(err) - _, err = s.network.WaitForHeight(height + 1) - s.Require().NoError(err) - - addr, err := multisigRecord.GetAddress() - s.Require().NoError(err) - - // Send coins from validator to multisig. - coins := sdk.NewInt64Coin(s.cfg.BondDenom, 15) - _, err = cli.MsgSendExec( - val1.ClientCtx, - val1.Address, - addr, - sdk.NewCoins(coins), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - fmt.Sprintf("--gas=%d", flags.DefaultGasLimit), - ) - s.Require().NoError(err) - - height, err = s.network.LatestHeight() - s.Require().NoError(err) - _, err = s.network.WaitForHeight(height + 1) - s.Require().NoError(err) - - // Generate multisig transaction. - multiGeneratedTx, err := cli.MsgSendExec( - val1.ClientCtx, - addr, - val1.Address, - sdk.NewCoins( - sdk.NewInt64Coin(s.cfg.BondDenom, 5), - ), - fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), - fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), - fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), - fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), - fmt.Sprintf("--%s=foobar", flags.FlagNote), - ) - s.Require().NoError(err) - - // Save tx to file - multiGeneratedTxFile := testutil.WriteToNewTempFile(s.T(), multiGeneratedTx.String()) - - // Sign with account1 - addr1, err := account1.GetAddress() - s.Require().NoError(err) - val1.ClientCtx.HomeDir = strings.Replace(val1.ClientCtx.HomeDir, "simd", "simcli", 1) - account1Signature, err := authtest.TxSignExec(val1.ClientCtx, addr1, multiGeneratedTxFile.Name(), "--multisig", addr.String()) - s.Require().NoError(err) - sign1File := testutil.WriteToNewTempFile(s.T(), account1Signature.String()) - - // Sign with account2 - addr2, err := account2.GetAddress() - s.Require().NoError(err) - account2Signature, err := authtest.TxSignExec(val1.ClientCtx, addr2, multiGeneratedTxFile.Name(), "--multisig", addr.String()) - s.Require().NoError(err) - sign2File := testutil.WriteToNewTempFile(s.T(), account2Signature.String()) - - // multisign tx - val1.ClientCtx.Offline = false - multiSigWith2Signatures, err := authtest.TxMultiSignExec(val1.ClientCtx, multisigRecord.Name, multiGeneratedTxFile.Name(), sign1File.Name(), sign2File.Name()) - s.Require().NoError(err) - - // convert from protoJSON to protoBinary for sim - sdkTx, err := val1.ClientCtx.TxConfig.TxJSONDecoder()(multiSigWith2Signatures.Bytes()) - s.Require().NoError(err) - txBytes, err := val1.ClientCtx.TxConfig.TxEncoder()(sdkTx) - s.Require().NoError(err) - - // simulate tx - sim := &tx.SimulateRequest{TxBytes: txBytes} - res, err := s.queryClient.Simulate(context.Background(), sim) - s.Require().NoError(err) - - // make sure gas was used - s.Require().Greater(res.GasInfo.GasUsed, uint64(0)) -} - -func (s IntegrationTestSuite) TestGetBlockWithTxs_GRPC() { - testCases := []struct { - name string - req *tx.GetBlockWithTxsRequest - expErr bool - expErrMsg string - expTxsLen int - }{ - {"nil request", nil, true, "request cannot be nil", 0}, - {"empty request", &tx.GetBlockWithTxsRequest{}, true, "height must not be less than 1 or greater than the current height", 0}, - {"bad height", &tx.GetBlockWithTxsRequest{Height: 99999999}, true, "height must not be less than 1 or greater than the current height", 0}, - {"bad pagination", &tx.GetBlockWithTxsRequest{Height: s.txHeight, Pagination: &query.PageRequest{Offset: 1000, Limit: 100}}, true, "out of range", 0}, - {"good request", &tx.GetBlockWithTxsRequest{Height: s.txHeight}, false, "", 1}, - {"with pagination request", &tx.GetBlockWithTxsRequest{Height: s.txHeight, Pagination: &query.PageRequest{Offset: 0, Limit: 1}}, false, "", 1}, - {"page all request", &tx.GetBlockWithTxsRequest{Height: s.txHeight, Pagination: &query.PageRequest{Offset: 0, Limit: 100}}, false, "", 1}, - {"block with 0 tx", &tx.GetBlockWithTxsRequest{Height: s.txHeight - 1, Pagination: &query.PageRequest{Offset: 0, Limit: 100}}, false, "", 0}, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - // Query the tx via gRPC. - grpcRes, err := s.queryClient.GetBlockWithTxs(context.Background(), tc.req) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrMsg) - } else { - s.Require().NoError(err) - if tc.expTxsLen > 0 { - s.Require().Equal("foobar", grpcRes.Txs[0].Body.Memo) - } - s.Require().Equal(grpcRes.Block.Header.Height, tc.req.Height) - if tc.req.Pagination != nil { - s.Require().LessOrEqual(len(grpcRes.Txs), int(tc.req.Pagination.Limit)) - } - } - }) - } -} - -func (s IntegrationTestSuite) TestGetBlockWithTxs_GRPCGateway() { - val := s.network.Validators[0] - testCases := []struct { - name string - url string - expErr bool - expErrMsg string - }{ - { - "empty params", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/0", val.APIAddress), - true, "height must not be less than 1 or greater than the current height", - }, - { - "bad height", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/%d", val.APIAddress, 9999999), - true, "height must not be less than 1 or greater than the current height", - }, - { - "good request", - fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/block/%d", val.APIAddress, s.txHeight), - false, "", - }, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - res, err := testutil.GetRequest(tc.url) - s.Require().NoError(err) - if tc.expErr { - s.Require().Contains(string(res), tc.expErrMsg) - } else { - var result tx.GetBlockWithTxsResponse - err = val.ClientCtx.Codec.UnmarshalJSON(res, &result) - s.Require().NoError(err) - s.Require().Equal("foobar", result.Txs[0].Body.Memo) - s.Require().Equal(result.Block.Header.Height, s.txHeight) - } - }) - } -} - -func TestIntegrationTestSuite(t *testing.T) { - suite.Run(t, new(IntegrationTestSuite)) -} - -func (s IntegrationTestSuite) mkTxBuilder() client.TxBuilder { - val := s.network.Validators[0] - s.Require().NoError(s.network.WaitForNextBlock()) - - // prepare txBuilder with msg - txBuilder := val.ClientCtx.TxConfig.NewTxBuilder() - feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)} - gasLimit := testdata.NewTestGasLimit() - s.Require().NoError( - txBuilder.SetMsgs(&banktypes.MsgSend{ - FromAddress: val.Address.String(), - ToAddress: val.Address.String(), - Amount: sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}, - }), - ) - txBuilder.SetFeeAmount(feeAmount) - txBuilder.SetGasLimit(gasLimit) - txBuilder.SetMemo("foobar") - - // setup txFactory - txFactory := clienttx.Factory{}. - WithChainID(val.ClientCtx.ChainID). - WithKeybase(val.ClientCtx.Keyring). - WithTxConfig(val.ClientCtx.TxConfig). - WithSignMode(signing.SignMode_SIGN_MODE_DIRECT) - - // Sign Tx. - err := authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, false, true) - s.Require().NoError(err) - - return txBuilder -} - -// protoTxProvider is a type which can provide a proto transaction. It is a -// workaround to get access to the wrapper TxBuilder's method GetProtoTx(). -// Deprecated: It's only used for testing the deprecated Simulate gRPC endpoint -// using a proto Tx field. -type protoTxProvider interface { - GetProtoTx() *tx.Tx -} - -// txBuilderToProtoTx converts a txBuilder into a proto tx.Tx. -// Deprecated: It's only used for testing the deprecated Simulate gRPC endpoint -// using a proto Tx field. -func txBuilderToProtoTx(txBuilder client.TxBuilder) (*tx.Tx, error) { // nolint - protoProvider, ok := txBuilder.(protoTxProvider) - if !ok { - return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "expected proto tx builder, got %T", txBuilder) - } - - return protoProvider.GetProtoTx(), nil -} diff --git a/x/auth/tx/service_test.go b/x/auth/tx/service_test.go index f5cccb5ae8db..e61abadd7a48 100644 --- a/x/auth/tx/service_test.go +++ b/x/auth/tx/service_test.go @@ -164,6 +164,11 @@ func TestEventRegex(t *testing.T) { event: "tx.message='something'", match: true, }, + { + name: "valid: with underscores", + event: "claim_reward.message_action='something'", + match: true, + }, { name: "valid: no quotes", event: "tx.message=something",