github.com/cosmos/cosmos-sdk@v0.50.10/server/grpc/server_test.go (about)

     1  package grpc_test
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/jhump/protoreflect/grpcreflect"
    10  	"github.com/stretchr/testify/require"
    11  	"github.com/stretchr/testify/suite"
    12  	"google.golang.org/grpc"
    13  	"google.golang.org/grpc/metadata"
    14  
    15  	"github.com/cosmos/cosmos-sdk/client"
    16  	reflectionv1 "github.com/cosmos/cosmos-sdk/client/grpc/reflection"
    17  	clienttx "github.com/cosmos/cosmos-sdk/client/tx"
    18  	"github.com/cosmos/cosmos-sdk/codec"
    19  	reflectionv2 "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1"
    20  	"github.com/cosmos/cosmos-sdk/testutil/network"
    21  	"github.com/cosmos/cosmos-sdk/testutil/testdata"
    22  	sdk "github.com/cosmos/cosmos-sdk/types"
    23  	grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
    24  	txtypes "github.com/cosmos/cosmos-sdk/types/tx"
    25  	"github.com/cosmos/cosmos-sdk/types/tx/signing"
    26  	authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
    27  	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
    28  	stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
    29  )
    30  
    31  type IntegrationTestSuite struct {
    32  	suite.Suite
    33  
    34  	cfg     network.Config
    35  	network *network.Network
    36  	conn    *grpc.ClientConn
    37  }
    38  
    39  func (s *IntegrationTestSuite) SetupSuite() {
    40  	var err error
    41  	s.T().Log("setting up integration test suite")
    42  
    43  	s.cfg, err = network.DefaultConfigWithAppConfig(network.MinimumAppConfig())
    44  	s.NoError(err)
    45  	s.cfg.NumValidators = 1
    46  
    47  	s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg)
    48  	s.Require().NoError(err)
    49  
    50  	_, err = s.network.WaitForHeight(2)
    51  	s.Require().NoError(err)
    52  
    53  	val0 := s.network.Validators[0]
    54  	s.conn, err = grpc.Dial( //nolint:staticcheck // ignore this line for this linter
    55  		val0.AppConfig.GRPC.Address,
    56  		grpc.WithInsecure(), //nolint:staticcheck // ignore SA1019, we don't need to use a secure connection for tests
    57  		grpc.WithDefaultCallOptions(grpc.ForceCodec(codec.NewProtoCodec(s.cfg.InterfaceRegistry).GRPCCodec())),
    58  	)
    59  	s.Require().NoError(err)
    60  }
    61  
    62  func (s *IntegrationTestSuite) TearDownSuite() {
    63  	s.T().Log("tearing down integration test suite")
    64  	s.conn.Close()
    65  	s.network.Cleanup()
    66  }
    67  
    68  func (s *IntegrationTestSuite) TestGRPCServer_TestService() {
    69  	// gRPC query to test service should work
    70  	testClient := testdata.NewQueryClient(s.conn)
    71  	testRes, err := testClient.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"})
    72  	s.Require().NoError(err)
    73  	s.Require().Equal("hello", testRes.Message)
    74  }
    75  
    76  func (s *IntegrationTestSuite) TestGRPCServer_BankBalance() {
    77  	val0 := s.network.Validators[0]
    78  
    79  	// gRPC query to bank service should work
    80  	denom := fmt.Sprintf("%stoken", val0.Moniker)
    81  	bankClient := banktypes.NewQueryClient(s.conn)
    82  	var header metadata.MD
    83  	bankRes, err := bankClient.Balance(
    84  		context.Background(),
    85  		&banktypes.QueryBalanceRequest{Address: val0.Address.String(), Denom: denom},
    86  		grpc.Header(&header), // Also fetch grpc header
    87  	)
    88  	s.Require().NoError(err)
    89  	s.Require().Equal(
    90  		sdk.NewCoin(denom, s.network.Config.AccountTokens),
    91  		*bankRes.GetBalance(),
    92  	)
    93  	blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader)
    94  	s.Require().NotEmpty(blockHeight[0]) // Should contain the block height
    95  
    96  	// Request metadata should work
    97  	_, err = bankClient.Balance(
    98  		metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, "1"), // Add metadata to request
    99  		&banktypes.QueryBalanceRequest{Address: val0.Address.String(), Denom: denom},
   100  		grpc.Header(&header),
   101  	)
   102  	s.Require().NoError(err)
   103  	blockHeight = header.Get(grpctypes.GRPCBlockHeightHeader)
   104  	s.Require().Equal([]string{"1"}, blockHeight)
   105  }
   106  
   107  func (s *IntegrationTestSuite) TestGRPCServer_Reflection() {
   108  	// Test server reflection
   109  	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
   110  	defer cancel()
   111  	// NOTE(fdymylja): we use grpcreflect because it solves imports too
   112  	// so that we can always assert that given a reflection server it is
   113  	// possible to fully query all the methods, without having any context
   114  	// on the proto registry
   115  	rc := grpcreflect.NewClientAuto(ctx, s.conn)
   116  
   117  	services, err := rc.ListServices()
   118  	s.Require().NoError(err)
   119  	s.Require().Greater(len(services), 0)
   120  
   121  	for _, svc := range services {
   122  		file, err := rc.FileContainingSymbol(svc)
   123  		s.Require().NoError(err)
   124  		sd := file.FindSymbol(svc)
   125  		s.Require().NotNil(sd)
   126  	}
   127  }
   128  
   129  func (s *IntegrationTestSuite) TestGRPCServer_InterfaceReflection() {
   130  	// this tests the application reflection capabilities and compatibility between v1 and v2
   131  	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
   132  	defer cancel()
   133  
   134  	clientV2 := reflectionv2.NewReflectionServiceClient(s.conn)
   135  	clientV1 := reflectionv1.NewReflectionServiceClient(s.conn)
   136  	codecDesc, err := clientV2.GetCodecDescriptor(ctx, nil)
   137  	s.Require().NoError(err)
   138  
   139  	interfaces, err := clientV1.ListAllInterfaces(ctx, nil)
   140  	s.Require().NoError(err)
   141  	s.Require().Equal(len(codecDesc.Codec.Interfaces), len(interfaces.InterfaceNames))
   142  	s.Require().Equal(len(s.cfg.InterfaceRegistry.ListAllInterfaces()), len(codecDesc.Codec.Interfaces))
   143  
   144  	for _, iface := range interfaces.InterfaceNames {
   145  		impls, err := clientV1.ListImplementations(ctx, &reflectionv1.ListImplementationsRequest{InterfaceName: iface})
   146  		s.Require().NoError(err)
   147  
   148  		s.Require().ElementsMatch(impls.ImplementationMessageNames, s.cfg.InterfaceRegistry.ListImplementations(iface))
   149  	}
   150  }
   151  
   152  func (s *IntegrationTestSuite) TestGRPCServer_GetTxsEvent() {
   153  	// Query the tx via gRPC without pagination. This used to panic, see
   154  	// https://github.com/cosmos/cosmos-sdk/issues/8038.
   155  	txServiceClient := txtypes.NewServiceClient(s.conn)
   156  	_, err := txServiceClient.GetTxsEvent(
   157  		context.Background(),
   158  		&txtypes.GetTxsEventRequest{
   159  			Query: "message.action='send'",
   160  		},
   161  	)
   162  	s.Require().NoError(err)
   163  }
   164  
   165  func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() {
   166  	val0 := s.network.Validators[0]
   167  
   168  	txBuilder := s.mkTxBuilder()
   169  
   170  	txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
   171  	s.Require().NoError(err)
   172  
   173  	// Broadcast the tx via gRPC.
   174  	queryClient := txtypes.NewServiceClient(s.conn)
   175  
   176  	grpcRes, err := queryClient.BroadcastTx(
   177  		context.Background(),
   178  		&txtypes.BroadcastTxRequest{
   179  			Mode:    txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
   180  			TxBytes: txBytes,
   181  		},
   182  	)
   183  	s.Require().NoError(err)
   184  	s.Require().Equal(uint32(0), grpcRes.TxResponse.Code)
   185  }
   186  
   187  // Test and enforce that we upfront reject any connections to baseapp containing
   188  // invalid initial x-cosmos-block-height that aren't positive  and in the range [0, max(int64)]
   189  // See issue https://github.com/cosmos/cosmos-sdk/issues/7662.
   190  func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() {
   191  	t := s.T()
   192  
   193  	// We should reject connections with invalid block heights off the bat.
   194  	invalidHeightStrs := []struct {
   195  		value   string
   196  		wantErr string
   197  	}{
   198  		{"-1", "height < 0"},
   199  		{"9223372036854775808", "value out of range"}, // > max(int64) by 1
   200  		{"-10", "height < 0"},
   201  		{"18446744073709551615", "value out of range"}, // max uint64, which is  > max(int64)
   202  		{"-9223372036854775809", "value out of range"}, // Out of the range of for negative int64
   203  	}
   204  	for _, tt := range invalidHeightStrs {
   205  		t.Run(tt.value, func(t *testing.T) {
   206  			testClient := testdata.NewQueryClient(s.conn)
   207  			ctx := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, tt.value)
   208  			testRes, err := testClient.Echo(ctx, &testdata.EchoRequest{Message: "hello"})
   209  			require.Error(t, err)
   210  			require.Nil(t, testRes)
   211  			require.Contains(t, err.Error(), tt.wantErr)
   212  		})
   213  	}
   214  }
   215  
   216  // TestGRPCUnpacker - tests the grpc endpoint for Validator and using the interface registry unpack and extract the
   217  // ConsAddr. (ref: https://github.com/cosmos/cosmos-sdk/issues/8045)
   218  func (s *IntegrationTestSuite) TestGRPCUnpacker() {
   219  	ir := s.cfg.InterfaceRegistry
   220  	queryClient := stakingtypes.NewQueryClient(s.conn)
   221  	validator, err := queryClient.Validator(context.Background(),
   222  		&stakingtypes.QueryValidatorRequest{ValidatorAddr: s.network.Validators[0].ValAddress.String()})
   223  	require.NoError(s.T(), err)
   224  
   225  	// no unpacked interfaces yet, so ConsAddr will be nil
   226  	nilAddr, err := validator.Validator.GetConsAddr()
   227  	require.Error(s.T(), err)
   228  	require.Nil(s.T(), nilAddr)
   229  
   230  	// unpack the interfaces and now ConsAddr is not nil
   231  	err = validator.Validator.UnpackInterfaces(ir)
   232  	require.NoError(s.T(), err)
   233  	addr, err := validator.Validator.GetConsAddr()
   234  	require.NotNil(s.T(), addr)
   235  	require.NoError(s.T(), err)
   236  }
   237  
   238  // mkTxBuilder creates a TxBuilder containing a signed tx from validator 0.
   239  func (s *IntegrationTestSuite) mkTxBuilder() client.TxBuilder {
   240  	val := s.network.Validators[0]
   241  	s.Require().NoError(s.network.WaitForNextBlock())
   242  
   243  	// prepare txBuilder with msg
   244  	txBuilder := val.ClientCtx.TxConfig.NewTxBuilder()
   245  	feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}
   246  	gasLimit := testdata.NewTestGasLimit()
   247  	s.Require().NoError(
   248  		txBuilder.SetMsgs(&banktypes.MsgSend{
   249  			FromAddress: val.Address.String(),
   250  			ToAddress:   val.Address.String(),
   251  			Amount:      sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)},
   252  		}),
   253  	)
   254  	txBuilder.SetFeeAmount(feeAmount)
   255  	txBuilder.SetGasLimit(gasLimit)
   256  	txBuilder.SetMemo("foobar")
   257  
   258  	// setup txFactory
   259  	txFactory := clienttx.Factory{}.
   260  		WithChainID(val.ClientCtx.ChainID).
   261  		WithKeybase(val.ClientCtx.Keyring).
   262  		WithTxConfig(val.ClientCtx.TxConfig).
   263  		WithSignMode(signing.SignMode_SIGN_MODE_DIRECT)
   264  
   265  	// Sign Tx.
   266  	err := authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, false, true)
   267  	s.Require().NoError(err)
   268  
   269  	return txBuilder
   270  }
   271  
   272  func TestIntegrationTestSuite(t *testing.T) {
   273  	suite.Run(t, new(IntegrationTestSuite))
   274  }