333 lines
9.7 KiB
Go
333 lines
9.7 KiB
Go
package client_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
abci "github.com/cometbft/cometbft/abci/types"
|
|
cmtjson "github.com/cometbft/cometbft/libs/json"
|
|
dbm "github.com/cosmos/cosmos-db"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/stretchr/testify/suite"
|
|
"google.golang.org/grpc"
|
|
"google.golang.org/grpc/credentials/insecure"
|
|
"google.golang.org/grpc/metadata"
|
|
|
|
"cosmossdk.io/depinject"
|
|
"cosmossdk.io/log"
|
|
"cosmossdk.io/math"
|
|
|
|
"github.com/cosmos/cosmos-sdk/baseapp"
|
|
"github.com/cosmos/cosmos-sdk/client"
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
|
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
|
|
"github.com/cosmos/cosmos-sdk/runtime"
|
|
"github.com/cosmos/cosmos-sdk/server/config"
|
|
"github.com/cosmos/cosmos-sdk/testutil/sims"
|
|
"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/x/auth/testutil"
|
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
|
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
|
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
)
|
|
|
|
type IntegrationTestSuite struct {
|
|
suite.Suite
|
|
|
|
ctx sdk.Context
|
|
cdc codec.Codec
|
|
genesisAccount *authtypes.BaseAccount
|
|
bankClient types.QueryClient
|
|
testClient testdata.QueryClient
|
|
genesisAccountBalance int64
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) SetupSuite() {
|
|
s.T().Log("setting up integration test suite")
|
|
var (
|
|
interfaceRegistry codectypes.InterfaceRegistry
|
|
bankKeeper bankkeeper.BaseKeeper
|
|
appBuilder *runtime.AppBuilder
|
|
cdc codec.Codec
|
|
)
|
|
|
|
// TODO duplicated from testutils/sims/app_helpers.go
|
|
// need more composable startup options for simapp, this test needed a handle to the closed over genesis account
|
|
// to query balances
|
|
err := depinject.Inject(
|
|
depinject.Configs(
|
|
testutil.AppConfig,
|
|
depinject.Supply(log.NewNopLogger()),
|
|
),
|
|
&interfaceRegistry, &bankKeeper, &appBuilder, &cdc)
|
|
s.NoError(err)
|
|
|
|
app := appBuilder.Build(dbm.NewMemDB(), nil)
|
|
err = app.Load(true)
|
|
s.NoError(err)
|
|
|
|
valSet, err := sims.CreateRandomValidatorSet()
|
|
s.NoError(err)
|
|
|
|
// generate genesis account
|
|
s.genesisAccountBalance = 100000000000000
|
|
senderPrivKey := secp256k1.GenPrivKey()
|
|
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
|
|
balance := types.Balance{
|
|
Address: acc.GetAddress().String(),
|
|
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, math.NewInt(s.genesisAccountBalance))),
|
|
}
|
|
|
|
genesisState, err := sims.GenesisStateWithValSet(cdc, app.DefaultGenesis(), valSet, []authtypes.GenesisAccount{acc}, balance)
|
|
s.NoError(err)
|
|
|
|
stateBytes, err := cmtjson.MarshalIndent(genesisState, "", " ")
|
|
s.NoError(err)
|
|
|
|
// init chain will set the validator set and initialize the genesis accounts
|
|
_, err = app.InitChain(&abci.RequestInitChain{
|
|
Validators: []abci.ValidatorUpdate{},
|
|
ConsensusParams: sims.DefaultConsensusParams,
|
|
AppStateBytes: stateBytes,
|
|
})
|
|
s.NoError(err)
|
|
|
|
_, err = app.FinalizeBlock(&abci.RequestFinalizeBlock{
|
|
Height: app.LastBlockHeight() + 1,
|
|
Hash: app.LastCommitID().Hash,
|
|
NextValidatorsHash: valSet.Hash(),
|
|
})
|
|
s.NoError(err)
|
|
|
|
// end of app init
|
|
|
|
s.ctx = app.NewContext(false)
|
|
s.cdc = cdc
|
|
queryHelper := baseapp.NewQueryServerTestHelper(s.ctx, interfaceRegistry)
|
|
types.RegisterQueryServer(queryHelper, bankKeeper)
|
|
testdata.RegisterQueryServer(queryHelper, testdata.QueryImpl{})
|
|
s.bankClient = types.NewQueryClient(queryHelper)
|
|
s.testClient = testdata.NewQueryClient(queryHelper)
|
|
s.genesisAccount = acc
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TearDownSuite() {
|
|
s.T().Log("tearing down integration test suite")
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGRPCQuery() {
|
|
denom := sdk.DefaultBondDenom
|
|
|
|
// gRPC query to test service should work
|
|
testRes, err := s.testClient.Echo(context.Background(), &testdata.EchoRequest{Message: "hello"})
|
|
s.Require().NoError(err)
|
|
s.Require().Equal("hello", testRes.Message)
|
|
|
|
// gRPC query to bank service should work
|
|
var header metadata.MD
|
|
res, err := s.bankClient.Balance(
|
|
context.Background(),
|
|
&types.QueryBalanceRequest{Address: s.genesisAccount.GetAddress().String(), Denom: denom},
|
|
grpc.Header(&header), // Also fetch grpc header
|
|
)
|
|
s.Require().NoError(err)
|
|
bal := res.GetBalance()
|
|
s.Equal(sdk.NewCoin(denom, math.NewInt(s.genesisAccountBalance)), *bal)
|
|
}
|
|
|
|
func TestIntegrationTestSuite(t *testing.T) {
|
|
suite.Run(t, new(IntegrationTestSuite))
|
|
}
|
|
|
|
func (s *IntegrationTestSuite) TestGetGRPCConnWithContext() {
|
|
defaultConn, err := grpc.NewClient("localhost:9090",
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
)
|
|
s.Require().NoError(err)
|
|
defer defaultConn.Close()
|
|
|
|
historicalConn, err := grpc.NewClient("localhost:9091",
|
|
grpc.WithTransportCredentials(insecure.NewCredentials()),
|
|
)
|
|
s.Require().NoError(err)
|
|
defer historicalConn.Close()
|
|
|
|
historicalConns := config.HistoricalGRPCConnections{
|
|
config.BlockRange{100, 500}: historicalConn,
|
|
}
|
|
provider := client.NewGRPCConnProvider(defaultConn, historicalConns)
|
|
testCases := []struct {
|
|
name string
|
|
height int64
|
|
setupCtx func() client.Context
|
|
expectedConn *grpc.ClientConn
|
|
}{
|
|
{
|
|
name: "context with GRPCConnProvider and historical height",
|
|
height: 300,
|
|
setupCtx: func() client.Context {
|
|
return client.Context{}.
|
|
WithCodec(s.cdc).
|
|
WithGRPCClient(defaultConn).
|
|
WithGRPCConnProvider(provider).
|
|
WithHeight(300)
|
|
},
|
|
expectedConn: historicalConn,
|
|
},
|
|
{
|
|
name: "context with GRPCConnProvider and latest height",
|
|
height: 0,
|
|
setupCtx: func() client.Context {
|
|
return client.Context{}.
|
|
WithCodec(s.cdc).
|
|
WithGRPCClient(defaultConn).
|
|
WithGRPCConnProvider(provider).
|
|
WithHeight(0)
|
|
},
|
|
expectedConn: defaultConn,
|
|
},
|
|
{
|
|
name: "context without GRPCConnProvider",
|
|
height: 300,
|
|
setupCtx: func() client.Context {
|
|
return client.Context{}.
|
|
WithCodec(s.cdc).
|
|
WithGRPCClient(defaultConn).
|
|
WithHeight(300)
|
|
},
|
|
expectedConn: defaultConn,
|
|
},
|
|
{
|
|
name: "context with nil historical connections map",
|
|
height: 100,
|
|
setupCtx: func() client.Context {
|
|
nilProvider := client.NewGRPCConnProvider(defaultConn, nil)
|
|
return client.Context{}.
|
|
WithCodec(s.cdc).
|
|
WithGRPCClient(defaultConn).
|
|
WithGRPCConnProvider(nilProvider).
|
|
WithHeight(100)
|
|
},
|
|
expectedConn: defaultConn,
|
|
},
|
|
{
|
|
name: "context with empty historical connections map",
|
|
height: 100,
|
|
setupCtx: func() client.Context {
|
|
emptyProvider := client.NewGRPCConnProvider(defaultConn, config.HistoricalGRPCConnections{})
|
|
return client.Context{}.
|
|
WithCodec(s.cdc).
|
|
WithGRPCClient(defaultConn).
|
|
WithGRPCConnProvider(emptyProvider).
|
|
WithHeight(100)
|
|
},
|
|
expectedConn: defaultConn,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
s.Run(tc.name, func() {
|
|
ctx := tc.setupCtx()
|
|
var actualConn *grpc.ClientConn
|
|
if ctx.GRPCConnProvider != nil {
|
|
actualConn = ctx.GRPCConnProvider.GetGRPCConn(ctx.Height)
|
|
} else {
|
|
actualConn = ctx.GRPCClient
|
|
}
|
|
s.Require().Equal(tc.expectedConn, actualConn)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetHeightFromMetadata(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setupContext func() context.Context
|
|
expectedHeight int64
|
|
}{
|
|
{
|
|
name: "valid height in metadata",
|
|
setupContext: func() context.Context {
|
|
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "12345")
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 12345,
|
|
},
|
|
{
|
|
name: "zero height in metadata",
|
|
setupContext: func() context.Context {
|
|
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "0")
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 0,
|
|
},
|
|
{
|
|
name: "negative height returns zero",
|
|
setupContext: func() context.Context {
|
|
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "-100")
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 0,
|
|
},
|
|
{
|
|
name: "no metadata returns zero",
|
|
setupContext: context.Background,
|
|
expectedHeight: 0,
|
|
},
|
|
{
|
|
name: "empty height header returns zero",
|
|
setupContext: func() context.Context {
|
|
md := metadata.New(map[string]string{})
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 0,
|
|
},
|
|
{
|
|
name: "invalid height string returns zero",
|
|
setupContext: func() context.Context {
|
|
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "not-a-number")
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 0,
|
|
},
|
|
{
|
|
name: "multiple height values uses first",
|
|
setupContext: func() context.Context {
|
|
md := metadata.Pairs(
|
|
grpctypes.GRPCBlockHeightHeader, "100",
|
|
grpctypes.GRPCBlockHeightHeader, "200",
|
|
)
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 100,
|
|
},
|
|
{
|
|
name: "very large height",
|
|
setupContext: func() context.Context {
|
|
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "9223372036854775807") // max int64
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 9223372036854775807,
|
|
},
|
|
{
|
|
name: "height exceeding int64 returns zero",
|
|
setupContext: func() context.Context {
|
|
md := metadata.Pairs(grpctypes.GRPCBlockHeightHeader, "9223372036854775808") // max int64 + 1
|
|
return metadata.NewOutgoingContext(context.Background(), md)
|
|
},
|
|
expectedHeight: 0,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
ctx := tt.setupContext()
|
|
height := client.GetHeightFromMetadata(ctx)
|
|
require.Equal(t, tt.expectedHeight, height)
|
|
})
|
|
}
|
|
}
|