Add tx broadcast gRPC endpoint (#7852)
* WIP tx/broadcast grpc endpoint
* fix lint
* fix proto lint
* Update service.proto
* resolve conflicts
* update service.proto
* Update service.proto
* review changes
* proto lint
* Switch to txraw
* Add check breaking at the end
* Fix broadcast
* Send Msg on SetupSuite
* Remove proto-check-breaking
* 1 validator in test
* Add grpc server tests for broadcast
* Fix grpc server tests
* Add some changes
* Add ress comments
* Add table tests for tx service
* Add test for mode
* Add simulate tests
* Add build flag back
* Revert custom stringer for enum
* Remove stray logs
* Use /txs/{hash}
Co-authored-by: Amaury Martiny <amaury.martiny@protonmail.com>
Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
This commit is contained in:
co-authored by
Amaury Martiny
Aleksandr Bezobchuk
parent
f57828c091
commit
b6c8d5ea9f
@@ -151,7 +151,7 @@ func QueryTxRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
|
||||
rest.WriteErrorResponse(w, http.StatusNotFound, fmt.Sprintf("no transaction found with hash %s", hashHexStr))
|
||||
}
|
||||
|
||||
err = checkSignModeError(clientCtx, output, "/cosmos/tx/v1beta1/tx/{txhash}")
|
||||
err = checkSignModeError(clientCtx, output, "/cosmos/tx/v1beta1/txs/{txhash}")
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
|
||||
|
||||
@@ -345,7 +345,7 @@ func (s *IntegrationTestSuite) broadcastReq(stdTx legacytx.StdTx, mode string) (
|
||||
// testQueryIBCTx is a helper function to test querying txs which:
|
||||
// - show an error message on legacy REST endpoints
|
||||
// - succeed using gRPC
|
||||
// In practise, we call this function on IBC txs.
|
||||
// In practice, we call this function on IBC txs.
|
||||
func (s *IntegrationTestSuite) testQueryIBCTx(txRes sdk.TxResponse, cmd *cobra.Command, args []string) {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
@@ -381,7 +381,7 @@ func (s *IntegrationTestSuite) testQueryIBCTx(txRes sdk.TxResponse, cmd *cobra.C
|
||||
}
|
||||
|
||||
// try fetching the txn using gRPC req, it will fetch info since it has proto codec.
|
||||
grpcJSON, err := rest.GetRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/tx/%s", val.APIAddress, txRes.TxHash))
|
||||
grpcJSON, err := rest.GetRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, txRes.TxHash))
|
||||
s.Require().NoError(err)
|
||||
|
||||
var getTxRes txtypes.GetTxResponse
|
||||
|
||||
+13
-1
@@ -49,6 +49,10 @@ const (
|
||||
|
||||
// TxsByEvents implements the ServiceServer.TxsByEvents RPC method.
|
||||
func (s txServer) GetTxsEvent(ctx context.Context, req *txtypes.GetTxsEventRequest) (*txtypes.GetTxsEventResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
||||
}
|
||||
|
||||
page, limit, err := pagination.ParsePagination(req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -113,7 +117,7 @@ func (s txServer) GetTxsEvent(ctx context.Context, req *txtypes.GetTxsEventReque
|
||||
|
||||
// Simulate implements the ServiceServer.Simulate RPC method.
|
||||
func (s txServer) Simulate(ctx context.Context, req *txtypes.SimulateRequest) (*txtypes.SimulateResponse, error) {
|
||||
if req.Tx == nil {
|
||||
if req == nil || req.Tx == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "invalid empty tx")
|
||||
}
|
||||
|
||||
@@ -139,6 +143,10 @@ func (s txServer) Simulate(ctx context.Context, req *txtypes.SimulateRequest) (*
|
||||
|
||||
// GetTx implements the ServiceServer.GetTx RPC method.
|
||||
func (s txServer) GetTx(ctx context.Context, req *txtypes.GetTxRequest) (*txtypes.GetTxResponse, error) {
|
||||
if req == nil {
|
||||
return nil, status.Error(codes.InvalidArgument, "request cannot be nil")
|
||||
}
|
||||
|
||||
// We get hash as a hex string in the request, convert it to bytes.
|
||||
hash, err := hex.DecodeString(req.Hash)
|
||||
if err != nil {
|
||||
@@ -170,6 +178,10 @@ func (s txServer) GetTx(ctx context.Context, req *txtypes.GetTxRequest) (*txtype
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s txServer) BroadcastTx(ctx context.Context, req *txtypes.BroadcastTxRequest) (*txtypes.BroadcastTxResponse, error) {
|
||||
return client.TxServiceBroadcast(ctx, s.clientCtx, req)
|
||||
}
|
||||
|
||||
// RegisterTxService registers the tx service on the gRPC router.
|
||||
func RegisterTxService(
|
||||
qrt gogogrpc.Server,
|
||||
|
||||
+322
-127
@@ -31,6 +31,7 @@ type IntegrationTestSuite struct {
|
||||
network *network.Network
|
||||
|
||||
queryClient tx.ServiceClient
|
||||
txRes sdk.TxResponse
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) SetupSuite() {
|
||||
@@ -41,67 +42,14 @@ func (s *IntegrationTestSuite) SetupSuite() {
|
||||
|
||||
s.cfg = cfg
|
||||
s.network = network.New(s.T(), cfg)
|
||||
|
||||
s.Require().NotNil(s.network)
|
||||
|
||||
val := s.network.Validators[0]
|
||||
|
||||
_, err := s.network.WaitForHeight(1)
|
||||
s.Require().NoError(err)
|
||||
|
||||
s.queryClient = tx.NewServiceClient(s.network.Validators[0].ClientCtx)
|
||||
}
|
||||
|
||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||
s.T().Log("tearing down integration test suite")
|
||||
s.network.Cleanup()
|
||||
}
|
||||
|
||||
func (s IntegrationTestSuite) TestSimulate() {
|
||||
val := s.network.Validators[0]
|
||||
|
||||
// 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)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Convert the txBuilder to a tx.Tx.
|
||||
protoTx, err := txBuilderToProtoTx(txBuilder)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Run the simulate gRPC query.
|
||||
res, err := s.queryClient.Simulate(
|
||||
context.Background(),
|
||||
&tx.SimulateRequest{Tx: protoTx},
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
|
||||
// Check the result and gas used are correct.
|
||||
s.Require().Equal(len(res.GetResult().GetEvents()), 4) // 1 transfer, 3 messages.
|
||||
s.Require().True(res.GetGasInfo().GetGasUsed() > 0) // Gas used sometimes change, just check it's not empty.
|
||||
}
|
||||
|
||||
func (s IntegrationTestSuite) TestGetTxEvents() {
|
||||
val := s.network.Validators[0]
|
||||
s.queryClient = tx.NewServiceClient(val.ClientCtx)
|
||||
|
||||
// Create a new MsgSend tx from val to itself.
|
||||
out, err := bankcli.MsgSendExec(
|
||||
@@ -118,51 +66,161 @@ func (s IntegrationTestSuite) TestGetTxEvents() {
|
||||
fmt.Sprintf("--%s=foobar", flags.FlagMemo),
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
var txRes sdk.TxResponse
|
||||
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes))
|
||||
s.Require().Equal(uint32(0), txRes.Code)
|
||||
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &s.txRes))
|
||||
s.Require().Equal(uint32(0), s.txRes.Code)
|
||||
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
}
|
||||
|
||||
// Query the tx via gRPC empty params.
|
||||
_, err = s.queryClient.GetTxsEvent(
|
||||
context.Background(),
|
||||
&tx.GetTxsEventRequest{},
|
||||
)
|
||||
s.Require().Error(err)
|
||||
func (s *IntegrationTestSuite) TearDownSuite() {
|
||||
s.T().Log("tearing down integration test suite")
|
||||
s.network.Cleanup()
|
||||
}
|
||||
|
||||
// Query the tx via gRPC.
|
||||
grpcRes, err := s.queryClient.GetTxsEvent(
|
||||
context.Background(),
|
||||
&tx.GetTxsEventRequest{
|
||||
Events: []string{"message.action=send"},
|
||||
Pagination: &query.PageRequest{
|
||||
CountTotal: false,
|
||||
Offset: 0,
|
||||
Limit: 1,
|
||||
func (s IntegrationTestSuite) TestSimulateTx_GRPC() {
|
||||
txBuilder := s.mkTxBuilder()
|
||||
// Convert the txBuilder to a tx.Tx.
|
||||
protoTx, err := txBuilderToProtoTx(txBuilder)
|
||||
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, "invalid empty tx"},
|
||||
{"valid request", &tx.SimulateRequest{Tx: protoTx}, 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.
|
||||
s.Require().Equal(len(res.GetResult().GetEvents()), 4) // 1 transfer, 3 messages.
|
||||
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)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
req *tx.SimulateRequest
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{"empty request", &tx.SimulateRequest{}, true, "invalid empty tx"},
|
||||
{"valid request", &tx.SimulateRequest{Tx: protoTx}, false, ""},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
req, err := val.ClientCtx.JSONMarshaler.MarshalJSON(tc.req)
|
||||
s.Require().NoError(err)
|
||||
res, err := rest.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.JSONMarshaler.UnmarshalJSON(res, &result)
|
||||
s.Require().NoError(err)
|
||||
// Check the result and gas used are correct.
|
||||
s.Require().Equal(len(result.GetResult().GetEvents()), 4) // 1 transfer, 3 messages.
|
||||
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
|
||||
}{
|
||||
{
|
||||
"nil request",
|
||||
nil,
|
||||
true, "request cannot be nil",
|
||||
},
|
||||
{
|
||||
"empty request",
|
||||
&tx.GetTxsEventRequest{},
|
||||
true, "must declare at least one event to search",
|
||||
},
|
||||
{
|
||||
"request with dummy event",
|
||||
&tx.GetTxsEventRequest{Events: []string{"foobar"}},
|
||||
true, "event foobar should be of the format: {eventType}.{eventAttribute}={value}",
|
||||
},
|
||||
{
|
||||
"without pagination",
|
||||
&tx.GetTxsEventRequest{
|
||||
Events: []string{"message.action=send"},
|
||||
},
|
||||
false, "",
|
||||
},
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(len(grpcRes.Txs), 1)
|
||||
s.Require().Equal("foobar", grpcRes.Txs[0].Body.Memo)
|
||||
|
||||
// Query the tx via gRPC without pagination. This used to panic, see
|
||||
// https://github.com/cosmos/cosmos-sdk/issues/8038.
|
||||
grpcRes, err = s.queryClient.GetTxsEvent(
|
||||
context.Background(),
|
||||
&tx.GetTxsEventRequest{
|
||||
Events: []string{"message.action=send"},
|
||||
{
|
||||
"with pagination",
|
||||
&tx.GetTxsEventRequest{
|
||||
Events: []string{"message.action=send"},
|
||||
Pagination: &query.PageRequest{
|
||||
CountTotal: false,
|
||||
Offset: 0,
|
||||
Limit: 1,
|
||||
},
|
||||
},
|
||||
false, "",
|
||||
},
|
||||
)
|
||||
// TODO Once https://github.com/cosmos/cosmos-sdk/pull/8029 is merged, this
|
||||
// should not error anymore.
|
||||
s.Require().NoError(err)
|
||||
{
|
||||
"with multi events",
|
||||
&tx.GetTxsEventRequest{
|
||||
Events: []string{"message.action=send", "message.module=bank"},
|
||||
},
|
||||
false, "",
|
||||
},
|
||||
}
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
rpcTests := []struct {
|
||||
func (s IntegrationTestSuite) TestGetTxEvents_GRPCGateway() {
|
||||
val := s.network.Validators[0]
|
||||
testCases := []struct {
|
||||
name string
|
||||
url string
|
||||
expectErr bool
|
||||
expErr bool
|
||||
expErrMsg string
|
||||
}{
|
||||
{
|
||||
@@ -196,15 +254,16 @@ func (s IntegrationTestSuite) TestGetTxEvents() {
|
||||
"",
|
||||
},
|
||||
}
|
||||
for _, tc := range rpcTests {
|
||||
for _, tc := range testCases {
|
||||
s.Run(tc.name, func() {
|
||||
res, err := rest.GetRequest(tc.url)
|
||||
s.Require().NoError(err)
|
||||
if tc.expectErr {
|
||||
if tc.expErr {
|
||||
s.Require().Contains(string(res), tc.expErrMsg)
|
||||
} else {
|
||||
var result tx.GetTxsEventResponse
|
||||
val.ClientCtx.JSONMarshaler.UnmarshalJSON(res, &result)
|
||||
err = val.ClientCtx.JSONMarshaler.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)
|
||||
@@ -213,52 +272,188 @@ func (s IntegrationTestSuite) TestGetTxEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s IntegrationTestSuite) TestGetTx() {
|
||||
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, "transaction hash cannot be empty"},
|
||||
{"request with dummy hash", &tx.GetTxRequest{Hash: "deadbeef"}, true, "tx (DEADBEEF) not found"},
|
||||
{"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, "transaction hash cannot be empty",
|
||||
},
|
||||
{
|
||||
"dummy hash",
|
||||
fmt.Sprintf("%s/cosmos/tx/v1beta1/txs/%s", val.APIAddress, "deadbeef"),
|
||||
true, "tx (DEADBEEF) not found",
|
||||
},
|
||||
{
|
||||
"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 := rest.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.JSONMarshaler.UnmarshalJSON(res, &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal("foobar", result.Tx.Body.Memo)
|
||||
s.Require().NotZero(result.TxResponse.Height)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new MsgSend tx from val to itself.
|
||||
out, err := bankcli.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.BroadcastBlock),
|
||||
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.FlagMemo),
|
||||
)
|
||||
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)
|
||||
var txRes sdk.TxResponse
|
||||
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &txRes))
|
||||
s.Require().Equal(uint32(0), txRes.Code)
|
||||
|
||||
s.Require().NoError(s.network.WaitForNextBlock())
|
||||
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, block"},
|
||||
{"valid request", &tx.BroadcastTxRequest{
|
||||
Mode: tx.BroadcastMode_BROADCAST_MODE_SYNC,
|
||||
TxBytes: txBytes,
|
||||
}, false, ""},
|
||||
}
|
||||
|
||||
// Query the tx via gRPC.
|
||||
grpcRes, err := s.queryClient.GetTx(
|
||||
context.Background(),
|
||||
&tx.GetTxRequest{Hash: txRes.TxHash},
|
||||
)
|
||||
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)
|
||||
s.Require().Equal("foobar", grpcRes.Tx.Body.Memo)
|
||||
s.Require().NotZero(grpcRes.TxResponse.Height)
|
||||
|
||||
// Query the tx via grpc-gateway.
|
||||
restRes, err := rest.GetRequest(fmt.Sprintf("%s/cosmos/tx/v1beta1/tx/%s", val.APIAddress, txRes.TxHash))
|
||||
s.Require().NoError(err)
|
||||
var getTxRes tx.GetTxResponse
|
||||
s.Require().NoError(val.ClientCtx.JSONMarshaler.UnmarshalJSON(restRes, &getTxRes))
|
||||
s.Require().Equal("foobar", grpcRes.Tx.Body.Memo)
|
||||
s.Require().NotZero(grpcRes.TxResponse.Height)
|
||||
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, block"},
|
||||
{"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.JSONMarshaler.MarshalJSON(tc.req)
|
||||
s.Require().NoError(err)
|
||||
res, err := rest.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.JSONMarshaler.UnmarshalJSON(res, &result)
|
||||
s.Require().NoError(err)
|
||||
s.Require().Equal(uint32(0), result.TxResponse.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
// 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)
|
||||
s.Require().NoError(err)
|
||||
|
||||
return txBuilder
|
||||
}
|
||||
|
||||
// txBuilderToProtoTx converts a txBuilder into a proto tx.Tx.
|
||||
func txBuilderToProtoTx(txBuilder client.TxBuilder) (*tx.Tx, error) { // nolint
|
||||
intoAnyTx, ok := txBuilder.(codectypes.IntoAny)
|
||||
|
||||
Reference in New Issue
Block a user