<!-- < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < ☺ v ✰ Thanks for creating a PR! ✰ v Before smashing the submit button please review the checkboxes. v If a checkbox is n/a - please still include it but + a little note why ☺ > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > --> ## Description ref: #9183 After some more recent conversations w/ @aaronc, I decided to go back to his original proposal of setting up a subcommand for running in-process testnets. This PR splits the `simd testnet` command into two subcommands: - `simd testnet start` which starts an in-process n-node testnet - `simd testnet init-files` which sets up configuration & genesis files for an n-node testnet to be run as separate processes (one per node, most likely via Docker Compose) --- Before we can merge this PR, please make sure that all the following items have been checked off. If any of the checklist items are not applicable, please leave them but write a little note why. - [x] Targeted PR against correct branch (see [CONTRIBUTING.md](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#pr-targeting)) - [x] Linked to Github issue with discussion and accepted design OR link to spec that describes this work. - [x] Code follows the [module structure standards](https://github.com/cosmos/cosmos-sdk/blob/master/docs/building-modules/structure.md). - **n/a** - [ ] Wrote unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/master/CONTRIBUTING.md#testing) - [x] Updated relevant documentation (`docs/`) or specification (`x/<module>/spec/`) - **see #9411** - [x] Added relevant `godoc` [comments](https://blog.golang.org/godoc-documenting-go-code). - [x] Added a relevant changelog entry to the `Unreleased` section in `CHANGELOG.md` - [x] Re-reviewed `Files changed` in the Github PR explorer - [ ] Review `Codecov Report` in the comment section below once CI passes
281 lines
9.4 KiB
Go
281 lines
9.4 KiB
Go
// +build norace
|
|
|
|
package grpc_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jhump/protoreflect/grpcreflect"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/metadata"
|
|
rpb "google.golang.org/grpc/reflection/grpc_reflection_v1alpha"
|
|
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
reflectionv1 "github.com/cosmos/cosmos-sdk/client/grpc/reflection"
|
|
clienttx "github.com/cosmos/cosmos-sdk/client/tx"
|
|
reflectionv2 "github.com/cosmos/cosmos-sdk/server/grpc/reflection/v2alpha1"
|
|
"github.com/cosmos/cosmos-sdk/simapp"
|
|
"github.com/cosmos/cosmos-sdk/testutil/network"
|
|
"github.com/cosmos/cosmos-sdk/testutil/testdata"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
|
|
"github.com/cosmos/cosmos-sdk/types/tx"
|
|
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
|
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
|
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
|
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
|
)
|
|
|
|
type IntegrationTestSuite struct {
|
|
suite.Suite
|
|
|
|
app *simapp.SimApp
|
|
cfg network.Config
|
|
network *network.Network
|
|
conn *grpc.ClientConn
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) SetupSuite() {
|
|
s.T().Log("setting up integration test suite")
|
|
s.app = simapp.Setup(false)
|
|
s.cfg = network.DefaultConfig()
|
|
s.cfg.NumValidators = 1
|
|
|
|
var err error
|
|
s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg)
|
|
s.Require().NoError(err)
|
|
|
|
_, err = s.network.WaitForHeight(2)
|
|
s.Require().NoError(err)
|
|
|
|
val0 := s.network.Validators[0]
|
|
s.conn, err = grpc.Dial(
|
|
val0.AppConfig.GRPC.Address,
|
|
grpc.WithInsecure(), // Or else we get "no transport security set"
|
|
)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
s.T().Log("tearing down integration test suite")
|
|
s.conn.Close()
|
|
s.network.Cleanup()
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGRPCServer_TestService() {
|
|
// gRPC query to test service should work
|
|
testClient := testdata.NewQueryClient(s.conn)
|
|
testRes, err := testClient.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"})
|
|
s.Require().NoError(err)
|
|
s.Require().Equal("hello", testRes.Message)
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGRPCServer_BankBalance() {
|
|
val0 := s.network.Validators[0]
|
|
|
|
// gRPC query to bank service should work
|
|
denom := fmt.Sprintf("%stoken", val0.Moniker)
|
|
bankClient := banktypes.NewQueryClient(s.conn)
|
|
var header metadata.MD
|
|
bankRes, err := bankClient.Balance(
|
|
context.Background(),
|
|
&banktypes.QueryBalanceRequest{Address: val0.Address.String(), Denom: denom},
|
|
grpc.Header(&header), // Also fetch grpc header
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(
|
|
sdk.NewCoin(denom, s.network.Config.AccountTokens),
|
|
*bankRes.GetBalance(),
|
|
)
|
|
blockHeight := header.Get(grpctypes.GRPCBlockHeightHeader)
|
|
s.Require().NotEmpty(blockHeight[0]) // Should contain the block height
|
|
|
|
// Request metadata should work
|
|
bankRes, err = bankClient.Balance(
|
|
metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, "1"), // Add metadata to request
|
|
&banktypes.QueryBalanceRequest{Address: val0.Address.String(), Denom: denom},
|
|
grpc.Header(&header),
|
|
)
|
|
s.Require().NoError(err)
|
|
blockHeight = header.Get(grpctypes.GRPCBlockHeightHeader)
|
|
s.Require().NotEmpty(blockHeight[0]) // blockHeight is []string, first element is block height.
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGRPCServer_Reflection() {
|
|
// Test server reflection
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
stub := rpb.NewServerReflectionClient(s.conn)
|
|
// NOTE(fdymylja): we use grpcreflect because it solves imports too
|
|
// so that we can always assert that given a reflection server it is
|
|
// possible to fully query all the methods, without having any context
|
|
// on the proto registry
|
|
rc := grpcreflect.NewClient(ctx, stub)
|
|
|
|
services, err := rc.ListServices()
|
|
s.Require().NoError(err)
|
|
s.Require().Greater(len(services), 0)
|
|
|
|
for _, svc := range services {
|
|
file, err := rc.FileContainingSymbol(svc)
|
|
s.Require().NoError(err)
|
|
sd := file.FindSymbol(svc)
|
|
s.Require().NotNil(sd)
|
|
}
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGRPCServer_InterfaceReflection() {
|
|
// this tests the application reflection capabilities and compatibility between v1 and v2
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
clientV2 := reflectionv2.NewReflectionServiceClient(s.conn)
|
|
clientV1 := reflectionv1.NewReflectionServiceClient(s.conn)
|
|
codecDesc, err := clientV2.GetCodecDescriptor(ctx, nil)
|
|
s.Require().NoError(err)
|
|
|
|
interfaces, err := clientV1.ListAllInterfaces(ctx, nil)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(len(codecDesc.Codec.Interfaces), len(interfaces.InterfaceNames))
|
|
s.Require().Equal(len(s.cfg.InterfaceRegistry.ListAllInterfaces()), len(codecDesc.Codec.Interfaces))
|
|
|
|
for _, iface := range interfaces.InterfaceNames {
|
|
impls, err := clientV1.ListImplementations(ctx, &reflectionv1.ListImplementationsRequest{InterfaceName: iface})
|
|
s.Require().NoError(err)
|
|
|
|
s.Require().ElementsMatch(impls.ImplementationMessageNames, s.cfg.InterfaceRegistry.ListImplementations(iface))
|
|
}
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGRPCServer_GetTxsEvent() {
|
|
// Query the tx via gRPC without pagination. This used to panic, see
|
|
// https://github.com/cosmos/cosmos-sdk/issues/8038.
|
|
txServiceClient := txtypes.NewServiceClient(s.conn)
|
|
_, err := txServiceClient.GetTxsEvent(
|
|
context.Background(),
|
|
&tx.GetTxsEventRequest{
|
|
Events: []string{"message.action='send'"},
|
|
},
|
|
)
|
|
s.Require().NoError(err)
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGRPCServer_BroadcastTx() {
|
|
val0 := s.network.Validators[0]
|
|
|
|
txBuilder := s.mkTxBuilder()
|
|
|
|
txBytes, err := val0.ClientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
|
|
s.Require().NoError(err)
|
|
|
|
// Broadcast the tx via gRPC.
|
|
queryClient := txtypes.NewServiceClient(s.conn)
|
|
|
|
grpcRes, err := queryClient.BroadcastTx(
|
|
context.Background(),
|
|
&txtypes.BroadcastTxRequest{
|
|
Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
|
|
TxBytes: txBytes,
|
|
},
|
|
)
|
|
s.Require().NoError(err)
|
|
s.Require().Equal(uint32(0), grpcRes.TxResponse.Code)
|
|
}
|
|
|
|
// Test and enforce that we upfront reject any connections to baseapp containing
|
|
// invalid initial x-cosmos-block-height that aren't positive and in the range [0, max(int64)]
|
|
// See issue https://github.com/cosmos/cosmos-sdk/issues/7662.
|
|
func (s *IntegrationTestSuite) TestGRPCServerInvalidHeaderHeights() {
|
|
t := s.T()
|
|
|
|
// We should reject connections with invalid block heights off the bat.
|
|
invalidHeightStrs := []struct {
|
|
value string
|
|
wantErr string
|
|
}{
|
|
{"-1", "\"x-cosmos-block-height\" must be >= 0"},
|
|
{"9223372036854775808", "value out of range"}, // > max(int64) by 1
|
|
{"-10", "\"x-cosmos-block-height\" must be >= 0"},
|
|
{"18446744073709551615", "value out of range"}, // max uint64, which is > max(int64)
|
|
{"-9223372036854775809", "value out of range"}, // Out of the range of for negative int64
|
|
}
|
|
for _, tt := range invalidHeightStrs {
|
|
t.Run(tt.value, func(t *testing.T) {
|
|
testClient := testdata.NewQueryClient(s.conn)
|
|
ctx := metadata.AppendToOutgoingContext(context.Background(), grpctypes.GRPCBlockHeightHeader, tt.value)
|
|
testRes, err := testClient.Echo(ctx, &testdata.EchoRequest{Message: "hello"})
|
|
require.Error(t, err)
|
|
require.Nil(t, testRes)
|
|
require.Contains(t, err.Error(), tt.wantErr)
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGRPCUnpacker - tests the grpc endpoint for Validator and using the interface registry unpack and extract the
|
|
// ConsAddr. (ref: https://github.com/cosmos/cosmos-sdk/issues/8045)
|
|
func (s *IntegrationTestSuite) TestGRPCUnpacker() {
|
|
ir := s.app.InterfaceRegistry()
|
|
queryClient := stakingtypes.NewQueryClient(s.conn)
|
|
validator, err := queryClient.Validator(context.Background(),
|
|
&stakingtypes.QueryValidatorRequest{ValidatorAddr: s.network.Validators[0].ValAddress.String()})
|
|
require.NoError(s.T(), err)
|
|
|
|
// no unpacked interfaces yet, so ConsAddr will be nil
|
|
nilAddr, err := validator.Validator.GetConsAddr()
|
|
require.Error(s.T(), err)
|
|
require.Nil(s.T(), nilAddr)
|
|
|
|
// unpack the interfaces and now ConsAddr is not nil
|
|
err = validator.Validator.UnpackInterfaces(ir)
|
|
require.NoError(s.T(), err)
|
|
addr, err := validator.Validator.GetConsAddr()
|
|
require.NotNil(s.T(), addr)
|
|
require.NoError(s.T(), err)
|
|
}
|
|
|
|
// mkTxBuilder creates a TxBuilder containing a signed tx from validator 0.
|
|
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
|
|
}
|
|
|
|
func TestIntegrationTestSuite(t *testing.T) {
|
|
suite.Run(t, new(IntegrationTestSuite))
|
|
}
|