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

Implement GraphQL API #858

Merged
merged 55 commits into from Aug 11, 2020
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
4a20fe9
Create basic GraphQL server; implement stats query
albrow Jul 9, 2020
46a6666
Implement order query
albrow Jul 9, 2020
a97cb45
Implement orders query
albrow Jul 9, 2020
ab8ca84
Remove JSON-RPC API
albrow Jul 14, 2020
7b157d9
Fix CI
albrow Jul 14, 2020
6ab2b46
Change types for some stats fields
albrow Jul 14, 2020
4ef48f8
Remove references to JSON-RPC API from cut-release script
albrow Jul 14, 2020
ecc29b7
Add GraphQL configuration options
albrow Jul 15, 2020
764b221
Update version of GraphiQL
albrow Jul 15, 2020
c459035
Use server.Shutdown for GraphQL server
albrow Jul 15, 2020
79697bb
Use go-bindata to embed schema from text file
albrow Jul 16, 2020
fe11e56
Add graphql config file
albrow Jul 16, 2020
05ab23d
Install go-bindata in deps-no-lockfile
albrow Jul 16, 2020
744c75a
Stub out remaining types and resolvers
albrow Jul 16, 2020
9b5143b
Switch to using gqlgen
albrow Jul 17, 2020
1b227ce
Set up new gqlgen server
albrow Jul 17, 2020
df10ab5
Re-implement resolvers
albrow Jul 17, 2020
cdb5db9
Implement AddOrders
albrow Jul 18, 2020
70f6673
Implement OrderEvents
albrow Jul 18, 2020
a6dcab7
Implement AddOrders in client
albrow Jul 23, 2020
ed17ddb
Implement and test Go client GetOrders without options
albrow Jul 24, 2020
a4f958e
Add test for more complicated GetOrders call
albrow Jul 25, 2020
78251cc
Remove WIP TypeScript client code
albrow Jul 29, 2020
1aa1347
Implement and test Go client GetOrder
albrow Jul 29, 2020
cb56995
Implement and test Go client GetStats
albrow Jul 29, 2020
8dde3b5
Clean up GraphQL integration tests
albrow Jul 29, 2020
a29fcdb
Implement basic working client with graphql-codegen
albrow Jul 31, 2020
73e5712
Switch to using apollo client
albrow Jul 31, 2020
7c9d1cd
WIP subscriptions support
albrow Aug 3, 2020
94f1782
Implement and test TypeScript client addOrdersAsync
albrow Aug 3, 2020
3290b79
Implement and test TypeScript client getOrderAsync
albrow Aug 3, 2020
cb3c2b8
Implement and test TypeScript client onOrderEvents
albrow Aug 3, 2020
9853b91
Remove ad hoc subscription test code
albrow Aug 4, 2020
3adc323
Fix linter errors
albrow Aug 4, 2020
5562a18
Implement and test TypeScript client getOrdersAsync
albrow Aug 4, 2020
a2d2a9c
Fix build error
albrow Aug 4, 2020
033658d
Remove old code related to typescript tests
albrow Aug 4, 2020
f8cb3c2
Implement and test TypeScript client rawQueryAsync
albrow Aug 4, 2020
7112300
Change env vars used for GraphQL integration tests
albrow Aug 4, 2020
b810b06
Basic cleanup; remove old code and deps
albrow Aug 4, 2020
95a9f25
Cleanup types and file structure for TypeScript client
albrow Aug 4, 2020
b977426
Re-enable browser integration tests
albrow Aug 4, 2020
cbfc5a5
Add missing TSDoc comments
albrow Aug 4, 2020
e71f1cf
Tweak environment variables.
albrow Aug 5, 2020
27bd7fe
Remove old Go examples
albrow Aug 6, 2020
eee3b1a
Add comment about mesh-bridge
albrow Aug 6, 2020
bd3d153
Update Dockerfiles
albrow Aug 6, 2020
2ad5a0e
Update documentation and comments
albrow Aug 7, 2020
d24a48f
Remove lingering references to coordinator error codes
albrow Aug 7, 2020
05f400f
Improve error handling for TypeScript client subscriptions
albrow Aug 7, 2020
bf9f85c
Address review feedback
albrow Aug 7, 2020
9b1779a
Fix failing TypeScript GraphQL client tests
albrow Aug 7, 2020
b416458
Update links in db_syncing.md
albrow Aug 7, 2020
6b02c87
Address PR feedback
albrow Aug 10, 2020
3b527b2
Throw all GraphQL errors in TypeScript client
albrow Aug 11, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitattributes
@@ -1,4 +1,4 @@
# Automatically collapse generated files in GitHub.
ethereum/wrappers/*.go linguist-generated=true
docs/json-rpc-clients/typescript/*.md linguist-generated=true
docs/graphql-clients/typescript/*.md linguist-generated=true

3 changes: 3 additions & 0 deletions .graphqlconfig
@@ -0,0 +1,3 @@
{
"schemaPath": "graphql/schema.graphql"
}
30 changes: 22 additions & 8 deletions Makefile
@@ -1,5 +1,5 @@
.PHONY: deps
deps: deps-ts wasmbrowsertest
deps: deps-ts wasmbrowsertest gqlgen


.PHONY: deps-ts
Expand All @@ -13,6 +13,13 @@ gobin:
GO111MODULE=off go get -u github.com/myitcv/gobin


# gqlgen is a tool for embedding files so that they are included in binaries.
# This installs the CLI for go-bindata.
.PHONY: gqlgen
gqlgen: gobin
gobin github.com/99designs/gqlgen@v0.11.3


# wasmbrowsertest is required for running WebAssembly tests in the browser.
.PHONY: wasmbrowsertest
wasmbrowsertest: gobin
Expand All @@ -21,7 +28,7 @@ wasmbrowsertest: gobin

# Installs dependencies without updating Gopkg.lock or yarn.lock
.PHONY: deps-no-lockfile
deps-no-lockfile: deps-ts-no-lockfile wasmbrowsertest
deps-no-lockfile: deps-ts-no-lockfile wasmbrowsertest gqlgen


.PHONY: deps-ts-no-lockfile
Expand All @@ -34,7 +41,7 @@ test-all: test-go test-wasm-browser test-ts test-browser-conversion test-browser


.PHONY: test-go
test-go: test-go-parallel test-go-serial
test-go: generate test-go-parallel test-go-serial


.PHONY: test-go-parallel
Expand Down Expand Up @@ -84,8 +91,14 @@ lint-ts:
lint-prettier:
yarn prettier:ci


.PHONY: generate
generate:
go generate ./...


.PHONY: mesh
mesh:
mesh: generate
go install ./cmd/mesh


Expand All @@ -105,7 +118,7 @@ db-integrity-check:


.PHONY: cut-release
cut-release:
cut-release: generate
go run ./cmd/cut-release/main.go


Expand All @@ -117,7 +130,7 @@ all: mesh mesh-keygen mesh-bootstrap db-integrity-check


.PHONY: docker-mesh
docker-mesh:
docker-mesh: generate
docker build . -t 0xorg/mesh -f ./dockerfiles/mesh/Dockerfile


Expand All @@ -132,5 +145,6 @@ docker-mesh-fluent-bit:


.PHONY: docker-mesh-bridge
docker-mesh-bridge:
docker build . -t 0xorg/mesh-bridge -f ./dockerfiles/mesh-bridge/Dockerfile
docker-mesh-bridge: generate
@echo 'WARN: mesh-bridge is currently disabled since it has not been updated to use the new GraphQL API'
# docker build . -t 0xorg/mesh-bridge -f ./dockerfiles/mesh-bridge/Dockerfile
12 changes: 6 additions & 6 deletions README.md
Expand Up @@ -39,12 +39,12 @@ in the network and begin receiving orders from and sending orders to them. You
do not need to know the identities (e.g., IP address or domain name) of any
peers in the network ahead of time and they do not need to know about you.

Developers can use the JSON-RPC API to interact with a Mesh node that they
control. The API allows you to send orders into the network, receive any new
orders, and get notified when the status of an existing order changes (e.g. when
it is filled, canceled, or expired). Under the hood, Mesh performs efficient
order validation and order book pruning, which takes out a lot of the hard work
for developers.
Developers can use the GraphQL API to interact with a Mesh node that they
control. The API allows you to send orders into the network, query for existing
orders, and get notified when an order is added or the status of an existing
order changes (e.g. when it is filled, canceled, or expired). Under the hood,
Mesh performs efficient order validation and order book pruning, which takes out
a lot of the hard work for developers.

## Documentation

Expand Down
6 changes: 3 additions & 3 deletions cmd/cut-release/main.go
Expand Up @@ -98,8 +98,8 @@ func updateHardCodedVersions(version string) {
newBrowserLiteDependencyString := fmt.Sprintf(`"@0x/mesh-browser-lite": "^%s"`, version)
newBrowserDependencyString := fmt.Sprintf(`"@0x/mesh-browser": "^%s"`, version)

// Update `packages/mesh-rpc-client/package.json`
tsClientPackageJSONPath := "packages/mesh-rpc-client/package.json"
// Update `packages/mesh-graphql-client/package.json`
tsClientPackageJSONPath := "packages/mesh-graphql-client/package.json"
regex := `"version": "(.*)"`
updateFileWithRegex(tsClientPackageJSONPath, regex, newVersionString)

Expand Down Expand Up @@ -156,7 +156,7 @@ func updateHardCodedVersions(version string) {
updateFileWithRegex(changelog, regex, newChangelogSection)

// Update badge in README.md
pathToMDFilesWithBadges := []string{"README.md", "docs/rpc_api.md", "docs/deployment.md", "docs/deployment_with_telemetry.md"}
pathToMDFilesWithBadges := []string{"README.md", "docs/graphql_api.md", "docs/deployment.md", "docs/deployment_with_telemetry.md"}
doubleDashVersion := strings.Replace(version, "-", "--", -1)
newSvgName := fmt.Sprintf("version-%s-orange.svg", doubleDashVersion)
regex = `version-(.*)-orange.svg`
Expand Down
213 changes: 108 additions & 105 deletions cmd/mesh-bridge/main.go
Expand Up @@ -5,120 +5,123 @@
//to flow to another
package main

import (
"context"
"time"
// Note(albrow): If we want to use mesh-bridge in the future we will need to update it to
// use the new GraphQL API. Considered low-priority as of the time of writing this comment.

"github.com/0xProject/0x-mesh/rpc"
"github.com/0xProject/0x-mesh/zeroex"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/plaid/go-envvar/envvar"
log "github.com/sirupsen/logrus"
)
// import (
// "context"
// "time"

const (
firstWSRPCAddressLabel = "FirstWSRPCAddress"
secondWSRPCAddressLabel = "SecondWSRPCAddress"
maxReceiveBatch = 100
receiveTimeout = 2 * time.Second
tenThousand = 10000
)
// "github.com/0xProject/0x-mesh/rpc"
// "github.com/0xProject/0x-mesh/zeroex"
// ethrpc "github.com/ethereum/go-ethereum/rpc"
// "github.com/plaid/go-envvar/envvar"
// log "github.com/sirupsen/logrus"
// )

type clientEnvVars struct {
FirstWSRPCAddress string `envvar:"FIRST_WS_RPC_ADDRESS"`
SecondWSRPCAddress string `envvar:"SECOND_WS_RPC_ADDRESS"`
Verbosity int `envvar:"VERBOSITY"`
}
// const (
// firstWSRPCAddressLabel = "FirstWSRPCAddress"
// secondWSRPCAddressLabel = "SecondWSRPCAddress"
// maxReceiveBatch = 100
// receiveTimeout = 2 * time.Second
// tenThousand = 10000
// )

// type clientEnvVars struct {
// FirstWSRPCAddress string `envvar:"FIRST_WS_RPC_ADDRESS"`
// SecondWSRPCAddress string `envvar:"SECOND_WS_RPC_ADDRESS"`
// Verbosity int `envvar:"VERBOSITY"`
// }

func main() {
env := clientEnvVars{}
if err := envvar.Parse(&env); err != nil {
panic(err)
}
// env := clientEnvVars{}
// if err := envvar.Parse(&env); err != nil {
// panic(err)
// }

log.SetLevel(log.Level(env.Verbosity))
// log.SetLevel(log.Level(env.Verbosity))

firstClient, err := rpc.NewClient(env.FirstWSRPCAddress)
if err != nil {
log.WithError(err).Fatal("could not create client")
}
stats, err := firstClient.GetStats()
if err != nil {
log.Fatal(err)
}
log.WithField("stats", stats).Info("Spun up first client")
// firstClient, err := rpc.NewClient(env.FirstWSRPCAddress)
// if err != nil {
// log.WithError(err).Fatal("could not create client")
// }
// stats, err := firstClient.GetStats()
// if err != nil {
// log.Fatal(err)
// }
// log.WithField("stats", stats).Info("Spun up first client")

secondClient, err := rpc.NewClient(env.SecondWSRPCAddress)
if err != nil {
log.WithError(err).Fatal("could not create client")
}
stats, err = secondClient.GetStats()
if err != nil {
log.Fatal(err)
}
log.WithField("stats", stats).Info("Spun up second client")
// secondClient, err := rpc.NewClient(env.SecondWSRPCAddress)
// if err != nil {
// log.WithError(err).Fatal("could not create client")
// }
// stats, err = secondClient.GetStats()
// if err != nil {
// log.Fatal(err)
// }
// log.WithField("stats", stats).Info("Spun up second client")

go func() {
pipeOrders(secondClient, firstClient, secondWSRPCAddressLabel, firstWSRPCAddressLabel)
}()
pipeOrders(firstClient, secondClient, firstWSRPCAddressLabel, secondWSRPCAddressLabel)
// go func() {
// pipeOrders(secondClient, firstClient, secondWSRPCAddressLabel, firstWSRPCAddressLabel)
// }()
// pipeOrders(firstClient, secondClient, firstWSRPCAddressLabel, secondWSRPCAddressLabel)
}

func pipeOrders(inClient, outClient *rpc.Client, inLabel, outLabel string) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
orderEventsChan := make(chan []*zeroex.OrderEvent, tenThousand)
clientSubscription, err := inClient.SubscribeToOrders(ctx, orderEventsChan)
if err != nil {
log.WithError(err).Fatal("Couldn't set up OrderStream subscription")
}
defer clientSubscription.Unsubscribe()
for {
incomingSignedOrders, err := receiveBatch(orderEventsChan, clientSubscription, inLabel, outLabel)
if err != nil {
log.Fatal(err)
}
if len(incomingSignedOrders) == 0 {
continue
}
validationResults, err := outClient.AddOrders(incomingSignedOrders)
if err != nil {
log.Fatal(err)
}
log.WithFields(log.Fields{
"from": inLabel,
"to": outLabel,
"numSent": len(incomingSignedOrders),
"numAccepted": len(validationResults.Accepted),
"numRejected": len(validationResults.Rejected),
}).Info("Finished bridging orders")
}
}
// func pipeOrders(inClient, outClient *rpc.Client, inLabel, outLabel string) {
// ctx, cancel := context.WithCancel(context.Background())
// defer cancel()
// orderEventsChan := make(chan []*zeroex.OrderEvent, tenThousand)
// clientSubscription, err := inClient.SubscribeToOrders(ctx, orderEventsChan)
// if err != nil {
// log.WithError(err).Fatal("Couldn't set up OrderStream subscription")
// }
// defer clientSubscription.Unsubscribe()
// for {
// incomingSignedOrders, err := receiveBatch(orderEventsChan, clientSubscription, inLabel, outLabel)
// if err != nil {
// log.Fatal(err)
// }
// if len(incomingSignedOrders) == 0 {
// continue
// }
// validationResults, err := outClient.AddOrders(incomingSignedOrders)
// if err != nil {
// log.Fatal(err)
// }
// log.WithFields(log.Fields{
// "from": inLabel,
// "to": outLabel,
// "numSent": len(incomingSignedOrders),
// "numAccepted": len(validationResults.Accepted),
// "numRejected": len(validationResults.Rejected),
// }).Info("Finished bridging orders")
// }
// }

func receiveBatch(inChan chan []*zeroex.OrderEvent, subscription *ethrpc.ClientSubscription, inLabel, outLabel string) ([]*zeroex.SignedOrder, error) {
signedOrders := []*zeroex.SignedOrder{}
timeoutChan := time.After(receiveTimeout)
for {
if len(signedOrders) >= maxReceiveBatch {
return signedOrders, nil
}
select {
case <-timeoutChan:
return signedOrders, nil
case orderEvents := <-inChan:
for _, orderEvent := range orderEvents {
if orderEvent.EndState != zeroex.ESOrderAdded {
continue
}
log.WithFields(log.Fields{
"from": inLabel,
"to": outLabel,
"orderHash": orderEvent.OrderHash.Hex(),
}).Info("Found new order over bridge")
signedOrders = append(signedOrders, orderEvent.SignedOrder)
}
case err := <-subscription.Err():
log.Fatal(err)
}
}
}
// func receiveBatch(inChan chan []*zeroex.OrderEvent, subscription *ethrpc.ClientSubscription, inLabel, outLabel string) ([]*zeroex.SignedOrder, error) {
// signedOrders := []*zeroex.SignedOrder{}
// timeoutChan := time.After(receiveTimeout)
// for {
// if len(signedOrders) >= maxReceiveBatch {
// return signedOrders, nil
// }
// select {
// case <-timeoutChan:
// return signedOrders, nil
// case orderEvents := <-inChan:
// for _, orderEvent := range orderEvents {
// if orderEvent.EndState != zeroex.ESOrderAdded {
// continue
// }
// log.WithFields(log.Fields{
// "from": inLabel,
// "to": outLabel,
// "orderHash": orderEvent.OrderHash.Hex(),
// }).Info("Found new order over bridge")
// signedOrders = append(signedOrders, orderEvent.SignedOrder)
// }
// case err := <-subscription.Err():
// log.Fatal(err)
// }
// }
// }