Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
abukosek committed May 4, 2020
1 parent f4c881f commit 37424c8
Show file tree
Hide file tree
Showing 14 changed files with 1,205 additions and 4 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,9 @@
*.o
*.so
oasis-core-rosetta-gateway
tests/oasis-net-runner
tests/oasis-node
tests/oasis_core_release.tar.gz
tests/oasis-core
tests/rosetta-cli*
tests/validator-data
56 changes: 54 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#!/usr/bin/env gmake

OASIS_RELEASE := 20.5.1
ROSETTA_CLI_RELEASE := 0.1.3

OASIS_GO ?= go
GO := env -u GOPATH $(OASIS_GO)
GOLINT := env -u GOPATH golangci-lint
Expand All @@ -25,6 +28,21 @@ OFF = ""
ECHO = echo
endif

# Check which tool to use for downloading.
HAVE_WGET := $(shell which wget > /dev/null && echo 1)
ifdef HAVE_WGET
DOWNLOAD := wget --quiet --show-progress --progress=bar:force:noscroll -O
else
HAVE_CURL := $(shell which curl > /dev/null && echo 1)
ifdef HAVE_CURL
DOWNLOAD := curl --progress-bar --location -o
else
$(error Please install wget or curl)
endif
endif

ROOT := $(shell dirname $(realpath $(firstword $(MAKEFILE_LIST))))

.PHONY: all build clean fmt lint nuke test

all: build
Expand All @@ -34,9 +52,35 @@ build:
@$(ECHO) "$(CYAN)*** Building...$(OFF)"
@$(GO) build

test:
tests/oasis_core_release.tar.gz:
@$(ECHO) "$(MAGENTA)*** Downloading oasis-core release $(OASIS_RELEASE)...$(OFF)"
# @$(DOWNLOAD) $@ https://github.com/oasislabs/oasis-core/releases/download/v$(OASIS_RELEASE)/oasis_core_$(OASIS_RELEASE)_linux_amd64.tar.gz
# Make a fake archive until a release with the latest net runner is made.
@cd tests && git clone [email protected]:oasislabs/oasis-core.git --depth 1
@cd tests/oasis-core/go && make oasis-net-runner oasis-node
@tar -zcf $@ -C $(ROOT)/tests/oasis-core/go/oasis-net-runner oasis-net-runner -C $(ROOT)/tests/oasis-core/go/oasis-node oasis-node

tests/oasis-net-runner: tests/oasis_core_release.tar.gz
@$(ECHO) "$(MAGENTA)*** Unpacking oasis-net-runner...$(OFF)"
@tar -xf $< -C tests oasis-net-runner

tests/oasis-node: tests/oasis_core_release.tar.gz
@$(ECHO) "$(MAGENTA)*** Unpacking oasis-node...$(OFF)"
@tar -xf $< -C tests oasis-node

tests/rosetta-cli.tar.gz:
@$(ECHO) "$(MAGENTA)*** Downloading rosetta-cli release $(ROSETTA_CLI_RELEASE)...$(OFF)"
@$(DOWNLOAD) $@ https://github.com/coinbase/rosetta-cli/archive/v$(ROSETTA_CLI_RELEASE).tar.gz

tests/rosetta-cli: tests/rosetta-cli.tar.gz
@$(ECHO) "$(MAGENTA)*** Building rosetta-cli...$(OFF)"
@tar -xf $< -C tests
@cd tests/rosetta-cli-$(ROSETTA_CLI_RELEASE) && go build
@cp tests/rosetta-cli-$(ROSETTA_CLI_RELEASE)/rosetta-cli tests/.

test: build tests/oasis-net-runner tests/oasis-node tests/rosetta-cli
@$(ECHO) "$(CYAN)*** Running tests...$(OFF)"
@$(GO) test --timeout 2m -race -v ./...
@$(ROOT)/tests/test.sh

fmt:
@$(ECHO) "$(CYAN)*** Formatting code...$(OFF)"
Expand All @@ -49,7 +93,15 @@ lint:
clean:
@$(ECHO) "$(CYAN)*** Cleaning up...$(OFF)"
@$(GO) clean
@-rm -f tests/oasis_core_release.tar.gz tests/oasis-net-runner tests/oasis-node
@-rm -rf tests/oasis-core
@-rm -f tests/rosetta-cli.tar.gz tests/rosetta-cli
@-rm -rf tests/rosetta-cli-$(ROSETTA_CLI_RELEASE) tests/validator-data

