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 (