From fd7926c7f7b1515d25c60c932494e44586007d7b Mon Sep 17 00:00:00 2001 From: Yuanchao Sun Date: Thu, 10 Jan 2019 22:59:40 +0800 Subject: [PATCH] Open source --- .dockerignore | 6 + README.md | 15 +- cloudbuild.yaml | 78 + compile-protos.sh | 6 + go.mod | 43 + go.sum | 154 ++ local-build.sh | 9 + messaging/topics.go | 7 + messaging/types.go | 17 + protos/match.pb.go | 2154 ++++++++++++++++++++++ protos/match.proto | 197 ++ protos/memorystore.pb.go | 970 ++++++++++ protos/memorystore.proto | 88 + protos/sql.pb.go | 1401 ++++++++++++++ protos/sql.proto | 140 ++ protos/wallet.pb.go | 467 +++++ protos/wallet.proto | 37 + svc-gateway/Dockerfile | 26 + svc-gateway/handlers.go | 245 +++ svc-gateway/main.go | 648 +++++++ svc-gateway/utils.go | 39 + svc-match/Dockerfile | 26 + svc-match/asset.go | 373 ++++ svc-match/fast-skiplist/LICENSE | 21 + svc-match/fast-skiplist/README.md | 126 ++ svc-match/fast-skiplist/skiplist.go | 182 ++ svc-match/fast-skiplist/skiplist_test.go | 229 +++ svc-match/fast-skiplist/type.go | 46 + svc-match/history.go | 154 ++ svc-match/main.go | 604 ++++++ svc-match/market.go | 811 ++++++++ svc-match/message.go | 316 ++++ svc-memorystore/Dockerfile | 26 + svc-memorystore/main.go | 1245 +++++++++++++ svc-sql/Dockerfile | 26 + svc-sql/main.go | 176 ++ svc-sql/messaging.go | 167 ++ svc-wallet/Dockerfile | 23 + svc-wallet/bitcoin/main.go | 7 + svc-wallet/ethereum/Dockerfile | 26 + svc-wallet/ethereum/events.go | 150 ++ svc-wallet/ethereum/hdwallet.go | 36 + svc-wallet/ethereum/main.go | 155 ++ svc-wallet/ethereum/messaging.go | 49 + svc-wallet/ethereum/timer.go | 62 + svc-wallet/main.go | 149 ++ svc-ws/Dockerfile | 27 + svc-ws/main.go | 1107 +++++++++++ svc-ws/message.go | 104 ++ svc-ws/time.go | 65 + utils/glog/glog.go | 145 ++ utils/recover.go | 11 + utils/time.go | 13 + 53 files changed, 13403 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 cloudbuild.yaml create mode 100755 compile-protos.sh create mode 100644 go.mod create mode 100644 go.sum create mode 100755 local-build.sh create mode 100644 messaging/topics.go create mode 100644 messaging/types.go create mode 100644 protos/match.pb.go create mode 100644 protos/match.proto create mode 100644 protos/memorystore.pb.go create mode 100644 protos/memorystore.proto create mode 100644 protos/sql.pb.go create mode 100644 protos/sql.proto create mode 100644 protos/wallet.pb.go create mode 100644 protos/wallet.proto create mode 100644 svc-gateway/Dockerfile create mode 100644 svc-gateway/handlers.go create mode 100644 svc-gateway/main.go create mode 100644 svc-gateway/utils.go create mode 100644 svc-match/Dockerfile create mode 100644 svc-match/asset.go create mode 100644 svc-match/fast-skiplist/LICENSE create mode 100644 svc-match/fast-skiplist/README.md create mode 100644 svc-match/fast-skiplist/skiplist.go create mode 100644 svc-match/fast-skiplist/skiplist_test.go create mode 100644 svc-match/fast-skiplist/type.go create mode 100644 svc-match/history.go create mode 100644 svc-match/main.go create mode 100644 svc-match/market.go create mode 100644 svc-match/message.go create mode 100644 svc-memorystore/Dockerfile create mode 100644 svc-memorystore/main.go create mode 100644 svc-sql/Dockerfile create mode 100644 svc-sql/main.go create mode 100644 svc-sql/messaging.go create mode 100644 svc-wallet/Dockerfile create mode 100644 svc-wallet/bitcoin/main.go create mode 100644 svc-wallet/ethereum/Dockerfile create mode 100644 svc-wallet/ethereum/events.go create mode 100644 svc-wallet/ethereum/hdwallet.go create mode 100644 svc-wallet/ethereum/main.go create mode 100644 svc-wallet/ethereum/messaging.go create mode 100644 svc-wallet/ethereum/timer.go create mode 100644 svc-wallet/main.go create mode 100644 svc-ws/Dockerfile create mode 100644 svc-ws/main.go create mode 100644 svc-ws/message.go create mode 100644 svc-ws/time.go create mode 100644 utils/glog/glog.go create mode 100644 utils/recover.go create mode 100644 utils/time.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0b5288e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +Dockerfile +LICENSE +README.md +cloudbuild.yaml +compile-protos.sh +local-build.sh diff --git a/README.md b/README.md index b77a103..5f2df86 100644 --- a/README.md +++ b/README.md @@ -1 +1,14 @@ -# siren \ No newline at end of file +# siren +siren is a cryptocurrency exchange built using microservice architecture. + +The main components are: +* grpc +* postgres +* redis +* kafka as message bus +* in-memory matching engine based on skiplist +* auth0 service for authentication + +The design of this project borrows from [viabtc_exchange_server](https://github.com/viabtc/viabtc_exchange_server), and can work with a front-end SPA (not open sourced) that integrates tradingview. + +This project only implemented core functions and has not been fully tested. diff --git a/cloudbuild.yaml b/cloudbuild.yaml new file mode 100644 index 0000000..0eef545 --- /dev/null +++ b/cloudbuild.yaml @@ -0,0 +1,78 @@ +steps: +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-f', 'svc-match/Dockerfile', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/match:latest', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/match:$COMMIT_SHA', '.'] +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-f', 'svc-memorystore/Dockerfile', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/memorystore:latest', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/memorystore:$COMMIT_SHA', '.'] +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-f', 'svc-sql/Dockerfile', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/sql:latest', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/sql:$COMMIT_SHA', '.'] +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-f', 'svc-wallet/Dockerfile', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/wallet:latest', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/wallet:$COMMIT_SHA', '.'] +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-f', 'svc-ws/Dockerfile', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/ws:latest', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/ws:$COMMIT_SHA', '.'] +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-f', 'svc-gateway/Dockerfile', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/gateway:latest', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/gateway:$COMMIT_SHA', '.'] +- name: 'gcr.io/cloud-builders/docker' + args: ['build', '-f', 'svc-wallet/ethereum/Dockerfile', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/ethereum:latest', '-t', 'gcr.io/$PROJECT_ID/$REPO_NAME/ethereum:$COMMIT_SHA', '.'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/match:latest'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/match:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/memorystore:latest'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/memorystore:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/sql:latest'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/sql:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/wallet:latest'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/wallet:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/ws:latest'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/ws:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/gateway:latest'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/gateway:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/ethereum:latest'] +- name: 'gcr.io/cloud-builders/docker' + args: ['push', 'gcr.io/$PROJECT_ID/$REPO_NAME/ethereum:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/kubectl' + env: + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=dev' + args: ['set', 'image', 'deployment', 'siren-match', 'siren-match=gcr.io/$PROJECT_ID/$REPO_NAME/match:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/kubectl' + env: + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=dev' + args: ['set', 'image', 'deployment', 'siren-memorystore', 'siren-memorystore=gcr.io/$PROJECT_ID/$REPO_NAME/memorystore:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/kubectl' + env: + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=dev' + args: ['set', 'image', 'deployment', 'siren-sql', 'siren-sql=gcr.io/$PROJECT_ID/$REPO_NAME/sql:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/kubectl' + env: + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=dev' + args: ['set', 'image', 'deployment', 'siren-wallet', 'siren-wallet=gcr.io/$PROJECT_ID/$REPO_NAME/wallet:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/kubectl' + env: + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=dev' + args: ['set', 'image', 'deployment', 'siren-ws', 'siren-ws=gcr.io/$PROJECT_ID/$REPO_NAME/ws:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/kubectl' + env: + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=dev' + args: ['set', 'image', 'deployment', 'siren-gateway', 'siren-gateway=gcr.io/$PROJECT_ID/$REPO_NAME/gateway:$COMMIT_SHA'] +- name: 'gcr.io/cloud-builders/kubectl' + env: + - 'CLOUDSDK_COMPUTE_ZONE=us-central1-a' + - 'CLOUDSDK_CONTAINER_CLUSTER=dev' + args: ['set', 'image', 'deployment', 'siren-wallet', 'ethereum=gcr.io/$PROJECT_ID/$REPO_NAME/ethereum:$COMMIT_SHA'] diff --git a/compile-protos.sh b/compile-protos.sh new file mode 100755 index 0000000..9b1ded1 --- /dev/null +++ b/compile-protos.sh @@ -0,0 +1,6 @@ +#! /bin/sh + +# $ protoc --version +# libprotoc 3.6.1 + +protoc --go_out=plugins=grpc,paths=source_relative:. protos/*.proto diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..80689fe --- /dev/null +++ b/go.mod @@ -0,0 +1,43 @@ +module github.com/en/siren + +go 1.14 + +require ( + github.com/BurntSushi/toml v0.3.1 // indirect + github.com/allegro/bigcache v1.1.0 // indirect + github.com/aristanetworks/goarista v0.0.0-20181220211322-0ca71131d8f7 // indirect + github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 + github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2 // indirect + github.com/codegangsta/negroni v1.0.0 // indirect + github.com/confluentinc/confluent-kafka-go v0.11.6 + github.com/davecgh/go-spew v1.1.1 + github.com/deckarep/golang-set v1.7.1 // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/ethereum/go-ethereum v1.8.20 + github.com/go-stack/stack v1.8.0 // indirect + github.com/gofrs/uuid v3.1.0+incompatible + github.com/golang/protobuf v1.2.0 + github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db // indirect + github.com/gomodule/redigo v2.0.0+incompatible + github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect + github.com/gorilla/context v1.1.1 // indirect + github.com/gorilla/mux v1.6.2 + github.com/gorilla/websocket v1.4.0 + github.com/jtolds/gls v4.2.1+incompatible // indirect + github.com/lib/pq v1.0.0 + github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20181004120025-8af197af5b28 + github.com/onsi/ginkgo v1.7.0 // indirect + github.com/onsi/gomega v1.4.3 // indirect + github.com/rs/cors v1.6.0 + github.com/rs/zerolog v1.11.0 + github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 + github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect + github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c // indirect + github.com/spf13/viper v1.3.1 + github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 // indirect + github.com/tyler-smith/go-bip39 v1.0.0 // indirect + github.com/urfave/negroni v1.0.0 + golang.org/x/net v0.0.0-20181217023233-e147a9138326 + google.golang.org/grpc v1.17.0 + gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..539e56d --- /dev/null +++ b/go.sum @@ -0,0 +1,154 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= +github.com/allegro/bigcache v1.1.0 h1:MLuIKTjdxDc+qsG2rhjsYjsHQC5LUGjIWzutg7M+W68= +github.com/allegro/bigcache v1.1.0/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/aristanetworks/goarista v0.0.0-20181220211322-0ca71131d8f7 h1:2747IX7CoiMWIiaSwu6X/+rn1176ym7AsNnxvz7X9H4= +github.com/aristanetworks/goarista v0.0.0-20181220211322-0ca71131d8f7/go.mod h1:D/tb0zPVXnP7fmsLZjtdUhSsumbK/ij54UXjjVgMGxQ= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7 h1:irR1cO6eek3n5uquIVaRAsQmZnlsfPuHNz31cXo4eyk= +github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= +github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2 h1:LPHpTTuR7vj3kD7YDRZnrDnFAoj1Ov4cpiO3jN8RnW4= +github.com/btcsuite/btcd v0.0.0-20181130015935-7d2daa5bfef2/go.mod h1:Jr9bmNVGZ7TH2Ux1QuP0ec+yGgh0gE9FIlkzQiI5bR0= +github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a h1:RQMUrEILyYJEoAT34XS/kLu40vC0+po/UfxrBBA4qZE= +github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg= +github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg= +github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY= +github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc= +github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY= +github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/codegangsta/negroni v1.0.0 h1:+aYywywx4bnKXWvoWtRfJ91vC59NbEhEY03sZjQhbVY= +github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= +github.com/confluentinc/confluent-kafka-go v0.11.6 h1:rEblubnNXCjRThwAGnFSzLKYIRAoXLDC3A9r4ciziHU= +github.com/confluentinc/confluent-kafka-go v0.11.6/go.mod h1:u2zNLny2xq+5rWeTQjFHbDzzNuba4P1vo31r9r4uAdg= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set v1.7.1 h1:SCQV0S6gTtp6itiFrTqI+pfmJ4LN85S1YzhDf9rTHJQ= +github.com/deckarep/golang-set v1.7.1/go.mod h1:93vsz/8Wt4joVM7c2AVqh+YRMiUSc14yDtF28KmMOgQ= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/ethereum/go-ethereum v1.8.20 h1:Sr6DLbdc7Fl2IMDC0sjF2wO1jTO5nALFC1SoQnyAQEk= +github.com/ethereum/go-ethereum v1.8.20/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= +github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v2.0.0+incompatible h1:K/R+8tc58AaqLkqG2Ol3Qk+DR/TlNuhuh457pBFPtt0= +github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= +github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= +github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= +github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20181004120025-8af197af5b28 h1:985eh2wb5OAdWNELOHgHo63s2Ett7N/6P2sY3nrLkwo= +github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20181004120025-8af197af5b28/go.mod h1:VVt+rn/itmf+9eZq9CAabVlsfsHveUNUQ0bRv3ChqxY= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/zerolog v1.11.0 h1:DRuq/S+4k52uJzBQciUcofXx45GrMC6yrEbb/CoK6+M= +github.com/rs/zerolog v1.11.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c h1:Ho+uVpkel/udgjbwB5Lktg9BtvJSh2DT0Hi6LPSyI2w= +github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38= +github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2 h1:GnOzE5fEFN3b2zDhJJABEofdb51uMRNb8eqIVtdducs= +github.com/syndtr/goleveldb v0.0.0-20181128100959-b001fa50d6b2/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= +github.com/tyler-smith/go-bip39 v1.0.0 h1:FOHg9gaQLeBBRbHE/QrTLfEiBHy5pQ/yXzf9JG5pYFM= +github.com/tyler-smith/go-bip39 v1.0.0/go.mod h1:sJ5fKU0s6JVwZjjcUEX2zFOnvq0ASQ2K9Zr6cf67kNs= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/urfave/negroni v1.0.0 h1:kIimOitoypq34K7TG7DUaJ9kq/N4Ofuwi1sjz0KipXc= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181217023233-e147a9138326 h1:iCzOf0xz39Tstp+Tu/WwyGjUXCk34QhQORRxBeXXTA4= +golang.org/x/net v0.0.0-20181217023233-e147a9138326/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce h1:+JknDZhAj8YMt7GC73Ei8pv4MzjDUNPHgQWJdtMAaDU= +gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce/go.mod h1:5AcXVHNjg+BDxry382+8OKon8SEWiKktQR07RKPsv1c= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/local-build.sh b/local-build.sh new file mode 100755 index 0000000..198c7a0 --- /dev/null +++ b/local-build.sh @@ -0,0 +1,9 @@ +#! /bin/sh + +go build -o gateway ./svc-gateway +go build -o match ./svc-match +go build -o memorystore ./svc-memorystore +go build -o sql ./svc-sql +go build -o wallet ./svc-wallet +go build -o ethereum ./svc-wallet/ethereum +go build -o ws ./svc-ws diff --git a/messaging/topics.go b/messaging/topics.go new file mode 100644 index 0000000..a3ba410 --- /dev/null +++ b/messaging/topics.go @@ -0,0 +1,7 @@ +package messaging + +var ( + TopicSirenBalances = "siren-balances-v1" + TopicSirenOrders = "siren-orders-v1" + TopicSirenTransfers = "siren-transfers-v1" +) diff --git a/messaging/types.go b/messaging/types.go new file mode 100644 index 0000000..65beca5 --- /dev/null +++ b/messaging/types.go @@ -0,0 +1,17 @@ +package messaging + +import ( + "time" + + "github.com/shopspring/decimal" +) + +type TransferMessage struct { + Id string `json:"id"` + UserId string `json:"user_id"` + Currency string `json:"currency"` + Type string `json:"type"` + Amount decimal.Decimal `json:"amount"` + Status string `json:"status"` + CreatedAt time.Time `json:"created_at"` +} diff --git a/protos/match.pb.go b/protos/match.pb.go new file mode 100644 index 0000000..6e242c2 --- /dev/null +++ b/protos/match.pb.go @@ -0,0 +1,2154 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: protos/match.proto + +package protos + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type EmptyRequest struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *EmptyRequest) Reset() { *m = EmptyRequest{} } +func (m *EmptyRequest) String() string { return proto.CompactTextString(m) } +func (*EmptyRequest) ProtoMessage() {} +func (*EmptyRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{0} +} + +func (m *EmptyRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_EmptyRequest.Unmarshal(m, b) +} +func (m *EmptyRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_EmptyRequest.Marshal(b, m, deterministic) +} +func (m *EmptyRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_EmptyRequest.Merge(m, src) +} +func (m *EmptyRequest) XXX_Size() int { + return xxx_messageInfo_EmptyRequest.Size(m) +} +func (m *EmptyRequest) XXX_DiscardUnknown() { + xxx_messageInfo_EmptyRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_EmptyRequest proto.InternalMessageInfo + +type Currency struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Currency) Reset() { *m = Currency{} } +func (m *Currency) String() string { return proto.CompactTextString(m) } +func (*Currency) ProtoMessage() {} +func (*Currency) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{1} +} + +func (m *Currency) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Currency.Unmarshal(m, b) +} +func (m *Currency) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Currency.Marshal(b, m, deterministic) +} +func (m *Currency) XXX_Merge(src proto.Message) { + xxx_messageInfo_Currency.Merge(m, src) +} +func (m *Currency) XXX_Size() int { + return xxx_messageInfo_Currency.Size(m) +} +func (m *Currency) XXX_DiscardUnknown() { + xxx_messageInfo_Currency.DiscardUnknown(m) +} + +var xxx_messageInfo_Currency proto.InternalMessageInfo + +func (m *Currency) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Currency) GetName() string { + if m != nil { + return m.Name + } + return "" +} + +type GetCurrencyResponse struct { + Currencies []*Currency `protobuf:"bytes,1,rep,name=currencies,proto3" json:"currencies,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetCurrencyResponse) Reset() { *m = GetCurrencyResponse{} } +func (m *GetCurrencyResponse) String() string { return proto.CompactTextString(m) } +func (*GetCurrencyResponse) ProtoMessage() {} +func (*GetCurrencyResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{2} +} + +func (m *GetCurrencyResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetCurrencyResponse.Unmarshal(m, b) +} +func (m *GetCurrencyResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetCurrencyResponse.Marshal(b, m, deterministic) +} +func (m *GetCurrencyResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetCurrencyResponse.Merge(m, src) +} +func (m *GetCurrencyResponse) XXX_Size() int { + return xxx_messageInfo_GetCurrencyResponse.Size(m) +} +func (m *GetCurrencyResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetCurrencyResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetCurrencyResponse proto.InternalMessageInfo + +func (m *GetCurrencyResponse) GetCurrencies() []*Currency { + if m != nil { + return m.Currencies + } + return nil +} + +type Symbol struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + BaseCurrency string `protobuf:"bytes,2,opt,name=base_currency,json=baseCurrency,proto3" json:"base_currency,omitempty"` + QuoteCurrency string `protobuf:"bytes,3,opt,name=quote_currency,json=quoteCurrency,proto3" json:"quote_currency,omitempty"` + BaseMinAmount string `protobuf:"bytes,4,opt,name=base_min_amount,json=baseMinAmount,proto3" json:"base_min_amount,omitempty"` + BaseIncrement string `protobuf:"bytes,5,opt,name=base_increment,json=baseIncrement,proto3" json:"base_increment,omitempty"` + QuoteIncrement string `protobuf:"bytes,6,opt,name=quote_increment,json=quoteIncrement,proto3" json:"quote_increment,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Symbol) Reset() { *m = Symbol{} } +func (m *Symbol) String() string { return proto.CompactTextString(m) } +func (*Symbol) ProtoMessage() {} +func (*Symbol) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{3} +} + +func (m *Symbol) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Symbol.Unmarshal(m, b) +} +func (m *Symbol) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Symbol.Marshal(b, m, deterministic) +} +func (m *Symbol) XXX_Merge(src proto.Message) { + xxx_messageInfo_Symbol.Merge(m, src) +} +func (m *Symbol) XXX_Size() int { + return xxx_messageInfo_Symbol.Size(m) +} +func (m *Symbol) XXX_DiscardUnknown() { + xxx_messageInfo_Symbol.DiscardUnknown(m) +} + +var xxx_messageInfo_Symbol proto.InternalMessageInfo + +func (m *Symbol) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Symbol) GetBaseCurrency() string { + if m != nil { + return m.BaseCurrency + } + return "" +} + +func (m *Symbol) GetQuoteCurrency() string { + if m != nil { + return m.QuoteCurrency + } + return "" +} + +func (m *Symbol) GetBaseMinAmount() string { + if m != nil { + return m.BaseMinAmount + } + return "" +} + +func (m *Symbol) GetBaseIncrement() string { + if m != nil { + return m.BaseIncrement + } + return "" +} + +func (m *Symbol) GetQuoteIncrement() string { + if m != nil { + return m.QuoteIncrement + } + return "" +} + +type GetSymbolsResponse struct { + Symbols []*Symbol `protobuf:"bytes,1,rep,name=symbols,proto3" json:"symbols,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetSymbolsResponse) Reset() { *m = GetSymbolsResponse{} } +func (m *GetSymbolsResponse) String() string { return proto.CompactTextString(m) } +func (*GetSymbolsResponse) ProtoMessage() {} +func (*GetSymbolsResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{4} +} + +func (m *GetSymbolsResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetSymbolsResponse.Unmarshal(m, b) +} +func (m *GetSymbolsResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetSymbolsResponse.Marshal(b, m, deterministic) +} +func (m *GetSymbolsResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetSymbolsResponse.Merge(m, src) +} +func (m *GetSymbolsResponse) XXX_Size() int { + return xxx_messageInfo_GetSymbolsResponse.Size(m) +} +func (m *GetSymbolsResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetSymbolsResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetSymbolsResponse proto.InternalMessageInfo + +func (m *GetSymbolsResponse) GetSymbols() []*Symbol { + if m != nil { + return m.Symbols + } + return nil +} + +type BalancesRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BalancesRequest) Reset() { *m = BalancesRequest{} } +func (m *BalancesRequest) String() string { return proto.CompactTextString(m) } +func (*BalancesRequest) ProtoMessage() {} +func (*BalancesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{5} +} + +func (m *BalancesRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BalancesRequest.Unmarshal(m, b) +} +func (m *BalancesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BalancesRequest.Marshal(b, m, deterministic) +} +func (m *BalancesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_BalancesRequest.Merge(m, src) +} +func (m *BalancesRequest) XXX_Size() int { + return xxx_messageInfo_BalancesRequest.Size(m) +} +func (m *BalancesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_BalancesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_BalancesRequest proto.InternalMessageInfo + +func (m *BalancesRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *BalancesRequest) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +type Balance struct { + Currency string `protobuf:"bytes,1,opt,name=currency,proto3" json:"currency,omitempty"` + Balance string `protobuf:"bytes,2,opt,name=balance,proto3" json:"balance,omitempty"` + Available string `protobuf:"bytes,3,opt,name=available,proto3" json:"available,omitempty"` + Hold string `protobuf:"bytes,4,opt,name=hold,proto3" json:"hold,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Balance) Reset() { *m = Balance{} } +func (m *Balance) String() string { return proto.CompactTextString(m) } +func (*Balance) ProtoMessage() {} +func (*Balance) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{6} +} + +func (m *Balance) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Balance.Unmarshal(m, b) +} +func (m *Balance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Balance.Marshal(b, m, deterministic) +} +func (m *Balance) XXX_Merge(src proto.Message) { + xxx_messageInfo_Balance.Merge(m, src) +} +func (m *Balance) XXX_Size() int { + return xxx_messageInfo_Balance.Size(m) +} +func (m *Balance) XXX_DiscardUnknown() { + xxx_messageInfo_Balance.DiscardUnknown(m) +} + +var xxx_messageInfo_Balance proto.InternalMessageInfo + +func (m *Balance) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *Balance) GetBalance() string { + if m != nil { + return m.Balance + } + return "" +} + +func (m *Balance) GetAvailable() string { + if m != nil { + return m.Available + } + return "" +} + +func (m *Balance) GetHold() string { + if m != nil { + return m.Hold + } + return "" +} + +type BalancesResponse struct { + Balances []*Balance `protobuf:"bytes,1,rep,name=balances,proto3" json:"balances,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *BalancesResponse) Reset() { *m = BalancesResponse{} } +func (m *BalancesResponse) String() string { return proto.CompactTextString(m) } +func (*BalancesResponse) ProtoMessage() {} +func (*BalancesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{7} +} + +func (m *BalancesResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_BalancesResponse.Unmarshal(m, b) +} +func (m *BalancesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_BalancesResponse.Marshal(b, m, deterministic) +} +func (m *BalancesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_BalancesResponse.Merge(m, src) +} +func (m *BalancesResponse) XXX_Size() int { + return xxx_messageInfo_BalancesResponse.Size(m) +} +func (m *BalancesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_BalancesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_BalancesResponse proto.InternalMessageInfo + +func (m *BalancesResponse) GetBalances() []*Balance { + if m != nil { + return m.Balances + } + return nil +} + +type NewOrderRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + ClientOrderId string `protobuf:"bytes,2,opt,name=client_order_id,json=clientOrderId,proto3" json:"client_order_id,omitempty"` + Symbol string `protobuf:"bytes,3,opt,name=symbol,proto3" json:"symbol,omitempty"` + Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` + Side string `protobuf:"bytes,5,opt,name=side,proto3" json:"side,omitempty"` + Price string `protobuf:"bytes,6,opt,name=price,proto3" json:"price,omitempty"` + Amount string `protobuf:"bytes,7,opt,name=amount,proto3" json:"amount,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *NewOrderRequest) Reset() { *m = NewOrderRequest{} } +func (m *NewOrderRequest) String() string { return proto.CompactTextString(m) } +func (*NewOrderRequest) ProtoMessage() {} +func (*NewOrderRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{8} +} + +func (m *NewOrderRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_NewOrderRequest.Unmarshal(m, b) +} +func (m *NewOrderRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_NewOrderRequest.Marshal(b, m, deterministic) +} +func (m *NewOrderRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_NewOrderRequest.Merge(m, src) +} +func (m *NewOrderRequest) XXX_Size() int { + return xxx_messageInfo_NewOrderRequest.Size(m) +} +func (m *NewOrderRequest) XXX_DiscardUnknown() { + xxx_messageInfo_NewOrderRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_NewOrderRequest proto.InternalMessageInfo + +func (m *NewOrderRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *NewOrderRequest) GetClientOrderId() string { + if m != nil { + return m.ClientOrderId + } + return "" +} + +func (m *NewOrderRequest) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +func (m *NewOrderRequest) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *NewOrderRequest) GetSide() string { + if m != nil { + return m.Side + } + return "" +} + +func (m *NewOrderRequest) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *NewOrderRequest) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +type Order struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Price string `protobuf:"bytes,2,opt,name=price,proto3" json:"price,omitempty"` + Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + Symbol string `protobuf:"bytes,4,opt,name=symbol,proto3" json:"symbol,omitempty"` + Side string `protobuf:"bytes,5,opt,name=side,proto3" json:"side,omitempty"` + Type string `protobuf:"bytes,6,opt,name=type,proto3" json:"type,omitempty"` + CreatedAt *timestamp.Timestamp `protobuf:"bytes,7,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamp.Timestamp `protobuf:"bytes,8,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + DoneAt *timestamp.Timestamp `protobuf:"bytes,9,opt,name=done_at,json=doneAt,proto3" json:"done_at,omitempty"` + DoneReason string `protobuf:"bytes,10,opt,name=done_reason,json=doneReason,proto3" json:"done_reason,omitempty"` + FillFees string `protobuf:"bytes,11,opt,name=fill_fees,json=fillFees,proto3" json:"fill_fees,omitempty"` + FilledAmount string `protobuf:"bytes,12,opt,name=filled_amount,json=filledAmount,proto3" json:"filled_amount,omitempty"` + RemainingAmount string `protobuf:"bytes,13,opt,name=remaining_amount,json=remainingAmount,proto3" json:"remaining_amount,omitempty"` + ExecutedValue string `protobuf:"bytes,14,opt,name=executed_value,json=executedValue,proto3" json:"executed_value,omitempty"` + Status string `protobuf:"bytes,15,opt,name=status,proto3" json:"status,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Order) Reset() { *m = Order{} } +func (m *Order) String() string { return proto.CompactTextString(m) } +func (*Order) ProtoMessage() {} +func (*Order) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{9} +} + +func (m *Order) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Order.Unmarshal(m, b) +} +func (m *Order) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Order.Marshal(b, m, deterministic) +} +func (m *Order) XXX_Merge(src proto.Message) { + xxx_messageInfo_Order.Merge(m, src) +} +func (m *Order) XXX_Size() int { + return xxx_messageInfo_Order.Size(m) +} +func (m *Order) XXX_DiscardUnknown() { + xxx_messageInfo_Order.DiscardUnknown(m) +} + +var xxx_messageInfo_Order proto.InternalMessageInfo + +func (m *Order) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Order) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *Order) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *Order) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +func (m *Order) GetSide() string { + if m != nil { + return m.Side + } + return "" +} + +func (m *Order) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *Order) GetCreatedAt() *timestamp.Timestamp { + if m != nil { + return m.CreatedAt + } + return nil +} + +func (m *Order) GetUpdatedAt() *timestamp.Timestamp { + if m != nil { + return m.UpdatedAt + } + return nil +} + +func (m *Order) GetDoneAt() *timestamp.Timestamp { + if m != nil { + return m.DoneAt + } + return nil +} + +func (m *Order) GetDoneReason() string { + if m != nil { + return m.DoneReason + } + return "" +} + +func (m *Order) GetFillFees() string { + if m != nil { + return m.FillFees + } + return "" +} + +func (m *Order) GetFilledAmount() string { + if m != nil { + return m.FilledAmount + } + return "" +} + +func (m *Order) GetRemainingAmount() string { + if m != nil { + return m.RemainingAmount + } + return "" +} + +func (m *Order) GetExecutedValue() string { + if m != nil { + return m.ExecutedValue + } + return "" +} + +func (m *Order) GetStatus() string { + if m != nil { + return m.Status + } + return "" +} + +type NewOrderResponse struct { + Order *Order `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *NewOrderResponse) Reset() { *m = NewOrderResponse{} } +func (m *NewOrderResponse) String() string { return proto.CompactTextString(m) } +func (*NewOrderResponse) ProtoMessage() {} +func (*NewOrderResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{10} +} + +func (m *NewOrderResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_NewOrderResponse.Unmarshal(m, b) +} +func (m *NewOrderResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_NewOrderResponse.Marshal(b, m, deterministic) +} +func (m *NewOrderResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_NewOrderResponse.Merge(m, src) +} +func (m *NewOrderResponse) XXX_Size() int { + return xxx_messageInfo_NewOrderResponse.Size(m) +} +func (m *NewOrderResponse) XXX_DiscardUnknown() { + xxx_messageInfo_NewOrderResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_NewOrderResponse proto.InternalMessageInfo + +func (m *NewOrderResponse) GetOrder() *Order { + if m != nil { + return m.Order + } + return nil +} + +type OrdersRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrdersRequest) Reset() { *m = OrdersRequest{} } +func (m *OrdersRequest) String() string { return proto.CompactTextString(m) } +func (*OrdersRequest) ProtoMessage() {} +func (*OrdersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{11} +} + +func (m *OrdersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrdersRequest.Unmarshal(m, b) +} +func (m *OrdersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrdersRequest.Marshal(b, m, deterministic) +} +func (m *OrdersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrdersRequest.Merge(m, src) +} +func (m *OrdersRequest) XXX_Size() int { + return xxx_messageInfo_OrdersRequest.Size(m) +} +func (m *OrdersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_OrdersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_OrdersRequest proto.InternalMessageInfo + +func (m *OrdersRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *OrdersRequest) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +type OrdersResponse struct { + Orders []*Order `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrdersResponse) Reset() { *m = OrdersResponse{} } +func (m *OrdersResponse) String() string { return proto.CompactTextString(m) } +func (*OrdersResponse) ProtoMessage() {} +func (*OrdersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{12} +} + +func (m *OrdersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrdersResponse.Unmarshal(m, b) +} +func (m *OrdersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrdersResponse.Marshal(b, m, deterministic) +} +func (m *OrdersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrdersResponse.Merge(m, src) +} +func (m *OrdersResponse) XXX_Size() int { + return xxx_messageInfo_OrdersResponse.Size(m) +} +func (m *OrdersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_OrdersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_OrdersResponse proto.InternalMessageInfo + +func (m *OrdersResponse) GetOrders() []*Order { + if m != nil { + return m.Orders + } + return nil +} + +// fund management +type UpdateBalanceRequest struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Type string `protobuf:"bytes,2,opt,name=type,proto3" json:"type,omitempty"` + UserId string `protobuf:"bytes,3,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Currency string `protobuf:"bytes,4,opt,name=currency,proto3" json:"currency,omitempty"` + Amount string `protobuf:"bytes,5,opt,name=amount,proto3" json:"amount,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateBalanceRequest) Reset() { *m = UpdateBalanceRequest{} } +func (m *UpdateBalanceRequest) String() string { return proto.CompactTextString(m) } +func (*UpdateBalanceRequest) ProtoMessage() {} +func (*UpdateBalanceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{13} +} + +func (m *UpdateBalanceRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateBalanceRequest.Unmarshal(m, b) +} +func (m *UpdateBalanceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateBalanceRequest.Marshal(b, m, deterministic) +} +func (m *UpdateBalanceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateBalanceRequest.Merge(m, src) +} +func (m *UpdateBalanceRequest) XXX_Size() int { + return xxx_messageInfo_UpdateBalanceRequest.Size(m) +} +func (m *UpdateBalanceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateBalanceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateBalanceRequest proto.InternalMessageInfo + +func (m *UpdateBalanceRequest) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *UpdateBalanceRequest) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *UpdateBalanceRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *UpdateBalanceRequest) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *UpdateBalanceRequest) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +type UpdateBalanceResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *UpdateBalanceResponse) Reset() { *m = UpdateBalanceResponse{} } +func (m *UpdateBalanceResponse) String() string { return proto.CompactTextString(m) } +func (*UpdateBalanceResponse) ProtoMessage() {} +func (*UpdateBalanceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{14} +} + +func (m *UpdateBalanceResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_UpdateBalanceResponse.Unmarshal(m, b) +} +func (m *UpdateBalanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_UpdateBalanceResponse.Marshal(b, m, deterministic) +} +func (m *UpdateBalanceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_UpdateBalanceResponse.Merge(m, src) +} +func (m *UpdateBalanceResponse) XXX_Size() int { + return xxx_messageInfo_UpdateBalanceResponse.Size(m) +} +func (m *UpdateBalanceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_UpdateBalanceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_UpdateBalanceResponse proto.InternalMessageInfo + +type GetAssetSummaryRequest struct { + Currencies []string `protobuf:"bytes,1,rep,name=currencies,proto3" json:"currencies,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAssetSummaryRequest) Reset() { *m = GetAssetSummaryRequest{} } +func (m *GetAssetSummaryRequest) String() string { return proto.CompactTextString(m) } +func (*GetAssetSummaryRequest) ProtoMessage() {} +func (*GetAssetSummaryRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{15} +} + +func (m *GetAssetSummaryRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAssetSummaryRequest.Unmarshal(m, b) +} +func (m *GetAssetSummaryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAssetSummaryRequest.Marshal(b, m, deterministic) +} +func (m *GetAssetSummaryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAssetSummaryRequest.Merge(m, src) +} +func (m *GetAssetSummaryRequest) XXX_Size() int { + return xxx_messageInfo_GetAssetSummaryRequest.Size(m) +} +func (m *GetAssetSummaryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetAssetSummaryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAssetSummaryRequest proto.InternalMessageInfo + +func (m *GetAssetSummaryRequest) GetCurrencies() []string { + if m != nil { + return m.Currencies + } + return nil +} + +type Asset struct { + Currency string `protobuf:"bytes,1,opt,name=currency,proto3" json:"currency,omitempty"` + Total string `protobuf:"bytes,2,opt,name=total,proto3" json:"total,omitempty"` + Available string `protobuf:"bytes,3,opt,name=available,proto3" json:"available,omitempty"` + AvailableCount int64 `protobuf:"varint,4,opt,name=available_count,json=availableCount,proto3" json:"available_count,omitempty"` + Hold string `protobuf:"bytes,5,opt,name=hold,proto3" json:"hold,omitempty"` + HoldCount int64 `protobuf:"varint,6,opt,name=hold_count,json=holdCount,proto3" json:"hold_count,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Asset) Reset() { *m = Asset{} } +func (m *Asset) String() string { return proto.CompactTextString(m) } +func (*Asset) ProtoMessage() {} +func (*Asset) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{16} +} + +func (m *Asset) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Asset.Unmarshal(m, b) +} +func (m *Asset) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Asset.Marshal(b, m, deterministic) +} +func (m *Asset) XXX_Merge(src proto.Message) { + xxx_messageInfo_Asset.Merge(m, src) +} +func (m *Asset) XXX_Size() int { + return xxx_messageInfo_Asset.Size(m) +} +func (m *Asset) XXX_DiscardUnknown() { + xxx_messageInfo_Asset.DiscardUnknown(m) +} + +var xxx_messageInfo_Asset proto.InternalMessageInfo + +func (m *Asset) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *Asset) GetTotal() string { + if m != nil { + return m.Total + } + return "" +} + +func (m *Asset) GetAvailable() string { + if m != nil { + return m.Available + } + return "" +} + +func (m *Asset) GetAvailableCount() int64 { + if m != nil { + return m.AvailableCount + } + return 0 +} + +func (m *Asset) GetHold() string { + if m != nil { + return m.Hold + } + return "" +} + +func (m *Asset) GetHoldCount() int64 { + if m != nil { + return m.HoldCount + } + return 0 +} + +type GetAssetSummaryResponse struct { + Assets []*Asset `protobuf:"bytes,1,rep,name=assets,proto3" json:"assets,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAssetSummaryResponse) Reset() { *m = GetAssetSummaryResponse{} } +func (m *GetAssetSummaryResponse) String() string { return proto.CompactTextString(m) } +func (*GetAssetSummaryResponse) ProtoMessage() {} +func (*GetAssetSummaryResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{17} +} + +func (m *GetAssetSummaryResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAssetSummaryResponse.Unmarshal(m, b) +} +func (m *GetAssetSummaryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAssetSummaryResponse.Marshal(b, m, deterministic) +} +func (m *GetAssetSummaryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAssetSummaryResponse.Merge(m, src) +} +func (m *GetAssetSummaryResponse) XXX_Size() int { + return xxx_messageInfo_GetAssetSummaryResponse.Size(m) +} +func (m *GetAssetSummaryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetAssetSummaryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAssetSummaryResponse proto.InternalMessageInfo + +func (m *GetAssetSummaryResponse) GetAssets() []*Asset { + if m != nil { + return m.Assets + } + return nil +} + +type OrderCancelRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Symbol string `protobuf:"bytes,2,opt,name=symbol,proto3" json:"symbol,omitempty"` + OrderId string `protobuf:"bytes,3,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderCancelRequest) Reset() { *m = OrderCancelRequest{} } +func (m *OrderCancelRequest) String() string { return proto.CompactTextString(m) } +func (*OrderCancelRequest) ProtoMessage() {} +func (*OrderCancelRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{18} +} + +func (m *OrderCancelRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderCancelRequest.Unmarshal(m, b) +} +func (m *OrderCancelRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderCancelRequest.Marshal(b, m, deterministic) +} +func (m *OrderCancelRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderCancelRequest.Merge(m, src) +} +func (m *OrderCancelRequest) XXX_Size() int { + return xxx_messageInfo_OrderCancelRequest.Size(m) +} +func (m *OrderCancelRequest) XXX_DiscardUnknown() { + xxx_messageInfo_OrderCancelRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderCancelRequest proto.InternalMessageInfo + +func (m *OrderCancelRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *OrderCancelRequest) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +func (m *OrderCancelRequest) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +type OrderCancelResponse struct { + Order *Order `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderCancelResponse) Reset() { *m = OrderCancelResponse{} } +func (m *OrderCancelResponse) String() string { return proto.CompactTextString(m) } +func (*OrderCancelResponse) ProtoMessage() {} +func (*OrderCancelResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{19} +} + +func (m *OrderCancelResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderCancelResponse.Unmarshal(m, b) +} +func (m *OrderCancelResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderCancelResponse.Marshal(b, m, deterministic) +} +func (m *OrderCancelResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderCancelResponse.Merge(m, src) +} +func (m *OrderCancelResponse) XXX_Size() int { + return xxx_messageInfo_OrderCancelResponse.Size(m) +} +func (m *OrderCancelResponse) XXX_DiscardUnknown() { + xxx_messageInfo_OrderCancelResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderCancelResponse proto.InternalMessageInfo + +func (m *OrderCancelResponse) GetOrder() *Order { + if m != nil { + return m.Order + } + return nil +} + +type OrderBookRequest struct { + Symbol string `protobuf:"bytes,1,opt,name=symbol,proto3" json:"symbol,omitempty"` + Side int32 `protobuf:"varint,2,opt,name=side,proto3" json:"side,omitempty"` + // int32 offset = 3; + Limit uint64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderBookRequest) Reset() { *m = OrderBookRequest{} } +func (m *OrderBookRequest) String() string { return proto.CompactTextString(m) } +func (*OrderBookRequest) ProtoMessage() {} +func (*OrderBookRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{20} +} + +func (m *OrderBookRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderBookRequest.Unmarshal(m, b) +} +func (m *OrderBookRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderBookRequest.Marshal(b, m, deterministic) +} +func (m *OrderBookRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderBookRequest.Merge(m, src) +} +func (m *OrderBookRequest) XXX_Size() int { + return xxx_messageInfo_OrderBookRequest.Size(m) +} +func (m *OrderBookRequest) XXX_DiscardUnknown() { + xxx_messageInfo_OrderBookRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderBookRequest proto.InternalMessageInfo + +func (m *OrderBookRequest) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +func (m *OrderBookRequest) GetSide() int32 { + if m != nil { + return m.Side + } + return 0 +} + +func (m *OrderBookRequest) GetLimit() uint64 { + if m != nil { + return m.Limit + } + return 0 +} + +type OrderBookResponse struct { + Orders []*Order `protobuf:"bytes,1,rep,name=orders,proto3" json:"orders,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderBookResponse) Reset() { *m = OrderBookResponse{} } +func (m *OrderBookResponse) String() string { return proto.CompactTextString(m) } +func (*OrderBookResponse) ProtoMessage() {} +func (*OrderBookResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{21} +} + +func (m *OrderBookResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderBookResponse.Unmarshal(m, b) +} +func (m *OrderBookResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderBookResponse.Marshal(b, m, deterministic) +} +func (m *OrderBookResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderBookResponse.Merge(m, src) +} +func (m *OrderBookResponse) XXX_Size() int { + return xxx_messageInfo_OrderBookResponse.Size(m) +} +func (m *OrderBookResponse) XXX_DiscardUnknown() { + xxx_messageInfo_OrderBookResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderBookResponse proto.InternalMessageInfo + +func (m *OrderBookResponse) GetOrders() []*Order { + if m != nil { + return m.Orders + } + return nil +} + +type OrderBookDepthRequest struct { + Symbol string `protobuf:"bytes,1,opt,name=symbol,proto3" json:"symbol,omitempty"` + Limit uint64 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderBookDepthRequest) Reset() { *m = OrderBookDepthRequest{} } +func (m *OrderBookDepthRequest) String() string { return proto.CompactTextString(m) } +func (*OrderBookDepthRequest) ProtoMessage() {} +func (*OrderBookDepthRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{22} +} + +func (m *OrderBookDepthRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderBookDepthRequest.Unmarshal(m, b) +} +func (m *OrderBookDepthRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderBookDepthRequest.Marshal(b, m, deterministic) +} +func (m *OrderBookDepthRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderBookDepthRequest.Merge(m, src) +} +func (m *OrderBookDepthRequest) XXX_Size() int { + return xxx_messageInfo_OrderBookDepthRequest.Size(m) +} +func (m *OrderBookDepthRequest) XXX_DiscardUnknown() { + xxx_messageInfo_OrderBookDepthRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderBookDepthRequest proto.InternalMessageInfo + +func (m *OrderBookDepthRequest) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +func (m *OrderBookDepthRequest) GetLimit() uint64 { + if m != nil { + return m.Limit + } + return 0 +} + +type OrderBookData struct { + Price string `protobuf:"bytes,1,opt,name=price,proto3" json:"price,omitempty"` + Quantity string `protobuf:"bytes,2,opt,name=quantity,proto3" json:"quantity,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderBookData) Reset() { *m = OrderBookData{} } +func (m *OrderBookData) String() string { return proto.CompactTextString(m) } +func (*OrderBookData) ProtoMessage() {} +func (*OrderBookData) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{23} +} + +func (m *OrderBookData) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderBookData.Unmarshal(m, b) +} +func (m *OrderBookData) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderBookData.Marshal(b, m, deterministic) +} +func (m *OrderBookData) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderBookData.Merge(m, src) +} +func (m *OrderBookData) XXX_Size() int { + return xxx_messageInfo_OrderBookData.Size(m) +} +func (m *OrderBookData) XXX_DiscardUnknown() { + xxx_messageInfo_OrderBookData.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderBookData proto.InternalMessageInfo + +func (m *OrderBookData) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *OrderBookData) GetQuantity() string { + if m != nil { + return m.Quantity + } + return "" +} + +type OrderBookDepthResponse struct { + Asks []*OrderBookData `protobuf:"bytes,1,rep,name=asks,proto3" json:"asks,omitempty"` + Bids []*OrderBookData `protobuf:"bytes,2,rep,name=bids,proto3" json:"bids,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderBookDepthResponse) Reset() { *m = OrderBookDepthResponse{} } +func (m *OrderBookDepthResponse) String() string { return proto.CompactTextString(m) } +func (*OrderBookDepthResponse) ProtoMessage() {} +func (*OrderBookDepthResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{24} +} + +func (m *OrderBookDepthResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderBookDepthResponse.Unmarshal(m, b) +} +func (m *OrderBookDepthResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderBookDepthResponse.Marshal(b, m, deterministic) +} +func (m *OrderBookDepthResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderBookDepthResponse.Merge(m, src) +} +func (m *OrderBookDepthResponse) XXX_Size() int { + return xxx_messageInfo_OrderBookDepthResponse.Size(m) +} +func (m *OrderBookDepthResponse) XXX_DiscardUnknown() { + xxx_messageInfo_OrderBookDepthResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderBookDepthResponse proto.InternalMessageInfo + +func (m *OrderBookDepthResponse) GetAsks() []*OrderBookData { + if m != nil { + return m.Asks + } + return nil +} + +func (m *OrderBookDepthResponse) GetBids() []*OrderBookData { + if m != nil { + return m.Bids + } + return nil +} + +type OrderDetailRequest struct { + Symbol string `protobuf:"bytes,1,opt,name=symbol,proto3" json:"symbol,omitempty"` + OrderId string `protobuf:"bytes,2,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderDetailRequest) Reset() { *m = OrderDetailRequest{} } +func (m *OrderDetailRequest) String() string { return proto.CompactTextString(m) } +func (*OrderDetailRequest) ProtoMessage() {} +func (*OrderDetailRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{25} +} + +func (m *OrderDetailRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderDetailRequest.Unmarshal(m, b) +} +func (m *OrderDetailRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderDetailRequest.Marshal(b, m, deterministic) +} +func (m *OrderDetailRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderDetailRequest.Merge(m, src) +} +func (m *OrderDetailRequest) XXX_Size() int { + return xxx_messageInfo_OrderDetailRequest.Size(m) +} +func (m *OrderDetailRequest) XXX_DiscardUnknown() { + xxx_messageInfo_OrderDetailRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderDetailRequest proto.InternalMessageInfo + +func (m *OrderDetailRequest) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +func (m *OrderDetailRequest) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +type OrderDetailResponse struct { + Order *Order `protobuf:"bytes,1,opt,name=order,proto3" json:"order,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderDetailResponse) Reset() { *m = OrderDetailResponse{} } +func (m *OrderDetailResponse) String() string { return proto.CompactTextString(m) } +func (*OrderDetailResponse) ProtoMessage() {} +func (*OrderDetailResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{26} +} + +func (m *OrderDetailResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderDetailResponse.Unmarshal(m, b) +} +func (m *OrderDetailResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderDetailResponse.Marshal(b, m, deterministic) +} +func (m *OrderDetailResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderDetailResponse.Merge(m, src) +} +func (m *OrderDetailResponse) XXX_Size() int { + return xxx_messageInfo_OrderDetailResponse.Size(m) +} +func (m *OrderDetailResponse) XXX_DiscardUnknown() { + xxx_messageInfo_OrderDetailResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderDetailResponse proto.InternalMessageInfo + +func (m *OrderDetailResponse) GetOrder() *Order { + if m != nil { + return m.Order + } + return nil +} + +type GetMarketSummaryRequest struct { + Symbols []string `protobuf:"bytes,1,rep,name=symbols,proto3" json:"symbols,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMarketSummaryRequest) Reset() { *m = GetMarketSummaryRequest{} } +func (m *GetMarketSummaryRequest) String() string { return proto.CompactTextString(m) } +func (*GetMarketSummaryRequest) ProtoMessage() {} +func (*GetMarketSummaryRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{27} +} + +func (m *GetMarketSummaryRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMarketSummaryRequest.Unmarshal(m, b) +} +func (m *GetMarketSummaryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMarketSummaryRequest.Marshal(b, m, deterministic) +} +func (m *GetMarketSummaryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMarketSummaryRequest.Merge(m, src) +} +func (m *GetMarketSummaryRequest) XXX_Size() int { + return xxx_messageInfo_GetMarketSummaryRequest.Size(m) +} +func (m *GetMarketSummaryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetMarketSummaryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMarketSummaryRequest proto.InternalMessageInfo + +func (m *GetMarketSummaryRequest) GetSymbols() []string { + if m != nil { + return m.Symbols + } + return nil +} + +type Market struct { + Symbol string `protobuf:"bytes,1,opt,name=symbol,proto3" json:"symbol,omitempty"` + AsksAmount string `protobuf:"bytes,2,opt,name=asks_amount,json=asksAmount,proto3" json:"asks_amount,omitempty"` + AsksCount int64 `protobuf:"varint,3,opt,name=asks_count,json=asksCount,proto3" json:"asks_count,omitempty"` + BidsAmount string `protobuf:"bytes,4,opt,name=bids_amount,json=bidsAmount,proto3" json:"bids_amount,omitempty"` + BidsCount int64 `protobuf:"varint,5,opt,name=bids_count,json=bidsCount,proto3" json:"bids_count,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Market) Reset() { *m = Market{} } +func (m *Market) String() string { return proto.CompactTextString(m) } +func (*Market) ProtoMessage() {} +func (*Market) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{28} +} + +func (m *Market) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Market.Unmarshal(m, b) +} +func (m *Market) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Market.Marshal(b, m, deterministic) +} +func (m *Market) XXX_Merge(src proto.Message) { + xxx_messageInfo_Market.Merge(m, src) +} +func (m *Market) XXX_Size() int { + return xxx_messageInfo_Market.Size(m) +} +func (m *Market) XXX_DiscardUnknown() { + xxx_messageInfo_Market.DiscardUnknown(m) +} + +var xxx_messageInfo_Market proto.InternalMessageInfo + +func (m *Market) GetSymbol() string { + if m != nil { + return m.Symbol + } + return "" +} + +func (m *Market) GetAsksAmount() string { + if m != nil { + return m.AsksAmount + } + return "" +} + +func (m *Market) GetAsksCount() int64 { + if m != nil { + return m.AsksCount + } + return 0 +} + +func (m *Market) GetBidsAmount() string { + if m != nil { + return m.BidsAmount + } + return "" +} + +func (m *Market) GetBidsCount() int64 { + if m != nil { + return m.BidsCount + } + return 0 +} + +type GetMarketSummaryResponse struct { + Markets []*Market `protobuf:"bytes,1,rep,name=markets,proto3" json:"markets,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMarketSummaryResponse) Reset() { *m = GetMarketSummaryResponse{} } +func (m *GetMarketSummaryResponse) String() string { return proto.CompactTextString(m) } +func (*GetMarketSummaryResponse) ProtoMessage() {} +func (*GetMarketSummaryResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_47c558542612ea92, []int{29} +} + +func (m *GetMarketSummaryResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMarketSummaryResponse.Unmarshal(m, b) +} +func (m *GetMarketSummaryResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMarketSummaryResponse.Marshal(b, m, deterministic) +} +func (m *GetMarketSummaryResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMarketSummaryResponse.Merge(m, src) +} +func (m *GetMarketSummaryResponse) XXX_Size() int { + return xxx_messageInfo_GetMarketSummaryResponse.Size(m) +} +func (m *GetMarketSummaryResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetMarketSummaryResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMarketSummaryResponse proto.InternalMessageInfo + +func (m *GetMarketSummaryResponse) GetMarkets() []*Market { + if m != nil { + return m.Markets + } + return nil +} + +func init() { + proto.RegisterType((*EmptyRequest)(nil), "protos.EmptyRequest") + proto.RegisterType((*Currency)(nil), "protos.Currency") + proto.RegisterType((*GetCurrencyResponse)(nil), "protos.GetCurrencyResponse") + proto.RegisterType((*Symbol)(nil), "protos.Symbol") + proto.RegisterType((*GetSymbolsResponse)(nil), "protos.GetSymbolsResponse") + proto.RegisterType((*BalancesRequest)(nil), "protos.BalancesRequest") + proto.RegisterType((*Balance)(nil), "protos.Balance") + proto.RegisterType((*BalancesResponse)(nil), "protos.BalancesResponse") + proto.RegisterType((*NewOrderRequest)(nil), "protos.NewOrderRequest") + proto.RegisterType((*Order)(nil), "protos.Order") + proto.RegisterType((*NewOrderResponse)(nil), "protos.NewOrderResponse") + proto.RegisterType((*OrdersRequest)(nil), "protos.OrdersRequest") + proto.RegisterType((*OrdersResponse)(nil), "protos.OrdersResponse") + proto.RegisterType((*UpdateBalanceRequest)(nil), "protos.UpdateBalanceRequest") + proto.RegisterType((*UpdateBalanceResponse)(nil), "protos.UpdateBalanceResponse") + proto.RegisterType((*GetAssetSummaryRequest)(nil), "protos.GetAssetSummaryRequest") + proto.RegisterType((*Asset)(nil), "protos.Asset") + proto.RegisterType((*GetAssetSummaryResponse)(nil), "protos.GetAssetSummaryResponse") + proto.RegisterType((*OrderCancelRequest)(nil), "protos.OrderCancelRequest") + proto.RegisterType((*OrderCancelResponse)(nil), "protos.OrderCancelResponse") + proto.RegisterType((*OrderBookRequest)(nil), "protos.OrderBookRequest") + proto.RegisterType((*OrderBookResponse)(nil), "protos.OrderBookResponse") + proto.RegisterType((*OrderBookDepthRequest)(nil), "protos.OrderBookDepthRequest") + proto.RegisterType((*OrderBookData)(nil), "protos.OrderBookData") + proto.RegisterType((*OrderBookDepthResponse)(nil), "protos.OrderBookDepthResponse") + proto.RegisterType((*OrderDetailRequest)(nil), "protos.OrderDetailRequest") + proto.RegisterType((*OrderDetailResponse)(nil), "protos.OrderDetailResponse") + proto.RegisterType((*GetMarketSummaryRequest)(nil), "protos.GetMarketSummaryRequest") + proto.RegisterType((*Market)(nil), "protos.Market") + proto.RegisterType((*GetMarketSummaryResponse)(nil), "protos.GetMarketSummaryResponse") +} + +func init() { proto.RegisterFile("protos/match.proto", fileDescriptor_47c558542612ea92) } + +var fileDescriptor_47c558542612ea92 = []byte{ + // 1334 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x57, 0xe9, 0x6e, 0xdb, 0x46, + 0x10, 0x86, 0x2e, 0x4a, 0x1a, 0x45, 0x47, 0x37, 0x8e, 0xcd, 0x30, 0x71, 0x1c, 0x30, 0xcd, 0x85, + 0xa2, 0x72, 0xe1, 0xfc, 0x48, 0x1b, 0xa0, 0x69, 0xe4, 0x1c, 0x46, 0x80, 0xa6, 0x01, 0x98, 0xa4, + 0x3f, 0xfa, 0x47, 0x5d, 0x91, 0x6b, 0x9b, 0x30, 0x0f, 0x99, 0xbb, 0x4c, 0xeb, 0xbe, 0x40, 0x5f, + 0xa3, 0xef, 0xd0, 0x17, 0xe8, 0x83, 0x14, 0x7d, 0x93, 0x02, 0xc5, 0x5e, 0x3c, 0xa5, 0x08, 0xee, + 0x2f, 0x71, 0x66, 0xbf, 0x99, 0x9d, 0x99, 0x9d, 0xf9, 0x76, 0x05, 0x68, 0x99, 0xc4, 0x2c, 0xa6, + 0xfb, 0x21, 0x66, 0xee, 0xe9, 0x54, 0x08, 0xc8, 0x90, 0x3a, 0x6b, 0xef, 0x24, 0x8e, 0x4f, 0x02, + 0xb2, 0x2f, 0xc4, 0x45, 0x7a, 0xbc, 0xcf, 0xfc, 0x90, 0x50, 0x86, 0xc3, 0xa5, 0x04, 0xda, 0x23, + 0xb8, 0xf2, 0x32, 0x5c, 0xb2, 0x0b, 0x87, 0x9c, 0xa7, 0x84, 0x32, 0x7b, 0x0a, 0xbd, 0xe7, 0x69, + 0x92, 0x90, 0xc8, 0xbd, 0x40, 0x23, 0x68, 0xfa, 0x9e, 0xd9, 0xb8, 0xdd, 0x78, 0xd0, 0x77, 0x9a, + 0xbe, 0x87, 0x10, 0xb4, 0x23, 0x1c, 0x12, 0xb3, 0x29, 0x34, 0xe2, 0xdb, 0x3e, 0x82, 0xab, 0x47, + 0x84, 0x69, 0x13, 0x87, 0xd0, 0x65, 0x1c, 0x51, 0x82, 0xbe, 0x02, 0x70, 0xa5, 0xce, 0x27, 0xd4, + 0x6c, 0xdc, 0x6e, 0x3d, 0x18, 0x1c, 0x4c, 0xe4, 0x96, 0x74, 0x9a, 0xa1, 0x0b, 0x18, 0xfb, 0xef, + 0x06, 0x18, 0xef, 0x2e, 0xc2, 0x45, 0x1c, 0xd4, 0xf6, 0xbd, 0x03, 0xc3, 0x05, 0xa6, 0x64, 0xae, + 0xd0, 0x17, 0x2a, 0x80, 0x2b, 0x5c, 0x99, 0x05, 0x7b, 0x17, 0x46, 0xe7, 0x69, 0xcc, 0x0a, 0xa8, + 0x96, 0x40, 0x0d, 0x85, 0x36, 0x83, 0xdd, 0x83, 0xb1, 0xf0, 0x15, 0xfa, 0xd1, 0x1c, 0x87, 0x71, + 0x1a, 0x31, 0xb3, 0x2d, 0x71, 0x5c, 0xfd, 0xc6, 0x8f, 0x66, 0x42, 0xc9, 0xdd, 0x09, 0x9c, 0x1f, + 0xb9, 0x09, 0x09, 0x49, 0xc4, 0xcc, 0x4e, 0x0e, 0x7b, 0xad, 0x95, 0xe8, 0x3e, 0x8c, 0xe5, 0xae, + 0x39, 0xce, 0x10, 0x38, 0x19, 0x4c, 0x06, 0xb4, 0x9f, 0x02, 0x3a, 0x22, 0x4c, 0x26, 0x48, 0xb3, + 0x32, 0x3d, 0x80, 0x2e, 0x95, 0x2a, 0x55, 0xa3, 0x91, 0xae, 0x91, 0x44, 0x3a, 0x7a, 0xd9, 0x7e, + 0x05, 0xe3, 0x43, 0x1c, 0xe0, 0xc8, 0x25, 0x54, 0x1d, 0x15, 0xda, 0x81, 0x6e, 0x4a, 0x49, 0x32, + 0xcf, 0x6a, 0x65, 0x70, 0xf1, 0xb5, 0x87, 0x2c, 0xe8, 0x55, 0x4a, 0x95, 0xc9, 0xf6, 0x39, 0x74, + 0x95, 0x9f, 0x12, 0xac, 0x51, 0x86, 0x21, 0x13, 0xba, 0x0b, 0x09, 0x53, 0x1e, 0xb4, 0x88, 0x6e, + 0x42, 0x1f, 0x7f, 0xc4, 0x7e, 0x80, 0x17, 0x01, 0x51, 0x25, 0xce, 0x15, 0xbc, 0x45, 0x4e, 0xe3, + 0xc0, 0x53, 0x35, 0x15, 0xdf, 0xf6, 0x77, 0x30, 0xc9, 0x43, 0x57, 0x89, 0x7f, 0x01, 0x3d, 0xe5, + 0x50, 0x67, 0x3e, 0xd6, 0x99, 0x2b, 0xac, 0x93, 0x01, 0xec, 0xbf, 0x1a, 0x30, 0xfe, 0x81, 0xfc, + 0xf2, 0x36, 0xf1, 0x48, 0xb2, 0x31, 0xf9, 0x7b, 0x30, 0x76, 0x03, 0x9f, 0x44, 0x6c, 0x1e, 0x73, + 0x3c, 0x07, 0xc8, 0x0c, 0x86, 0x52, 0x2d, 0xbc, 0xbc, 0xf6, 0xd0, 0x36, 0x18, 0xb2, 0xb6, 0x2a, + 0x09, 0x25, 0xf1, 0x0c, 0xd8, 0xc5, 0x92, 0xe8, 0x0c, 0xf8, 0x37, 0xd7, 0x51, 0xdf, 0x23, 0xaa, + 0x05, 0xc4, 0x37, 0xda, 0x82, 0xce, 0x32, 0xf1, 0x5d, 0xa2, 0xce, 0x5b, 0x0a, 0xdc, 0xab, 0xea, + 0xaa, 0xae, 0xf4, 0x2a, 0x25, 0xfb, 0xdf, 0x16, 0x74, 0xc4, 0xce, 0xb5, 0xe6, 0xce, 0xfc, 0x34, + 0x57, 0xfb, 0x69, 0x15, 0xfd, 0x14, 0xa2, 0x6e, 0x57, 0xa3, 0xae, 0x45, 0xa8, 0x33, 0x31, 0x0a, + 0x99, 0x7c, 0x03, 0xe0, 0x26, 0x04, 0x33, 0xe2, 0xcd, 0xb1, 0x8c, 0x71, 0x70, 0x60, 0x4d, 0x25, + 0x49, 0x4c, 0x35, 0x49, 0x4c, 0xdf, 0x6b, 0x92, 0x70, 0xfa, 0x0a, 0x3d, 0x63, 0xdc, 0x34, 0x5d, + 0x7a, 0xda, 0xb4, 0xb7, 0xd9, 0x54, 0xa1, 0x67, 0x0c, 0x3d, 0x82, 0xae, 0x17, 0x47, 0x84, 0xdb, + 0xf5, 0x37, 0xda, 0x19, 0x1c, 0x3a, 0x63, 0x68, 0x0f, 0x06, 0xc2, 0x28, 0x21, 0x98, 0xc6, 0x91, + 0x09, 0x22, 0x0b, 0xe0, 0x2a, 0x47, 0x68, 0xd0, 0x0d, 0xe8, 0x1f, 0xfb, 0x41, 0x30, 0x3f, 0x26, + 0x84, 0x9a, 0x03, 0xd9, 0xc0, 0x5c, 0xf1, 0x8a, 0x10, 0xca, 0x39, 0x83, 0x7f, 0xf3, 0x60, 0x65, + 0x1d, 0xaf, 0x48, 0xce, 0x90, 0x4a, 0x35, 0xe4, 0x0f, 0x61, 0x92, 0x90, 0x10, 0xfb, 0x91, 0x1f, + 0x9d, 0x68, 0xdc, 0x50, 0xe0, 0xc6, 0x99, 0x3e, 0xe7, 0x03, 0xf2, 0x2b, 0x71, 0x53, 0x9e, 0xfe, + 0x47, 0x1c, 0xa4, 0xc4, 0x1c, 0xc9, 0xae, 0xd2, 0xda, 0x1f, 0xb9, 0x52, 0x9c, 0x0f, 0xc3, 0x2c, + 0xa5, 0xe6, 0x58, 0x9d, 0x8f, 0x90, 0xec, 0xc7, 0x30, 0xc9, 0x3b, 0x58, 0xcd, 0xc0, 0x1d, 0xe8, + 0x88, 0x16, 0x15, 0xcd, 0x30, 0x38, 0x18, 0xea, 0x01, 0x90, 0x28, 0xb9, 0x66, 0x3f, 0x83, 0xa1, + 0x90, 0x37, 0x4f, 0x7d, 0xde, 0x1a, 0xcd, 0x62, 0x6b, 0xd8, 0x8f, 0x61, 0xa4, 0x3d, 0xa8, 0x8d, + 0xef, 0x82, 0x21, 0x9c, 0xeb, 0xd1, 0xab, 0xec, 0xac, 0x16, 0xed, 0xdf, 0x1b, 0xb0, 0xf5, 0x41, + 0x9c, 0xa1, 0x1e, 0x49, 0x15, 0xc2, 0x8a, 0x7b, 0x41, 0x34, 0x5a, 0xb3, 0xd0, 0x68, 0x85, 0x30, + 0x5b, 0x6b, 0xc9, 0xa9, 0x5d, 0x61, 0x9d, 0xbc, 0xeb, 0x3b, 0xa5, 0xe9, 0xd9, 0x81, 0x6b, 0x95, + 0x40, 0x64, 0x26, 0xf6, 0xd7, 0xb0, 0x7d, 0x44, 0xd8, 0x8c, 0x52, 0xc2, 0xde, 0xa5, 0x61, 0x88, + 0x13, 0x7d, 0x8f, 0xa1, 0x5b, 0xb5, 0x0b, 0xa8, 0x5f, 0xba, 0x6e, 0xfe, 0x6c, 0x40, 0x47, 0xd8, + 0x7d, 0x92, 0x06, 0xb7, 0xa0, 0xc3, 0x62, 0x86, 0x75, 0x49, 0xa5, 0xb0, 0x81, 0x02, 0xef, 0xc3, + 0x38, 0x13, 0xe6, 0x6e, 0x76, 0xc3, 0xb4, 0x9c, 0x51, 0xa6, 0x7e, 0x2e, 0x5a, 0x4a, 0x73, 0x65, + 0x27, 0xe7, 0x4a, 0xb4, 0x0b, 0xc0, 0x7f, 0x95, 0x9d, 0x21, 0xec, 0xfa, 0x5c, 0x23, 0x4c, 0xec, + 0x67, 0xb0, 0x53, 0xcb, 0x37, 0x3f, 0x54, 0xcc, 0xf5, 0xb5, 0x43, 0x15, 0x68, 0x47, 0x2d, 0xda, + 0x3f, 0x03, 0x12, 0xa7, 0xfc, 0x9c, 0xd7, 0x31, 0xf8, 0xbf, 0x4d, 0x85, 0xae, 0x43, 0x2f, 0xa3, + 0x57, 0x59, 0x81, 0x6e, 0x2c, 0x89, 0xd5, 0x7e, 0x02, 0x57, 0x4b, 0x3b, 0x5c, 0xa6, 0xdb, 0xdf, + 0xc3, 0x44, 0xc8, 0x87, 0x71, 0x7c, 0xa6, 0x63, 0xcb, 0x43, 0x68, 0xac, 0xa4, 0x3c, 0x1e, 0x58, + 0x27, 0x27, 0xe5, 0xc0, 0x0f, 0x7d, 0xc9, 0x9a, 0x6d, 0x47, 0x0a, 0xf6, 0x13, 0xf8, 0xac, 0xe0, + 0xf5, 0x72, 0x43, 0xf0, 0x12, 0xae, 0x65, 0xb6, 0x2f, 0xc8, 0x92, 0x9d, 0x6e, 0x0a, 0x2b, 0x0b, + 0xa1, 0x59, 0x0c, 0x61, 0xa6, 0xc6, 0x58, 0xb8, 0xc1, 0x0c, 0xe7, 0xb4, 0xdf, 0x28, 0xd2, 0xbe, + 0x05, 0xbd, 0xf3, 0x14, 0x47, 0xcc, 0x67, 0xd9, 0xcd, 0xad, 0x65, 0x3b, 0x82, 0xed, 0x6a, 0x24, + 0x2a, 0x95, 0x87, 0xd0, 0xc6, 0xf4, 0x4c, 0x27, 0x72, 0xad, 0x94, 0x88, 0xde, 0xd0, 0x11, 0x10, + 0x0e, 0x5d, 0xf8, 0x1e, 0x35, 0x9b, 0x9f, 0x84, 0x72, 0x88, 0x7d, 0xa4, 0x3a, 0xe5, 0x05, 0x61, + 0xd8, 0x0f, 0x36, 0xa5, 0x5d, 0x6c, 0x88, 0xe6, 0xea, 0x86, 0xd0, 0x8e, 0x2e, 0xd3, 0x10, 0x8f, + 0x44, 0xc3, 0xbf, 0xc1, 0xc9, 0x59, 0x6d, 0xc2, 0xcd, 0xf2, 0xdb, 0xa9, 0x9f, 0xbf, 0x95, 0xfe, + 0x68, 0x80, 0x21, 0x4d, 0xd6, 0x86, 0xbb, 0x07, 0x03, 0x5e, 0x0f, 0x4d, 0xfa, 0x32, 0x62, 0xe0, + 0x2a, 0xc5, 0xf7, 0xbb, 0x20, 0x24, 0x35, 0x88, 0x2d, 0x39, 0x88, 0x5c, 0x23, 0x67, 0x77, 0x0f, + 0x06, 0xbc, 0x48, 0xe5, 0x27, 0x24, 0x70, 0x55, 0x6e, 0x2f, 0x00, 0x6e, 0x46, 0x67, 0x2d, 0xa7, + 0xcf, 0x35, 0x72, 0x90, 0x5f, 0x80, 0x59, 0xcf, 0x2b, 0x7f, 0x14, 0x86, 0x62, 0xa1, 0xf6, 0x28, + 0x94, 0x78, 0x47, 0x2f, 0x1f, 0xfc, 0x63, 0x40, 0xe7, 0x0d, 0x7f, 0xf5, 0xa3, 0x43, 0x18, 0xe6, + 0xcf, 0x70, 0x9f, 0x50, 0xb4, 0xa5, 0x6d, 0x8a, 0xaf, 0x7b, 0xeb, 0x86, 0xd6, 0xae, 0x7a, 0xb3, + 0x3f, 0x05, 0xc8, 0x9f, 0xa8, 0x6b, 0x1c, 0x58, 0x05, 0x07, 0xd5, 0xc7, 0xec, 0xb7, 0xd0, 0xd3, + 0xef, 0x3c, 0xb4, 0x53, 0x79, 0xcd, 0xe9, 0xeb, 0xcb, 0x32, 0xeb, 0x0b, 0xb9, 0xb9, 0xbe, 0x22, + 0x73, 0xf3, 0xca, 0xb3, 0x2f, 0x37, 0xaf, 0xdd, 0xa6, 0x8f, 0xc1, 0x90, 0xd7, 0x1c, 0x2a, 0x77, + 0x75, 0xb6, 0xf3, 0x76, 0x55, 0xad, 0x0c, 0xbf, 0x87, 0x61, 0xe9, 0x72, 0x41, 0x37, 0x35, 0x70, + 0xd5, 0xe5, 0x67, 0xed, 0xae, 0x59, 0x55, 0xde, 0x1c, 0x18, 0x57, 0x18, 0x1a, 0xdd, 0x2a, 0xd4, + 0x6c, 0xc5, 0x55, 0x65, 0xed, 0xad, 0x5d, 0x57, 0x3e, 0x5f, 0xc1, 0xa0, 0xc0, 0xa8, 0xc8, 0x2a, + 0x25, 0x52, 0x22, 0xf2, 0xfc, 0x80, 0x57, 0x51, 0xf0, 0x33, 0xe8, 0x67, 0x83, 0x8e, 0xcc, 0xda, + 0xec, 0x6b, 0x1f, 0xd7, 0x57, 0xac, 0x28, 0x0f, 0x6f, 0xd5, 0x5b, 0x22, 0xe3, 0x20, 0xb4, 0x5b, + 0xa7, 0x90, 0x02, 0x4b, 0x5a, 0xb7, 0xd6, 0x2d, 0x57, 0x52, 0x93, 0xdc, 0x50, 0x49, 0xad, 0xc4, + 0x3c, 0x95, 0xd4, 0x2a, 0x64, 0xf2, 0x01, 0x26, 0xd5, 0x79, 0x42, 0xc5, 0xba, 0xae, 0x62, 0x10, + 0xeb, 0xf6, 0x7a, 0x80, 0x74, 0x7b, 0xf8, 0xf9, 0x4f, 0xf6, 0x89, 0xcf, 0x4e, 0xd3, 0xc5, 0xd4, + 0x8d, 0xc3, 0x7d, 0xef, 0x37, 0x46, 0xdc, 0xd3, 0x7d, 0xea, 0x27, 0x24, 0xfa, 0x72, 0x19, 0x53, + 0xf9, 0xaf, 0x9a, 0x2e, 0xe4, 0x9f, 0xed, 0x47, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x37, 0xa4, + 0xb8, 0x5f, 0x89, 0x0f, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MatchClient is the client API for Match service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MatchClient interface { + GetCurrencies(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*GetCurrencyResponse, error) + GetSymbols(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*GetSymbolsResponse, error) + Balances(ctx context.Context, in *BalancesRequest, opts ...grpc.CallOption) (*BalancesResponse, error) + NewOrder(ctx context.Context, in *NewOrderRequest, opts ...grpc.CallOption) (*NewOrderResponse, error) + Orders(ctx context.Context, in *OrdersRequest, opts ...grpc.CallOption) (*OrdersResponse, error) + UpdateBalance(ctx context.Context, in *UpdateBalanceRequest, opts ...grpc.CallOption) (*UpdateBalanceResponse, error) + GetAssetSummary(ctx context.Context, in *GetAssetSummaryRequest, opts ...grpc.CallOption) (*GetAssetSummaryResponse, error) + OrderCancel(ctx context.Context, in *OrderCancelRequest, opts ...grpc.CallOption) (*OrderCancelResponse, error) + OrderBook(ctx context.Context, in *OrderBookRequest, opts ...grpc.CallOption) (*OrderBookResponse, error) + OrderBookDepth(ctx context.Context, in *OrderBookDepthRequest, opts ...grpc.CallOption) (*OrderBookDepthResponse, error) + OrderDetail(ctx context.Context, in *OrderDetailRequest, opts ...grpc.CallOption) (*OrderDetailResponse, error) + GetMarketSummary(ctx context.Context, in *GetMarketSummaryRequest, opts ...grpc.CallOption) (*GetMarketSummaryResponse, error) +} + +type matchClient struct { + cc *grpc.ClientConn +} + +func NewMatchClient(cc *grpc.ClientConn) MatchClient { + return &matchClient{cc} +} + +func (c *matchClient) GetCurrencies(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*GetCurrencyResponse, error) { + out := new(GetCurrencyResponse) + err := c.cc.Invoke(ctx, "/protos.Match/GetCurrencies", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) GetSymbols(ctx context.Context, in *EmptyRequest, opts ...grpc.CallOption) (*GetSymbolsResponse, error) { + out := new(GetSymbolsResponse) + err := c.cc.Invoke(ctx, "/protos.Match/GetSymbols", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) Balances(ctx context.Context, in *BalancesRequest, opts ...grpc.CallOption) (*BalancesResponse, error) { + out := new(BalancesResponse) + err := c.cc.Invoke(ctx, "/protos.Match/Balances", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) NewOrder(ctx context.Context, in *NewOrderRequest, opts ...grpc.CallOption) (*NewOrderResponse, error) { + out := new(NewOrderResponse) + err := c.cc.Invoke(ctx, "/protos.Match/NewOrder", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) Orders(ctx context.Context, in *OrdersRequest, opts ...grpc.CallOption) (*OrdersResponse, error) { + out := new(OrdersResponse) + err := c.cc.Invoke(ctx, "/protos.Match/Orders", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) UpdateBalance(ctx context.Context, in *UpdateBalanceRequest, opts ...grpc.CallOption) (*UpdateBalanceResponse, error) { + out := new(UpdateBalanceResponse) + err := c.cc.Invoke(ctx, "/protos.Match/UpdateBalance", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) GetAssetSummary(ctx context.Context, in *GetAssetSummaryRequest, opts ...grpc.CallOption) (*GetAssetSummaryResponse, error) { + out := new(GetAssetSummaryResponse) + err := c.cc.Invoke(ctx, "/protos.Match/GetAssetSummary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) OrderCancel(ctx context.Context, in *OrderCancelRequest, opts ...grpc.CallOption) (*OrderCancelResponse, error) { + out := new(OrderCancelResponse) + err := c.cc.Invoke(ctx, "/protos.Match/OrderCancel", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) OrderBook(ctx context.Context, in *OrderBookRequest, opts ...grpc.CallOption) (*OrderBookResponse, error) { + out := new(OrderBookResponse) + err := c.cc.Invoke(ctx, "/protos.Match/OrderBook", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) OrderBookDepth(ctx context.Context, in *OrderBookDepthRequest, opts ...grpc.CallOption) (*OrderBookDepthResponse, error) { + out := new(OrderBookDepthResponse) + err := c.cc.Invoke(ctx, "/protos.Match/OrderBookDepth", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) OrderDetail(ctx context.Context, in *OrderDetailRequest, opts ...grpc.CallOption) (*OrderDetailResponse, error) { + out := new(OrderDetailResponse) + err := c.cc.Invoke(ctx, "/protos.Match/OrderDetail", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *matchClient) GetMarketSummary(ctx context.Context, in *GetMarketSummaryRequest, opts ...grpc.CallOption) (*GetMarketSummaryResponse, error) { + out := new(GetMarketSummaryResponse) + err := c.cc.Invoke(ctx, "/protos.Match/GetMarketSummary", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MatchServer is the server API for Match service. +type MatchServer interface { + GetCurrencies(context.Context, *EmptyRequest) (*GetCurrencyResponse, error) + GetSymbols(context.Context, *EmptyRequest) (*GetSymbolsResponse, error) + Balances(context.Context, *BalancesRequest) (*BalancesResponse, error) + NewOrder(context.Context, *NewOrderRequest) (*NewOrderResponse, error) + Orders(context.Context, *OrdersRequest) (*OrdersResponse, error) + UpdateBalance(context.Context, *UpdateBalanceRequest) (*UpdateBalanceResponse, error) + GetAssetSummary(context.Context, *GetAssetSummaryRequest) (*GetAssetSummaryResponse, error) + OrderCancel(context.Context, *OrderCancelRequest) (*OrderCancelResponse, error) + OrderBook(context.Context, *OrderBookRequest) (*OrderBookResponse, error) + OrderBookDepth(context.Context, *OrderBookDepthRequest) (*OrderBookDepthResponse, error) + OrderDetail(context.Context, *OrderDetailRequest) (*OrderDetailResponse, error) + GetMarketSummary(context.Context, *GetMarketSummaryRequest) (*GetMarketSummaryResponse, error) +} + +func RegisterMatchServer(s *grpc.Server, srv MatchServer) { + s.RegisterService(&_Match_serviceDesc, srv) +} + +func _Match_GetCurrencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).GetCurrencies(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/GetCurrencies", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).GetCurrencies(ctx, req.(*EmptyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_GetSymbols_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(EmptyRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).GetSymbols(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/GetSymbols", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).GetSymbols(ctx, req.(*EmptyRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_Balances_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(BalancesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).Balances(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/Balances", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).Balances(ctx, req.(*BalancesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_NewOrder_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NewOrderRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).NewOrder(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/NewOrder", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).NewOrder(ctx, req.(*NewOrderRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_Orders_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrdersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).Orders(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/Orders", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).Orders(ctx, req.(*OrdersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_UpdateBalance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UpdateBalanceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).UpdateBalance(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/UpdateBalance", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).UpdateBalance(ctx, req.(*UpdateBalanceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_GetAssetSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAssetSummaryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).GetAssetSummary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/GetAssetSummary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).GetAssetSummary(ctx, req.(*GetAssetSummaryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_OrderCancel_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderCancelRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).OrderCancel(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/OrderCancel", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).OrderCancel(ctx, req.(*OrderCancelRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_OrderBook_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderBookRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).OrderBook(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/OrderBook", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).OrderBook(ctx, req.(*OrderBookRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_OrderBookDepth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderBookDepthRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).OrderBookDepth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/OrderBookDepth", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).OrderBookDepth(ctx, req.(*OrderBookDepthRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_OrderDetail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(OrderDetailRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).OrderDetail(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/OrderDetail", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).OrderDetail(ctx, req.(*OrderDetailRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Match_GetMarketSummary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMarketSummaryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MatchServer).GetMarketSummary(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Match/GetMarketSummary", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MatchServer).GetMarketSummary(ctx, req.(*GetMarketSummaryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Match_serviceDesc = grpc.ServiceDesc{ + ServiceName: "protos.Match", + HandlerType: (*MatchServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetCurrencies", + Handler: _Match_GetCurrencies_Handler, + }, + { + MethodName: "GetSymbols", + Handler: _Match_GetSymbols_Handler, + }, + { + MethodName: "Balances", + Handler: _Match_Balances_Handler, + }, + { + MethodName: "NewOrder", + Handler: _Match_NewOrder_Handler, + }, + { + MethodName: "Orders", + Handler: _Match_Orders_Handler, + }, + { + MethodName: "UpdateBalance", + Handler: _Match_UpdateBalance_Handler, + }, + { + MethodName: "GetAssetSummary", + Handler: _Match_GetAssetSummary_Handler, + }, + { + MethodName: "OrderCancel", + Handler: _Match_OrderCancel_Handler, + }, + { + MethodName: "OrderBook", + Handler: _Match_OrderBook_Handler, + }, + { + MethodName: "OrderBookDepth", + Handler: _Match_OrderBookDepth_Handler, + }, + { + MethodName: "OrderDetail", + Handler: _Match_OrderDetail_Handler, + }, + { + MethodName: "GetMarketSummary", + Handler: _Match_GetMarketSummary_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "protos/match.proto", +} diff --git a/protos/match.proto b/protos/match.proto new file mode 100644 index 0000000..84868ff --- /dev/null +++ b/protos/match.proto @@ -0,0 +1,197 @@ +syntax = "proto3"; + +package protos; + +option go_package = "github.com/en/siren/protos"; + +import "google/protobuf/timestamp.proto"; + +service Match { + rpc GetCurrencies (EmptyRequest) returns (GetCurrencyResponse); + rpc GetSymbols (EmptyRequest) returns (GetSymbolsResponse); + rpc Balances (BalancesRequest) returns (BalancesResponse); + rpc NewOrder (NewOrderRequest) returns (NewOrderResponse); + rpc Orders (OrdersRequest) returns (OrdersResponse); + + rpc UpdateBalance (UpdateBalanceRequest) returns (UpdateBalanceResponse); + + rpc GetAssetSummary (GetAssetSummaryRequest) returns (GetAssetSummaryResponse); + rpc OrderCancel (OrderCancelRequest) returns (OrderCancelResponse); + rpc OrderBook (OrderBookRequest) returns (OrderBookResponse); + rpc OrderBookDepth (OrderBookDepthRequest) returns (OrderBookDepthResponse); + rpc OrderDetail (OrderDetailRequest) returns (OrderDetailResponse); + rpc GetMarketSummary (GetMarketSummaryRequest) returns (GetMarketSummaryResponse); +} + +message EmptyRequest {} + +message Currency { + string id = 1; + string name = 2; +} + +message GetCurrencyResponse { + repeated Currency currencies = 1; +} + +message Symbol { + string id = 1; + string base_currency = 2; + string quote_currency = 3; + string base_min_amount = 4; + string base_increment = 5; + string quote_increment = 6; +} + +message GetSymbolsResponse { + repeated Symbol symbols = 1; +} + +message BalancesRequest { + string user_id = 1; + string currency = 2; +} + +message Balance { + string currency = 1; + string balance = 2; + string available = 3; + string hold = 4; +} + +message BalancesResponse { + repeated Balance balances = 1; +} + +message NewOrderRequest { + string user_id = 1; + string client_order_id = 2; + string symbol = 3; + string type = 4; // limit or market + string side = 5; // buy or sell + string price = 6; + string amount = 7; +} + +message Order { + string id = 1; + string price = 2; + string amount = 3; + string symbol = 4; + string side = 5; + string type = 6; + google.protobuf.Timestamp created_at = 7; + google.protobuf.Timestamp updated_at = 8; + google.protobuf.Timestamp done_at = 9; + string done_reason = 10; + string fill_fees = 11; + string filled_amount = 12; + string remaining_amount = 13; + string executed_value = 14; + string status = 15; // "pending" +} + +message NewOrderResponse { + Order order = 1; +} + +message OrdersRequest { + string user_id = 1; + string symbol = 2; +} + +message OrdersResponse { + repeated Order orders = 1; +} + +// fund management +message UpdateBalanceRequest { + string id = 1; + string type = 2; + string user_id = 3; + string currency = 4; + string amount = 5; +} + +message UpdateBalanceResponse { +} +// + +message GetAssetSummaryRequest { + repeated string currencies = 1; +} + +message Asset { + string currency = 1; + string total = 2; + string available = 3; + int64 available_count = 4; + string hold = 5; + int64 hold_count = 6; +} + +message GetAssetSummaryResponse { + repeated Asset assets = 1; +} + +message OrderCancelRequest { + string user_id = 1; + string symbol = 2; + string order_id = 3; +} + +message OrderCancelResponse { + Order order = 1; +} + +message OrderBookRequest { + string symbol = 1; + int32 side = 2; + // int32 offset = 3; + uint64 limit = 3; +} + +message OrderBookResponse { + repeated Order orders = 1; +} + +message OrderBookDepthRequest { + string symbol = 1; + uint64 limit = 2; + // double interval = 3; +} + +message OrderBookData { + string price = 1; + string quantity = 2; +} + +message OrderBookDepthResponse { + repeated OrderBookData asks = 1; + repeated OrderBookData bids = 2; +} + +message OrderDetailRequest { + string symbol = 1; + string order_id = 2; +} + +message OrderDetailResponse { + Order order = 1; +} + +message GetMarketSummaryRequest { + repeated string symbols = 1; +} + +message Market { + string symbol = 1; + string asks_amount = 2; + int64 asks_count = 3; + string bids_amount = 4; + int64 bids_count = 5; +} + +message GetMarketSummaryResponse { + repeated Market markets = 1; +} diff --git a/protos/memorystore.pb.go b/protos/memorystore.pb.go new file mode 100644 index 0000000..4c855ee --- /dev/null +++ b/protos/memorystore.pb.go @@ -0,0 +1,970 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: protos/memorystore.proto + +package protos + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type MarketStatusRequest struct { + Market string `protobuf:"bytes,1,opt,name=market,proto3" json:"market,omitempty"` + Period int32 `protobuf:"varint,2,opt,name=period,proto3" json:"period,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketStatusRequest) Reset() { *m = MarketStatusRequest{} } +func (m *MarketStatusRequest) String() string { return proto.CompactTextString(m) } +func (*MarketStatusRequest) ProtoMessage() {} +func (*MarketStatusRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{0} +} + +func (m *MarketStatusRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketStatusRequest.Unmarshal(m, b) +} +func (m *MarketStatusRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketStatusRequest.Marshal(b, m, deterministic) +} +func (m *MarketStatusRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketStatusRequest.Merge(m, src) +} +func (m *MarketStatusRequest) XXX_Size() int { + return xxx_messageInfo_MarketStatusRequest.Size(m) +} +func (m *MarketStatusRequest) XXX_DiscardUnknown() { + xxx_messageInfo_MarketStatusRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketStatusRequest proto.InternalMessageInfo + +func (m *MarketStatusRequest) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +func (m *MarketStatusRequest) GetPeriod() int32 { + if m != nil { + return m.Period + } + return 0 +} + +type MarketStatusReply struct { + Low float64 `protobuf:"fixed64,1,opt,name=low,proto3" json:"low,omitempty"` + High float64 `protobuf:"fixed64,2,opt,name=high,proto3" json:"high,omitempty"` + Open float64 `protobuf:"fixed64,3,opt,name=open,proto3" json:"open,omitempty"` + Close float64 `protobuf:"fixed64,4,opt,name=close,proto3" json:"close,omitempty"` + Volume float64 `protobuf:"fixed64,5,opt,name=volume,proto3" json:"volume,omitempty"` + Deal float64 `protobuf:"fixed64,6,opt,name=deal,proto3" json:"deal,omitempty"` + Last float64 `protobuf:"fixed64,7,opt,name=last,proto3" json:"last,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketStatusReply) Reset() { *m = MarketStatusReply{} } +func (m *MarketStatusReply) String() string { return proto.CompactTextString(m) } +func (*MarketStatusReply) ProtoMessage() {} +func (*MarketStatusReply) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{1} +} + +func (m *MarketStatusReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketStatusReply.Unmarshal(m, b) +} +func (m *MarketStatusReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketStatusReply.Marshal(b, m, deterministic) +} +func (m *MarketStatusReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketStatusReply.Merge(m, src) +} +func (m *MarketStatusReply) XXX_Size() int { + return xxx_messageInfo_MarketStatusReply.Size(m) +} +func (m *MarketStatusReply) XXX_DiscardUnknown() { + xxx_messageInfo_MarketStatusReply.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketStatusReply proto.InternalMessageInfo + +func (m *MarketStatusReply) GetLow() float64 { + if m != nil { + return m.Low + } + return 0 +} + +func (m *MarketStatusReply) GetHigh() float64 { + if m != nil { + return m.High + } + return 0 +} + +func (m *MarketStatusReply) GetOpen() float64 { + if m != nil { + return m.Open + } + return 0 +} + +func (m *MarketStatusReply) GetClose() float64 { + if m != nil { + return m.Close + } + return 0 +} + +func (m *MarketStatusReply) GetVolume() float64 { + if m != nil { + return m.Volume + } + return 0 +} + +func (m *MarketStatusReply) GetDeal() float64 { + if m != nil { + return m.Deal + } + return 0 +} + +func (m *MarketStatusReply) GetLast() float64 { + if m != nil { + return m.Last + } + return 0 +} + +type MarketKlineRequest struct { + Market string `protobuf:"bytes,1,opt,name=market,proto3" json:"market,omitempty"` + Start int64 `protobuf:"varint,2,opt,name=start,proto3" json:"start,omitempty"` + End int64 `protobuf:"varint,3,opt,name=end,proto3" json:"end,omitempty"` + Interval int64 `protobuf:"varint,4,opt,name=interval,proto3" json:"interval,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketKlineRequest) Reset() { *m = MarketKlineRequest{} } +func (m *MarketKlineRequest) String() string { return proto.CompactTextString(m) } +func (*MarketKlineRequest) ProtoMessage() {} +func (*MarketKlineRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{2} +} + +func (m *MarketKlineRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketKlineRequest.Unmarshal(m, b) +} +func (m *MarketKlineRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketKlineRequest.Marshal(b, m, deterministic) +} +func (m *MarketKlineRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketKlineRequest.Merge(m, src) +} +func (m *MarketKlineRequest) XXX_Size() int { + return xxx_messageInfo_MarketKlineRequest.Size(m) +} +func (m *MarketKlineRequest) XXX_DiscardUnknown() { + xxx_messageInfo_MarketKlineRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketKlineRequest proto.InternalMessageInfo + +func (m *MarketKlineRequest) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +func (m *MarketKlineRequest) GetStart() int64 { + if m != nil { + return m.Start + } + return 0 +} + +func (m *MarketKlineRequest) GetEnd() int64 { + if m != nil { + return m.End + } + return 0 +} + +func (m *MarketKlineRequest) GetInterval() int64 { + if m != nil { + return m.Interval + } + return 0 +} + +type KlineInfo struct { + Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` + Low float64 `protobuf:"fixed64,2,opt,name=low,proto3" json:"low,omitempty"` + High float64 `protobuf:"fixed64,3,opt,name=high,proto3" json:"high,omitempty"` + Open float64 `protobuf:"fixed64,4,opt,name=open,proto3" json:"open,omitempty"` + Close float64 `protobuf:"fixed64,5,opt,name=close,proto3" json:"close,omitempty"` + Volume float64 `protobuf:"fixed64,6,opt,name=volume,proto3" json:"volume,omitempty"` + Deal float64 `protobuf:"fixed64,7,opt,name=deal,proto3" json:"deal,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *KlineInfo) Reset() { *m = KlineInfo{} } +func (m *KlineInfo) String() string { return proto.CompactTextString(m) } +func (*KlineInfo) ProtoMessage() {} +func (*KlineInfo) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{3} +} + +func (m *KlineInfo) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_KlineInfo.Unmarshal(m, b) +} +func (m *KlineInfo) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_KlineInfo.Marshal(b, m, deterministic) +} +func (m *KlineInfo) XXX_Merge(src proto.Message) { + xxx_messageInfo_KlineInfo.Merge(m, src) +} +func (m *KlineInfo) XXX_Size() int { + return xxx_messageInfo_KlineInfo.Size(m) +} +func (m *KlineInfo) XXX_DiscardUnknown() { + xxx_messageInfo_KlineInfo.DiscardUnknown(m) +} + +var xxx_messageInfo_KlineInfo proto.InternalMessageInfo + +func (m *KlineInfo) GetTime() int64 { + if m != nil { + return m.Time + } + return 0 +} + +func (m *KlineInfo) GetLow() float64 { + if m != nil { + return m.Low + } + return 0 +} + +func (m *KlineInfo) GetHigh() float64 { + if m != nil { + return m.High + } + return 0 +} + +func (m *KlineInfo) GetOpen() float64 { + if m != nil { + return m.Open + } + return 0 +} + +func (m *KlineInfo) GetClose() float64 { + if m != nil { + return m.Close + } + return 0 +} + +func (m *KlineInfo) GetVolume() float64 { + if m != nil { + return m.Volume + } + return 0 +} + +func (m *KlineInfo) GetDeal() float64 { + if m != nil { + return m.Deal + } + return 0 +} + +type MarketKlineReply struct { + Items []*KlineInfo `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketKlineReply) Reset() { *m = MarketKlineReply{} } +func (m *MarketKlineReply) String() string { return proto.CompactTextString(m) } +func (*MarketKlineReply) ProtoMessage() {} +func (*MarketKlineReply) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{4} +} + +func (m *MarketKlineReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketKlineReply.Unmarshal(m, b) +} +func (m *MarketKlineReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketKlineReply.Marshal(b, m, deterministic) +} +func (m *MarketKlineReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketKlineReply.Merge(m, src) +} +func (m *MarketKlineReply) XXX_Size() int { + return xxx_messageInfo_MarketKlineReply.Size(m) +} +func (m *MarketKlineReply) XXX_DiscardUnknown() { + xxx_messageInfo_MarketKlineReply.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketKlineReply proto.InternalMessageInfo + +func (m *MarketKlineReply) GetItems() []*KlineInfo { + if m != nil { + return m.Items + } + return nil +} + +type MarketDealsRequest struct { + Market string `protobuf:"bytes,1,opt,name=market,proto3" json:"market,omitempty"` + Limit int32 `protobuf:"varint,2,opt,name=limit,proto3" json:"limit,omitempty"` + LastId uint64 `protobuf:"varint,3,opt,name=last_id,json=lastId,proto3" json:"last_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketDealsRequest) Reset() { *m = MarketDealsRequest{} } +func (m *MarketDealsRequest) String() string { return proto.CompactTextString(m) } +func (*MarketDealsRequest) ProtoMessage() {} +func (*MarketDealsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{5} +} + +func (m *MarketDealsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketDealsRequest.Unmarshal(m, b) +} +func (m *MarketDealsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketDealsRequest.Marshal(b, m, deterministic) +} +func (m *MarketDealsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketDealsRequest.Merge(m, src) +} +func (m *MarketDealsRequest) XXX_Size() int { + return xxx_messageInfo_MarketDealsRequest.Size(m) +} +func (m *MarketDealsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_MarketDealsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketDealsRequest proto.InternalMessageInfo + +func (m *MarketDealsRequest) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +func (m *MarketDealsRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +func (m *MarketDealsRequest) GetLastId() uint64 { + if m != nil { + return m.LastId + } + return 0 +} + +type MarketDeal struct { + Amount string `protobuf:"bytes,1,opt,name=amount,proto3" json:"amount,omitempty"` + Id int32 `protobuf:"varint,2,opt,name=id,proto3" json:"id,omitempty"` + Time int64 `protobuf:"varint,3,opt,name=time,proto3" json:"time,omitempty"` + Price string `protobuf:"bytes,4,opt,name=price,proto3" json:"price,omitempty"` + Type string `protobuf:"bytes,5,opt,name=type,proto3" json:"type,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketDeal) Reset() { *m = MarketDeal{} } +func (m *MarketDeal) String() string { return proto.CompactTextString(m) } +func (*MarketDeal) ProtoMessage() {} +func (*MarketDeal) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{6} +} + +func (m *MarketDeal) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketDeal.Unmarshal(m, b) +} +func (m *MarketDeal) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketDeal.Marshal(b, m, deterministic) +} +func (m *MarketDeal) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketDeal.Merge(m, src) +} +func (m *MarketDeal) XXX_Size() int { + return xxx_messageInfo_MarketDeal.Size(m) +} +func (m *MarketDeal) XXX_DiscardUnknown() { + xxx_messageInfo_MarketDeal.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketDeal proto.InternalMessageInfo + +func (m *MarketDeal) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *MarketDeal) GetId() int32 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *MarketDeal) GetTime() int64 { + if m != nil { + return m.Time + } + return 0 +} + +func (m *MarketDeal) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *MarketDeal) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +type MarketDealsReply struct { + Items []*MarketDeal `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketDealsReply) Reset() { *m = MarketDealsReply{} } +func (m *MarketDealsReply) String() string { return proto.CompactTextString(m) } +func (*MarketDealsReply) ProtoMessage() {} +func (*MarketDealsReply) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{7} +} + +func (m *MarketDealsReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketDealsReply.Unmarshal(m, b) +} +func (m *MarketDealsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketDealsReply.Marshal(b, m, deterministic) +} +func (m *MarketDealsReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketDealsReply.Merge(m, src) +} +func (m *MarketDealsReply) XXX_Size() int { + return xxx_messageInfo_MarketDealsReply.Size(m) +} +func (m *MarketDealsReply) XXX_DiscardUnknown() { + xxx_messageInfo_MarketDealsReply.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketDealsReply proto.InternalMessageInfo + +func (m *MarketDealsReply) GetItems() []*MarketDeal { + if m != nil { + return m.Items + } + return nil +} + +type MarketLastRequest struct { + Market string `protobuf:"bytes,1,opt,name=market,proto3" json:"market,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketLastRequest) Reset() { *m = MarketLastRequest{} } +func (m *MarketLastRequest) String() string { return proto.CompactTextString(m) } +func (*MarketLastRequest) ProtoMessage() {} +func (*MarketLastRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{8} +} + +func (m *MarketLastRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketLastRequest.Unmarshal(m, b) +} +func (m *MarketLastRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketLastRequest.Marshal(b, m, deterministic) +} +func (m *MarketLastRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketLastRequest.Merge(m, src) +} +func (m *MarketLastRequest) XXX_Size() int { + return xxx_messageInfo_MarketLastRequest.Size(m) +} +func (m *MarketLastRequest) XXX_DiscardUnknown() { + xxx_messageInfo_MarketLastRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketLastRequest proto.InternalMessageInfo + +func (m *MarketLastRequest) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +type MarketLastReply struct { + Last float64 `protobuf:"fixed64,1,opt,name=last,proto3" json:"last,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketLastReply) Reset() { *m = MarketLastReply{} } +func (m *MarketLastReply) String() string { return proto.CompactTextString(m) } +func (*MarketLastReply) ProtoMessage() {} +func (*MarketLastReply) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{9} +} + +func (m *MarketLastReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketLastReply.Unmarshal(m, b) +} +func (m *MarketLastReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketLastReply.Marshal(b, m, deterministic) +} +func (m *MarketLastReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketLastReply.Merge(m, src) +} +func (m *MarketLastReply) XXX_Size() int { + return xxx_messageInfo_MarketLastReply.Size(m) +} +func (m *MarketLastReply) XXX_DiscardUnknown() { + xxx_messageInfo_MarketLastReply.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketLastReply proto.InternalMessageInfo + +func (m *MarketLastReply) GetLast() float64 { + if m != nil { + return m.Last + } + return 0 +} + +type MarketStatusTodayRequest struct { + Market string `protobuf:"bytes,1,opt,name=market,proto3" json:"market,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketStatusTodayRequest) Reset() { *m = MarketStatusTodayRequest{} } +func (m *MarketStatusTodayRequest) String() string { return proto.CompactTextString(m) } +func (*MarketStatusTodayRequest) ProtoMessage() {} +func (*MarketStatusTodayRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{10} +} + +func (m *MarketStatusTodayRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketStatusTodayRequest.Unmarshal(m, b) +} +func (m *MarketStatusTodayRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketStatusTodayRequest.Marshal(b, m, deterministic) +} +func (m *MarketStatusTodayRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketStatusTodayRequest.Merge(m, src) +} +func (m *MarketStatusTodayRequest) XXX_Size() int { + return xxx_messageInfo_MarketStatusTodayRequest.Size(m) +} +func (m *MarketStatusTodayRequest) XXX_DiscardUnknown() { + xxx_messageInfo_MarketStatusTodayRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketStatusTodayRequest proto.InternalMessageInfo + +func (m *MarketStatusTodayRequest) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +type MarketStatusTodayReply struct { + Low float64 `protobuf:"fixed64,1,opt,name=low,proto3" json:"low,omitempty"` + High float64 `protobuf:"fixed64,2,opt,name=high,proto3" json:"high,omitempty"` + Open float64 `protobuf:"fixed64,3,opt,name=open,proto3" json:"open,omitempty"` + Last float64 `protobuf:"fixed64,4,opt,name=last,proto3" json:"last,omitempty"` + Volume float64 `protobuf:"fixed64,5,opt,name=volume,proto3" json:"volume,omitempty"` + Deal float64 `protobuf:"fixed64,6,opt,name=deal,proto3" json:"deal,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketStatusTodayReply) Reset() { *m = MarketStatusTodayReply{} } +func (m *MarketStatusTodayReply) String() string { return proto.CompactTextString(m) } +func (*MarketStatusTodayReply) ProtoMessage() {} +func (*MarketStatusTodayReply) Descriptor() ([]byte, []int) { + return fileDescriptor_291d29c3dbb734b4, []int{11} +} + +func (m *MarketStatusTodayReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketStatusTodayReply.Unmarshal(m, b) +} +func (m *MarketStatusTodayReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketStatusTodayReply.Marshal(b, m, deterministic) +} +func (m *MarketStatusTodayReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketStatusTodayReply.Merge(m, src) +} +func (m *MarketStatusTodayReply) XXX_Size() int { + return xxx_messageInfo_MarketStatusTodayReply.Size(m) +} +func (m *MarketStatusTodayReply) XXX_DiscardUnknown() { + xxx_messageInfo_MarketStatusTodayReply.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketStatusTodayReply proto.InternalMessageInfo + +func (m *MarketStatusTodayReply) GetLow() float64 { + if m != nil { + return m.Low + } + return 0 +} + +func (m *MarketStatusTodayReply) GetHigh() float64 { + if m != nil { + return m.High + } + return 0 +} + +func (m *MarketStatusTodayReply) GetOpen() float64 { + if m != nil { + return m.Open + } + return 0 +} + +func (m *MarketStatusTodayReply) GetLast() float64 { + if m != nil { + return m.Last + } + return 0 +} + +func (m *MarketStatusTodayReply) GetVolume() float64 { + if m != nil { + return m.Volume + } + return 0 +} + +func (m *MarketStatusTodayReply) GetDeal() float64 { + if m != nil { + return m.Deal + } + return 0 +} + +func init() { + proto.RegisterType((*MarketStatusRequest)(nil), "protos.MarketStatusRequest") + proto.RegisterType((*MarketStatusReply)(nil), "protos.MarketStatusReply") + proto.RegisterType((*MarketKlineRequest)(nil), "protos.MarketKlineRequest") + proto.RegisterType((*KlineInfo)(nil), "protos.KlineInfo") + proto.RegisterType((*MarketKlineReply)(nil), "protos.MarketKlineReply") + proto.RegisterType((*MarketDealsRequest)(nil), "protos.MarketDealsRequest") + proto.RegisterType((*MarketDeal)(nil), "protos.MarketDeal") + proto.RegisterType((*MarketDealsReply)(nil), "protos.MarketDealsReply") + proto.RegisterType((*MarketLastRequest)(nil), "protos.MarketLastRequest") + proto.RegisterType((*MarketLastReply)(nil), "protos.MarketLastReply") + proto.RegisterType((*MarketStatusTodayRequest)(nil), "protos.MarketStatusTodayRequest") + proto.RegisterType((*MarketStatusTodayReply)(nil), "protos.MarketStatusTodayReply") +} + +func init() { proto.RegisterFile("protos/memorystore.proto", fileDescriptor_291d29c3dbb734b4) } + +var fileDescriptor_291d29c3dbb734b4 = []byte{ + // 604 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x95, 0xc1, 0x6f, 0xd3, 0x3e, + 0x14, 0xc7, 0x97, 0xa6, 0x69, 0x7f, 0x7d, 0xfd, 0x09, 0x36, 0x53, 0x6d, 0x26, 0x48, 0xa8, 0xb2, + 0x40, 0x54, 0x42, 0xb4, 0x52, 0x39, 0xc2, 0x69, 0x62, 0x12, 0x13, 0xec, 0x62, 0x90, 0x90, 0xe0, + 0x80, 0xb2, 0xc6, 0xac, 0x16, 0x4e, 0x1c, 0x62, 0x77, 0xa8, 0x1c, 0xf9, 0x13, 0xb8, 0x72, 0xe4, + 0x1f, 0x45, 0xb6, 0x9b, 0xc6, 0x29, 0x11, 0x05, 0x71, 0x7b, 0xef, 0xd9, 0xf9, 0xfa, 0xf9, 0xe3, + 0xaf, 0x1d, 0xc0, 0x45, 0x29, 0xb5, 0x54, 0xb3, 0x8c, 0x65, 0xb2, 0x5c, 0x2b, 0x2d, 0x4b, 0x36, + 0xb5, 0x25, 0xd4, 0x73, 0x23, 0xe4, 0x0c, 0x6e, 0x5d, 0x24, 0xe5, 0x47, 0xa6, 0x5f, 0xe9, 0x44, + 0xaf, 0x14, 0x65, 0x9f, 0x56, 0x4c, 0x69, 0x74, 0x0c, 0xbd, 0xcc, 0x96, 0x71, 0x30, 0x0e, 0x26, + 0x03, 0xba, 0xc9, 0x4c, 0xbd, 0x60, 0x25, 0x97, 0x29, 0xee, 0x8c, 0x83, 0x49, 0x44, 0x37, 0x19, + 0xf9, 0x11, 0xc0, 0x51, 0x53, 0xa7, 0x10, 0x6b, 0x74, 0x08, 0xa1, 0x90, 0x9f, 0xad, 0x44, 0x40, + 0x4d, 0x88, 0x10, 0x74, 0x97, 0xfc, 0x6a, 0x69, 0xbf, 0x0e, 0xa8, 0x8d, 0x4d, 0x4d, 0x16, 0x2c, + 0xc7, 0xa1, 0xab, 0x99, 0x18, 0x8d, 0x20, 0x5a, 0x08, 0xa9, 0x18, 0xee, 0xda, 0xa2, 0x4b, 0xcc, + 0xea, 0xd7, 0x52, 0xac, 0x32, 0x86, 0x23, 0x5b, 0xde, 0x64, 0x46, 0x21, 0x65, 0x89, 0xc0, 0x3d, + 0xa7, 0x60, 0x62, 0x53, 0x13, 0x89, 0xd2, 0xb8, 0xef, 0x6a, 0x26, 0x26, 0x05, 0x20, 0xd7, 0xe4, + 0x0b, 0xc1, 0x73, 0xb6, 0x6f, 0xaf, 0x23, 0x88, 0x94, 0x4e, 0x4a, 0x6d, 0x9b, 0x0d, 0xa9, 0x4b, + 0xcc, 0x9e, 0x58, 0x9e, 0xda, 0x66, 0x43, 0x6a, 0x42, 0x14, 0xc3, 0x7f, 0x3c, 0xd7, 0xac, 0xbc, + 0x4e, 0x84, 0x6d, 0x37, 0xa4, 0xdb, 0x9c, 0x7c, 0x0f, 0x60, 0x60, 0x17, 0x3b, 0xcf, 0x3f, 0x48, + 0xd3, 0x93, 0xe6, 0x19, 0xb3, 0xeb, 0x84, 0xd4, 0xc6, 0x15, 0xa3, 0xce, 0xaf, 0x8c, 0xc2, 0x16, + 0x46, 0xdd, 0x36, 0x46, 0x51, 0x3b, 0xa3, 0x5e, 0x2b, 0xa3, 0x7e, 0xcd, 0x88, 0x3c, 0x81, 0xc3, + 0x06, 0x0f, 0x73, 0x66, 0x0f, 0x20, 0xe2, 0x9a, 0x65, 0x0a, 0x07, 0xe3, 0x70, 0x32, 0x9c, 0x1f, + 0x39, 0xbf, 0xa8, 0xe9, 0x76, 0x17, 0xd4, 0x8d, 0x93, 0x77, 0x15, 0xcc, 0x67, 0x2c, 0x11, 0xea, + 0x0f, 0x60, 0x0a, 0x9e, 0x71, 0xbd, 0xf1, 0x8d, 0x4b, 0xd0, 0x09, 0xf4, 0xcd, 0xc1, 0xbc, 0xe7, + 0x0e, 0x68, 0x97, 0xf6, 0x4c, 0x7a, 0x9e, 0x92, 0x12, 0xa0, 0x16, 0x37, 0xa2, 0x49, 0x26, 0x57, + 0xf9, 0x56, 0xd4, 0x65, 0xe8, 0x06, 0x74, 0x78, 0xe5, 0xc4, 0x0e, 0x4f, 0xb7, 0x7c, 0x43, 0x8f, + 0xef, 0x08, 0xa2, 0xa2, 0xe4, 0x0b, 0xe7, 0xa4, 0x01, 0x75, 0x89, 0x9d, 0xb9, 0x2e, 0x1c, 0xba, + 0x01, 0xb5, 0x31, 0x79, 0x5a, 0xd1, 0xd8, 0x6c, 0xc8, 0xd0, 0x98, 0x34, 0x69, 0xa0, 0x8a, 0x46, + 0x3d, 0xb1, 0xc2, 0xf1, 0xb0, 0xba, 0x00, 0x2f, 0x13, 0xa5, 0xf7, 0xd0, 0x20, 0xf7, 0xe1, 0xa6, + 0x3f, 0xd9, 0xac, 0x54, 0xf9, 0x35, 0xf0, 0xfc, 0x3a, 0x07, 0xec, 0x5f, 0xaa, 0xd7, 0x32, 0x4d, + 0xd6, 0xfb, 0xa4, 0xbf, 0x05, 0x70, 0xdc, 0xf2, 0xd1, 0xbf, 0x5d, 0xc7, 0xaa, 0xb9, 0x6e, 0xdd, + 0xdc, 0xdf, 0x5c, 0xc6, 0xf9, 0xd7, 0x10, 0x86, 0x17, 0xf5, 0x1b, 0x84, 0x9e, 0xc3, 0xff, 0x7e, + 0x8f, 0xe8, 0x4e, 0x93, 0x6b, 0xe3, 0x2d, 0x8a, 0x6f, 0xb7, 0x0f, 0x16, 0x62, 0x4d, 0x0e, 0xd0, + 0x19, 0x0c, 0x3d, 0x0b, 0xa3, 0xb8, 0x39, 0xd7, 0xbf, 0xe7, 0x31, 0x6e, 0x1d, 0xdb, 0x91, 0xb1, + 0x67, 0xbf, 0x2b, 0xe3, 0x3b, 0x7c, 0x57, 0xa6, 0x36, 0x0b, 0x39, 0x40, 0xa7, 0x95, 0x6d, 0xcd, + 0xb9, 0xa2, 0x9d, 0xc6, 0x3d, 0x63, 0xc4, 0x27, 0x6d, 0x43, 0x4e, 0xe3, 0x4d, 0xf3, 0x25, 0xb5, + 0xe7, 0x87, 0xc6, 0x6d, 0x0c, 0x7c, 0x3f, 0xc4, 0x77, 0x7f, 0x33, 0xc3, 0x0a, 0x9f, 0xde, 0x7b, + 0x4b, 0xae, 0xb8, 0x5e, 0xae, 0x2e, 0xa7, 0x0b, 0x99, 0xcd, 0xd2, 0x2f, 0x9a, 0x2d, 0x96, 0x33, + 0xc5, 0x4b, 0x96, 0x3f, 0x2a, 0xa4, 0x9a, 0xb9, 0xcf, 0x2f, 0xdd, 0x8f, 0xe1, 0xf1, 0xcf, 0x00, + 0x00, 0x00, 0xff, 0xff, 0xa4, 0x32, 0x91, 0x5d, 0x3b, 0x06, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MemorystoreClient is the client API for Memorystore service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MemorystoreClient interface { + MarketStatus(ctx context.Context, in *MarketStatusRequest, opts ...grpc.CallOption) (*MarketStatusReply, error) + MarketKline(ctx context.Context, in *MarketKlineRequest, opts ...grpc.CallOption) (*MarketKlineReply, error) + MarketDeals(ctx context.Context, in *MarketDealsRequest, opts ...grpc.CallOption) (*MarketDealsReply, error) + MarketLast(ctx context.Context, in *MarketLastRequest, opts ...grpc.CallOption) (*MarketLastReply, error) + MarketStatusToday(ctx context.Context, in *MarketStatusTodayRequest, opts ...grpc.CallOption) (*MarketStatusTodayReply, error) +} + +type memorystoreClient struct { + cc *grpc.ClientConn +} + +func NewMemorystoreClient(cc *grpc.ClientConn) MemorystoreClient { + return &memorystoreClient{cc} +} + +func (c *memorystoreClient) MarketStatus(ctx context.Context, in *MarketStatusRequest, opts ...grpc.CallOption) (*MarketStatusReply, error) { + out := new(MarketStatusReply) + err := c.cc.Invoke(ctx, "/protos.Memorystore/MarketStatus", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memorystoreClient) MarketKline(ctx context.Context, in *MarketKlineRequest, opts ...grpc.CallOption) (*MarketKlineReply, error) { + out := new(MarketKlineReply) + err := c.cc.Invoke(ctx, "/protos.Memorystore/MarketKline", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memorystoreClient) MarketDeals(ctx context.Context, in *MarketDealsRequest, opts ...grpc.CallOption) (*MarketDealsReply, error) { + out := new(MarketDealsReply) + err := c.cc.Invoke(ctx, "/protos.Memorystore/MarketDeals", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memorystoreClient) MarketLast(ctx context.Context, in *MarketLastRequest, opts ...grpc.CallOption) (*MarketLastReply, error) { + out := new(MarketLastReply) + err := c.cc.Invoke(ctx, "/protos.Memorystore/MarketLast", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *memorystoreClient) MarketStatusToday(ctx context.Context, in *MarketStatusTodayRequest, opts ...grpc.CallOption) (*MarketStatusTodayReply, error) { + out := new(MarketStatusTodayReply) + err := c.cc.Invoke(ctx, "/protos.Memorystore/MarketStatusToday", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MemorystoreServer is the server API for Memorystore service. +type MemorystoreServer interface { + MarketStatus(context.Context, *MarketStatusRequest) (*MarketStatusReply, error) + MarketKline(context.Context, *MarketKlineRequest) (*MarketKlineReply, error) + MarketDeals(context.Context, *MarketDealsRequest) (*MarketDealsReply, error) + MarketLast(context.Context, *MarketLastRequest) (*MarketLastReply, error) + MarketStatusToday(context.Context, *MarketStatusTodayRequest) (*MarketStatusTodayReply, error) +} + +func RegisterMemorystoreServer(s *grpc.Server, srv MemorystoreServer) { + s.RegisterService(&_Memorystore_serviceDesc, srv) +} + +func _Memorystore_MarketStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MarketStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemorystoreServer).MarketStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Memorystore/MarketStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemorystoreServer).MarketStatus(ctx, req.(*MarketStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Memorystore_MarketKline_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MarketKlineRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemorystoreServer).MarketKline(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Memorystore/MarketKline", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemorystoreServer).MarketKline(ctx, req.(*MarketKlineRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Memorystore_MarketDeals_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MarketDealsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemorystoreServer).MarketDeals(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Memorystore/MarketDeals", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemorystoreServer).MarketDeals(ctx, req.(*MarketDealsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Memorystore_MarketLast_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MarketLastRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemorystoreServer).MarketLast(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Memorystore/MarketLast", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemorystoreServer).MarketLast(ctx, req.(*MarketLastRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Memorystore_MarketStatusToday_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MarketStatusTodayRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MemorystoreServer).MarketStatusToday(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Memorystore/MarketStatusToday", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MemorystoreServer).MarketStatusToday(ctx, req.(*MarketStatusTodayRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Memorystore_serviceDesc = grpc.ServiceDesc{ + ServiceName: "protos.Memorystore", + HandlerType: (*MemorystoreServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "MarketStatus", + Handler: _Memorystore_MarketStatus_Handler, + }, + { + MethodName: "MarketKline", + Handler: _Memorystore_MarketKline_Handler, + }, + { + MethodName: "MarketDeals", + Handler: _Memorystore_MarketDeals_Handler, + }, + { + MethodName: "MarketLast", + Handler: _Memorystore_MarketLast_Handler, + }, + { + MethodName: "MarketStatusToday", + Handler: _Memorystore_MarketStatusToday_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "protos/memorystore.proto", +} diff --git a/protos/memorystore.proto b/protos/memorystore.proto new file mode 100644 index 0000000..4ec51c1 --- /dev/null +++ b/protos/memorystore.proto @@ -0,0 +1,88 @@ +syntax = "proto3"; + +option go_package = "github.com/en/siren/protos"; + +package protos; + +service Memorystore { + rpc MarketStatus (MarketStatusRequest) returns (MarketStatusReply) {} + rpc MarketKline (MarketKlineRequest) returns (MarketKlineReply) {} + rpc MarketDeals (MarketDealsRequest) returns (MarketDealsReply) {} + rpc MarketLast (MarketLastRequest) returns (MarketLastReply) {} + rpc MarketStatusToday (MarketStatusTodayRequest) returns (MarketStatusTodayReply) {} +} + +message MarketStatusRequest { + string market = 1; + int32 period = 2; +} + +message MarketStatusReply { + double low = 1; + double high = 2; + double open = 3; + double close = 4; + double volume = 5; + double deal = 6; + double last = 7; +} + +message MarketKlineRequest { + string market = 1; + int64 start = 2; + int64 end = 3; + int64 interval = 4; +} + +message KlineInfo { + int64 time = 1; + double low = 2; + double high = 3; + double open = 4; + double close = 5; + double volume = 6; + double deal = 7; +} + +message MarketKlineReply { + repeated KlineInfo items = 1; +} + +message MarketDealsRequest { + string market = 1; + int32 limit = 2; + uint64 last_id = 3; +} + +message MarketDeal { + string amount = 1; + int32 id = 2; + int64 time = 3; + string price = 4; + string type = 5; +} + +message MarketDealsReply { + repeated MarketDeal items = 1; +} + +message MarketLastRequest { + string market = 1; +} + +message MarketLastReply { + double last = 1; +} + +message MarketStatusTodayRequest { + string market = 1; +} + +message MarketStatusTodayReply { + double low = 1; + double high = 2; + double open = 3; + double last = 4; + double volume = 5; + double deal = 6; +} diff --git a/protos/sql.pb.go b/protos/sql.pb.go new file mode 100644 index 0000000..b0b1067 --- /dev/null +++ b/protos/sql.pb.go @@ -0,0 +1,1401 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: protos/sql.proto + +package protos + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type TransfersRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Since *timestamp.Timestamp `protobuf:"bytes,4,opt,name=since,proto3" json:"since,omitempty"` + Until *timestamp.Timestamp `protobuf:"bytes,5,opt,name=until,proto3" json:"until,omitempty"` + Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransfersRequest) Reset() { *m = TransfersRequest{} } +func (m *TransfersRequest) String() string { return proto.CompactTextString(m) } +func (*TransfersRequest) ProtoMessage() {} +func (*TransfersRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{0} +} + +func (m *TransfersRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransfersRequest.Unmarshal(m, b) +} +func (m *TransfersRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransfersRequest.Marshal(b, m, deterministic) +} +func (m *TransfersRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransfersRequest.Merge(m, src) +} +func (m *TransfersRequest) XXX_Size() int { + return xxx_messageInfo_TransfersRequest.Size(m) +} +func (m *TransfersRequest) XXX_DiscardUnknown() { + xxx_messageInfo_TransfersRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_TransfersRequest proto.InternalMessageInfo + +func (m *TransfersRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *TransfersRequest) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *TransfersRequest) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *TransfersRequest) GetSince() *timestamp.Timestamp { + if m != nil { + return m.Since + } + return nil +} + +func (m *TransfersRequest) GetUntil() *timestamp.Timestamp { + if m != nil { + return m.Until + } + return nil +} + +func (m *TransfersRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +type Transfer struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + Type string `protobuf:"bytes,3,opt,name=type,proto3" json:"type,omitempty"` + Amount string `protobuf:"bytes,4,opt,name=amount,proto3" json:"amount,omitempty"` + Status string `protobuf:"bytes,5,opt,name=status,proto3" json:"status,omitempty"` + CreatedAt *timestamp.Timestamp `protobuf:"bytes,6,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + UpdatedAt *timestamp.Timestamp `protobuf:"bytes,7,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *Transfer) Reset() { *m = Transfer{} } +func (m *Transfer) String() string { return proto.CompactTextString(m) } +func (*Transfer) ProtoMessage() {} +func (*Transfer) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{1} +} + +func (m *Transfer) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_Transfer.Unmarshal(m, b) +} +func (m *Transfer) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_Transfer.Marshal(b, m, deterministic) +} +func (m *Transfer) XXX_Merge(src proto.Message) { + xxx_messageInfo_Transfer.Merge(m, src) +} +func (m *Transfer) XXX_Size() int { + return xxx_messageInfo_Transfer.Size(m) +} +func (m *Transfer) XXX_DiscardUnknown() { + xxx_messageInfo_Transfer.DiscardUnknown(m) +} + +var xxx_messageInfo_Transfer proto.InternalMessageInfo + +func (m *Transfer) GetId() string { + if m != nil { + return m.Id + } + return "" +} + +func (m *Transfer) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *Transfer) GetType() string { + if m != nil { + return m.Type + } + return "" +} + +func (m *Transfer) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *Transfer) GetStatus() string { + if m != nil { + return m.Status + } + return "" +} + +func (m *Transfer) GetCreatedAt() *timestamp.Timestamp { + if m != nil { + return m.CreatedAt + } + return nil +} + +func (m *Transfer) GetUpdatedAt() *timestamp.Timestamp { + if m != nil { + return m.UpdatedAt + } + return nil +} + +type TransfersResponse struct { + Transfers []*Transfer `protobuf:"bytes,1,rep,name=transfers,proto3" json:"transfers,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *TransfersResponse) Reset() { *m = TransfersResponse{} } +func (m *TransfersResponse) String() string { return proto.CompactTextString(m) } +func (*TransfersResponse) ProtoMessage() {} +func (*TransfersResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{2} +} + +func (m *TransfersResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_TransfersResponse.Unmarshal(m, b) +} +func (m *TransfersResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_TransfersResponse.Marshal(b, m, deterministic) +} +func (m *TransfersResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_TransfersResponse.Merge(m, src) +} +func (m *TransfersResponse) XXX_Size() int { + return xxx_messageInfo_TransfersResponse.Size(m) +} +func (m *TransfersResponse) XXX_DiscardUnknown() { + xxx_messageInfo_TransfersResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_TransfersResponse proto.InternalMessageInfo + +func (m *TransfersResponse) GetTransfers() []*Transfer { + if m != nil { + return m.Transfers + } + return nil +} + +type GetOrderHistoryRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Market string `protobuf:"bytes,2,opt,name=market,proto3" json:"market,omitempty"` + StartTime int64 `protobuf:"varint,3,opt,name=start_time,json=startTime,proto3" json:"start_time,omitempty"` + EndTime int64 `protobuf:"varint,4,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty"` + Offset int32 `protobuf:"varint,5,opt,name=offset,proto3" json:"offset,omitempty"` + Limit int32 `protobuf:"varint,6,opt,name=limit,proto3" json:"limit,omitempty"` + Side int32 `protobuf:"varint,7,opt,name=side,proto3" json:"side,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderHistoryRequest) Reset() { *m = GetOrderHistoryRequest{} } +func (m *GetOrderHistoryRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderHistoryRequest) ProtoMessage() {} +func (*GetOrderHistoryRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{3} +} + +func (m *GetOrderHistoryRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderHistoryRequest.Unmarshal(m, b) +} +func (m *GetOrderHistoryRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderHistoryRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderHistoryRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderHistoryRequest.Merge(m, src) +} +func (m *GetOrderHistoryRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderHistoryRequest.Size(m) +} +func (m *GetOrderHistoryRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderHistoryRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderHistoryRequest proto.InternalMessageInfo + +func (m *GetOrderHistoryRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *GetOrderHistoryRequest) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +func (m *GetOrderHistoryRequest) GetStartTime() int64 { + if m != nil { + return m.StartTime + } + return 0 +} + +func (m *GetOrderHistoryRequest) GetEndTime() int64 { + if m != nil { + return m.EndTime + } + return 0 +} + +func (m *GetOrderHistoryRequest) GetOffset() int32 { + if m != nil { + return m.Offset + } + return 0 +} + +func (m *GetOrderHistoryRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +func (m *GetOrderHistoryRequest) GetSide() int32 { + if m != nil { + return m.Side + } + return 0 +} + +type OrderHistory struct { + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Ctime int64 `protobuf:"varint,2,opt,name=ctime,proto3" json:"ctime,omitempty"` + Ftime int64 `protobuf:"varint,3,opt,name=ftime,proto3" json:"ftime,omitempty"` + User string `protobuf:"bytes,4,opt,name=user,proto3" json:"user,omitempty"` + Market string `protobuf:"bytes,5,opt,name=market,proto3" json:"market,omitempty"` + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + Type uint32 `protobuf:"varint,7,opt,name=type,proto3" json:"type,omitempty"` + Side uint32 `protobuf:"varint,8,opt,name=side,proto3" json:"side,omitempty"` + Price string `protobuf:"bytes,9,opt,name=price,proto3" json:"price,omitempty"` + Amount string `protobuf:"bytes,10,opt,name=amount,proto3" json:"amount,omitempty"` + TakerFee string `protobuf:"bytes,11,opt,name=taker_fee,json=takerFee,proto3" json:"taker_fee,omitempty"` + MakerFee string `protobuf:"bytes,12,opt,name=maker_fee,json=makerFee,proto3" json:"maker_fee,omitempty"` + DealStock string `protobuf:"bytes,13,opt,name=deal_stock,json=dealStock,proto3" json:"deal_stock,omitempty"` + DealMoney string `protobuf:"bytes,14,opt,name=deal_money,json=dealMoney,proto3" json:"deal_money,omitempty"` + DealFee string `protobuf:"bytes,15,opt,name=deal_fee,json=dealFee,proto3" json:"deal_fee,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderHistory) Reset() { *m = OrderHistory{} } +func (m *OrderHistory) String() string { return proto.CompactTextString(m) } +func (*OrderHistory) ProtoMessage() {} +func (*OrderHistory) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{4} +} + +func (m *OrderHistory) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderHistory.Unmarshal(m, b) +} +func (m *OrderHistory) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderHistory.Marshal(b, m, deterministic) +} +func (m *OrderHistory) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderHistory.Merge(m, src) +} +func (m *OrderHistory) XXX_Size() int { + return xxx_messageInfo_OrderHistory.Size(m) +} +func (m *OrderHistory) XXX_DiscardUnknown() { + xxx_messageInfo_OrderHistory.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderHistory proto.InternalMessageInfo + +func (m *OrderHistory) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *OrderHistory) GetCtime() int64 { + if m != nil { + return m.Ctime + } + return 0 +} + +func (m *OrderHistory) GetFtime() int64 { + if m != nil { + return m.Ftime + } + return 0 +} + +func (m *OrderHistory) GetUser() string { + if m != nil { + return m.User + } + return "" +} + +func (m *OrderHistory) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +func (m *OrderHistory) GetSource() string { + if m != nil { + return m.Source + } + return "" +} + +func (m *OrderHistory) GetType() uint32 { + if m != nil { + return m.Type + } + return 0 +} + +func (m *OrderHistory) GetSide() uint32 { + if m != nil { + return m.Side + } + return 0 +} + +func (m *OrderHistory) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *OrderHistory) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *OrderHistory) GetTakerFee() string { + if m != nil { + return m.TakerFee + } + return "" +} + +func (m *OrderHistory) GetMakerFee() string { + if m != nil { + return m.MakerFee + } + return "" +} + +func (m *OrderHistory) GetDealStock() string { + if m != nil { + return m.DealStock + } + return "" +} + +func (m *OrderHistory) GetDealMoney() string { + if m != nil { + return m.DealMoney + } + return "" +} + +func (m *OrderHistory) GetDealFee() string { + if m != nil { + return m.DealFee + } + return "" +} + +type GetOrderHistoryReply struct { + Items []*OrderHistory `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderHistoryReply) Reset() { *m = GetOrderHistoryReply{} } +func (m *GetOrderHistoryReply) String() string { return proto.CompactTextString(m) } +func (*GetOrderHistoryReply) ProtoMessage() {} +func (*GetOrderHistoryReply) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{5} +} + +func (m *GetOrderHistoryReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderHistoryReply.Unmarshal(m, b) +} +func (m *GetOrderHistoryReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderHistoryReply.Marshal(b, m, deterministic) +} +func (m *GetOrderHistoryReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderHistoryReply.Merge(m, src) +} +func (m *GetOrderHistoryReply) XXX_Size() int { + return xxx_messageInfo_GetOrderHistoryReply.Size(m) +} +func (m *GetOrderHistoryReply) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderHistoryReply.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderHistoryReply proto.InternalMessageInfo + +func (m *GetOrderHistoryReply) GetItems() []*OrderHistory { + if m != nil { + return m.Items + } + return nil +} + +type GetOrderDealsRequest struct { + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + Offset int32 `protobuf:"varint,2,opt,name=offset,proto3" json:"offset,omitempty"` + Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderDealsRequest) Reset() { *m = GetOrderDealsRequest{} } +func (m *GetOrderDealsRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderDealsRequest) ProtoMessage() {} +func (*GetOrderDealsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{6} +} + +func (m *GetOrderDealsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderDealsRequest.Unmarshal(m, b) +} +func (m *GetOrderDealsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderDealsRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderDealsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderDealsRequest.Merge(m, src) +} +func (m *GetOrderDealsRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderDealsRequest.Size(m) +} +func (m *GetOrderDealsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderDealsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderDealsRequest proto.InternalMessageInfo + +func (m *GetOrderDealsRequest) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +func (m *GetOrderDealsRequest) GetOffset() int32 { + if m != nil { + return m.Offset + } + return 0 +} + +func (m *GetOrderDealsRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +type OrderDeals struct { + Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` + User string `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` + Id uint64 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` + Role int32 `protobuf:"varint,4,opt,name=role,proto3" json:"role,omitempty"` + Price string `protobuf:"bytes,5,opt,name=price,proto3" json:"price,omitempty"` + Amount string `protobuf:"bytes,6,opt,name=amount,proto3" json:"amount,omitempty"` + Deal string `protobuf:"bytes,7,opt,name=deal,proto3" json:"deal,omitempty"` + Fee string `protobuf:"bytes,8,opt,name=fee,proto3" json:"fee,omitempty"` + DealOrderId uint64 `protobuf:"varint,9,opt,name=deal_order_id,json=dealOrderId,proto3" json:"deal_order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *OrderDeals) Reset() { *m = OrderDeals{} } +func (m *OrderDeals) String() string { return proto.CompactTextString(m) } +func (*OrderDeals) ProtoMessage() {} +func (*OrderDeals) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{7} +} + +func (m *OrderDeals) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_OrderDeals.Unmarshal(m, b) +} +func (m *OrderDeals) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_OrderDeals.Marshal(b, m, deterministic) +} +func (m *OrderDeals) XXX_Merge(src proto.Message) { + xxx_messageInfo_OrderDeals.Merge(m, src) +} +func (m *OrderDeals) XXX_Size() int { + return xxx_messageInfo_OrderDeals.Size(m) +} +func (m *OrderDeals) XXX_DiscardUnknown() { + xxx_messageInfo_OrderDeals.DiscardUnknown(m) +} + +var xxx_messageInfo_OrderDeals proto.InternalMessageInfo + +func (m *OrderDeals) GetTime() int64 { + if m != nil { + return m.Time + } + return 0 +} + +func (m *OrderDeals) GetUser() string { + if m != nil { + return m.User + } + return "" +} + +func (m *OrderDeals) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *OrderDeals) GetRole() int32 { + if m != nil { + return m.Role + } + return 0 +} + +func (m *OrderDeals) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *OrderDeals) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *OrderDeals) GetDeal() string { + if m != nil { + return m.Deal + } + return "" +} + +func (m *OrderDeals) GetFee() string { + if m != nil { + return m.Fee + } + return "" +} + +func (m *OrderDeals) GetDealOrderId() uint64 { + if m != nil { + return m.DealOrderId + } + return 0 +} + +type GetOrderDealsReply struct { + Items []*OrderDeals `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderDealsReply) Reset() { *m = GetOrderDealsReply{} } +func (m *GetOrderDealsReply) String() string { return proto.CompactTextString(m) } +func (*GetOrderDealsReply) ProtoMessage() {} +func (*GetOrderDealsReply) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{8} +} + +func (m *GetOrderDealsReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderDealsReply.Unmarshal(m, b) +} +func (m *GetOrderDealsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderDealsReply.Marshal(b, m, deterministic) +} +func (m *GetOrderDealsReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderDealsReply.Merge(m, src) +} +func (m *GetOrderDealsReply) XXX_Size() int { + return xxx_messageInfo_GetOrderDealsReply.Size(m) +} +func (m *GetOrderDealsReply) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderDealsReply.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderDealsReply proto.InternalMessageInfo + +func (m *GetOrderDealsReply) GetItems() []*OrderDeals { + if m != nil { + return m.Items + } + return nil +} + +type GetOrderDetailFinishedRequest struct { + OrderId string `protobuf:"bytes,1,opt,name=order_id,json=orderId,proto3" json:"order_id,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderDetailFinishedRequest) Reset() { *m = GetOrderDetailFinishedRequest{} } +func (m *GetOrderDetailFinishedRequest) String() string { return proto.CompactTextString(m) } +func (*GetOrderDetailFinishedRequest) ProtoMessage() {} +func (*GetOrderDetailFinishedRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{9} +} + +func (m *GetOrderDetailFinishedRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderDetailFinishedRequest.Unmarshal(m, b) +} +func (m *GetOrderDetailFinishedRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderDetailFinishedRequest.Marshal(b, m, deterministic) +} +func (m *GetOrderDetailFinishedRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderDetailFinishedRequest.Merge(m, src) +} +func (m *GetOrderDetailFinishedRequest) XXX_Size() int { + return xxx_messageInfo_GetOrderDetailFinishedRequest.Size(m) +} +func (m *GetOrderDetailFinishedRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderDetailFinishedRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderDetailFinishedRequest proto.InternalMessageInfo + +func (m *GetOrderDetailFinishedRequest) GetOrderId() string { + if m != nil { + return m.OrderId + } + return "" +} + +type GetOrderDetailFinishedReply struct { + Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + Ctime int64 `protobuf:"varint,2,opt,name=ctime,proto3" json:"ctime,omitempty"` + Ftime int64 `protobuf:"varint,3,opt,name=ftime,proto3" json:"ftime,omitempty"` + User string `protobuf:"bytes,4,opt,name=user,proto3" json:"user,omitempty"` + Market string `protobuf:"bytes,5,opt,name=market,proto3" json:"market,omitempty"` + Source string `protobuf:"bytes,6,opt,name=source,proto3" json:"source,omitempty"` + Type uint32 `protobuf:"varint,7,opt,name=type,proto3" json:"type,omitempty"` + Side uint32 `protobuf:"varint,8,opt,name=side,proto3" json:"side,omitempty"` + Price string `protobuf:"bytes,9,opt,name=price,proto3" json:"price,omitempty"` + Amount string `protobuf:"bytes,10,opt,name=amount,proto3" json:"amount,omitempty"` + TakerFee string `protobuf:"bytes,11,opt,name=taker_fee,json=takerFee,proto3" json:"taker_fee,omitempty"` + MakerFee string `protobuf:"bytes,12,opt,name=maker_fee,json=makerFee,proto3" json:"maker_fee,omitempty"` + DealStock string `protobuf:"bytes,13,opt,name=deal_stock,json=dealStock,proto3" json:"deal_stock,omitempty"` + DealMoney string `protobuf:"bytes,14,opt,name=deal_money,json=dealMoney,proto3" json:"deal_money,omitempty"` + DealFee string `protobuf:"bytes,15,opt,name=deal_fee,json=dealFee,proto3" json:"deal_fee,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetOrderDetailFinishedReply) Reset() { *m = GetOrderDetailFinishedReply{} } +func (m *GetOrderDetailFinishedReply) String() string { return proto.CompactTextString(m) } +func (*GetOrderDetailFinishedReply) ProtoMessage() {} +func (*GetOrderDetailFinishedReply) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{10} +} + +func (m *GetOrderDetailFinishedReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetOrderDetailFinishedReply.Unmarshal(m, b) +} +func (m *GetOrderDetailFinishedReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetOrderDetailFinishedReply.Marshal(b, m, deterministic) +} +func (m *GetOrderDetailFinishedReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetOrderDetailFinishedReply.Merge(m, src) +} +func (m *GetOrderDetailFinishedReply) XXX_Size() int { + return xxx_messageInfo_GetOrderDetailFinishedReply.Size(m) +} +func (m *GetOrderDetailFinishedReply) XXX_DiscardUnknown() { + xxx_messageInfo_GetOrderDetailFinishedReply.DiscardUnknown(m) +} + +var xxx_messageInfo_GetOrderDetailFinishedReply proto.InternalMessageInfo + +func (m *GetOrderDetailFinishedReply) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *GetOrderDetailFinishedReply) GetCtime() int64 { + if m != nil { + return m.Ctime + } + return 0 +} + +func (m *GetOrderDetailFinishedReply) GetFtime() int64 { + if m != nil { + return m.Ftime + } + return 0 +} + +func (m *GetOrderDetailFinishedReply) GetUser() string { + if m != nil { + return m.User + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetSource() string { + if m != nil { + return m.Source + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetType() uint32 { + if m != nil { + return m.Type + } + return 0 +} + +func (m *GetOrderDetailFinishedReply) GetSide() uint32 { + if m != nil { + return m.Side + } + return 0 +} + +func (m *GetOrderDetailFinishedReply) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetTakerFee() string { + if m != nil { + return m.TakerFee + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetMakerFee() string { + if m != nil { + return m.MakerFee + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetDealStock() string { + if m != nil { + return m.DealStock + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetDealMoney() string { + if m != nil { + return m.DealMoney + } + return "" +} + +func (m *GetOrderDetailFinishedReply) GetDealFee() string { + if m != nil { + return m.DealFee + } + return "" +} + +type GetMarketUserDealsRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Market string `protobuf:"bytes,2,opt,name=market,proto3" json:"market,omitempty"` + Offset int32 `protobuf:"varint,3,opt,name=offset,proto3" json:"offset,omitempty"` + Limit int32 `protobuf:"varint,4,opt,name=limit,proto3" json:"limit,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMarketUserDealsRequest) Reset() { *m = GetMarketUserDealsRequest{} } +func (m *GetMarketUserDealsRequest) String() string { return proto.CompactTextString(m) } +func (*GetMarketUserDealsRequest) ProtoMessage() {} +func (*GetMarketUserDealsRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{11} +} + +func (m *GetMarketUserDealsRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMarketUserDealsRequest.Unmarshal(m, b) +} +func (m *GetMarketUserDealsRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMarketUserDealsRequest.Marshal(b, m, deterministic) +} +func (m *GetMarketUserDealsRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMarketUserDealsRequest.Merge(m, src) +} +func (m *GetMarketUserDealsRequest) XXX_Size() int { + return xxx_messageInfo_GetMarketUserDealsRequest.Size(m) +} +func (m *GetMarketUserDealsRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetMarketUserDealsRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMarketUserDealsRequest proto.InternalMessageInfo + +func (m *GetMarketUserDealsRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *GetMarketUserDealsRequest) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +func (m *GetMarketUserDealsRequest) GetOffset() int32 { + if m != nil { + return m.Offset + } + return 0 +} + +func (m *GetMarketUserDealsRequest) GetLimit() int32 { + if m != nil { + return m.Limit + } + return 0 +} + +type MarketUserDeals struct { + Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` + User string `protobuf:"bytes,2,opt,name=user,proto3" json:"user,omitempty"` + Id uint64 `protobuf:"varint,3,opt,name=id,proto3" json:"id,omitempty"` + Side int32 `protobuf:"varint,4,opt,name=side,proto3" json:"side,omitempty"` + Role int32 `protobuf:"varint,5,opt,name=role,proto3" json:"role,omitempty"` + Price string `protobuf:"bytes,6,opt,name=price,proto3" json:"price,omitempty"` + Amount string `protobuf:"bytes,7,opt,name=amount,proto3" json:"amount,omitempty"` + Deal string `protobuf:"bytes,8,opt,name=deal,proto3" json:"deal,omitempty"` + Fee string `protobuf:"bytes,9,opt,name=fee,proto3" json:"fee,omitempty"` + DealOrderId uint64 `protobuf:"varint,10,opt,name=deal_order_id,json=dealOrderId,proto3" json:"deal_order_id,omitempty"` + Market string `protobuf:"bytes,11,opt,name=market,proto3" json:"market,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *MarketUserDeals) Reset() { *m = MarketUserDeals{} } +func (m *MarketUserDeals) String() string { return proto.CompactTextString(m) } +func (*MarketUserDeals) ProtoMessage() {} +func (*MarketUserDeals) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{12} +} + +func (m *MarketUserDeals) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_MarketUserDeals.Unmarshal(m, b) +} +func (m *MarketUserDeals) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_MarketUserDeals.Marshal(b, m, deterministic) +} +func (m *MarketUserDeals) XXX_Merge(src proto.Message) { + xxx_messageInfo_MarketUserDeals.Merge(m, src) +} +func (m *MarketUserDeals) XXX_Size() int { + return xxx_messageInfo_MarketUserDeals.Size(m) +} +func (m *MarketUserDeals) XXX_DiscardUnknown() { + xxx_messageInfo_MarketUserDeals.DiscardUnknown(m) +} + +var xxx_messageInfo_MarketUserDeals proto.InternalMessageInfo + +func (m *MarketUserDeals) GetTime() int64 { + if m != nil { + return m.Time + } + return 0 +} + +func (m *MarketUserDeals) GetUser() string { + if m != nil { + return m.User + } + return "" +} + +func (m *MarketUserDeals) GetId() uint64 { + if m != nil { + return m.Id + } + return 0 +} + +func (m *MarketUserDeals) GetSide() int32 { + if m != nil { + return m.Side + } + return 0 +} + +func (m *MarketUserDeals) GetRole() int32 { + if m != nil { + return m.Role + } + return 0 +} + +func (m *MarketUserDeals) GetPrice() string { + if m != nil { + return m.Price + } + return "" +} + +func (m *MarketUserDeals) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *MarketUserDeals) GetDeal() string { + if m != nil { + return m.Deal + } + return "" +} + +func (m *MarketUserDeals) GetFee() string { + if m != nil { + return m.Fee + } + return "" +} + +func (m *MarketUserDeals) GetDealOrderId() uint64 { + if m != nil { + return m.DealOrderId + } + return 0 +} + +func (m *MarketUserDeals) GetMarket() string { + if m != nil { + return m.Market + } + return "" +} + +type GetMarketUserDealsReply struct { + Items []*MarketUserDeals `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetMarketUserDealsReply) Reset() { *m = GetMarketUserDealsReply{} } +func (m *GetMarketUserDealsReply) String() string { return proto.CompactTextString(m) } +func (*GetMarketUserDealsReply) ProtoMessage() {} +func (*GetMarketUserDealsReply) Descriptor() ([]byte, []int) { + return fileDescriptor_5d1fb125921f7262, []int{13} +} + +func (m *GetMarketUserDealsReply) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetMarketUserDealsReply.Unmarshal(m, b) +} +func (m *GetMarketUserDealsReply) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetMarketUserDealsReply.Marshal(b, m, deterministic) +} +func (m *GetMarketUserDealsReply) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetMarketUserDealsReply.Merge(m, src) +} +func (m *GetMarketUserDealsReply) XXX_Size() int { + return xxx_messageInfo_GetMarketUserDealsReply.Size(m) +} +func (m *GetMarketUserDealsReply) XXX_DiscardUnknown() { + xxx_messageInfo_GetMarketUserDealsReply.DiscardUnknown(m) +} + +var xxx_messageInfo_GetMarketUserDealsReply proto.InternalMessageInfo + +func (m *GetMarketUserDealsReply) GetItems() []*MarketUserDeals { + if m != nil { + return m.Items + } + return nil +} + +func init() { + proto.RegisterType((*TransfersRequest)(nil), "protos.TransfersRequest") + proto.RegisterType((*Transfer)(nil), "protos.Transfer") + proto.RegisterType((*TransfersResponse)(nil), "protos.TransfersResponse") + proto.RegisterType((*GetOrderHistoryRequest)(nil), "protos.GetOrderHistoryRequest") + proto.RegisterType((*OrderHistory)(nil), "protos.OrderHistory") + proto.RegisterType((*GetOrderHistoryReply)(nil), "protos.GetOrderHistoryReply") + proto.RegisterType((*GetOrderDealsRequest)(nil), "protos.GetOrderDealsRequest") + proto.RegisterType((*OrderDeals)(nil), "protos.OrderDeals") + proto.RegisterType((*GetOrderDealsReply)(nil), "protos.GetOrderDealsReply") + proto.RegisterType((*GetOrderDetailFinishedRequest)(nil), "protos.GetOrderDetailFinishedRequest") + proto.RegisterType((*GetOrderDetailFinishedReply)(nil), "protos.GetOrderDetailFinishedReply") + proto.RegisterType((*GetMarketUserDealsRequest)(nil), "protos.GetMarketUserDealsRequest") + proto.RegisterType((*MarketUserDeals)(nil), "protos.MarketUserDeals") + proto.RegisterType((*GetMarketUserDealsReply)(nil), "protos.GetMarketUserDealsReply") +} + +func init() { proto.RegisterFile("protos/sql.proto", fileDescriptor_5d1fb125921f7262) } + +var fileDescriptor_5d1fb125921f7262 = []byte{ + // 952 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0xe4, 0x44, + 0x13, 0x7e, 0x3d, 0x1e, 0xcf, 0x8c, 0x6b, 0x36, 0x9b, 0xbc, 0xad, 0x28, 0x71, 0xbc, 0x84, 0x0d, + 0x06, 0xa4, 0x11, 0xd2, 0xce, 0xa0, 0x70, 0x82, 0x03, 0x62, 0x17, 0x94, 0x5d, 0x84, 0x56, 0x2b, + 0xbc, 0xcb, 0x65, 0x2f, 0x23, 0xc7, 0xae, 0x49, 0xac, 0xf8, 0x2b, 0xee, 0xf6, 0x61, 0xf6, 0xc6, + 0x85, 0x3f, 0xc3, 0xef, 0xe0, 0xce, 0x85, 0xff, 0xc1, 0x8d, 0x2b, 0xea, 0x6a, 0x7f, 0xce, 0x38, + 0x19, 0xad, 0x90, 0x38, 0x71, 0xeb, 0xfa, 0xea, 0xae, 0x7a, 0xea, 0xa9, 0xb2, 0xe1, 0x20, 0xcb, + 0x53, 0x91, 0xf2, 0x05, 0xbf, 0x8d, 0xe6, 0x74, 0x64, 0x23, 0xa5, 0xb1, 0x1f, 0x5f, 0xa5, 0xe9, + 0x55, 0x84, 0x0b, 0x12, 0x2f, 0x8b, 0xd5, 0x42, 0x84, 0x31, 0x72, 0xe1, 0xc5, 0x99, 0x72, 0x74, + 0xfe, 0xd0, 0xe0, 0xe0, 0x4d, 0xee, 0x25, 0x7c, 0x85, 0x39, 0x77, 0xf1, 0xb6, 0x40, 0x2e, 0xd8, + 0x31, 0x8c, 0x0b, 0x8e, 0xf9, 0x32, 0x0c, 0x2c, 0xed, 0x4c, 0x9b, 0x99, 0xee, 0x48, 0x8a, 0xdf, + 0x07, 0xcc, 0x86, 0x89, 0x5f, 0xe4, 0x39, 0x26, 0xfe, 0xda, 0x1a, 0x90, 0xa5, 0x96, 0x19, 0x83, + 0xa1, 0x58, 0x67, 0x68, 0xe9, 0xa4, 0xa7, 0x33, 0xfb, 0x1c, 0x0c, 0x1e, 0x26, 0x3e, 0x5a, 0xc3, + 0x33, 0x6d, 0x36, 0x3d, 0xb7, 0xe7, 0x2a, 0x9d, 0x79, 0x95, 0xce, 0xfc, 0x4d, 0x95, 0x8e, 0xab, + 0x1c, 0x65, 0x44, 0x91, 0x88, 0x30, 0xb2, 0x8c, 0xdd, 0x11, 0xe4, 0xc8, 0x0e, 0xc1, 0x88, 0xc2, + 0x38, 0x14, 0xd6, 0xe8, 0x4c, 0x9b, 0x19, 0xae, 0x12, 0x9c, 0x3f, 0x35, 0x98, 0x54, 0x75, 0xb1, + 0x87, 0x30, 0xa8, 0x4b, 0x19, 0x84, 0xef, 0x5f, 0xc6, 0x11, 0x8c, 0xbc, 0x38, 0x2d, 0x12, 0x41, + 0x75, 0x98, 0x6e, 0x29, 0x49, 0x3d, 0x17, 0x9e, 0x28, 0x38, 0x65, 0x6b, 0xba, 0xa5, 0xc4, 0xbe, + 0x04, 0xf0, 0x73, 0xf4, 0x04, 0x06, 0x4b, 0x4f, 0xe5, 0x75, 0x7f, 0x25, 0x66, 0xe9, 0xfd, 0x54, + 0xc8, 0xd0, 0x22, 0x0b, 0xaa, 0xd0, 0xf1, 0xee, 0xd0, 0xd2, 0xfb, 0xa9, 0x70, 0xbe, 0x85, 0xff, + 0xb7, 0x3a, 0xc9, 0xb3, 0x34, 0xe1, 0xc8, 0xe6, 0x60, 0x8a, 0x4a, 0x69, 0x69, 0x67, 0xfa, 0x6c, + 0x7a, 0x7e, 0xa0, 0xee, 0xe1, 0xf3, 0xca, 0xdb, 0x6d, 0x5c, 0x9c, 0xdf, 0x34, 0x38, 0x7a, 0x8e, + 0xe2, 0x55, 0x1e, 0x60, 0xfe, 0x22, 0xe4, 0x22, 0xcd, 0xd7, 0x3b, 0x59, 0x71, 0x04, 0xa3, 0xd8, + 0xcb, 0x6f, 0x50, 0x94, 0x60, 0x96, 0x12, 0x3b, 0x05, 0xe0, 0xc2, 0xcb, 0xc5, 0x52, 0x92, 0x8e, + 0x00, 0xd5, 0x5d, 0x93, 0x34, 0x32, 0x7f, 0x76, 0x02, 0x13, 0x4c, 0x02, 0x65, 0x1c, 0x92, 0x71, + 0x8c, 0x49, 0x40, 0xa6, 0x23, 0x18, 0xa5, 0xab, 0x15, 0x47, 0x41, 0xc0, 0x1a, 0x6e, 0x29, 0xf5, + 0xf7, 0x5a, 0xb6, 0x8c, 0x87, 0x01, 0x12, 0x5a, 0x86, 0x4b, 0x67, 0xe7, 0xaf, 0x01, 0x3c, 0x68, + 0x17, 0xd1, 0xe2, 0xc0, 0x90, 0x38, 0x70, 0x08, 0x86, 0x4f, 0x4f, 0x0f, 0xe8, 0x69, 0x25, 0x48, + 0xed, 0xaa, 0x95, 0xad, 0x12, 0xe4, 0x03, 0xb2, 0xd4, 0xb2, 0xfb, 0x74, 0x6e, 0x15, 0x6d, 0x74, + 0x8a, 0x96, 0x9c, 0x48, 0x8b, 0xdc, 0x47, 0xca, 0x51, 0x72, 0x82, 0xa4, 0x9a, 0x57, 0x32, 0xc9, + 0xbd, 0x92, 0x57, 0x55, 0xe2, 0x13, 0xa5, 0x93, 0x67, 0x99, 0x41, 0x96, 0x87, 0x3e, 0x5a, 0x26, + 0x85, 0x2b, 0xa1, 0xc5, 0x40, 0xe8, 0x30, 0xf0, 0x11, 0x98, 0xc2, 0xbb, 0xc1, 0x7c, 0xb9, 0x42, + 0xb4, 0xa6, 0x8a, 0xca, 0xa4, 0xb8, 0x40, 0x94, 0xc6, 0xb8, 0x36, 0x3e, 0x50, 0xc6, 0xb8, 0x32, + 0x9e, 0x02, 0x04, 0xe8, 0x45, 0x4b, 0x2e, 0x52, 0xff, 0xc6, 0xda, 0x23, 0xab, 0x29, 0x35, 0xaf, + 0xa5, 0xa2, 0x36, 0xc7, 0x69, 0x82, 0x6b, 0xeb, 0x61, 0x63, 0x7e, 0x29, 0x15, 0xb2, 0x77, 0x64, + 0x96, 0x37, 0xef, 0x93, 0x71, 0x2c, 0xe5, 0x0b, 0x44, 0xe7, 0x19, 0x1c, 0x6e, 0x11, 0x28, 0x8b, + 0xd6, 0xec, 0x33, 0x30, 0x42, 0x81, 0x71, 0xc5, 0xc2, 0xc3, 0x8a, 0x85, 0x1d, 0x4f, 0xe5, 0xe2, + 0x2c, 0x9b, 0x3b, 0xbe, 0x43, 0x2f, 0xaa, 0x17, 0xd3, 0x09, 0x4c, 0x52, 0xa9, 0x6c, 0x38, 0x38, + 0x26, 0x59, 0x91, 0xb0, 0xa4, 0xcc, 0xa0, 0x9f, 0x32, 0x7a, 0x7b, 0x3d, 0xfc, 0xae, 0x01, 0x34, + 0xd7, 0x53, 0x73, 0x64, 0xd7, 0x35, 0xea, 0xfa, 0xb0, 0xd3, 0xf4, 0x41, 0xab, 0xe9, 0x8a, 0x44, + 0x7a, 0x4d, 0x22, 0x06, 0xc3, 0x3c, 0x8d, 0x14, 0x7d, 0x0d, 0x97, 0xce, 0x4d, 0x03, 0x8d, 0xfe, + 0x06, 0x8e, 0x3a, 0x0d, 0x64, 0x30, 0x94, 0xc0, 0x11, 0x2d, 0x4c, 0x97, 0xce, 0xec, 0x00, 0x74, + 0x89, 0xeb, 0x84, 0x54, 0xf2, 0xc8, 0x1c, 0xd8, 0x23, 0xb8, 0xeb, 0xe2, 0x4d, 0x4a, 0x61, 0x2a, + 0x95, 0xaf, 0x14, 0x00, 0xce, 0xd7, 0xc0, 0x36, 0x30, 0x93, 0xa8, 0xcf, 0xba, 0xa8, 0xb3, 0x0e, + 0xea, 0xca, 0xaf, 0xc4, 0xfc, 0x2b, 0x38, 0x6d, 0xe2, 0x85, 0x17, 0x46, 0x17, 0x61, 0x12, 0xf2, + 0x6b, 0x0c, 0x76, 0x83, 0xef, 0xfc, 0xa2, 0xc3, 0xa3, 0xbb, 0x82, 0x65, 0x16, 0xff, 0x0d, 0xdf, + 0xbf, 0x34, 0x7c, 0xef, 0xe0, 0xe4, 0x39, 0x8a, 0x97, 0x84, 0xc6, 0x4f, 0x7c, 0x63, 0x7a, 0xde, + 0x7b, 0x81, 0x37, 0x33, 0xa5, 0xf7, 0xcf, 0xd4, 0xb0, 0x3d, 0x53, 0x3f, 0x0f, 0x60, 0x7f, 0xe3, + 0xe5, 0x7f, 0x32, 0x58, 0xd4, 0x9c, 0x61, 0xb3, 0xd2, 0xeb, 0x61, 0x33, 0xfa, 0x86, 0x6d, 0xd4, + 0xdf, 0xb0, 0x71, 0xef, 0xb0, 0x4d, 0xb6, 0x87, 0xcd, 0xbc, 0x67, 0xd8, 0x60, 0x6b, 0xd8, 0x5a, + 0x88, 0x4d, 0xdb, 0x88, 0x39, 0x2f, 0xe0, 0xb8, 0x0f, 0x7f, 0x39, 0x03, 0x4f, 0xba, 0x93, 0x78, + 0x5c, 0x4d, 0xe2, 0xa6, 0xb3, 0xf2, 0x3a, 0xff, 0x55, 0x07, 0xfd, 0xf5, 0x6d, 0xc4, 0xbe, 0x01, + 0xb3, 0xfe, 0xaa, 0x33, 0x6b, 0xf3, 0xd3, 0x5d, 0xf5, 0xd6, 0x3e, 0xe9, 0xb1, 0x94, 0xbf, 0x00, + 0x3f, 0xc2, 0xfe, 0xc6, 0x42, 0x66, 0x1f, 0x56, 0xde, 0xfd, 0x9f, 0x7a, 0xfb, 0x83, 0x3b, 0xed, + 0x59, 0xb4, 0x76, 0xfe, 0xc7, 0x7e, 0x80, 0xbd, 0xce, 0xae, 0x61, 0x5b, 0x01, 0x6d, 0xe2, 0xd9, + 0xf6, 0x1d, 0x56, 0x75, 0xd9, 0xaa, 0xf9, 0xe3, 0xe8, 0xee, 0x0e, 0xf6, 0xe9, 0x76, 0x5c, 0xcf, + 0x62, 0xb2, 0x3f, 0xde, 0xe5, 0xa6, 0xde, 0x79, 0x4b, 0x0b, 0x72, 0x93, 0xa1, 0x1f, 0xb5, 0x82, + 0xfb, 0xe7, 0xc6, 0x7e, 0x7c, 0x9f, 0x0b, 0xdd, 0xfd, 0xec, 0x93, 0xb7, 0xce, 0x55, 0x28, 0xae, + 0x8b, 0xcb, 0xb9, 0x9f, 0xc6, 0x8b, 0xe0, 0x9d, 0x40, 0xff, 0x7a, 0xc1, 0xc3, 0x1c, 0x93, 0x27, + 0x59, 0xca, 0xd5, 0xef, 0x37, 0xbf, 0x54, 0x7f, 0xe5, 0x5f, 0xfc, 0x1d, 0x00, 0x00, 0xff, 0xff, + 0x75, 0x31, 0x46, 0xd5, 0xb0, 0x0b, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// SqlClient is the client API for Sql service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type SqlClient interface { + Transfers(ctx context.Context, in *TransfersRequest, opts ...grpc.CallOption) (*TransfersResponse, error) + GetOrderHistory(ctx context.Context, in *GetOrderHistoryRequest, opts ...grpc.CallOption) (*GetOrderHistoryReply, error) + GetOrderDeals(ctx context.Context, in *GetOrderDealsRequest, opts ...grpc.CallOption) (*GetOrderDealsReply, error) + GetOrderDetailFinished(ctx context.Context, in *GetOrderDetailFinishedRequest, opts ...grpc.CallOption) (*GetOrderDetailFinishedReply, error) + GetMarketUserDeals(ctx context.Context, in *GetMarketUserDealsRequest, opts ...grpc.CallOption) (*GetMarketUserDealsReply, error) +} + +type sqlClient struct { + cc *grpc.ClientConn +} + +func NewSqlClient(cc *grpc.ClientConn) SqlClient { + return &sqlClient{cc} +} + +func (c *sqlClient) Transfers(ctx context.Context, in *TransfersRequest, opts ...grpc.CallOption) (*TransfersResponse, error) { + out := new(TransfersResponse) + err := c.cc.Invoke(ctx, "/protos.Sql/Transfers", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sqlClient) GetOrderHistory(ctx context.Context, in *GetOrderHistoryRequest, opts ...grpc.CallOption) (*GetOrderHistoryReply, error) { + out := new(GetOrderHistoryReply) + err := c.cc.Invoke(ctx, "/protos.Sql/GetOrderHistory", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sqlClient) GetOrderDeals(ctx context.Context, in *GetOrderDealsRequest, opts ...grpc.CallOption) (*GetOrderDealsReply, error) { + out := new(GetOrderDealsReply) + err := c.cc.Invoke(ctx, "/protos.Sql/GetOrderDeals", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sqlClient) GetOrderDetailFinished(ctx context.Context, in *GetOrderDetailFinishedRequest, opts ...grpc.CallOption) (*GetOrderDetailFinishedReply, error) { + out := new(GetOrderDetailFinishedReply) + err := c.cc.Invoke(ctx, "/protos.Sql/GetOrderDetailFinished", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *sqlClient) GetMarketUserDeals(ctx context.Context, in *GetMarketUserDealsRequest, opts ...grpc.CallOption) (*GetMarketUserDealsReply, error) { + out := new(GetMarketUserDealsReply) + err := c.cc.Invoke(ctx, "/protos.Sql/GetMarketUserDeals", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// SqlServer is the server API for Sql service. +type SqlServer interface { + Transfers(context.Context, *TransfersRequest) (*TransfersResponse, error) + GetOrderHistory(context.Context, *GetOrderHistoryRequest) (*GetOrderHistoryReply, error) + GetOrderDeals(context.Context, *GetOrderDealsRequest) (*GetOrderDealsReply, error) + GetOrderDetailFinished(context.Context, *GetOrderDetailFinishedRequest) (*GetOrderDetailFinishedReply, error) + GetMarketUserDeals(context.Context, *GetMarketUserDealsRequest) (*GetMarketUserDealsReply, error) +} + +func RegisterSqlServer(s *grpc.Server, srv SqlServer) { + s.RegisterService(&_Sql_serviceDesc, srv) +} + +func _Sql_Transfers_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TransfersRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SqlServer).Transfers(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Sql/Transfers", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SqlServer).Transfers(ctx, req.(*TransfersRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Sql_GetOrderHistory_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderHistoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SqlServer).GetOrderHistory(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Sql/GetOrderHistory", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SqlServer).GetOrderHistory(ctx, req.(*GetOrderHistoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Sql_GetOrderDeals_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderDealsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SqlServer).GetOrderDeals(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Sql/GetOrderDeals", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SqlServer).GetOrderDeals(ctx, req.(*GetOrderDealsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Sql_GetOrderDetailFinished_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetOrderDetailFinishedRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SqlServer).GetOrderDetailFinished(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Sql/GetOrderDetailFinished", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SqlServer).GetOrderDetailFinished(ctx, req.(*GetOrderDetailFinishedRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Sql_GetMarketUserDeals_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetMarketUserDealsRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SqlServer).GetMarketUserDeals(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Sql/GetMarketUserDeals", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SqlServer).GetMarketUserDeals(ctx, req.(*GetMarketUserDealsRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Sql_serviceDesc = grpc.ServiceDesc{ + ServiceName: "protos.Sql", + HandlerType: (*SqlServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Transfers", + Handler: _Sql_Transfers_Handler, + }, + { + MethodName: "GetOrderHistory", + Handler: _Sql_GetOrderHistory_Handler, + }, + { + MethodName: "GetOrderDeals", + Handler: _Sql_GetOrderDeals_Handler, + }, + { + MethodName: "GetOrderDetailFinished", + Handler: _Sql_GetOrderDetailFinished_Handler, + }, + { + MethodName: "GetMarketUserDeals", + Handler: _Sql_GetMarketUserDeals_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "protos/sql.proto", +} diff --git a/protos/sql.proto b/protos/sql.proto new file mode 100644 index 0000000..44c9433 --- /dev/null +++ b/protos/sql.proto @@ -0,0 +1,140 @@ +syntax = "proto3"; + +package protos; + +option go_package = "github.com/en/siren/protos"; + +import "google/protobuf/timestamp.proto"; + +service Sql { + rpc Transfers (TransfersRequest) returns (TransfersResponse); + + rpc GetOrderHistory (GetOrderHistoryRequest) returns (GetOrderHistoryReply) {} + rpc GetOrderDeals (GetOrderDealsRequest) returns (GetOrderDealsReply) {} + rpc GetOrderDetailFinished (GetOrderDetailFinishedRequest) returns (GetOrderDetailFinishedReply) {} + rpc GetMarketUserDeals (GetMarketUserDealsRequest) returns (GetMarketUserDealsReply) {} +} + +message TransfersRequest { + string user_id = 1; + string currency = 2; + string type = 3; + google.protobuf.Timestamp since = 4; + google.protobuf.Timestamp until = 5; + int32 limit = 6; +} + +message Transfer { + string id = 1; + string currency = 2; + string type = 3; + string amount = 4; + string status = 5; + google.protobuf.Timestamp created_at = 6; + google.protobuf.Timestamp updated_at = 7; +} + +message TransfersResponse { + repeated Transfer transfers = 1; +} + +message GetOrderHistoryRequest { + string user_id = 1; + string market = 2; + int64 start_time = 3; + int64 end_time = 4; + int32 offset = 5; + int32 limit = 6; + int32 side = 7; +} + +message OrderHistory { + uint64 id = 1; + int64 ctime = 2; + int64 ftime = 3; + string user = 4; + string market = 5; + string source = 6; + uint32 type = 7; + uint32 side = 8; + string price = 9; + string amount = 10; + string taker_fee = 11; + string maker_fee = 12; + string deal_stock = 13; + string deal_money = 14; + string deal_fee = 15; +} + +message GetOrderHistoryReply { + repeated OrderHistory items = 1; +} + +message GetOrderDealsRequest { + string order_id = 1; + int32 offset = 2; + int32 limit = 3; +} + +message OrderDeals { + int64 time = 1; + string user = 2; + uint64 id = 3; + int32 role = 4; + string price = 5; + string amount = 6; + string deal = 7; + string fee = 8; + uint64 deal_order_id = 9; +} + +message GetOrderDealsReply { + repeated OrderDeals items = 1; +} + +message GetOrderDetailFinishedRequest { + string order_id = 1; +} + +message GetOrderDetailFinishedReply { + uint64 id = 1; + int64 ctime = 2; + int64 ftime = 3; + string user = 4; + string market = 5; + string source = 6; + uint32 type = 7; + uint32 side = 8; + string price = 9; + string amount = 10; + string taker_fee = 11; + string maker_fee = 12; + string deal_stock = 13; + string deal_money = 14; + string deal_fee = 15; +} + +message GetMarketUserDealsRequest { + string user_id = 1; + string market = 2; + int32 offset = 3; + int32 limit = 4; +} + +message MarketUserDeals { + int64 time = 1; + string user = 2; + uint64 id = 3; + int32 side = 4; + int32 role = 5; + string price = 6; + string amount = 7; + string deal = 8; + string fee = 9; + uint64 deal_order_id = 10; + string market = 11; +} + +message GetMarketUserDealsReply { + repeated MarketUserDeals items = 1; +} diff --git a/protos/wallet.pb.go b/protos/wallet.pb.go new file mode 100644 index 0000000..0bf9f52 --- /dev/null +++ b/protos/wallet.pb.go @@ -0,0 +1,467 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// source: protos/wallet.proto + +package protos + +import ( + fmt "fmt" + proto "github.com/golang/protobuf/proto" + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" + math "math" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + +type GetAddressRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAddressRequest) Reset() { *m = GetAddressRequest{} } +func (m *GetAddressRequest) String() string { return proto.CompactTextString(m) } +func (*GetAddressRequest) ProtoMessage() {} +func (*GetAddressRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_b906fab2aed9696f, []int{0} +} + +func (m *GetAddressRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAddressRequest.Unmarshal(m, b) +} +func (m *GetAddressRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAddressRequest.Marshal(b, m, deterministic) +} +func (m *GetAddressRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAddressRequest.Merge(m, src) +} +func (m *GetAddressRequest) XXX_Size() int { + return xxx_messageInfo_GetAddressRequest.Size(m) +} +func (m *GetAddressRequest) XXX_DiscardUnknown() { + xxx_messageInfo_GetAddressRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAddressRequest proto.InternalMessageInfo + +func (m *GetAddressRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *GetAddressRequest) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +type GetAddressResponse struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *GetAddressResponse) Reset() { *m = GetAddressResponse{} } +func (m *GetAddressResponse) String() string { return proto.CompactTextString(m) } +func (*GetAddressResponse) ProtoMessage() {} +func (*GetAddressResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b906fab2aed9696f, []int{1} +} + +func (m *GetAddressResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_GetAddressResponse.Unmarshal(m, b) +} +func (m *GetAddressResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_GetAddressResponse.Marshal(b, m, deterministic) +} +func (m *GetAddressResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_GetAddressResponse.Merge(m, src) +} +func (m *GetAddressResponse) XXX_Size() int { + return xxx_messageInfo_GetAddressResponse.Size(m) +} +func (m *GetAddressResponse) XXX_DiscardUnknown() { + xxx_messageInfo_GetAddressResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_GetAddressResponse proto.InternalMessageInfo + +func (m *GetAddressResponse) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +type WithdrawRequest struct { + UserId string `protobuf:"bytes,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` + Currency string `protobuf:"bytes,2,opt,name=currency,proto3" json:"currency,omitempty"` + Amount string `protobuf:"bytes,3,opt,name=amount,proto3" json:"amount,omitempty"` + Address string `protobuf:"bytes,4,opt,name=address,proto3" json:"address,omitempty"` + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WithdrawRequest) Reset() { *m = WithdrawRequest{} } +func (m *WithdrawRequest) String() string { return proto.CompactTextString(m) } +func (*WithdrawRequest) ProtoMessage() {} +func (*WithdrawRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_b906fab2aed9696f, []int{2} +} + +func (m *WithdrawRequest) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawRequest.Unmarshal(m, b) +} +func (m *WithdrawRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawRequest.Marshal(b, m, deterministic) +} +func (m *WithdrawRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawRequest.Merge(m, src) +} +func (m *WithdrawRequest) XXX_Size() int { + return xxx_messageInfo_WithdrawRequest.Size(m) +} +func (m *WithdrawRequest) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawRequest proto.InternalMessageInfo + +func (m *WithdrawRequest) GetUserId() string { + if m != nil { + return m.UserId + } + return "" +} + +func (m *WithdrawRequest) GetCurrency() string { + if m != nil { + return m.Currency + } + return "" +} + +func (m *WithdrawRequest) GetAmount() string { + if m != nil { + return m.Amount + } + return "" +} + +func (m *WithdrawRequest) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +type WithdrawResponse struct { + XXX_NoUnkeyedLiteral struct{} `json:"-"` + XXX_unrecognized []byte `json:"-"` + XXX_sizecache int32 `json:"-"` +} + +func (m *WithdrawResponse) Reset() { *m = WithdrawResponse{} } +func (m *WithdrawResponse) String() string { return proto.CompactTextString(m) } +func (*WithdrawResponse) ProtoMessage() {} +func (*WithdrawResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b906fab2aed9696f, []int{3} +} + +func (m *WithdrawResponse) XXX_Unmarshal(b []byte) error { + return xxx_messageInfo_WithdrawResponse.Unmarshal(m, b) +} +func (m *WithdrawResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + return xxx_messageInfo_WithdrawResponse.Marshal(b, m, deterministic) +} +func (m *WithdrawResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_WithdrawResponse.Merge(m, src) +} +func (m *WithdrawResponse) XXX_Size() int { + return xxx_messageInfo_WithdrawResponse.Size(m) +} +func (m *WithdrawResponse) XXX_DiscardUnknown() { + xxx_messageInfo_WithdrawResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_WithdrawResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*GetAddressRequest)(nil), "protos.GetAddressRequest") + proto.RegisterType((*GetAddressResponse)(nil), "protos.GetAddressResponse") + proto.RegisterType((*WithdrawRequest)(nil), "protos.WithdrawRequest") + proto.RegisterType((*WithdrawResponse)(nil), "protos.WithdrawResponse") +} + +func init() { proto.RegisterFile("protos/wallet.proto", fileDescriptor_b906fab2aed9696f) } + +var fileDescriptor_b906fab2aed9696f = []byte{ + // 282 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x52, 0x3d, 0x4f, 0xf3, 0x30, + 0x10, 0x7e, 0xf3, 0x82, 0x92, 0x70, 0x0b, 0x70, 0x48, 0x34, 0x64, 0x42, 0x11, 0x03, 0x0b, 0x89, + 0x54, 0x66, 0x06, 0x8a, 0x2a, 0x60, 0x83, 0x2e, 0x95, 0x58, 0x50, 0x1a, 0x9f, 0x88, 0xa5, 0x26, + 0x0e, 0xf6, 0x59, 0x05, 0x7e, 0x03, 0x3f, 0x1a, 0x11, 0x27, 0xb4, 0x50, 0x36, 0x3a, 0x3e, 0x77, + 0xbe, 0xe7, 0x4b, 0x86, 0x83, 0x46, 0x2b, 0x56, 0x26, 0x5b, 0xe4, 0xf3, 0x39, 0x71, 0xda, 0x22, + 0xf4, 0xdd, 0x30, 0xb9, 0x81, 0xfd, 0x6b, 0xe2, 0x4b, 0x21, 0x34, 0x19, 0x33, 0xa1, 0x67, 0x4b, + 0x86, 0x71, 0x00, 0x81, 0x35, 0xa4, 0x1f, 0xa5, 0x88, 0xbc, 0x63, 0xef, 0x74, 0x67, 0xe2, 0x7f, + 0xc2, 0x5b, 0x81, 0x31, 0x84, 0x85, 0xd5, 0x9a, 0xea, 0xe2, 0x35, 0xfa, 0xdf, 0x6e, 0xbe, 0x70, + 0x92, 0x02, 0xae, 0x32, 0x99, 0x46, 0xd5, 0x86, 0x30, 0x82, 0x20, 0x77, 0xa3, 0x8e, 0xaa, 0x87, + 0xc9, 0x0b, 0xec, 0x4e, 0x25, 0x97, 0x42, 0xe7, 0x8b, 0xbf, 0xe8, 0xe2, 0x21, 0xf8, 0x79, 0xa5, + 0x6c, 0xcd, 0xd1, 0x96, 0xbb, 0x71, 0x68, 0x55, 0x79, 0xfb, 0xbb, 0x32, 0xc2, 0xde, 0x52, 0xd9, + 0xf9, 0x1c, 0xbe, 0x7b, 0xe0, 0x4f, 0xdb, 0x82, 0xf0, 0x0a, 0x60, 0x19, 0x04, 0x8f, 0x5c, 0x61, + 0x26, 0x5d, 0xab, 0x29, 0x8e, 0x7f, 0x5b, 0x75, 0xb9, 0x2f, 0x20, 0xec, 0x35, 0x70, 0xd0, 0xbf, + 0xfb, 0x91, 0x37, 0x8e, 0xd6, 0x17, 0x9d, 0x9d, 0x7b, 0x08, 0xc7, 0x5c, 0x92, 0x26, 0x5b, 0xe1, + 0x78, 0x03, 0x7e, 0x92, 0x7f, 0xc3, 0x3b, 0x08, 0x46, 0x92, 0x0b, 0x25, 0xeb, 0x0d, 0x31, 0x8e, + 0x4e, 0x1e, 0x92, 0x27, 0xc9, 0xa5, 0x9d, 0xa5, 0x85, 0xaa, 0x32, 0xf1, 0xc6, 0x54, 0x94, 0x99, + 0x91, 0x9a, 0xea, 0xb3, 0x46, 0x99, 0xcc, 0x9d, 0xce, 0xdc, 0x4f, 0x3b, 0xff, 0x08, 0x00, 0x00, + 0xff, 0xff, 0x54, 0xa1, 0x00, 0x9d, 0x87, 0x02, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// WalletClient is the client API for Wallet service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type WalletClient interface { + GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*GetAddressResponse, error) + Withdraw(ctx context.Context, in *WithdrawRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) +} + +type walletClient struct { + cc *grpc.ClientConn +} + +func NewWalletClient(cc *grpc.ClientConn) WalletClient { + return &walletClient{cc} +} + +func (c *walletClient) GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*GetAddressResponse, error) { + out := new(GetAddressResponse) + err := c.cc.Invoke(ctx, "/protos.Wallet/GetAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *walletClient) Withdraw(ctx context.Context, in *WithdrawRequest, opts ...grpc.CallOption) (*WithdrawResponse, error) { + out := new(WithdrawResponse) + err := c.cc.Invoke(ctx, "/protos.Wallet/Withdraw", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// WalletServer is the server API for Wallet service. +type WalletServer interface { + GetAddress(context.Context, *GetAddressRequest) (*GetAddressResponse, error) + Withdraw(context.Context, *WithdrawRequest) (*WithdrawResponse, error) +} + +func RegisterWalletServer(s *grpc.Server, srv WalletServer) { + s.RegisterService(&_Wallet_serviceDesc, srv) +} + +func _Wallet_GetAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServer).GetAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Wallet/GetAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServer).GetAddress(ctx, req.(*GetAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Wallet_Withdraw_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(WithdrawRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(WalletServer).Withdraw(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Wallet/Withdraw", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(WalletServer).Withdraw(ctx, req.(*WithdrawRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Wallet_serviceDesc = grpc.ServiceDesc{ + ServiceName: "protos.Wallet", + HandlerType: (*WalletServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAddress", + Handler: _Wallet_GetAddress_Handler, + }, + { + MethodName: "Withdraw", + Handler: _Wallet_Withdraw_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "protos/wallet.proto", +} + +// EthereumClient is the client API for Ethereum service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type EthereumClient interface { + GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*GetAddressResponse, error) +} + +type ethereumClient struct { + cc *grpc.ClientConn +} + +func NewEthereumClient(cc *grpc.ClientConn) EthereumClient { + return ðereumClient{cc} +} + +func (c *ethereumClient) GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*GetAddressResponse, error) { + out := new(GetAddressResponse) + err := c.cc.Invoke(ctx, "/protos.Ethereum/GetAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// EthereumServer is the server API for Ethereum service. +type EthereumServer interface { + GetAddress(context.Context, *GetAddressRequest) (*GetAddressResponse, error) +} + +func RegisterEthereumServer(s *grpc.Server, srv EthereumServer) { + s.RegisterService(&_Ethereum_serviceDesc, srv) +} + +func _Ethereum_GetAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(EthereumServer).GetAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Ethereum/GetAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(EthereumServer).GetAddress(ctx, req.(*GetAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Ethereum_serviceDesc = grpc.ServiceDesc{ + ServiceName: "protos.Ethereum", + HandlerType: (*EthereumServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAddress", + Handler: _Ethereum_GetAddress_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "protos/wallet.proto", +} + +// BitcoinClient is the client API for Bitcoin service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type BitcoinClient interface { + GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*GetAddressResponse, error) +} + +type bitcoinClient struct { + cc *grpc.ClientConn +} + +func NewBitcoinClient(cc *grpc.ClientConn) BitcoinClient { + return &bitcoinClient{cc} +} + +func (c *bitcoinClient) GetAddress(ctx context.Context, in *GetAddressRequest, opts ...grpc.CallOption) (*GetAddressResponse, error) { + out := new(GetAddressResponse) + err := c.cc.Invoke(ctx, "/protos.Bitcoin/GetAddress", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// BitcoinServer is the server API for Bitcoin service. +type BitcoinServer interface { + GetAddress(context.Context, *GetAddressRequest) (*GetAddressResponse, error) +} + +func RegisterBitcoinServer(s *grpc.Server, srv BitcoinServer) { + s.RegisterService(&_Bitcoin_serviceDesc, srv) +} + +func _Bitcoin_GetAddress_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAddressRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BitcoinServer).GetAddress(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/protos.Bitcoin/GetAddress", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BitcoinServer).GetAddress(ctx, req.(*GetAddressRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Bitcoin_serviceDesc = grpc.ServiceDesc{ + ServiceName: "protos.Bitcoin", + HandlerType: (*BitcoinServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetAddress", + Handler: _Bitcoin_GetAddress_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "protos/wallet.proto", +} diff --git a/protos/wallet.proto b/protos/wallet.proto new file mode 100644 index 0000000..6037a93 --- /dev/null +++ b/protos/wallet.proto @@ -0,0 +1,37 @@ +syntax = "proto3"; + +package protos; + +option go_package = "github.com/en/siren/protos"; + +service Wallet { + rpc GetAddress (GetAddressRequest) returns (GetAddressResponse); + rpc Withdraw (WithdrawRequest) returns (WithdrawResponse); +} + +message GetAddressRequest { + string user_id = 1; + string currency = 2; +} + +message GetAddressResponse { + string address = 1; +} + +message WithdrawRequest { + string user_id = 1; + string currency = 2; + string amount = 3; + string address = 4; +} + +message WithdrawResponse { +} + +service Ethereum { + rpc GetAddress (GetAddressRequest) returns (GetAddressResponse) {} +} + +service Bitcoin { + rpc GetAddress (GetAddressRequest) returns (GetAddressResponse) {} +} diff --git a/svc-gateway/Dockerfile b/svc-gateway/Dockerfile new file mode 100644 index 0000000..5e3ed92 --- /dev/null +++ b/svc-gateway/Dockerfile @@ -0,0 +1,26 @@ +FROM gcr.io/cloud-builders/go:latest as builder + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache pkgconfig librdkafka-dev + +WORKDIR /go/src/siren/ +COPY . . + +ENV GOPATH=/go +RUN GO111MODULE=on go install -v ./svc-gateway +RUN GO111MODULE=on go install -v ./svc-match +RUN GO111MODULE=on go install -v ./svc-memorystore +RUN GO111MODULE=on go install -v ./svc-sql +RUN GO111MODULE=on go install -v ./svc-wallet +RUN GO111MODULE=on go install -v ./svc-wallet/ethereum +RUN GO111MODULE=on go install -v ./svc-ws + +FROM alpine:3.8 + +RUN apk add --update --update-cache ca-certificates && \ + update-ca-certificates + +WORKDIR /siren/ +COPY --from=builder /go/bin/svc-gateway . + +ENTRYPOINT ["/siren/svc-gateway"] diff --git a/svc-gateway/handlers.go b/svc-gateway/handlers.go new file mode 100644 index 0000000..9a398ab --- /dev/null +++ b/svc-gateway/handlers.go @@ -0,0 +1,245 @@ +package main + +import ( + "context" + "net/http" + "strconv" + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/gofrs/uuid" + "github.com/golang/protobuf/ptypes" + timestamp "github.com/golang/protobuf/ptypes/timestamp" + "github.com/gorilla/mux" + + pb "github.com/en/siren/protos" + log "github.com/en/siren/utils/glog" +) + +func currenciesHandler(w http.ResponseWriter, req *http.Request) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.GetCurrencies(ctx, &pb.EmptyRequest{}) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("failed to get currencies") + return + } + writeInnerSlice(w, r) +} + +func symbolsHandler(w http.ResponseWriter, req *http.Request) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.GetSymbols(ctx, &pb.EmptyRequest{}) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("failed to get currency pairs") + return + } + writeInnerSlice(w, r) +} + +func balancesHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + request := new(pb.BalancesRequest) + request.UserId = sub + request.Currency = vars["currency"] + + log.Info().Str("user_id", sub).Str("currency", vars["currency"]).Msg("query balance") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.Balances(ctx, request) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("failed to get balances") + return + } + + if vars["currency"] != "" { + if len(r.Balances) != 1 { + return + } + writeProto(w, r.Balances[0]) + } else { + if r.Balances == nil { + r.Balances = make([]*pb.Balance, 0) + } + writeInnerSlice(w, r) + } +} + +func transfersHandler(w http.ResponseWriter, req *http.Request) { + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + var ( + since *timestamp.Timestamp + until *timestamp.Timestamp + limit int64 + err error + ) + if req.FormValue("since") != "" { + t, err := time.Parse(time.RFC3339Nano, req.FormValue("since")) + if err != nil { + log.Error().Err(err).Msg("parsing since failed") + return + } + since, err = ptypes.TimestampProto(t) + if err != nil { + log.Error().Err(err).Msg("parsing since failed") + return + } + } + if req.FormValue("until") != "" { + t, err := time.Parse(time.RFC3339Nano, req.FormValue("until")) + if err != nil { + log.Error().Err(err).Msg("parsing until failed") + return + } + until, err = ptypes.TimestampProto(t) + if err != nil { + log.Error().Err(err).Msg("parsing until failed") + return + } + } + if req.FormValue("limit") != "" { + limit, err = strconv.ParseInt(req.FormValue("limit"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + } + if limit == 0 { + limit = 10 + } + if limit > 50 { + limit = 50 + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := sqlClient.Transfers(ctx, &pb.TransfersRequest{ + UserId: sub, + Currency: req.FormValue("currency"), + Type: req.FormValue("type"), + Since: since, + Until: until, + Limit: int32(limit), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("failed to get transfers") + return + } + writeInnerSlice(w, r) +} + +func depositsAddressHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + log.Info().Str("user", sub).Msg("query address") + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := walletClient.GetAddress(ctx, &pb.GetAddressRequest{UserId: sub, Currency: vars["currency"]}) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("get address") + return + } + writeProto(w, r) +} + +func withdrawalsHandler(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := walletClient.Withdraw(ctx, &pb.WithdrawRequest{UserId: sub, Currency: vars["currency"], Amount: req.PostFormValue("amount"), Address: req.PostFormValue("address")}) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("withdraw") + return + } + writeProto(w, r) +} + +func newOrderHandler(w http.ResponseWriter, req *http.Request) { + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.NewOrder(ctx, &pb.NewOrderRequest{ + UserId: sub, + ClientOrderId: req.PostFormValue("client_order_id"), + Symbol: req.PostFormValue("symbol"), + Type: req.PostFormValue("type"), + Side: req.PostFormValue("side"), + Price: req.PostFormValue("price"), + Amount: req.PostFormValue("amount"), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("get new order") + return + } + writeProto(w, r.Order) +} + +func ordersHandler(w http.ResponseWriter, req *http.Request) { + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.Orders(ctx, &pb.OrdersRequest{ + UserId: sub, + Symbol: req.FormValue("symbol"), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("get orders failed") + return + } + if r.Orders == nil { + r.Orders = make([]*pb.Order, 0) + } + writeInnerSlice(w, r) +} + +func mgmtBalanceHandler(w http.ResponseWriter, req *http.Request) { + id, err := uuid.NewV4() + if err != nil { + log.Printf("failed to generate UUID: %v", err) + return + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.UpdateBalance(ctx, &pb.UpdateBalanceRequest{ + Id: id.String(), + Type: req.PostFormValue("type"), + UserId: req.PostFormValue("user_id"), + Currency: req.PostFormValue("currency"), + Amount: req.PostFormValue("amount"), + }) + + // TODO: log to db + + writeProto(w, r) +} diff --git a/svc-gateway/main.go b/svc-gateway/main.go new file mode 100644 index 0000000..d23ad0d --- /dev/null +++ b/svc-gateway/main.go @@ -0,0 +1,648 @@ +package main + +import ( + "context" + "encoding/json" + "errors" + "io" + "net/http" + "strconv" + "strings" + "time" + + "github.com/auth0/go-jwt-middleware" + "github.com/dgrijalva/jwt-go" + "github.com/golang/protobuf/jsonpb" + "github.com/gorilla/mux" + "github.com/rs/cors" + "github.com/spf13/viper" + "github.com/urfave/negroni" + "google.golang.org/grpc" + + pb "github.com/en/siren/protos" + log "github.com/en/siren/utils/glog" +) + +var walletClient pb.WalletClient +var sqlClient pb.SqlClient +var memorystoreClient pb.MemorystoreClient +var matchClient pb.MatchClient + +type Jwks struct { + Keys []JSONWebKeys `json:"keys"` +} + +type JSONWebKeys struct { + Kty string `json:"kty"` + Kid string `json:"kid"` + Use string `json:"use"` + N string `json:"n"` + E string `json:"e"` + X5c []string `json:"x5c"` +} + +func main() { + log.Info().Msg("Starting siren-gateway") + + viper.SetEnvPrefix("gateway") + viper.AutomaticEnv() + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + + jwtMiddleware := jwtmiddleware.New(jwtmiddleware.Options{ + ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) { + // Verify 'aud' claim + aud := viper.GetString("auth0-audience") + checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) + if !checkAud { + return token, errors.New("Invalid audience.") + } + // Verify 'iss' claim + iss := "https://" + viper.GetString("auth0-domain") + "/" + checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) + if !checkIss { + return token, errors.New("Invalid issuer.") + } + + cert, err := getPemCert(token) + if err != nil { + panic(err.Error()) + } + + result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) + return result, nil + }, + SigningMethod: jwt.SigningMethodRS256, + }) + + { + conn, err := grpc.Dial("siren-match:50051", grpc.WithInsecure()) + if err != nil { + log.Fatal().Err(err).Msg("did not connect") + } + defer conn.Close() + matchClient = pb.NewMatchClient(conn) + log.Info().Msg("connected to siren-match:50051") + } + { + conn, err := grpc.Dial("siren-sql:50051", grpc.WithInsecure()) + if err != nil { + log.Fatal().Err(err).Msg("did not connect") + } + defer conn.Close() + sqlClient = pb.NewSqlClient(conn) + log.Info().Msg("connected to siren-sql:50051") + } + { + conn, err := grpc.Dial("siren-memorystore:50051", grpc.WithInsecure()) + if err != nil { + log.Fatal().Err(err).Msg("did not connect") + } + defer conn.Close() + memorystoreClient = pb.NewMemorystoreClient(conn) + log.Info().Msg("connected to siren-memorystore:50051") + } + { + conn, err := grpc.Dial("siren-wallet:50051", grpc.WithInsecure()) + if err != nil { + log.Fatal().Err(err).Msg("did not connect") + } + defer conn.Close() + walletClient = pb.NewWalletClient(conn) + log.Info().Msg("connected to siren-wallet:50051") + } + + r := mux.NewRouter() + r.HandleFunc("/healthz", func(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + io.WriteString(w, `{}`) + }).Methods("GET") + + // /v1 + v1 := mux.NewRouter().PathPrefix("/v1").Subrouter() + v1.HandleFunc("/currencies", currenciesHandler).Methods("GET") + v1.HandleFunc("/symbols", symbolsHandler).Methods("GET") + + v1.Handle("/balances", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(balancesHandler)))).Methods("GET") + v1.Handle("/balances/{currency:[A-Z]+}", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(balancesHandler)))).Methods("GET") + v1.Handle("/transfers", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(transfersHandler)))).Methods("GET") + v1.Handle("/deposits/{currency:[A-Z]+}/address", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(depositsAddressHandler)))).Methods("GET") + v1.Handle("/withdrawals/{currency:[A-Z]+}", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(withdrawalsHandler)))).Methods("POST") + v1.Handle("/orders", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(newOrderHandler)))).Methods("POST") + v1.Handle("/orders", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(ordersHandler)))).Methods("GET") + + v1.HandleFunc("/mgmt/balance", mgmtBalanceHandler).Methods("POST") + + v1.HandleFunc("/asset.summary", func(w http.ResponseWriter, req *http.Request) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + request := new(pb.GetAssetSummaryRequest) + if values, ok := req.URL.Query()["currency"]; ok { + for _, value := range values { + request.Currencies = append(request.Currencies, value) + } + } + + r, err := matchClient.GetAssetSummary(ctx, request) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("failed to get asset summary") + return + } + + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal get asset summary reply") + } + }).Methods("GET") + + v1.Handle("/order.cancel/{order_id}", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + vars := mux.Vars(req) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.OrderCancel(ctx, &pb.OrderCancelRequest{ + UserId: sub, + Symbol: req.FormValue("symbol"), + OrderId: vars["order_id"], + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal order cancel") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal order cancel reply") + } + })))).Methods("DELETE") + + v1.HandleFunc("/order.book/{symbol}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + side, err := strconv.ParseInt(req.FormValue("side"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + limit, err := strconv.ParseInt(req.FormValue("limit"), 10, 64) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + + r, err := matchClient.OrderBook(ctx, &pb.OrderBookRequest{ + Symbol: vars["symbol"], + Side: int32(side), + Limit: uint64(limit), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal order book") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal order book reply") + } + }).Methods("GET") + + v1.HandleFunc("/order.depth/{symbol}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + limit, err := strconv.ParseInt(req.FormValue("limit"), 10, 64) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + + r, err := matchClient.OrderBookDepth(ctx, &pb.OrderBookDepthRequest{ + Symbol: vars["symbol"], + Limit: uint64(limit), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal order depth") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal order depth reply") + } + }).Methods("GET") + + v1.HandleFunc("/order.pending_detail/{symbol}/{order_id}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.OrderDetail(ctx, &pb.OrderDetailRequest{ + Symbol: vars["symbol"], + OrderId: vars["order_id"], + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal order pending_detail") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal order pending_detail reply") + } + }).Methods("GET") + + v1.HandleFunc("/order.deals/{order_id}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + offset, err := strconv.ParseInt(req.FormValue("offset"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + limit, err := strconv.ParseInt(req.FormValue("limit"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + r, err := sqlClient.GetOrderDeals(ctx, &pb.GetOrderDealsRequest{ + OrderId: vars["order_id"], + Offset: int32(offset), + Limit: int32(limit), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal get order deals") + return + } + b, err := json.Marshal(r.Items) + if err != nil { + log.Error().Err(err).Msg("marshal get order deals reply") + return + } + + w.Write(b) + }).Methods("GET") + + v1.Handle("/order.finished", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + startTime, err := strconv.ParseInt(req.FormValue("start_time"), 10, 64) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + endTime, err := strconv.ParseInt(req.FormValue("end_time"), 10, 64) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + offset, err := strconv.ParseInt(req.FormValue("offset"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + limit, err := strconv.ParseInt(req.FormValue("limit"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + side, err := strconv.ParseInt(req.FormValue("side"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + r, err := sqlClient.GetOrderHistory(ctx, &pb.GetOrderHistoryRequest{ + UserId: sub, + Market: req.FormValue("symbol"), + StartTime: startTime, + EndTime: endTime, + Offset: int32(offset), + Limit: int32(limit), + Side: int32(side), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal get order finished") + return + } + b, err := json.Marshal(r.Items) + if err != nil { + log.Error().Err(err).Msg("marshal get order finished reply") + return + } + + w.Write(b) + })))).Methods("GET") + + v1.HandleFunc("/order.finished_detail/{order_id}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := sqlClient.GetOrderDetailFinished(ctx, &pb.GetOrderDetailFinishedRequest{ + OrderId: vars["order_id"], + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal get order finished detail") + return + } + b, err := json.Marshal(r) + if err != nil { + log.Error().Err(err).Msg("marshal get order finished detail reply") + return + } + + w.Write(b) + }).Methods("GET") + + v1.HandleFunc("/market.last/{symbol}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := memorystoreClient.MarketLast(ctx, &pb.MarketLastRequest{ + Market: vars["symbol"], + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal market last") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal market last reply") + } + }).Methods("GET") + + v1.HandleFunc("/market.deals/{symbol}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + limit, err := strconv.ParseInt(req.FormValue("limit"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + r, err := memorystoreClient.MarketDeals(ctx, &pb.MarketDealsRequest{ + Market: vars["symbol"], + Limit: int32(limit), + LastId: 0, + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal market deals") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal market deals reply") + } + }).Methods("GET") + + v1.HandleFunc("/market.kline/{symbol}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + // TODO: use req.Context() ? + ctx := context.Background() + // ctx, cancel := context.WithTimeout(context.Background(), time.Second) + // defer cancel() + start, err := strconv.ParseInt(req.FormValue("start"), 10, 64) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + end, err := strconv.ParseInt(req.FormValue("end"), 10, 64) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + interval, err := strconv.ParseInt(req.FormValue("interval"), 10, 64) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + r, err := memorystoreClient.MarketKline(ctx, &pb.MarketKlineRequest{ + Market: vars["symbol"], + Start: start, + End: end, + Interval: interval, + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal market kline") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal market kline reply") + } + }).Methods("GET") + + v1.HandleFunc("/market.status/{symbol}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + period, err := strconv.ParseInt(req.FormValue("period"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + r, err := memorystoreClient.MarketStatus(ctx, &pb.MarketStatusRequest{ + Market: vars["symbol"], + Period: int32(period), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal market status") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal market status reply") + } + }).Methods("GET") + + v1.HandleFunc("/market.status_today/{symbol}", func(w http.ResponseWriter, req *http.Request) { + vars := mux.Vars(req) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := memorystoreClient.MarketStatusToday(ctx, &pb.MarketStatusTodayRequest{ + Market: vars["symbol"], + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal market status today") + return + } + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal market status today") + } + }).Methods("GET") + + v1.Handle("/market.user_deals", negroni.New( + negroni.HandlerFunc(jwtMiddleware.HandlerWithNext), + negroni.Wrap(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + token, _ := req.Context().Value("user").(*jwt.Token) + claims, _ := token.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + // TODO: use req.Context() ? + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + offset, err := strconv.ParseInt(req.FormValue("offset"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + limit, err := strconv.ParseInt(req.FormValue("limit"), 10, 32) + if err != nil { + log.Error().Err(err).Msg("strconv failed") + return + } + r, err := sqlClient.GetMarketUserDeals(ctx, &pb.GetMarketUserDealsRequest{ + UserId: sub, + Market: req.FormValue("symbol"), + Offset: int32(offset), + Limit: int32(limit), + }) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("marshal get market user deals") + return + } + b, err := json.Marshal(r.Items) + if err != nil { + log.Error().Err(err).Msg("marshal get market user deals reply") + return + } + + w.Write(b) + })))).Methods("GET") + + v1.HandleFunc("/market.summary", func(w http.ResponseWriter, req *http.Request) { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + request := new(pb.GetMarketSummaryRequest) + if values, ok := req.URL.Query()["symbol"]; ok { + for _, value := range values { + request.Symbols = append(request.Symbols, value) + } + } + + r, err := matchClient.GetMarketSummary(ctx, request) + if err != nil { + // TODO: return error message + log.Error().Err(err).Msg("failed to get market summary") + return + } + + m := jsonpb.Marshaler{EmitDefaults: true} + err = m.Marshal(w, r) + if err != nil { + log.Error().Err(err).Msg("marshal get market summary reply") + } + }).Methods("GET") + + l := negroni.NewLogger() + l.SetFormat("{{.Status}} | \t {{.Duration}} | {{.Hostname}} | {{.Method}} {{.Path}} \n") + c := cors.New(cors.Options{ + AllowedOrigins: []string{"*"}, + AllowedMethods: []string{"GET", "POST", "DELETE"}, + AllowCredentials: true, + AllowedHeaders: []string{"Authorization"}, + }) + r.PathPrefix("/v1").Handler(negroni.New( + l, + c, + negroni.Wrap(v1), + )) + + n := negroni.New() + n.Use(negroni.NewRecovery()) + n.UseHandler(r) + + n.Run(":3000") +} + +func getPemCert(token *jwt.Token) (string, error) { + cert := "" + resp, err := http.Get("https://" + viper.GetString("auth0-domain") + "/.well-known/jwks.json") + + if err != nil { + return cert, err + } + defer resp.Body.Close() + + var jwks = Jwks{} + err = json.NewDecoder(resp.Body).Decode(&jwks) + + if err != nil { + return cert, err + } + + for k, _ := range jwks.Keys { + if token.Header["kid"] == jwks.Keys[k].Kid { + cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----" + } + } + + if cert == "" { + err := errors.New("Unable to find appropriate key.") + return cert, err + } + + return cert, nil +} diff --git a/svc-gateway/utils.go b/svc-gateway/utils.go new file mode 100644 index 0000000..f1d8503 --- /dev/null +++ b/svc-gateway/utils.go @@ -0,0 +1,39 @@ +package main + +import ( + "bytes" + "net/http" + + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" +) + +func writeInnerSlice(w http.ResponseWriter, pb proto.Message) (int, error) { + m := jsonpb.Marshaler{EmitDefaults: true, OrigName: true} + + var buf bytes.Buffer + err := m.Marshal(&buf, pb) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + return 0, err + } + b := buf.Bytes() + l := bytes.Index(b, []byte("[")) + r := bytes.LastIndex(b, []byte("]")) + if l == -1 && r == -1 { + w.WriteHeader(http.StatusInternalServerError) + return 0, nil + } + + return w.Write(b[l : r+1]) +} + +func writeProto(w http.ResponseWriter, pb proto.Message) { + m := jsonpb.Marshaler{EmitDefaults: true, OrigName: true} + err := m.Marshal(w, pb) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + } +} diff --git a/svc-match/Dockerfile b/svc-match/Dockerfile new file mode 100644 index 0000000..2a445a8 --- /dev/null +++ b/svc-match/Dockerfile @@ -0,0 +1,26 @@ +FROM gcr.io/cloud-builders/go:latest as builder + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache pkgconfig librdkafka-dev + +WORKDIR /go/src/siren/ +COPY . . + +ENV GOPATH=/go +RUN GO111MODULE=on go install -v ./svc-gateway +RUN GO111MODULE=on go install -v ./svc-match +RUN GO111MODULE=on go install -v ./svc-memorystore +RUN GO111MODULE=on go install -v ./svc-sql +RUN GO111MODULE=on go install -v ./svc-wallet +RUN GO111MODULE=on go install -v ./svc-wallet/ethereum +RUN GO111MODULE=on go install -v ./svc-ws + +FROM alpine:3.8 + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache librdkafka + +WORKDIR /siren/ +COPY --from=builder /go/bin/svc-match . + +ENTRYPOINT ["/siren/svc-match"] diff --git a/svc-match/asset.go b/svc-match/asset.go new file mode 100644 index 0000000..d7aff10 --- /dev/null +++ b/svc-match/asset.go @@ -0,0 +1,373 @@ +package main + +import ( + "errors" + "sync" + + "github.com/en/siren/utils" + "github.com/shopspring/decimal" + + log "github.com/en/siren/utils/glog" +) + +type currency struct { + id string `json:"id"` + name string `json:"name"` + minSize string `json:"min_size"` +} + +type AccountType int32 + +const ( + ACCOUNT_TYPE_CASH AccountType = 0 + ACCOUNT_TYPE_MARGIN AccountType = 1 +) + +type BalanceType int32 + +const ( + BALANCE_TYPE_AVAILABLE BalanceType = 1 + BALANCE_TYPE_HOLD BalanceType = 2 +) + +type account struct { + available decimal.Decimal + hold decimal.Decimal +} + +type user struct { + sync.RWMutex + + cash map[string]account + margin map[string]account +} + +type asset struct { + sync.RWMutex + + users map[string]*user +} + +var assets asset +var currencies map[string]currency + +func balanceGet(currencyId string, userId string, accountType AccountType) account { + assets.RLock() + u, ok := assets.users[userId] + assets.RUnlock() + if !ok { + return account{} + } + + u.RLock() + defer u.RUnlock() + if accountType == ACCOUNT_TYPE_CASH { + return u.cash[currencyId] + } else { + return u.margin[currencyId] + } +} + +func balanceSet(currencyId string, userId string, accountType AccountType, available string, hold string) error { + _, ok := currencies[currencyId] + if !ok { + return errors.New("currency not found") + } + + assets.RLock() + u, ok := assets.users[userId] + assets.RUnlock() + + a, err := decimal.NewFromString(available) + if err != nil { + log.Error().Str("available", available).Msg("wrong decimal") + return nil + } + h, err := decimal.NewFromString(hold) + if err != nil { + log.Error().Str("hold", hold).Msg("wrong decimal") + return nil + } + if ok { + u.Lock() + if accountType == ACCOUNT_TYPE_CASH { + u.cash[currencyId] = account{available: a, hold: h} + } else { + u.margin[currencyId] = account{available: a, hold: h} + } + u.Unlock() + } else { + u := new(user) + u.cash = make(map[string]account) + u.margin = make(map[string]account) + if accountType == ACCOUNT_TYPE_CASH { + u.cash[currencyId] = account{available: a, hold: h} + } else { + u.margin[currencyId] = account{available: a, hold: h} + } + assets.Lock() + assets.users[userId] = u + assets.Unlock() + } + + return nil +} + +func balanceAdd(currencyId string, userId string, accountType AccountType, balanceType BalanceType, amount decimal.Decimal) error { + _, ok := currencies[currencyId] + if !ok { + return errors.New("currency not found") + } + + assets.RLock() + u, ok := assets.users[userId] + assets.RUnlock() + if ok { + u.Lock() + if accountType == ACCOUNT_TYPE_CASH { + old := u.cash[currencyId] + if balanceType == BALANCE_TYPE_AVAILABLE { + u.cash[currencyId] = account{available: old.available.Add(amount), hold: old.hold} + } else { + u.cash[currencyId] = account{available: old.available, hold: old.hold.Add(amount)} + } + } else { + old := u.margin[currencyId] + if balanceType == BALANCE_TYPE_AVAILABLE { + u.margin[currencyId] = account{available: old.available.Add(amount), hold: old.hold} + } else { + u.margin[currencyId] = account{available: old.available, hold: old.hold.Add(amount)} + } + } + u.Unlock() + } else { + u := new(user) + u.cash = make(map[string]account) + u.margin = make(map[string]account) + if accountType == ACCOUNT_TYPE_CASH { + if balanceType == BALANCE_TYPE_AVAILABLE { + u.cash[currencyId] = account{available: amount, hold: decimal.Zero} + } else { + u.cash[currencyId] = account{available: decimal.Zero, hold: amount} + } + } else { + if balanceType == BALANCE_TYPE_AVAILABLE { + u.margin[currencyId] = account{available: amount, hold: decimal.Zero} + } else { + u.margin[currencyId] = account{available: decimal.Zero, hold: amount} + } + } + assets.Lock() + assets.users[userId] = u + assets.Unlock() + } + + return nil +} + +func balanceSub(currencyId string, userId string, accountType AccountType, balanceType BalanceType, amount decimal.Decimal) error { + _, ok := currencies[currencyId] + if !ok { + return errors.New("currency not found") + } + + assets.RLock() + u, ok := assets.users[userId] + assets.RUnlock() + + if !ok { + return errors.New("user_id not found") + } + + u.Lock() + defer u.Unlock() + if accountType == ACCOUNT_TYPE_CASH { + old := u.cash[currencyId] + if balanceType == BALANCE_TYPE_AVAILABLE { + if old.available.LessThan(amount) { + return errors.New("not enough amount to sub") + } + u.cash[currencyId] = account{available: old.available.Sub(amount), hold: old.hold} + } else { + if old.hold.LessThan(amount) { + return errors.New("not enough amount to sub") + } + u.cash[currencyId] = account{available: old.available, hold: old.hold.Sub(amount)} + } + } else { + old := u.margin[currencyId] + if balanceType == BALANCE_TYPE_AVAILABLE { + if old.available.LessThan(amount) { + return errors.New("not enough amount to sub") + } + u.margin[currencyId] = account{available: old.available.Sub(amount), hold: old.hold} + } else { + if old.hold.LessThan(amount) { + return errors.New("not enough amount to sub") + } + u.margin[currencyId] = account{available: old.available, hold: old.hold.Sub(amount)} + } + } + + return nil +} + +func balanceFreeze(currencyId string, userId string, accountType AccountType, amount decimal.Decimal) error { + _, ok := currencies[currencyId] + if !ok { + return errors.New("currency not found") + } + + assets.RLock() + u, ok := assets.users[userId] + assets.RUnlock() + + if !ok { + return errors.New("user_id not found") + } + + u.Lock() + defer u.Unlock() + if accountType == ACCOUNT_TYPE_CASH { + old := u.cash[currencyId] + if old.available.LessThan(amount) { + return errors.New("not enough amount to freeze") + } + u.cash[currencyId] = account{available: old.available.Sub(amount), hold: old.hold.Add(amount)} + } else { + old := u.margin[currencyId] + if old.available.LessThan(amount) { + return errors.New("not enough amount to freeze") + } + u.margin[currencyId] = account{available: old.available.Sub(amount), hold: old.hold.Add(amount)} + } + + return nil +} + +func balanceUnfreeze(currencyId string, userId string, accountType AccountType, amount decimal.Decimal) error { + _, ok := currencies[currencyId] + if !ok { + return errors.New("currency not found") + } + + assets.RLock() + u, ok := assets.users[userId] + assets.RUnlock() + + if !ok { + return errors.New("user_id not found") + } + + u.Lock() + defer u.Unlock() + if accountType == ACCOUNT_TYPE_CASH { + old := u.cash[currencyId] + if old.hold.LessThan(amount) { + return errors.New("not enough amount to freeze") + } + u.cash[currencyId] = account{available: old.available.Add(amount), hold: old.hold.Sub(amount)} + } else { + old := u.margin[currencyId] + if old.hold.LessThan(amount) { + return errors.New("not enough amount to freeze") + } + u.margin[currencyId] = account{available: old.available.Add(amount), hold: old.hold.Sub(amount)} + } + + return nil +} + +type status struct { + total decimal.Decimal + available decimal.Decimal + hold decimal.Decimal + availableCount int64 + holdCount int64 +} + +func balanceStatus(currencyId string, accountType AccountType) status { + var s status + + _, ok := currencies[currencyId] + if !ok { + return s + } + + assets.RLock() + for _, u := range assets.users { + u.RLock() + if accountType == ACCOUNT_TYPE_CASH { + balance := u.cash[currencyId] + if balance.available.IsPositive() { + s.total = s.total.Add(balance.available) + s.available = s.available.Add(balance.available) + s.availableCount += 1 + } + + if balance.hold.IsPositive() { + s.total = s.total.Add(balance.hold) + s.hold = s.hold.Add(balance.hold) + s.holdCount += 1 + } + } else { + balance := u.margin[currencyId] + if balance.available.IsPositive() { + s.total = s.total.Add(balance.available) + s.available = s.available.Add(balance.available) + s.availableCount += 1 + } + + if balance.hold.IsPositive() { + s.total = s.total.Add(balance.hold) + s.hold = s.hold.Add(balance.hold) + s.holdCount += 1 + } + } + u.RUnlock() + } + assets.RUnlock() + return s +} + +func updateUserBalance(push bool, id string, typ string, userId string, currencyId string, accountType AccountType, amount decimal.Decimal) { + _, ok := currencies[currencyId] + if !ok { + log.Error().Str("currency_id", currencyId).Msg("currency_id not exists") + return + } + + assets.RLock() + _, ok = assets.users[userId] + assets.RUnlock() + if !ok { + log.Info().Str("user_id", userId).Msg("user not found, create one") + newUser := new(user) + newUser.cash = make(map[string]account) + newUser.margin = make(map[string]account) + assets.Lock() + assets.users[userId] = newUser + assets.Unlock() + } + + var err error + if typ == "Deposit" { + err = balanceAdd(currencyId, userId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + } else { + err = balanceSub(currencyId, userId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + } + + if err != nil { + log.Error().Err(err).Msg("failed to update balance") + return + } + if push { + now := utils.NowUnixMilli() + if typ == "Deposit" { + appendUserBalanceHistory(now, userId, currencyId, typ, amount, id) + } else { + appendUserBalanceHistory(now, userId, currencyId, typ, amount.Neg(), id) + } + pushBalanceMessage(now, userId, currencyId, typ, amount) + } +} diff --git a/svc-match/fast-skiplist/LICENSE b/svc-match/fast-skiplist/LICENSE new file mode 100644 index 0000000..72669b1 --- /dev/null +++ b/svc-match/fast-skiplist/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 sean + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/svc-match/fast-skiplist/README.md b/svc-match/fast-skiplist/README.md new file mode 100644 index 0000000..a07cbf9 --- /dev/null +++ b/svc-match/fast-skiplist/README.md @@ -0,0 +1,126 @@ +## fast-skiplist + + + +### Purpose + +As the basic building block of an in-memory data structure store, I needed an implementation of skip lists in Go. It needed to be easy to use and thread-safe while maintaining the properties of a classic skip list. + +There are several skip list implementations in Go. However, they all are implemented in slightly different ways with sparse optimizations and occasional shortcomings. **Please see the [skiplist-survey](https://github.com/sean-public/skiplist-survey) repo for a comparison of Go skip list implementations (including benchmarks).** + +The purpose of this repo is to offer a new, fast implementation with an easy-to-use interface that will suit general data storage purposes. + +| Operation | Time Complexity | +| ------------------ | -------- | +| Insertion | O(log N) | +| Removal | O(log N) | +| Check if contains | O(log N) | +| Enumerate in order | O(N) | + + +### Quickstart + +To start using the library right away, just do: + +```sh +go get github.com/sean-public/fast-skiplist +``` + +There are no external dependencies, so you can start using it right away: + +```go +import github.com/sean-public/fast-skiplist + +list := skiplist.New() +list.Set(123, "This string data is stored at key 123!") +fmt.Println(list.Get(123).value) +fmt.Println(list.Length) // prints 1 +list.Remove(123) +fmt.Println(list.Length) // prints 0 +``` + +Of course there are tests, including benchmarks and race condition detection with concurrency: + +``` +$ go test -cover +PASS +coverage: 100.0% of statements +ok github.com/sean-public/fast-skiplist 0.006s + +$ go test -race +Structure sizes: SkipList is 136, Element is 48 bytes +PASS +ok github.com/sean-public/fast-skiplist 41.530s + +$ go test -bench=. +Structure sizes: SkipList is 136, Element is 48 bytes +goos: darwin +goarch: amd64 +pkg: github.com/sean-public/fast-skiplist +BenchmarkIncSet-8 5000000 370 ns/op 13484040.32 MB/s 62 B/op 3 allocs/op +BenchmarkIncGet-8 10000000 205 ns/op 48592107.58 MB/s 0 B/op 0 allocs/op +BenchmarkDecSet-8 10000000 281 ns/op 35547886.82 MB/s 62 B/op 3 allocs/op +BenchmarkDecGet-8 10000000 212 ns/op 47124462.78 MB/s 0 B/op 0 allocs/op +PASS +ok github.com/sean-public/fast-skiplist 21.709s +``` + + + +### About fast-skiplist + +> "Perfection is achieved not when there is nothing more to add, but rather when there is nothing more to take away" *— Antoine de Saint-Exupery* + +If fast-skiplist is faster than other packages with the same features, it's because it does *less* wherever possible. It locks less, it blocks less, and it traverses less data. Even with these tricks up its sleeve, it has fewer lines of code than most implementations. + +###### Calculating the Height of New Nodes + +When inserting, it calculates "height" directly instead of consecutive "coin tosses" to add levels. Additionally, it uses a local PRNG source that isn't blocked globally for improved concurrent insert performance. + +The probability of adding new nodes to each level of the structure (it's *height*) is determined by successively "rolling the dice" at each level until it doesn't meet a fixed value *P*. The default *P* values for skip lists in the wild range from 0.25 to 0.5. In this implementation, the default is *1/e*, which is optimal for a general-purpose skip list. To find the derivation of this number, see [Analysis of an optimized search algorithm for skip lists](http://www.sciencedirect.com/science/article/pii/030439759400296U) Kirschenhofer et al (1995). + +Almost all other implementations are using common functions in `math/rand`, which will block because querying the PRNG to determine the height of new nodes [waits then acquires a lock via the system-wide random number generator](http://blog.sgmansfield.com/2016/01/the-hidden-dangers-of-default-rand/). We get around this by assigning a new rand source to each skip list instantiated, so each skip list can only ever block itself. This significantly speeds up insert times when you are managing multiple lists with high concurrency. + +Additionally, this implementation always requests just one number from the PRNG. A pre-computed probability table is used to look up what the *height* of the new node will be. This is faster and offers a fixed calculation time compared to successive "dice rolls" for each level. The table is computed for each level *L* using the default *P* value of *1/e*: `math.Pow(1.0/math.E, L-1)`. It is consulted during inserts by querying for a random number in range [0.0,1.0) and finding the highest level in the table where the random number is less than or equal to the computed number. + +For example, let's say `math.Float64()` returned `r=0.029` and the table was pre-computed to contain (with a maximum height of 6): + +| height | probability | +| ------ | ----------- | +| 1 | 1.000000000 | +| 2 | 0.367879441 | +| 3 | 0.135335283 | +| 4 | 0.049787068 | +| 5 | 0.018315639 | +| 6 | 0.006737947 | + +So the height for the new node would be 5 because *p5 > r ≥ p6*, or 0.018315639 > 0.029 ≥ 0.006737947. + +I believe this fast new node height calculation to be novel and faster than any others with user-defined *P* values. [Ticki, for example, proposes an O(1) calculation](http://ticki.github.io/blog/skip-lists-done-right/) but it is fixed to *P=0.5* and I haven't encountered any other optimizations of this calculation. In local benchmarks, this optimization saves 10-25ns per insert. + +###### Better Cooperative Multitasking + +Why not a lock-free implementation? The overhead created is more than the time spent in contention of a locking version under normal loads. Most research on lock-free structures assume manual alloc/free as well and have separate compaction processes running that are unnecessary in Go (particularly with improved GC as of 1.8). The same is true for the newest variation, [the rotating skip list](http://poseidon.it.usyd.edu.au/~gramoli/web/doc/pubs/rotating-skiplist-preprint-2016.pdf), which claims to be the fastest to date for C/C++ and Java because the compared implementations have maintenance threads with increased overhead for memory management. + + +###### Caching and Search Fingers + +As Pugh described in [A Skip List Cookbook](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.17.524), search "fingers" can be retained after each lookup operation. When starting the next operation, the finger will point to where the last one occurred and afford the opportunity to pick up the search there instead of starting at the head of the list. This offers *O(log m)* search times, where *m* is the number of elements between the last lookup and the current one (*m* is always less than *n*). + +This implementation of a search finger does not suffer the usual problem of "climbing" up in levels when resuming search because it stores pointers to previous nodes for each level independently. + + + +### Benchmarks + +Speed is a feature! Below is a set of results demonstrating the flat performance (time per operation) as the list grows to millions of elements. Please see the [skiplist-survey](https://github.com/sean-public/skiplist-survey) repo for complete benchmark results from this and other Go skip list implementations. + +![benchmark results chart](http://i.imgur.com/VqUbsWr.png) + + + +### Todo + +- Build more complex test cases (specifically to prove correctness during high concurrency). +- Benchmark memory usage. +- Add "span" to each element to store the distance to the next node on every level. This gives each node a calculable index (ZRANK and associated commands in Redis). diff --git a/svc-match/fast-skiplist/skiplist.go b/svc-match/fast-skiplist/skiplist.go new file mode 100644 index 0000000..52c7d57 --- /dev/null +++ b/svc-match/fast-skiplist/skiplist.go @@ -0,0 +1,182 @@ +package skiplist + +import ( + "math" + "math/rand" + "time" + + "github.com/shopspring/decimal" +) + +const ( + DefaultMaxLevel int = 18 + DefaultProbability float64 = 1 / math.E +) + +func (list *SkipList) cmp(k1, k2 decimal.Decimal) bool { + if list.inOrder { + return k1.LessThanOrEqual(k2) + } + return k1.GreaterThanOrEqual(k2) +} + +// Front returns the head node of the list. +func (list *SkipList) Front() *Element { + return list.next[0] +} + +// Set inserts a value in the list with the specified key, ordered by the key. +// If the key exists, it updates the value in the existing node. +// Returns a pointer to the new element. +// Locking is optimistic and happens only after searching. +func (list *SkipList) Set(key decimal.Decimal, value interface{}) *Element { + list.mutex.Lock() + defer list.mutex.Unlock() + + var element *Element + prevs := list.getPrevElementNodes(key) + + if element = prevs[0].next[0]; element != nil && list.cmp(element.key, key) { + element.value = value + return element + } + + element = &Element{ + elementNode: elementNode{ + next: make([]*Element, list.randLevel()), + }, + key: key, + value: value, + } + + for i := range element.next { + element.next[i] = prevs[i].next[i] + prevs[i].next[i] = element + } + + list.Length++ + return element +} + +// Get finds an element by key. It returns element pointer if found, nil if not found. +// Locking is optimistic and happens only after searching with a fast check for deletion after locking. +func (list *SkipList) Get(key decimal.Decimal) *Element { + list.mutex.Lock() + defer list.mutex.Unlock() + + var prev *elementNode = &list.elementNode + var next *Element + + for i := list.maxLevel - 1; i >= 0; i-- { + next = prev.next[i] + + for next != nil && !list.cmp(key, next.key) { + prev = &next.elementNode + next = next.next[i] + } + } + + if next != nil && list.cmp(next.key, key) { + return next + } + + return nil +} + +// Remove deletes an element from the list. +// Returns removed element pointer if found, nil if not found. +// Locking is optimistic and happens only after searching with a fast check on adjacent nodes after locking. +func (list *SkipList) Remove(key decimal.Decimal) *Element { + list.mutex.Lock() + defer list.mutex.Unlock() + prevs := list.getPrevElementNodes(key) + + // found the element, remove it + if element := prevs[0].next[0]; element != nil && list.cmp(element.key, key) { + for k, v := range element.next { + prevs[k].next[k] = v + } + + list.Length-- + return element + } + + return nil +} + +// getPrevElementNodes is the private search mechanism that other functions use. +// Finds the previous nodes on each level relative to the current Element and +// caches them. This approach is similar to a "search finger" as described by Pugh: +// http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.17.524 +func (list *SkipList) getPrevElementNodes(key decimal.Decimal) []*elementNode { + var prev *elementNode = &list.elementNode + var next *Element + + prevs := list.prevNodesCache + + for i := list.maxLevel - 1; i >= 0; i-- { + next = prev.next[i] + + for next != nil && !list.cmp(key, next.key) { + prev = &next.elementNode + next = next.next[i] + } + + prevs[i] = prev + } + + return prevs +} + +// SetProbability changes the current P value of the list. +// It doesn't alter any existing data, only changes how future insert heights are calculated. +func (list *SkipList) SetProbability(newProbability float64) { + list.probability = newProbability + list.probTable = probabilityTable(list.probability, list.maxLevel) +} + +func (list *SkipList) randLevel() (level int) { + // Our random number source only has Int63(), so we have to produce a float64 from it + // Reference: https://golang.org/src/math/rand/rand.go#L150 + r := float64(list.randSource.Int63()) / (1 << 63) + + level = 1 + for level < list.maxLevel && r < list.probTable[level] { + level++ + } + return +} + +// probabilityTable calculates in advance the probability of a new node having a given level. +// probability is in [0, 1], MaxLevel is (0, 64] +// Returns a table of floating point probabilities that each level should be included during an insert. +func probabilityTable(probability float64, MaxLevel int) (table []float64) { + for i := 1; i <= MaxLevel; i++ { + prob := math.Pow(probability, float64(i-1)) + table = append(table, prob) + } + return table +} + +// NewWithMaxLevel creates a new skip list with MaxLevel set to the provided number. +// Returns a pointer to the new list. +func NewWithMaxLevel(maxLevel int, inOrder bool) *SkipList { + if maxLevel < 1 || maxLevel > 64 { + panic("maxLevel for a SkipList must be a positive integer <= 64") + } + + return &SkipList{ + elementNode: elementNode{next: make([]*Element, DefaultMaxLevel)}, + prevNodesCache: make([]*elementNode, DefaultMaxLevel), + maxLevel: maxLevel, + randSource: rand.New(rand.NewSource(time.Now().UnixNano())), + probability: DefaultProbability, + probTable: probabilityTable(DefaultProbability, DefaultMaxLevel), + inOrder: inOrder, + } +} + +// New creates a new skip list with default parameters. Returns a pointer to the new list. +func New(inOrder bool) *SkipList { + return NewWithMaxLevel(DefaultMaxLevel, inOrder) +} diff --git a/svc-match/fast-skiplist/skiplist_test.go b/svc-match/fast-skiplist/skiplist_test.go new file mode 100644 index 0000000..ae4e9b0 --- /dev/null +++ b/svc-match/fast-skiplist/skiplist_test.go @@ -0,0 +1,229 @@ +package skiplist + +import ( + "fmt" + "sync" + "testing" + "unsafe" + + "github.com/shopspring/decimal" +) + +var benchList *SkipList +var discard *Element + +func init() { + // Initialize a big SkipList for the Get() benchmark + benchList = New(true) + + for i := 0; i <= 10000000; i++ { + benchList.Set(decimal.NewFromFloat(float64(i)), [1]byte{}) + } + + // Display the sizes of our basic structs + var sl SkipList + var el Element + fmt.Printf("Structure sizes: SkipList is %v, Element is %v bytes\n", unsafe.Sizeof(sl), unsafe.Sizeof(el)) +} + +func checkSanity(list *SkipList, t *testing.T) { + // each level must be correctly ordered + for k, v := range list.next { + //t.Log("Level", k) + + if v == nil { + continue + } + + if k > len(v.next) { + t.Fatal("first node's level must be no less than current level") + } + + next := v + cnt := 1 + + for next.next[k] != nil { + if !(next.next[k].key.GreaterThanOrEqual(next.key)) { + t.Fatalf("next key value must be greater than prev key value. [next:%v] [prev:%v]", next.next[k].key, next.key) + } + + if k > len(next.next) { + t.Fatalf("node's level must be no less than current level. [cur:%v] [node:%v]", k, next.next) + } + + next = next.next[k] + cnt++ + } + + if k == 0 { + if cnt != list.Length { + t.Fatalf("list len must match the level 0 nodes count. [cur:%v] [level0:%v]", cnt, list.Length) + } + } + } +} + +func TestBasicIntCRUD(t *testing.T) { + var list *SkipList + + list = New(true) + + list.Set(decimal.NewFromFloat(10), 1) + list.Set(decimal.NewFromFloat(60), 2) + list.Set(decimal.NewFromFloat(30), 3) + list.Set(decimal.NewFromFloat(20), 4) + list.Set(decimal.NewFromFloat(90), 5) + checkSanity(list, t) + + list.Set(decimal.NewFromFloat(30), 9) + checkSanity(list, t) + + list.Remove(decimal.Zero) + list.Remove(decimal.NewFromFloat(20)) + checkSanity(list, t) + + v1 := list.Get(decimal.NewFromFloat(10)) + v2 := list.Get(decimal.NewFromFloat(60)) + v3 := list.Get(decimal.NewFromFloat(30)) + v4 := list.Get(decimal.NewFromFloat(20)) + v5 := list.Get(decimal.NewFromFloat(90)) + v6 := list.Get(decimal.NewFromFloat(0)) + + if v1 == nil || v1.value.(int) != 1 || !v1.key.Equal(decimal.NewFromFloat(10)) { + t.Fatal(`wrong "10" value (expected "1")`, v1) + } + + if v2 == nil || v2.value.(int) != 2 { + t.Fatal(`wrong "60" value (expected "2")`) + } + + if v3 == nil || v3.value.(int) != 9 { + t.Fatal(`wrong "30" value (expected "9")`) + } + + if v4 != nil { + t.Fatal(`found value for key "20", which should have been deleted`) + } + + if v5 == nil || v5.value.(int) != 5 { + t.Fatal(`wrong "90" value`) + } + + if v6 != nil { + t.Fatal(`found value for key "0", which should have been deleted`) + } +} + +func TestChangeLevel(t *testing.T) { + var i float64 + list := New(true) + + if list.maxLevel != DefaultMaxLevel { + t.Fatal("max level must equal default max value") + } + + list = NewWithMaxLevel(4, true) + if list.maxLevel != 4 { + t.Fatal("wrong maxLevel (wanted 4)", list.maxLevel) + } + + for i = 1; i <= 201; i++ { + list.Set(decimal.NewFromFloat(i), i*10) + } + + checkSanity(list, t) + + if list.Length != 201 { + t.Fatal("wrong list length", list.Length) + } + + for c := list.Front(); c != nil; c = c.Next() { + if !c.key.Mul(decimal.NewFromFloat(10)).Equal(decimal.NewFromFloat(c.value.(float64))) { + t.Fatal("wrong list element value") + } + } +} + +func TestChangeProbability(t *testing.T) { + list := New(true) + + if list.probability != DefaultProbability { + t.Fatal("new lists should have P value = DefaultProbability") + } + + list.SetProbability(0.5) + if list.probability != 0.5 { + t.Fatal("failed to set new list probability value: expected 0.5, got", list.probability) + } +} + +func TestConcurrency(t *testing.T) { + list := New(true) + + wg := &sync.WaitGroup{} + wg.Add(2) + go func() { + for i := 0; i < 100000; i++ { + list.Set(decimal.NewFromFloat(float64(i)), i) + } + wg.Done() + }() + + go func() { + for i := 0; i < 100000; i++ { + list.Get(decimal.NewFromFloat(float64(i))) + } + wg.Done() + }() + + wg.Wait() + if list.Length != 100000 { + t.Fail() + } +} + +func BenchmarkIncSet(b *testing.B) { + b.ReportAllocs() + list := New(true) + + for i := 0; i < b.N; i++ { + list.Set(decimal.NewFromFloat(float64(i)), [1]byte{}) + } + + b.SetBytes(int64(b.N)) +} + +func BenchmarkIncGet(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + res := benchList.Get(decimal.NewFromFloat(float64(i))) + if res == nil { + b.Fatal("failed to Get an element that should exist") + } + } + + b.SetBytes(int64(b.N)) +} + +func BenchmarkDecSet(b *testing.B) { + b.ReportAllocs() + list := New(true) + + for i := b.N; i > 0; i-- { + list.Set(decimal.NewFromFloat(float64(i)), [1]byte{}) + } + + b.SetBytes(int64(b.N)) +} + +func BenchmarkDecGet(b *testing.B) { + b.ReportAllocs() + for i := b.N; i > 0; i-- { + res := benchList.Get(decimal.NewFromFloat(float64(i))) + if res == nil { + b.Fatal("failed to Get an element that should exist", i) + } + } + + b.SetBytes(int64(b.N)) +} diff --git a/svc-match/fast-skiplist/type.go b/svc-match/fast-skiplist/type.go new file mode 100644 index 0000000..7639799 --- /dev/null +++ b/svc-match/fast-skiplist/type.go @@ -0,0 +1,46 @@ +package skiplist + +import ( + "math/rand" + "sync" + + "github.com/shopspring/decimal" +) + +type elementNode struct { + next []*Element +} + +type Element struct { + elementNode + key decimal.Decimal + value interface{} +} + +// Key allows retrieval of the key for a given Element +func (e *Element) Key() decimal.Decimal { + return e.key +} + +// Value allows retrieval of the value for a given Element +func (e *Element) Value() interface{} { + return e.value +} + +// Next returns the following Element or nil if we're at the end of the list. +// Only operates on the bottom level of the skip list (a fully linked list). +func (element *Element) Next() *Element { + return element.next[0] +} + +type SkipList struct { + elementNode + maxLevel int + Length int + randSource rand.Source + probability float64 + probTable []float64 + mutex sync.RWMutex + prevNodesCache []*elementNode + inOrder bool +} diff --git a/svc-match/history.go b/svc-match/history.go new file mode 100644 index 0000000..4014585 --- /dev/null +++ b/svc-match/history.go @@ -0,0 +1,154 @@ +package main + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/shopspring/decimal" + + "github.com/en/siren/utils" + log "github.com/en/siren/utils/glog" +) + +type HistoryType int32 + +const ( + HISTORY_TYPE_USER_BALANCE HistoryType = 0 + HISTORY_TYPE_USER_ORDER HistoryType = 1 + HISTORY_TYPE_USER_DEAL HistoryType = 2 + HISTORY_TYPE_ORDER_DETAIL HistoryType = 3 + HISTORY_TYPE_ORDER_DEAL HistoryType = 4 +) + +var history map[HistoryType]string + +func runCleanHistory() { + timer := time.After(1000 * time.Millisecond) + for { + select { + case <-timer: + timer = time.After(1000 * time.Millisecond) + if len(history) == 0 { + continue + } + for k, sql := range history { + fmt.Println("sql:", sql) + delete(history, k) + } + } + } +} + +func appendUserOrder(o *order) { + var ( + sql string + ok bool + ) + sql, ok = history[HISTORY_TYPE_USER_ORDER] + if ok { + sql = sql + ", " + } else { + sql = "INSERT INTO `order_history` (id, `create_time`, `finish_time`, `user`, `market`, `t`, `side`, `price`, `amount`, `taker_fee`, `maker_fee`, `deal_stock`, `deal_money`, `deal_fee`) VALUES " + } + + sql = sql + fmt.Sprintf("('%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", o.Id, utils.UnixMilli(o.CreatedAt), utils.UnixMilli(o.UpdatedAt), o.UserId, o.Symbol, o.Type, o.Side, o.Price, o.Amount, o.TakerFee, o.MakerFee, o.FilledAmount, o.ExecutedValue, o.FillFees) + history[HISTORY_TYPE_USER_ORDER] = sql +} + +func appendOrderDetail(o *order) { + var ( + sql string + ok bool + ) + sql, ok = history[HISTORY_TYPE_ORDER_DETAIL] + if ok { + sql = sql + ", " + } else { + sql = "INSERT INTO `order_detail` (`id`, `create_time`, `finish_time`, `user`, `market`, `t`, `side`, `price`, `amount`, `taker_fee`, `maker_fee`, `deal_stock`, `deal_money`, `deal_fee`) VALUES " + } + + sql = sql + fmt.Sprintf("('%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", o.Id, utils.UnixMilli(o.CreatedAt), utils.UnixMilli(o.UpdatedAt), o.UserId, o.Symbol, o.Type, o.Side, o.Price, o.Amount, o.TakerFee, o.MakerFee, o.FilledAmount, o.ExecutedValue, o.FillFees) + history[HISTORY_TYPE_ORDER_DETAIL] = sql +} + +func appendOrderDeal(t int64, userId string, dealId string, orderId string, dealOrderId string, role int32, price decimal.Decimal, amount decimal.Decimal, deal decimal.Decimal, fee decimal.Decimal, dealFee decimal.Decimal) { + var ( + sql string + ok bool + ) + sql, ok = history[HISTORY_TYPE_ORDER_DEAL] + if ok { + sql = sql + ", " + } else { + sql = "INSERT INTO `deal_history` (`id`, `time`, `user`, `deal_id`, `order_id`, `deal_order_id`, `role`, `price`, `amount`, `deal`, `fee`, `deal_fee`) VALUES " + } + + sql = sql + fmt.Sprintf("(NULL, %d, '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s')", t, userId, dealId, orderId, dealOrderId, role, price.String(), amount.String(), deal.String(), fee.String(), dealFee.String()) + history[HISTORY_TYPE_ORDER_DEAL] = sql +} + +func appendUserDeal(t int64, userId string, market string, dealId string, orderId string, dealOrderId string, side string, role int32, price decimal.Decimal, amount decimal.Decimal, deal decimal.Decimal, fee decimal.Decimal, dealFee decimal.Decimal) { + var ( + sql string + ok bool + ) + sql, ok = history[HISTORY_TYPE_USER_DEAL] + if ok { + sql = sql + ", " + } else { + sql = sql + "INSERT INTO `user_deal_history` (`id`, `time`, `user`, `market`, `deal_id`, `order_id`, `deal_order_id`, `side`, `role`, `price`, `amount`, `deal`, `fee`, `deal_fee`) VALUES " + } + + sql = sql + fmt.Sprintf("(NULL, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s')", t, userId, market, dealId, orderId, dealOrderId, side, role, price.String(), amount.String(), deal.String(), fee.String(), dealFee.String()) + history[HISTORY_TYPE_USER_DEAL] = sql +} + +func appendUserBalance(t int64, userId string, asset string, business string, change decimal.Decimal, balance decimal.Decimal, detail string) { + var ( + sql string + ok bool + ) + sql, ok = history[HISTORY_TYPE_USER_BALANCE] + if ok { + sql = sql + ", " + } else { + sql = sql + "INSERT INTO `balance_history` (`id`, `time`, `user`, `asset`, `business`, `change`, `balance`, `detail`) VALUES " + } + + sql = sql + fmt.Sprintf("(NULL, %d, '%s', '%s', '%s', '%s', '%s', '%s')", t, userId, asset, business, change.String(), balance.String(), detail) + history[HISTORY_TYPE_USER_BALANCE] = sql +} + +func appendOrderHistory(o *order) { + appendUserOrder(o) + appendOrderDetail(o) +} + +func appendOrderDealHistory(t int64, dealId string, ask *order, askRole int32, bid *order, bidRole int32, price decimal.Decimal, amount decimal.Decimal, deal decimal.Decimal, askFee decimal.Decimal, bidFee decimal.Decimal) { + appendOrderDeal(t, ask.UserId, dealId, ask.Id, bid.Id, askRole, price, amount, deal, askFee, bidFee) + appendOrderDeal(t, bid.UserId, dealId, bid.Id, ask.Id, bidRole, price, amount, deal, bidFee, askFee) + appendUserDeal(t, ask.UserId, ask.Symbol, dealId, ask.Id, bid.Id, ask.Side, askRole, price, amount, deal, askFee, bidFee) + appendUserDeal(t, bid.UserId, ask.Symbol, dealId, bid.Id, ask.Id, bid.Side, bidRole, price, amount, deal, bidFee, askFee) +} + +func appendUserBalanceHistory(t int64, userId string, asset string, business string, change decimal.Decimal, detail string) { + balance := balanceGet(asset, userId, ACCOUNT_TYPE_CASH) + appendUserBalance(t, userId, asset, business, change, balance.available.Add(balance.hold), detail) + if business == "Trade" { + key := fmt.Sprintf("%s:cash:%s", userId, asset) + k := Kb{ + RefId: "TODO:trade_id", + Type: "Trade", + Available: balance.available.String(), + Hold: balance.hold.String(), + } + b, err := json.Marshal(k) + if err != nil { + log.Error().Err(err).Msg("failed to marshal kafka balance message") + return + } + producer.ProduceChannel() <- &kafka.Message{TopicPartition: kafka.TopicPartition{Topic: &topicSirenBalances, Partition: kafka.PartitionAny}, Value: b, Key: []byte(key)} + } +} diff --git a/svc-match/main.go b/svc-match/main.go new file mode 100644 index 0000000..8f0b957 --- /dev/null +++ b/svc-match/main.go @@ -0,0 +1,604 @@ +package main + +import ( + "container/list" + "context" + "database/sql" + "encoding/json" + "fmt" + "net" + "strings" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/golang/protobuf/ptypes" + _ "github.com/lib/pq" + "github.com/shopspring/decimal" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + "github.com/en/siren/messaging" + pb "github.com/en/siren/protos" + skiplist "github.com/en/siren/svc-match/fast-skiplist" + log "github.com/en/siren/utils/glog" +) + +const ( + port = ":50051" +) + +var db *sql.DB + +type server struct{} + +func (s *server) GetCurrencies(ctx context.Context, in *pb.EmptyRequest) (*pb.GetCurrencyResponse, error) { + response := new(pb.GetCurrencyResponse) + for _, v := range currencies { + c := new(pb.Currency) + c.Id = v.id + c.Name = v.name + response.Currencies = append(response.Currencies, c) + } + return response, nil +} + +func (s *server) GetSymbols(ctx context.Context, in *pb.EmptyRequest) (*pb.GetSymbolsResponse, error) { + response := new(pb.GetSymbolsResponse) + for _, v := range currencyPairs { + cp := new(pb.Symbol) + cp.Id = v.id + cp.BaseCurrency = v.baseCurrency + cp.QuoteCurrency = v.quoteCurrency + cp.BaseMinAmount = v.baseMinAmount + cp.BaseIncrement = v.baseIncrement + cp.QuoteIncrement = v.quoteIncrement + response.Symbols = append(response.Symbols, cp) + } + return response, nil +} + +func (s *server) Balances(ctx context.Context, in *pb.BalancesRequest) (*pb.BalancesResponse, error) { + response := new(pb.BalancesResponse) + if in.Currency != "" { + assets.RLock() + u, ok := assets.users[in.UserId] + assets.RUnlock() + b := new(pb.Balance) + b.Currency = in.Currency + + if ok { + u.RLock() + account := u.cash[in.Currency] + u.RUnlock() + b.Balance = account.available.Add(account.hold).StringFixedBank(8) + b.Available = account.available.StringFixedBank(8) + b.Hold = account.hold.StringFixedBank(8) + } else { + b.Balance = decimal.Zero.StringFixedBank(8) + b.Available = decimal.Zero.StringFixedBank(8) + b.Hold = decimal.Zero.StringFixedBank(8) + } + response.Balances = append(response.Balances, b) + return response, nil + } + assets.RLock() + u, ok := assets.users[in.UserId] + assets.RUnlock() + + if ok { + u.RLock() + for c, a := range u.cash { + b := new(pb.Balance) + b.Currency = c + b.Balance = a.available.Add(a.hold).StringFixedBank(8) + b.Available = a.available.StringFixedBank(8) + b.Hold = a.hold.StringFixedBank(8) + response.Balances = append(response.Balances, b) + } + u.RUnlock() + } + return response, nil +} + +func (s *server) GetAssetSummary(ctx context.Context, in *pb.GetAssetSummaryRequest) (*pb.GetAssetSummaryResponse, error) { + response := new(pb.GetAssetSummaryResponse) + if len(in.Currencies) == 0 { + for k := range currencies { + a := new(pb.Asset) + a.Currency = k + status := balanceStatus(k, ACCOUNT_TYPE_CASH) + a.Total = status.total.String() + a.Available = status.available.String() + a.AvailableCount = status.availableCount + a.Hold = status.hold.String() + a.HoldCount = status.holdCount + response.Assets = append(response.Assets, a) + } + } else { + for _, id := range in.Currencies { + a := new(pb.Asset) + a.Currency = id + status := balanceStatus(id, ACCOUNT_TYPE_CASH) + a.Total = status.total.String() + a.Available = status.available.String() + a.AvailableCount = status.availableCount + a.Hold = status.hold.String() + a.HoldCount = status.holdCount + response.Assets = append(response.Assets, a) + } + } + return response, nil +} + +// TODO: recover +func (s *server) NewOrder(ctx context.Context, in *pb.NewOrderRequest) (*pb.NewOrderResponse, error) { + response := new(pb.NewOrderResponse) + response.Order = new(pb.Order) + market, ok := currencyPairs[in.Symbol] + if ok { + var order *order + if in.Type == "limit" { + order = marketPutLimitOrder(true, market, in.UserId, in.Side, in.Amount, in.Price) + // TODO: if not err + // appendOperlog("limit_order", []interface{}{in.UserId, in.Symbol, in.Side, in.Amount, in.Price, in.TakerFee, in.MakerFee}) + } else if in.Type == "market" { + order = marketPutMarketOrder(true, market, in.UserId, in.Side, in.Amount) + // TODO: if not err + // appendOperlog("market_order", []interface{}{in.UserId, in.Symbol, in.Side, in.Amount, in.TakerFee}) + } else { + // move to gateway + // TODO: error + } + if order != nil { + createdAt, err := ptypes.TimestampProto(order.CreatedAt) + if err != nil { + log.Error().Msg("timestamp convert failed") + return response, err + } + updatedAt, err := ptypes.TimestampProto(order.UpdatedAt) + if err != nil { + log.Error().Msg("timestamp convert failed") + return response, err + } + doneAt, err := ptypes.TimestampProto(order.DoneAt) + if err != nil { + log.Error().Msg("timestamp convert failed") + return response, err + } + + response.Order.Id = order.Id + response.Order.Price = order.Price.String() + response.Order.Amount = order.Amount.String() + response.Order.Symbol = order.Symbol + response.Order.Side = order.Side + response.Order.Type = order.Type + response.Order.CreatedAt = createdAt + response.Order.UpdatedAt = updatedAt + response.Order.DoneAt = doneAt + response.Order.DoneReason = order.DoneReason + response.Order.FillFees = order.FillFees.String() + response.Order.FilledAmount = order.FilledAmount.String() + response.Order.RemainingAmount = order.RemainingAmount.String() + response.Order.ExecutedValue = order.ExecutedValue.String() + response.Order.Status = order.Status + } + } + return response, nil +} + +func (s *server) Orders(ctx context.Context, in *pb.OrdersRequest) (*pb.OrdersResponse, error) { + response := new(pb.OrdersResponse) + market, ok := currencyPairs[in.Symbol] + if ok { + if orders, ok := market.ordersByUser[in.UserId]; ok { + for e := orders.Front(); e != nil; e = e.Next() { + id := e.Value.(string) + if o, ok := market.orders[id]; ok { + createdAt, err := ptypes.TimestampProto(o.CreatedAt) + if err != nil { + log.Error().Msg("timestamp convert failed") + return response, err + } + updatedAt, err := ptypes.TimestampProto(o.UpdatedAt) + if err != nil { + log.Error().Msg("timestamp convert failed") + return response, err + } + doneAt, err := ptypes.TimestampProto(o.DoneAt) + if err != nil { + log.Error().Msg("timestamp convert failed") + return response, err + } + order := new(pb.Order) + order.Id = id + order.Price = o.Price.String() + order.Amount = o.Amount.String() + order.Symbol = o.Symbol + order.Side = o.Side + order.Type = o.Type + order.CreatedAt = createdAt + order.UpdatedAt = updatedAt + order.DoneAt = doneAt + order.DoneReason = o.DoneReason + order.FillFees = o.FillFees.String() + order.FilledAmount = o.FilledAmount.String() + order.RemainingAmount = o.RemainingAmount.String() + order.ExecutedValue = o.ExecutedValue.String() + order.Status = o.Status + response.Orders = append(response.Orders, order) + } else { + log.Error().Str("order_id", id).Msg("order not found") + } + } + } + } + return response, nil +} + +func (s *server) OrderCancel(ctx context.Context, in *pb.OrderCancelRequest) (*pb.OrderCancelResponse, error) { + response := new(pb.OrderCancelResponse) + market, ok := currencyPairs[in.Symbol] + if ok { + order, ok := market.orders[in.OrderId] + if ok { + // TODO: check order.UserId == in.UserId + marketCancelOrder(true, market, order) + // response.Order.Id = order.Id + // response.Order.Type = order.Type + // response.Order.Side = order.Side + // response.Order.CreateTime = order.CreateTime + // response.Order.UpdateTime = order.UpdateTime + // response.Order.UserId = order.UserId + // response.Order.Market = order.Market + // response.Order.Price = order.Price.String() + // response.Order.Amount = order.Amount.String() + // response.Order.TakerFee = order.TakerFee.String() + // response.Order.MakerFee = order.MakerFee.String() + // response.Order.Left = order.Left.String() + // response.Order.DealStock = order.DealStock.String() + // response.Order.DealMoney = order.DealMoney.String() + // response.Order.DealFee = order.DealFee.String() + + // appendOperlog("cancel_order", []interface{}{in.UserId, in.Symbol, in.OrderId}) + } + } + return response, nil +} + +func (s *server) OrderBook(ctx context.Context, in *pb.OrderBookRequest) (*pb.OrderBookResponse, error) { + response := new(pb.OrderBookResponse) + market, ok := currencyPairs[in.Symbol] + if ok { + var e *skiplist.Element + if in.Side == 1 { + e = market.asks.Front() + } else { + e = market.bids.Front() + } + count := uint64(0) + for ; e != nil; e = e.Next() { + if count < in.Limit { + count = count + 1 + // o := e.Value().(*order) + o1 := new(pb.Order) + // o1.Id = o.Id + // o1.Type = o.Type + // o1.Side = o.Side + // o1.CreateTime = o.CreateTime + // o1.UpdateTime = o.UpdateTime + // o1.UserId = o.UserId + // o1.Market = o.Market + // o1.Price = o.Price.String() + // o1.Amount = o.Amount.String() + // o1.TakerFee = o.TakerFee.String() + // o1.MakerFee = o.MakerFee.String() + // o1.Left = o.Left.String() + // o1.DealStock = o.DealStock.String() + // o1.DealMoney = o.DealMoney.String() + // o1.DealFee = o.DealFee.String() + response.Orders = append(response.Orders, o1) + } + } + + } + return response, nil +} + +func (s *server) OrderBookDepth(ctx context.Context, in *pb.OrderBookDepthRequest) (*pb.OrderBookDepthResponse, error) { + response := new(pb.OrderBookDepthResponse) + market, ok := currencyPairs[in.Symbol] + if ok { + count := uint64(0) + for e := market.asks.Front(); e != nil; { + if count >= in.Limit { + break + } + count = count + 1 + o := e.Value().(*order) + data := new(pb.OrderBookData) + price := o.Price + quantity := o.RemainingAmount + for e = e.Next(); e != nil; e = e.Next() { + o1 := e.Value().(*order) + if o1.Price.Equal(price) { + quantity = quantity.Add(o1.RemainingAmount) + } else { + break + } + } + data.Price = price.String() + data.Quantity = quantity.String() + response.Asks = append(response.Asks, data) + } + + count = uint64(0) + for e := market.bids.Front(); e != nil; { + if count >= in.Limit { + break + } + count = count + 1 + o := e.Value().(*order) + data := new(pb.OrderBookData) + price := o.Price + quantity := o.RemainingAmount + for e = e.Next(); e != nil; e = e.Next() { + o1 := e.Value().(*order) + if o1.Price.Equal(price) { + quantity = quantity.Add(o1.RemainingAmount) + } else { + break + } + } + data.Price = price.String() + data.Quantity = quantity.String() + response.Asks = append(response.Asks, data) + } + } + return response, nil +} + +func (s *server) OrderDetail(ctx context.Context, in *pb.OrderDetailRequest) (*pb.OrderDetailResponse, error) { + response := new(pb.OrderDetailResponse) + market, ok := currencyPairs[in.Symbol] + if ok { + order, ok := market.orders[in.OrderId] + if ok { + response.Order.Id = order.Id + // response.Order.Type = order.Type + // response.Order.Side = order.Side + // response.Order.CreateTime = order.CreateTime + // response.Order.UpdateTime = order.UpdateTime + // response.Order.UserId = order.UserId + // response.Order.Market = order.Market + // response.Order.Price = order.Price.String() + // response.Order.Amount = order.Amount.String() + // response.Order.TakerFee = order.TakerFee.String() + // response.Order.MakerFee = order.MakerFee.String() + // response.Order.Left = order.Left.String() + // response.Order.DealStock = order.DealStock.String() + // response.Order.DealMoney = order.DealMoney.String() + // response.Order.DealFee = order.DealFee.String() + } + } + return response, nil +} + +func (s *server) GetMarketSummary(ctx context.Context, in *pb.GetMarketSummaryRequest) (*pb.GetMarketSummaryResponse, error) { + response := new(pb.GetMarketSummaryResponse) + if len(in.Symbols) == 0 { + for id, v := range currencyPairs { + m := new(pb.Market) + m.Symbol = id + // TODO: check this + m.AsksCount, m.AsksAmount, m.BidsCount, m.BidsAmount = marketGetStatus(v) + response.Markets = append(response.Markets, m) + } + } else { + for _, id := range in.Symbols { + m := new(pb.Market) + m.Symbol = id + if cp, ok := currencyPairs[id]; ok { + m.AsksCount, m.AsksAmount, m.BidsCount, m.BidsAmount = marketGetStatus(cp) + } + response.Markets = append(response.Markets, m) + } + } + return response, nil +} + +func (s *server) UpdateBalance(ctx context.Context, in *pb.UpdateBalanceRequest) (*pb.UpdateBalanceResponse, error) { + response := new(pb.UpdateBalanceResponse) + amount, err := decimal.NewFromString(in.Amount) + if err != nil { + log.Error().Str("amount", in.Amount).Msg("wrong decimal") + return response, nil + } + updateUserBalance(true, in.Id, in.Type, in.UserId, in.Currency, ACCOUNT_TYPE_CASH, amount) + // appendOperlog("update_balance", []interface{}{in.Id, in.Type, in.UserId, in.Currency, amount}) + key := fmt.Sprintf("%s:cash:%s", in.UserId, in.Currency) + balance := balanceGet(in.Currency, in.UserId, ACCOUNT_TYPE_CASH) + k := Kb{ + RefId: in.Id, + Type: "Transfer", + Available: balance.available.String(), + Hold: balance.hold.String(), + } + b, err := json.Marshal(k) + if err != nil { + log.Error().Err(err).Msg("failed to marshal kafka balance message") + } + producer.ProduceChannel() <- &kafka.Message{TopicPartition: kafka.TopicPartition{Topic: &topicSirenBalances, Partition: kafka.PartitionAny}, Value: b, Key: []byte(key)} + + transfer := messaging.TransferMessage{ + Id: in.Id, + UserId: in.UserId, + Currency: in.Currency, + Type: in.Type, + Amount: amount, + Status: "", + CreatedAt: time.Now(), + } + b, err = json.Marshal(transfer) + if err != nil { + log.Error().Err(err).Msg("failed to marshal kafka transfer message") + } + producer.ProduceChannel() <- &kafka.Message{TopicPartition: kafka.TopicPartition{Topic: &messaging.TopicSirenTransfers, Partition: kafka.PartitionAny}, Value: b} + return response, nil +} + +func loadLimitOrder(params []interface{}) { + userId := params[0].(string) + market := params[1].(string) + side := params[2].(string) + amount := params[3].(string) + price := params[4].(string) + takerFee := params[5].(string) + makerFee := params[6].(string) + fmt.Println("load_limit_order, user_id:", userId, "market:", market, "side:", side, "amount:", amount, "price:", price, "taker_fee:", takerFee, "maker_fee:", makerFee) + // source + m, ok := currencyPairs[market] + if ok { + marketPutLimitOrder(false, m, userId, side, amount, price) + } else { + log.Error().Str("currency_pair", market).Msg("market not exists") + } +} + +func loadMarketOrder(params []interface{}) { + userId := params[0].(string) + market := params[1].(string) + side := params[2].(string) + amount := params[3].(string) + takerFee := params[4].(string) + fmt.Println("load_market_order, user_id:", userId, "market:", market, "side:", side, "amount:", amount, "taker_fee:", takerFee) + // source + m, ok := currencyPairs[market] + if ok { + marketPutMarketOrder(false, m, userId, side, amount) + } else { + log.Error().Str("currency_pair", market).Msg("market not exists") + } +} + +func loadCancelOrder(params []interface{}) { + userId := params[0].(string) + market := params[1].(string) + orderId := params[2].(string) + fmt.Println("load_cancel_order, user_id:", userId, "market:", market, "order_id:", orderId) + m, ok := currencyPairs[market] + if ok { + order, ok := m.orders[orderId] + if ok { + marketCancelOrder(false, m, order) + } else { + log.Error().Str("order_id", orderId).Msg("order not found") + } + } else { + log.Error().Str("currency_pair", market).Msg("market not exists") + } +} + +func main() { + log.Info().Msg("Starting siren-match") + + viper.SetEnvPrefix("match") + viper.AutomaticEnv() + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + + pqInfo := fmt.Sprintf("user=%s password=%s host=%s port=5432 dbname=%s sslmode=disable", + viper.GetString("db-user"), + viper.GetString("db-password"), + viper.GetString("db-host"), + viper.GetString("db-dbname")) + var err error + db, err = sql.Open("postgres", pqInfo) + if err != nil { + log.Fatal().Msgf("failed to open database: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + if err := db.PingContext(ctx); err != nil { + log.Fatal().Msgf("failed to ping: %v", err) + } + + // init_balance + rows, err := db.QueryContext(ctx, "SELECT id, name, min_size FROM currencies") + if err != nil { + log.Fatal().Err(err).Msg("") + } + defer rows.Close() + + assets = asset{users: make(map[string]*user)} + currencies = make(map[string]currency) + for rows.Next() { + var c currency + if err := rows.Scan(&c.id, &c.name, &c.minSize); err != nil { + log.Fatal().Err(err).Msg("") + } + symbol := strings.ToUpper(c.id) + currencies[symbol] = c + } + if err := rows.Err(); err != nil { + log.Fatal().Err(err).Msg("") + } + fmt.Println(currencies) + + // init_trade + currencyPairs = make(map[string]*currencyPair) + rows, err = db.QueryContext(ctx, "SELECT id, base_currency, quote_currency, base_min_size, base_max_size, base_increment, quote_increment FROM symbols") + if err != nil { + log.Fatal().Err(err).Msg("") + } + defer rows.Close() + + for rows.Next() { + cp := new(currencyPair) + if err := rows.Scan(&cp.id, &cp.baseCurrency, &cp.quoteCurrency, &cp.baseMinAmount, &cp.baseMaxAmount, &cp.baseIncrement, &cp.quoteIncrement); err != nil { + log.Fatal().Err(err).Msg("") + } + + cp.asks = skiplist.New(true) + cp.bids = skiplist.New(false) + cp.orders = make(map[string]*order) + cp.ordersByUser = make(map[string]*list.List) + cp.takerFee, _ = decimal.NewFromString("0.002") + cp.makerFee, _ = decimal.NewFromString("0.002") + currencyPairs[cp.id] = cp + } + if err := rows.Err(); err != nil { + log.Fatal().Err(err).Msg("") + } + fmt.Println(currencyPairs) + + // init_update + + // init_from_db + loadFromKafka() + // init_from_db end + + // init_operlog + + // init_history + history = make(map[HistoryType]string) + go runCleanHistory() + // init_message + initMessage() + defer producer.Close() + // init_persist + // init_cli + // init_server + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatal().Err(err).Msg("failed to listen") + } + s := grpc.NewServer() + // TODO + pb.RegisterMatchServer(s, &server{}) + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatal().Msg("failed to serve") + } +} diff --git a/svc-match/market.go b/svc-match/market.go new file mode 100644 index 0000000..2a6ea01 --- /dev/null +++ b/svc-match/market.go @@ -0,0 +1,811 @@ +package main + +import ( + "container/list" + "encoding/json" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/gofrs/uuid" + "github.com/shopspring/decimal" + + skiplist "github.com/en/siren/svc-match/fast-skiplist" + "github.com/en/siren/utils" + log "github.com/en/siren/utils/glog" +) + +const ( + MARKET_ORDER_SIDE_ASK = 1 + MARKET_ORDER_SIDE_BID = 2 + + MARKET_ROLE_MAKER = 1 + MARKET_ROLE_TAKER = 2 +) + +type order struct { + UserId string `json:"user_id"` + + Freeze decimal.Decimal `json:"freeze"` + + Id string `json:"id"` + Price decimal.Decimal `json:"price"` + Amount decimal.Decimal `json:"amount"` + Symbol string `json:"symbol"` + Side string `json:"side"` + Type string `json:"type"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DoneAt time.Time `json:"done_at"` + DoneReason string `json:"done_reason"` + FillFees decimal.Decimal `json:"fill_fees"` + FilledAmount decimal.Decimal `json:"filled_amount"` + RemainingAmount decimal.Decimal `json:"remaining_amount"` + ExecutedValue decimal.Decimal `json:"executed_value"` + Status string `json:"status"` + + TakerFee decimal.Decimal `json:"taker_fee"` + MakerFee decimal.Decimal `json:"maker_fee"` +} + +type currencyPair struct { + id string `json:"id"` + baseCurrency string `json:"base_currency"` + quoteCurrency string `json:"quote_currency"` + baseMinAmount string `json:"base_min_amount"` + baseMaxAmount string `json:"base_min_amount"` + baseIncrement string `json:"base_Increment"` + quoteIncrement string `json:"quote_increment"` + + takerFee decimal.Decimal + makerFee decimal.Decimal + + asks *skiplist.SkipList + bids *skiplist.SkipList + + // order_id -> *order + orders map[string]*order + // user_id -> list of order_id + ordersByUser map[string]*list.List +} + +var currencyPairs map[string]*currencyPair + +type HistoryDetail struct { + M string `json:"m"` + I string `json:"i"` + P string `json:"p"` + A string `json:"a"` +} + +type ubh struct { + Detail HistoryDetail `json:"detail"` +} + +func appendBalanceTradeAdd(o *order, asset string, change decimal.Decimal, price decimal.Decimal, amount decimal.Decimal) { + detail := ubh{ + Detail: HistoryDetail{ + M: o.Symbol, + I: o.Id, + P: price.String(), + A: amount.String(), + }, + } + b, err := json.Marshal(detail) + if err != nil { + log.Error().Err(err).Msg("failed to marshal balance trade add message") + return + } + + appendUserBalanceHistory(utils.UnixMilli(o.UpdatedAt), o.UserId, asset, "Trade", change, string(b)) +} + +func appendBalanceTradeSub(o *order, asset string, change decimal.Decimal, price decimal.Decimal, amount decimal.Decimal) { + detail := ubh{ + Detail: HistoryDetail{ + M: o.Symbol, + I: o.Id, + P: price.String(), + A: amount.String(), + }, + } + b, err := json.Marshal(detail) + if err != nil { + log.Error().Err(err).Msg("failed to marshal balance trade sub message") + return + } + + appendUserBalanceHistory(utils.UnixMilli(o.UpdatedAt), o.UserId, asset, "Trade", change.Neg(), string(b)) +} + +func appendBalanceTradeFee(o *order, asset string, change decimal.Decimal, price decimal.Decimal, amount decimal.Decimal, feeRate decimal.Decimal) { + type HistoryDetailWithFee struct { + M string `json:"m"` + I string `json:"i"` + P string `json:"p"` + A string `json:"a"` + F string `json:"f"` + } + + type ubh struct { + Detail HistoryDetailWithFee `json:"detail"` + } + + detail := ubh{ + Detail: HistoryDetailWithFee{ + M: o.Symbol, + I: o.Id, + P: price.String(), + A: amount.String(), + F: feeRate.String(), + }, + } + b, err := json.Marshal(detail) + if err != nil { + log.Error().Err(err).Msg("failed to marshal balance trade fee message") + return + } + appendUserBalanceHistory(utils.UnixMilli(o.UpdatedAt), o.UserId, asset, "Trade", change.Neg(), string(b)) +} + +func executeLimitAskOrder(real bool, m *currencyPair, taker *order) { + var ( + price decimal.Decimal + amount decimal.Decimal + executedValue decimal.Decimal + askFee decimal.Decimal + bidFee decimal.Decimal + ) + + for e := m.bids.Front(); e != nil; e = e.Next() { + if taker.RemainingAmount.IsZero() { + break + } + maker := e.Value().(*order) + if taker.Price.GreaterThan(maker.Price) { + break + } + + price = maker.Price + if taker.RemainingAmount.LessThan(maker.RemainingAmount) { + amount = taker.RemainingAmount + } else { + amount = maker.RemainingAmount + } + + executedValue = price.Mul(amount) + askFee = executedValue.Mul(taker.TakerFee) + bidFee = amount.Mul(maker.MakerFee) + + now := time.Now() + taker.UpdatedAt = now + maker.UpdatedAt = now + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + break + } + dealId := id.String() + if real { + appendOrderDealHistory(utils.UnixMilli(taker.UpdatedAt), dealId, taker, MARKET_ROLE_TAKER, maker, MARKET_ROLE_MAKER, price, amount, executedValue, askFee, bidFee) + pushDealMessage(utils.UnixMilli(taker.UpdatedAt), m.id, taker, maker, price, amount, askFee, bidFee, MARKET_ORDER_SIDE_ASK, dealId, m.baseCurrency, m.quoteCurrency) + } + + taker.RemainingAmount = taker.RemainingAmount.Sub(amount) + taker.FilledAmount = taker.FilledAmount.Add(amount) + taker.ExecutedValue = taker.ExecutedValue.Add(executedValue) + taker.FillFees = taker.FillFees.Add(askFee) + + balanceSub(m.baseCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + if real { + appendBalanceTradeSub(taker, m.baseCurrency, amount, price, amount) + } + balanceAdd(m.quoteCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, executedValue) + if real { + appendBalanceTradeAdd(taker, m.quoteCurrency, executedValue, price, amount) + } + + if askFee.IsPositive() { + balanceSub(m.quoteCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, askFee) + if real { + appendBalanceTradeFee(taker, m.quoteCurrency, askFee, price, amount, taker.TakerFee) + } + } + + maker.RemainingAmount = maker.RemainingAmount.Sub(amount) + maker.Freeze = maker.Freeze.Sub(executedValue) + maker.FilledAmount = maker.FilledAmount.Add(amount) + maker.ExecutedValue = maker.ExecutedValue.Add(executedValue) + maker.FillFees = maker.FillFees.Add(bidFee) + + balanceSub(m.quoteCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_HOLD, executedValue) + if real { + appendBalanceTradeSub(maker, m.quoteCurrency, executedValue, price, amount) + } + balanceAdd(m.baseCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + if real { + appendBalanceTradeAdd(maker, m.baseCurrency, amount, price, amount) + } + + if bidFee.IsPositive() { + balanceSub(m.baseCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, bidFee) + if real { + appendBalanceTradeFee(maker, m.baseCurrency, bidFee, price, amount, maker.MakerFee) + } + } + + if maker.RemainingAmount.IsZero() { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_FINISH, maker, m.baseCurrency, m.quoteCurrency) + } + orderFinish(real, m, maker) + } else { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_UPDATE, maker, m.baseCurrency, m.quoteCurrency) + } + } + } +} + +func executeLimitBidOrder(real bool, m *currencyPair, taker *order) { + var ( + price decimal.Decimal + amount decimal.Decimal + executedValue decimal.Decimal + askFee decimal.Decimal + bidFee decimal.Decimal + ) + + for e := m.asks.Front(); e != nil; e = e.Next() { + if taker.RemainingAmount.IsZero() { + break + } + + maker := e.Value().(*order) + if taker.Price.LessThan(maker.Price) { + break + } + + price = maker.Price + if taker.RemainingAmount.LessThan(maker.RemainingAmount) { + amount = taker.RemainingAmount + } else { + amount = maker.RemainingAmount + } + + executedValue = price.Mul(amount) + askFee = executedValue.Mul(maker.MakerFee) + bidFee = amount.Mul(taker.TakerFee) + + now := time.Now() + taker.UpdatedAt = now + maker.UpdatedAt = now + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + break + } + dealId := id.String() + if real { + appendOrderDealHistory(utils.UnixMilli(taker.UpdatedAt), dealId, maker, MARKET_ROLE_MAKER, taker, MARKET_ROLE_TAKER, price, amount, executedValue, askFee, bidFee) + pushDealMessage(utils.UnixMilli(taker.UpdatedAt), m.id, maker, taker, price, amount, askFee, bidFee, MARKET_ORDER_SIDE_BID, dealId, m.baseCurrency, m.quoteCurrency) + } + + taker.RemainingAmount = taker.RemainingAmount.Sub(amount) + taker.FilledAmount = taker.FilledAmount.Add(amount) + taker.ExecutedValue = taker.ExecutedValue.Add(executedValue) + taker.FillFees = taker.FillFees.Add(bidFee) + + balanceSub(m.quoteCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, executedValue) + if real { + appendBalanceTradeSub(taker, m.quoteCurrency, executedValue, price, amount) + } + balanceAdd(m.baseCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + if real { + appendBalanceTradeAdd(taker, m.baseCurrency, amount, price, amount) + } + + if bidFee.IsPositive() { + balanceSub(m.baseCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, bidFee) + if real { + appendBalanceTradeFee(taker, m.baseCurrency, bidFee, price, amount, taker.TakerFee) + } + } + + maker.RemainingAmount = maker.RemainingAmount.Sub(amount) + maker.Freeze = maker.Freeze.Sub(amount) + maker.FilledAmount = maker.FilledAmount.Add(amount) + maker.ExecutedValue = maker.ExecutedValue.Add(executedValue) + maker.FillFees = maker.FillFees.Add(askFee) + + balanceSub(m.baseCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_HOLD, amount) + if real { + appendBalanceTradeSub(maker, m.baseCurrency, amount, price, amount) + } + balanceAdd(m.quoteCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, executedValue) + if real { + appendBalanceTradeAdd(maker, m.quoteCurrency, executedValue, price, amount) + } + + if askFee.IsPositive() { + balanceSub(m.quoteCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, askFee) + if real { + appendBalanceTradeFee(maker, m.quoteCurrency, askFee, price, amount, maker.MakerFee) + } + } + + if maker.RemainingAmount.IsZero() { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_FINISH, maker, m.baseCurrency, m.quoteCurrency) + } + orderFinish(real, m, maker) + } else { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_UPDATE, maker, m.baseCurrency, m.quoteCurrency) + } + } + } +} + +func marketPutLimitOrder(real bool, m *currencyPair, userId string, side string, _amount string, _price string) *order { + amount, err := decimal.NewFromString(_amount) + if err != nil { + log.Error().Str("amount", _amount).Msg("wrong decimal") + return nil + } + price, err := decimal.NewFromString(_price) + if err != nil { + log.Error().Str("price", _price).Msg("wrong decimal") + return nil + } + if side == "sell" { + balance := balanceGet(m.baseCurrency, userId, ACCOUNT_TYPE_CASH).available + if balance.LessThan(amount) { + log.Debug().Str("avaiable", balance.String()).Str("require", _amount).Msg("not enough balance") + // TODO: handle error + return nil + } + } else { // "buy" + balance := balanceGet(m.quoteCurrency, userId, ACCOUNT_TYPE_CASH).available + require := amount.Mul(price) + if balance.LessThan(require) { + log.Debug().Str("avaiable", balance.String()).Str("require", require.String()).Msg("not enough balance") + return nil + } + } + // TODO move this check to gateway + // if amount < m.fMinAmount { + // return "" + // } + + now := time.Now() + o := new(order) + o.UserId = userId + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + return nil + } + o.Id = id.String() + o.Price = price + o.Amount = amount + o.Symbol = m.id + o.Side = side + o.Type = "limit" + o.CreatedAt = now + o.UpdatedAt = now + o.FillFees = decimal.Zero + o.FilledAmount = decimal.Zero + o.RemainingAmount = amount + o.ExecutedValue = decimal.Zero + o.TakerFee = m.takerFee + o.MakerFee = m.makerFee + + o.Freeze = decimal.Zero + + if side == "sell" { + executeLimitAskOrder(real, m, o) + } else { // "buy" + executeLimitBidOrder(real, m, o) + } + // TODO: handle error + if o.RemainingAmount.IsZero() { + if real { + appendOrderHistory(o) + pushOrderMessage(ORDER_EVENT_TYPE_FINISH, o, m.baseCurrency, m.quoteCurrency) + } + } else { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_PUT, o, m.baseCurrency, m.quoteCurrency) + } + orderPut(true, m, o) + } + return o +} + +func executeMarketAskOrder(real bool, m *currencyPair, taker *order) { + var ( + price decimal.Decimal + amount decimal.Decimal + executedValue decimal.Decimal + askFee decimal.Decimal + bidFee decimal.Decimal + ) + + for e := m.bids.Front(); e != nil; e = e.Next() { + if taker.RemainingAmount.IsZero() { + break + } + + maker := e.Value().(*order) + price = maker.Price + if taker.RemainingAmount.LessThan(maker.RemainingAmount) { + amount = taker.RemainingAmount + } else { + amount = maker.RemainingAmount + } + + executedValue = price.Mul(amount) + askFee = executedValue.Mul(taker.TakerFee) + bidFee = amount.Mul(maker.MakerFee) + + now := time.Now() + taker.UpdatedAt = now + maker.UpdatedAt = now + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + break + } + dealId := id.String() + if real { + appendOrderDealHistory(utils.UnixMilli(taker.UpdatedAt), dealId, taker, MARKET_ROLE_TAKER, maker, MARKET_ROLE_MAKER, price, amount, executedValue, askFee, bidFee) + pushDealMessage(utils.UnixMilli(taker.UpdatedAt), m.id, taker, maker, price, amount, askFee, bidFee, MARKET_ORDER_SIDE_ASK, dealId, m.baseCurrency, m.quoteCurrency) + } + + taker.RemainingAmount = taker.RemainingAmount.Sub(amount) + taker.FilledAmount = taker.FilledAmount.Add(amount) + taker.ExecutedValue = taker.ExecutedValue.Add(executedValue) + taker.FillFees = taker.FillFees.Add(askFee) + + balanceSub(m.baseCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + if real { + appendBalanceTradeSub(taker, m.baseCurrency, amount, price, amount) + } + balanceAdd(m.quoteCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, executedValue) + if real { + appendBalanceTradeAdd(taker, m.quoteCurrency, executedValue, price, amount) + } + + if askFee.IsPositive() { + balanceSub(m.quoteCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, askFee) + if real { + appendBalanceTradeFee(taker, m.quoteCurrency, askFee, price, amount, taker.TakerFee) + } + } + + maker.RemainingAmount = maker.RemainingAmount.Sub(amount) + maker.Freeze = maker.Freeze.Sub(executedValue) + maker.FilledAmount = maker.FilledAmount.Add(amount) + maker.ExecutedValue = maker.ExecutedValue.Add(executedValue) + maker.FillFees = maker.FillFees.Add(bidFee) + + balanceSub(m.quoteCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_HOLD, executedValue) + if real { + appendBalanceTradeSub(maker, m.quoteCurrency, executedValue, price, amount) + } + balanceAdd(m.baseCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + if real { + appendBalanceTradeAdd(maker, m.baseCurrency, amount, price, amount) + } + + if bidFee.IsPositive() { + balanceSub(m.baseCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, bidFee) + if real { + appendBalanceTradeFee(maker, m.baseCurrency, bidFee, price, amount, maker.MakerFee) + } + } + + if maker.RemainingAmount.IsZero() { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_FINISH, maker, m.baseCurrency, m.quoteCurrency) + } + orderFinish(real, m, maker) + } else { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_UPDATE, maker, m.baseCurrency, m.quoteCurrency) + } + } + } +} + +func executeMarketBidOrder(real bool, m *currencyPair, taker *order) { + var ( + price decimal.Decimal + amount decimal.Decimal + executedValue decimal.Decimal + askFee decimal.Decimal + bidFee decimal.Decimal + result decimal.Decimal + ) + + for e := m.asks.Front(); e != nil; e = e.Next() { + if taker.RemainingAmount.IsZero() { + break + } + + maker := e.Value().(*order) + price = maker.Price + // TODO: check this, maybe DivRound + amount = taker.RemainingAmount.Div(price) + for { + result = amount.Mul(price) + if result.GreaterThan(taker.RemainingAmount) { + // TODO + result, _ = decimal.NewFromString("0.00000001") + amount = amount.Sub(result) + } else { + break + } + } + + if amount.GreaterThan(maker.RemainingAmount) { + amount = maker.RemainingAmount + } + if amount.IsZero() { + break + } + + executedValue = price.Mul(amount) + askFee = executedValue.Mul(maker.MakerFee) + bidFee = amount.Mul(taker.TakerFee) + + now := time.Now() + taker.UpdatedAt = now + maker.UpdatedAt = now + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + break + } + dealId := id.String() + if real { + appendOrderDealHistory(utils.UnixMilli(taker.UpdatedAt), dealId, maker, MARKET_ROLE_MAKER, taker, MARKET_ROLE_TAKER, price, amount, executedValue, askFee, bidFee) + pushDealMessage(utils.UnixMilli(taker.UpdatedAt), m.id, maker, taker, price, amount, askFee, bidFee, MARKET_ORDER_SIDE_BID, dealId, m.baseCurrency, m.quoteCurrency) + } + + taker.RemainingAmount = taker.RemainingAmount.Sub(amount) // TODO: should be amount? + taker.FilledAmount = taker.FilledAmount.Add(amount) + taker.ExecutedValue = taker.ExecutedValue.Add(executedValue) + taker.FillFees = taker.FillFees.Add(bidFee) + + balanceSub(m.quoteCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, executedValue) + if real { + appendBalanceTradeSub(taker, m.quoteCurrency, executedValue, price, amount) + } + balanceAdd(m.baseCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, amount) + if real { + appendBalanceTradeAdd(taker, m.baseCurrency, amount, price, amount) + } + + if bidFee.IsPositive() { + balanceSub(m.baseCurrency, taker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, bidFee) + if real { + appendBalanceTradeFee(taker, m.baseCurrency, bidFee, price, amount, taker.TakerFee) + } + } + + maker.RemainingAmount = maker.RemainingAmount.Sub(amount) + maker.Freeze = maker.Freeze.Sub(amount) + maker.FilledAmount = maker.FilledAmount.Add(amount) + maker.ExecutedValue = maker.ExecutedValue.Add(executedValue) + maker.FillFees = maker.FillFees.Add(askFee) + + balanceSub(m.baseCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_HOLD, amount) + if real { + appendBalanceTradeSub(maker, m.baseCurrency, amount, price, amount) + } + balanceAdd(m.quoteCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, executedValue) + if real { + appendBalanceTradeAdd(maker, m.quoteCurrency, executedValue, price, amount) + } + + if askFee.IsPositive() { + balanceSub(m.quoteCurrency, maker.UserId, ACCOUNT_TYPE_CASH, BALANCE_TYPE_AVAILABLE, askFee) + if real { + appendBalanceTradeFee(maker, m.quoteCurrency, askFee, price, amount, maker.MakerFee) + } + } + + if maker.RemainingAmount.IsZero() { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_FINISH, maker, m.baseCurrency, m.quoteCurrency) + } + orderFinish(real, m, maker) + } else { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_UPDATE, maker, m.baseCurrency, m.quoteCurrency) + } + } + } +} + +func marketPutMarketOrder(real bool, m *currencyPair, userId string, side string, _amount string) *order { + amount, err := decimal.NewFromString(_amount) + if err != nil { + log.Error().Str("amount", _amount).Msg("wrong decimal") + return nil + } + if side == "sell" { + balance := balanceGet(m.baseCurrency, userId, ACCOUNT_TYPE_CASH).available + if balance.LessThan(amount) { + // TODO: handle error + return nil + } + e := m.bids.Front() + if e == nil { + return nil + } + // if amount < m.fMinAmount { + // return "" + // } + } else { + balance := balanceGet(m.quoteCurrency, userId, ACCOUNT_TYPE_CASH).available + if balance.LessThan(amount) { + return nil + } + e := m.asks.Front() + if e == nil { + return nil + } + o := e.Value().(*order) + // TODO + require := o.Price.Mul(decimal.NewFromFloat(0.001)) + if amount.LessThan(require) { + return nil + } + } + + now := time.Now() + o := new(order) + o.UserId = userId + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + return nil + } + o.Id = id.String() + o.Price = decimal.Zero + o.Amount = amount + o.Symbol = m.id + o.Side = side + o.Type = "market" + o.CreatedAt = now + o.UpdatedAt = now + o.FillFees = decimal.Zero + o.FilledAmount = decimal.Zero + o.RemainingAmount = amount + o.ExecutedValue = decimal.Zero + + o.TakerFee = m.takerFee + o.MakerFee = decimal.Zero // TODO + + o.Freeze = decimal.Zero + + if side == "sell" { + executeMarketAskOrder(real, m, o) + } else { // "buy" + executeMarketBidOrder(real, m, o) + } + // TODO: handle error + if real { + appendOrderHistory(o) + pushOrderMessage(ORDER_EVENT_TYPE_FINISH, o, m.baseCurrency, m.quoteCurrency) + } + return o +} + +func marketCancelOrder(real bool, m *currencyPair, o *order) *order { + if real { + pushOrderMessage(ORDER_EVENT_TYPE_FINISH, o, m.baseCurrency, m.quoteCurrency) + } + orderFinish(real, m, o) + // TODO: remove return value? + return o +} + +func marketGetStatus(m *currencyPair) (int64, string, int64, string) { + askCount := int64(m.asks.Length) + bidCount := int64(m.bids.Length) + askAmount := decimal.Zero + bidAmount := decimal.Zero + for e := m.asks.Front(); e != nil; e = e.Next() { + o := e.Value().(*order) + askAmount = askAmount.Add(o.RemainingAmount) + } + + for e := m.bids.Front(); e != nil; e = e.Next() { + o := e.Value().(*order) + bidAmount = bidAmount.Add(o.RemainingAmount) + } + return askCount, askAmount.String(), bidCount, bidAmount.String() +} + +func orderPut(real bool, m *currencyPair, o *order) { + if o.Type != "limit" { + // TODO: log error + log.Error().Str("type", o.Type).Msg("order type error") + return + } + + // TODO: check dup key + m.orders[o.Id] = o + + _, ok := m.ordersByUser[o.UserId] + if !ok { + m.ordersByUser[o.UserId] = list.New() + } + m.ordersByUser[o.UserId].PushFront(o.Id) + + if o.Side == "sell" { + m.asks.Set(o.Price, o) + o.Freeze = o.RemainingAmount + // TODO: handle error + balanceFreeze(m.baseCurrency, o.UserId, ACCOUNT_TYPE_CASH, o.RemainingAmount) + } else { + m.bids.Set(o.Price, o) + result := o.Price.Mul(o.RemainingAmount) + o.Freeze = result + balanceFreeze(m.quoteCurrency, o.UserId, ACCOUNT_TYPE_CASH, result) + } + b, err := json.Marshal(o) + if err != nil { + log.Error().Err(err).Msg("failed to marshal kafka order message") + return + } + if real { + producer.ProduceChannel() <- &kafka.Message{TopicPartition: kafka.TopicPartition{Topic: &topicSirenOrders, Partition: kafka.PartitionAny}, Value: b, Key: []byte(o.Id)} + } +} + +// TODO: send balance to kafka? +func orderFinish(real bool, m *currencyPair, o *order) { + if o.Side == "sell" { + e := m.asks.Remove(o.Price) + // check + node := e.Value().(*order) + if node != o { + log.Print("order not match") + } + if o.Freeze.IsPositive() { + balanceUnfreeze(m.baseCurrency, o.UserId, ACCOUNT_TYPE_CASH, o.Freeze) + } + } else { + e := m.bids.Remove(o.Price) + // check + node := e.Value().(*order) + if node != o { + log.Print("order not match") + } + if o.Freeze.IsPositive() { + balanceUnfreeze(m.quoteCurrency, o.UserId, ACCOUNT_TYPE_CASH, o.Freeze) + } + } + + delete(m.orders, o.Id) + if orders, ok := m.ordersByUser[o.UserId]; ok { + for e := orders.Front(); e != nil; e = e.Next() { + if e.Value == o.Id { + orders.Remove(e) + log.Info().Str("order_id", o.Id).Msg("order finished, remove") + break + } + } + } + + if real { + if o.FilledAmount.IsPositive() { + appendOrderHistory(o) + // TODO: handle error + } + } +} diff --git a/svc-match/message.go b/svc-match/message.go new file mode 100644 index 0000000..9d51a4f --- /dev/null +++ b/svc-match/message.go @@ -0,0 +1,316 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/davecgh/go-spew/spew" + "github.com/shopspring/decimal" + + log "github.com/en/siren/utils/glog" +) + +var ( + topicBalances = "balances" + topicOrders = "orders" + topicDeals = "deals" + topicSirenBalances = "siren-balances-v1" + topicSirenOrders = "siren-orders-v1" +) + +type OrderEventType uint32 + +const ( + ORDER_EVENT_TYPE_PUT OrderEventType = 1 + ORDER_EVENT_TYPE_UPDATE OrderEventType = 2 + ORDER_EVENT_TYPE_FINISH OrderEventType = 3 +) + +type Kb struct { + RefId string `json:"ref_id"` + Type string `json:"type"` + Available string `json:"available"` + Hold string `json:"hold"` +} + +var producer *kafka.Producer + +func loadFromKafka() { + consumer, err := kafka.NewConsumer(&kafka.ConfigMap{ + "bootstrap.servers": "REMOVED.us-central1.gcp.confluent.cloud:9092", + "api.version.request": true, + "broker.version.fallback": "0.10.0.0", + "api.version.fallback.ms": 0, + "sasl.mechanisms": "PLAIN", + "security.protocol": "SASL_SSL", + "ssl.ca.location": "/etc/ssl/cert.pem", + "sasl.username": "REMOVED", + "sasl.password": "REMOVED", + "group.id": "siren-match", + "session.timeout.ms": 6000, + "enable.partition.eof": true, + "auto.offset.reset": "earliest"}) + + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create consumer: %s\n", err) + os.Exit(1) + } + + log.Info().Msgf("Created Consumer %v\n", consumer) + + log.Info().Msg("Loading balances") + err = consumer.SubscribeTopics([]string{topicSirenBalances}, func(c *kafka.Consumer, event kafka.Event) error { + log.Info().Msgf("Rebalanced: %s", event) + switch e := event.(type) { + case kafka.AssignedPartitions: + for i := range e.Partitions { + log.Debug().Str("partition", spew.Sdump(e.Partitions[i])).Msg("reset offset of partition") + e.Partitions[i].Offset = kafka.OffsetBeginning + } + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Assign(e.Partitions) + case kafka.RevokedPartitions: + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Unassign() + } + return nil + }) + run := true + + balances := make(map[string][]byte) + + for run == true { + ev := consumer.Poll(100) + if ev == nil { + continue + } + + switch e := ev.(type) { + case *kafka.Message: + fmt.Printf("%% Message on %s:\n%s: %s\n", + e.TopicPartition, string(e.Key), string(e.Value)) + if e.Headers != nil { + fmt.Printf("%% Headers: %v\n", e.Headers) + } + switch *e.TopicPartition.Topic { + case topicSirenBalances: + log.Debug().Str("key", string(e.Key)).Str("value", string(e.Value)).Msg("receive a siren balances message") + if e.Value == nil || len(e.Value) == 0 { + log.Debug().Interface("empty e.Value", e.Value).Msg("") + delete(balances, string(e.Key)) + } else { + b, ok := balances[string(e.Key)] + if ok { + log.Warn().Str("key", string(e.Key)).Str("old value", string(b)).Str("new value", string(e.Value)).Msg("dup balances key") + } + balances[string(e.Key)] = e.Value + } + default: + log.Error().Str("topic", *e.TopicPartition.Topic).Msg("unknown topic") + } + case kafka.Error: + // Errors should generally be considered as informational, the client will try to automatically recover + fmt.Fprintf(os.Stderr, "%% Error: %v\n", e) + case kafka.PartitionEOF: + fmt.Printf("%% Reached %v\n", e) + run = false + default: + fmt.Printf("Ignored %v\n", e) + } + } + + log.Debug().Msgf("len(balances): %d", len(balances)) + for k, v := range balances { + keys := strings.Split(k, ":") + userId, currencyId := keys[0], keys[2] + var value Kb + err := json.Unmarshal(v, &value) + if err != nil { + log.Error().Str("value", string(v)).Msg("failed to unmarshal value") + continue + } + + balanceSet(currencyId, userId, ACCOUNT_TYPE_CASH, value.Available, value.Hold) + } + + log.Info().Msg("Loading orders") + err = consumer.SubscribeTopics([]string{topicSirenOrders}, func(c *kafka.Consumer, event kafka.Event) error { + log.Info().Msgf("Rebalanced: %s", event) + switch e := event.(type) { + case kafka.AssignedPartitions: + for i := range e.Partitions { + log.Debug().Str("partition", spew.Sdump(e.Partitions[i])).Msg("reset offset of partition") + e.Partitions[i].Offset = kafka.OffsetBeginning + } + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Assign(e.Partitions) + case kafka.RevokedPartitions: + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Unassign() + } + return nil + }) + run = true + + orders := make(map[string][]byte) + + for run == true { + ev := consumer.Poll(100) + if ev == nil { + continue + } + + switch e := ev.(type) { + case *kafka.Message: + fmt.Printf("%% Message on %s:\n%s: %s\n", + e.TopicPartition, string(e.Key), string(e.Value)) + if e.Headers != nil { + fmt.Printf("%% Headers: %v\n", e.Headers) + } + switch *e.TopicPartition.Topic { + case topicSirenOrders: + log.Debug().Str("key", string(e.Key)).Str("value", string(e.Value)).Msg("receive a siren orders message") + + if e.Value == nil || len(e.Value) == 0 { + log.Debug().Interface("empty e.Value", e.Value).Msg("") + delete(orders, string(e.Key)) + } else { + o, ok := orders[string(e.Key)] + if ok { + log.Warn().Str("key", string(e.Key)).Str("old value", string(o)).Str("new value", string(e.Value)).Msg("dup orders key") + } + orders[string(e.Key)] = e.Value + } + default: + log.Error().Str("topic", *e.TopicPartition.Topic).Msg("unknown topic") + } + case kafka.Error: + // Errors should generally be considered as informational, the client will try to automatically recover + fmt.Fprintf(os.Stderr, "%% Error: %v\n", e) + case kafka.PartitionEOF: + fmt.Printf("%% Reached %v\n", e) + run = false + default: + fmt.Printf("Ignored %v\n", e) + } + } + + log.Debug().Msgf("len(orders): %d", len(orders)) + for _, v := range orders { + order := new(order) + err := json.Unmarshal(v, order) + if err != nil { + log.Error().Str("value", string(v)).Msg("failed to unmarshal order") + continue + } + if market, ok := currencyPairs[order.Symbol]; ok { + orderPut(false, market, order) + } + } + + fmt.Printf("Closing consumer\n") + consumer.Close() +} + +func initMessage() { + var err error + producer, err = kafka.NewProducer(&kafka.ConfigMap{ + "bootstrap.servers": "REMOVED.us-central1.gcp.confluent.cloud:9092", + "api.version.request": true, + "broker.version.fallback": "0.10.0.0", + "api.version.fallback.ms": 0, + "sasl.mechanisms": "PLAIN", + "security.protocol": "SASL_SSL", + "ssl.ca.location": "/etc/ssl/cert.pem", + "sasl.username": "REMOVED", + "sasl.password": "REMOVED", + }) + + if err != nil { + fmt.Printf("Failed to create producer: %s\n", err) + os.Exit(1) + } + + fmt.Printf("Created Producer %v\n", producer) + + go func() { + for e := range producer.Events() { + switch ev := e.(type) { + case *kafka.Message: + m := ev + if m.TopicPartition.Error != nil { + fmt.Printf("Delivery failed: %v\n", m.TopicPartition.Error) + } else { + fmt.Printf("Delivered message to topic %s [%d] at offset %v\n", + *m.TopicPartition.Topic, m.TopicPartition.Partition, m.TopicPartition.Offset) + } + default: + fmt.Printf("Ignored event: %s\n", ev) + } + } + }() +} + +func pushBalanceMessage(t int64, userId string, asset string, business string, change decimal.Decimal) { + var m []interface{} + m = append(m, t) + m = append(m, asset) + m = append(m, userId) + m = append(m, business) + m = append(m, change) + b, err := json.Marshal(m) + if err != nil { + log.Error().Err(err).Msg("failed to marshal balance message") + return + } + producer.ProduceChannel() <- &kafka.Message{TopicPartition: kafka.TopicPartition{Topic: &topicBalances, Partition: kafka.PartitionAny}, Value: b} +} + +type orderMessage struct { + Event OrderEventType `json:"event"` + Order *order `json:"order"` + Stock string `json:"stock"` + Money string `json:"money"` +} + +func pushOrderMessage(event OrderEventType, o *order, stock string, money string) { + m := orderMessage{ + Event: event, + Order: o, + Stock: stock, + Money: money, + } + b, err := json.Marshal(m) + if err != nil { + log.Error().Err(err).Msg("failed to marshal order message") + return + } + producer.ProduceChannel() <- &kafka.Message{TopicPartition: kafka.TopicPartition{Topic: &topicOrders, Partition: kafka.PartitionAny}, Value: b} +} + +func pushDealMessage(t int64, market string, ask *order, bid *order, price decimal.Decimal, amount decimal.Decimal, askFee decimal.Decimal, bidFee decimal.Decimal, side int32, id string, stock string, money string) { + var m []interface{} + m = append(m, t) + m = append(m, market) + m = append(m, ask.Id) + m = append(m, bid.Id) + m = append(m, ask.UserId) + m = append(m, bid.UserId) + m = append(m, price.String()) + m = append(m, amount.String()) + m = append(m, askFee.String()) + m = append(m, bidFee.String()) + m = append(m, side) + m = append(m, id) + m = append(m, stock) + m = append(m, money) + b, err := json.Marshal(m) + if err != nil { + log.Error().Err(err).Msg("failed to marshal deal message") + return + } + producer.ProduceChannel() <- &kafka.Message{TopicPartition: kafka.TopicPartition{Topic: &topicDeals, Partition: kafka.PartitionAny}, Value: b} +} diff --git a/svc-memorystore/Dockerfile b/svc-memorystore/Dockerfile new file mode 100644 index 0000000..00a4cb4 --- /dev/null +++ b/svc-memorystore/Dockerfile @@ -0,0 +1,26 @@ +FROM gcr.io/cloud-builders/go:latest as builder + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache pkgconfig librdkafka-dev + +WORKDIR /go/src/siren/ +COPY . . + +ENV GOPATH=/go +RUN GO111MODULE=on go install -v ./svc-gateway +RUN GO111MODULE=on go install -v ./svc-match +RUN GO111MODULE=on go install -v ./svc-memorystore +RUN GO111MODULE=on go install -v ./svc-sql +RUN GO111MODULE=on go install -v ./svc-wallet +RUN GO111MODULE=on go install -v ./svc-wallet/ethereum +RUN GO111MODULE=on go install -v ./svc-ws + +FROM alpine:3.8 + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache librdkafka + +WORKDIR /siren/ +COPY --from=builder /go/bin/svc-memorystore . + +ENTRYPOINT ["/siren/svc-memorystore"] diff --git a/svc-memorystore/main.go b/svc-memorystore/main.go new file mode 100644 index 0000000..1b267ac --- /dev/null +++ b/svc-memorystore/main.go @@ -0,0 +1,1245 @@ +package main + +import ( + "container/list" + "context" + "encoding/json" + "fmt" + "net" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/gomodule/redigo/redis" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + pb "github.com/en/siren/protos" + log "github.com/en/siren/utils/glog" +) + +const ( + port = ":50051" +) + +var redisPool *redis.Pool +var matchClient pb.MatchClient + +// tradingview {time, close, open, high, low, volume} +// coinbase [ time, low, high, open, close, volume ], +type marketInfo struct { + min map[int64]pb.KlineInfo + hour map[int64]pb.KlineInfo + day map[int64]pb.KlineInfo + last float64 + update *list.List + deals *list.List // for updating? + dealsJson *list.List + updateTime time.Time +} + +var markets map[string]*marketInfo +var lastFlush time.Time + +// [1542165471, "ETHBTC", 7, 41, "auth0|5b8f90a24341cb20a04174d0", "auth0|5b8f90a24341cb20a04174d0", "0.03279700", "0.36699287", "0.00012036265157390000", "0.003669928700", 2, 6, "ETH", "BTC"] +type dealsMessage struct { + time int64 + market string + i int32 // ? + j int32 // ? + user1 string // ? + user2 string // ? + price string + amount string + fee1 string // ? + fee2 string // ? + side int32 + id int32 + currency1 string // ? + currency2 string // ? +} + +const ( + KlineMin = iota + KlineHour + KlineDay +) + +type updateKey struct { + klineType int + timestamp int64 +} + +type server struct{} + +func (s *server) MarketStatus(ctx context.Context, in *pb.MarketStatusRequest) (*pb.MarketStatusReply, error) { + reply := new(pb.MarketStatusReply) + market := in.GetMarket() + period := in.GetPeriod() + m, ok := markets[market] + if !ok { + // TODO + return reply, nil + } + now := time.Now() + start := now.Add(-time.Duration(period) * time.Second).Truncate(time.Minute) + var info *pb.KlineInfo + for t := start; t.Before(now); t = t.Add(time.Minute) { + // fmt.Println("status search", t, t.Unix()) + if kline, ok := m.min[t.Unix()]; ok { + if info == nil { + info = new(pb.KlineInfo) + info.Open = kline.Open + info.Close = kline.Open + info.Low = kline.Open + info.High = kline.Open + } + klineInfoMerge(info, &kline) + } + } + if info == nil { + info = new(pb.KlineInfo) + } + reply.Last = m.last + reply.Open = info.Open + reply.Close = info.Close + reply.Low = info.Low + reply.High = info.High + reply.Volume = info.Volume + reply.Deal = info.Deal + return reply, nil +} + +func (s *server) MarketKline(ctx context.Context, in *pb.MarketKlineRequest) (*pb.MarketKlineReply, error) { + reply := new(pb.MarketKlineReply) + market := in.GetMarket() + start := in.GetStart() + end := in.GetEnd() + interval := in.GetInterval() + m, ok := markets[market] + if !ok { + // TODO + return reply, nil + } + + now := time.Now().Unix() + if end > now { + end = now + } + if start > end { + // TODO + fmt.Println("start > end", start, end) + return reply, nil + } + if interval < 60 { + // TODO + fmt.Println("interval < 60", interval) + return reply, nil + } else if interval < 3600 { + if interval%60 != 0 || 3600%interval != 0 { + // TODO + fmt.Println("interval:", interval) + return reply, nil + } + getMarketKlineMin(m, start, end, interval, reply) + } else if interval < 86400 { + if interval%3600 != 0 || 86400%interval != 0 { + // TODO + fmt.Println("interval:", interval) + return reply, nil + } + getMarketKlineHour(m, start, end, interval, reply) + } else if interval < 86400*7 { + if interval%86400 != 0 { + // TODO + fmt.Println("interval:", interval) + return reply, nil + } + getMarketKlineDay(m, start, end, interval, reply) + } else if interval == 86400*7 { + getMarketKlineWeek(m, start, end, interval, reply) + } else if interval == 86400*30 { + getMarketKlineMonth(m, start, end, interval, reply) + } else { + // TODO + fmt.Println("interval > 86400 * 30", interval) + return reply, nil + } + + return reply, nil +} + +func (s *server) MarketDeals(ctx context.Context, in *pb.MarketDealsRequest) (*pb.MarketDealsReply, error) { + reply := new(pb.MarketDealsReply) + market := in.GetMarket() + limit := in.GetLimit() + lastId := in.GetLastId() + m, ok := markets[market] + if !ok { + // TODO + return reply, nil + } + count := int32(0) + for e := m.dealsJson.Front(); e != nil; e = e.Next() { + fmt.Println("dealsJson:", e.Value) + d := e.Value.(pb.MarketDeal) + if uint64(d.Id) <= lastId { + break + } + reply.Items = append(reply.Items, &d) + count++ + if count == limit { + break + } + } + return reply, nil +} + +func (s *server) MarketLast(ctx context.Context, in *pb.MarketLastRequest) (*pb.MarketLastReply, error) { + reply := new(pb.MarketLastReply) + market := in.GetMarket() + m, ok := markets[market] + if !ok { + // TODO + return reply, nil + } + reply.Last = m.last + return reply, nil +} + +func (s *server) MarketStatusToday(ctx context.Context, in *pb.MarketStatusTodayRequest) (*pb.MarketStatusTodayReply, error) { + reply := new(pb.MarketStatusTodayReply) + market := in.GetMarket() + info, ok := markets[market] + if !ok { + // TODO + return reply, nil + } + now := time.Now() + midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.Local) + entry, ok := info.day[midnight.Unix()] + if ok { + fmt.Println("in ok") + reply.Low = entry.Low + reply.High = entry.High + reply.Open = entry.Open + reply.Last = entry.Close + } else { + kline := getLastKline(info.day, midnight.Add(-time.Hour*24), midnight.Add(-time.Hour*24*30), time.Hour*24) + if kline != nil { + fmt.Println("in last") + reply.Low = kline.Close + reply.High = kline.Close + reply.Open = kline.Close + reply.Last = kline.Close + } else { + fmt.Println("in else") + reply.Low = 0.0 + reply.High = 0.0 + reply.Open = 0.0 + reply.Last = 0.0 + } + } + start := now.Add(-24 * time.Hour).Truncate(time.Minute) + volume := 0.0 + for t := start; t.Before(now); t = t.Add(time.Minute) { + // fmt.Println("volume search", t, t.Unix()) + if kline, ok := info.min[t.Unix()]; ok { + volume += kline.Volume + } + } + reply.Volume = volume + // TODO: deal + fmt.Println("reply:", reply) + return reply, nil +} + +func getLastKline(m map[int64]pb.KlineInfo, start time.Time, end time.Time, interval time.Duration) *pb.KlineInfo { + fmt.Println("start:", start, "end:", end) + for i := start; i.After(end); i = i.Add(-interval) { + // fmt.Println("search", i, i.Unix()) + if kline, ok := m[i.Unix()]; ok { + return &kline + } + } + return nil +} + +func marketExist(market string) bool { + _, ok := markets[market] + return ok +} + +func loadMarketKlineMin(name string, info *marketInfo) { + fmt.Println("market name:", name) + conn := redisPool.Get() + defer conn.Close() + key := fmt.Sprintf("k:%s:1m", name) + s, err := redis.Values(conn.Do("HGETALL", key)) + if err != nil { + fmt.Println(err) + } + for i := 0; i < len(s); i += 2 { + // open, close, high, low, volume, deal + data := s[i+1].([]byte) + var r []string + err := json.Unmarshal(data, &r) + if err != nil { + fmt.Println("failed to unmarshal redis data", err) + break + } + time, err := strconv.ParseInt(string(s[i].([]byte)), 10, 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + + open, err := strconv.ParseFloat(r[0], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + close, err := strconv.ParseFloat(r[1], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + high, err := strconv.ParseFloat(r[2], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + low, err := strconv.ParseFloat(r[3], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + volume, err := strconv.ParseFloat(r[4], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + deal, err := strconv.ParseFloat(r[5], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + + kline := pb.KlineInfo{} + kline.Time = time + kline.Close = close + kline.Open = open + kline.High = high + kline.Low = low + kline.Volume = volume + kline.Deal = deal + // fmt.Println("kline data:", kline) + info.min[time] = kline + } +} + +func loadMarketKlineHour(name string, info *marketInfo) { + fmt.Println("market name:", name) + conn := redisPool.Get() + defer conn.Close() + key := fmt.Sprintf("k:%s:1h", name) + s, err := redis.Values(conn.Do("HGETALL", key)) + if err != nil { + fmt.Println(err) + } + for i := 0; i < len(s); i += 2 { + // open, close, high, low, volume, deal + data := s[i+1].([]byte) + var r []string + err := json.Unmarshal(data, &r) + if err != nil { + fmt.Println("failed to unmarshal redis data", err) + break + } + time, err := strconv.ParseInt(string(s[i].([]byte)), 10, 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + + open, err := strconv.ParseFloat(r[0], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + close, err := strconv.ParseFloat(r[1], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + high, err := strconv.ParseFloat(r[2], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + low, err := strconv.ParseFloat(r[3], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + volume, err := strconv.ParseFloat(r[4], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + deal, err := strconv.ParseFloat(r[5], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + + kline := pb.KlineInfo{} + kline.Time = time + kline.Close = close + kline.Open = open + kline.High = high + kline.Low = low + kline.Volume = volume + kline.Deal = deal + fmt.Println("kline data:", kline) + info.hour[time] = kline + } +} + +func loadMarketKlineDay(name string, info *marketInfo) { + fmt.Println("market name:", name) + conn := redisPool.Get() + defer conn.Close() + key := fmt.Sprintf("k:%s:1d", name) + s, err := redis.Values(conn.Do("HGETALL", key)) + if err != nil { + fmt.Println(err) + } + for i := 0; i < len(s); i += 2 { + // open, close, high, low, volume, deal + data := s[i+1].([]byte) + var r []string + err := json.Unmarshal(data, &r) + if err != nil { + fmt.Println("failed to unmarshal redis data", err) + break + } + time, err := strconv.ParseInt(string(s[i].([]byte)), 10, 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + + open, err := strconv.ParseFloat(r[0], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + close, err := strconv.ParseFloat(r[1], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + high, err := strconv.ParseFloat(r[2], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + low, err := strconv.ParseFloat(r[3], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + volume, err := strconv.ParseFloat(r[4], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + deal, err := strconv.ParseFloat(r[5], 64) + if err != nil { + fmt.Println("failed to strconv", err) + break + } + + kline := pb.KlineInfo{} + kline.Time = time + kline.Close = close + kline.Open = open + kline.High = high + kline.Low = low + kline.Volume = volume + kline.Deal = deal + fmt.Println("kline data:", kline) + info.day[time] = kline + } +} + +func loadMarketDeals(name string, info *marketInfo) { + conn := redisPool.Get() + defer conn.Close() + key := fmt.Sprintf("k:%s:deals", name) + s, err := redis.Values(conn.Do("LRANGE", key, 0, 10000)) + if err != nil { + fmt.Println(err) + } + for i := 0; i < len(s); i++ { + data := s[i].([]byte) + fmt.Println("deals:", string(data)) + var deal pb.MarketDeal + err := json.Unmarshal(data, &deal) + if err != nil { + log.Error().Err(err).Msg("unmarshal market deal") + continue + } + info.dealsJson.PushBack(deal) + } +} + +func loadMarketLast(name string, info *marketInfo) { + fmt.Println("market name:", name) + conn := redisPool.Get() + defer conn.Close() + key := fmt.Sprintf("k:%s:last", name) + s, err := redis.String(conn.Do("GET", key)) + fmt.Printf("last:%#v, %v\n", s, err) + last, err := strconv.ParseFloat(s, 64) + if err != nil { + fmt.Println("failed to strconv", err) + last = float64(0.0) + } + info.last = last +} + +func klineInfoMerge(info *pb.KlineInfo, update *pb.KlineInfo) { + info.Close = update.Close + info.Volume = info.Volume + update.Volume + info.Deal = info.Deal + update.Deal + if update.High > info.High { + info.High = update.High + } + if update.Low < info.Low { + info.Low = update.Low + } +} + +func klineInfoUpdate(info *pb.KlineInfo, price float64, amount float64) { + info.Close = price + info.Volume = info.Volume + amount + info.Deal = info.Deal + price*amount + if price > info.High { + info.High = price + } + if price < info.Low { + info.Low = price + } +} + +func getMarketKlineMin(info *marketInfo, start int64, end int64, interval int64, reply *pb.MarketKlineReply) { + now := time.Now() + minStart := now.Add(-365 * 24 * time.Hour).Truncate(time.Minute) + if start < minStart.Unix() { + start = minStart.Unix() + } + tStart := time.Unix(start, 0) + tInterval := time.Duration(interval) * time.Second + tStart = tStart.Truncate(tInterval) + kbefore := getLastKline(info.min, tStart.Add(-60*time.Second), minStart, 60*time.Second) + klast := kbefore + step := interval / 60 + fmt.Println("loop begin") + for s := tStart.Unix(); s <= end; s += interval { + var kinfo *pb.KlineInfo + // fmt.Println("inner loop begin") + for i := int64(0); i < step; i++ { + timestamp := s + int64(i)*60 + // fmt.Println("get market min search", timestamp) + if kline, ok := info.min[timestamp]; ok { + if kinfo == nil { + kinfo = new(pb.KlineInfo) + kinfo.Open = kline.Open + kinfo.Close = kline.Open + kinfo.Low = kline.Open + kinfo.High = kline.Open + } + klineInfoMerge(kinfo, &kline) + + } else { + continue + } + } + // fmt.Println("inner loop end") + if kinfo == nil { + if klast == nil { + continue + } + kinfo = new(pb.KlineInfo) + kinfo.Open = klast.Close + kinfo.Close = klast.Close + kinfo.Low = klast.Close + kinfo.High = klast.Close + } + kinfo.Time = s + reply.Items = append(reply.Items, kinfo) + klast = kinfo + } + fmt.Println("loop end") +} + +func getMarketKlineHour(info *marketInfo, start int64, end int64, interval int64, reply *pb.MarketKlineReply) { + now := time.Now() + minStart := now.Add(-365 * 24 * 10 * time.Hour).Truncate(time.Hour) + if start < minStart.Unix() { + start = minStart.Unix() + } + tStart := time.Unix(start, 0) + midnight := time.Date(tStart.Year(), tStart.Month(), tStart.Day(), 0, 0, 0, 0, time.Local) + + for midnight.Unix()+interval <= start { + midnight = midnight.Add(time.Duration(interval) * time.Second) + } + tStart = midnight + + kbefore := getLastKline(info.hour, tStart.Add(-time.Hour), minStart, time.Hour) + klast := kbefore + step := interval / 3600 + for s := tStart.Unix(); s <= end; s += interval { + var kinfo *pb.KlineInfo + for i := int64(0); i < step; i++ { + timestamp := s + int64(i)*3600 + if kline, ok := info.hour[timestamp]; ok { + if kinfo == nil { + kinfo = new(pb.KlineInfo) + kinfo.Open = kline.Open + kinfo.Close = kline.Open + kinfo.Low = kline.Open + kinfo.High = kline.Open + } + klineInfoMerge(kinfo, &kline) + + } else { + continue + } + } + if kinfo == nil { + if klast == nil { + continue + } + kinfo = new(pb.KlineInfo) + kinfo.Open = klast.Close + kinfo.Close = klast.Close + kinfo.Low = klast.Close + kinfo.High = klast.Close + } + kinfo.Time = s + reply.Items = append(reply.Items, kinfo) + klast = kinfo + } +} + +func getMarketKlineDay(info *marketInfo, start int64, end int64, interval int64, reply *pb.MarketKlineReply) { + start = start / interval * interval + tStart := time.Unix(start, 0) + + kbefore := getLastKline(info.day, tStart.Add(-24*time.Hour), tStart.Add(-30*24*time.Hour), 24*time.Hour) + klast := kbefore + step := interval / 86400 + for s := tStart.Unix(); s <= end; s += interval { + var kinfo *pb.KlineInfo + for i := int64(0); i < step; i++ { + timestamp := s + int64(i)*86400 + if kline, ok := info.day[timestamp]; ok { + if kinfo == nil { + kinfo = new(pb.KlineInfo) + kinfo.Open = kline.Open + kinfo.Close = kline.Open + kinfo.Low = kline.Open + kinfo.High = kline.Open + } + klineInfoMerge(kinfo, &kline) + + } else { + continue + } + } + if kinfo == nil { + if klast == nil { + continue + } + kinfo = new(pb.KlineInfo) + kinfo.Open = klast.Close + kinfo.Close = klast.Close + kinfo.Low = klast.Close + kinfo.High = klast.Close + } + kinfo.Time = s + reply.Items = append(reply.Items, kinfo) + klast = kinfo + } +} + +func getMarketKlineWeek(info *marketInfo, start int64, end int64, interval int64, reply *pb.MarketKlineReply) { + base := start/interval*interval - 3*86400 + for base+interval <= start { + base += interval + } + start = base + tStart := time.Unix(start, 0) + + kbefore := getLastKline(info.day, tStart.Add(-24*time.Hour), tStart.Add(-30*24*time.Hour), 24*time.Hour) + klast := kbefore + step := interval / 86400 + for s := tStart.Unix(); s <= end; s += interval { + var kinfo *pb.KlineInfo + for i := int64(0); i < step; i++ { + timestamp := s + int64(i)*86400 + if kline, ok := info.day[timestamp]; ok { + if kinfo == nil { + kinfo = new(pb.KlineInfo) + kinfo.Open = kline.Open + kinfo.Close = kline.Open + kinfo.Low = kline.Open + kinfo.High = kline.Open + } + klineInfoMerge(kinfo, &kline) + + } else { + continue + } + } + if kinfo == nil { + if klast == nil { + continue + } + kinfo = new(pb.KlineInfo) + kinfo.Open = klast.Close + kinfo.Close = klast.Close + kinfo.Low = klast.Close + kinfo.High = klast.Close + } + kinfo.Time = s + reply.Items = append(reply.Items, kinfo) + klast = kinfo + } +} + +func getMarketKlineMonth(info *marketInfo, start int64, end int64, interval int64, reply *pb.MarketKlineReply) { + tStart := time.Unix(start, 0) + monStart := time.Date(tStart.Year(), tStart.Month(), 1, 0, 0, 0, 0, time.Local) + + kbefore := getLastKline(info.day, monStart.Add(-24*time.Hour), tStart.Add(-30*24*time.Hour), 24*time.Hour) + klast := kbefore + for s := monStart.Unix(); s <= end; { + var kinfo *pb.KlineInfo + year := time.Unix(s, 0).Year() + next := int(time.Unix(s, 0).Month()) + 1 + var nextMonth time.Month + if next == 13 { + nextMonth = time.January + year = year + 1 + } else { + nextMonth = time.Month(next) + } + monNext := time.Date(year, nextMonth, 1, 0, 0, 0, 0, time.Local) + for timestamp := s; timestamp < monNext.Unix() && timestamp <= end; timestamp += 86400 { + if kline, ok := info.day[timestamp]; ok { + if kinfo == nil { + kinfo = new(pb.KlineInfo) + kinfo.Open = kline.Open + kinfo.Close = kline.Open + kinfo.Low = kline.Open + kinfo.High = kline.Open + } + klineInfoMerge(kinfo, &kline) + } else { + continue + } + } + if kinfo == nil { + if klast == nil { + continue + } + kinfo = new(pb.KlineInfo) + kinfo.Open = klast.Close + kinfo.Close = klast.Close + kinfo.Low = klast.Close + kinfo.High = klast.Close + } + kinfo.Time = s + reply.Items = append(reply.Items, kinfo) + s = monNext.Unix() + klast = kinfo + } +} + +func marketUpdate(message *dealsMessage) { + info, ok := markets[message.market] + if !ok { + fmt.Println(message.market, "not found") + return + } + price, err := strconv.ParseFloat(message.price, 64) + if err != nil { + fmt.Println("failed to strconv", err) + return + } + amount, err := strconv.ParseFloat(message.amount, 64) + if err != nil { + fmt.Println("failed to strconv", err) + return + } + + // update min + // discard fraction + timeMin := time.Unix(0, message.time*1000*1000).Truncate(time.Minute) + _, ok = info.min[timeMin.Unix()] + if !ok { + kinfo := pb.KlineInfo{} + kinfo.Open = price + kinfo.Close = price + kinfo.Low = price + kinfo.High = price + info.min[timeMin.Unix()] = kinfo + } + kinfo, ok := info.min[timeMin.Unix()] + if !ok { + fmt.Println("failed to get kinfo") + return + } + klineInfoUpdate(&kinfo, price, amount) + info.min[timeMin.Unix()] = kinfo + info.update.PushBack(updateKey{KlineMin, timeMin.Unix()}) + + // update hour + timeHour := time.Unix(0, message.time*1000*1000).Truncate(time.Hour) + _, ok = info.hour[timeHour.Unix()] + if !ok { + kinfo := pb.KlineInfo{} + kinfo.Open = price + kinfo.Close = price + kinfo.Low = price + kinfo.High = price + info.hour[timeHour.Unix()] = kinfo + } + kinfo, ok = info.hour[timeHour.Unix()] + if !ok { + fmt.Println("failed to get kinfo") + return + } + klineInfoUpdate(&kinfo, price, amount) + info.hour[timeHour.Unix()] = kinfo + info.update.PushBack(updateKey{KlineHour, timeHour.Unix()}) + + // update day + t := time.Unix(0, message.time*1000*1000) + timeDay := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local) + _, ok = info.day[timeDay.Unix()] + if !ok { + kinfo := pb.KlineInfo{} + kinfo.Open = price + kinfo.Close = price + kinfo.Low = price + kinfo.High = price + info.day[timeDay.Unix()] = kinfo + } + kinfo, ok = info.day[timeDay.Unix()] + if !ok { + fmt.Println("failed to get kinfo") + return + } + klineInfoUpdate(&kinfo, price, amount) + info.day[timeDay.Unix()] = kinfo + info.update.PushBack(updateKey{KlineDay, timeDay.Unix()}) + + // update last + info.last = price + + // update deals + var deal pb.MarketDeal + deal.Amount = message.amount + deal.Id = message.id + deal.Time = message.time + deal.Price = message.price + if message.side == 1 { + deal.Type = "sell" + } else { + deal.Type = "buy" + } + info.deals.PushBack(deal) + info.dealsJson.PushFront(deal) + + if info.dealsJson.Len() > 10000 { + info.dealsJson.Remove(info.dealsJson.Back()) + } + + // set update time + info.updateTime = time.Now() +} + +func flushUpdate(market string, info *marketInfo) { + conn := redisPool.Get() + defer conn.Close() + for e := info.update.Front(); e != nil; e = e.Next() { + update := e.Value.(updateKey) + var key string + var kinfo pb.KlineInfo + var ok bool + if update.klineType == KlineMin { + key = fmt.Sprintf("k:%s:1m", market) + kinfo, ok = info.min[update.timestamp] + } else if update.klineType == KlineHour { + key = fmt.Sprintf("k:%s:1h", market) + kinfo, ok = info.hour[update.timestamp] + } else if update.klineType == KlineDay { + key = fmt.Sprintf("k:%s:1d", market) + kinfo, ok = info.day[update.timestamp] + } + if !ok { + fmt.Println("kline not found:", update) + continue + } + var r []string + r = append(r, fmt.Sprintf("%d", kinfo.Open)) + r = append(r, fmt.Sprintf("%d", kinfo.Close)) + r = append(r, fmt.Sprintf("%d", kinfo.High)) + r = append(r, fmt.Sprintf("%d", kinfo.Low)) + r = append(r, fmt.Sprintf("%d", kinfo.Volume)) + r = append(r, fmt.Sprintf("%d", kinfo.Deal)) + b, err := json.Marshal(r) + if err != nil { + log.Error().Err(err).Msg("failed to marshal kline") + continue + } + s := string(b) + fmt.Println("update kline:", s) + + _, err = redis.Int64(conn.Do("HSET", key, update.timestamp, s)) + if err != nil { + fmt.Println(err) + continue + } + info.update.Remove(e) + } +} + +func flushLast(market string, last float64) { + conn := redisPool.Get() + defer conn.Close() + key := fmt.Sprintf("k:%s:last", market) + slast := fmt.Sprintf("%.6f", last) + s, err := redis.String(conn.Do("SET", key, slast)) + if err != nil { + fmt.Println(err) + } + fmt.Println("update last:", s) +} + +func flushDeals(market string, info *marketInfo) { + conn := redisPool.Get() + defer conn.Close() + args := redis.Args{} + for e := info.deals.Front(); e != nil; e = e.Next() { + deal := e.Value.(pb.MarketDeal) + b, err := json.Marshal(deal) + if err != nil { + log.Error().Err(err).Msg("failed to marshal deal") + continue + } + s := string(b) + args = args.Add(s) + } + key := fmt.Sprintf("k:%s:deals", market) + _, err := redis.Int64(conn.Do("LPUSH", key, args)) + if err != nil { + fmt.Println(err) + } + _, err = redis.String(conn.Do("LTRIM", key, 0, 10000)) + if err != nil { + fmt.Println(err) + } + // clear list + info.deals.Init() +} + +func flushMarket() { + for k, v := range markets { + if v.updateTime.Before(lastFlush) { + continue + } + // flush update + flushUpdate(k, v) + // flush last + flushLast(k, v.last) + if v.deals.Len() == 0 { + continue + } + // flush deals + flushDeals(k, v) + } + // flush offset + lastFlush = time.Now() +} + +func clearKline() { + now := time.Now() + + for _, v := range markets { + // clear min + for k := range v.min { + if k < now.Add(-365*24*time.Hour).Truncate(time.Minute).Unix() { + delete(v.min, k) + } + } + // clear hour + for k := range v.hour { + if k < now.Add(-365*24*10*time.Hour).Truncate(time.Hour).Unix() { + delete(v.hour, k) + } + } + } +} + +func clearRedis() { + now := time.Now() + + for k := range markets { + conn := redisPool.Get() + defer conn.Close() + + key := fmt.Sprintf("k:%s:1m", k) + s, err := redis.Values(conn.Do("HGETALL", key)) + if err != nil { + fmt.Println(err) + } + for i := 0; i < len(s); i += 2 { + t, err := strconv.ParseInt(string(s[i].([]byte)), 10, 64) + if err != nil { + fmt.Println("failed to strconv", err) + continue + } + if t < now.Add(-365*24*time.Hour).Truncate(time.Minute).Unix() { + _, err := redis.Values(conn.Do("HDEL", key, string(s[i].([]byte)))) + if err != nil { + fmt.Println(err) + } + } + } + + key = fmt.Sprintf("k:%s:1h", k) + s, err = redis.Values(conn.Do("HGETALL", key)) + if err != nil { + fmt.Println(err) + } + for i := 0; i < len(s); i += 2 { + t, err := strconv.ParseInt(string(s[i].([]byte)), 10, 64) + if err != nil { + fmt.Println("failed to strconv", err) + continue + } + if t < now.Add(-365*24*10*time.Hour).Truncate(time.Hour).Unix() { + _, err := redis.Values(conn.Do("HDEL", key, string(s[i].([]byte)))) + if err != nil { + fmt.Println(err) + } + } + } + } +} + +func main() { + log.Info().Msg("Starting siren-memorystore") + + viper.SetEnvPrefix("memorystore") + viper.AutomaticEnv() + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + + redisHost := viper.GetString("redis-host") + redisPort := viper.GetString("redis-port") + redisAddr := fmt.Sprintf("%s:%s", redisHost, redisPort) + + const maxConnections = 10 + redisPool = redis.NewPool(func() (redis.Conn, error) { + return redis.Dial("tcp", redisAddr) + }, maxConnections) + + { + conn := redisPool.Get() + defer conn.Close() + + s, err := redis.String(conn.Do("PING")) + fmt.Printf("%#v, %v\n", s, err) + } + + { + conn, err := grpc.Dial("siren-match:50051", grpc.WithInsecure()) + if err != nil { + log.Fatal().Err(err).Msg("did not connect") + } + defer conn.Close() + matchClient = pb.NewMatchClient(conn) + log.Info().Msg("connected to siren-match:50051") + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.GetSymbols(ctx, &pb.EmptyRequest{}) + if err != nil { + log.Fatal().Err(err).Msg("failed to get currency pairs") + } + + markets = make(map[string]*marketInfo) + for _, cp := range r.Symbols { + market := cp.BaseCurrency + cp.QuoteCurrency + markets[market] = new(marketInfo) + markets[market].update = list.New() + markets[market].deals = list.New() + markets[market].dealsJson = list.New() + markets[market].min = make(map[int64]pb.KlineInfo) + markets[market].hour = make(map[int64]pb.KlineInfo) + markets[market].day = make(map[int64]pb.KlineInfo) + loadMarketKlineMin(market, markets[market]) + loadMarketKlineHour(market, markets[market]) + loadMarketKlineDay(market, markets[market]) + loadMarketDeals(market, markets[market]) + loadMarketLast(market, markets[market]) + } + // for k, v := range markets { + // fmt.Println("market:", k) + // fmt.Println("min>") + // for k1, v1 := range v.min { + // fmt.Println(k1, v1) + // } + // fmt.Println("hour>") + // for k1, v1 := range v.hour { + // fmt.Println(k1, v1) + // } + // fmt.Println("day>") + // for k1, v1 := range v.day { + // fmt.Println(k1, v1) + // } + // } + + var offset int64 + { + conn := redisPool.Get() + defer conn.Close() + + s, err := redis.String(conn.Do("GET", "k:offset")) + fmt.Printf("offset:%#v, %v\n", s, err) + offset, err = strconv.ParseInt(s, 10, 64) + if err != nil { + fmt.Println("failed to strconv", err) + offset = int64(0) + } + } + fmt.Println("offset:", offset) + + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) + topic := "deals" + c, err := kafka.NewConsumer(&kafka.ConfigMap{ + "bootstrap.servers": "REMOVED.us-central1.gcp.confluent.cloud:9092", + "api.version.request": true, + "broker.version.fallback": "0.10.0.0", + "api.version.fallback.ms": 0, + "sasl.mechanisms": "PLAIN", + "security.protocol": "SASL_SSL", + "ssl.ca.location": "/etc/ssl/cert.pem", + "sasl.username": "REMOVED", + "sasl.password": "REMOVED", + "group.id": "siren-memorystore", + "enable.partition.eof": true, + "auto.offset.reset": "earliest"}) + + if err != nil { + panic(err) + } + + err = c.Subscribe(topic, nil) + if err != nil { + panic(err) + } + + go func() { + run := true + + for run == true { + select { + case sig := <-sigchan: + fmt.Printf("Caught signal %v: terminating\n", sig) + run = false + default: + ev := c.Poll(100) + if ev == nil { + continue + } + + switch e := ev.(type) { + case *kafka.Message: + fmt.Printf("%% Message on %s:\n%s\n", + e.TopicPartition, string(e.Value)) + if e.Headers != nil { + fmt.Printf("%% Headers: %v\n", e.Headers) + } + var dm dealsMessage + tmp := []interface{}{&dm.time, &dm.market, &dm.i, &dm.j, &dm.user1, &dm.user2, &dm.price, &dm.amount, &dm.fee1, &dm.fee2, &dm.side, &dm.id, &dm.currency1, &dm.currency2} + err := json.Unmarshal(e.Value, &tmp) + if err != nil { + fmt.Fprintf(os.Stderr, "%% Unmarshal Error: %v\n", err) + } + marketUpdate(&dm) + // TODO: update offset? + case kafka.PartitionEOF: + fmt.Printf("%% Reached %v\n", e) + case kafka.Error: + fmt.Fprintf(os.Stderr, "%% Error: %v\n", e) + run = false + default: + fmt.Printf("Ignored %v\n", e) + } + } + } + + fmt.Printf("Closing consumer\n") + c.Close() + }() + + go func() { + marketTimer := time.After(10 * time.Second) // 10 sec + clearTimer := time.After(time.Hour) // 1 hour + redisTimer := time.After(24 * time.Hour) // 1 day + for { + select { + case <-marketTimer: + fmt.Println("in market timer ...") + flushMarket() + marketTimer = time.After(10 * time.Second) + case <-clearTimer: + fmt.Println("in clear timer ...") + clearKline() + clearTimer = time.After(time.Hour) + case <-redisTimer: + fmt.Println("in redis timer ...") + go clearRedis() + redisTimer = time.After(24 * time.Hour) + } + } + }() + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatal().Err(err).Msg("failed to listen") + } + s := grpc.NewServer() + // TODO + pb.RegisterMemorystoreServer(s, &server{}) + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatal().Err(err).Msg("failed to serve") + } +} diff --git a/svc-sql/Dockerfile b/svc-sql/Dockerfile new file mode 100644 index 0000000..3bb5363 --- /dev/null +++ b/svc-sql/Dockerfile @@ -0,0 +1,26 @@ +FROM gcr.io/cloud-builders/go:latest as builder + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache pkgconfig librdkafka-dev + +WORKDIR /go/src/siren/ +COPY . . + +ENV GOPATH=/go +RUN GO111MODULE=on go install -v ./svc-gateway +RUN GO111MODULE=on go install -v ./svc-match +RUN GO111MODULE=on go install -v ./svc-memorystore +RUN GO111MODULE=on go install -v ./svc-sql +RUN GO111MODULE=on go install -v ./svc-wallet +RUN GO111MODULE=on go install -v ./svc-wallet/ethereum +RUN GO111MODULE=on go install -v ./svc-ws + +FROM alpine:3.8 + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache librdkafka + +WORKDIR /siren/ +COPY --from=builder /go/bin/svc-sql . + +ENTRYPOINT ["/siren/svc-sql"] diff --git a/svc-sql/main.go b/svc-sql/main.go new file mode 100644 index 0000000..3ec2b33 --- /dev/null +++ b/svc-sql/main.go @@ -0,0 +1,176 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + "net" + "strings" + "time" + + "github.com/golang/protobuf/ptypes" + _ "github.com/lib/pq" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + pb "github.com/en/siren/protos" + log "github.com/en/siren/utils/glog" +) + +const ( + port = ":50051" +) + +var db *sql.DB + +type server struct{} + +func (s *server) Transfers(ctx context.Context, in *pb.TransfersRequest) (*pb.TransfersResponse, error) { + response := new(pb.TransfersResponse) + valueArgs := make([]interface{}, 0) + i := 1 + stmt := "SELECT id, currency, type, amount, status, created_at, updated_at FROM transfers WHERE user_id = $1" + valueArgs = append(valueArgs, in.UserId) + i++ + + if in.Currency != "" { + stmt = stmt + fmt.Sprintf(" AND currency = $%d", i) + valueArgs = append(valueArgs, in.Currency) + i++ + } + + if in.Type != "" { + stmt = stmt + fmt.Sprintf(" AND type = $%d", i) + valueArgs = append(valueArgs, in.Type) + i++ + } + + if in.Since != nil { + since, err := ptypes.Timestamp(in.Since) + if err == nil { + stmt = stmt + fmt.Sprintf(" AND created_at >= $%d", i) + valueArgs = append(valueArgs, since) + i++ + } else { + log.Error().Err(err).Msg("") + } + } + + if in.Until != nil { + until, err := ptypes.Timestamp(in.Until) + if err == nil { + stmt = stmt + fmt.Sprintf(" AND created_at < $%d", i) + valueArgs = append(valueArgs, until) + i++ + } else { + log.Error().Err(err).Msg("") + } + } + + stmt = stmt + " ORDER BY created_at DESC" + + if in.Limit != 0 { + stmt = stmt + fmt.Sprintf(" LIMIT $%d", i) + valueArgs = append(valueArgs, in.Limit) + i++ + } + + log.Debug().Str("stmt", stmt).Interface("args", valueArgs).Msg("query transfers") + rows, err := db.QueryContext(ctx, stmt, valueArgs...) + fmt.Println(rows, err) + if err != nil { + log.Error().Err(err).Msg("") + return response, err + } + defer rows.Close() + + var createdAt time.Time + var updatedAt time.Time + + for rows.Next() { + transfer := new(pb.Transfer) + if err := rows.Scan(&transfer.Id, &transfer.Currency, &transfer.Type, &transfer.Amount, &transfer.Status, &createdAt, &updatedAt); err != nil { + log.Error().Err(err).Msg("") + continue + } + ca, err := ptypes.TimestampProto(createdAt) + if err != nil { + log.Error().Err(err).Msg("parsing created_at failed") + continue + } + ua, err := ptypes.TimestampProto(updatedAt) + if err != nil { + log.Error().Err(err).Msg("parsing updated_at failed") + continue + } + transfer.CreatedAt = ca + transfer.UpdatedAt = ua + fmt.Println(transfer) + response.Transfers = append(response.Transfers, transfer) + } + if err := rows.Err(); err != nil { + log.Error().Err(err).Msg("") + return response, err + } + return response, nil +} + +func (s *server) GetOrderHistory(ctx context.Context, in *pb.GetOrderHistoryRequest) (*pb.GetOrderHistoryReply, error) { + response := new(pb.GetOrderHistoryReply) + return response, nil +} + +func (s *server) GetOrderDeals(ctx context.Context, in *pb.GetOrderDealsRequest) (*pb.GetOrderDealsReply, error) { + response := new(pb.GetOrderDealsReply) + return response, nil +} + +func (s *server) GetOrderDetailFinished(ctx context.Context, in *pb.GetOrderDetailFinishedRequest) (*pb.GetOrderDetailFinishedReply, error) { + response := new(pb.GetOrderDetailFinishedReply) + return response, nil +} + +func (s *server) GetMarketUserDeals(ctx context.Context, in *pb.GetMarketUserDealsRequest) (*pb.GetMarketUserDealsReply, error) { + response := new(pb.GetMarketUserDealsReply) + return response, nil +} + +func main() { + log.Info().Msg("Starting siren-sql") + + viper.SetEnvPrefix("sql") + viper.AutomaticEnv() + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + + pqInfo := fmt.Sprintf("user=%s password=%s host=%s port=5432 dbname=%s sslmode=disable", + viper.GetString("db-user"), + viper.GetString("db-password"), + viper.GetString("db-host"), + viper.GetString("db-dbname")) + var err error + db, err = sql.Open("postgres", pqInfo) + if err != nil { + log.Fatal().Msgf("failed to open database: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + if err := db.PingContext(ctx); err != nil { + log.Fatal().Msgf("failed to ping: %v", err) + } + + initMessaging() + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatal().Err(err).Msg("failed to listen") + } + s := grpc.NewServer() + // TODO + pb.RegisterSqlServer(s, &server{}) + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatal().Err(err).Msg("failed to serve") + } +} diff --git a/svc-sql/messaging.go b/svc-sql/messaging.go new file mode 100644 index 0000000..3ffe8e5 --- /dev/null +++ b/svc-sql/messaging.go @@ -0,0 +1,167 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + + "github.com/en/siren/messaging" + log "github.com/en/siren/utils/glog" +) + +func initMessaging() { + consumer, err := kafka.NewConsumer(&kafka.ConfigMap{ + "bootstrap.servers": "REMOVED.us-central1.gcp.confluent.cloud:9092", + "api.version.request": true, + "broker.version.fallback": "0.10.0.0", + "api.version.fallback.ms": 0, + "sasl.mechanisms": "PLAIN", + "security.protocol": "SASL_SSL", + "ssl.ca.location": "/etc/ssl/cert.pem", + "sasl.username": "REMOVED", + "sasl.password": "REMOVED", + "group.id": "siren-sql", + "session.timeout.ms": 6000, + "go.events.channel.enable": true, + "go.application.rebalance.enable": true, + // Enable generation of PartitionEOF when the + // end of a partition is reached. + "enable.partition.eof": true, + "auto.offset.reset": "earliest"}) + + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create consumer: %s\n", err) + os.Exit(1) + } + log.Info().Msgf("Created Consumer %v\n", consumer) + + err = consumer.SubscribeTopics([]string{messaging.TopicSirenTransfers}, nil) + if err != nil { + log.Fatal().Msgf("Failed to Subscribe topic: %v\n", err) + } + + ch := make(chan []byte) + go readMessage(consumer, ch) + go save(ch) + +} + +func readMessage(consumer *kafka.Consumer, ch chan []byte) { + defer func() { + close(ch) + fmt.Printf("Closing consumer\n") + consumer.Close() + }() + for { + select { + case ev := <-consumer.Events(): + switch e := ev.(type) { + case kafka.AssignedPartitions: + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Assign(e.Partitions) + case kafka.RevokedPartitions: + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Unassign() + case *kafka.Message: + fmt.Printf("%% Message on %s:\n%s: %s\n", + e.TopicPartition, string(e.Key), string(e.Value)) + switch *e.TopicPartition.Topic { + case messaging.TopicSirenTransfers: + log.Debug().Str("value", string(e.Value)).Msg("receive a siren transfer message") + ch <- e.Value + default: + log.Error().Str("topic", *e.TopicPartition.Topic).Msg("unknown topic") + } + case kafka.PartitionEOF: + fmt.Printf("%% Reached %v\n", e) + case kafka.Error: + // Errors should generally be considered as informational, the client will try to automatically recover + fmt.Fprintf(os.Stderr, "%% Error: %v\n", e) + } + } + } +} + +func save(ch chan []byte) { + transfers := make([][]byte, 0) + timer := time.After(500 * time.Millisecond) + for { + select { + case <-timer: + timer = time.After(500 * time.Millisecond) + if len(transfers) == 0 { + continue + } + err := bulkInsert(transfers) + if err != nil { + log.Error().Err(err).Msg("failed to bulk insert") + } + transfers = make([][]byte, 0) + log.Debug().Msg("tick ...") + case value, ok := <-ch: + if !ok { + log.Debug().Msg("ch not ok") + return + } + transfers = append(transfers, value) + log.Debug().Str("new value", string(value)).Msg("new value appended") + } + } +} + +func bulkInsert(transfers [][]byte) error { + ctx := context.Background() + valueStrings := make([]string, 0) + valueArgs := make([]interface{}, 0) + i := 0 + for _, t := range transfers { + var transfer messaging.TransferMessage + err := json.Unmarshal(t, &transfer) + if err != nil { + log.Error().Str("value", string(t)).Msg("failed to unmarshal transfer") + return err + } + if transfer.Status == "done" { + stmt := "UPDATE transfers SET status = $1 WHERE id = $2" + log.Debug().Str("stmt", stmt).Msg("update transfers") + result, err := db.ExecContext(ctx, stmt, transfer.Status, transfer.Id) + fmt.Println(result, err) + if err != nil { + continue + } + rows, err := result.RowsAffected() + fmt.Println(rows, err) + } else { + valueStrings = append(valueStrings, fmt.Sprintf("($%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d, $%d)", i*10+1, i*10+2, i*10+3, i*10+4, i*10+5, i*10+6, i*10+7, i*10+8, i*10+9, i*10+10)) + valueArgs = append(valueArgs, transfer.Id) + valueArgs = append(valueArgs, transfer.UserId) + valueArgs = append(valueArgs, transfer.Currency) + valueArgs = append(valueArgs, transfer.Type) + valueArgs = append(valueArgs, transfer.Amount) + valueArgs = append(valueArgs, 0.0) + valueArgs = append(valueArgs, "") + valueArgs = append(valueArgs, transfer.CreatedAt) + valueArgs = append(valueArgs, transfer.CreatedAt) + valueArgs = append(valueArgs, transfer.Status) + i++ + } + } + if len(valueStrings) == 0 { + return nil + } + stmt := fmt.Sprintf("INSERT INTO transfers (id, user_id, currency, type, amount, fee, address, created_at, updated_at, status) VALUES %s", strings.Join(valueStrings, ",")) + log.Debug().Str("stmt", stmt).Msg("insert transfers") + result, err := db.ExecContext(ctx, stmt, valueArgs...) + fmt.Println(result, err) + if err != nil { + return err + } + rows, err := result.RowsAffected() + fmt.Println(rows, err) + return err +} diff --git a/svc-wallet/Dockerfile b/svc-wallet/Dockerfile new file mode 100644 index 0000000..45de35b --- /dev/null +++ b/svc-wallet/Dockerfile @@ -0,0 +1,23 @@ +FROM gcr.io/cloud-builders/go:latest as builder + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache pkgconfig librdkafka-dev + +WORKDIR /go/src/siren/ +COPY . . + +ENV GOPATH=/go +RUN GO111MODULE=on go install -v ./svc-gateway +RUN GO111MODULE=on go install -v ./svc-match +RUN GO111MODULE=on go install -v ./svc-memorystore +RUN GO111MODULE=on go install -v ./svc-sql +RUN GO111MODULE=on go install -v ./svc-wallet +RUN GO111MODULE=on go install -v ./svc-wallet/ethereum +RUN GO111MODULE=on go install -v ./svc-ws + +FROM alpine:3.8 + +WORKDIR /siren/ +COPY --from=builder /go/bin/svc-wallet . + +ENTRYPOINT ["/siren/svc-wallet"] diff --git a/svc-wallet/bitcoin/main.go b/svc-wallet/bitcoin/main.go new file mode 100644 index 0000000..50e8d8d --- /dev/null +++ b/svc-wallet/bitcoin/main.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("vim-go") +} diff --git a/svc-wallet/ethereum/Dockerfile b/svc-wallet/ethereum/Dockerfile new file mode 100644 index 0000000..95e0bed --- /dev/null +++ b/svc-wallet/ethereum/Dockerfile @@ -0,0 +1,26 @@ +FROM gcr.io/cloud-builders/go:latest as builder + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache pkgconfig librdkafka-dev + +WORKDIR /go/src/siren/ +COPY . . + +ENV GOPATH=/go +RUN GO111MODULE=on go install -v ./svc-gateway +RUN GO111MODULE=on go install -v ./svc-match +RUN GO111MODULE=on go install -v ./svc-memorystore +RUN GO111MODULE=on go install -v ./svc-sql +RUN GO111MODULE=on go install -v ./svc-wallet +RUN GO111MODULE=on go install -v ./svc-wallet/ethereum +RUN GO111MODULE=on go install -v ./svc-ws + +FROM alpine:3.8 + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache librdkafka + +WORKDIR /siren/ +COPY --from=builder /go/bin/ethereum . + +ENTRYPOINT ["/siren/ethereum"] diff --git a/svc-wallet/ethereum/events.go b/svc-wallet/ethereum/events.go new file mode 100644 index 0000000..8c45461 --- /dev/null +++ b/svc-wallet/ethereum/events.go @@ -0,0 +1,150 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "math/big" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/params" + "github.com/gofrs/uuid" + "github.com/shopspring/decimal" + + "github.com/en/siren/messaging" + log "github.com/en/siren/utils/glog" +) + +var Confirmations = big.NewInt(30) + +// block number -> transfer_id +var pendingTxs map[string][]string + +var Ether, _ = decimal.NewFromString(big.NewInt(params.Ether).String()) + +func loop(client *ethclient.Client) { + heads := make(chan *types.Header, 16) + sub, err := client.SubscribeNewHead(context.Background(), heads) + if err != nil { + log.Fatal().Msgf("Failed to subscribe to head events: %v", err) + } + defer sub.Unsubscribe() + + for { + select { + case head := <-heads: + timestamp := time.Unix(head.Time.Int64(), 0) + log.Debug().Interface("time", timestamp) + if time.Since(timestamp) > time.Hour { + log.Warn().Interface("number", head.Number).Interface("hash", head.Hash()).Interface("age", timestamp).Msg("Skipping faucet refresh, head too old") + continue + } + log.Info().Interface("number", head.Number).Interface("hash", head.Hash()).Interface("age", timestamp).Msg("Updated faucet state") + _, err := client.BlockByHash(context.Background(), head.Hash()) + if err != nil { + log.Debug().Err(err).Msg("BlockByHash") + } + block, err := client.BlockByNumber(context.Background(), head.Number) + if err != nil { + log.Debug().Err(err).Msg("1") + continue + } + + completed := new(big.Int).Sub(head.Number, Confirmations) + fmt.Printf("pending_txs: %v\n", pendingTxs) + fmt.Printf("head: %v, completed: %v\n", head.Number, completed) + if txs, ok := pendingTxs[completed.String()]; ok { + for _, tx := range txs { + transfer := messaging.TransferMessage{ + Id: tx, + Currency: "ETH", + Type: "Deposit", + Status: "done", + } + b, err := json.Marshal(transfer) + if err != nil { + log.Error().Err(err).Msg("failed to marshal kafka transfer message") + continue + } + err = producer.Produce(&kafka.Message{ + TopicPartition: kafka.TopicPartition{Topic: &messaging.TopicSirenTransfers, Partition: kafka.PartitionAny}, + Value: b, + // Headers: []kafka.Header{{Key: "myTestHeader", Value: []byte("header values are binary")}}, + }, nil) + } + delete(pendingTxs, completed.String()) + } + for _, tx := range block.Transactions() { + fmt.Println("tx_hash", tx.Hash().Hex()) // 0xREMOVED + fmt.Println(tx.Value().String()) // 10000000000000000 + fmt.Println(tx.Gas()) // 105000 + fmt.Println(tx.GasPrice().Uint64()) // 102000000000 + fmt.Println(tx.Nonce()) // 110644 + fmt.Println(tx.Data()) // [] + if tx.To() != nil { + fmt.Println("to:", tx.To().Hex()) // 0xREMOVED + } else { + log.Warn().Msg("to is nil") + } + + chainID, err := client.NetworkID(context.Background()) + if err != nil { + log.Fatal().Err(err).Msg("") + } + + if msg, err := tx.AsMessage(types.NewEIP155Signer(chainID)); err == nil { + fmt.Println("from:", msg.From().Hex()) // 0xREMOVED + } + + receipt, err := client.TransactionReceipt(context.Background(), tx.Hash()) + if err != nil { + log.Fatal().Err(err).Msg("2") + } + + fmt.Println(receipt.Status) // 1 + if tx.Value().Cmp(new(big.Int)) == 1 { + if info, ok := addresses[tx.To().Hex()]; ok { + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + continue + } + value, err := decimal.NewFromString(tx.Value().String()) + if err != nil { + log.Error().Err(err).Msg("wrong decimal") + continue + } + amount := value.Div(Ether) + transfer := messaging.TransferMessage{ + Id: id.String(), + UserId: info.userId, + Currency: "ETH", + Type: "Deposit", + Amount: amount, + Status: "pending", + CreatedAt: time.Now(), + } + b, err := json.Marshal(transfer) + if err != nil { + log.Error().Err(err).Msg("failed to marshal kafka transfer message") + continue + } + err = producer.Produce(&kafka.Message{ + TopicPartition: kafka.TopicPartition{Topic: &messaging.TopicSirenTransfers, Partition: kafka.PartitionAny}, + Value: b, + // Headers: []kafka.Header{{Key: "myTestHeader", Value: []byte("header values are binary")}}, + }, nil) + if pendingTxs[head.Number.String()] == nil { + pendingTxs[head.Number.String()] = make([]string, 0) + } + pendingTxs[head.Number.String()] = append(pendingTxs[head.Number.String()], id.String()) + } + } + } + + } + } +} diff --git a/svc-wallet/ethereum/hdwallet.go b/svc-wallet/ethereum/hdwallet.go new file mode 100644 index 0000000..3027516 --- /dev/null +++ b/svc-wallet/ethereum/hdwallet.go @@ -0,0 +1,36 @@ +package main + +import ( + "fmt" + + "github.com/miguelmota/go-ethereum-hdwallet" + + log "github.com/en/siren/utils/glog" +) + +const ( + HDPathFormat = "m/44'/60'/0'/0/%d" +) + +var ( + wallet *hdwallet.Wallet + accountIndex uint32 +) + +func initHDWallet() (err error) { + // TODO: secret + mnemonic := "REMOVED REMOVED REMOVED REMOVED REMOVED REMOVED REMOVED REMOVED REMOVED REMOVED REMOVED REMOVED" + wallet, err = hdwallet.NewFromMnemonic(mnemonic) + return +} + +func getAddress(accountIndex uint32) (string, error) { + path := hdwallet.MustParseDerivationPath(fmt.Sprintf(HDPathFormat, accountIndex)) + account, err := wallet.Derive(path, false) + if err != nil { + log.Fatal().Err(err).Msg("failed to derive account") + return "", err + } + + return account.Address.Hex(), nil +} diff --git a/svc-wallet/ethereum/main.go b/svc-wallet/ethereum/main.go new file mode 100644 index 0000000..b2812c4 --- /dev/null +++ b/svc-wallet/ethereum/main.go @@ -0,0 +1,155 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + "net" + "strings" + "time" + + "github.com/ethereum/go-ethereum/ethclient" + _ "github.com/lib/pq" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + pb "github.com/en/siren/protos" + log "github.com/en/siren/utils/glog" +) + +const ( + port = ":50053" +) + +var db *sql.DB + +type addressInfo struct { + userId string + accountIndex uint32 +} + +var addresses map[string]addressInfo + +type server struct{} + +func (s *server) GetAddress(ctx context.Context, in *pb.GetAddressRequest) (*pb.GetAddressResponse, error) { + response := new(pb.GetAddressResponse) + var address string + var addressSig string + err := db.QueryRowContext(ctx, "SELECT address, address_sig FROM deposit_addresses_eth WHERE user_id = $1", in.UserId).Scan(&address, &addressSig) + switch { + case err == sql.ErrNoRows: + log.Printf("No address with user_id %s", in.UserId) + address, err = getAddress(accountIndex) + now := time.Now() + result, err := db.ExecContext(ctx, "INSERT INTO deposit_addresses_eth (address, address_sig, user_id, created_at, updated_at, status) VALUES ($1, $2, $3, $4, $5, $6)", address, "", in.UserId, now, now, "") + if err != nil { + log.Error().Err(err).Msg("insert error") + return response, err + } + rows, err := result.RowsAffected() + if err != nil { + log.Error().Err(err).Msg("rows affected error") + return response, err + } + if rows != 1 { + log.Error().Int64("rows", rows).Msg("rows != 1") + return response, err + } + // TODO: atomic + accountIndex = accountIndex + 1 + case err != nil: + log.Fatal().Err(err).Msg("") + return response, err + } + response.Address = address + return response, nil +} + +func main() { + log.Info().Msg("Starting siren-wallet ethereum") + + viper.SetEnvPrefix("wallet") + viper.AutomaticEnv() + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + + pqInfo := fmt.Sprintf("user=%s password=%s host=%s port=5432 dbname=%s sslmode=disable", + viper.GetString("db-user"), + viper.GetString("db-password"), + viper.GetString("db-host"), + viper.GetString("db-dbname")) + var err error + db, err = sql.Open("postgres", pqInfo) + if err != nil { + log.Fatal().Msgf("failed to open database: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + if err := db.PingContext(ctx); err != nil { + log.Fatal().Msgf("failed to ping: %v", err) + } + + // init account index + err = db.QueryRowContext(ctx, "SELECT id FROM deposit_addresses_eth ORDER BY id DESC LIMIT 1").Scan(&accountIndex) + switch { + case err == sql.ErrNoRows: + log.Info().Msg("Maybe we have a brand new database, set account_index to 1") + accountIndex = 1 + case err != nil: + log.Fatal().Err(err).Msg("") + default: + accountIndex = accountIndex + 1 + log.Info().Msgf("Set initial account_index to %d", accountIndex) + } + // init addresses map + addresses = make(map[string]addressInfo) + rows, err := db.QueryContext(ctx, "SELECT id, address, user_id FROM deposit_addresses_eth") + if err != nil { + log.Fatal().Err(err).Msg("") + } + defer rows.Close() + + for rows.Next() { + var address string + var ai addressInfo + if err := rows.Scan(&ai.accountIndex, &address, &ai.userId); err != nil { + log.Fatal().Err(err).Msg("") + } + + addresses[address] = ai + } + if err := rows.Err(); err != nil { + log.Fatal().Err(err).Msg("") + } + fmt.Println(addresses) + pendingTxs = make(map[string][]string) + // TODO: init pending list, check head number, clean + + err = initHDWallet() + if err != nil { + log.Fatal().Msgf("Failed to init HD Wallet: %v", err) + } + + client, err := ethclient.Dial("ws://kovan:8546") + if err != nil { + log.Fatal().Msgf("Failed to connect to URL %v", err) + } + + initMessaging() + go loop(client) + go transferFunds(client) + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatal().Err(err).Msg("failed to listen") + } + s := grpc.NewServer() + // TODO + pb.RegisterEthereumServer(s, &server{}) + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatal().Err(err).Msg("failed to serve") + } +} diff --git a/svc-wallet/ethereum/messaging.go b/svc-wallet/ethereum/messaging.go new file mode 100644 index 0000000..798bbdd --- /dev/null +++ b/svc-wallet/ethereum/messaging.go @@ -0,0 +1,49 @@ +package main + +import ( + "fmt" + "os" + + "github.com/confluentinc/confluent-kafka-go/kafka" +) + +var producer *kafka.Producer + +func initMessaging() { + var err error + producer, err = kafka.NewProducer(&kafka.ConfigMap{ + "bootstrap.servers": "REMOVED.us-central1.gcp.confluent.cloud:9092", + "api.version.request": true, + "broker.version.fallback": "0.10.0.0", + "api.version.fallback.ms": 0, + "sasl.mechanisms": "PLAIN", + "security.protocol": "SASL_SSL", + "ssl.ca.location": "/etc/ssl/cert.pem", + "sasl.username": "REMOVED", + "sasl.password": "REMOVED", + }) + + if err != nil { + fmt.Printf("Failed to create producer: %s\n", err) + os.Exit(1) + } + + fmt.Printf("Created Producer %v\n", producer) + + go func() { + for e := range producer.Events() { + switch ev := e.(type) { + case *kafka.Message: + m := ev + if m.TopicPartition.Error != nil { + fmt.Printf("Delivery failed: %v\n", m.TopicPartition.Error) + } else { + fmt.Printf("Delivered message to topic %s [%d] at offset %v\n", + *m.TopicPartition.Topic, m.TopicPartition.Partition, m.TopicPartition.Offset) + } + default: + fmt.Printf("Ignored event: %s\n", ev) + } + } + }() +} diff --git a/svc-wallet/ethereum/timer.go b/svc-wallet/ethereum/timer.go new file mode 100644 index 0000000..da7f550 --- /dev/null +++ b/svc-wallet/ethereum/timer.go @@ -0,0 +1,62 @@ +package main + +import ( + "context" + "fmt" + "math/big" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/miguelmota/go-ethereum-hdwallet" + + log "github.com/en/siren/utils/glog" +) + +var toAddress = common.HexToAddress("0xREMOVED") + +func transferFunds(client *ethclient.Client) { + timer := time.After(1 * time.Hour) + for { + select { + case <-timer: + timer = time.After(1 * time.Hour) + for k, v := range addresses { + account := common.HexToAddress(k) + balance, err := client.BalanceAt(context.Background(), account, nil) + if err != nil { + log.Fatal().Err(err).Msg("") + continue + } + if balance.Cmp(new(big.Int)) == 1 { + nonce := uint64(0) + gasLimit := uint64(21000) + gasPrice := big.NewInt(21000000000) + var data []byte + + tx := types.NewTransaction(nonce, toAddress, balance, gasLimit, gasPrice, data) + + path := hdwallet.MustParseDerivationPath(fmt.Sprintf(HDPathFormat, v.accountIndex)) + account, err := wallet.Derive(path, false) + if err != nil { + log.Fatal().Err(err).Msg("") + continue + } + signedTx, err := wallet.SignTx(account, tx, nil) + if err != nil { + log.Fatal().Err(err).Msg("") + continue + } + err = client.SendTransaction(context.Background(), signedTx) + if err != nil { + log.Fatal().Err(err).Msg("") + continue + } + + fmt.Printf("tx sent: %s", signedTx.Hash().Hex()) + } + } + } + } +} diff --git a/svc-wallet/main.go b/svc-wallet/main.go new file mode 100644 index 0000000..2681f7a --- /dev/null +++ b/svc-wallet/main.go @@ -0,0 +1,149 @@ +package main + +import ( + "context" + "database/sql" + "fmt" + "net" + "strings" + "time" + + "github.com/gofrs/uuid" + _ "github.com/lib/pq" + "github.com/shopspring/decimal" + "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/reflection" + + pb "github.com/en/siren/protos" + log "github.com/en/siren/utils/glog" +) + +const ( + port = ":50051" +) + +var db *sql.DB +var bitcoinClient pb.BitcoinClient +var ethereumClient pb.EthereumClient + +type server struct{} + +func (s *server) GetAddress(ctx context.Context, in *pb.GetAddressRequest) (*pb.GetAddressResponse, error) { + userId := in.GetUserId() + currency := in.GetCurrency() + log.Info().Str("user_id", userId).Str("currency", currency).Msg("get address") + response := new(pb.GetAddressResponse) + var err error + switch currency { + case "BTC": + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + response, err = bitcoinClient.GetAddress(ctx, in) + if err != nil { + log.Error().Err(err).Msg("failed to generate new BTC address") + return response, err + } + case "ETH": + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + response, err = ethereumClient.GetAddress(ctx, in) + if err != nil { + log.Error().Err(err).Msg("failed to generate new ETH address") + return response, err + } + default: + log.Error().Str("currency", currency).Msg("unknown currency") + // TODO: return error + return response, nil + } + return response, nil +} + +func (s *server) Withdraw(ctx context.Context, in *pb.WithdrawRequest) (*pb.WithdrawResponse, error) { + response := new(pb.WithdrawResponse) + userId := in.GetUserId() + currency := in.GetCurrency() + address := in.GetAddress() + log.Info().Str("user_id", userId).Str("currency", currency).Str("amount", in.GetAmount()).Msg("withdraw request") + amount, err := decimal.NewFromString(in.GetAmount()) + if err != nil { + return response, err + } + fee := decimal.NewFromFloat(0.0) + now := time.Now() + id, err := uuid.NewV4() + if err != nil { + log.Error().Err(err).Msg("failed to generate UUID") + return response, err + } + result, err := db.ExecContext(ctx, "INSERT INTO transfers (id, user_id, currency, type, amount, fee, address, created_at, updated_at, status) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)", id, userId, currency, "Withdrawal", amount, fee, address, now, now, "pending") + if err != nil { + log.Error().Err(err).Msg("insert error") + return response, err + } + rows, err := result.RowsAffected() + if err != nil { + log.Error().Err(err).Msg("rows affected error") + return response, err + } + if rows != 1 { + log.Error().Int64("rows", rows).Msg("rows != 1") + return response, err + } + return response, nil +} + +func main() { + log.Info().Msg("Starting siren-wallet") + + viper.SetEnvPrefix("wallet") + viper.AutomaticEnv() + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + + pqInfo := fmt.Sprintf("user=%s password=%s host=%s port=5432 dbname=%s sslmode=disable", + viper.GetString("db-user"), + viper.GetString("db-password"), + viper.GetString("db-host"), + viper.GetString("db-dbname")) + var err error + db, err = sql.Open("postgres", pqInfo) + if err != nil { + log.Fatal().Msgf("failed to open database: %v", err) + } + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) + defer cancel() + if err := db.PingContext(ctx); err != nil { + log.Fatal().Msgf("failed to ping: %v", err) + } + + bitcoinAddr := viper.GetString("bitcoin") + conn, err := grpc.Dial(bitcoinAddr, grpc.WithInsecure()) + if err != nil { + log.Fatal().Err(err).Msg("did not connect") + } + defer conn.Close() + bitcoinClient = pb.NewBitcoinClient(conn) + log.Info().Str("addr", bitcoinAddr).Msg("connected to bitcoin") + + ethereumAddr := viper.GetString("ethereum") + conn, err = grpc.Dial(ethereumAddr, grpc.WithInsecure()) + if err != nil { + log.Fatal().Err(err).Msg("did not connect") + } + defer conn.Close() + ethereumClient = pb.NewEthereumClient(conn) + log.Info().Str("addr", ethereumAddr).Msg("connected to ethereum") + + lis, err := net.Listen("tcp", port) + if err != nil { + log.Fatal().Err(err).Msg("failed to listen") + } + s := grpc.NewServer() + pb.RegisterWalletServer(s, &server{}) + reflection.Register(s) + if err := s.Serve(lis); err != nil { + log.Fatal().Err(err).Msg("failed to serve") + } +} diff --git a/svc-ws/Dockerfile b/svc-ws/Dockerfile new file mode 100644 index 0000000..9c9f9a0 --- /dev/null +++ b/svc-ws/Dockerfile @@ -0,0 +1,27 @@ +FROM gcr.io/cloud-builders/go:latest as builder + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache pkgconfig librdkafka-dev + +WORKDIR /go/src/siren/ +COPY . . + +ENV GOPATH=/go +RUN GO111MODULE=on go install -v ./svc-gateway +RUN GO111MODULE=on go install -v ./svc-match +RUN GO111MODULE=on go install -v ./svc-memorystore +RUN GO111MODULE=on go install -v ./svc-sql +RUN GO111MODULE=on go install -v ./svc-wallet +RUN GO111MODULE=on go install -v ./svc-wallet/ethereum +RUN GO111MODULE=on go install -v ./svc-ws + +FROM alpine:3.8 + +RUN sed -i -e 's/v[[:digit:]]\.[[:digit:]]/edge/g' /etc/apk/repositories && \ + apk add --update --update-cache librdkafka ca-certificates && \ + update-ca-certificates + +WORKDIR /siren/ +COPY --from=builder /go/bin/svc-ws . + +ENTRYPOINT ["/siren/svc-ws"] diff --git a/svc-ws/main.go b/svc-ws/main.go new file mode 100644 index 0000000..43cad58 --- /dev/null +++ b/svc-ws/main.go @@ -0,0 +1,1107 @@ +package main + +import ( + "container/list" + "context" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/confluentinc/confluent-kafka-go/kafka" + "github.com/dgrijalva/jwt-go" + "github.com/gorilla/mux" + "github.com/gorilla/websocket" + "github.com/spf13/viper" + "github.com/urfave/negroni" + "google.golang.org/grpc" + + pb "github.com/en/siren/protos" + log "github.com/en/siren/utils/glog" +) + +var memorystoreClient pb.MemorystoreClient +var matchClient pb.MatchClient + +type Jwks struct { + Keys []JSONWebKeys `json:"keys"` +} + +type JSONWebKeys struct { + Kty string `json:"kty"` + Kid string `json:"kid"` + Use string `json:"use"` + N string `json:"n"` + E string `json:"e"` + X5c []string `json:"x5c"` +} + +func validationKeyGetter(token *jwt.Token) (interface{}, error) { + // Verify 'aud' claim + aud := viper.GetString("auth0-audience") + checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) + if !checkAud { + return token, errors.New("Invalid audience.") + } + // Verify 'iss' claim + iss := "https://" + viper.GetString("auth0-domain") + "/" + checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) + if !checkIss { + return token, errors.New("Invalid issuer.") + } + + cert, err := getPemCert(token) + if err != nil { + panic(err.Error()) + } + + result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) + return result, nil +} + +func getPemCert(token *jwt.Token) (string, error) { + cert := "" + resp, err := http.Get("https://" + viper.GetString("auth0-domain") + "/.well-known/jwks.json") + + if err != nil { + return cert, err + } + defer resp.Body.Close() + + var jwks = Jwks{} + err = json.NewDecoder(resp.Body).Decode(&jwks) + + if err != nil { + return cert, err + } + + for k, _ := range jwks.Keys { + if token.Header["kid"] == jwks.Keys[k].Kid { + cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----" + } + } + + if cert == "" { + err := errors.New("Unable to find appropriate key.") + return cert, err + } + + return cert, nil +} + +type userOrder struct { + Id int `json:"id"` + Ctime int64 `json:"ctime"` + TakerFee string `json:"taker_fee"` + Market string `json:"market"` + Side int `json:"side"` + Source string `json:"source"` + DealMoney string `json:"deal_money"` + Type int `json:"type"` + User string `json:"user"` + Mtime int64 `json:"mtime"` + DealStock string `json:"deal_stock"` + Price string `json:"price"` + Amount string `json:"amount"` + MakerFee string `json:"maker_fee"` + Left string `json:"left"` + DealFee string `json:"deal_fee"` +} +type orderMessage struct { + Event int64 `json:"event"` + Order json.RawMessage `json:"order"` + Stock string `json:"stock"` + Money string `json:"money"` +} + +type rpcReply struct { + Result json.RawMessage `json:"result"` + Id json.RawMessage `json:"id"` + Error json.RawMessage `json:"error"` +} + +type rpcToClient struct { + Method string `json:"method"` + Params json.RawMessage `json:"result"` + Id json.RawMessage `json:"id"` +} + +var upgrader = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +type dealsVars struct { + Conns *list.List + LastTradeId int64 +} + +type orderVars struct { + Conn *websocket.Conn + ProductId string +} + +type assetVars struct { + Conn *websocket.Conn + Name string +} + +var kline map[string]*list.List +var depth map[string]*list.List +var today map[string]*list.List +var deals map[string]dealsVars +var order map[string]*list.List +var asset map[string]*list.List + +func metimer(interval time.Duration) error { + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + for k, v := range depth { + if v.Len() == 0 { + break + } + fields := strings.Split(k, "-") + // productId := strings.Join(fields[:len(fields)-1], "-") + productId := strings.Join(fields, "") + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.OrderBookDepth(ctx, &pb.OrderBookDepthRequest{ + Symbol: productId, + Limit: 100, + }) + if err != nil { + // TODO: return error message + log.Error().Msg("failed to get order book depth") + continue + } + var rtc Message + rtc.Type = "depth-snapshot" + rtc.ProductId = k + // TODO: len(r.Asks) == 0 + for _, data := range r.Asks { + rtc.Asks = append(rtc.Asks, SnapshotEntry{Price: fmt.Sprintf("%.6f", data.Price), Size: fmt.Sprintf("%.6f", data.Quantity)}) + } + for _, data := range r.Bids { + rtc.Bids = append(rtc.Bids, SnapshotEntry{Price: fmt.Sprintf("%.6f", data.Price), Size: fmt.Sprintf("%.6f", data.Quantity)}) + } + b, err := json.Marshal(rtc) + if err != nil { + log.Error().Msg("marshal reply") + continue + } + // TODO: compare last broadcast time + for e := v.Front(); e != nil; { + next := e.Next() + conn, ok := e.Value.(*websocket.Conn) + if ok { + err := conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("failed to write message") + v.Remove(e) + } + } else { + log.Error().Msg("type assertion failed") + v.Remove(e) + } + e = next + } + } + } + } +} + +func mptimer(interval time.Duration) error { + ticker := time.NewTicker(interval) + defer ticker.Stop() + ctx := context.Background() + for { + select { + case <-ticker.C: + for k, v := range today { + if v.Len() == 0 { + break + } + fields := strings.Split(k, "-") + // productId := strings.Join(fields[:len(fields)-1], "-") + productId := strings.Join(fields, "") + r, err := memorystoreClient.MarketStatusToday(ctx, &pb.MarketStatusTodayRequest{ + Market: productId, + }) + if err != nil { + // TODO: return error message + log.Error().Msg("get market status today") + continue + } + var rtc Message + rtc.Type = "ticker" + rtc.ProductId = k + rtc.BestAsk = "0" + rtc.BestBid = "0" + rtc.High24h = fmt.Sprintf("%.6f", r.High) + rtc.Low24h = fmt.Sprintf("%.6f", r.Low) + rtc.Open24h = fmt.Sprintf("%.6f", r.Open) + rtc.Price = fmt.Sprintf("%.6f", r.Last) + rtc.Volume24h = fmt.Sprintf("%.6f", r.Volume) + rtc.Volume30d = fmt.Sprintf("%.6f", r.Deal) // TODO: for debugging + b, err := json.Marshal(rtc) + if err != nil { + log.Error().Msg("marshal reply") + continue + } + for e := v.Front(); e != nil; { + next := e.Next() + conn, ok := e.Value.(*websocket.Conn) + if ok { + err := conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("failed to write message") + v.Remove(e) + } + } else { + log.Error().Msg("type assertion failed") + v.Remove(e) + } + e = next + } + } + + for k, v := range deals { + if v.Conns.Len() == 0 { + break + } + fields := strings.Split(k, "-") + // productId := strings.Join(fields[:len(fields)-1], "-") + productId := strings.Join(fields, "") + r, err := memorystoreClient.MarketDeals(ctx, &pb.MarketDealsRequest{ + Market: productId, + Limit: 100, + LastId: uint64(v.LastTradeId), + }) + if err != nil { + // TODO: return error message + log.Error().Msg("get market deals") + continue + } + + for _, item := range r.Items { + var rtc Message + rtc.Type = "match" + rtc.ProductId = k + rtc.TradeId = int(item.Id) + rtc.Side = item.Type + rtc.Size = item.Amount + rtc.Price = item.Price + rtc.Time = Time(time.Unix(0, int64(item.Time*1000*1000*1000))) + b, err := json.Marshal(rtc) + if err != nil { + log.Error().Msg("marshal reply") + continue + } + for e := v.Conns.Front(); e != nil; { + next := e.Next() + conn, ok := e.Value.(*websocket.Conn) + if ok { + err := conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("failed to write message") + v.Conns.Remove(e) + } + } else { + log.Error().Msg("type assertion failed") + v.Conns.Remove(e) + } + e = next + } + } + if len(r.Items) > 0 { + fmt.Println("trade items:", r.Items) + deals[k] = dealsVars{deals[k].Conns, int64(r.Items[0].Id)} + } + } + } + } +} + +func writeErrorMessage(conn *websocket.Conn, message string) { + msg := Message{ + Type: "error", + Message: message, + } + b, err := json.Marshal(msg) + if err != nil { + log.Error().Str("msg", message).Msg("marshal error message") + return + } + + err = conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("write error message") + } +} + +func writeAuthSuccessMessage(conn *websocket.Conn) { + msg := Message{ + Type: "auth-success", + } + b, err := json.Marshal(msg) + if err != nil { + log.Error().Msg("marshal auth success message") + return + } + + err = conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("write auth-success message") + } +} + +func writeSuccessMessage(conn *websocket.Conn) { + msg := Message{ + Type: "subscriptions", + Channels: []MessageChannel{}, + } + b, err := json.Marshal(msg) + if err != nil { + log.Error().Msg("marshal success message") + return + } + + err = conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("write success message") + } +} + +func subscribeKline(conn *websocket.Conn, productIds []string) { + for _, id := range productIds { + _, ok := kline[id] + if !ok { + kline[id] = list.New() + } + exist := false + for e := kline[id].Front(); e != nil; e = e.Next() { + if e.Value == conn { + exist = true + break + } + } + if !exist { + kline[id].PushBack(conn) + } + } + writeSuccessMessage(conn) +} + +func subscribeDepth(conn *websocket.Conn, productIds []string) { + for _, id := range productIds { + _, ok := depth[id] + if !ok { + depth[id] = list.New() + } + exist := false + for e := depth[id].Front(); e != nil; e = e.Next() { + if e.Value == conn { + exist = true + break + } + } + if !exist { + depth[id].PushBack(conn) + } + } + writeSuccessMessage(conn) +} + +func subscribeTrade(conn *websocket.Conn, lastTradeId int, productIds []string) { + for _, id := range productIds { + _, ok := deals[id] + if !ok { + deals[id] = dealsVars{list.New(), int64(lastTradeId)} + } + exist := false + for e := deals[id].Conns.Front(); e != nil; e = e.Next() { + if e.Value == conn { + exist = true + break + } + } + if !exist { + deals[id].Conns.PushBack(conn) + // TODO: logger + deals[id] = dealsVars{deals[id].Conns, int64(lastTradeId)} + } + } + writeSuccessMessage(conn) +} + +func subscribeTicker(conn *websocket.Conn, productIds []string) { + for _, id := range productIds { + _, ok := today[id] + if !ok { + today[id] = list.New() + } + exist := false + for e := today[id].Front(); e != nil; e = e.Next() { + if e.Value == conn { + exist = true + break + } + } + if !exist { + today[id].PushBack(conn) + } + } + writeSuccessMessage(conn) +} + +func subscribeOrder(conn *websocket.Conn, subject string, productIds []string) { + if subject == "" { + log.Error().Msg("empty user token") + return + } + _, ok := order[subject] + if !ok { + order[subject] = list.New() + } + for _, id := range productIds { + exist := false + for e := order[subject].Front(); e != nil; e = e.Next() { + vars, ok := e.Value.(orderVars) + if ok { + if vars.Conn == conn && vars.ProductId == id { + exist = true + break + } + } else { + log.Error().Msg("type assertion failed") + } + } + if !exist { + order[subject].PushBack(orderVars{conn, id}) + } + } + writeSuccessMessage(conn) +} + +func subscribeAsset(conn *websocket.Conn, subject string, productIds []string) { + if subject == "" { + log.Error().Msg("empty user token") + return + } + _, ok := asset[subject] + if !ok { + asset[subject] = list.New() + } + + for _, id := range productIds { + exist := false + for e := asset[subject].Front(); e != nil; e = e.Next() { + vars, ok := e.Value.(assetVars) + if ok { + if vars.Conn == conn && vars.Name == id { + exist = true + break + } + } else { + log.Error().Msg("type assertion failed") + } + } + if !exist { + asset[subject].PushBack(assetVars{conn, id}) + } + } + writeSuccessMessage(conn) +} + +func unsubscribeKline(conn *websocket.Conn, productIds []string) { + for _, id := range productIds { + _, ok := kline[id] + if ok { + for e := kline[id].Front(); e != nil; { + next := e.Next() + if e.Value == conn { + kline[id].Remove(e) + log.Info().Str("market", id).Msg("unsubscribed kline") + break + } + e = next + } + } + } + writeSuccessMessage(conn) +} + +func unsubscribeDepth(conn *websocket.Conn, productIds []string) { + for _, id := range productIds { + _, ok := depth[id] + if ok { + for e := depth[id].Front(); e != nil; { + next := e.Next() + if e.Value == conn { + depth[id].Remove(e) + log.Info().Str("market", id).Msg("unsubscribed depth") + break + } + e = next + } + } + } + writeSuccessMessage(conn) +} + +func unsubscribeTrade(conn *websocket.Conn, productIds []string) { + for _, id := range productIds { + _, ok := deals[id] + if ok { + for e := deals[id].Conns.Front(); e != nil; { + next := e.Next() + if e.Value == conn { + deals[id].Conns.Remove(e) + log.Info().Str("market", id).Msg("unsubscribed trade") + break + } + e = next + } + } + } + writeSuccessMessage(conn) +} + +func unsubscribeTicker(conn *websocket.Conn, productIds []string) { + for _, id := range productIds { + _, ok := today[id] + if ok { + for e := today[id].Front(); e != nil; { + next := e.Next() + if e.Value == conn { + today[id].Remove(e) + log.Info().Str("market", id).Msg("unsubscribed ticker") + break + } + e = next + } + } + } + writeSuccessMessage(conn) +} + +func unsubscribeOrder(conn *websocket.Conn, subject string, productIds []string) { + if subject == "" { + log.Error().Msg("empty user token") + return + } + l, ok := order[subject] + if !ok { + log.Error().Str("user", subject).Msg("user haven't subscribed to any products") + return + } + + for _, id := range productIds { + for e := l.Front(); e != nil; { + next := e.Next() + vars, ok := e.Value.(orderVars) + if ok { + if vars.Conn == conn && vars.ProductId == id { + l.Remove(e) + log.Info().Str("market", id).Msg("unsubscribed order") + break + } + } else { + log.Error().Msg("type assertion failed") + l.Remove(e) + } + e = next + } + } + writeSuccessMessage(conn) +} + +func unsubscribeAsset(conn *websocket.Conn, subject string, productIds []string) { + if subject == "" { + log.Error().Msg("empty user token") + return + } + l, ok := asset[subject] + if !ok { + log.Error().Str("user", subject).Msg("user haven't subscribed to any products") + return + } + + for _, id := range productIds { + for e := l.Front(); e != nil; { + next := e.Next() + vars, ok := e.Value.(assetVars) + if ok { + if vars.Conn == conn && vars.Name == id { + l.Remove(e) + log.Info().Str("market", id).Msg("unsubscribed asset") + break + } + } else { + log.Error().Msg("type assertion failed") + l.Remove(e) + } + e = next + } + } + writeSuccessMessage(conn) +} + +func home(w http.ResponseWriter, r *http.Request) { + log.Info().Str("realIP", r.Header.Get("X-Forwarded-For")).Str("host", r.Host).Msg("Request Received.") + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + log.Warn().Msg("Upgrade failed") + return + } + defer conn.Close() + var subject string + + for { + _, message, err := conn.ReadMessage() + if err != nil { + log.Error().Msg("read from ws") + break + } + + log.Info().Str("msg", string(message)).Msg("receive from ws") + + var msg Message + err = json.Unmarshal(message, &msg) + if err != nil { + log.Error().Msg("unmarshal message") + writeErrorMessage(conn, "malformed JSON") + break + } + + switch msg.Type { + case "auth": + if msg.Token == "" { + log.Error().Msg("empty user token") + writeErrorMessage(conn, "empty user token") + return + } + parsedToken, err := jwt.Parse(msg.Token, validationKeyGetter) + if err != nil { + log.Error().Msg("Error parsing token") + writeErrorMessage(conn, "Error parsing token") + return + } + if !parsedToken.Valid { + log.Error().Interface("token", parsedToken).Msg("Token is invalid") + writeErrorMessage(conn, "Token is invalid") + return + } + claims, _ := parsedToken.Claims.(jwt.MapClaims) + subject, _ = claims["sub"].(string) + log.Info().Str("user", subject).Msg("auth") + writeAuthSuccessMessage(conn) + case "unauth": + if msg.Token == "" { + log.Error().Msg("empty user token") + writeErrorMessage(conn, "empty user token") + return + } + parsedToken, err := jwt.Parse(msg.Token, validationKeyGetter) + if err != nil { + log.Error().Msg("Error parsing token") + writeErrorMessage(conn, "Error parsing token") + return + } + if !parsedToken.Valid { + log.Error().Interface("token", parsedToken).Msg("Token is invalid") + writeErrorMessage(conn, "Token is invalid") + return + } + claims, _ := parsedToken.Claims.(jwt.MapClaims) + sub, _ := claims["sub"].(string) + + if subject == sub { + log.Info().Str("user", subject).Msg("unauth") + subject = "" + writeAuthSuccessMessage(conn) + } else { + log.Info().Str("oldUser", subject).Str("newUser", sub).Msg("unauth failed, token not match") + writeErrorMessage(conn, "token not match") + return + } + case "subscribe": + for _, c := range msg.Channels { + switch c.Name { + case "kline": + subscribeKline(conn, c.ProductIds) + case "depth": + subscribeDepth(conn, c.ProductIds) + case "ticker": + subscribeTicker(conn, c.ProductIds) + case "trade": + subscribeTrade(conn, msg.LastTradeId, c.ProductIds) + case "order": + subscribeOrder(conn, subject, c.ProductIds) + case "asset": + subscribeAsset(conn, subject, c.ProductIds) + default: + log.Error().Str("name", c.Name).Msg("unknown channel") + writeErrorMessage(conn, "unknown channel") + return + } + } + case "unsubscribe": + for _, c := range msg.Channels { + switch c.Name { + case "kline": + unsubscribeKline(conn, c.ProductIds) + case "depth": + unsubscribeDepth(conn, c.ProductIds) + case "ticker": + unsubscribeTicker(conn, c.ProductIds) + case "trade": + unsubscribeTrade(conn, c.ProductIds) + case "order": + unsubscribeOrder(conn, subject, c.ProductIds) + case "asset": + unsubscribeAsset(conn, subject, c.ProductIds) + default: + log.Error().Str("name", c.Name).Msg("unknown channel") + writeErrorMessage(conn, "unknown channel") + return + } + } + default: + log.Error().Str("type", msg.Type).Msg("unknown message type") + writeErrorMessage(conn, "unknown message type") + return + } + } +} + +func handleMessages() { + consumer, err := kafka.NewConsumer(&kafka.ConfigMap{ + "bootstrap.servers": "REMOVED.us-central1.gcp.confluent.cloud:9092", + "api.version.request": true, + "broker.version.fallback": "0.10.0.0", + "api.version.fallback.ms": 0, + "sasl.mechanisms": "PLAIN", + "security.protocol": "SASL_SSL", + "ssl.ca.location": "/etc/ssl/cert.pem", + "sasl.username": "REMOVED", + "sasl.password": "REMOVED", + "group.id": "siren-ws", + "session.timeout.ms": 6000, + "go.events.channel.enable": true, + "go.application.rebalance.enable": true, + // Enable generation of PartitionEOF when the + // end of a partition is reached. + "enable.partition.eof": true, + "auto.offset.reset": "earliest"}) + + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to create consumer: %s\n", err) + os.Exit(1) + } + log.Info().Msgf("Created Consumer %v\n", consumer) + + err = consumer.SubscribeTopics([]string{"orders", "balances"}, nil) + + for { + select { + case ev := <-consumer.Events(): + switch e := ev.(type) { + case kafka.AssignedPartitions: + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Assign(e.Partitions) + case kafka.RevokedPartitions: + fmt.Fprintf(os.Stderr, "%% %v\n", e) + consumer.Unassign() + case *kafka.Message: + fmt.Printf("%% Message on %s:\n%s: %s\n", + e.TopicPartition, string(e.Key), string(e.Value)) + switch *e.TopicPartition.Topic { + case "balances": + log.Info().Str("key", string(e.Key)).Str("value", string(e.Value)).Msg("receive balance message") + var fields []json.RawMessage + + // var msg orderMessage + err = json.Unmarshal(e.Value, &fields) + if err != nil { + log.Error().Msg("unmarshal balance message") + continue + } + + var sub string + err = json.Unmarshal(fields[1], &sub) + if err != nil { + log.Error().Msg("unmarshal auth subject") + continue + } + + var name string + err = json.Unmarshal(fields[2], &name) + if err != nil { + log.Error().Msg("unmarshal asset name") + continue + } + + l, ok := asset[sub] + if ok { + for e := l.Front(); e != nil; { + next := e.Next() + vars, ok := e.Value.(assetVars) + if !ok { + log.Error().Msg("type assertion failed") + l.Remove(e) + e = next + continue + } + if vars.Name == name { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.Balances(ctx, &pb.BalancesRequest{ + UserId: sub, + Currency: vars.Name, + }) + if err != nil { + // TODO: return error message + log.Error().Msg("failed to get balance") + continue + } + var rtc Message + if len(r.Balances) != 1 { + log.Error().Int("len", len(r.Balances)).Msg("wrong length of asset array") + continue + } + rtc.Type = "asset" + rtc.Balance = r.Balances[0].Available + r.Balances[0].Hold + rtc.Available = r.Balances[0].Available + rtc.Hold = r.Balances[0].Hold + rtc.ProductId = vars.Name + b, err := json.Marshal(rtc) + if err != nil { + log.Error().Msg("marshal reply") + continue + } + err = vars.Conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("failed to write message") + l.Remove(e) + } + } + e = next + } + } + case "orders": + log.Info().Str("key", string(e.Key)).Str("value", string(e.Value)).Msg("receive order message") + var msg orderMessage + err = json.Unmarshal(e.Value, &msg) + if err != nil { + log.Error().Msg("unmarshal order message") + continue + } + + if msg.Event == 0 { + continue + } + + var uo userOrder + err = json.Unmarshal(msg.Order, &uo) + if err != nil { + log.Error().Msg("unmarshal user order") + continue + } + + l, ok := asset[uo.User] + if ok { + for e := l.Front(); e != nil; { + next := e.Next() + vars, ok := e.Value.(assetVars) + if !ok { + log.Error().Msg("type assertion failed") + l.Remove(e) + e = next + continue + } + + if vars.Name == msg.Stock { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.Balances(ctx, &pb.BalancesRequest{ + UserId: uo.User, + Currency: vars.Name, + }) + if err != nil { + // TODO: return error message + log.Error().Msg("failed to get balance") + continue + } + var rtc Message + if len(r.Balances) != 1 { + log.Error().Int("len", len(r.Balances)).Msg("wrong length of asset array") + continue + } + rtc.Type = "asset" + rtc.Balance = r.Balances[0].Available + r.Balances[0].Hold + rtc.Available = r.Balances[0].Available + rtc.Hold = r.Balances[0].Hold + rtc.ProductId = vars.Name + b, err := json.Marshal(rtc) + if err != nil { + log.Error().Msg("marshal reply") + continue + } + err = vars.Conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("failed to write message") + l.Remove(e) + } + } else if vars.Name == msg.Money { + ctx, cancel := context.WithTimeout(context.Background(), time.Second) + defer cancel() + r, err := matchClient.Balances(ctx, &pb.BalancesRequest{ + UserId: uo.User, + Currency: vars.Name, + }) + if err != nil { + // TODO: return error message + log.Error().Msg("failed to get balance") + continue + } + var rtc Message + if len(r.Balances) != 1 { + log.Error().Int("len", len(r.Balances)).Msg("wrong length of asset array") + continue + } + rtc.Type = "asset" + rtc.Balance = r.Balances[0].Available + r.Balances[0].Hold + rtc.Available = r.Balances[0].Available + rtc.Hold = r.Balances[0].Hold + rtc.ProductId = vars.Name + b, err := json.Marshal(rtc) + if err != nil { + log.Error().Msg("marshal reply") + continue + } + err = vars.Conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Error().Msg("failed to write message") + l.Remove(e) + } + } + e = next + } + } + + l, ok = order[uo.User] + if ok { + event := strconv.FormatInt(msg.Event, 10) + params := json.RawMessage(`[` + event + `,`) + params = append(params, msg.Order...) + params = append(params, json.RawMessage(`]`)...) + rtc := rpcToClient{ + Id: json.RawMessage(`0`), + Method: "order.update", + Params: params, + } + b, err := json.Marshal(rtc) + if err != nil { + log.Error().Msg("marshal reply") + continue + } + for e := l.Front(); e != nil; { + next := e.Next() + vars, ok := e.Value.(orderVars) + if ok { + fields := strings.Split(vars.ProductId, "-") + productId := strings.Join(fields, "") + if productId == uo.Market { + err = vars.Conn.WriteMessage(websocket.TextMessage, b) + if err != nil { + log.Warn().Msg("failed to write message") + l.Remove(e) + } + } + } else { + log.Error().Msg("type assertion failed") + l.Remove(e) + } + e = next + } + } + default: + log.Error().Str("topic", *e.TopicPartition.Topic).Msg("unknown topic") + } + case kafka.PartitionEOF: + fmt.Printf("%% Reached %v\n", e) + case kafka.Error: + // Errors should generally be considered as informational, the client will try to automatically recover + fmt.Fprintf(os.Stderr, "%% Error: %v\n", e) + } + } + } + + fmt.Printf("Closing consumer\n") + consumer.Close() +} + +func main() { + log.Info().Msg("Starting siren-ws") + + viper.SetEnvPrefix("ws") + viper.AutomaticEnv() + replacer := strings.NewReplacer("-", "_") + viper.SetEnvKeyReplacer(replacer) + + { + conn, err := grpc.Dial("siren-match:50051", grpc.WithInsecure()) + if err != nil { + log.Panic().Msgf("did not connect: %v", err) + } + defer conn.Close() + matchClient = pb.NewMatchClient(conn) + log.Info().Msg("connected to siren-match:50051") + } + { + conn, err := grpc.Dial("siren-memorystore:50051", grpc.WithInsecure()) + if err != nil { + log.Panic().Msgf("did not connect: %v", err) + } + defer conn.Close() + memorystoreClient = pb.NewMemorystoreClient(conn) + log.Info().Msg("connected to siren-memorystore:50051") + } + + kline = make(map[string]*list.List) + depth = make(map[string]*list.List) + today = make(map[string]*list.List) + deals = make(map[string]dealsVars) + order = make(map[string]*list.List) + asset = make(map[string]*list.List) + + // interval := 500 * time.Millisecond + interval := 5000 * time.Millisecond + go metimer(interval) + go mptimer(interval) + go handleMessages() + + r := mux.NewRouter() + r.HandleFunc("/healthz", func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) + w.Header().Set("Content-Type", "application/json") + io.WriteString(w, `{}`) + }).Methods("GET") + r.HandleFunc("/", home) + + n := negroni.New() + n.Use(negroni.NewRecovery()) + n.UseHandler(r) + + n.Run(":3001") +} diff --git a/svc-ws/message.go b/svc-ws/message.go new file mode 100644 index 0000000..cc356e6 --- /dev/null +++ b/svc-ws/message.go @@ -0,0 +1,104 @@ +package main + +import ( + "encoding/json" +) + +type Message struct { + Type string `json:"type"` + ProductId string `json:"product_id,omitempty"` + ProductIds []string `json:"product_ids,omitempty"` + TradeId int `json:"trade_id,number,omitempty"` + OrderId string `json:"order_id,omitempty"` + Sequence int64 `json:"sequence,number,omitempty"` + MakerOrderId string `json:"maker_order_id,omitempty"` + TakerOrderId string `json:"taker_order_id,omitempty"` + Time Time `json:"time,string,omitempty"` + RemainingSize string `json:"remaining_size,omitempty"` + NewSize string `json:"new_size,omitempty"` + OldSize string `json:"old_size,omitempty"` + Size string `json:"size,omitempty"` + Price string `json:"price,omitempty"` + Side string `json:"side,omitempty"` + Reason string `json:"reason,omitempty"` + OrderType string `json:"order_type,omitempty"` + Funds string `json:"funds,omitempty"` + NewFunds string `json:"new_funds,omitempty"` + OldFunds string `json:"old_funds,omitempty"` + Message string `json:"message,omitempty"` + Bids []SnapshotEntry `json:"bids,omitempty"` + Asks []SnapshotEntry `json:"asks,omitempty"` + Changes []SnapshotChange `json:"changes,omitempty"` + LastSize string `json:"last_size,omitempty"` + BestBid string `json:"best_bid,omitempty"` + BestAsk string `json:"best_ask,omitempty"` + Channels []MessageChannel `json:"channels,omitempty"` + UserId string `json:"user_id,omitempty"` + ProfileId string `json:"profile_id,omitempty"` + LastTradeId int `json:"last_trade_id,omitempty"` + // new + High24h string `json:"high_24h,omitempty"` + Low24h string `json:"low_24h,omitempty"` + Open24h string `json:"open_24h,omitempty"` + Volume24h string `json:"volume_24h,omitempty"` + Volume30d string `json:"volume_30d,omitempty"` + Balance string `json:"balance,omitempty"` + Available string `json:"available,omitempty"` + Hold string `json:"hold,omitempty"` + Token string `json:"token,omitempty"` +} + +type MessageChannel struct { + Name string `json:"name"` + ProductIds []string `json:"product_ids"` +} + +type SnapshotChange struct { + Side string + Price string + Size string +} + +type SnapshotEntry struct { + Price string + Size string +} + +type SignedMessage struct { + Message + Key string `json:"key"` + Passphrase string `json:"passphrase"` + Timestamp string `json:"timestamp"` + Signature string `json:"signature"` +} + +func (e *SnapshotEntry) MarshalJSON() ([]byte, error) { + return json.Marshal([]string{e.Price, e.Size}) +} + +func (e *SnapshotEntry) UnmarshalJSON(data []byte) error { + var entry []string + + if err := json.Unmarshal(data, &entry); err != nil { + return err + } + + e.Price = entry[0] + e.Size = entry[1] + + return nil +} + +func (e *SnapshotChange) UnmarshalJSON(data []byte) error { + var entry []string + + if err := json.Unmarshal(data, &entry); err != nil { + return err + } + + e.Side = entry[0] + e.Price = entry[1] + e.Size = entry[2] + + return nil +} diff --git a/svc-ws/time.go b/svc-ws/time.go new file mode 100644 index 0000000..1152e2c --- /dev/null +++ b/svc-ws/time.go @@ -0,0 +1,65 @@ +package main + +import ( + // "fmt" + "strings" + "time" +) + +type ServerTime struct { + ISO string `json:"iso"` + Epoch float64 `json:"epoch,number"` +} + +// func (c *Client) GetTime() (ServerTime, error) { +// var serverTime ServerTime +// +// url := fmt.Sprintf("/time") +// _, err := c.Request("GET", url, nil, &serverTime) +// return serverTime, err +// } + +type Time time.Time + +func (t *Time) UnmarshalJSON(data []byte) error { + var err error + var parsedTime time.Time + + if string(data) == "null" { + *t = Time(time.Time{}) + return nil + } + + layouts := []string{ + "2006-01-02 15:04:05+00", + "2006-01-02T15:04:05.999999Z", + + "2006-01-02 15:04:05.999999", + "2006-01-02T15:04:05Z", + "2006-01-02 15:04:05.999999+00"} + for _, layout := range layouts { + parsedTime, err = time.Parse(layout, + strings.Replace(string(data), "\"", "", -1)) + if err != nil { + continue + } + + break + } + if parsedTime.IsZero() { + return err + } + + *t = Time(parsedTime) + + return nil +} + +// MarshalJSON marshal time back to time.Time for json encoding +func (t Time) MarshalJSON() ([]byte, error) { + return t.Time().MarshalJSON() +} + +func (t *Time) Time() time.Time { + return time.Time(*t) +} diff --git a/utils/glog/glog.go b/utils/glog/glog.go new file mode 100644 index 0000000..a79ec4d --- /dev/null +++ b/utils/glog/glog.go @@ -0,0 +1,145 @@ +package glog + +import ( + "context" + "io" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +// DEFAULT (0) The log entry has no assigned severity level. +// DEBUG (100) Debug or trace information. +// INFO (200) Routine information, such as ongoing status or performance. +// NOTICE (300) Normal but significant events, such as start up, shut down, or a configuration change. +// WARNING (400) Warning events might cause problems. +// ERROR (500) Error events are likely to cause problems. +// CRITICAL (600) Critical events cause more severe problems or outages. +// ALERT (700) A person must take an action immediately. +// EMERGENCY (800) One or more systems are unusable. + +type SeverityHook struct{} + +func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { + switch level { + case zerolog.DebugLevel: + e.Str("severity", "DEBUG") + case zerolog.InfoLevel: + e.Str("severity", "INFO") + case zerolog.WarnLevel: + e.Str("severity", "WARNING") + case zerolog.ErrorLevel: + e.Str("severity", "ERROR") + case zerolog.FatalLevel: + e.Str("severity", "CRITICAL") + case zerolog.PanicLevel: + e.Str("severity", "ALERT") + default: + e.Str("severity", "DEFAULT") + } +} + +// Logger is the global logger. +var Logger = log.Hook(SeverityHook{}) + +// Output duplicates the global logger and sets w as its output. +func Output(w io.Writer) zerolog.Logger { + return Logger.Output(w) +} + +// With creates a child logger with the field added to its context. +func With() zerolog.Context { + return Logger.With() +} + +// Level creates a child logger with the minimum accepted level set to level. +func Level(level zerolog.Level) zerolog.Logger { + return Logger.Level(level) +} + +// Sample returns a logger with the s sampler. +func Sample(s zerolog.Sampler) zerolog.Logger { + return Logger.Sample(s) +} + +// Hook returns a logger with the h Hook. +func Hook(h zerolog.Hook) zerolog.Logger { + return Logger.Hook(h) +} + +// Debug starts a new message with debug level. +// +// You must call Msg on the returned event in order to send the event. +func Debug() *zerolog.Event { + return Logger.Debug() +} + +// Info starts a new message with info level. +// +// You must call Msg on the returned event in order to send the event. +func Info() *zerolog.Event { + return Logger.Info() +} + +// Warn starts a new message with warn level. +// +// You must call Msg on the returned event in order to send the event. +func Warn() *zerolog.Event { + return Logger.Warn() +} + +// Error starts a new message with error level. +// +// You must call Msg on the returned event in order to send the event. +func Error() *zerolog.Event { + return Logger.Error() +} + +// Fatal starts a new message with fatal level. The os.Exit(1) function +// is called by the Msg method. +// +// You must call Msg on the returned event in order to send the event. +func Fatal() *zerolog.Event { + return Logger.Fatal() +} + +// Panic starts a new message with panic level. The message is also sent +// to the panic function. +// +// You must call Msg on the returned event in order to send the event. +func Panic() *zerolog.Event { + return Logger.Panic() +} + +// WithLevel starts a new message with level. +// +// You must call Msg on the returned event in order to send the event. +func WithLevel(level zerolog.Level) *zerolog.Event { + return Logger.WithLevel(level) +} + +// Log starts a new message with no level. Setting zerolog.GlobalLevel to +// zerolog.Disabled will still disable events produced by this method. +// +// You must call Msg on the returned event in order to send the event. +func Log() *zerolog.Event { + return Logger.Log() +} + +// Print sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Print. +func Print(v ...interface{}) { + Logger.Print(v...) +} + +// Printf sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Printf. +func Printf(format string, v ...interface{}) { + Logger.Printf(format, v...) +} + +// Ctx returns the Logger associated with the ctx. If no logger +// is associated, a disabled logger is returned. +func Ctx(ctx context.Context) *zerolog.Logger { + return zerolog.Ctx(ctx) +} diff --git a/utils/recover.go b/utils/recover.go new file mode 100644 index 0000000..8444c40 --- /dev/null +++ b/utils/recover.go @@ -0,0 +1,11 @@ +package utils + +import ( + log "github.com/en/siren/utils/glog" +) + +func SirenRecover() { + if r := recover(); r != nil { + log.Debug().Interface("recover", r).Msg("") + } +} diff --git a/utils/time.go b/utils/time.go new file mode 100644 index 0000000..dab721f --- /dev/null +++ b/utils/time.go @@ -0,0 +1,13 @@ +package utils + +import ( + "time" +) + +func NowUnixMilli() int64 { + return time.Now().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) +} + +func UnixMilli(t time.Time) int64 { + return t.UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond)) +}