nuke:
@$(ECHO) "$(CYAN)*** Cleaning up really well...$(OFF)"
@$(GO) clean -cache -testcache -modcache
@-rm -f tests/oasis_core_release.tar.gz tests/oasis-net-runner tests/oasis-node
@-rm -rf tests/oasis-core
@-rm -f tests/rosetta-cli.tar.gz tests/rosetta-cli
@-rm -rf tests/rosetta-cli-$(ROSETTA_CLI_RELEASE) tests/validator-data
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
module github.com/oasislabs/oasis-core-rosetta-gateway

go 1.14

replace github.com/tendermint/tendermint => github.com/oasislabs/tendermint v0.33.4-oasis1

require (
github.com/coinbase/rosetta-sdk-go v0.1.5
github.com/oasislabs/oasis-core/go v0.0.0-20200429090753-8642c9ab7688
github.com/tendermint/tendermint v0.33.4 // indirect
google.golang.org/grpc v1.28.1
)
59 changes: 57 additions & 2 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,62 @@
package main

import "fmt"
import (
"context"
"fmt"
"net/http"
"os"
"strconv"

"github.com/coinbase/rosetta-sdk-go/server"

"github.com/oasislabs/oasis-core-rosetta-gateway/oasis-client"
"github.com/oasislabs/oasis-core-rosetta-gateway/services"
)

// NewBlockchainRouter returns a Mux http.Handler from a collection of
// Rosetta service controllers.
func NewBlockchainRouter(oasisClient *oasis_client.OasisClient) http.Handler {
networkAPIController := server.NewNetworkAPIController(services.NewNetworkAPIService(oasisClient))
accountAPIController := server.NewAccountAPIController(services.NewAccountAPIService(oasisClient))
blockAPIController := server.NewBlockAPIController(services.NewBlockAPIService(oasisClient))
constructionAPIController := server.NewConstructionAPIController(services.NewConstructionAPIService(oasisClient))

return server.NewRouter(networkAPIController, accountAPIController, blockAPIController, constructionAPIController)
}

func main() {
fmt.Println("Hello, world.")
// Get server port from environment variable or use the default.
port := os.Getenv("OASIS_ROSETTA_GATEWAY_PORT")
if port == "" {
port = "8080"
}
nPort, err := strconv.Atoi(port)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: Malformed OASIS_ROSETTA_GATEWAY_PORT environment variable: %v\n", err)
os.Exit(1)
}

// Prepare a new Oasis gRPC client.
oasisClient, err := oasis_client.New()
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: Failed to prepare Oasis gRPC client: %v\n", err)
os.Exit(1)
}

// Make a test request using the client to see if the node works.
cid, err := oasisClient.GetChainID(context.Background())
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: Node connectivity error: %v\n", err)
os.Exit(1)
}
fmt.Printf("Node is connected to chain '%s'.\n", cid)

// Start the server.
router := NewBlockchainRouter(oasisClient)
fmt.Printf("Oasis Rosetta Gateway listening on port %d...\n", nPort)
err = http.ListenAndServe(fmt.Sprintf(":%d", nPort), router)
if err != nil {
fmt.Fprintf(os.Stderr, "Oasis Rosetta Gateway server exited with error: %v\n", err)
os.Exit(1)
}
}
197 changes: 197 additions & 0 deletions oasis-client/oasis-client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
package oasis_client

import (
"context"
"encoding/json"
"fmt"
"os"
"sync"

"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"

"github.com/oasislabs/oasis-core/go/common/cbor"
"github.com/oasislabs/oasis-core/go/common/crypto/signature"
cmnGrpc "github.com/oasislabs/oasis-core/go/common/grpc"
consensus "github.com/oasislabs/oasis-core/go/consensus/api"
"github.com/oasislabs/oasis-core/go/consensus/api/transaction"
tmAPI "github.com/oasislabs/oasis-core/go/consensus/tendermint/api"
staking "github.com/oasislabs/oasis-core/go/staking/api"
)

// LatestHeight can be used as the height in queries to specify the latest height.
const LatestHeight = consensus.HeightLatest

// GenesisHeight is the height of the genesis block.
const GenesisHeight = int64(1)

// OasisClient can be used to query an Oasis node for information
// and to submit transactions.
type OasisClient struct {
sync.RWMutex

// Connection to an Oasis node's internal socket.
grpcConn *grpc.ClientConn

// Cached chain ID.
chainID string
}

// OasisBlock is a representation of the Oasis block metadata,
// converted to be more compatible with the Rosetta API.
type OasisBlock struct {
Height int64 // Block height.
Hash string // Block hash.
Timestamp int64 // UNIX time, converted to milliseconds.
ParentHeight int64 // Height of parent block.
ParentHash string // Hash of parent block.
}

