Skip to content

Commit

Permalink
chore: In gnoNativeService, support using the remote Gnokey Mobile se…
Browse files Browse the repository at this point in the history
…rvice (#163)

PR gnolang/gnokey-mobile#2 creates the Gnokey
Mobile service. This PR updates `GnoNativeService` with the option to
use the remote Gnokey Mobile service as needed (for example, to sign a
transaction).

* In BridgeConfig and service.Config, add the config flag
`UseGnokeyMobile`.
* In `initService`, if `UseGnokeyMobile` then set `gnokeyMobileClient`
to a gRPC client connected to the remote Gnokey Mobile service. Set
`client` to a gnoclient without a Keybase (because the purpose is to use
Keybase in the remote Gnokey Mobile service).
* In `gnoNativeService`, add `getClient` which returns the correct
`gnoclient.Client`. If not `useGnokeyMobile` then simply return
`s.client` . If `useGnokeyMobile` then it's possible that Gnokey Mobile
has been changed to use a different gno.land remote, so we must call the
Gnokey Mobile method `GetRemote` . If this is different from our local
`s.remote` then reconfigure the `RPCClient` of the gnoclient and save
the new `s.remote`. Change `QueryAccount`, `Query`, `Render` and `QEval`
to call `getClient` and then proceed as normal. This means that if an
app is using Gno Native Kit with `UseGnokeyMobile`, then the app can
send these queries directly to the gno.land remote node, but we must
make sure that it is the correct remote.
* In `GetRemote` and `ListKeyInfo`, if `useGnokeyMobile` then we
directly use the Gnokey Mobile service.
* In `Call`, if `useGnokeyMobile` then use the local `MakeCallTx` to
make the transaction. Use the `SignTx` API of the remote Gnokey Mobile
service to sign the transaction. Then use the local `BroadcastTxCommit`
to broadcast the signed transaction and get the streaming result.

The above changes allow an app to configure its Gno Native Kit to use a
gRPC client with the Gnokey Mobile service. But we still need the Gnokey
Mobile app to start the Gnokey Mobile gRPC server. The following changes
allow the Gnokey Mobile app to set `start_gnokey_mobile_service` in [the
configuration](https://github.com/gnolang/dsocial/blob/925846f2055e9f107cd91ddb776afa3e7be06820/mobile/app/_layout.tsx#L10-L13)
for its `GnoNativeProvider`.

* In the Go `Bridge`, add `StartGnokeyMobileService`. See the
description comment. Add this method to the TypeScript
`GoBridgeInterface` and to the Java and Swift `GnonativeModule`.
* In the TypeScript `Config`, add a flag `start_gnokey_mobile_service`.
Check this flag in the `GnoNativeApi` `initClient` and call
`startGnokeyMobileService()`.

---------

Signed-off-by: Jeff Thompson <[email protected]>
  • Loading branch information
jefft0 authored Aug 14, 2024
1 parent a8ac845 commit 69d4f3e
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 4 deletions.
13 changes: 13 additions & 0 deletions expo/android/src/main/java/land/gno/gnonative/GnonativeModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ class GnonativeModule : Module() {
}
}

AsyncFunction("startGnokeyMobileService") { promise: Promise ->
try {
bridgeGnoNative?.let {
bridgeGnoNative!!.startGnokeyMobileService()
promise.resolve(true)
} ?: run {
throw GoBridgeNotStartedError()
}
} catch (err: CodedException) {
promise.reject(err)
}
}

AsyncFunction("invokeGrpcMethod") { method: String, jsonMessage: String, promise: Promise ->
try {
bridgeGnoNative?.let {
Expand Down
12 changes: 12 additions & 0 deletions expo/ios/GnonativeModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,18 @@ public class GnonativeModule: Module {
}
}

AsyncFunction("startGnokeyMobileService") { (promise: Promise) in
do {
guard let service = self.bridge else {
throw GnoError(.notStarted)
}
try service.startGnokeyMobileService()
promise.resolve(true)
} catch let error {
promise.reject(error)
}
}

AsyncFunction("closeBridge") { (promise: Promise) in
do {
guard let service = self.bridge else {
Expand Down
5 changes: 5 additions & 0 deletions expo/src/GoBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface GoBridgeInterface {
initBridge(): Promise<void>;
closeBridge(): Promise<void>;
getTcpPort(): Promise<number>;
startGnokeyMobileService(): Promise<void>;
invokeGrpcMethod(method: string, jsonMessage: string): Promise<string>;
createStreamClient(method: string, jsonMessage: string): Promise<string>;
streamClientReceive(id: string): Promise<string>;
Expand All @@ -23,6 +24,10 @@ class GoBridge implements GoBridgeInterface {
return GnonativeModule.getTcpPort();
}

startGnokeyMobileService(): Promise<void> {
return GnonativeModule.startGnokeyMobileService();
}

invokeGrpcMethod(method: string, jsonMessage: string): Promise<string> {
return GnonativeModule.invokeGrpcMethod(method, jsonMessage);
}
Expand Down
7 changes: 7 additions & 0 deletions expo/src/api/GnoNativeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export class GnoNativeApi implements GnoKeyApi, GoBridgeInterface {
new SetChainIDRequest({ chainId: this.config.chain_id }),
);
console.log('✅ GnoNative bridge initialized.');
if (this.config.start_gnokey_mobile_service) {
await this.startGnokeyMobileService();
console.log('✅ Gnokey Mobile service started.');
}
return true;
} catch (error) {
console.error(error);
Expand Down Expand Up @@ -380,6 +384,9 @@ export class GnoNativeApi implements GnoKeyApi, GoBridgeInterface {
getTcpPort(): Promise<number> {
return GoBridge.getTcpPort();
}
startGnokeyMobileService(): Promise<void> {
return GoBridge.startGnokeyMobileService();
}
invokeGrpcMethod(method: string, jsonMessage: string): Promise<string> {
return GoBridge.invokeGrpcMethod(method, jsonMessage);
}
Expand Down
2 changes: 2 additions & 0 deletions expo/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export enum BridgeStatus {
export interface Config {
remote: string;
chain_id: string;
// If true, initBridge also starts a Gnokey Mobile service. (Only needed for the Gnokey Mobile app.)
start_gnokey_mobile_service: boolean;
}

export interface GnoKeyApi {
Expand Down
32 changes: 32 additions & 0 deletions framework/service/bridge.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"github.com/peterbourgon/unixtransport"
"go.uber.org/multierr"

gnokey_mobile_service "github.com/gnolang/gnokey-mobile/service"
api_gen "github.com/gnolang/gnonative/api/gen/go"
"github.com/gnolang/gnonative/api/gen/go/_goconnect"
"github.com/gnolang/gnonative/service"
Expand All @@ -23,6 +24,7 @@ type BridgeConfig struct {
TmpDir string
UseTcpListener bool
DisableUdsListener bool
UseGnokeyMobile bool
}

func NewBridgeConfig() *BridgeConfig {
Expand All @@ -38,6 +40,8 @@ type Bridge struct {

serviceServer service.GnoNativeService

gnokeyMobileService gnokey_mobile_service.GnokeyMobileService

ServiceClient
}

Expand Down Expand Up @@ -77,6 +81,10 @@ func NewBridge(config *BridgeConfig) (*Bridge, error) {
svcOpts = append(svcOpts, service.WithDisableUdsListener())
}

if config.UseGnokeyMobile {
svcOpts = append(svcOpts, service.WithUseGnokeyMobile())
}

serviceServer, err := service.NewGnoNativeService(svcOpts...)
if err != nil {
return nil, errors.Wrap(err, "unable to create bridge service")
Expand Down Expand Up @@ -152,6 +160,28 @@ func (b *Bridge) GetTcpAddr() string {
return b.serviceServer.GetTcpAddr()
}

// Start the Gnokey Mobile service and save it in gnokeyMobileService. This will be closed in Close().
// If the gnonative serviceServer is not started, do nothing.
// If gnokeyMobileService is already started, do nothing.
func (b *Bridge) StartGnokeyMobileService() error {
if b.serviceServer == nil {
return nil
}
if b.gnokeyMobileService != nil {
// Already started
return nil
}

// Use the default options
gnokeyMobileService, err := gnokey_mobile_service.NewGnokeyMobileService(b.serviceServer)
if err != nil {
return err
}

b.gnokeyMobileService = gnokeyMobileService
return nil
}

func (b *Bridge) Close() error {
var errs error

Expand All @@ -177,6 +207,8 @@ func (b *Bridge) Close() error {
errs = multierr.Append(errs, err)
}

// TODO: Close b.gnokeyMobileService

cancel()
}

Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
connectrpc.com/grpchealth v1.3.0
connectrpc.com/grpcreflect v1.2.0
github.com/gnolang/gno v0.1.1
github.com/gnolang/gnokey-mobile v0.0.0-20240814140149-eb333b936c7c
github.com/oklog/run v1.1.0
github.com/peterbourgon/ff/v3 v3.4.0
github.com/peterbourgon/unixtransport v0.0.3
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gnolang/gnokey-mobile v0.0.0-20240814140149-eb333b936c7c h1:rL7dVjWOpdQxmbsh69HrgAklolhydTZmPvgo6BpgdhE=
github.com/gnolang/gnokey-mobile v0.0.0-20240814140149-eb333b936c7c/go.mod h1:2NrHp15t6QXGNDruOpw6/xN6z50kquVvZs6uxvUNhsQ=
github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216 h1:GKvsK3oLWG9B1GL7WP/VqwM6C92j5tIvB844oggL9Lk=
github.com/gnolang/overflow v0.0.0-20170615021017-4d914c927216/go.mod h1:xJhtEL7ahjM1WJipt89gel8tHzfIl/LyMY+lCYh38d8=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
Expand Down
91 changes: 87 additions & 4 deletions service/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ func (s *gnoNativeService) SetRemote(ctx context.Context, req *connect.Request[a
}

func (s *gnoNativeService) GetRemote(ctx context.Context, req *connect.Request[api_gen.GetRemoteRequest]) (*connect.Response[api_gen.GetRemoteResponse], error) {
if s.useGnokeyMobile {
// Always get the remote from the Gnokey Mobile service
res, err := s.gnokeyMobileClient.GetRemote(context.Background(), req)
if err != nil {
return nil, err
}

return connect.NewResponse(res.Msg), nil
}

return connect.NewResponse(&api_gen.GetRemoteResponse{Remote: s.ClientGetRemote()}), nil
}

Expand Down Expand Up @@ -77,6 +87,16 @@ func ConvertKeyInfo(key crypto_keys.Info) (*api_gen.KeyInfo, error) {
func (s *gnoNativeService) ListKeyInfo(ctx context.Context, req *connect.Request[api_gen.ListKeyInfoRequest]) (*connect.Response[api_gen.ListKeyInfoResponse], error) {
s.logger.Debug("ListKeyInfo called")

if s.useGnokeyMobile {
// Always get the list of keys from the Gnokey Mobile service
res, err := s.gnokeyMobileClient.ListKeyInfo(context.Background(), req)
if err != nil {
return nil, err
}

return connect.NewResponse(res.Msg), nil
}

keys, err := s.ClientListKeyInfo()
if err != nil {
return nil, err
Expand Down Expand Up @@ -272,8 +292,12 @@ func (s *gnoNativeService) GetActiveAccount(ctx context.Context, req *connect.Re
func (s *gnoNativeService) QueryAccount(ctx context.Context, req *connect.Request[api_gen.QueryAccountRequest]) (*connect.Response[api_gen.QueryAccountResponse], error) {
s.logger.Debug("QueryAccount", zap.ByteString("address", req.Msg.Address))

c, err := s.getClient()
if err != nil {
return nil, getGrpcError(err)
}
// gnoclient wants the bech32 address.
account, _, err := s.client.QueryAccount(crypto.AddressFromBytes(req.Msg.Address))
account, _, err := c.QueryAccount(crypto.AddressFromBytes(req.Msg.Address))
if err != nil {
return nil, getGrpcError(err)
}
Expand Down Expand Up @@ -324,7 +348,11 @@ func (s *gnoNativeService) Query(ctx context.Context, req *connect.Request[api_g
Data: req.Msg.Data,
}

bres, err := s.client.Query(cfg)
c, err := s.getClient()
if err != nil {
return nil, getGrpcError(err)
}
bres, err := c.Query(cfg)
if err != nil {
return nil, getGrpcError(err)
}
Expand All @@ -335,7 +363,11 @@ func (s *gnoNativeService) Query(ctx context.Context, req *connect.Request[api_g
func (s *gnoNativeService) Render(ctx context.Context, req *connect.Request[api_gen.RenderRequest]) (*connect.Response[api_gen.RenderResponse], error) {
s.logger.Debug("Render", zap.String("packagePath", req.Msg.PackagePath), zap.String("args", req.Msg.Args))

result, _, err := s.client.Render(req.Msg.PackagePath, req.Msg.Args)
c, err := s.getClient()
if err != nil {
return nil, getGrpcError(err)
}
result, _, err := c.Render(req.Msg.PackagePath, req.Msg.Args)
if err != nil {
return nil, getGrpcError(err)
}
Expand All @@ -346,7 +378,11 @@ func (s *gnoNativeService) Render(ctx context.Context, req *connect.Request[api_
func (s *gnoNativeService) QEval(ctx context.Context, req *connect.Request[api_gen.QEvalRequest]) (*connect.Response[api_gen.QEvalResponse], error) {
s.logger.Debug("QEval", zap.String("packagePath", req.Msg.PackagePath), zap.String("expression", req.Msg.Expression))

result, _, err := s.client.QEval(req.Msg.PackagePath, req.Msg.Expression)
c, err := s.getClient()
if err != nil {
return nil, getGrpcError(err)
}
result, _, err := c.QEval(req.Msg.PackagePath, req.Msg.Expression)
if err != nil {
return nil, getGrpcError(err)
}
Expand All @@ -361,6 +397,53 @@ func (s *gnoNativeService) Call(ctx context.Context, req *connect.Request[api_ge

cfg, msgs := convertCallRequest(req.Msg)

if s.useGnokeyMobile {
c, err := s.getClient()
if err != nil {
return getGrpcError(err)
}
tx, err := c.MakeCallTx(*cfg, msgs...)
if err != nil {
return err
}
txJSON, err := amino.MarshalJSON(tx)
if err != nil {
return err
}

// Use Gnokey Mobile to sign.
// Note that req.Msg.CallerAddress must be set to the desired signer. The app can get the
// address using ListKeyInfo.
signedTxJSON, err := s.gnokeyMobileClient.SignTx(
context.Background(),
connect.NewRequest(&api_gen.SignTxRequest{
TxJson: string(txJSON),
}),
)
if err != nil {
return err
}
signedTx := &std.Tx{}
if err := amino.UnmarshalJSON([]byte(signedTxJSON.Msg.SignedTxJson), signedTx); err != nil {
return err
}

// Now broadcast
bres, err := c.BroadcastTxCommit(signedTx)
if err != nil {
return getGrpcError(err)
}

if err := stream.Send(&api_gen.CallResponse{
Result: bres.DeliverTx.Data,
}); err != nil {
s.logger.Error("Call stream.Send returned error", zap.Error(err))
return err
}

return nil
}

s.lock.RLock()
if s.activeAccount == nil {
s.lock.RUnlock()
Expand Down
11 changes: 11 additions & 0 deletions service/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type Config struct {
UdsPath string
UseTcpListener bool
DisableUdsListener bool
UseGnokeyMobile bool
}

type GnoNativeOption func(cfg *Config) error
Expand Down Expand Up @@ -327,6 +328,16 @@ var WithDisableUdsListener = func() GnoNativeOption {
}
}

// --- Gnokey Mobile options ---

// WithUseGnokeyMobile sets the gRPC service to use Gnokey Mobile for key-based operations.
var WithUseGnokeyMobile = func() GnoNativeOption {
return func(cfg *Config) error {
cfg.UseGnokeyMobile = true
return nil
}
}

// --- Fallback options ---

var defaults = []FallBackOption{
Expand Down
Loading

0 comments on commit 69d4f3e

Please sign in to comment.