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

feat: initial implementation of grpc #14

Merged
merged 19 commits into from
Sep 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ cocoapods 1.12.1
java openjdk-18.0.1.1
yarn 1.22.19
golang 1.20.7
buf 1.15.1
35 changes: 33 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ build.ios: generate $(gnocore_xcframework)
build.android: generate $(gnocore_aar) $(gnocore_jar)
cd $(react_native_dir); $(MAKE) node_modules

# Generate API from protofiles
generate: api.generate

# Clean all generated files
clean: bind.clean

Expand All @@ -56,7 +59,35 @@ fclean: clean
rm -rf node_modules
rm -rf $(cache_dir)

.PHONY: generate build.ios build.android fclean
.PHONY: generate build.ios build.android clean fclean

# - API : Handle API generation and cleaning

api.generate: _api.generate.protocol
api.clean: _api.clean.protocol

# - API - gnomobiletypes

protos_src := $(wildcard api/*.proto)
gen_src := $(protos_src) Makefile
gen_sum := gen.sum

_api.generate.protocol: $(gen_sum)
_api.clean.protocol:
rm -f framework/service/gnomobiletypes/*.pb.go
rm -f gnoboard/android/app/src/main/java/land/gno/gnomobile/*.java

$(gen_sum): $(gen_src)
$(call check-program, shasum buf)
@shasum $(gen_src) | sort -k 2 > $(gen_sum).tmp
@diff -q $(gen_sum).tmp $(gen_sum) || ( \
buf generate api; \
shasum $(gen_src) | sort -k 2 > $(gen_sum).tmp; \
mv $(gen_sum).tmp $(gen_sum); \
go mod tidy \
)

.PHONY: api.generate _api.generate.protocol _api.clean.protocol

# - Bind : Handle gomobile bind

Expand Down Expand Up @@ -97,7 +128,7 @@ $(gnocore_aar): $(bind_init_files) $(go_deps)
@mkdir -p $(dir $@) .cache/bind/android
$(gomobile) bind -v \
-cache $(cache_dir)/android-gmomobile \
-javapkg=gnoland.gno \
-javapkg=gnolang.gno \
-o $@ -target android -androidapi 21 ./framework
_bind.clean.android:
rm -rf $(gnocore_jar) $(gnocore_aar)
Expand Down
8 changes: 8 additions & 0 deletions api/buf.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
version: v1
name: buf.build/gnolang/gnomobile
breaking:
use:
- FILE
lint:
use:
- DEFAULT
148 changes: 148 additions & 0 deletions api/gnomobiletypes.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
syntax = "proto3";

package gnomobile.v1;

option go_package = "github.com/gnolang/gnomobile/service/gnomobiletypes";
option java_package = "land.gno.gnomobile";
option objc_class_prefix = "RTG";

// GnomobileService is the service to interact with the Gno blockchain
service GnomobileService {
// Set the connection addresse for the remote node. If you don't call this,
// the default is "127.0.0.1:26657"
rpc SetRemote(SetRemote.Request) returns (SetRemote.Reply);

// Set the chain ID for the remote node. If you don't call this, the default
// is "dev"
rpc SetChainID(SetChainID.Request) returns (SetChainID.Reply);

// Set the nameOrBech32 for the account in the keybase, used for later
// operations
rpc SetNameOrBech32(SetNameOrBech32.Request) returns (SetNameOrBech32.Reply);

// Set the password for the account in the keybase, used for later operations
rpc SetPassword(SetPassword.Request) returns (SetPassword.Reply);

// Get the keys informations in the keybase
rpc ListKeyInfo(ListKeyInfo.Request) returns (ListKeyInfo.Reply);

// Create a new account the keybase using the name an password specified by
// SetAccount
rpc CreateAccount(CreateAccount.Request) returns (CreateAccount.Reply);

// SelectAccount selects the account to use for later operations
rpc SelectAccount(SelectAccount.Request) returns (SelectAccount.Reply);

// Make an ABCI query to the remote node.
rpc Query(Query.Request) returns (Query.Reply);

// Call a specific realm function.
rpc Call(Call.Request) returns (Call.Reply);
}

message SetRemote {
message Request { string remote = 1; }
message Reply {}
}

message SetChainID {
message Request { string chainID = 1; }
message Reply {}
}

message SetNameOrBech32 {
message Request { string nameOrBech32 = 1; }
message Reply {}
}

message SetPassword {
message Request { string password = 1; }
message Reply {}
}

enum KeyType {
TypeLocal = 0;
TypeLedger = 1;
TypeOffline = 2;
TypeMulti = 3;
}

message KeyInfo {
KeyType type = 1;
string name = 2;
bytes pubKey = 3;
bytes address = 4;
bytes path = 5;
}

message ListKeyInfo {
message Request {}
message Reply { repeated KeyInfo keys = 1; }
}

message CreateAccount {
message Request {
string nameOrBech32 = 1;
string mnemonic = 2;
string bip39Passwd = 3;
string password = 4;
uint32 account = 5;
uint32 index = 6;
}
message Reply { KeyInfo key = 1; }
}

message SelectAccount {
message Request { string nameOrBech32 = 1; }
message Reply { KeyInfo key = 1; }
}

message Query {
message Request {
string path = 1; // Example: "vm/qrender"
string data = 2; // Example: "gno.land/r/demo/boards\ntestboard"
}
message Reply { bytes result = 1; }
}

message Call {
message Request {
string packagePath = 1; // Example: "gno.land/r/demo/boards"
string fnc = 2; // Example: "CreateReply"
repeated string args = 3; // list of arguments specific to the function
string gasFee = 4;
int64 gasWanted = 5;
string password = 6;
}
message Reply { bytes result = 1; }
}

enum ErrCode {
//----------------
// Special errors
//----------------

Undefined = 0; // default value, should never be set manually

TODO = 1; // indicates that you plan to create an error later
ErrNotImplemented = 2; // indicates that a method is not implemented yet
ErrInternal = 3; // indicates an unknown error (without Code), i.e. in gRPC

//----------------
// Generic errors
//----------------

// Parameters and I/O errors

ErrInvalidInput = 100;
ErrBridgeInterrupted = 101;
ErrMissingInput = 102;
ErrSerialization = 103;
ErrDeserialization = 104;
ErrCryptoKeyTypeUnknown = 105;
ErrCryptoKeyNotFound = 106;
ErrNoActiveAccount = 107;
ErrRunGRPCServer = 108;
}

message ErrDetails { repeated ErrCode codes = 1; }
21 changes: 21 additions & 0 deletions buf.gen.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
version: v1
plugins:
- plugin: buf.build/protocolbuffers/go
out: ./
opt: module=github.com/gnolang/gnomobile
- plugin: buf.build/grpc/go
out: ./
opt: module=github.com/gnolang/gnomobile
- plugin: buf.build/grpc/java:v1.57.2
out: gnoboard/android/app/src/main/java
opt: lite
- plugin: buf.build/protocolbuffers/java
out: gnoboard/android/app/src/main/java
opt: lite
- plugin: buf.build/grpc/swift:v1.19.1
out: gnoboard/ios/Sources/api
opt: Client=true,Server=false
# dependencies
- plugin: buf.build/apple/swift
out: gnoboard/ios/Sources/api
opt: Visibility=Public
138 changes: 138 additions & 0 deletions framework/bridge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package gnomobile

import (
"context"
"sync"
"time"

"github.com/gnolang/gno/tm2/pkg/errors"
"github.com/oklog/run"
"go.uber.org/multierr"

"github.com/gnolang/gnomobile/service"
"github.com/gnolang/gnomobile/service/gnomobiletypes"
)

type PromiseBlock interface {
CallResolve(reply string)
CallReject(error error)
}

type BridgeConfig struct {
RootDir string
TmpDir string
UseTcpListener bool
}

func NewBridgeConfig() *BridgeConfig {
return &BridgeConfig{}
}

type Bridge struct {
errc chan error
closec chan struct{}

onceCloser sync.Once
workers run.Group

serviceServer service.GnomobileService
}

func NewBridge(config *BridgeConfig) (*Bridge, error) {
svcOpts := []service.GnomobileOption{}

// create bridge instance
b := &Bridge{
errc: make(chan error),
closec: make(chan struct{}),
}

// create cancel service
{
b.workers.Add(func() error {
// wait for closing signal
<-b.closec
return gnomobiletypes.ErrCode_ErrBridgeInterrupted
}, func(error) {
b.onceCloser.Do(func() { close(b.closec) })
})
}

// start gRPC service
{
svcOpts = append(svcOpts,
service.WithRootDir(config.RootDir),
service.WithTmpDir(config.TmpDir),
service.WithTcpListener(config.UseTcpListener),
)

serviceServer, err := service.NewGnomobileService(svcOpts...)
if err != nil {
return nil, errors.Wrap(err, "unable to create bridge service")
}
b.serviceServer = serviceServer
}

// start Bridge
go func() {
b.errc <- b.workers.Run()
}()

return b, nil
}

func (b *Bridge) GetSocketPath() string {
if b.serviceServer == nil {
return ""
}

return b.serviceServer.GetSocketPath()
}

func (b *Bridge) GetTcpPort() int {
if b.serviceServer == nil {
return 0
}

return b.serviceServer.GetTcpPort()
}

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

// close gRPC bridge
if !b.isClosed() {
// send close signal
b.onceCloser.Do(func() { close(b.closec) })

// set close timeout
ctx, cancel := context.WithTimeout(context.Background(), time.Second*4)

// wait or die
var err error
select {
case err = <-b.errc:
case <-ctx.Done():
err = ctx.Err()
}

b.serviceServer.Close()

if !gnomobiletypes.Is(err, gnomobiletypes.ErrCode_ErrBridgeInterrupted) {
errs = multierr.Append(errs, err)
}

cancel()
}

return errs
}

func (b *Bridge) isClosed() bool {
select {
case <-b.closec:
return true
default:
return false
}
}
Loading