-
Notifications
You must be signed in to change notification settings - Fork 370
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
Kava gRPC Client #1784
Kava gRPC Client #1784
Changes from 8 commits
1a46674
d42354f
2685253
b68f152
d083188
ce70a1a
1cea368
b6a77f0
199a616
1365e00
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
# Kava gRPC Client | ||
|
||
The Kava gRPC client is a tool for making gRPC queries on a Kava chain. | ||
|
||
## Features | ||
|
||
- Easy-to-use gRPC client for the Kava chain. | ||
- Access all query clients for Cosmos and Kava modules using `client.Query` (e.g., `client.Query.Bank.Balance`). | ||
- Utilize utility functions for common queries (e.g., `client.BaseAccount(str)`). | ||
|
||
## Usage | ||
|
||
### Creating a new client | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
kavaGrpc "github.com/kava-labs/kava/client/grpc" | ||
) | ||
grpcUrl := "https://grpc.kava.io:443" | ||
client, err := kavaGrpc.NewClient(url) | ||
DracoLi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
panic(err) | ||
} | ||
``` | ||
|
||
### Making grpc queries | ||
|
||
Query clients for both Cosmos and Kava modules are available via `client.Query`. | ||
|
||
Example: Query Cosmos module `x/bank` for address balance | ||
|
||
```go | ||
import ( | ||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" | ||
) | ||
|
||
rsp, err := client.Query.Bank.Balance(context.Background(), &banktypes.QueryBalanceRequest{ | ||
Address: "kava19rjk5qmmwywnzfccwzyn02jywgpwjqf60afj92", | ||
Denom: "ukava", | ||
}) | ||
``` | ||
|
||
Example: Query Kava module `x/evmutil` for params | ||
|
||
```go | ||
import ( | ||
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" | ||
) | ||
|
||
rsp, err := client.Query.Evmutil.Params( | ||
context.Background(), &evmutiltypes.QueryParamsRequest{}, | ||
) | ||
``` | ||
|
||
#### Query Utilities | ||
|
||
Utility functions for common queries are available directly on the client. | ||
|
||
Example: Util query to get a base account | ||
|
||
```go | ||
kavaAcc := "kava19rjk5qmmwywnzfccwzyn02jywgpwjqf60afj92" | ||
rsp, err := client.BaseAccount(kavaAcc) | ||
if err != nil { | ||
panic(err) | ||
} | ||
fmt.Printf("account sequence for %s: %d\n", kavaAcc, rsp.Sequence) | ||
``` | ||
|
||
## Query Tests | ||
|
||
To test queries, a Kava node is required. Therefore, the e2e tests for the gRPC client queries can be found in the `tests/e2e` directory. Tests for new utility queries should be added as e2e tests under the `test/e2e` directory. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package grpc | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/kava-labs/kava/client/grpc/query" | ||
"github.com/kava-labs/kava/client/grpc/util" | ||
) | ||
|
||
// KavaGrpcClient enables the usage of kava grpc query clients and query utils | ||
type KavaGrpcClient struct { | ||
config KavaGrpcClientConfig | ||
|
||
// Query clients for cosmos and kava modules | ||
Query *query.QueryClient | ||
|
||
// Utils for common queries (ie fetch an unpacked BaseAccount) | ||
*util.Util | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggest "sdk" as a more intention revealing name here as we're builidng a higher level interface for making common dApp workflows simpler without having to make n number of calls to multiple api's to stitch together the end result that a dApp developer or kava engineer writing tests would care about There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so like |
||
} | ||
|
||
// KavaGrpcClientConfig is a configuration struct for a KavaGrpcClient | ||
type KavaGrpcClientConfig struct { | ||
// note: add future config options here | ||
} | ||
|
||
// NewClient creates a new KavaGrpcClient via a grpc url | ||
func NewClient(grpcUrl string) (*KavaGrpcClient, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. recommend adding url as a field to the config struct above and taking that as the only input to this function There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. majority of the time the config won't be needed and is optional, so I think some benefit in splitting it as that allows the client to be created with less code. Thoughts? |
||
return NewClientWithConfig(grpcUrl, NewDefaultConfig()) | ||
} | ||
|
||
// NewClientWithConfig creates a new KavaGrpcClient via a grpc url and config | ||
func NewClientWithConfig(grpcUrl string, config KavaGrpcClientConfig) (*KavaGrpcClient, error) { | ||
if grpcUrl == "" { | ||
return nil, errors.New("grpc url cannot be empty") | ||
} | ||
query, error := query.NewQueryClient(grpcUrl) | ||
if error != nil { | ||
return nil, error | ||
} | ||
client := &KavaGrpcClient{ | ||
Query: query, | ||
Util: util.NewUtil(query), | ||
config: config, | ||
} | ||
return client, nil | ||
} | ||
|
||
func NewDefaultConfig() KavaGrpcClientConfig { | ||
return KavaGrpcClientConfig{} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package grpc_test | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/kava-labs/kava/client/grpc" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestNewClient_InvalidEndpoint(t *testing.T) { | ||
_, err := grpc.NewClient("invalid-url") | ||
require.ErrorContains(t, err, "unknown grpc url scheme") | ||
_, err = grpc.NewClient("") | ||
require.ErrorContains(t, err, "grpc url cannot be empty") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package query | ||
|
||
import ( | ||
"context" | ||
"crypto/tls" | ||
"fmt" | ||
"net/url" | ||
|
||
"google.golang.org/grpc" | ||
"google.golang.org/grpc/credentials" | ||
"google.golang.org/grpc/credentials/insecure" | ||
) | ||
|
||
// newGrpcConnection parses a GRPC endpoint and creates a connection to it | ||
func newGrpcConnection(ctx context.Context, endpoint string) (*grpc.ClientConn, error) { | ||
grpcUrl, err := url.Parse(endpoint) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to parse grpc connection \"%s\": %v", endpoint, err) | ||
} | ||
|
||
var creds credentials.TransportCredentials | ||
switch grpcUrl.Scheme { | ||
case "http": | ||
creds = insecure.NewCredentials() | ||
case "https": | ||
creds = credentials.NewTLS(&tls.Config{}) | ||
default: | ||
return nil, fmt.Errorf("unknown grpc url scheme: %s", grpcUrl.Scheme) | ||
} | ||
|
||
secureOpt := grpc.WithTransportCredentials(creds) | ||
grpcConn, err := grpc.DialContext(ctx, grpcUrl.Host, secureOpt) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return grpcConn, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
/* | ||
The query package includes Cosmos and Kava gRPC query clients. | ||
|
||
To ensure that the `QueryClient` stays updated, add new module query clients | ||
to the `QueryClient` whenever new modules with grpc queries are added to the Kava app. | ||
*/ | ||
package query |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package query | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice" | ||
txtypes "github.com/cosmos/cosmos-sdk/types/tx" | ||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" | ||
authz "github.com/cosmos/cosmos-sdk/x/authz" | ||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" | ||
disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" | ||
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types" | ||
govv1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1" | ||
govv1beta1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" | ||
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" | ||
paramstypes "github.com/cosmos/cosmos-sdk/x/params/types/proposal" | ||
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types" | ||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" | ||
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" | ||
|
||
ibctransfertypes "github.com/cosmos/ibc-go/v6/modules/apps/transfer/types" | ||
ibcclienttypes "github.com/cosmos/ibc-go/v6/modules/core/02-client/types" | ||
evmtypes "github.com/evmos/ethermint/x/evm/types" | ||
feemarkettypes "github.com/evmos/ethermint/x/feemarket/types" | ||
|
||
auctiontypes "github.com/kava-labs/kava/x/auction/types" | ||
bep3types "github.com/kava-labs/kava/x/bep3/types" | ||
cdptypes "github.com/kava-labs/kava/x/cdp/types" | ||
committeetypes "github.com/kava-labs/kava/x/committee/types" | ||
communitytypes "github.com/kava-labs/kava/x/community/types" | ||
earntypes "github.com/kava-labs/kava/x/earn/types" | ||
evmutiltypes "github.com/kava-labs/kava/x/evmutil/types" | ||
hardtypes "github.com/kava-labs/kava/x/hard/types" | ||
incentivetypes "github.com/kava-labs/kava/x/incentive/types" | ||
issuancetypes "github.com/kava-labs/kava/x/issuance/types" | ||
kavadisttypes "github.com/kava-labs/kava/x/kavadist/types" | ||
liquidtypes "github.com/kava-labs/kava/x/liquid/types" | ||
pricefeedtypes "github.com/kava-labs/kava/x/pricefeed/types" | ||
savingstypes "github.com/kava-labs/kava/x/savings/types" | ||
swaptypes "github.com/kava-labs/kava/x/swap/types" | ||
) | ||
|
||
// QueryClient is a wrapper with all Cosmos and Kava grpc query clients | ||
type QueryClient struct { | ||
// cosmos-sdk query clients | ||
|
||
Tm tmservice.ServiceClient | ||
Tx txtypes.ServiceClient | ||
Auth authtypes.QueryClient | ||
Authz authz.QueryClient | ||
Bank banktypes.QueryClient | ||
Distribution disttypes.QueryClient | ||
Evidence evidencetypes.QueryClient | ||
Gov govv1types.QueryClient | ||
GovBeta govv1beta1types.QueryClient | ||
Mint minttypes.QueryClient | ||
Params paramstypes.QueryClient | ||
Slashing slashingtypes.QueryClient | ||
Staking stakingtypes.QueryClient | ||
Upgrade upgradetypes.QueryClient | ||
|
||
// 3rd party query clients | ||
|
||
Evm evmtypes.QueryClient | ||
Feemarket feemarkettypes.QueryClient | ||
IbcClient ibcclienttypes.QueryClient | ||
IbcTransfer ibctransfertypes.QueryClient | ||
|
||
// kava module query clients | ||
|
||
Auction auctiontypes.QueryClient | ||
Bep3 bep3types.QueryClient | ||
Cdp cdptypes.QueryClient | ||
Committee committeetypes.QueryClient | ||
Community communitytypes.QueryClient | ||
Earn earntypes.QueryClient | ||
Evmutil evmutiltypes.QueryClient | ||
Hard hardtypes.QueryClient | ||
Incentive incentivetypes.QueryClient | ||
Issuance issuancetypes.QueryClient | ||
Kavadist kavadisttypes.QueryClient | ||
Liquid liquidtypes.QueryClient | ||
Pricefeed pricefeedtypes.QueryClient | ||
Savings savingstypes.QueryClient | ||
Swap swaptypes.QueryClient | ||
} | ||
|
||
// NewQueryClient creates a new QueryClient and initializes all the module query clients | ||
func NewQueryClient(grpcEndpoint string) (*QueryClient, error) { | ||
conn, err := newGrpcConnection(context.Background(), grpcEndpoint) | ||
if err != nil { | ||
return &QueryClient{}, err | ||
} | ||
client := &QueryClient{ | ||
Tm: tmservice.NewServiceClient(conn), | ||
Tx: txtypes.NewServiceClient(conn), | ||
Auth: authtypes.NewQueryClient(conn), | ||
Authz: authz.NewQueryClient(conn), | ||
Bank: banktypes.NewQueryClient(conn), | ||
Distribution: disttypes.NewQueryClient(conn), | ||
Evidence: evidencetypes.NewQueryClient(conn), | ||
Gov: govv1types.NewQueryClient(conn), | ||
GovBeta: govv1beta1types.NewQueryClient(conn), | ||
Mint: minttypes.NewQueryClient(conn), | ||
Params: paramstypes.NewQueryClient(conn), | ||
Slashing: slashingtypes.NewQueryClient(conn), | ||
Staking: stakingtypes.NewQueryClient(conn), | ||
Upgrade: upgradetypes.NewQueryClient(conn), | ||
|
||
Evm: evmtypes.NewQueryClient(conn), | ||
Feemarket: feemarkettypes.NewQueryClient(conn), | ||
IbcClient: ibcclienttypes.NewQueryClient(conn), | ||
IbcTransfer: ibctransfertypes.NewQueryClient(conn), | ||
|
||
Auction: auctiontypes.NewQueryClient(conn), | ||
Bep3: bep3types.NewQueryClient(conn), | ||
Cdp: cdptypes.NewQueryClient(conn), | ||
Committee: committeetypes.NewQueryClient(conn), | ||
Community: communitytypes.NewQueryClient(conn), | ||
Earn: earntypes.NewQueryClient(conn), | ||
Evmutil: evmutiltypes.NewQueryClient(conn), | ||
Hard: hardtypes.NewQueryClient(conn), | ||
Incentive: incentivetypes.NewQueryClient(conn), | ||
Issuance: issuancetypes.NewQueryClient(conn), | ||
Kavadist: kavadisttypes.NewQueryClient(conn), | ||
Liquid: liquidtypes.NewQueryClient(conn), | ||
Pricefeed: pricefeedtypes.NewQueryClient(conn), | ||
Savings: savingstypes.NewQueryClient(conn), | ||
Swap: swaptypes.NewQueryClient(conn), | ||
} | ||
return client, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DracoLi found out that trying to use this client from another package (kava's ethermint forked) failed with error during go dependency resolution,
only fix was to update the go.mod with the same replaces as we have in kava's go.mod
probably worth calling out in the readme to save someone else time in resolving it (ideally this is why I would like to see this client in a seperate package, by keeping it in the kava monorepo we aren't really dogfooding the client and feeling / fixing pain points that a third party developer would go through cc @karzak @Behdad-Kava )
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also it not being a separate package is leading to import cycles trying to use this in ethermint as the client for talking to the cosmos sdk...ending up falling back to copy pasting all the code in this package for setting up the grpc connection, defeating the very purpose of this client in terms of being the one true / last implementation of a go client for talking to the kava grpc endpoints
this is the code I would be able to write if this was a standalone package, now it's gonna be a lot more lines / copy pasting / duplication of effort
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some thoughts:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my two cents 😄
attempting to use the grpc client in ethermint is not the same as including it in a service that interacts with the chain. i don't think a dependency of kava should attempt to import this grpc client. it's intended end user is a service interacting with the chain, not the chain interacting with itself
regarding the replaces, it's been my experience that you always need to replaces to match kava's. not sure if there's a more ergonomic way around it..