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 }