From 08f2a26e514de14334d33fe3fa1d97ac024fb5fe Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 27 Oct 2024 23:55:50 +0100 Subject: [PATCH] test: e2e/client to system tests (backport #21981) (#21986) Co-authored-by: Julien Robert Co-authored-by: Julian Toledano --- .../client/grpc/cmtservice/service_test.go | 347 ------------------ tests/systemtests/cometbft_client_test.go | 330 +++++++++++++++++ tests/systemtests/go.mod | 3 +- tests/systemtests/go.sum | 2 + tests/systemtests/rpc_client.go | 80 ++++ 5 files changed, 414 insertions(+), 348 deletions(-) delete mode 100644 tests/e2e/client/grpc/cmtservice/service_test.go create mode 100644 tests/systemtests/cometbft_client_test.go diff --git a/tests/e2e/client/grpc/cmtservice/service_test.go b/tests/e2e/client/grpc/cmtservice/service_test.go deleted file mode 100644 index e4177a11edfb..000000000000 --- a/tests/e2e/client/grpc/cmtservice/service_test.go +++ /dev/null @@ -1,347 +0,0 @@ -//go:build e2e -// +build e2e - -package cmtservice_test - -import ( - "context" - "fmt" - "testing" - - "github.com/stretchr/testify/suite" - - "cosmossdk.io/simapp" - _ "cosmossdk.io/x/gov" - - "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" - "github.com/cosmos/cosmos-sdk/testutil" - "github.com/cosmos/cosmos-sdk/testutil/network" - qtypes "github.com/cosmos/cosmos-sdk/types/query" - "github.com/cosmos/cosmos-sdk/version" -) - -type E2ETestSuite struct { - suite.Suite - - cfg network.Config - network network.NetworkI - queryClient cmtservice.ServiceClient -} - -func TestE2ETestSuite(t *testing.T) { - suite.Run(t, new(E2ETestSuite)) -} - -func (s *E2ETestSuite) SetupSuite() { - s.T().Log("setting up e2e test suite") - - cfg := network.DefaultConfig(simapp.NewTestNetworkFixture) - cfg.NumValidators = 1 - s.cfg = cfg - - var err error - s.network, err = network.New(s.T(), s.T().TempDir(), s.cfg) - s.Require().NoError(err) - - s.Require().NoError(s.network.WaitForNextBlock()) - - s.queryClient = cmtservice.NewServiceClient(s.network.GetValidators()[0].GetClientCtx()) -} - -func (s *E2ETestSuite) TearDownSuite() { - s.T().Log("tearing down e2e test suite") - s.network.Cleanup() -} - -func (s *E2ETestSuite) TestQueryNodeInfo() { - val := s.network.GetValidators()[0] - - res, err := s.queryClient.GetNodeInfo(context.Background(), &cmtservice.GetNodeInfoRequest{}) - s.Require().NoError(err) - s.Require().Equal(res.ApplicationVersion.AppName, version.NewInfo().AppName) - - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/node_info", val.GetAPIAddress())) - s.Require().NoError(err) - var getInfoRes cmtservice.GetNodeInfoResponse - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(restRes, &getInfoRes)) - s.Require().Equal(getInfoRes.ApplicationVersion.AppName, version.NewInfo().AppName) -} - -func (s *E2ETestSuite) TestQuerySyncing() { - val := s.network.GetValidators()[0] - - _, err := s.queryClient.GetSyncing(context.Background(), &cmtservice.GetSyncingRequest{}) - s.Require().NoError(err) - - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/syncing", val.GetAPIAddress())) - s.Require().NoError(err) - var syncingRes cmtservice.GetSyncingResponse - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(restRes, &syncingRes)) -} - -func (s *E2ETestSuite) TestQueryLatestBlock() { - val := s.network.GetValidators()[0] - - _, err := s.queryClient.GetLatestBlock(context.Background(), &cmtservice.GetLatestBlockRequest{}) - s.Require().NoError(err) - - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/blocks/latest", val.GetAPIAddress())) - s.Require().NoError(err) - var blockInfoRes cmtservice.GetLatestBlockResponse - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(restRes, &blockInfoRes)) - s.Require().Contains(blockInfoRes.SdkBlock.Header.ProposerAddress, "cosmosvalcons") -} - -func (s *E2ETestSuite) TestQueryBlockByHeight() { - val := s.network.GetValidators()[0] - _, err := s.queryClient.GetBlockByHeight(context.Background(), &cmtservice.GetBlockByHeightRequest{Height: 1}) - s.Require().NoError(err) - - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/blocks/%d", val.GetAPIAddress(), 1)) - s.Require().NoError(err) - var blockInfoRes cmtservice.GetBlockByHeightResponse - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(restRes, &blockInfoRes)) - s.Require().Contains(blockInfoRes.SdkBlock.Header.ProposerAddress, "cosmosvalcons") -} - -func (s *E2ETestSuite) TestQueryLatestValidatorSet() { - val := s.network.GetValidators()[0] - - // nil pagination - res, err := s.queryClient.GetLatestValidatorSet(context.Background(), &cmtservice.GetLatestValidatorSetRequest{ - Pagination: nil, - }) - s.Require().NoError(err) - s.Require().Equal(1, len(res.Validators)) - content, ok := res.Validators[0].PubKey.GetCachedValue().(cryptotypes.PubKey) - s.Require().Equal(true, ok) - s.Require().Equal(content, val.GetPubKey()) - - // with pagination - _, err = s.queryClient.GetLatestValidatorSet(context.Background(), &cmtservice.GetLatestValidatorSetRequest{Pagination: &qtypes.PageRequest{ - Offset: 0, - Limit: 10, - }}) - s.Require().NoError(err) - - // rest request without pagination - _, err = testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest", val.GetAPIAddress())) - s.Require().NoError(err) - - // rest request with pagination - restRes, err := testutil.GetRequest(fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=%d&pagination.limit=%d", val.GetAPIAddress(), 0, 1)) - s.Require().NoError(err) - var validatorSetRes cmtservice.GetLatestValidatorSetResponse - s.Require().NoError(val.GetClientCtx().Codec.UnmarshalJSON(restRes, &validatorSetRes)) - s.Require().Equal(1, len(validatorSetRes.Validators)) - anyPub, err := codectypes.NewAnyWithValue(val.GetPubKey()) - s.Require().NoError(err) - s.Require().Equal(validatorSetRes.Validators[0].PubKey, anyPub) -} - -func (s *E2ETestSuite) TestLatestValidatorSet_GRPC() { - vals := s.network.GetValidators() - testCases := []struct { - name string - req *cmtservice.GetLatestValidatorSetRequest - expErr bool - expErrMsg string - }{ - {"nil request", nil, true, "cannot be nil"}, - {"no pagination", &cmtservice.GetLatestValidatorSetRequest{}, false, ""}, - {"with pagination", &cmtservice.GetLatestValidatorSetRequest{Pagination: &qtypes.PageRequest{Offset: 0, Limit: uint64(len(vals))}}, false, ""}, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - grpcRes, err := s.queryClient.GetLatestValidatorSet(context.Background(), tc.req) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrMsg) - } else { - s.Require().NoError(err) - s.Require().Len(grpcRes.Validators, len(vals)) - s.Require().Equal(grpcRes.Pagination.Total, uint64(len(vals))) - content, ok := grpcRes.Validators[0].PubKey.GetCachedValue().(cryptotypes.PubKey) - s.Require().Equal(true, ok) - s.Require().Equal(content, vals[0].GetPubKey()) - } - }) - } -} - -func (s *E2ETestSuite) TestLatestValidatorSet_GRPCGateway() { - vals := s.network.GetValidators() - testCases := []struct { - name string - url string - expErr bool - expErrMsg string - }{ - {"no pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest", vals[0].GetAPIAddress()), false, ""}, - {"pagination invalid fields", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=-1&pagination.limit=-2", vals[0].GetAPIAddress()), true, "strconv.ParseUint"}, - {"with pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=0&pagination.limit=2", vals[0].GetAPIAddress()), false, ""}, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - res, err := testutil.GetRequest(tc.url) - s.Require().NoError(err) - if tc.expErr { - s.Require().Contains(string(res), tc.expErrMsg) - } else { - var result cmtservice.GetLatestValidatorSetResponse - err = vals[0].GetClientCtx().Codec.UnmarshalJSON(res, &result) - s.Require().NoError(err) - s.Require().Equal(uint64(len(vals)), result.Pagination.Total) - anyPub, err := codectypes.NewAnyWithValue(vals[0].GetPubKey()) - s.Require().NoError(err) - s.Require().Equal(result.Validators[0].PubKey, anyPub) - } - }) - } -} - -func (s *E2ETestSuite) TestValidatorSetByHeight_GRPC() { - vals := s.network.GetValidators() - testCases := []struct { - name string - req *cmtservice.GetValidatorSetByHeightRequest - expErr bool - expErrMsg string - }{ - {"nil request", nil, true, "request cannot be nil"}, - {"empty request", &cmtservice.GetValidatorSetByHeightRequest{}, true, "height must be greater than 0"}, - {"no pagination", &cmtservice.GetValidatorSetByHeightRequest{Height: 1}, false, ""}, - {"with pagination", &cmtservice.GetValidatorSetByHeightRequest{Height: 1, Pagination: &qtypes.PageRequest{Offset: 0, Limit: 1}}, false, ""}, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - grpcRes, err := s.queryClient.GetValidatorSetByHeight(context.Background(), tc.req) - if tc.expErr { - s.Require().Error(err) - s.Require().Contains(err.Error(), tc.expErrMsg) - } else { - s.Require().NoError(err) - s.Require().Len(grpcRes.Validators, len(vals)) - s.Require().Equal(grpcRes.Pagination.Total, uint64(len(vals))) - } - }) - } -} - -func (s *E2ETestSuite) TestValidatorSetByHeight_GRPCGateway() { - vals := s.network.GetValidators() - testCases := []struct { - name string - url string - expErr bool - expErrMsg string - }{ - {"invalid height", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", vals[0].GetAPIAddress(), -1), true, "height must be greater than 0"}, - {"no pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", vals[0].GetAPIAddress(), 1), false, ""}, - {"pagination invalid fields", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.offset=-1&pagination.limit=-2", vals[0].GetAPIAddress(), 1), true, "strconv.ParseUint"}, - {"with pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.offset=0&pagination.limit=2", vals[0].GetAPIAddress(), 1), false, ""}, - } - for _, tc := range testCases { - s.Run(tc.name, func() { - res, err := testutil.GetRequest(tc.url) - s.Require().NoError(err) - if tc.expErr { - s.Require().Contains(string(res), tc.expErrMsg) - } else { - var result cmtservice.GetValidatorSetByHeightResponse - err = vals[0].GetClientCtx().Codec.UnmarshalJSON(res, &result) - s.Require().NoError(err) - s.Require().Equal(uint64(len(vals)), result.Pagination.Total) - } - }) - } -} - -func (s *E2ETestSuite) TestABCIQuery() { - testCases := []struct { - name string - req *cmtservice.ABCIQueryRequest - expectErr bool - expectedCode uint32 - validQuery bool - }{ - { - name: "valid request with proof", - req: &cmtservice.ABCIQueryRequest{ - Path: "/store/gov/key", - Data: []byte{0x03}, - Prove: true, - }, - validQuery: true, - }, - { - name: "valid request without proof", - req: &cmtservice.ABCIQueryRequest{ - Path: "/store/gov/key", - Data: []byte{0x03}, - Prove: false, - }, - validQuery: true, - }, - { - name: "request with invalid path", - req: &cmtservice.ABCIQueryRequest{ - Path: "/foo/bar", - Data: []byte{0x03}, - }, - expectErr: true, - }, - { - name: "request with invalid path recursive", - req: &cmtservice.ABCIQueryRequest{ - Path: "/cosmos.base.tendermint.v1beta1.Service/ABCIQuery", - Data: s.cfg.Codec.MustMarshal(&cmtservice.ABCIQueryRequest{ - Path: "/cosmos.base.tendermint.v1beta1.Service/ABCIQuery", - }), - }, - expectErr: true, - }, - { - name: "request with invalid broadcast tx path", - req: &cmtservice.ABCIQueryRequest{ - Path: "/cosmos.tx.v1beta1.Service/BroadcastTx", - Data: []byte{0x00}, - }, - expectErr: true, - }, - { - name: "request with invalid data", - req: &cmtservice.ABCIQueryRequest{ - Path: "/store/gov/key", - Data: []byte{0x0044, 0x00}, - }, - validQuery: false, - }, - } - - for _, tc := range testCases { - s.Run(tc.name, func() { - res, err := s.queryClient.ABCIQuery(context.Background(), tc.req) - if tc.expectErr { - s.Require().Error(err) - s.Require().Nil(res) - } else { - s.Require().NoError(err) - s.Require().NotNil(res) - s.Require().Equal(res.Code, tc.expectedCode) - } - - if tc.validQuery { - s.Require().Greater(res.Height, int64(0)) - s.Require().Greater(len(res.Key), 0, "expected non-empty key") - s.Require().Greater(len(res.Value), 0, "expected non-empty value") - } - - if tc.req.Prove { - s.Require().Greater(len(res.ProofOps.Ops), 0, "expected proofs") - } - }) - } -} diff --git a/tests/systemtests/cometbft_client_test.go b/tests/systemtests/cometbft_client_test.go new file mode 100644 index 000000000000..6e0668ff71c7 --- /dev/null +++ b/tests/systemtests/cometbft_client_test.go @@ -0,0 +1,330 @@ +//go:build system_test + +package systemtests + +import ( + "context" + "fmt" + "net/http" + "net/url" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/tidwall/gjson" + + "github.com/cosmos/cosmos-sdk/client/grpc/cmtservice" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" + qtypes "github.com/cosmos/cosmos-sdk/types/query" +) + +func TestQueryNodeInfo(t *testing.T) { + baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) + sut.ResetChain(t) + sut.StartChain(t) + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + res, err := qc.GetNodeInfo(context.Background(), &cmtservice.GetNodeInfoRequest{}) + assert.NoError(t, err) + + v := NewCLIWrapper(t, sut, true).Version() + assert.Equal(t, res.ApplicationVersion.Version, v) + + // TODO: we should be adding a way to distinguish a v2. Eventually we should skip some v2 system depending on the consensus engine we want to test + restRes := GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/node_info"))) + assert.NoError(t, err) + assert.Equal(t, gjson.GetBytes(restRes, "application_version.version").String(), res.ApplicationVersion.Version) +} + +func TestQuerySyncing(t *testing.T) { + baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) + sut.ResetChain(t) + sut.StartChain(t) + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + res, err := qc.GetSyncing(context.Background(), &cmtservice.GetSyncingRequest{}) + assert.NoError(t, err) + + restRes := GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/syncing"))) + assert.Equal(t, gjson.GetBytes(restRes, "syncing").Bool(), res.Syncing) +} + +func TestQueryLatestBlock(t *testing.T) { + baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) + sut.ResetChain(t) + sut.StartChain(t) + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + res, err := qc.GetLatestBlock(context.Background(), &cmtservice.GetLatestBlockRequest{}) + assert.NoError(t, err) + assert.Contains(t, res.SdkBlock.Header.ProposerAddress, "cosmosvalcons") + + _ = GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/blocks/latest"))) +} + +func TestQueryBlockByHeight(t *testing.T) { + baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) + sut.ResetChain(t) + sut.StartChain(t) + + sut.AwaitNBlocks(t, 2, time.Second*25) + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + res, err := qc.GetBlockByHeight(context.Background(), &cmtservice.GetBlockByHeightRequest{Height: 2}) + assert.NoError(t, err) + assert.Equal(t, res.SdkBlock.Header.Height, int64(2)) + assert.Contains(t, res.SdkBlock.Header.ProposerAddress, "cosmosvalcons") + + restRes := GetRequest(t, mustV(url.JoinPath(baseurl, "/cosmos/base/tendermint/v1beta1/blocks/2"))) + assert.Equal(t, gjson.GetBytes(restRes, "sdk_block.header.height").Int(), int64(2)) + assert.Contains(t, gjson.GetBytes(restRes, "sdk_block.header.proposer_address").String(), "cosmosvalcons") +} + +func TestQueryLatestValidatorSet(t *testing.T) { + baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) + sut.ResetChain(t) + sut.StartChain(t) + + vals := sut.RPCClient(t).Validators() + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + res, err := qc.GetLatestValidatorSet(context.Background(), &cmtservice.GetLatestValidatorSetRequest{ + Pagination: nil, + }) + assert.NoError(t, err) + assert.Equal(t, len(res.Validators), len(vals)) + + // with pagination + res, err = qc.GetLatestValidatorSet(context.Background(), &cmtservice.GetLatestValidatorSetRequest{Pagination: &qtypes.PageRequest{ + Offset: 0, + Limit: 2, + }}) + assert.NoError(t, err) + assert.Equal(t, len(res.Validators), 2) + + restRes := GetRequest(t, fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=%d&pagination.limit=%d", baseurl, 0, 2)) + assert.Equal(t, len(gjson.GetBytes(restRes, "validators").Array()), 2) +} + +func TestLatestValidatorSet(t *testing.T) { + sut.ResetChain(t) + sut.StartChain(t) + + vals := sut.RPCClient(t).Validators() + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + testCases := []struct { + name string + req *cmtservice.GetLatestValidatorSetRequest + expErr bool + expErrMsg string + }{ + {"nil request", nil, true, "cannot be nil"}, + {"no pagination", &cmtservice.GetLatestValidatorSetRequest{}, false, ""}, + {"with pagination", &cmtservice.GetLatestValidatorSetRequest{Pagination: &qtypes.PageRequest{Offset: 0, Limit: uint64(len(vals))}}, false, ""}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res, err := qc.GetLatestValidatorSet(context.Background(), tc.req) + if tc.expErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expErrMsg) + } else { + assert.NoError(t, err) + assert.Equal(t, len(res.Validators), len(vals)) + content, ok := res.Validators[0].PubKey.GetCachedValue().(cryptotypes.PubKey) + assert.True(t, ok) + assert.Equal(t, content.Address(), vals[0].PubKey.Address()) + } + }) + } +} + +func TestLatestValidatorSet_GRPCGateway(t *testing.T) { + sut.ResetChain(t) + sut.StartChain(t) + + baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) + + vals := sut.RPCClient(t).Validators() + + testCases := []struct { + name string + url string + expErr bool + expErrMsg string + }{ + {"no pagination", "/cosmos/base/tendermint/v1beta1/validatorsets/latest", false, ""}, + {"pagination invalid fields", "/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=-1&pagination.limit=-2", true, "strconv.ParseUint"}, + {"with pagination", "/cosmos/base/tendermint/v1beta1/validatorsets/latest?pagination.offset=0&pagination.limit=2", false, ""}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if tc.expErr { + rsp := GetRequestWithHeaders(t, baseurl+tc.url, nil, http.StatusBadRequest) + errMsg := gjson.GetBytes(rsp, "message").String() + assert.Contains(t, errMsg, tc.expErrMsg) + return + } + rsp := GetRequest(t, baseurl+tc.url) + assert.Equal(t, len(vals), int(gjson.GetBytes(rsp, "pagination.total").Int())) + + }) + } +} + +func TestValidatorSetByHeight(t *testing.T) { + sut.ResetChain(t) + sut.StartChain(t) + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + vals := sut.RPCClient(t).Validators() + + testCases := []struct { + name string + req *cmtservice.GetValidatorSetByHeightRequest + expErr bool + expErrMsg string + }{ + {"nil request", nil, true, "request cannot be nil"}, + {"empty request", &cmtservice.GetValidatorSetByHeightRequest{}, true, "height must be greater than 0"}, + {"no pagination", &cmtservice.GetValidatorSetByHeightRequest{Height: 1}, false, ""}, + {"with pagination", &cmtservice.GetValidatorSetByHeightRequest{Height: 1, Pagination: &qtypes.PageRequest{Offset: 0, Limit: uint64(len(vals))}}, false, ""}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res, err := qc.GetValidatorSetByHeight(context.Background(), tc.req) + if tc.expErr { + assert.Error(t, err) + assert.Contains(t, err.Error(), tc.expErrMsg) + } else { + assert.NoError(t, err) + assert.Equal(t, len(res.Validators), len(vals)) + } + }) + } +} + +func TestValidatorSetByHeight_GRPCGateway(t *testing.T) { + sut.ResetChain(t) + sut.StartChain(t) + + vals := sut.RPCClient(t).Validators() + + baseurl := fmt.Sprintf("http://localhost:%d", apiPortStart) + + block := sut.AwaitNextBlock(t, time.Second*3) + testCases := []struct { + name string + url string + expErr bool + expErrMsg string + expHttpCode int + }{ + {"invalid height", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", baseurl, -1), true, "height must be greater than 0", http.StatusInternalServerError}, + {"no pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d", baseurl, block), false, "", http.StatusOK}, + {"pagination invalid fields", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.offset=-1&pagination.limit=-2", baseurl, block), true, "strconv.ParseUint", http.StatusBadRequest}, + {"with pagination", fmt.Sprintf("%s/cosmos/base/tendermint/v1beta1/validatorsets/%d?pagination.limit=2", baseurl, 1), false, "", http.StatusOK}, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + rsp := GetRequestWithHeaders(t, tc.url, nil, tc.expHttpCode) + if tc.expErr { + errMsg := gjson.GetBytes(rsp, "message").String() + assert.Contains(t, errMsg, tc.expErrMsg) + } else { + assert.Equal(t, len(vals), int(gjson.GetBytes(rsp, "pagination.total").Int())) + } + }) + } +} + +func TestABCIQuery(t *testing.T) { + sut.ResetChain(t) + sut.StartChain(t) + sut.AwaitNBlocks(t, 3) + + qc := cmtservice.NewServiceClient(sut.RPCClient(t)) + cdc := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + testCases := []struct { + name string + req *cmtservice.ABCIQueryRequest + expectErr bool + expectedCode uint32 + validQuery bool + }{ + { + name: "valid request with proof", + req: &cmtservice.ABCIQueryRequest{ + Path: "/store/gov/key", + Data: []byte{0x03}, + Prove: true, + }, + validQuery: true, + }, + { + name: "valid request without proof", + req: &cmtservice.ABCIQueryRequest{ + Path: "/store/gov/key", + Data: []byte{0x03}, + Prove: false, + }, + validQuery: true, + }, + { + name: "request with invalid path", + req: &cmtservice.ABCIQueryRequest{ + Path: "/foo/bar", + Data: []byte{0x03}, + }, + expectErr: true, + }, + { + name: "request with invalid path recursive", + req: &cmtservice.ABCIQueryRequest{ + Path: "/cosmos.base.tendermint.v1beta1.Service/ABCIQuery", + Data: cdc.MustMarshal(&cmtservice.ABCIQueryRequest{ + Path: "/cosmos.base.tendermint.v1beta1.Service/ABCIQuery", + }), + }, + expectErr: true, + }, + { + name: "request with invalid broadcast tx path", + req: &cmtservice.ABCIQueryRequest{ + Path: "/cosmos.tx.v1beta1.Service/BroadcastTx", + Data: []byte{0x00}, + }, + expectErr: true, + }, + { + name: "request with invalid data", + req: &cmtservice.ABCIQueryRequest{ + Path: "/store/gov/key", + Data: []byte{0x0044, 0x00}, + }, + validQuery: false, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + res, err := qc.ABCIQuery(context.Background(), tc.req) + if tc.expectErr { + assert.Error(t, err) + assert.Nil(t, res) + } else { + assert.NoError(t, err) + assert.NotNil(t, res) + assert.Equal(t, tc.expectedCode, res.Code) + } + + if tc.validQuery { + assert.Greater(t, res.Height, int64(0)) + assert.Greater(t, len(res.Key), 0) + assert.Greater(t, len(res.Value), 0) + } + }) + } +} diff --git a/tests/systemtests/go.mod b/tests/systemtests/go.mod index 9370b6e3d495..c27102452408 100644 --- a/tests/systemtests/go.mod +++ b/tests/systemtests/go.mod @@ -20,12 +20,13 @@ require ( github.com/stretchr/testify v1.9.0 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/grpc v1.67.1 // indirect + google.golang.org/grpc v1.67.1 ) require ( cosmossdk.io/math v1.3.0 github.com/cometbft/cometbft v0.38.8 + github.com/cometbft/cometbft/api v1.0.0-rc.1 github.com/creachadair/tomledit v0.0.26 github.com/tidwall/gjson v1.14.2 github.com/tidwall/sjson v1.2.5 diff --git a/tests/systemtests/go.sum b/tests/systemtests/go.sum index 40c4b8586663..bfb368bf410b 100644 --- a/tests/systemtests/go.sum +++ b/tests/systemtests/go.sum @@ -126,6 +126,8 @@ github.com/cometbft/cometbft v0.38.8 h1:XyJ9Cu3xqap6xtNxiemrO8roXZ+KS2Zlu7qQ0w1t github.com/cometbft/cometbft v0.38.8/go.mod h1:xOoGZrtUT+A5izWfHSJgl0gYZUE7lu7Z2XIS1vWG/QQ= github.com/cometbft/cometbft-db v1.0.1 h1:SylKuLseMLQKw3+i8y8KozZyJcQSL98qEe2CGMCGTYE= github.com/cometbft/cometbft-db v1.0.1/go.mod h1:EBrFs1GDRiTqrWXYi4v90Awf/gcdD5ExzdPbg4X8+mk= +github.com/cometbft/cometbft/api v1.0.0-rc.1 h1:GtdXwDGlqwHYs16A4egjwylfYOMYyEacLBrs3Zvpt7g= +github.com/cometbft/cometbft/api v1.0.0-rc.1/go.mod h1:NDFKiBBD8HJC6QQLAoUI99YhsiRZtg2+FJWfk6A6m6o= github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= diff --git a/tests/systemtests/rpc_client.go b/tests/systemtests/rpc_client.go index d1acfd780ff4..4714715be600 100644 --- a/tests/systemtests/rpc_client.go +++ b/tests/systemtests/rpc_client.go @@ -2,11 +2,23 @@ package systemtests import ( "context" + "errors" + "reflect" + "strconv" "testing" + abci "github.com/cometbft/cometbft/api/cometbft/abci/v1" + rpcclient "github.com/cometbft/cometbft/rpc/client" client "github.com/cometbft/cometbft/rpc/client/http" cmtypes "github.com/cometbft/cometbft/types" "github.com/stretchr/testify/require" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/codec/types" + cryptocodec "github.com/cosmos/cosmos-sdk/crypto/codec" + grpctypes "github.com/cosmos/cosmos-sdk/types/grpc" ) // RPCClient is a test helper to interact with a node via the RPC endpoint. @@ -31,3 +43,71 @@ func (r RPCClient) Validators() []*cmtypes.Validator { require.NoError(r.t, err) return v.Validators } + +func (r RPCClient) Invoke(ctx context.Context, method string, req, reply interface{}, opts ...grpc.CallOption) error { + if reflect.ValueOf(req).IsNil() { + return errors.New("request cannot be nil") + } + + ir := types.NewInterfaceRegistry() + cryptocodec.RegisterInterfaces(ir) + cdc := codec.NewProtoCodec(ir).GRPCCodec() + + reqBz, err := cdc.Marshal(req) + if err != nil { + return err + } + + var height int64 + md, _ := metadata.FromOutgoingContext(ctx) + if heights := md.Get(grpctypes.GRPCBlockHeightHeader); len(heights) > 0 { + height, err := strconv.ParseInt(heights[0], 10, 64) + if err != nil { + return err + } + if height < 0 { + return errors.New("height must be greater than or equal to 0") + } + } + + abciReq := abci.QueryRequest{ + Path: method, + Data: reqBz, + Height: height, + } + + abciOpts := rpcclient.ABCIQueryOptions{ + Height: height, + Prove: abciReq.Prove, + } + + result, err := r.client.ABCIQueryWithOptions(ctx, abciReq.Path, abciReq.Data, abciOpts) + if err != nil { + return err + } + + if !result.Response.IsOK() { + return errors.New(result.Response.String()) + } + + err = cdc.Unmarshal(result.Response.Value, reply) + if err != nil { + return err + } + + md = metadata.Pairs(grpctypes.GRPCBlockHeightHeader, strconv.FormatInt(result.Response.Height, 10)) + for _, callOpt := range opts { + header, ok := callOpt.(grpc.HeaderCallOption) + if !ok { + continue + } + + *header.HeaderAddr = md + } + + return types.UnpackInterfaces(reply, ir) +} + +func (r RPCClient) NewStream(ctx context.Context, desc *grpc.StreamDesc, method string, opts ...grpc.CallOption) (grpc.ClientStream, error) { + return nil, errors.New("not implemented") +}