-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,205 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
|
@@ -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 | ||
|
@@ -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)" | ||
|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.