// Returns gRPC connection to Oasis node via its internal socket.
// The connection is cached in the OasisClient struct and re-established
// automatically by this method if it gets shut down.
func (oc *OasisClient) connect(ctx context.Context) (*grpc.ClientConn, error) {
oc.Lock()
defer oc.Unlock()

// Check if the existing connection is good.
if oc.grpcConn != nil && oc.grpcConn.GetState() != connectivity.Shutdown {
// Return existing connection.
return oc.grpcConn, nil
} else {
// Connection needs to be re-established.
oc.grpcConn = nil
}

// Get gRPC host address from environment variable.
grpcAddr := os.Getenv("GRPC_ADDR")
if grpcAddr == "" {
return nil, fmt.Errorf("GRPC_ADDR environment variable not specified.")
}

// Establish new gRPC connection.
var err error
oc.grpcConn, err = cmnGrpc.Dial(grpcAddr, grpc.WithInsecure())
if err != nil {
return nil, fmt.Errorf("failed to dial gRPC connection to '%s': %v", grpcAddr, err)
}
return oc.grpcConn, nil
}

// Returns network chain ID, as specified in the genesis document.
func (oc *OasisClient) GetChainID(ctx context.Context) (string, error) {
// Return cached chain ID if we already have it.
oc.RLock()
cid := oc.chainID
oc.RUnlock()
if cid != "" {
return cid, nil
}

conn, err := oc.connect(ctx)
if err != nil {
return "", err
}

oc.Lock()
defer oc.Unlock()

client := consensus.NewConsensusClient(conn)
genesis, err := client.StateToGenesis(ctx, consensus.HeightLatest)
if err != nil {
return "", err
}
oc.chainID = genesis.ChainID
return oc.chainID, nil
}

// Returns Oasis block at given height.
func (oc *OasisClient) GetBlock(ctx context.Context, height int64) (*OasisBlock, error) {
conn, err := oc.connect(ctx)
if err != nil {
return nil, err
}
client := consensus.NewConsensusClient(conn)
blk, err := client.GetBlock(ctx, height)
if err != nil {
return nil, err
}
var tmMeta tmAPI.BlockMeta
err = cbor.Unmarshal(blk.Meta, &tmMeta)
if err != nil {
return nil, err
}
parentHeight := blk.Height - 1
parentHash := tmMeta.Header.LastBlockID.Hash.String()
if parentHeight <= 0 {
genBlk, err := client.GetBlock(ctx, GenesisHeight)
if err != nil {
return nil, err
}
var genTMMeta tmAPI.BlockMeta
err = cbor.Unmarshal(genBlk.Meta, &genTMMeta)
if err != nil {
return nil, err
}
parentHeight = genBlk.Height
parentHash = genTMMeta.Header.Hash().String()
}
return &OasisBlock{
Height: blk.Height,
Hash: tmMeta.Header.Hash().String(),
Timestamp: tmMeta.Header.Time.UnixNano() / 1000000, // ms
ParentHeight: parentHeight,
ParentHash: parentHash,
}, nil
}

// Returns latest Oasis block.
func (oc *OasisClient) GetLatestBlock(ctx context.Context) (*OasisBlock, error) {
return oc.GetBlock(ctx, consensus.HeightLatest)
}

// Returns Oasis genesis block.
func (oc *OasisClient) GetGenesisBlock(ctx context.Context) (*OasisBlock, error) {
return oc.GetBlock(ctx, GenesisHeight)
}

// Returns the Oasis staking account for given owner.
func (oc *OasisClient) GetAccount(ctx context.Context, height int64, owner signature.PublicKey) (*staking.Account, error) {
conn, err := oc.connect(ctx)
if err != nil {
return nil, err
}
client := staking.NewStakingClient(conn)
return client.AccountInfo(ctx, &staking.OwnerQuery{
Height: height,
Owner: owner,
})
}

// Returns Oasis staking events at given height.
func (oc *OasisClient) GetStakingEvents(ctx context.Context, height int64) ([]staking.Event, error) {
conn, err := oc.connect(ctx)
if err != nil {
return nil, err
}
client := staking.NewStakingClient(conn)
return client.GetEvents(ctx, height)
}

// Submits the given JSON-encoded transaction to the node.
func (oc *OasisClient) SubmitTx(ctx context.Context, txRaw string) error {
conn, err := oc.connect(ctx)
if err != nil {
return err
}
client := consensus.NewConsensusClient(conn)
var tx *transaction.SignedTransaction
if err := json.Unmarshal([]byte(txRaw), &tx); err != nil {
return err
}
return client.SubmitTx(ctx, tx)
}

func New() (*OasisClient, error) {
return &OasisClient{}, nil
}
Loading

0 comments on commit 37424c8

Please sign in to comment.