Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Testing, Tooling] Expose integration app via gRPC/HTTP/WS #1017

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions pkg/client/block/block_result.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,14 @@ import (
type cometBlockResult coretypes.ResultBlock

func (cbr *cometBlockResult) Height() int64 {

//cdc := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) cbrPreJSON, err := cdc.MarshalJSON(cbr)

//cbrJSON, err := json.MarshalIndent(cbrPreJSON, "", " ")
//if err != nil {
// panic(err)
//}
//fmt.Println(string(cbrJSON))
return cbr.Block.Header.Height
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/client/interface.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//go:generate mockgen -destination=../../testutil/mockclient/events_query_client_mock.go -package=mockclient . Dialer,Connection,EventsQueryClient
//go:generate mockgen -destination=../../testutil/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient
//go:generate mockgen -destination=../../testutil/mockclient/block_client_mock.go -package=mockclient . Block,BlockClient,BlockQueryClient
//go:generate mockgen -destination=../../testutil/mockclient/delegation_client_mock.go -package=mockclient . DelegationClient
//go:generate mockgen -destination=../../testutil/mockclient/tx_client_mock.go -package=mockclient . TxContext,TxClient
//go:generate mockgen -destination=../../testutil/mockclient/supplier_client_mock.go -package=mockclient . SupplierClient
Expand Down
130 changes: 130 additions & 0 deletions testutil/e2e/app.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package e2e

import (
"context"
"errors"
"fmt"
"net"
"net/http"
"sync"
"testing"

"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/gorilla/websocket"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

coretypes "github.com/cometbft/cometbft/rpc/core/types"

"github.com/pokt-network/poktroll/testutil/integration"
"github.com/pokt-network/poktroll/testutil/testclient"
)

// E2EApp wraps an integration.App and provides both gRPC and WebSocket servers for end-to-end testing
type E2EApp struct {
*integration.App
grpcServer *grpc.Server
grpcListener net.Listener
wsServer *http.Server
wsListener net.Listener
wsUpgrader websocket.Upgrader
wsConnMutex sync.RWMutex
wsConnections map[*websocket.Conn]map[string]struct{} // maps connections to their subscribed event queries
blockEventChan chan *coretypes.ResultEvent
}

// NewE2EApp creates a new E2EApp instance with integration.App, gRPC, and WebSocket servers
func NewE2EApp(t *testing.T, opts ...integration.IntegrationAppOptionFn) *E2EApp {
t.Helper()

// Initialize and start gRPC server
creds := insecure.NewCredentials()
grpcServer := grpc.NewServer(grpc.Creds(creds))
mux := runtime.NewServeMux()

// Create the integration app
app := integration.NewCompleteIntegrationApp(t, opts...)
app.RegisterGRPCServer(grpcServer)
//app.RegisterGRPCServer(e2eApp.grpcServer)

flagSet := testclient.NewFlagSet(t, "tcp://127.0.0.1:42070")
keyRing := keyring.NewInMemory(app.GetCodec())
clientCtx := testclient.NewLocalnetClientCtx(t, flagSet).WithKeyring(keyRing)

for moduleName, mod := range app.GetModuleManager().Modules {
fmt.Printf(">>> %s\n", moduleName)
mod.(module.AppModuleBasic).RegisterGRPCGatewayRoutes(clientCtx, mux)
}

// Create listeners for gRPC, WebSocket, and HTTP
grpcListener, err := net.Listen("tcp", "localhost:42069")
require.NoError(t, err, "failed to create gRPC listener")

wsListener, err := net.Listen("tcp", "localhost:6969")
require.NoError(t, err, "failed to create WebSocket listener")

e2eApp := &E2EApp{
App: app,
grpcListener: grpcListener,
grpcServer: grpcServer,
wsListener: wsListener,
wsConnections: make(map[*websocket.Conn]map[string]struct{}),
wsUpgrader: websocket.Upgrader{},
blockEventChan: make(chan *coretypes.ResultEvent, 1),
}

go func() {
if err := e2eApp.grpcServer.Serve(grpcListener); err != nil {
panic(err)
}
}()

// Initialize and start WebSocket server
e2eApp.wsServer = newWebSocketServer(e2eApp)
go func() {
if err := e2eApp.wsServer.Serve(wsListener); err != nil && errors.Is(err, http.ErrServerClosed) {
panic(err)
}
}()

// Initialize and start HTTP server
go func() {
if err := http.ListenAndServe("localhost:42070", mux); err != nil {
panic(err)
}
}()

// Start event handling
go e2eApp.handleBlockEvents(t)

return e2eApp
}

// Close gracefully shuts down the E2EApp and its servers
func (app *E2EApp) Close() error {
app.grpcServer.GracefulStop()
if err := app.wsServer.Close(); err != nil {
return err
}

close(app.blockEventChan)

return nil
}

// GetClientConn returns a gRPC client connection to the E2EApp's gRPC server
func (app *E2EApp) GetClientConn(ctx context.Context) (*grpc.ClientConn, error) {
return grpc.DialContext(

Check failure on line 120 in testutil/e2e/app.go

View workflow job for this annotation

GitHub Actions / go-test

SA1019: grpc.DialContext is deprecated: use NewClient instead. Will be supported throughout 1.x. (staticcheck)
ctx,
app.grpcListener.Addr().String(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
}

// GetWSEndpoint returns the WebSocket endpoint URL
func (app *E2EApp) GetWSEndpoint() string {
return "ws://" + app.wsListener.Addr().String() + "/websocket"
}
237 changes: 237 additions & 0 deletions testutil/e2e/app_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package e2e

import (
"context"
"encoding/hex"
"net/http"
"testing"
"time"

"cosmossdk.io/depinject"
"cosmossdk.io/math"
abci "github.com/cometbft/cometbft/abci/types"
cometrpctypes "github.com/cometbft/cometbft/rpc/core/types"
"github.com/cometbft/cometbft/types"
cosmostx "github.com/cosmos/cosmos-sdk/client/tx"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
cosmostypes "github.com/cosmos/cosmos-sdk/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
"github.com/golang/mock/gomock"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"

"github.com/pokt-network/poktroll/app/volatile"
"github.com/pokt-network/poktroll/pkg/client/block"
"github.com/pokt-network/poktroll/pkg/client/events"
"github.com/pokt-network/poktroll/pkg/client/query"
"github.com/pokt-network/poktroll/pkg/client/tx"
txtypes "github.com/pokt-network/poktroll/pkg/client/tx/types"
"github.com/pokt-network/poktroll/testutil/integration"
"github.com/pokt-network/poktroll/testutil/mockclient"
"github.com/pokt-network/poktroll/testutil/testclient"
gatewaytypes "github.com/pokt-network/poktroll/x/gateway/types"
)

func TestGRPCServer2(t *testing.T) {
grpcServer := grpc.NewServer()
//gwKeeper := gatewaykeeper.NewKeeper()
//gwSvc := gatewaykeeper.NewMsgServerImpl(gwKeeper)

app := integration.NewCompleteIntegrationApp(t)
app.RegisterGRPCServer(grpcServer)

mux := runtime.NewServeMux()
err := http.ListenAndServe(":42070", mux)
require.NoError(t, err)
//gatewaytypes.RegisterMsgServer(grpcServer, gwSvc)
//gatewaytypes.RegisterMsgServer(app.MsgServiceRouter(), gwSvc)

//gatewaytypes.RegisterQueryHandlerFromEndpoint()

//reflectionService, err := services.NewReflectionService()
//require.NoError(t, err)

//desc, err := reflectionService.FileDescriptors(nil, nil)
//require.NoError(t, err)

//app := integration.NewCompleteIntegrationApp(t)
//grpcServer.RegisterService(desc, app.MsgServiceRouter())
}

func TestSanity(t *testing.T) {
app := integration.NewCompleteIntegrationApp(t)

//app.Query(nil, &authtypes.QueryAccountRequest{
// Address: "pokt1h04g6njyuv03dhd74a73pyzeadmd8dk7l9tsk8",
//})

//app.Query(nil, types2.RequestQuery{
// Data: nil,
// Path: "",
// Height: 0,
// Prove: false,
//})

ctrl := gomock.NewController(t)
blockQueryClient := mockclient.NewMockBlockQueryClient(ctrl)
blockQueryClient.EXPECT().
Block(gomock.Any(), gomock.Any()).
DoAndReturn(
func(ctx context.Context, height *int64) (*cometrpctypes.ResultBlock, error) {
blockResultMock := &cometrpctypes.ResultBlock{
Block: &types.Block{
Header: types.Header{
Height: 1,
},
},
}
return blockResultMock, nil
},
).AnyTimes()
deps := depinject.Supply(app.QueryHelper(), blockQueryClient)
sharedClient, err := query.NewSharedQuerier(deps)
require.NoError(t, err)

params, err := sharedClient.GetParams(app.GetSdkCtx())
require.NoError(t, err)

t.Logf("shared params: %+v", params)
}

func TestNewE2EApp(t *testing.T) {
initialHeight := int64(7553)
// TODO_IN_THIS_COMMIT: does this 👆 need to be reconciled with the internal height of app?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[linter-name (fail-on-found)] reported by reviewdog 🐶
// TODO_IN_THIS_COMMIT: does this 👆 need to be reconciled with the internal height of app?


app := NewE2EApp(t)

keyRing := keyring.NewInMemory(app.GetCodec())
rec, err := keyRing.NewAccount(
"gateway2",
"suffer wet jelly furnace cousin flip layer render finish frequent pledge feature economy wink like water disease final erase goat include apple state furnace",
"",
cosmostypes.FullFundraiserPath,
hd.Secp256k1,
)
require.NoError(t, err)

gateway2Addr, err := rec.GetAddress()
require.NoError(t, err)

// TODO_IN_THIS_COMMOT: fund gateway2 account.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[linter-name (fail-on-found)] reported by reviewdog 🐶
// TODO_IN_THIS_COMMOT: fund gateway2 account.

_, err = app.RunMsg(t, &banktypes.MsgSend{
FromAddress: app.GetFaucetBech32(),
ToAddress: gateway2Addr.String(),
Amount: cosmostypes.NewCoins(cosmostypes.NewInt64Coin(volatile.DenomuPOKT, 100000000)),
})
require.NoError(t, err)

ctrl := gomock.NewController(t)
blockQueryClient := mockclient.NewMockBlockQueryClient(ctrl)
blockQueryClient.EXPECT().
Block(gomock.Any(), gomock.Any()).
DoAndReturn(
func(ctx context.Context, height *int64) (*cometrpctypes.ResultBlock, error) {
//time.Sleep(time.Second * 100)
blockResultMock := &cometrpctypes.ResultBlock{
Block: &types.Block{
Header: types.Header{
Height: initialHeight,
},
},
}
return blockResultMock, nil
},
).AnyTimes()
//blockQueryClient, err := sdkclient.NewClientFromNode("tcp://127.0.0.1:42070")
//blockQueryClient, err := sdkclient.NewClientFromNode("tcp://127.0.0.1:26657")
//require.NoError(t, err)

deps := depinject.Supply(app.QueryHelper(), blockQueryClient)

sharedQueryClient, err := query.NewSharedQuerier(deps)
require.NoError(t, err)

sharedParams, err := sharedQueryClient.GetParams(app.GetSdkCtx())
require.NoError(t, err)

t.Logf("shared params: %+v", sharedParams)

eventsQueryClient := events.NewEventsQueryClient("ws://127.0.0.1:6969/websocket")
//eventsQueryClient := events.NewEventsQueryClient("ws://127.0.0.1:26657/websocket")
deps = depinject.Configs(deps, depinject.Supply(eventsQueryClient))
blockClient, err := block.NewBlockClient(app.GetSdkCtx(), deps)
require.NoError(t, err)

// TODO_IN_THIS_COMMIT: NOT localnet flagset NOR context, should be
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[linter-name (fail-on-found)] reported by reviewdog 🐶
// TODO_IN_THIS_COMMIT: NOT localnet flagset NOR context, should be

// configured to match the E2E app listeners.
flagSet := testclient.NewFlagSet(t, "127.0.0.1:42069")
clientCtx := testclient.NewLocalnetClientCtx(t, flagSet).WithKeyring(keyRing)

txFactory, err := cosmostx.NewFactoryCLI(clientCtx, flagSet)
require.NoError(t, err)

deps = depinject.Configs(deps, depinject.Supply(txtypes.Context(clientCtx), txFactory))

//_, txContext := testtx.NewE2ETxContext(t, keyRing, flagSet)
txContext, err := tx.NewTxContext(deps)
require.NoError(t, err)

deps = depinject.Configs(deps, depinject.Supply(blockClient, txContext))
txClient, err := tx.NewTxClient(app.GetSdkCtx(), deps, tx.WithSigningKeyName("gateway2"))
require.NoError(t, err)

time.Sleep(time.Second * 1)

eitherErr := txClient.SignAndBroadcast(
app.GetSdkCtx(),
gatewaytypes.NewMsgStakeGateway(
"pokt15w3fhfyc0lttv7r585e2ncpf6t2kl9uh8rsnyz",
cosmostypes.NewCoin(volatile.DenomuPOKT, math.NewInt(100000001)),
),
)

// TODO_IN_THIS_COMMIT: signal to the WS server to send another block result event...
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[linter-name (fail-on-found)] reported by reviewdog 🐶
// TODO_IN_THIS_COMMIT: signal to the WS server to send another block result event...

//app.NextBlock(t)

err, errCh := eitherErr.SyncOrAsyncError()
require.NoError(t, err)
require.NoError(t, <-errCh)
}

func TestGRPCServer(t *testing.T) {
app := NewE2EApp(t)
t.Cleanup(func() {
app.Close()
})

grpcConn, err := grpc.NewClient("tcp://127.0.0.1:42069", grpc.WithInsecure())

Check failure on line 209 in testutil/e2e/app_test.go

View workflow job for this annotation

GitHub Actions / go-test

SA1019: grpc.WithInsecure is deprecated: use WithTransportCredentials and insecure.NewCredentials() instead. Will be supported throughout 1.x. (staticcheck)
require.NoError(t, err)

dataHex, err := hex.DecodeString("0A2B706F6B74313577336668667963306C747476377235383565326E6370663674326B6C3975683872736E797A")
require.NoError(t, err)

req := &abci.RequestQuery{
Data: dataHex,
Path: "/cosmos.auth.v1beta1.Query/Account",
Height: 0,
Prove: false,
}
res := &abci.ResponseQuery{}

grpcConn.Invoke(context.Background(), "abci_query", req, res)

//"method" : "abci_query",
//"params" : {
// "data" : "0A2B706F6B74313577336668667963306C747476377235383565326E6370663674326B6C3975683872736E797A",
// "height" : "0",
// "path" : "/cosmos.auth.v1beta1.Query/Account",
// "prove" : false
//}

//"method" : "broadcast_tx_async",
//"params" : {
// "tx" : "CmsKZgohL3Bva3Ryb2xsLmdhdGV3YXkuTXNnU3Rha2VHYXRld2F5EkEKK3Bva3QxNXczZmhmeWMwbHR0djdyNTg1ZTJuY3BmNnQya2w5dWg4cnNueXoSEgoFdXBva3QSCTEwMDAwMDAwMRiGOxJYCk4KRgofL2Nvc21vcy5jcnlwdG8uc2VjcDI1NmsxLlB1YktleRIjCiEDZo2bY9XquUsFljtW/OKWVCDhYFf7NbidN4Y99VQ9438SBAoCCAESBhCqoYLJAhpAw5e7iJN5SpFit3fftxnZY7EDiFqupi7XEL3sUyeV0IBSQv2JZ7Cdu0dCG0yEVgj0xarkPi7dR10pNDL1gcUJxw=="
//}
}
Loading
Loading