github.com/Finschia/finschia-sdk@v0.48.1/server/grpc/server_test.go (about)

     1  //go:build norace
     2  // +build norace
     3  
     4  package grpc_test
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/jhump/protoreflect/grpcreflect"
    13  
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/stretchr/testify/suite"
    16  
    17  	"google.golang.org/grpc"
    18  	"google.golang.org/grpc/metadata"
    19  	rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
    20  
    21  	"github.com/Finschia/finschia-sdk/client"
    22  	reflectionv1 "github.com/Finschia/finschia-sdk/client/grpc/reflection"
    23  	clienttx "github.com/Finschia/finschia-sdk/client/tx"
    24  	reflectionv2 "github.com/Finschia/finschia-sdk/server/grpc/reflection/v2"
    25  	"github.com/Finschia/finschia-sdk/simapp"
    26  	"github.com/Finschia/finschia-sdk/testutil/network"
    27  	"github.com/Finschia/finschia-sdk/testutil/testdata"
    28  	sdk "github.com/Finschia/finschia-sdk/types"
    29  	grpctypes "github.com/Finschia/finschia-sdk/types/grpc"
    30  	"github.com/Finschia/finschia-sdk/types/tx"
    31  	txtypes "github.com/Finschia/finschia-sdk/types/tx"
    32  	"github.com/Finschia/finschia-sdk/types/tx/signing"
    33  	authclient "github.com/Finschia/finschia-sdk/x/auth/client"
    34  	authtypes "github.com/Finschia/finschia-sdk/x/auth/types"
    35  	banktypes "github.com/Finschia/finschia-sdk/x/bank/types"
    36  	stakingtypes "github.com/Finschia/finschia-sdk/x/staking/types"
    37  )
    38  
    39  type IntegrationTestSuite struct {
    40  	suite.Suite
    41  
    42  	app     *simapp.SimApp
    43  	cfg     network.Config
    44  	network *network.Network
    45  	conn    *grpc.ClientConn
    46  }
    47  
    48  func (s *IntegrationTestSuite) SetupSuite() {
    49  	s.T().Log("setting up integration test suite")
    50  	s.app = simapp.Setup(false)
    51  	s.cfg = network.DefaultConfig()
    52  	s.cfg.NumValidators = 1
    53  	s.network = network.New(s.T(), s.cfg)
    54  	s.Require().NotNil(s.network)
    55  
    56  	_, err := s.network.WaitForHeight(2)
    57  	s.Require().NoError(err)
    58  
    59  	val0 := s.network.Validators[0]
    60  	s.conn, err = grpc.Dial(
    61  		val0.AppConfig.GRPC.Address,
    62  		grpc.WithInsecure(), // Or else we get "no transport security set"
    63  	)
    64  	s.Require().NoError(err)
    65  }
    66  
    67  func (s *IntegrationTestSuite) TearDownSuite() {
    68  	s.T().Log("tearing down integration test suite")
    69  	s.conn.Close()
    70  	s.network.Cleanup()
    71  }
    72  
    73  func (s *IntegrationTestSuite) TestGRPCServer_TestService() {
    74  	// gRPC query to test service should work
    75  	testClient := testdata.NewQueryClient(s.conn)
    76  	testRes, err := testClient.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"})
    77  	s.Require().NoError(err)
    78  	s.Require().Equal("hello", testRes.Message)
    79  }
    80  
    81  func (s *IntegrationTestSuite) TestGRPCServer_BankBalance() {
    82  	val0 := s.network.Validators[0]
    83  
    84  	// gRPC query to bank service should work
    85  	denom := fmt.Sprintf("%stoken", val0.Moniker)
    86  	bankClient := banktypes.NewQueryClient(s.conn)
    87  	var header metadata.MD
    88  	bankRes, err := bankClient.Balance(
    89  		context.Background(),
    90  		&banktypes.QueryBalanceRequest{Address: val0.Address.String(), Denom: denom},
    91  		grpc.Header(&header), // Also fetch grpc header
    92  	)
    93  	s.Require().NoError(err)
    94  	s.Require().Equal(
    95  		sdk.NewCoin(denom, s.network.Config.AccountTokens),
    96  		*bankRes.GetBalance(),
    97  	)
    98  	blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader)
    99  	s.Require().NotEmpty(blockHeight[0]) // Should contain the block height
   100  
   101  	// Request metadata should work
   102  	bankRes, err = bankClient.Balance(
   103  		metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, "1"), // Add metadata to request
   104  		&banktypes.QueryBalanceRequest{Address: val0.Address.String(), Denom: denom},
   105  		grpc.Header(&header),
   106  	)
   107  	s.Require().NoError(err)
   108  	blockHeight = header.Get(grpctypes.GRPCBlockHeightHeader)
   109  	s.Require().Equal([]string{"1"}, blockHeight)
   110  }
   111  
   112  func (s *IntegrationTestSuite) TestGRPCServer_Reflection() {
   113  	// Test server reflection
   114  	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
   115  	defer cancel()
   116  	stub := rpb.NewServerReflectionClient(s.conn)
   117  	// NOTE(fdymylja): we use grpcreflect because it solves imports too
   118  	// so that we can always assert that given a reflection server it is
   119  	// possible to fully query all the methods, without having any context
   120  	// on the proto registry
   121  	rc := grpcreflect.NewClient(ctx, stub)
   122  
   123  	services, err := rc.ListServices()
   124  	s.Require().NoError(err)
   125  	s.Require().Greater(len(services), 0)
   126  
   127  	for _, svc := range services {
   128  		file, err := rc.FileContainingSymbol(svc)
   129  		s.Require().NoError(err)
   130  		sd := file.FindSymbol(svc)
   131  		s.Require().NotNil(sd)
   132  	}
   133  }
   134  
   135  func (s *IntegrationTestSuite) TestGRPCServer_InterfaceReflection() {
   136  	// this tests the application reflection capabilities and compatibility between v1 and v2
   137  	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
   138  	defer cancel()
   139  
   140  	clientV2 := reflectionv2.NewReflectionServiceClient(s.conn)
   141  	clientV1 := reflectionv1.NewReflectionServiceClient(s.conn)
   142  	codecDesc, err := clientV2.GetCodecDescriptor(ctx, nil)
   143  	s.Require().NoError(err)
   144  
   145  	interfaces, err := clientV1.ListAllInterfaces(ctx, nil)
   146  	s.Require().NoError(err)
   147  	s.Require().Equal(len(codecDesc.Codec.Interfaces), len(interfaces.InterfaceNames))
   148  	s.Require().Equal(len(s.cfg.InterfaceRegistry.ListAllInterfaces()), len(codecDesc.Codec.Interfaces))
   149  
   150  	for _, iface := range interfaces.InterfaceNames {
   151  		impls, err := clientV1.ListImplementations(ctx, &reflectionv1.ListImplementationsRequest{InterfaceName: iface})
   152  		s.Require().NoError(err)
   153  
   154  		s.Require().ElementsMatch(impls.ImplementationMessageNames, s.cfg.InterfaceRegistry.ListImplementations(iface))
   155  	}
   156  }
   157  
   158  func (s *IntegrationTestSuite) TestGRPCServer_GetTxsEvent() {
   159  	// Query the tx via gRPC without pagination. This used to panic, see
   160  	// https://github.com/cosmos/cosmos-sdk/issues/8038.
   161  	txServiceClient := txtypes.NewServiceClient(s.conn)
   162  	_, err := txServiceClient.GetTxsEvent(
   163  		context.Background(),
   164  		&tx.GetTxsEventRequest{
   165  			Events: []string{"message.action='send'"},
   166  		},
   167  	)
   168  	s.Require().NoError(err)
   169  }
   170  
   171  func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() {
   172  	val0 := s.network.Validators[0]
   173  
   174  	txBuilder := s.mkTxBuilder()
   175  
   176  	txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
   177  	s.Require().NoError(err)
   178  
   179  	// Broadcast the tx via gRPC.
   180  	queryClient := txtypes.NewServiceClient(s.conn)
   181  
   182  	grpcRes, err := queryClient.BroadcastTx(
   183  		context.Background(),
   184  		&txtypes.BroadcastTxRequest{
   185  			Mode:    txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
   186  			TxBytes: txBytes,
   187  		},
   188  	)
   189  	s.Require().NoError(err)
   190  	s.Require().Equal(uint32(0), grpcRes.TxResponse.Code)
   191  }
   192  
   193  // Test and enforce that we upfront reject any connections to baseapp containing
   194  // invalid initial x-cosmos-block-height that aren't positive  and in the range [0, max(int64)]
   195  // See issue https://github.com/cosmos/cosmos-sdk/issues/7662.
   196  func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() {
   197  	t := s.T()
   198  
   199  	// We should reject connections with invalid block heights off the bat.
   200  	invalidHeightStrs := []struct {
   201  		value   string
   202  		wantErr string
   203  	}{
   204  		{"-1", "height < 0"},
   205  		{"9223372036854775808", "value out of range"}, // > max(int64) by 1
   206  		{"-10", "height < 0"},
   207  		{"18446744073709551615", "value out of range"}, // max uint64, which is  > max(int64)
   208  		{"-9223372036854775809", "value out of range"}, // Out of the range of for negative int64
   209  	}
   210  	for _, tt := range invalidHeightStrs {
   211  		t.Run(tt.value, func(t *testing.T) {
   212  			testClient := testdata.NewQueryClient(s.conn)
   213  			ctx := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, tt.value)
   214  			testRes, err := testClient.Echo(ctx, &testdata.EchoRequest{Message: "hello"})
   215  			require.Error(t, err)
   216  			require.Nil(t, testRes)
   217  			require.Contains(t, err.Error(), tt.wantErr)
   218  		})
   219  	}
   220  }
   221  
   222  // TestGRPCUnpacker - tests the grpc endpoint for Validator and using the interface registry unpack and extract the
   223  // ConsAddr. (ref: https://github.com/cosmos/cosmos-sdk/issues/8045)
   224  func (s *IntegrationTestSuite) TestGRPCUnpacker() {
   225  	ir := s.app.InterfaceRegistry()
   226  	queryClient := stakingtypes.NewQueryClient(s.conn)
   227  	validator, err := queryClient.Validator(context.Background(),
   228  		&stakingtypes.QueryValidatorRequest{ValidatorAddr: s.network.Validators[0].ValAddress.String()})
   229  	require.NoError(s.T(), err)
   230  
   231  	// no unpacked interfaces yet, so ConsAddr will be nil
   232  	nilAddr, err := validator.Validator.GetConsAddr()
   233  	require.Error(s.T(), err)
   234  	require.True(s.T(), nilAddr.Empty())
   235  
   236  	// unpack the interfaces and now ConsAddr is not nil
   237  	err = validator.Validator.UnpackInterfaces(ir)
   238  	require.NoError(s.T(), err)
   239  	addr, err := validator.Validator.GetConsAddr()
   240  	require.False(s.T(), addr.Empty())
   241  	require.NoError(s.T(), err)
   242  }
   243  
   244  // mkTxBuilder creates a TxBuilder containing a signed tx from validator 0.
   245  func (s IntegrationTestSuite) mkTxBuilder() client.TxBuilder {
   246  	val := s.network.Validators[0]
   247  	s.Require().NoError(s.network.WaitForNextBlock())
   248  
   249  	// prepare txBuilder with msg
   250  	txBuilder := val.ClientCtx.TxConfig.NewTxBuilder()
   251  	feeAmount := sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)}
   252  	gasLimit := testdata.NewTestGasLimit()
   253  	s.Require().NoError(
   254  		txBuilder.SetMsgs(&banktypes.MsgSend{
   255  			FromAddress: val.Address.String(),
   256  			ToAddress:   val.Address.String(),
   257  			Amount:      sdk.Coins{sdk.NewInt64Coin(s.cfg.BondDenom, 10)},
   258  		}),
   259  	)
   260  	txBuilder.SetFeeAmount(feeAmount)
   261  	txBuilder.SetGasLimit(gasLimit)
   262  	txBuilder.SetMemo("foobar")
   263  
   264  	// setup txFactory
   265  	txFactory := clienttx.Factory{}.
   266  		WithChainID(val.ClientCtx.ChainID).
   267  		WithKeybase(val.ClientCtx.Keyring).
   268  		WithTxConfig(val.ClientCtx.TxConfig).
   269  		WithSignMode(signing.SignMode_SIGN_MODE_DIRECT)
   270  
   271  	// Sign Tx.
   272  	err := authclient.SignTx(txFactory, val.ClientCtx, val.Moniker, txBuilder, false, true)
   273  	s.Require().NoError(err)
   274  
   275  	return txBuilder
   276  }
   277  
   278  func (s *IntegrationTestSuite) TestGRPCCheckStateHeader() {
   279  	val0 := s.network.Validators[0]
   280  	authClient := authtypes.NewQueryClient(s.conn)
   281  	initSeq := uint64(1)
   282  	AfterCheckStateSeq := uint64(2)
   283  
   284  	header := make(metadata.MD)
   285  	res, err := authClient.Account(
   286  		context.Background(),
   287  		&authtypes.QueryAccountRequest{Address: val0.Address.String()},
   288  		grpc.Header(&header), // Also fetch grpc header
   289  	)
   290  	s.Require().NoError(err)
   291  	var accRes authtypes.AccountI
   292  	err = s.app.InterfaceRegistry().UnpackAny(res.Account, &accRes)
   293  	s.Require().NoError(err)
   294  	s.Require().Equal(
   295  		initSeq,
   296  		accRes.GetSequence(),
   297  	)
   298  	blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader)
   299  	s.Require().NotEmpty(blockHeight[0]) // Should contain the block height
   300  
   301  	// Broadcast tx to verify gRPC CheckStateHeader
   302  	txBuilder := s.mkTxBuilder()
   303  	txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
   304  	s.Require().NoError(err)
   305  	queryClient := txtypes.NewServiceClient(s.conn)
   306  
   307  	grpcRes, _ := queryClient.BroadcastTx(
   308  		context.Background(),
   309  		&txtypes.BroadcastTxRequest{
   310  			Mode:    txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
   311  			TxBytes: txBytes,
   312  		},
   313  	)
   314  	s.Require().Equal(uint32(0), grpcRes.TxResponse.Code)
   315  
   316  	// In order for the block to be mined, even a single node requires at least 1~2 seconds, so the sequence number is not yet increased if we query immediately.
   317  	// So we can validate our CheckState querying logic without `WaitForHeight`
   318  	ctxWithCheckState := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCCheckStateHeader, "on")
   319  	res, err = authClient.Account(
   320  		ctxWithCheckState,
   321  		&authtypes.QueryAccountRequest{Address: val0.Address.String()},
   322  	)
   323  	s.Require().NoError(err)
   324  	err = s.app.InterfaceRegistry().UnpackAny(res.Account, &accRes)
   325  	s.Require().NoError(err)
   326  	s.Require().Equal(
   327  		AfterCheckStateSeq,
   328  		accRes.GetSequence(),
   329  	)
   330  
   331  	res, _ = authClient.Account(
   332  		context.Background(),
   333  		&authtypes.QueryAccountRequest{Address: val0.Address.String()},
   334  	)
   335  	_ = s.app.InterfaceRegistry().UnpackAny(res.Account, &accRes)
   336  	s.Require().Equal(initSeq, accRes.GetSequence())
   337  
   338  	// Wrong header value case. It is deliberately run last to avoid interfering with earlier sequence tests.
   339  	ctxWithCheckState = metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCCheckStateHeader, "wrong")
   340  	_, err = authClient.Account(
   341  		ctxWithCheckState,
   342  		&authtypes.QueryAccountRequest{Address: val0.Address.String()},
   343  	)
   344  	s.Require().ErrorContains(err, "invalid checkState header \"x-lbm-checkstate\"")
   345  }
   346  
   347  func TestIntegrationTestSuite(t *testing.T) {
   348  	suite.Run(t, new(IntegrationTestSuite))
   349  }