From 7089e728ffbe706e999eba08315bda7f4b7cbcf8 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 8 Apr 2019 10:03:22 +0800 Subject: [PATCH 01/14] rpc: change dependency --- client/rpc/client.go | 433 +++++++++++++++ client/rpc/dex.go | 1 + client/rpc/ws_client.go | 557 ++++++++++++++++++++ types/account.go => common/types/address.go | 0 common/types/bank.go | 13 + common/types/wire.go | 1 + e2e/e2e_rpc_test.go | 1 + go.mod | 12 +- go.sum | 64 ++- types/coin.go | 126 ----- 10 files changed, 1079 insertions(+), 129 deletions(-) create mode 100644 client/rpc/client.go create mode 100644 client/rpc/dex.go create mode 100644 client/rpc/ws_client.go rename types/account.go => common/types/address.go (100%) create mode 100644 common/types/bank.go create mode 100644 common/types/wire.go create mode 100644 e2e/e2e_rpc_test.go delete mode 100644 types/coin.go diff --git a/client/rpc/client.go b/client/rpc/client.go new file mode 100644 index 00000000..3b718a91 --- /dev/null +++ b/client/rpc/client.go @@ -0,0 +1,433 @@ +package rpc + +import ( + "context" + "github.com/binance-chain/go-sdk/common/uuid" + "github.com/tendermint/tendermint/rpc/client" + "strings" + "sync" + "time" + + "github.com/pkg/errors" + + amino "github.com/tendermint/go-amino" + + cmn "github.com/tendermint/tendermint/libs/common" + tmpubsub "github.com/tendermint/tendermint/libs/pubsub" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + rpcclient "github.com/tendermint/tendermint/rpc/lib/client" + "github.com/tendermint/tendermint/types" +) + +func NewRPCClient(nodeURI string) *HTTP { + return NewHTTP(nodeURI, "/websock") +} + +type HTTP struct { + remote string + rpc *rpcclient.JSONRPCClient + *WSEvents +} + +// NewHTTP takes a remote endpoint in the form tcp://: +// and the websocket path (which always seems to be "/websocket") +func NewHTTP(remote, wsEndpoint string) *HTTP { + rc := rpcclient.NewJSONRPCClient(remote) + cdc := rc.Codec() + ctypes.RegisterAmino(cdc) + rc.SetCodec(cdc) + + return &HTTP{ + rpc: rc, + remote: remote, + WSEvents: newWSEvents(cdc, remote, wsEndpoint), + } +} + +var _ client.Client = (*HTTP)(nil) +var ( + _ client.Client = (*HTTP)(nil) + _ client.NetworkClient = (*HTTP)(nil) + _ client.EventsClient = (*HTTP)(nil) +) + +func (c *HTTP) Status() (*ctypes.ResultStatus, error) { + result := new(ctypes.ResultStatus) + _, err := c.rpc.Call("status", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "Status") + } + return result, nil +} + +func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + result := new(ctypes.ResultABCIInfo) + _, err := c.rpc.Call("abci_info", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "ABCIInfo") + } + return result, nil +} + +func (c *HTTP) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { + return c.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) +} + +func (c *HTTP) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + result := new(ctypes.ResultABCIQuery) + _, err := c.rpc.Call("abci_query", + map[string]interface{}{"path": path, "data": data, "height": opts.Height, "prove": opts.Prove}, + result) + if err != nil { + return nil, errors.Wrap(err, "ABCIQuery") + } + return result, nil +} + +func (c *HTTP) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + result := new(ctypes.ResultBroadcastTxCommit) + _, err := c.rpc.Call("broadcast_tx_commit", map[string]interface{}{"tx": tx}, result) + if err != nil { + return nil, errors.Wrap(err, "broadcast_tx_commit") + } + return result, nil +} + +func (c *HTTP) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return c.broadcastTX("broadcast_tx_async", tx) +} + +func (c *HTTP) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return c.broadcastTX("broadcast_tx_sync", tx) +} + +func (c *HTTP) broadcastTX(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + result := new(ctypes.ResultBroadcastTx) + _, err := c.rpc.Call(route, map[string]interface{}{"tx": tx}, result) + if err != nil { + return nil, errors.Wrap(err, route) + } + return result, nil +} + +func (c *HTTP) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { + result := new(ctypes.ResultUnconfirmedTxs) + _, err := c.rpc.Call("unconfirmed_txs", map[string]interface{}{"limit": limit}, result) + if err != nil { + return nil, errors.Wrap(err, "unconfirmed_txs") + } + return result, nil +} + +func (c *HTTP) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { + result := new(ctypes.ResultUnconfirmedTxs) + _, err := c.rpc.Call("num_unconfirmed_txs", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "num_unconfirmed_txs") + } + return result, nil +} + +func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) { + result := new(ctypes.ResultNetInfo) + _, err := c.rpc.Call("net_info", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "NetInfo") + } + return result, nil +} + +func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { + result := new(ctypes.ResultDumpConsensusState) + _, err := c.rpc.Call("dump_consensus_state", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "DumpConsensusState") + } + return result, nil +} + +func (c *HTTP) ConsensusState() (*ctypes.ResultConsensusState, error) { + result := new(ctypes.ResultConsensusState) + _, err := c.rpc.Call("consensus_state", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "ConsensusState") + } + return result, nil +} + +func (c *HTTP) Health() (*ctypes.ResultHealth, error) { + result := new(ctypes.ResultHealth) + _, err := c.rpc.Call("health", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "Health") + } + return result, nil +} + +func (c *HTTP) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + result := new(ctypes.ResultBlockchainInfo) + _, err := c.rpc.Call("blockchain", + map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight}, + result) + if err != nil { + return nil, errors.Wrap(err, "BlockchainInfo") + } + return result, nil +} + +func (c *HTTP) Genesis() (*ctypes.ResultGenesis, error) { + result := new(ctypes.ResultGenesis) + _, err := c.rpc.Call("genesis", map[string]interface{}{}, result) + if err != nil { + return nil, errors.Wrap(err, "Genesis") + } + return result, nil +} + +func (c *HTTP) Block(height *int64) (*ctypes.ResultBlock, error) { + result := new(ctypes.ResultBlock) + _, err := c.rpc.Call("block", map[string]interface{}{"height": height}, result) + if err != nil { + return nil, errors.Wrap(err, "Block") + } + return result, nil +} + +func (c *HTTP) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { + result := new(ctypes.ResultBlockResults) + _, err := c.rpc.Call("block_results", map[string]interface{}{"height": height}, result) + if err != nil { + return nil, errors.Wrap(err, "Block Result") + } + return result, nil +} + +func (c *HTTP) Commit(height *int64) (*ctypes.ResultCommit, error) { + result := new(ctypes.ResultCommit) + _, err := c.rpc.Call("commit", map[string]interface{}{"height": height}, result) + if err != nil { + return nil, errors.Wrap(err, "Commit") + } + return result, nil +} + +func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { + result := new(ctypes.ResultTx) + params := map[string]interface{}{ + "hash": hash, + "prove": prove, + } + _, err := c.rpc.Call("tx", params, result) + if err != nil { + return nil, errors.Wrap(err, "Tx") + } + return result, nil +} + +func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { + result := new(ctypes.ResultTxSearch) + params := map[string]interface{}{ + "query": query, + "prove": prove, + "page": page, + "per_page": perPage, + } + _, err := c.rpc.Call("tx_search", params, result) + if err != nil { + return nil, errors.Wrap(err, "TxSearch") + } + return result, nil +} + +func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { + result := new(ctypes.ResultValidators) + _, err := c.rpc.Call("validators", map[string]interface{}{"height": height}, result) + if err != nil { + return nil, errors.Wrap(err, "Validators") + } + return result, nil +} + +/** websocket event stuff here... **/ + +type WSEvents struct { + cmn.BaseService + cdc *amino.Codec + remote string + endpoint string + ws *WSClient + + mtx sync.RWMutex + // query -> chan + subscriptions map[string]chan ctypes.ResultEvent + + + responseChanMap sync.Map +} + +func newWSEvents(cdc *amino.Codec, remote, endpoint string) *WSEvents { + wsEvents := &WSEvents{ + cdc: cdc, + endpoint: endpoint, + remote: remote, + subscriptions: make(map[string]chan ctypes.ResultEvent), + } + + wsEvents.BaseService = *cmn.NewBaseService(nil, "WSEvents", wsEvents) + return wsEvents +} + +// OnStart implements cmn.Service by starting WSClient and event loop. +func (w *WSEvents) OnStart() error { + w.ws = NewWSClient(w.remote, w.endpoint, OnReconnect(func() { + w.redoSubscriptionsAfter(0 * time.Second) + })) + w.ws.SetCodec(w.cdc) + + err := w.ws.Start() + if err != nil { + return err + } + + go w.eventListener() + return nil +} + +// OnStop implements cmn.Service by stopping WSClient. +func (w *WSEvents) OnStop() { + _ = w.ws.Stop() +} + +// Subscribe implements EventsClient by using WSClient to subscribe given +// subscriber to query. By default, returns a channel with cap=1. Error is +// returned if it fails to subscribe. +// Channel is never closed to prevent clients from seeing an erroneus event. +func (w *WSEvents) Subscribe(ctx context.Context, subscriber, query string, + outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { + + if err := w.ws.Subscribe(ctx, query); err != nil { + return nil, err + } + + outCap := 1 + if len(outCapacity) > 0 { + outCap = outCapacity[0] + } + + outc := make(chan ctypes.ResultEvent, outCap) + w.mtx.Lock() + // subscriber param is ignored because Tendermint will override it with + // remote IP anyway. + w.subscriptions[query] = outc + w.mtx.Unlock() + + return outc, nil +} + +// Unsubscribe implements EventsClient by using WSClient to unsubscribe given +// subscriber from query. +func (w *WSEvents) Unsubscribe(ctx context.Context, subscriber, query string) error { + if err := w.ws.Unsubscribe(ctx, query); err != nil { + return err + } + + w.mtx.Lock() + _, ok := w.subscriptions[query] + if ok { + delete(w.subscriptions, query) + } + w.mtx.Unlock() + + return nil +} + +// UnsubscribeAll implements EventsClient by using WSClient to unsubscribe +// given subscriber from all the queries. +func (w *WSEvents) UnsubscribeAll(ctx context.Context, subscriber string) error { + if err := w.ws.UnsubscribeAll(ctx); err != nil { + return err + } + + w.mtx.Lock() + w.subscriptions = make(map[string]chan ctypes.ResultEvent) + w.mtx.Unlock() + + return nil +} + +func (w *WSEvents) Status(ctx context.Context) (*ctypes.ResultStatus, error) { + requestId, err := w.ws.Status(ctx); + if err != nil { + return nil, err + } + + outc := make(chan []byte, 1) + w.responseChanMap.Store(requestId,outc) + + return outc, nil +} + +// After being reconnected, it is necessary to redo subscription to server +// otherwise no data will be automatically received. +func (w *WSEvents) redoSubscriptionsAfter(d time.Duration) { + time.Sleep(d) + + for q := range w.subscriptions { + err := w.ws.Subscribe(context.Background(), q) + if err != nil { + w.Logger.Error("Failed to resubscribe", "err", err) + } + } +} + +func isErrAlreadySubscribed(err error) bool { + return strings.Contains(err.Error(), tmpubsub.ErrAlreadySubscribed.Error()) +} + +func (w *WSEvents) eventListener() { + for { + select { + case resp, ok := <-w.ws.ResponsesCh: + if !ok { + return + } + + if resp.Error != nil { + w.Logger.Error("WS error", "err", resp.Error.Error()) + // Error can be ErrAlreadySubscribed or max client (subscriptions per + // client) reached or Tendermint exited. + // We can ignore ErrAlreadySubscribed, but need to retry in other + // cases. + if !isErrAlreadySubscribed(resp.Error) { + // Resubscribe after 1 second to give Tendermint time to restart (if + // crashed). + w.redoSubscriptionsAfter(1 * time.Second) + } + continue + } + + result := new(ctypes.ResultEvent) + err := w.cdc.UnmarshalJSON(resp.Result, result) + if err != nil { + w.Logger.Error("failed to unmarshal response", "err", err) + continue + } + + w.mtx.RLock() + if out, ok := w.subscriptions[result.Query]; ok { + if cap(out) == 0 { + out <- *result + } else { + select { + case out <- *result: + default: + w.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query) + } + } + } + w.mtx.RUnlock() + case <-w.Quit(): + return + } + } +} diff --git a/client/rpc/dex.go b/client/rpc/dex.go new file mode 100644 index 00000000..9ab1e3e8 --- /dev/null +++ b/client/rpc/dex.go @@ -0,0 +1 @@ +package rpc diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go new file mode 100644 index 00000000..99d96cc6 --- /dev/null +++ b/client/rpc/ws_client.go @@ -0,0 +1,557 @@ +package rpc + +import ( + "context" + "encoding/json" + "fmt" + "github.com/binance-chain/go-sdk/common/uuid" + "net" + "net/http" + "strings" + "sync" + "time" + + "github.com/gorilla/websocket" + "github.com/pkg/errors" + "github.com/rcrowley/go-metrics" + + "github.com/tendermint/go-amino" + cmn "github.com/tendermint/tendermint/libs/common" + types "github.com/tendermint/tendermint/rpc/lib/types" +) + +const ( + defaultMaxReconnectAttempts = 25 + defaultWriteWait = 0 + defaultReadWait = 0 + defaultPingPeriod = 0 + + protoHTTP = "http" + protoHTTPS = "https" + protoWSS = "wss" + protoWS = "ws" + protoTCP = "tcp" + +) + +// WSClient is a WebSocket client. The methods of WSClient are safe for use by +// multiple goroutines. +type WSClient struct { + cmn.BaseService + + conn *websocket.Conn + cdc *amino.Codec + + Address string // IP:PORT or /path/to/socket + Endpoint string // /websocket/url/endpoint + Dialer func(string, string) (net.Conn, error) + + // Time between sending a ping and receiving a pong. See + // https://godoc.org/github.com/rcrowley/go-metrics#Timer. + PingPongLatencyTimer metrics.Timer + + // Single user facing channel to read RPCResponses from, closed only when the client is being stopped. + ResponsesCh chan types.RPCResponse + + // Callback, which will be called each time after successful reconnect. + onReconnect func() + + // internal channels + send chan types.RPCRequest // user requests + backlog chan types.RPCRequest // stores a single user request received during a conn failure + reconnectAfter chan error // reconnect requests + readRoutineQuit chan struct{} // a way for readRoutine to close writeRoutine + + wg sync.WaitGroup + + mtx sync.RWMutex + sentLastPingAt time.Time + reconnecting bool + + // Maximum reconnect attempts (0 or greater; default: 25). + maxReconnectAttempts int + + // Time allowed to write a message to the server. 0 means block until operation succeeds. + writeWait time.Duration + + // Time allowed to read the next message from the server. 0 means block until operation succeeds. + readWait time.Duration + + // Send pings to server with this period. Must be less than readWait. If 0, no pings will be sent. + pingPeriod time.Duration + + // Support both ws and wss protocols + protocol string +} + +// NewWSClient returns a new client. See the commentary on the func(*WSClient) +// functions for a detailed description of how to configure ping period and +// pong wait time. The endpoint argument must begin with a `/`. +func NewWSClient(remoteAddr, endpoint string, options ...func(*WSClient)) *WSClient { + protocol, addr, dialer := makeHTTPDialer(remoteAddr) + // default to ws protocol, unless wss is explicitly specified + if protocol != "wss" { + protocol = "ws" + } + + c := &WSClient{ + cdc: amino.NewCodec(), + Address: addr, + Dialer: dialer, + Endpoint: endpoint, + PingPongLatencyTimer: metrics.NewTimer(), + + maxReconnectAttempts: defaultMaxReconnectAttempts, + readWait: defaultReadWait, + writeWait: defaultWriteWait, + pingPeriod: defaultPingPeriod, + protocol: protocol, + } + c.BaseService = *cmn.NewBaseService(nil, "WSClient", c) + for _, option := range options { + option(c) + } + return c +} + +// MaxReconnectAttempts sets the maximum number of reconnect attempts before returning an error. +// It should only be used in the constructor and is not Goroutine-safe. +func MaxReconnectAttempts(max int) func(*WSClient) { + return func(c *WSClient) { + c.maxReconnectAttempts = max + } +} + +// ReadWait sets the amount of time to wait before a websocket read times out. +// It should only be used in the constructor and is not Goroutine-safe. +func ReadWait(readWait time.Duration) func(*WSClient) { + return func(c *WSClient) { + c.readWait = readWait + } +} + +// WriteWait sets the amount of time to wait before a websocket write times out. +// It should only be used in the constructor and is not Goroutine-safe. +func WriteWait(writeWait time.Duration) func(*WSClient) { + return func(c *WSClient) { + c.writeWait = writeWait + } +} + +// PingPeriod sets the duration for sending websocket pings. +// It should only be used in the constructor - not Goroutine-safe. +func PingPeriod(pingPeriod time.Duration) func(*WSClient) { + return func(c *WSClient) { + c.pingPeriod = pingPeriod + } +} + +// OnReconnect sets the callback, which will be called every time after +// successful reconnect. +func OnReconnect(cb func()) func(*WSClient) { + return func(c *WSClient) { + c.onReconnect = cb + } +} + +// String returns WS client full address. +func (c *WSClient) String() string { + return fmt.Sprintf("%s (%s)", c.Address, c.Endpoint) +} + +// OnStart implements cmn.Service by dialing a server and creating read and +// write routines. +func (c *WSClient) OnStart() error { + err := c.dial() + if err != nil { + return err + } + + c.ResponsesCh = make(chan types.RPCResponse) + + c.send = make(chan types.RPCRequest) + // 1 additional error may come from the read/write + // goroutine depending on which failed first. + c.reconnectAfter = make(chan error, 1) + // capacity for 1 request. a user won't be able to send more because the send + // channel is unbuffered. + c.backlog = make(chan types.RPCRequest, 1) + + c.startReadWriteRoutines() + go c.reconnectRoutine() + + return nil +} + +// Stop overrides cmn.Service#Stop. There is no other way to wait until Quit +// channel is closed. +func (c *WSClient) Stop() error { + if err := c.BaseService.Stop(); err != nil { + return err + } + // only close user-facing channels when we can't write to them + c.wg.Wait() + close(c.ResponsesCh) + + return nil +} + +// IsReconnecting returns true if the client is reconnecting right now. +func (c *WSClient) IsReconnecting() bool { + c.mtx.RLock() + defer c.mtx.RUnlock() + return c.reconnecting +} + +// IsActive returns true if the client is running and not reconnecting. +func (c *WSClient) IsActive() bool { + return c.IsRunning() && !c.IsReconnecting() +} + +// Send the given RPC request to the server. Results will be available on +// ResponsesCh, errors, if any, on ErrorsCh. Will block until send succeeds or +// ctx.Done is closed. +func (c *WSClient) Send(ctx context.Context, request types.RPCRequest) error { + select { + case c.send <- request: + c.Logger.Info("sent a request", "req", request) + return nil + case <-ctx.Done(): + return ctx.Err() + } +} + +// Call the given method. See Send description. +func (c *WSClient) Call(ctx context.Context, method string, params map[string]interface{}) (string, error) { + id, err := uuid.NewV4() + if err != nil { + return "", err + } + requestId := id.String() + request, err := types.MapToRequest(c.cdc, types.JSONRPCStringID(requestId), method, params) + if err != nil { + return "", err + } + return requestId, c.Send(ctx, request) +} + +// CallWithArrayParams the given method with params in a form of array. See +// Send description. +func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, params []interface{}) error { + request, err := types.ArrayToRequest(c.cdc, types.JSONRPCStringID("ws-client"), method, params) + if err != nil { + return err + } + return c.Send(ctx, request) +} + +func (c *WSClient) Codec() *amino.Codec { + return c.cdc +} + +func (c *WSClient) SetCodec(cdc *amino.Codec) { + c.cdc = cdc +} + +/////////////////////////////////////////////////////////////////////////////// +// Private methods + +func (c *WSClient) dial() error { + dialer := &websocket.Dialer{ + NetDial: c.Dialer, + Proxy: http.ProxyFromEnvironment, + } + rHeader := http.Header{} + conn, _, err := dialer.Dial(c.protocol+"://"+c.Address+c.Endpoint, rHeader) + if err != nil { + return err + } + c.conn = conn + return nil +} + +// reconnect tries to redial up to maxReconnectAttempts with exponential +// backoff. +func (c *WSClient) reconnect() error { + attempt := 0 + + c.mtx.Lock() + c.reconnecting = true + c.mtx.Unlock() + defer func() { + c.mtx.Lock() + c.reconnecting = false + c.mtx.Unlock() + }() + + for { + jitterSeconds := time.Duration(cmn.RandFloat64() * float64(time.Second)) // 1s == (1e9 ns) + backoffDuration := jitterSeconds + ((1 << uint(attempt)) * time.Second) + + c.Logger.Info("reconnecting", "attempt", attempt+1, "backoff_duration", backoffDuration) + time.Sleep(backoffDuration) + + err := c.dial() + if err != nil { + c.Logger.Error("failed to redial", "err", err) + } else { + c.Logger.Info("reconnected") + if c.onReconnect != nil { + go c.onReconnect() + } + return nil + } + + attempt++ + + if attempt > c.maxReconnectAttempts { + return errors.Wrap(err, "reached maximum reconnect attempts") + } + } +} + +func (c *WSClient) startReadWriteRoutines() { + c.wg.Add(2) + c.readRoutineQuit = make(chan struct{}) + go c.readRoutine() + go c.writeRoutine() +} + +func (c *WSClient) processBacklog() error { + select { + case request := <-c.backlog: + if c.writeWait > 0 { + if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeWait)); err != nil { + c.Logger.Error("failed to set write deadline", "err", err) + } + } + if err := c.conn.WriteJSON(request); err != nil { + c.Logger.Error("failed to resend request", "err", err) + c.reconnectAfter <- err + // requeue request + c.backlog <- request + return err + } + c.Logger.Info("resend a request", "req", request) + default: + } + return nil +} + +func (c *WSClient) reconnectRoutine() { + for { + select { + case originalError := <-c.reconnectAfter: + // wait until writeRoutine and readRoutine finish + c.wg.Wait() + if err := c.reconnect(); err != nil { + c.Logger.Error("failed to reconnect", "err", err, "original_err", originalError) + c.Stop() + return + } + // drain reconnectAfter + LOOP: + for { + select { + case <-c.reconnectAfter: + default: + break LOOP + } + } + err := c.processBacklog() + if err == nil { + c.startReadWriteRoutines() + } + + case <-c.Quit(): + return + } + } +} + +// The client ensures that there is at most one writer to a connection by +// executing all writes from this goroutine. +func (c *WSClient) writeRoutine() { + var ticker *time.Ticker + if c.pingPeriod > 0 { + // ticker with a predefined period + ticker = time.NewTicker(c.pingPeriod) + } else { + // ticker that never fires + ticker = &time.Ticker{C: make(<-chan time.Time)} + } + + defer func() { + ticker.Stop() + if err := c.conn.Close(); err != nil { + // ignore error; it will trigger in tests + // likely because it's closing an already closed connection + } + c.wg.Done() + }() + + for { + select { + case request := <-c.send: + if c.writeWait > 0 { + if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeWait)); err != nil { + c.Logger.Error("failed to set write deadline", "err", err) + } + } + if err := c.conn.WriteJSON(request); err != nil { + c.Logger.Error("failed to send request", "err", err) + c.reconnectAfter <- err + // add request to the backlog, so we don't lose it + c.backlog <- request + return + } + case <-ticker.C: + if c.writeWait > 0 { + if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeWait)); err != nil { + c.Logger.Error("failed to set write deadline", "err", err) + } + } + if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { + c.Logger.Error("failed to write ping", "err", err) + c.reconnectAfter <- err + return + } + c.mtx.Lock() + c.sentLastPingAt = time.Now() + c.mtx.Unlock() + c.Logger.Debug("sent ping") + case <-c.readRoutineQuit: + return + case <-c.Quit(): + if err := c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil { + c.Logger.Error("failed to write message", "err", err) + } + return + } + } +} + +// The client ensures that there is at most one reader to a connection by +// executing all reads from this goroutine. +func (c *WSClient) readRoutine() { + defer func() { + if err := c.conn.Close(); err != nil { + // ignore error; it will trigger in tests + // likely because it's closing an already closed connection + } + c.wg.Done() + }() + + c.conn.SetPongHandler(func(string) error { + // gather latency stats + c.mtx.RLock() + t := c.sentLastPingAt + c.mtx.RUnlock() + c.PingPongLatencyTimer.UpdateSince(t) + + c.Logger.Debug("got pong") + return nil + }) + + for { + // reset deadline for every message type (control or data) + if c.readWait > 0 { + if err := c.conn.SetReadDeadline(time.Now().Add(c.readWait)); err != nil { + c.Logger.Error("failed to set read deadline", "err", err) + } + } + _, data, err := c.conn.ReadMessage() + if err != nil { + if !websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { + return + } + + c.Logger.Error("failed to read response", "err", err) + close(c.readRoutineQuit) + c.reconnectAfter <- err + return + } + + var response types.RPCResponse + err = json.Unmarshal(data, &response) + if err != nil { + c.Logger.Error("failed to parse response", "err", err, "data", string(data)) + continue + } + c.Logger.Info("got response", "resp", response.Result) + // Combine a non-blocking read on BaseService.Quit with a non-blocking write on ResponsesCh to avoid blocking + // c.wg.Wait() in c.Stop(). Note we rely on Quit being closed so that it sends unlimited Quit signals to stop + // both readRoutine and writeRoutine + select { + case <-c.Quit(): + case c.ResponsesCh <- response: + } + } +} + +/////////////////////////////////////////////////////////////////////////////// +// Predefined methods + +// Subscribe to a query. Note the server must have a "subscribe" route +// defined. +func (c *WSClient) Subscribe(ctx context.Context, query string) error { + params := map[string]interface{}{"query": query} + _, err := c.Call(ctx, "subscribe", params) + return err +} + +// Unsubscribe from a query. Note the server must have a "unsubscribe" route +// defined. +func (c *WSClient) Unsubscribe(ctx context.Context, query string) error { + params := map[string]interface{}{"query": query} + + _, err := c.Call(ctx, "unsubscribe", params) + return err +} + +// UnsubscribeAll from all. Note the server must have a "unsubscribe_all" route +// defined. +func (c *WSClient) UnsubscribeAll(ctx context.Context) error { + params := map[string]interface{}{} + _, err := c.Call(ctx, "unsubscribe_all", params) + return err +} + +func (c *WSClient) Status(ctx context.Context) (string, error) { + return c.Call(ctx, "status", map[string]interface{}{}) +} + +func makeHTTPDialer(remoteAddr string) (string, string, func(string, string) (net.Conn, error)) { + // protocol to use for http operations, to support both http and https + clientProtocol := protoHTTP + + parts := strings.SplitN(remoteAddr, "://", 2) + var protocol, address string + if len(parts) == 1 { + // default to tcp if nothing specified + protocol, address = protoTCP, remoteAddr + } else if len(parts) == 2 { + protocol, address = parts[0], parts[1] + } else { + // return a invalid message + msg := fmt.Sprintf("Invalid addr: %s", remoteAddr) + return clientProtocol, msg, func(_ string, _ string) (net.Conn, error) { + return nil, errors.New(msg) + } + } + + // accept http as an alias for tcp and set the client protocol + switch protocol { + case protoHTTP, protoHTTPS: + clientProtocol = protocol + protocol = protoTCP + case protoWS, protoWSS: + clientProtocol = protocol + } + + // replace / with . for http requests (kvstore domain) + trimmedAddress := strings.Replace(address, "/", ".", -1) + return clientProtocol, trimmedAddress, func(proto, addr string) (net.Conn, error) { + return net.Dial(protocol, address) + } +} \ No newline at end of file diff --git a/types/account.go b/common/types/address.go similarity index 100% rename from types/account.go rename to common/types/address.go diff --git a/common/types/bank.go b/common/types/bank.go new file mode 100644 index 00000000..35d60d7e --- /dev/null +++ b/common/types/bank.go @@ -0,0 +1,13 @@ +package types + +import "github.com/binance-chain/go-sdk/types" + +// Token definition +type Token struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + OrigSymbol string `json:"original_symbol"` + TotalSupply Fixed8 `json:"total_supply"` + Owner types.AccAddress `json:"owner"` + Mintable bool `json:"mintable"` +} diff --git a/common/types/wire.go b/common/types/wire.go new file mode 100644 index 00000000..ab1254f4 --- /dev/null +++ b/common/types/wire.go @@ -0,0 +1 @@ +package types diff --git a/e2e/e2e_rpc_test.go b/e2e/e2e_rpc_test.go new file mode 100644 index 00000000..df8caf70 --- /dev/null +++ b/e2e/e2e_rpc_test.go @@ -0,0 +1 @@ +package e2e diff --git a/go.mod b/go.mod index e381637a..de3435a1 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,30 @@ module github.com/binance-chain/go-sdk require ( - github.com/binance-chain/bnc-go-amino v0.14.1-binance.1 github.com/btcsuite/btcd v0.0.0-20181013004428-67e573d211ac github.com/btcsuite/btcutil v0.0.0-20180706230648-ab6388e0c60a + github.com/coreos/go-iptables v0.4.0 github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d github.com/davecgh/go-spew v1.1.1 // indirect github.com/go-kit/kit v0.8.0 // indirect github.com/go-logfmt/logfmt v0.3.0 // indirect github.com/gogo/protobuf v1.1.1 // indirect - github.com/golang/protobuf v1.2.0 // indirect github.com/gorilla/websocket v1.4.0 github.com/pkg/errors v0.8.0 github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_golang v0.9.2 // indirect + github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a + github.com/rs/cors v1.6.0 // indirect github.com/stretchr/testify v1.2.2 + github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tendermint/btcd v0.0.0-20180816174608-e5840949ff4f github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9 // indirect + github.com/tendermint/go-amino v0.14.1 + github.com/tendermint/tendermint v0.31.2-rc0 golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 golang.org/x/sys v0.0.0-20181116161606-93218def8b18 // indirect + google.golang.org/grpc v1.19.1 // indirect gopkg.in/resty.v1 v1.10.3 ) + +replace github.com/tendermint/go-amino => github.com/binance-chain/bnc-go-amino v0.14.1-binance.1 diff --git a/go.sum b/go.sum index 82756f9d..4518d925 100644 --- a/go.sum +++ b/go.sum @@ -1,31 +1,93 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/binance-chain/bnc-go-amino v0.14.1-binance.1 h1:RU33Toj/38sztKjcJTdziUIL0MBoMF7Xt1gQ3bT2zK0= github.com/binance-chain/bnc-go-amino v0.14.1-binance.1/go.mod h1:yaElUUxWtv/TC/ldGtlKAvS1vKwokxgJ1d97I+6is80= github.com/btcsuite/btcd v0.0.0-20181013004428-67e573d211ac h1:/zx+Hglw2JN/pwVam1Z8cTCTl4pWyrbvOn2oooqCQSs= github.com/btcsuite/btcd v0.0.0-20181013004428-67e573d211ac/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ= 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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/go-iptables v0.4.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d h1:49RLWk1j44Xu4fjHb6JFYmeUnDORVwHNkDxaQ0ctCVU= github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y= 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/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2ic= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +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/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= +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/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/prometheus/client_golang v0.9.2 h1:awm861/B8OKDd2I/6o1dy3ra4BamzKhYOiGItCeZ740= +github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275 h1:PnBWHBf+6L0jOqq0gIVUe6Yk0/QMZ640k6NvkxcBf+8= +github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a h1:9a8MnZMP0X2nLJdBg+pBmGgkJlSaKC2KaQmTCk1XDtE= +github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a h1:9ZKAASQSHhDYGoxY8uLVpewe1GDZ2vu2Tr/vTdVAkFQ= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +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/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= +github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/tendermint/btcd v0.0.0-20180816174608-e5840949ff4f h1:R0wLxgASGMoRQTF/dCSk4N+M3j9DLyPDzDff2WtCg/I= github.com/tendermint/btcd v0.0.0-20180816174608-e5840949ff4f/go.mod h1:DC6/m53jtQzr/NFmMNEu0rxf18/ktVoVtMrnDD5pN+U= github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9/go.mod h1:nt45hbhDkWVdMBkr2TOgOzCrpBccXdN09WOiOYTHVEk= +github.com/tendermint/tendermint v0.31.2-rc0 h1:Iltc7dXcninZMH0p7iMxJZOv4jXTUznwBwfWge5EJlo= +github.com/tendermint/tendermint v0.31.2-rc0/go.mod h1:ymcPyWblXCplCPQjbOYbrF1fWnpslATMVqiGgWbZrlc= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 h1:kkXA53yGe04D0adEYJwEVQjeBppL01Exg+fnMjfUraU= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/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-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc h1:a3CU5tJYVj92DY2LaA1kUkrsqD5/3mLDhx2NcNqyW+0= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-20181116161606-93218def8b18 h1:Wh+XCfg3kNpjhdq2LXrsiOProjtQZKme5XUx7VcxwAw= golang.org/x/sys v0.0.0-20181116161606-93218def8b18/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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.19.1 h1:TrBcJ1yqAl1G++wO39nD/qtgpsW9/1+QGrluyMGEYgM= +google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/resty.v1 v1.10.3 h1:w8FjChB7PWrvE5z6JX/gfFzVwTDj38qiAQJKgdWDGvA= gopkg.in/resty.v1 v1.10.3/go.mod h1:nrgQYbPhkRfn2BfT32NNTLfq3K9NuHRB0MsAcA9weWY= +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= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/types/coin.go b/types/coin.go deleted file mode 100644 index 0fa8b34b..00000000 --- a/types/coin.go +++ /dev/null @@ -1,126 +0,0 @@ -package types - -import "strings" - -// Coin def -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - -func (coin Coin) IsZero() bool { - return coin.Amount == 0 -} - -func (coin Coin) IsPositive() bool { - return coin.Amount > 0 -} - -func (coin Coin) IsNotNegative() bool { - return coin.Amount >= 0 -} - -func (coin Coin) SameDenomAs(other Coin) bool { - return (coin.Denom == other.Denom) -} - -func (coin Coin) Plus(coinB Coin) Coin { - if !coin.SameDenomAs(coinB) { - return coin - } - return Coin{coin.Denom, coin.Amount + coinB.Amount} -} - -// Coins def -type Coins []Coin - -func (coins Coins) IsValid() bool { - switch len(coins) { - case 0: - return true - case 1: - return !coins[0].IsZero() - default: - lowDenom := coins[0].Denom - for _, coin := range coins[1:] { - if coin.Denom <= lowDenom { - return false - } - if coin.IsZero() { - return false - } - lowDenom = coin.Denom - } - return true - } -} - -func (coins Coins) IsPositive() bool { - if len(coins) == 0 { - return false - } - for _, coin := range coins { - if !coin.IsPositive() { - return false - } - } - return true -} - -func (coins Coins) Plus(coinsB Coins) Coins { - sum := ([]Coin)(nil) - indexA, indexB := 0, 0 - lenA, lenB := len(coins), len(coinsB) - for { - if indexA == lenA { - if indexB == lenB { - return sum - } - return append(sum, coinsB[indexB:]...) - } else if indexB == lenB { - return append(sum, coins[indexA:]...) - } - coinA, coinB := coins[indexA], coinsB[indexB] - switch strings.Compare(coinA.Denom, coinB.Denom) { - case -1: - sum = append(sum, coinA) - indexA++ - case 0: - if coinA.Amount+coinB.Amount == 0 { - // ignore 0 sum coin type - } else { - sum = append(sum, coinA.Plus(coinB)) - } - indexA++ - indexB++ - case 1: - sum = append(sum, coinB) - indexB++ - } - } -} - -// IsEqual returns true if the two sets of Coins have the same value -func (coins Coins) IsEqual(coinsB Coins) bool { - if len(coins) != len(coinsB) { - return false - } - for i := 0; i < len(coins); i++ { - if coins[i].Denom != coinsB[i].Denom || !(coins[i].Amount == coinsB[i].Amount) { - return false - } - } - return true -} - -func (coins Coins) IsNotNegative() bool { - if len(coins) == 0 { - return true - } - for _, coin := range coins { - if !coin.IsNotNegative() { - return false - } - } - return true -} From 5f8b4a1589d45b07ec0ba0ab00dc41245b6ed184 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 8 Apr 2019 10:04:29 +0800 Subject: [PATCH 02/14] support rpc to peer node --- client/client.go | 2 +- client/query/get_tokens.go | 13 +- client/query/query.go | 3 +- client/rpc/client.go | 545 +++++++++++++++---------- client/rpc/dex.go | 61 +++ client/rpc/ws_client.go | 190 ++++++--- client/transaction/deposit_proposal.go | 3 +- client/transaction/send_token.go | 2 +- client/transaction/submit_proposal.go | 4 +- common/crypto/encoding/amino/amino.go | 2 +- common/crypto/secp256k1/secp256k1.go | 2 +- common/types/bank.go | 311 +++++++++++++- common/types/wire.go | 10 + e2e/e2e_rpc_test.go | 301 ++++++++++++++ e2e/e2e_trans_test.go | 4 +- e2e/e2e_ws_test.go | 3 +- keys/keys.go | 17 +- types/msg/msg-burn.go | 2 +- types/msg/msg-dexList.go | 2 +- types/msg/msg-freeze.go | 2 +- types/msg/msg-gov.go | 5 +- types/msg/msg-issue.go | 2 +- types/msg/msg-mint.go | 2 +- types/msg/msg-order.go | 2 +- types/msg/msg-send.go | 2 +- types/msg/msg.go | 2 +- types/msg/wire.go | 2 +- types/tx/wire.go | 2 +- 28 files changed, 1179 insertions(+), 319 deletions(-) diff --git a/client/client.go b/client/client.go index 47c880ed..01d69457 100644 --- a/client/client.go +++ b/client/client.go @@ -1,6 +1,7 @@ package client import ( + "github.com/binance-chain/go-sdk/common/types" "gopkg.in/resty.v1" "github.com/binance-chain/go-sdk/client/basic" @@ -8,7 +9,6 @@ import ( "github.com/binance-chain/go-sdk/client/transaction" "github.com/binance-chain/go-sdk/client/websocket" "github.com/binance-chain/go-sdk/keys" - "github.com/binance-chain/go-sdk/types" ) // dexClient wrapper diff --git a/client/query/get_tokens.go b/client/query/get_tokens.go index f061d064..4baed4cf 100644 --- a/client/query/get_tokens.go +++ b/client/query/get_tokens.go @@ -2,26 +2,19 @@ package query import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" ) -// Token definition -type Token struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - TotalSupply string `json:"total_supply"` - Owner string `json:"owner"` - OriginalSymbol string `json:"original_symbol"` -} // GetTokens returns list of tokens -func (c *client) GetTokens() ([]Token, error) { +func (c *client) GetTokens() ([]types.Token, error) { qp := map[string]string{} resp, err := c.baseClient.Get("/tokens", qp) if err != nil { return nil, err } - var tokens []Token + var tokens []types.Token if err := json.Unmarshal(resp, &tokens); err != nil { return nil, err } diff --git a/client/query/query.go b/client/query/query.go index 2cc9989a..4e7c92b0 100644 --- a/client/query/query.go +++ b/client/query/query.go @@ -2,6 +2,7 @@ package query import ( "github.com/binance-chain/go-sdk/client/basic" + "github.com/binance-chain/go-sdk/common/types" ) type QueryClient interface { @@ -15,7 +16,7 @@ type QueryClient interface { GetTrades(query *TradesQuery) (*Trades, error) GetAccount(string) (*Account, error) GetTime() (*Time, error) - GetTokens() ([]Token, error) + GetTokens() ([]types.Token, error) GetNodeInfo() (*ResultStatus, error) } diff --git a/client/rpc/client.go b/client/rpc/client.go index 3b718a91..4c37e5fd 100644 --- a/client/rpc/client.go +++ b/client/rpc/client.go @@ -2,30 +2,30 @@ package rpc import ( "context" - "github.com/binance-chain/go-sdk/common/uuid" - "github.com/tendermint/tendermint/rpc/client" + "fmt" + "github.com/pkg/errors" "strings" "sync" "time" - "github.com/pkg/errors" - - amino "github.com/tendermint/go-amino" - + ntypes "github.com/binance-chain/go-sdk/common/types" + "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" - tmpubsub "github.com/tendermint/tendermint/libs/pubsub" + "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" - rpcclient "github.com/tendermint/tendermint/rpc/lib/client" + "github.com/tendermint/tendermint/rpc/lib/client" + "github.com/tendermint/tendermint/rpc/lib/types" "github.com/tendermint/tendermint/types" ) +const defaultTimeout = 5 * time.Second + func NewRPCClient(nodeURI string) *HTTP { - return NewHTTP(nodeURI, "/websock") + return NewHTTP(nodeURI, "/websocket") } type HTTP struct { remote string - rpc *rpcclient.JSONRPCClient *WSEvents } @@ -35,38 +35,25 @@ func NewHTTP(remote, wsEndpoint string) *HTTP { rc := rpcclient.NewJSONRPCClient(remote) cdc := rc.Codec() ctypes.RegisterAmino(cdc) - rc.SetCodec(cdc) + ntypes.RegisterWire(cdc) - return &HTTP{ - rpc: rc, + + rc.SetCodec(cdc) + wsEvent := newWSEvents(cdc, remote, wsEndpoint) + client := &HTTP{ remote: remote, - WSEvents: newWSEvents(cdc, remote, wsEndpoint), + WSEvents: wsEvent, } + client.Start() + return client } -var _ client.Client = (*HTTP)(nil) -var ( - _ client.Client = (*HTTP)(nil) - _ client.NetworkClient = (*HTTP)(nil) - _ client.EventsClient = (*HTTP)(nil) -) - func (c *HTTP) Status() (*ctypes.ResultStatus, error) { - result := new(ctypes.ResultStatus) - _, err := c.rpc.Call("status", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "Status") - } - return result, nil + return c.WSEvents.Status() } func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - result := new(ctypes.ResultABCIInfo) - _, err := c.rpc.Call("abci_info", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "ABCIInfo") - } - return result, nil + return c.WSEvents.ABCIInfo() } func (c *HTTP) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { @@ -74,178 +61,88 @@ func (c *HTTP) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuer } func (c *HTTP) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { - result := new(ctypes.ResultABCIQuery) - _, err := c.rpc.Call("abci_query", - map[string]interface{}{"path": path, "data": data, "height": opts.Height, "prove": opts.Prove}, - result) - if err != nil { - return nil, errors.Wrap(err, "ABCIQuery") - } - return result, nil + return c.WSEvents.ABCIQueryWithOptions(path, data, opts) } func (c *HTTP) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - result := new(ctypes.ResultBroadcastTxCommit) - _, err := c.rpc.Call("broadcast_tx_commit", map[string]interface{}{"tx": tx}, result) - if err != nil { - return nil, errors.Wrap(err, "broadcast_tx_commit") - } - return result, nil + return c.WSEvents.BroadcastTxCommit(tx) } func (c *HTTP) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return c.broadcastTX("broadcast_tx_async", tx) + return c.WSEvents.BroadcastTx("broadcast_tx_async", tx) } func (c *HTTP) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return c.broadcastTX("broadcast_tx_sync", tx) -} - -func (c *HTTP) broadcastTX(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - result := new(ctypes.ResultBroadcastTx) - _, err := c.rpc.Call(route, map[string]interface{}{"tx": tx}, result) - if err != nil { - return nil, errors.Wrap(err, route) - } - return result, nil + return c.WSEvents.BroadcastTx("broadcast_tx_sync", tx) } func (c *HTTP) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { - result := new(ctypes.ResultUnconfirmedTxs) - _, err := c.rpc.Call("unconfirmed_txs", map[string]interface{}{"limit": limit}, result) - if err != nil { - return nil, errors.Wrap(err, "unconfirmed_txs") - } - return result, nil + return c.WSEvents.UnconfirmedTxs(limit) } func (c *HTTP) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { - result := new(ctypes.ResultUnconfirmedTxs) - _, err := c.rpc.Call("num_unconfirmed_txs", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "num_unconfirmed_txs") - } - return result, nil + return c.WSEvents.NumUnconfirmedTxs() } func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) { - result := new(ctypes.ResultNetInfo) - _, err := c.rpc.Call("net_info", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "NetInfo") - } - return result, nil + return c.WSEvents.NetInfo() } func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - result := new(ctypes.ResultDumpConsensusState) - _, err := c.rpc.Call("dump_consensus_state", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "DumpConsensusState") - } - return result, nil + return c.WSEvents.DumpConsensusState() } func (c *HTTP) ConsensusState() (*ctypes.ResultConsensusState, error) { - result := new(ctypes.ResultConsensusState) - _, err := c.rpc.Call("consensus_state", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "ConsensusState") - } - return result, nil + return c.WSEvents.ConsensusState() } func (c *HTTP) Health() (*ctypes.ResultHealth, error) { - result := new(ctypes.ResultHealth) - _, err := c.rpc.Call("health", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "Health") - } - return result, nil + return c.WSEvents.Health() } func (c *HTTP) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { - result := new(ctypes.ResultBlockchainInfo) - _, err := c.rpc.Call("blockchain", - map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight}, - result) - if err != nil { - return nil, errors.Wrap(err, "BlockchainInfo") - } - return result, nil + return c.WSEvents.BlockchainInfo(minHeight, maxHeight) } func (c *HTTP) Genesis() (*ctypes.ResultGenesis, error) { - result := new(ctypes.ResultGenesis) - _, err := c.rpc.Call("genesis", map[string]interface{}{}, result) - if err != nil { - return nil, errors.Wrap(err, "Genesis") - } - return result, nil + return c.WSEvents.Genesis() } func (c *HTTP) Block(height *int64) (*ctypes.ResultBlock, error) { - result := new(ctypes.ResultBlock) - _, err := c.rpc.Call("block", map[string]interface{}{"height": height}, result) - if err != nil { - return nil, errors.Wrap(err, "Block") - } - return result, nil + return c.WSEvents.Block(height) } func (c *HTTP) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { - result := new(ctypes.ResultBlockResults) - _, err := c.rpc.Call("block_results", map[string]interface{}{"height": height}, result) - if err != nil { - return nil, errors.Wrap(err, "Block Result") - } - return result, nil + return c.WSEvents.BlockResults(height) } func (c *HTTP) Commit(height *int64) (*ctypes.ResultCommit, error) { - result := new(ctypes.ResultCommit) - _, err := c.rpc.Call("commit", map[string]interface{}{"height": height}, result) - if err != nil { - return nil, errors.Wrap(err, "Commit") - } - return result, nil + return c.WSEvents.Commit(height) } func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { - result := new(ctypes.ResultTx) - params := map[string]interface{}{ - "hash": hash, - "prove": prove, - } - _, err := c.rpc.Call("tx", params, result) - if err != nil { - return nil, errors.Wrap(err, "Tx") - } - return result, nil + return c.WSEvents.Tx(hash, prove) } func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { - result := new(ctypes.ResultTxSearch) - params := map[string]interface{}{ - "query": query, - "prove": prove, - "page": page, - "per_page": perPage, - } - _, err := c.rpc.Call("tx_search", params, result) - if err != nil { - return nil, errors.Wrap(err, "TxSearch") - } - return result, nil + return c.WSEvents.TxSearch(query, prove, page, perPage) } func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { - result := new(ctypes.ResultValidators) - _, err := c.rpc.Call("validators", map[string]interface{}{"height": height}, result) + return c.WSEvents.Validators(height) +} + +func (c *HTTP) QueryStore(key cmn.HexBytes, storeName string) ([]byte, error) { + path := fmt.Sprintf("/store/%s/%s", storeName, "key") + result, err := c.ABCIQuery(path, key) if err != nil { - return nil, errors.Wrap(err, "Validators") + return nil, err + } + resp := result.Response + if !resp.IsOK() { + return nil, errors.Errorf(resp.Log) } - return result, nil + return resp.Value, nil } /** websocket event stuff here... **/ @@ -259,18 +156,25 @@ type WSEvents struct { mtx sync.RWMutex // query -> chan - subscriptions map[string]chan ctypes.ResultEvent + subscriptionsQuitMap map[string]chan struct{} + subscriptionsIdMap map[string]rpctypes.JSONRPCStringID + subscriptionSet map[rpctypes.JSONRPCStringID]bool responseChanMap sync.Map + + timeout time.Duration } func newWSEvents(cdc *amino.Codec, remote, endpoint string) *WSEvents { wsEvents := &WSEvents{ - cdc: cdc, - endpoint: endpoint, - remote: remote, - subscriptions: make(map[string]chan ctypes.ResultEvent), + cdc: cdc, + endpoint: endpoint, + remote: remote, + subscriptionsQuitMap: make(map[string]chan struct{}), + subscriptionsIdMap: make(map[string]rpctypes.JSONRPCStringID), + subscriptionSet: make(map[rpctypes.JSONRPCStringID]bool), + timeout: defaultTimeout, } wsEvents.BaseService = *cmn.NewBaseService(nil, "WSEvents", wsEvents) @@ -302,40 +206,61 @@ func (w *WSEvents) OnStop() { // subscriber to query. By default, returns a channel with cap=1. Error is // returned if it fails to subscribe. // Channel is never closed to prevent clients from seeing an erroneus event. -func (w *WSEvents) Subscribe(ctx context.Context, subscriber, query string, - outCapacity ...int) (out <-chan ctypes.ResultEvent, err error) { +func (w *WSEvents) Subscribe(query string, + outCapacity ...int) (out chan ctypes.ResultEvent, err error) { + if _, ok := w.subscriptionsIdMap[query]; ok { + return nil, fmt.Errorf("already subscribe") + } - if err := w.ws.Subscribe(ctx, query); err != nil { + id, err := w.ws.GenRequestId() + if err != nil { return nil, err } - outCap := 1 if len(outCapacity) > 0 { outCap = outCapacity[0] } + outEvent := make(chan ctypes.ResultEvent, outCap) + outResp := make(chan rpctypes.RPCResponse, cap(outEvent)) + w.responseChanMap.Store(id, outResp) + ctx, cancel := w.NewContext() + defer cancel() + err = w.ws.Subscribe(ctx, id, query) + if err != nil { + w.responseChanMap.Delete(id) + return nil, err + } - outc := make(chan ctypes.ResultEvent, outCap) + quit := make(chan struct{}) w.mtx.Lock() - // subscriber param is ignored because Tendermint will override it with - // remote IP anyway. - w.subscriptions[query] = outc + w.subscriptionsQuitMap[query] = quit + w.subscriptionsIdMap[query] = id + w.subscriptionSet[id] = true w.mtx.Unlock() + go w.WaitForEventResponse(id, outResp, outEvent, quit) - return outc, nil + return outEvent, nil } // Unsubscribe implements EventsClient by using WSClient to unsubscribe given // subscriber from query. -func (w *WSEvents) Unsubscribe(ctx context.Context, subscriber, query string) error { - if err := w.ws.Unsubscribe(ctx, query); err != nil { +func (w *WSEvents) Unsubscribe(query string) error { + ctx, cancel := w.NewContext() + defer cancel() + if err := w.ws.Unsubscribe(ctx, w.ws.EmptyRequest(), query); err != nil { return err } w.mtx.Lock() - _, ok := w.subscriptions[query] - if ok { - delete(w.subscriptions, query) + if id, ok := w.subscriptionsIdMap[query]; ok { + delete(w.subscriptionSet, id) + w.responseChanMap.Delete(id) } + if quit, ok := w.subscriptionsQuitMap[query]; ok { + close(quit) + } + delete(w.subscriptionsIdMap, query) + delete(w.subscriptionsQuitMap, query) w.mtx.Unlock() return nil @@ -343,28 +268,233 @@ func (w *WSEvents) Unsubscribe(ctx context.Context, subscriber, query string) er // UnsubscribeAll implements EventsClient by using WSClient to unsubscribe // given subscriber from all the queries. -func (w *WSEvents) UnsubscribeAll(ctx context.Context, subscriber string) error { - if err := w.ws.UnsubscribeAll(ctx); err != nil { +func (w *WSEvents) UnsubscribeAll() error { + ctx, cancel := w.NewContext() + defer cancel() + if err := w.ws.UnsubscribeAll(ctx, w.ws.EmptyRequest()); err != nil { return err } w.mtx.Lock() - w.subscriptions = make(map[string]chan ctypes.ResultEvent) + for _, id := range w.subscriptionsIdMap { + w.responseChanMap.Delete(id) + } + for _, quit := range w.subscriptionsQuitMap { + close(quit) + } + w.subscriptionSet = make(map[rpctypes.JSONRPCStringID]bool) + w.subscriptionsQuitMap = make(map[string]chan struct{}) + w.subscriptionsIdMap = make(map[string]rpctypes.JSONRPCStringID) w.mtx.Unlock() return nil } -func (w *WSEvents) Status(ctx context.Context) (*ctypes.ResultStatus, error) { - requestId, err := w.ws.Status(ctx); +func (w *WSEvents) WaitForEventResponse(requestId interface{}, in chan rpctypes.RPCResponse, eventOut chan ctypes.ResultEvent, quit chan struct{}) { + + for { + select { + case <-quit: + return + case resp, ok := <-in: + if !ok { + w.Logger.Info("channel of event stream is closed", "request id", requestId) + return + } + if resp.Error != nil { + w.Logger.Error("receive error from event stream", "error", resp.Error) + continue + } + res := new(ctypes.ResultEvent) + err := w.cdc.UnmarshalJSON(resp.Result, res) + if err != nil { + w.Logger.Debug("receive unexpected data from event stream", "result", resp.Result) + continue + } + eventOut <- *res + } + } +} + +func (w *WSEvents) WaitForResponse(ctx context.Context, outChan chan rpctypes.RPCResponse, result interface{}) error { + select { + case resp, ok := <-outChan: + if !ok { + return fmt.Errorf("response channel is closed") + } + if resp.Error != nil { + return resp.Error + } + return w.cdc.UnmarshalJSON(resp.Result, result) + case <-ctx.Done(): + return ctx.Err() + } +} + +func (w *WSEvents) SimpleCall(doRpc func(ctx context.Context, id rpctypes.JSONRPCStringID) error, proto interface{}) error { + id, err := w.ws.GenRequestId() if err != nil { - return nil, err + return err + } + outChan := make(chan rpctypes.RPCResponse, 1) + w.responseChanMap.Store(id, outChan) + defer close(outChan) + defer w.responseChanMap.Delete(id) + ctx, cancel := w.NewContext() + defer cancel() + if err = doRpc(ctx, id); err != nil { + return err } + return w.WaitForResponse(ctx, outChan, proto) +} - outc := make(chan []byte, 1) - w.responseChanMap.Store(requestId,outc) +func (w *WSEvents) Status() (*ctypes.ResultStatus, error) { + status := new(ctypes.ResultStatus) + err := w.SimpleCall(w.ws.Status, status) + return status, err +} + +func (w *WSEvents) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + info := new(ctypes.ResultABCIInfo) + err := w.SimpleCall(w.ws.ABCIInfo, info) + return info, err +} + +func (w *WSEvents) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + abciQuery := new(ctypes.ResultABCIQuery) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.ABCIQueryWithOptions(ctx, id, path, data, opts) + }, abciQuery) + return abciQuery, err +} + +func (w *WSEvents) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + txCommit := new(ctypes.ResultBroadcastTxCommit) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BroadcastTxCommit(ctx, id, tx) + }, txCommit) + return txCommit, err +} + +func (w *WSEvents) BroadcastTx(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + txRes := new(ctypes.ResultBroadcastTx) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BroadcastTx(ctx, id, route, tx) + }, txRes) + return txRes, err +} - return outc, nil +func (w *WSEvents) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { + unConfirmTxs := new(ctypes.ResultUnconfirmedTxs) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.UnconfirmedTxs(ctx, id, limit) + }, unConfirmTxs) + return unConfirmTxs, err +} + +func (w *WSEvents) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { + numUnConfirmTxs := new(ctypes.ResultUnconfirmedTxs) + err := w.SimpleCall(w.ws.NumUnconfirmedTxs, numUnConfirmTxs) + return numUnConfirmTxs, err +} + +func (w *WSEvents) NetInfo() (*ctypes.ResultNetInfo, error) { + netInfo := new(ctypes.ResultNetInfo) + err := w.SimpleCall(w.ws.NetInfo, netInfo) + return netInfo, err +} + +func (w *WSEvents) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { + consensusState := new(ctypes.ResultDumpConsensusState) + err := w.SimpleCall(w.ws.DumpConsensusState, consensusState) + return consensusState, err +} + +func (w *WSEvents) ConsensusState() (*ctypes.ResultConsensusState, error) { + consensusState := new(ctypes.ResultConsensusState) + err := w.SimpleCall(w.ws.ConsensusState, consensusState) + return consensusState, err +} + +func (w *WSEvents) Health() (*ctypes.ResultHealth, error) { + health := new(ctypes.ResultHealth) + err := w.SimpleCall(w.ws.Health, health) + return health, err +} + +func (w *WSEvents) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + + blocksInfo := new(ctypes.ResultBlockchainInfo) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BlockchainInfo(ctx, id, minHeight, maxHeight) + }, blocksInfo) + return blocksInfo, err +} + +func (w *WSEvents) Genesis() (*ctypes.ResultGenesis, error) { + + genesis := new(ctypes.ResultGenesis) + err := w.SimpleCall(w.ws.Genesis, genesis) + return genesis, err +} + +func (w *WSEvents) Block(height *int64) (*ctypes.ResultBlock, error) { + block := new(ctypes.ResultBlock) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Block(ctx, id, height) + }, block) + return block, err +} + +func (w *WSEvents) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { + + block := new(ctypes.ResultBlockResults) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BlockResults(ctx, id, height) + }, block) + return block, err +} + +func (w *WSEvents) Commit(height *int64) (*ctypes.ResultCommit, error) { + commit := new(ctypes.ResultCommit) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Commit(ctx, id, height) + }, commit) + return commit, err +} + +func (w *WSEvents) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { + + tx := new(ctypes.ResultTx) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Tx(ctx, id, hash, prove) + }, tx) + return tx, err +} + +func (w *WSEvents) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { + + txs := new(ctypes.ResultTxSearch) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.TxSearch(ctx, id, query, prove, page, perPage) + }, txs) + return txs, err +} + +func (w *WSEvents) Validators(height *int64) (*ctypes.ResultValidators, error) { + validators := new(ctypes.ResultValidators) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Validators(ctx, id, height) + }, validators) + return validators, err +} + +func (w *WSEvents) SetTimeOut(timeout time.Duration) { + w.timeout = timeout +} + +func (w *WSEvents) NewContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), w.timeout) } // After being reconnected, it is necessary to redo subscription to server @@ -372,18 +502,15 @@ func (w *WSEvents) Status(ctx context.Context) (*ctypes.ResultStatus, error) { func (w *WSEvents) redoSubscriptionsAfter(d time.Duration) { time.Sleep(d) - for q := range w.subscriptions { - err := w.ws.Subscribe(context.Background(), q) + for q, id := range w.subscriptionsIdMap { + ctx, _ := context.WithTimeout(context.Background(), w.timeout) + err := w.ws.Subscribe(ctx, id, q) if err != nil { w.Logger.Error("Failed to resubscribe", "err", err) } } } -func isErrAlreadySubscribed(err error) bool { - return strings.Contains(err.Error(), tmpubsub.ErrAlreadySubscribed.Error()) -} - func (w *WSEvents) eventListener() { for { select { @@ -391,41 +518,29 @@ func (w *WSEvents) eventListener() { if !ok { return } - - if resp.Error != nil { - w.Logger.Error("WS error", "err", resp.Error.Error()) - // Error can be ErrAlreadySubscribed or max client (subscriptions per - // client) reached or Tendermint exited. - // We can ignore ErrAlreadySubscribed, but need to retry in other - // cases. - if !isErrAlreadySubscribed(resp.Error) { - // Resubscribe after 1 second to give Tendermint time to restart (if - // crashed). - w.redoSubscriptionsAfter(1 * time.Second) - } + id, ok := resp.ID.(rpctypes.JSONRPCStringID) + if !ok { + w.Logger.Error("unexpected request id type") continue } - - result := new(ctypes.ResultEvent) - err := w.cdc.UnmarshalJSON(resp.Result, result) - if err != nil { - w.Logger.Error("failed to unmarshal response", "err", err) + if exist := w.subscriptionSet[id]; exist { + // receive ack event, need ignore it continue } - - w.mtx.RLock() - if out, ok := w.subscriptions[result.Query]; ok { - if cap(out) == 0 { - out <- *result - } else { - select { - case out <- *result: - default: - w.Logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query) - } + idParts := strings.Split(string(id), "#") + realId := rpctypes.JSONRPCStringID(idParts[0]) + if out, ok := w.responseChanMap.Load(realId); ok { + outChan, ok := out.(chan rpctypes.RPCResponse) + if !ok { + w.Logger.Error("unexpected data type in responseChanMap") + continue + } + select { + case outChan <- resp: + default: + w.Logger.Error("wanted to publish response, but out channel is full", "result", resp.Result) } } - w.mtx.RUnlock() case <-w.Quit(): return } diff --git a/client/rpc/dex.go b/client/rpc/dex.go index 9ab1e3e8..ba0f68d0 100644 --- a/client/rpc/dex.go +++ b/client/rpc/dex.go @@ -1 +1,62 @@ package rpc + +import ( + "fmt" + "github.com/binance-chain/go-sdk/common/types" +) +const ( + MainStoreName = "main" + AccountStoreName = "acc" + ValAddrStoreName = "val" + TokenStoreName = "tokens" + DexStoreName = "dex" + PairStoreName = "pairs" + StakeStoreName = "stake" + ParamsStoreName = "params" + GovStoreName = "gov" + + StakeTransientStoreName = "transient_stake" + ParamsTransientStoreName = "transient_params" +) + +func (c *HTTP) ListAllTokens(offset int, limit int) ([]types.Token, error) { + path:=fmt.Sprintf("tokens/list/%d/%d", offset, limit) + result, err := c.ABCIQuery(path, nil) + if err != nil { + return nil, err + } + bz:=result.Response.GetValue() + tokens := make([]types.Token, 0) + err = c.cdc.UnmarshalBinaryLengthPrefixed(bz, &tokens) + return tokens, err +} + +func (c *HTTP) GetTokenInfo(symbol string) (*types.Token, error) { + path:=fmt.Sprintf("tokens/info/%s", symbol) + result, err := c.ABCIQuery(path, nil) + if err != nil { + return nil, err + } + bz:=result.Response.GetValue() + token := new(types.Token) + err = c.cdc.UnmarshalBinaryLengthPrefixed(bz, token) + return token, err +} + +func (c *HTTP) GetAccount(addr types.AccAddress)(acc types.Account, err error) { + key:=append([]byte("account:"), addr.Bytes()...) + bz, err := c.QueryStore(key, AccountStoreName) + if err != nil { + return nil, err + } + if bz == nil { + return nil, nil + } + err = c.cdc.UnmarshalBinaryBare(bz, &acc) + if err != nil { + return nil, err + } + return acc, err +} + + diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index 99d96cc6..88e55640 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/binance-chain/go-sdk/common/uuid" "net" "net/http" "strings" @@ -17,21 +16,25 @@ import ( "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/rpc/client" types "github.com/tendermint/tendermint/rpc/lib/types" + ctypes "github.com/tendermint/tendermint/types" + + "github.com/binance-chain/go-sdk/common/uuid" ) const ( - defaultMaxReconnectAttempts = 25 - defaultWriteWait = 0 - defaultReadWait = 0 - defaultPingPeriod = 0 + defaultMaxReconnectAttempts = 25 + defaultMaxReconnectBackOffTime = 600 * time.Second + defaultWriteWait = 100 * time.Millisecond + defaultReadWait = 0 + defaultPingPeriod = 0 protoHTTP = "http" protoHTTPS = "https" protoWSS = "wss" protoWS = "ws" protoTCP = "tcp" - ) // WSClient is a WebSocket client. The methods of WSClient are safe for use by @@ -58,7 +61,6 @@ type WSClient struct { // internal channels send chan types.RPCRequest // user requests - backlog chan types.RPCRequest // stores a single user request received during a conn failure reconnectAfter chan error // reconnect requests readRoutineQuit chan struct{} // a way for readRoutine to close writeRoutine @@ -69,6 +71,7 @@ type WSClient struct { reconnecting bool // Maximum reconnect attempts (0 or greater; default: 25). + // Less than 0 means always try to reconnect. maxReconnectAttempts int // Time allowed to write a message to the server. 0 means block until operation succeeds. @@ -175,7 +178,6 @@ func (c *WSClient) OnStart() error { c.reconnectAfter = make(chan error, 1) // capacity for 1 request. a user won't be able to send more because the send // channel is unbuffered. - c.backlog = make(chan types.RPCRequest, 1) c.startReadWriteRoutines() go c.reconnectRoutine() @@ -222,17 +224,15 @@ func (c *WSClient) Send(ctx context.Context, request types.RPCRequest) error { } // Call the given method. See Send description. -func (c *WSClient) Call(ctx context.Context, method string, params map[string]interface{}) (string, error) { - id, err := uuid.NewV4() - if err != nil { - return "", err +func (c *WSClient) Call(ctx context.Context, method string, id types.JSONRPCStringID, params map[string]interface{}) error { + if c.IsReconnecting(){ + return fmt.Errorf("websocket is reconnecting, can't send any request") } - requestId := id.String() - request, err := types.MapToRequest(c.cdc, types.JSONRPCStringID(requestId), method, params) + request, err := types.MapToRequest(c.cdc, id, method, params) if err != nil { - return "", err + return err } - return requestId, c.Send(ctx, request) + return c.Send(ctx, request) } // CallWithArrayParams the given method with params in a form of array. See @@ -253,6 +253,18 @@ func (c *WSClient) SetCodec(cdc *amino.Codec) { c.cdc = cdc } +func (c *WSClient) GenRequestId() (types.JSONRPCStringID, error) { + id, err := uuid.NewV4() + if err != nil { + return "", err + } + return types.JSONRPCStringID(id.String()), nil +} + +func (c *WSClient) EmptyRequest() types.JSONRPCStringID { + return types.JSONRPCStringID("") +} + /////////////////////////////////////////////////////////////////////////////// // Private methods @@ -283,13 +295,15 @@ func (c *WSClient) reconnect() error { c.reconnecting = false c.mtx.Unlock() }() - + backOffDuration := 1 * time.Second for { - jitterSeconds := time.Duration(cmn.RandFloat64() * float64(time.Second)) // 1s == (1e9 ns) - backoffDuration := jitterSeconds + ((1 << uint(attempt)) * time.Second) - - c.Logger.Info("reconnecting", "attempt", attempt+1, "backoff_duration", backoffDuration) - time.Sleep(backoffDuration) + // will never overflow until doomsday + backOffDuration := time.Duration(attempt)*time.Second + backOffDuration + if backOffDuration > defaultMaxReconnectBackOffTime { + backOffDuration = defaultMaxReconnectBackOffTime + } + c.Logger.Info("reconnecting", "attempt", attempt+1, "backoff_duration", backOffDuration) + time.Sleep(backOffDuration) err := c.dial() if err != nil { @@ -304,7 +318,7 @@ func (c *WSClient) reconnect() error { attempt++ - if attempt > c.maxReconnectAttempts { + if c.maxReconnectAttempts >=0 && attempt > c.maxReconnectAttempts { return errors.Wrap(err, "reached maximum reconnect attempts") } } @@ -317,27 +331,6 @@ func (c *WSClient) startReadWriteRoutines() { go c.writeRoutine() } -func (c *WSClient) processBacklog() error { - select { - case request := <-c.backlog: - if c.writeWait > 0 { - if err := c.conn.SetWriteDeadline(time.Now().Add(c.writeWait)); err != nil { - c.Logger.Error("failed to set write deadline", "err", err) - } - } - if err := c.conn.WriteJSON(request); err != nil { - c.Logger.Error("failed to resend request", "err", err) - c.reconnectAfter <- err - // requeue request - c.backlog <- request - return err - } - c.Logger.Info("resend a request", "req", request) - default: - } - return nil -} - func (c *WSClient) reconnectRoutine() { for { select { @@ -358,10 +351,6 @@ func (c *WSClient) reconnectRoutine() { break LOOP } } - err := c.processBacklog() - if err == nil { - c.startReadWriteRoutines() - } case <-c.Quit(): return @@ -402,7 +391,7 @@ func (c *WSClient) writeRoutine() { c.Logger.Error("failed to send request", "err", err) c.reconnectAfter <- err // add request to the backlog, so we don't lose it - c.backlog <- request + //c.backlog <- request return } case <-ticker.C: @@ -494,31 +483,110 @@ func (c *WSClient) readRoutine() { // Subscribe to a query. Note the server must have a "subscribe" route // defined. -func (c *WSClient) Subscribe(ctx context.Context, query string) error { +func (c *WSClient) Subscribe(ctx context.Context, id types.JSONRPCStringID, query string) error { params := map[string]interface{}{"query": query} - _, err := c.Call(ctx, "subscribe", params) - return err + return c.Call(ctx, "subscribe", id, params) } // Unsubscribe from a query. Note the server must have a "unsubscribe" route // defined. -func (c *WSClient) Unsubscribe(ctx context.Context, query string) error { +func (c *WSClient) Unsubscribe(ctx context.Context, id types.JSONRPCStringID, query string) error { params := map[string]interface{}{"query": query} - _, err := c.Call(ctx, "unsubscribe", params) - return err + return c.Call(ctx, "unsubscribe", id, params) } // UnsubscribeAll from all. Note the server must have a "unsubscribe_all" route // defined. -func (c *WSClient) UnsubscribeAll(ctx context.Context) error { +func (c *WSClient) UnsubscribeAll(ctx context.Context, id types.JSONRPCStringID) error { params := map[string]interface{}{} - _, err := c.Call(ctx, "unsubscribe_all", params) - return err + return c.Call(ctx, "unsubscribe_all", id, params) +} + +func (c *WSClient) Status(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "status", id, map[string]interface{}{}) +} + +func (c *WSClient) ABCIInfo(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "abci_info", id, map[string]interface{}{}) } -func (c *WSClient) Status(ctx context.Context) (string, error) { - return c.Call(ctx, "status", map[string]interface{}{}) +func (c *WSClient) ABCIQueryWithOptions(ctx context.Context, id types.JSONRPCStringID, path string, data cmn.HexBytes, opts client.ABCIQueryOptions) error { + return c.Call(ctx, "abci_query", id, map[string]interface{}{"path": path, "data": data, "height": opts.Height, "prove": opts.Prove}) +} + +func (c *WSClient) BroadcastTxCommit(ctx context.Context, id types.JSONRPCStringID, tx ctypes.Tx) error { + return c.Call(ctx, "broadcast_tx_commit", id, map[string]interface{}{"tx": tx}) +} + +func (c *WSClient) BroadcastTx(ctx context.Context, id types.JSONRPCStringID, route string, tx ctypes.Tx) error { + return c.Call(ctx, route, id, map[string]interface{}{"tx": tx}) +} + +func (c *WSClient) UnconfirmedTxs(ctx context.Context, id types.JSONRPCStringID, limit int) error { + return c.Call(ctx, "unconfirmed_txs", id, map[string]interface{}{"limit": limit}) +} + +func (c *WSClient) NumUnconfirmedTxs(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "num_unconfirmed_txs", id, map[string]interface{}{}) +} + +func (c *WSClient) NetInfo(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "net_info", id, map[string]interface{}{}) +} + +func (c *WSClient) DumpConsensusState(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "dump_consensus_state", id, map[string]interface{}{}) +} + +func (c *WSClient) ConsensusState(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "consensus_state", id, map[string]interface{}{}) +} + +func (c *WSClient) Health(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "health", id, map[string]interface{}{}) +} + +func (c *WSClient) BlockchainInfo(ctx context.Context, id types.JSONRPCStringID, minHeight, maxHeight int64) error { + return c.Call(ctx, "blockchain", id, + map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight}) +} + +func (c *WSClient) Genesis(ctx context.Context, id types.JSONRPCStringID) error { + return c.Call(ctx, "genesis", id, map[string]interface{}{}) +} + +func (c *WSClient) Block(ctx context.Context, id types.JSONRPCStringID, height *int64) error { + return c.Call(ctx, "block", id, map[string]interface{}{"height": height}) +} + +func (c *WSClient) BlockResults(ctx context.Context, id types.JSONRPCStringID, height *int64) error { + return c.Call(ctx, "block_results", id, map[string]interface{}{"height": height}) +} + +func (c *WSClient) Commit(ctx context.Context, id types.JSONRPCStringID, height *int64) error { + return c.Call(ctx, "commit", id, map[string]interface{}{"height": height}) +} +func (c *WSClient) Tx(ctx context.Context, id types.JSONRPCStringID, hash []byte, prove bool) error { + params := map[string]interface{}{ + "hash": hash, + "prove": prove, + } + return c.Call(ctx, "tx", id, params) +} + +func (c *WSClient) TxSearch(ctx context.Context, id types.JSONRPCStringID, query string, prove bool, page, perPage int) error { + params := map[string]interface{}{ + "query": query, + "prove": prove, + "page": page, + "per_page": perPage, + } + return c.Call(ctx, "tx_search", id, params) +} + +func (c *WSClient) Validators(ctx context.Context, id types.JSONRPCStringID, height *int64) error { + return c.Call(ctx, "validators", id, map[string]interface{}{"height": height}) } func makeHTTPDialer(remoteAddr string) (string, string, func(string, string) (net.Conn, error)) { @@ -554,4 +622,4 @@ func makeHTTPDialer(remoteAddr string) (string, string, func(string, string) (ne return clientProtocol, trimmedAddress, func(proto, addr string) (net.Conn, error) { return net.Dial(protocol, address) } -} \ No newline at end of file +} diff --git a/client/transaction/deposit_proposal.go b/client/transaction/deposit_proposal.go index d4561966..b7113dc7 100644 --- a/client/transaction/deposit_proposal.go +++ b/client/transaction/deposit_proposal.go @@ -1,6 +1,7 @@ package transaction import ( + ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/types" "github.com/binance-chain/go-sdk/types/msg" "github.com/binance-chain/go-sdk/types/tx" @@ -12,7 +13,7 @@ type DepositProposalResult struct { func (c *client) DepositProposal(proposalID int64, amount int64, sync bool) (*DepositProposalResult, error) { fromAddr := c.keyManager.GetAddr() - coins := types.Coins{types.Coin{Denom: types.NativeSymbol, Amount: amount}} + coins := ctypes.Coins{ctypes.Coin{Denom: types.NativeSymbol, Amount: amount}} depositMsg := msg.NewDepositMsg(fromAddr, proposalID, coins) err := depositMsg.ValidateBasic() if err != nil { diff --git a/client/transaction/send_token.go b/client/transaction/send_token.go index efca2083..6b3a12cf 100644 --- a/client/transaction/send_token.go +++ b/client/transaction/send_token.go @@ -1,7 +1,7 @@ package transaction import ( - "github.com/binance-chain/go-sdk/types" + "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/types/msg" "github.com/binance-chain/go-sdk/types/tx" ) diff --git a/client/transaction/submit_proposal.go b/client/transaction/submit_proposal.go index 6a07ce5d..6d6b1c05 100644 --- a/client/transaction/submit_proposal.go +++ b/client/transaction/submit_proposal.go @@ -8,6 +8,8 @@ import ( "github.com/binance-chain/go-sdk/types" "github.com/binance-chain/go-sdk/types/msg" "github.com/binance-chain/go-sdk/types/tx" + ctypes "github.com/binance-chain/go-sdk/common/types" + ) type SubmitProposalResult struct { @@ -25,7 +27,7 @@ func (c *client) SubmitListPairProposal(title string, param msg.ListTradingPairP func (c *client) SubmitProposal(title string, description string, proposalType msg.ProposalKind, initialDeposit int64, votingPeriod time.Duration, sync bool) (*SubmitProposalResult, error) { fromAddr := c.keyManager.GetAddr() - coins := types.Coins{types.Coin{Denom: types.NativeSymbol, Amount: initialDeposit}} + coins := ctypes.Coins{ctypes.Coin{Denom: types.NativeSymbol, Amount: initialDeposit}} proposalMsg := msg.NewMsgSubmitProposal(title, description, proposalType, fromAddr, coins, votingPeriod) err := proposalMsg.ValidateBasic() if err != nil { diff --git a/common/crypto/encoding/amino/amino.go b/common/crypto/encoding/amino/amino.go index a924fd36..7294f7bb 100644 --- a/common/crypto/encoding/amino/amino.go +++ b/common/crypto/encoding/amino/amino.go @@ -1,7 +1,7 @@ package cryptoAmino import ( - "github.com/binance-chain/bnc-go-amino" + "github.com/tendermint/go-amino" "github.com/binance-chain/go-sdk/common/crypto" "github.com/binance-chain/go-sdk/common/crypto/secp256k1" diff --git a/common/crypto/secp256k1/secp256k1.go b/common/crypto/secp256k1/secp256k1.go index ffe44004..81d6275a 100644 --- a/common/crypto/secp256k1/secp256k1.go +++ b/common/crypto/secp256k1/secp256k1.go @@ -8,7 +8,7 @@ import ( "io" secp256k1 "github.com/tendermint/btcd/btcec" - "github.com/binance-chain/bnc-go-amino" + "github.com/tendermint/go-amino" "golang.org/x/crypto/ripemd160" "github.com/binance-chain/go-sdk/common/crypto" diff --git a/common/types/bank.go b/common/types/bank.go index 35d60d7e..adb2b35f 100644 --- a/common/types/bank.go +++ b/common/types/bank.go @@ -1,6 +1,10 @@ package types -import "github.com/binance-chain/go-sdk/types" +import ( + "github.com/binance-chain/go-sdk/common/crypto" + "github.com/pkg/errors" + "strings" +) // Token definition type Token struct { @@ -8,6 +12,309 @@ type Token struct { Symbol string `json:"symbol"` OrigSymbol string `json:"original_symbol"` TotalSupply Fixed8 `json:"total_supply"` - Owner types.AccAddress `json:"owner"` + Owner AccAddress `json:"owner"` Mintable bool `json:"mintable"` } + +// AppAccount definition +type AppAccount struct { + BaseAccount `json:"base"` + Name string `json:"name"` + FrozenCoins []Coin `json:"frozen"` + LockedCoins []Coin `json:"locked"` +} + +// Coin def +// Coin def +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} + +func (coin Coin) IsZero() bool { + return coin.Amount == 0 +} + +func (coin Coin) IsPositive() bool { + return coin.Amount > 0 +} + +func (coin Coin) IsNotNegative() bool { + return coin.Amount >= 0 +} + +func (coin Coin) SameDenomAs(other Coin) bool { + return (coin.Denom == other.Denom) +} + +func (coin Coin) Plus(coinB Coin) Coin { + if !coin.SameDenomAs(coinB) { + return coin + } + return Coin{coin.Denom, coin.Amount + coinB.Amount} +} + +// Coins def +type Coins []Coin + +func (coins Coins) IsValid() bool { + switch len(coins) { + case 0: + return true + case 1: + return !coins[0].IsZero() + default: + lowDenom := coins[0].Denom + for _, coin := range coins[1:] { + if coin.Denom <= lowDenom { + return false + } + if coin.IsZero() { + return false + } + lowDenom = coin.Denom + } + return true + } +} + +func (coins Coins) IsPositive() bool { + if len(coins) == 0 { + return false + } + for _, coin := range coins { + if !coin.IsPositive() { + return false + } + } + return true +} + +func (coins Coins) Plus(coinsB Coins) Coins { + sum := ([]Coin)(nil) + indexA, indexB := 0, 0 + lenA, lenB := len(coins), len(coinsB) + for { + if indexA == lenA { + if indexB == lenB { + return sum + } + return append(sum, coinsB[indexB:]...) + } else if indexB == lenB { + return append(sum, coins[indexA:]...) + } + coinA, coinB := coins[indexA], coinsB[indexB] + switch strings.Compare(coinA.Denom, coinB.Denom) { + case -1: + sum = append(sum, coinA) + indexA++ + case 0: + if coinA.Amount+coinB.Amount == 0 { + // ignore 0 sum coin type + } else { + sum = append(sum, coinA.Plus(coinB)) + } + indexA++ + indexB++ + case 1: + sum = append(sum, coinB) + indexB++ + } + } +} + +// IsEqual returns true if the two sets of Coins have the same value +func (coins Coins) IsEqual(coinsB Coins) bool { + if len(coins) != len(coinsB) { + return false + } + for i := 0; i < len(coins); i++ { + if coins[i].Denom != coinsB[i].Denom || !(coins[i].Amount == coinsB[i].Amount) { + return false + } + } + return true +} + +func (coins Coins) IsNotNegative() bool { + if len(coins) == 0 { + return true + } + for _, coin := range coins { + if !coin.IsNotNegative() { + return false + } + } + return true +} + + + +type Account interface { + GetAddress() AccAddress + SetAddress(address AccAddress) error // errors if already set. + + GetPubKey() crypto.PubKey // can return nil. + SetPubKey(crypto.PubKey) error + + GetAccountNumber() int64 + SetAccountNumber(int64) error + + GetSequence() int64 + SetSequence(int64) error + + GetCoins() Coins + SetCoins(Coins) error + Clone() Account +} + +type NamedAccount interface { + Account + GetName() string + SetName(string) + + GetFrozenCoins() Coins + SetFrozenCoins(Coins) + + //TODO: this should merge into Coin + GetLockedCoins() Coins + SetLockedCoins(Coins) +} + +type NamedAcount interface { + Account + GetName() string + SetName(string) + + GetFrozenCoins() []Coin + SetFrozenCoins([]Coin) + + //TODO: this should merge into Coin + GetLockedCoins() []Coin + SetLockedCoins([]Coin) +} + +func (acc AppAccount) GetName() string { return acc.Name } +func (acc *AppAccount) SetName(name string) { acc.Name = name } +func (acc AppAccount) GetFrozenCoins() []Coin { return acc.FrozenCoins } +func (acc *AppAccount) SetFrozenCoins(frozen []Coin) { acc.FrozenCoins = frozen } +func (acc AppAccount) GetLockedCoins() []Coin { return acc.LockedCoins } +func (acc *AppAccount) SetLockedCoins(frozen []Coin) { acc.LockedCoins = frozen } + +func (acc *AppAccount) Clone() Account { + baseAcc := acc.BaseAccount.Clone().(*BaseAccount) + clonedAcc := &AppAccount{ + BaseAccount: *baseAcc, + Name: acc.Name, + } + if acc.FrozenCoins == nil { + clonedAcc.FrozenCoins = nil + } else { + coins := Coins{} + for _, coin := range acc.FrozenCoins { + coins = append(coins, Coin{Denom: coin.Denom, Amount: coin.Amount}) + } + clonedAcc.FrozenCoins = coins + } + if acc.LockedCoins == nil { + clonedAcc.LockedCoins = nil + } else { + coins := Coins{} + for _, coin := range acc.LockedCoins { + coins = append(coins, Coin{Denom: coin.Denom, Amount: coin.Amount}) + } + clonedAcc.LockedCoins = coins + } + return clonedAcc +} + +type BaseAccount struct { + Address AccAddress `json:"address"` + Coins Coins `json:"coins"` + PubKey crypto.PubKey `json:"public_key"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` +} + +// Implements sdk.Account. +func (acc BaseAccount) GetAddress() AccAddress { + return acc.Address +} + +// Implements sdk.Account. +func (acc *BaseAccount) SetAddress(addr AccAddress) error { + if len(acc.Address) != 0 { + return errors.New("cannot override BaseAccount address") + } + acc.Address = addr + return nil +} + +// Implements sdk.Account. +func (acc BaseAccount) GetPubKey() crypto.PubKey { + return acc.PubKey +} + +// Implements sdk.Account. +func (acc *BaseAccount) SetPubKey(pubKey crypto.PubKey) error { + acc.PubKey = pubKey + return nil +} + +// Implements sdk.Account. +func (acc *BaseAccount) GetCoins() Coins { + return acc.Coins +} + +// Implements sdk.Account. +func (acc *BaseAccount) SetCoins(coins Coins) error { + acc.Coins = coins + return nil +} + +// Implements Account +func (acc *BaseAccount) GetAccountNumber() int64 { + return acc.AccountNumber +} + +// Implements Account +func (acc *BaseAccount) SetAccountNumber(accNumber int64) error { + acc.AccountNumber = accNumber + return nil +} + +// Implements sdk.Account. +func (acc *BaseAccount) GetSequence() int64 { + return acc.Sequence +} + +// Implements sdk.Account. +func (acc *BaseAccount) SetSequence(seq int64) error { + acc.Sequence = seq + return nil +} + +// Implements sdk.Account. +func (acc *BaseAccount) Clone() Account { + // given the fact PubKey and Address doesn't change, + // it should be fine if not deep copy them. if both of + // the two interfaces can provide a Clone() method would be terrific. + clonedAcc := &BaseAccount{ + PubKey: acc.PubKey, + Address: acc.Address, + AccountNumber: acc.AccountNumber, + Sequence: acc.Sequence, + } + + if acc.Coins == nil { + clonedAcc.Coins = nil + } else { + coins := make(Coins, 0, len(acc.Coins)) + for _, coin := range acc.Coins { + coins = append(coins, Coin{Denom: coin.Denom, Amount: coin.Amount}) + } + clonedAcc.Coins = coins + } + + return clonedAcc +} \ No newline at end of file diff --git a/common/types/wire.go b/common/types/wire.go index ab1254f4..9f641502 100644 --- a/common/types/wire.go +++ b/common/types/wire.go @@ -1 +1,11 @@ package types + +import "github.com/tendermint/go-amino" + +func RegisterWire(cdc *amino.Codec) { + + cdc.RegisterConcrete(Token{}, "bnbchain/Token", nil) + cdc.RegisterInterface((*Account)(nil), nil) + cdc.RegisterInterface((*NamedAccount)(nil), nil) + cdc.RegisterConcrete(&AppAccount{}, "bnbchain/Account", nil) +} diff --git a/e2e/e2e_rpc_test.go b/e2e/e2e_rpc_test.go index df8caf70..30aa37b3 100644 --- a/e2e/e2e_rpc_test.go +++ b/e2e/e2e_rpc_test.go @@ -1 +1,302 @@ package e2e + +import ( + "encoding/hex" + "encoding/json" + "fmt" + ctypes "github.com/binance-chain/go-sdk/common/types" + "math/rand" + "sync" + "testing" + "time" + + "github.com/binance-chain/go-sdk/client/rpc" + "github.com/stretchr/testify/assert" + tmquery "github.com/tendermint/tendermint/libs/pubsub/query" +) + +var ( + nodeAddr = "tcp://data-seed-pre-1-s3.binance.org:80" + badAddr = "tcp://127.0.0.1:80" + testTxHash = "A9DBDB2052FEEA13B953B40F8E6D3D0B0D0C592A9A0736A99BA4A4C31A3E33C8" + testTxHeight = 6064550 + + onceClient = sync.Once{} + testClientInstance *rpc.HTTP +) + +func defaultClient() *rpc.HTTP { + onceClient.Do(func() { + testClientInstance = rpc.NewRPCClient(nodeAddr) + }) + return testClientInstance +} + +func TestRPCStatus(t *testing.T) { + c := defaultClient() + status, err := c.Status() + assert.NoError(t, err) + bz, err := json.Marshal(status) + fmt.Println(string(bz)) +} + +func TestRPCABCIInfo(t *testing.T) { + c := defaultClient() + info, err := c.ABCIInfo() + assert.NoError(t, err) + bz, err := json.Marshal(info) + fmt.Println(string(bz)) +} + +func TestUnconfirmedTxs(t *testing.T) { + c := defaultClient() + txs, err := c.UnconfirmedTxs(10) + assert.NoError(t, err) + bz, err := json.Marshal(txs) + fmt.Println(string(bz)) +} + +func TestNumUnconfirmedTxs(t *testing.T) { + c := defaultClient() + numTxs, err := c.NumUnconfirmedTxs() + assert.NoError(t, err) + bz, err := json.Marshal(numTxs) + fmt.Println(string(bz)) +} + +func TestNetInfo(t *testing.T) { + c := defaultClient() + netInfo, err := c.NetInfo() + assert.NoError(t, err) + bz, err := json.Marshal(netInfo) + fmt.Println(string(bz)) +} + +func TestDumpConsensusState(t *testing.T) { + c := defaultClient() + state, err := c.DumpConsensusState() + assert.NoError(t, err) + bz, err := json.Marshal(state) + fmt.Println(string(bz)) +} + +func TestConsensusState(t *testing.T) { + c := defaultClient() + state, err := c.ConsensusState() + assert.NoError(t, err) + bz, err := json.Marshal(state) + fmt.Println(string(bz)) +} + +func TestHealth(t *testing.T) { + c := defaultClient() + health, err := c.Health() + assert.NoError(t, err) + bz, err := json.Marshal(health) + fmt.Println(string(bz)) +} + +func TestBlockchainInfo(t *testing.T) { + c := defaultClient() + blockInfos, err := c.BlockchainInfo(1, 5) + assert.NoError(t, err) + bz, err := json.Marshal(blockInfos) + fmt.Println(string(bz)) +} + +func TestGenesis(t *testing.T) { + c := defaultClient() + genesis, err := c.Genesis() + assert.NoError(t, err) + bz, err := json.Marshal(genesis) + fmt.Println(string(bz)) +} + +func TestBlock(t *testing.T) { + c := defaultClient() + block, err := c.Block(nil) + assert.NoError(t, err) + bz, err := json.Marshal(block) + fmt.Println(string(bz)) +} + +func TestBlockResults(t *testing.T) { + c := defaultClient() + block, err := c.BlockResults(nil) + assert.NoError(t, err) + bz, err := json.Marshal(block) + fmt.Println(string(bz)) +} + +func TestCommit(t *testing.T) { + c := defaultClient() + commit, err := c.Commit(nil) + assert.NoError(t, err) + bz, err := json.Marshal(commit) + fmt.Println(string(bz)) +} + +func TestTx(t *testing.T) { + c := defaultClient() + bz, err := hex.DecodeString(testTxHash) + assert.NoError(t, err) + + tx, err := c.Tx(bz, false) + assert.NoError(t, err) + bz, err = json.Marshal(tx) + fmt.Println(string(bz)) +} + + +func TestReconnection(t *testing.T) { + c := defaultClient() + status, err := c.Status() + assert.NoError(t, err) + bz, err := json.Marshal(status) + fmt.Println(string(bz)) + time.Sleep(10*time.Second) + status, err = c.Status() + assert.Error(t, err) + fmt.Println(err) + time.Sleep(10*time.Second) + status, err = c.Status() + assert.Error(t, err) + fmt.Println(err) + bz, err = json.Marshal(status) + fmt.Println(string(bz)) +} + + +func TestTxSearch(t *testing.T) { + c := defaultClient() + + tx, err := c.TxSearch(fmt.Sprintf("tx.height=%d", testTxHeight), false, 1, 10) + assert.NoError(t, err) + bz, err := json.Marshal(tx) + fmt.Println(string(bz)) +} + +func TestValidators(t *testing.T) { + c := defaultClient() + validators, err := c.Validators(nil) + assert.NoError(t, err) + bz, err := json.Marshal(validators) + fmt.Println(string(bz)) +} + +func TestBadNodeAddr(t *testing.T) { + c := rpc.NewRPCClient(badAddr) + _, err := c.Validators(nil) + assert.Error(t, err, "context deadline exceeded") +} + +func TestSetTimeOut(t *testing.T) { + c := rpc.NewRPCClient(badAddr) + c.SetTimeOut(1 * time.Second) + before := time.Now() + _, err := c.Validators(nil) + duration := time.Now().Sub(before).Seconds() + assert.True(t, duration > 1) + assert.True(t, duration < 2) + assert.Error(t, err, "context deadline exceeded") +} + +func TestSubscribeEvent(t *testing.T) { + c := defaultClient() + query := "tm.event = 'CompleteProposal'" + _, err := tmquery.New(query) + assert.NoError(t, err) + out, err := c.Subscribe(query, 10) + assert.NoError(t, err) + noMoreEvent := make(chan struct{}, 1) + go func() { + for { + select { + case o := <-out: + bz, err := json.Marshal(o) + assert.NoError(t, err) + fmt.Println(string(bz)) + case <-noMoreEvent: + fmt.Println("no more event after") + } + } + }() + time.Sleep(100 * time.Second) + err = c.Unsubscribe(query) + noMoreEvent <- struct{}{} + assert.NoError(t, err) + time.Sleep(3 * time.Second) +} + +func TestSubscribeEventTwice(t *testing.T) { + c := defaultClient() + query := "tm.event = 'CompleteProposal'" + _, err := tmquery.New(query) + assert.NoError(t, err) + _, err = c.Subscribe(query, 10) + assert.NoError(t, err) + _, err = c.Subscribe(query, 10) + assert.Error(t, err) +} + +func TestReceiveWithRequestId(t *testing.T) { + c := defaultClient() + c.SetTimeOut(5 * time.Second) + w := sync.WaitGroup{} + w.Add(10) + testCases := []func(t *testing.T){ + TestRPCStatus, + TestRPCABCIInfo, + TestUnconfirmedTxs, + TestNumUnconfirmedTxs, + TestNetInfo, + TestDumpConsensusState, + TestConsensusState, + TestHealth, + TestBlockchainInfo, + TestGenesis, + TestBlock, + TestBlockResults, + TestCommit, + TestTx, + TestTxSearch, + //TestValidators, + } + for i := 0; i < 10; i++ { + testFuncIndex := rand.Intn(len(testCases)) + go func() { + testCases[testFuncIndex](t) + w.Done() + }() + } + w.Wait() +} + +func TestListAllTokens(t *testing.T) { + c := defaultClient() + tokens, err := c.ListAllTokens(1,10) + assert.NoError(t, err) + bz, err := json.Marshal(tokens) + fmt.Println(string(bz)) +} + + +func TestGetTokenInfo(t *testing.T) { + c := defaultClient() + token, err := c.GetTokenInfo("BNB") + assert.NoError(t, err) + bz, err := json.Marshal(token) + fmt.Println(string(bz)) +} + +func TestGetAccount(t *testing.T) { + ctypes.Network = ctypes.TestNetwork + c := defaultClient() + acc,err:=ctypes.AccAddressFromBech32("tbnb1z7sr92ar6njy9f80r4zl5rjtgm0hsej4686asa") + assert.NoError(t,err) + account,err:=c.GetAccount(acc) + assert.NoError(t,err) + bz, err := json.Marshal(account) + fmt.Println(string(bz)) +} + diff --git a/e2e/e2e_trans_test.go b/e2e/e2e_trans_test.go index de7fb09f..178e0a5c 100644 --- a/e2e/e2e_trans_test.go +++ b/e2e/e2e_trans_test.go @@ -10,8 +10,8 @@ import ( sdk "github.com/binance-chain/go-sdk/client" "github.com/binance-chain/go-sdk/client/query" "github.com/binance-chain/go-sdk/common" + ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/keys" - "github.com/binance-chain/go-sdk/types" "github.com/binance-chain/go-sdk/types/msg" tx2 "github.com/binance-chain/go-sdk/types/tx" ) @@ -121,7 +121,7 @@ func TestTransProcess(t *testing.T) { fmt.Printf("GetTx: %v\n", tx) //---- Send tx ----------- - send, err := client.SendToken([]msg.Transfer{{testAccount2, []types.Coin{{nativeSymbol, 100000000}}}, {testAccount3, []types.Coin{{nativeSymbol, 100000000}}}}, true) + send, err := client.SendToken([]msg.Transfer{{testAccount2, []ctypes.Coin{{nativeSymbol, 100000000}}}, {testAccount3, []ctypes.Coin{{nativeSymbol, 100000000}}}}, true) assert.NoError(t, err) assert.True(t, send.Ok) fmt.Printf("Send token: %v\n", send) diff --git a/e2e/e2e_ws_test.go b/e2e/e2e_ws_test.go index 49a76e5a..48337468 100644 --- a/e2e/e2e_ws_test.go +++ b/e2e/e2e_ws_test.go @@ -6,6 +6,7 @@ import ( sdk "github.com/binance-chain/go-sdk/client" "github.com/binance-chain/go-sdk/client/query" "github.com/binance-chain/go-sdk/client/websocket" + ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/keys" "github.com/binance-chain/go-sdk/types" "github.com/stretchr/testify/assert" @@ -18,7 +19,7 @@ func NewClient(t *testing.T) sdk.DexClient { keyManager, err := keys.NewMnemonicKeyManager(mnemonic) assert.NoError(t, err) - client, err := sdk.NewDexClient("testnet-dex.binance.org", types.TestNetwork, keyManager) + client, err := sdk.NewDexClient("testnet-dex.binance.org", ctypes.TestNetwork, keyManager) assert.NoError(t, err) return client } diff --git a/keys/keys.go b/keys/keys.go index 97c5f545..fc7080a8 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + ctypes "github.com/binance-chain/go-sdk/common/types" "io/ioutil" "strings" @@ -16,7 +17,6 @@ import ( "github.com/binance-chain/go-sdk/common/crypto" "github.com/binance-chain/go-sdk/common/crypto/secp256k1" "github.com/binance-chain/go-sdk/common/uuid" - "github.com/binance-chain/go-sdk/types" "github.com/binance-chain/go-sdk/types/tx" ) @@ -27,7 +27,7 @@ const ( type KeyManager interface { Sign(tx.StdSignMsg) ([]byte, error) GetPrivKey() crypto.PrivKey - GetAddr() types.AccAddress + GetAddr() ctypes.AccAddress ExportAsMnemonic() (string, error) ExportAsPrivateKey() (string, error) @@ -54,7 +54,7 @@ func NewPrivateKeyManager(priKey string) (KeyManager, error) { type keyManager struct { privKey crypto.PrivKey - addr types.AccAddress + addr ctypes.AccAddress mnemonic string } @@ -105,7 +105,7 @@ func (m *keyManager) recoveryFromKMnemonic(mnemonic string) error { return err } priKey := secp256k1.PrivKeySecp256k1(derivedPriv) - addr := types.AccAddress(priKey.PubKey().Address()) + addr := ctypes.AccAddress(priKey.PubKey().Address()) if err != nil { return err } @@ -138,7 +138,7 @@ func (m *keyManager) recoveryFromKeyStore(keystoreFile string, auth string) erro var keyBytesArray [32]byte copy(keyBytesArray[:], keyBytes[:32]) priKey := secp256k1.PrivKeySecp256k1(keyBytesArray) - addr := types.AccAddress(priKey.PubKey().Address()) + addr := ctypes.AccAddress(priKey.PubKey().Address()) m.addr = addr m.privKey = priKey return nil @@ -156,7 +156,7 @@ func (m *keyManager) recoveryFromPrivateKey(privateKey string) error { var keyBytesArray [32]byte copy(keyBytesArray[:], priBytes[:32]) priKey := secp256k1.PrivKeySecp256k1(keyBytesArray) - addr := types.AccAddress(priKey.PubKey().Address()) + addr := ctypes.AccAddress(priKey.PubKey().Address()) m.addr = addr m.privKey = priKey return nil @@ -172,6 +172,7 @@ func (m *keyManager) Sign(msg tx.StdSignMsg) ([]byte, error) { if err != nil { return nil, err } + fmt.Println(string(bz)) //return bz, nil return []byte(hex.EncodeToString(bz)), nil } @@ -180,7 +181,7 @@ func (m *keyManager) GetPrivKey() crypto.PrivKey { return m.privKey } -func (m *keyManager) GetAddr() types.AccAddress { +func (m *keyManager) GetAddr() ctypes.AccAddress { return m.addr } @@ -201,7 +202,7 @@ func (m *keyManager) makeSignature(msg tx.StdSignMsg) (sig tx.StdSignature, err } func generateKeyStore(privateKey crypto.PrivKey, password string) (*EncryptedKeyJSON, error) { - addr := types.AccAddress(privateKey.PubKey().Address()) + addr := ctypes.AccAddress(privateKey.PubKey().Address()) salt, err := common.GenerateRandomBytes(32) if err != nil { return nil, err diff --git a/types/msg/msg-burn.go b/types/msg/msg-burn.go index 36196142..8ab01917 100644 --- a/types/msg/msg-burn.go +++ b/types/msg/msg-burn.go @@ -3,7 +3,7 @@ package msg import ( "encoding/json" "fmt" - "github.com/binance-chain/go-sdk/types" + "github.com/binance-chain/go-sdk/common/types" ) // TokenBurnMsg def diff --git a/types/msg/msg-dexList.go b/types/msg/msg-dexList.go index f9b0f49f..0337e86a 100644 --- a/types/msg/msg-dexList.go +++ b/types/msg/msg-dexList.go @@ -3,7 +3,7 @@ package msg import ( "encoding/json" "fmt" - "github.com/binance-chain/go-sdk/types" + "github.com/binance-chain/go-sdk/common/types" ) // DexListMsg def diff --git a/types/msg/msg-freeze.go b/types/msg/msg-freeze.go index cc0ea490..af16c112 100644 --- a/types/msg/msg-freeze.go +++ b/types/msg/msg-freeze.go @@ -3,7 +3,7 @@ package msg import ( "encoding/json" "fmt" - "github.com/binance-chain/go-sdk/types" + "github.com/binance-chain/go-sdk/common/types" ) // TokenFreezeMsg def diff --git a/types/msg/msg-gov.go b/types/msg/msg-gov.go index a21333d7..1cf12c8f 100644 --- a/types/msg/msg-gov.go +++ b/types/msg/msg-gov.go @@ -6,9 +6,8 @@ import ( "time" "github.com/pkg/errors" - - "github.com/binance-chain/bnc-go-amino" - "github.com/binance-chain/go-sdk/types" + "github.com/tendermint/go-amino" + "github.com/binance-chain/go-sdk/common/types" ) // name to idetify transaction types diff --git a/types/msg/msg-issue.go b/types/msg/msg-issue.go index eff2e275..93cfd5ee 100644 --- a/types/msg/msg-issue.go +++ b/types/msg/msg-issue.go @@ -4,11 +4,11 @@ import ( "encoding/json" "errors" "fmt" + "github.com/binance-chain/go-sdk/common/types" "math" "strings" "github.com/binance-chain/go-sdk/common" - "github.com/binance-chain/go-sdk/types" ) // TokenIssueMsg def diff --git a/types/msg/msg-mint.go b/types/msg/msg-mint.go index 323fbe39..b1be1e02 100644 --- a/types/msg/msg-mint.go +++ b/types/msg/msg-mint.go @@ -3,7 +3,7 @@ package msg import ( "encoding/json" "fmt" - "github.com/binance-chain/go-sdk/types" + "github.com/binance-chain/go-sdk/common/types" ) type MintMsg struct { diff --git a/types/msg/msg-order.go b/types/msg/msg-order.go index a5f3240c..ff653804 100644 --- a/types/msg/msg-order.go +++ b/types/msg/msg-order.go @@ -4,9 +4,9 @@ import ( "encoding/json" "errors" "fmt" + "github.com/binance-chain/go-sdk/common/types" "strings" - "github.com/binance-chain/go-sdk/types" ) // Order routes diff --git a/types/msg/msg-send.go b/types/msg/msg-send.go index 979fa838..42ded943 100644 --- a/types/msg/msg-send.go +++ b/types/msg/msg-send.go @@ -3,7 +3,7 @@ package msg import ( "encoding/json" "fmt" - "github.com/binance-chain/go-sdk/types" + "github.com/binance-chain/go-sdk/common/types" ) // SendMsg - high level transaction of the coin module diff --git a/types/msg/msg.go b/types/msg/msg.go index b5935e94..efb78fef 100644 --- a/types/msg/msg.go +++ b/types/msg/msg.go @@ -2,13 +2,13 @@ package msg import ( "fmt" + "github.com/binance-chain/go-sdk/common/types" "regexp" "strings" "github.com/pkg/errors" "github.com/binance-chain/go-sdk/common" - "github.com/binance-chain/go-sdk/types" ) // constants diff --git a/types/msg/wire.go b/types/msg/wire.go index 0e407db2..d3963ce3 100644 --- a/types/msg/wire.go +++ b/types/msg/wire.go @@ -1,7 +1,7 @@ package msg import ( - "github.com/binance-chain/bnc-go-amino" + "github.com/tendermint/go-amino" "github.com/binance-chain/go-sdk/common/crypto/encoding/amino" ) diff --git a/types/tx/wire.go b/types/tx/wire.go index 88a912f7..a23548ff 100644 --- a/types/tx/wire.go +++ b/types/tx/wire.go @@ -1,7 +1,7 @@ package tx import ( - "github.com/binance-chain/bnc-go-amino" + "github.com/tendermint/go-amino" "github.com/binance-chain/go-sdk/types/msg" ) From 43a1d012b5be66ba2782b726f81bc9496a7c86c8 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 8 Apr 2019 10:37:55 +0800 Subject: [PATCH 03/14] rpc: delete useless file after dependent to tendermint --- client/query/get_node_info.go | 10 +- common/crypto/bytes.go | 62 ---------- common/crypto/crypto.go | 26 ---- common/crypto/encoding/amino/amino.go | 43 ------- common/crypto/hash.go | 11 -- common/crypto/random.go | 106 ----------------- common/crypto/secp256k1/secp256k1.go | 163 -------------------------- common/types/bank.go | 43 ++++--- keys/keys.go | 4 +- types/msg/wire.go | 3 +- types/tx/stdsign.go | 2 +- 11 files changed, 31 insertions(+), 442 deletions(-) delete mode 100644 common/crypto/bytes.go delete mode 100644 common/crypto/crypto.go delete mode 100644 common/crypto/encoding/amino/amino.go delete mode 100644 common/crypto/hash.go delete mode 100644 common/crypto/random.go delete mode 100644 common/crypto/secp256k1/secp256k1.go diff --git a/client/query/get_node_info.go b/client/query/get_node_info.go index 45bb1a95..c7fc7d29 100644 --- a/client/query/get_node_info.go +++ b/client/query/get_node_info.go @@ -4,7 +4,7 @@ import ( "encoding/json" "time" - "github.com/binance-chain/go-sdk/common/crypto" + cmn "github.com/tendermint/tendermint/libs/common" ) // Account definition @@ -24,7 +24,7 @@ type NodeInfo struct { // Channels are HexBytes so easier to read as JSON Network string `json:"network"` // network/chain ID Version string `json:"version"` // major.minor.revision - Channels crypto.HexBytes `json:"channels"` // channels this node knows about + Channels cmn.HexBytes `json:"channels"` // channels this node knows about // ASCIIText fields Moniker string `json:"moniker"` // arbitrary moniker @@ -32,13 +32,13 @@ type NodeInfo struct { } type ValidatorInfo struct { - Address crypto.HexBytes `json:"address"` + Address cmn.HexBytes `json:"address"` PubKey []uint8 `json:"pub_key"` VotingPower int64 `json:"voting_power"` } type SyncInfo struct { - LatestBlockHash crypto.HexBytes `json:"latest_block_hash"` - LatestAppHash crypto.HexBytes `json:"latest_app_hash"` + LatestBlockHash cmn.HexBytes `json:"latest_block_hash"` + LatestAppHash cmn.HexBytes `json:"latest_app_hash"` LatestBlockHeight int64 `json:"latest_block_height"` LatestBlockTime time.Time `json:"latest_block_time"` CatchingUp bool `json:"catching_up"` diff --git a/common/crypto/bytes.go b/common/crypto/bytes.go deleted file mode 100644 index 4f526820..00000000 --- a/common/crypto/bytes.go +++ /dev/null @@ -1,62 +0,0 @@ -package crypto - -import ( - "encoding/hex" - "fmt" - "strings" -) - -// The main purpose of HexBytes is to enable HEX-encoding for json/encoding. -type HexBytes []byte - -// Marshal needed for protobuf compatibility -func (bz HexBytes) Marshal() ([]byte, error) { - return bz, nil -} - -// Unmarshal needed for protobuf compatibility -func (bz *HexBytes) Unmarshal(data []byte) error { - *bz = data - return nil -} - -// This is the point of Bytes. -func (bz HexBytes) MarshalJSON() ([]byte, error) { - s := strings.ToUpper(hex.EncodeToString(bz)) - jbz := make([]byte, len(s)+2) - jbz[0] = '"' - copy(jbz[1:], []byte(s)) - jbz[len(jbz)-1] = '"' - return jbz, nil -} - -// This is the point of Bytes. -func (bz *HexBytes) UnmarshalJSON(data []byte) error { - if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' { - return fmt.Errorf("Invalid hex string: %s", data) - } - bz2, err := hex.DecodeString(string(data[1 : len(data)-1])) - if err != nil { - return err - } - *bz = bz2 - return nil -} - -// Allow it to fulfill various interfaces in light-client, etc... -func (bz HexBytes) Bytes() []byte { - return bz -} - -func (bz HexBytes) String() string { - return strings.ToUpper(hex.EncodeToString(bz)) -} - -func (bz HexBytes) Format(s fmt.State, verb rune) { - switch verb { - case 'p': - s.Write([]byte(fmt.Sprintf("%p", bz))) - default: - s.Write([]byte(fmt.Sprintf("%X", []byte(bz)))) - } -} diff --git a/common/crypto/crypto.go b/common/crypto/crypto.go deleted file mode 100644 index 3ad72e85..00000000 --- a/common/crypto/crypto.go +++ /dev/null @@ -1,26 +0,0 @@ -package crypto - -type PrivKey interface { - Bytes() []byte - Sign(msg []byte) ([]byte, error) - PubKey() PubKey - Equals(PrivKey) bool -} - -// An address is a []byte, but hex-encoded even in JSON. -// []byte leaves us the option to change the address length. -// Use an alias so Unmarshal methods (with ptr receivers) are available too. -type Address = HexBytes - -type PubKey interface { - Address() Address - Bytes() []byte - VerifyBytes(msg []byte, sig []byte) bool - Equals(PubKey) bool -} - -type Symmetric interface { - Keygen() []byte - Encrypt(plaintext []byte, secret []byte) (ciphertext []byte) - Decrypt(ciphertext []byte, secret []byte) (plaintext []byte, err error) -} diff --git a/common/crypto/encoding/amino/amino.go b/common/crypto/encoding/amino/amino.go deleted file mode 100644 index 7294f7bb..00000000 --- a/common/crypto/encoding/amino/amino.go +++ /dev/null @@ -1,43 +0,0 @@ -package cryptoAmino - -import ( - "github.com/tendermint/go-amino" - - "github.com/binance-chain/go-sdk/common/crypto" - "github.com/binance-chain/go-sdk/common/crypto/secp256k1" -) - -var cdc = amino.NewCodec() - -func init() { - // NOTE: It's important that there be no conflicts here, - // as that would change the canonical representations, - // and therefore change the address. - // TODO: Remove above note when - // https://github.com/tendermint/go-amino/issues/9 - // is resolved - RegisterAmino(cdc) -} - -// RegisterAmino registers all crypto related types in the given (amino) codec. -func RegisterAmino(cdc *amino.Codec) { - // These are all written here instead of - cdc.RegisterInterface((*crypto.PubKey)(nil), nil) - - cdc.RegisterConcrete(secp256k1.PubKeySecp256k1{}, - secp256k1.PubKeyAminoRoute, nil) - - cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) - cdc.RegisterConcrete(secp256k1.PrivKeySecp256k1{}, - secp256k1.PrivKeyAminoRoute, nil) -} - -func PrivKeyFromBytes(privKeyBytes []byte) (privKey crypto.PrivKey, err error) { - err = cdc.UnmarshalBinaryBare(privKeyBytes, &privKey) - return -} - -func PubKeyFromBytes(pubKeyBytes []byte) (pubKey crypto.PubKey, err error) { - err = cdc.UnmarshalBinaryBare(pubKeyBytes, &pubKey) - return -} diff --git a/common/crypto/hash.go b/common/crypto/hash.go deleted file mode 100644 index e1d22523..00000000 --- a/common/crypto/hash.go +++ /dev/null @@ -1,11 +0,0 @@ -package crypto - -import ( - "crypto/sha256" -) - -func Sha256(bytes []byte) []byte { - hasher := sha256.New() - hasher.Write(bytes) - return hasher.Sum(nil) -} diff --git a/common/crypto/random.go b/common/crypto/random.go deleted file mode 100644 index 579441c5..00000000 --- a/common/crypto/random.go +++ /dev/null @@ -1,106 +0,0 @@ -package crypto - -import ( - "crypto/aes" - "crypto/cipher" - crand "crypto/rand" - "crypto/sha256" - "encoding/hex" - "io" - "sync" -) - -var gRandInfo *randInfo - -func init() { - gRandInfo = &randInfo{} - gRandInfo.MixEntropy(randBytes(32)) // Init -} - -// Mix additional bytes of randomness, e.g. from hardware, user-input, etc. -// It is OK to call it multiple times. It does not diminish security. -func MixEntropy(seedBytes []byte) { - gRandInfo.MixEntropy(seedBytes) -} - -// This only uses the OS's randomness -func randBytes(numBytes int) []byte { - b := make([]byte, numBytes) - _, err := crand.Read(b) - if err != nil { - panic(err) - } - return b -} - -// This uses the OS and the Seed(s). -func CRandBytes(numBytes int) []byte { - b := make([]byte, numBytes) - _, err := gRandInfo.Read(b) - if err != nil { - panic(err) - } - return b -} - -// CRandHex returns a hex encoded string that's floor(numDigits/2) * 2 long. -// -// Note: CRandHex(24) gives 96 bits of randomness that -// are usually strong enough for most purposes. -func CRandHex(numDigits int) string { - return hex.EncodeToString(CRandBytes(numDigits / 2)) -} - -// Returns a crand.Reader mixed with user-supplied entropy -func CReader() io.Reader { - return gRandInfo -} - -//-------------------------------------------------------------------------------- - -type randInfo struct { - mtx sync.Mutex - seedBytes [32]byte - cipherAES256 cipher.Block - streamAES256 cipher.Stream - reader io.Reader -} - -// You can call this as many times as you'd like. -// XXX TODO review -func (ri *randInfo) MixEntropy(seedBytes []byte) { - ri.mtx.Lock() - defer ri.mtx.Unlock() - // Make new ri.seedBytes using passed seedBytes and current ri.seedBytes: - // ri.seedBytes = sha256( seedBytes || ri.seedBytes ) - h := sha256.New() - h.Write(seedBytes) - h.Write(ri.seedBytes[:]) - hashBytes := h.Sum(nil) - hashBytes32 := [32]byte{} - copy(hashBytes32[:], hashBytes) - ri.seedBytes = xorBytes32(ri.seedBytes, hashBytes32) - // Create new cipher.Block - var err error - ri.cipherAES256, err = aes.NewCipher(ri.seedBytes[:]) - if err != nil { - panic("Error creating AES256 cipher: " + err.Error()) - } - // Create new stream - ri.streamAES256 = cipher.NewCTR(ri.cipherAES256, randBytes(aes.BlockSize)) - // Create new reader - ri.reader = &cipher.StreamReader{S: ri.streamAES256, R: crand.Reader} -} - -func (ri *randInfo) Read(b []byte) (n int, err error) { - ri.mtx.Lock() - defer ri.mtx.Unlock() - return ri.reader.Read(b) -} - -func xorBytes32(bytesA [32]byte, bytesB [32]byte) (res [32]byte) { - for i, b := range bytesA { - res[i] = b ^ bytesB[i] - } - return res -} diff --git a/common/crypto/secp256k1/secp256k1.go b/common/crypto/secp256k1/secp256k1.go deleted file mode 100644 index 81d6275a..00000000 --- a/common/crypto/secp256k1/secp256k1.go +++ /dev/null @@ -1,163 +0,0 @@ -package secp256k1 - -import ( - "bytes" - "crypto/sha256" - "crypto/subtle" - "fmt" - "io" - - secp256k1 "github.com/tendermint/btcd/btcec" - "github.com/tendermint/go-amino" - "golang.org/x/crypto/ripemd160" - - "github.com/binance-chain/go-sdk/common/crypto" -) - -//------------------------------------- -const ( - PrivKeyAminoRoute = "tendermint/PrivKeySecp256k1" - PubKeyAminoRoute = "tendermint/PubKeySecp256k1" -) - -var cdc = amino.NewCodec() - -func init() { - cdc.RegisterInterface((*crypto.PubKey)(nil), nil) - cdc.RegisterConcrete(PubKeySecp256k1{}, - PubKeyAminoRoute, nil) - - cdc.RegisterInterface((*crypto.PrivKey)(nil), nil) - cdc.RegisterConcrete(PrivKeySecp256k1{}, - PrivKeyAminoRoute, nil) -} - -//------------------------------------- - -var _ crypto.PrivKey = PrivKeySecp256k1{} - -// PrivKeySecp256k1 implements PrivKey. -type PrivKeySecp256k1 [32]byte - -// Bytes marshalls the private key using amino encoding. -func (privKey PrivKeySecp256k1) Bytes() []byte { - return cdc.MustMarshalBinaryBare(privKey) -} - -// Sign creates an ECDSA signature on curve Secp256k1, using SHA256 on the msg. -func (privKey PrivKeySecp256k1) Sign(msg []byte) ([]byte, error) { - priv, _ := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:]) - sig, err := priv.Sign(crypto.Sha256(msg)) - if err != nil { - return nil, err - } - return sig.Serialize(), nil -} - -// PubKey performs the point-scalar multiplication from the privKey on the -// generator point to get the pubkey. -func (privKey PrivKeySecp256k1) PubKey() crypto.PubKey { - _, pubkeyObject := secp256k1.PrivKeyFromBytes(secp256k1.S256(), privKey[:]) - var pubkeyBytes PubKeySecp256k1 - copy(pubkeyBytes[:], pubkeyObject.SerializeCompressed()) - return pubkeyBytes -} - -// Equals - you probably don't need to use this. -// Runs in constant time based on length of the keys. -func (privKey PrivKeySecp256k1) Equals(other crypto.PrivKey) bool { - if otherSecp, ok := other.(PrivKeySecp256k1); ok { - return subtle.ConstantTimeCompare(privKey[:], otherSecp[:]) == 1 - } - return false -} - -// GenPrivKey generates a new ECDSA private key on curve secp256k1 private key. -// It uses OS randomness in conjunction with the current global random seed -// in tendermint/libs/common to generate the private key. -func GenPrivKey() PrivKeySecp256k1 { - return genPrivKey(crypto.CReader()) -} - -// genPrivKey generates a new secp256k1 private key using the provided reader. -func genPrivKey(rand io.Reader) PrivKeySecp256k1 { - privKeyBytes := [32]byte{} - _, err := io.ReadFull(rand, privKeyBytes[:]) - if err != nil { - panic(err) - } - // crypto.CRandBytes is guaranteed to be 32 bytes long, so it can be - // casted to PrivKeySecp256k1. - return PrivKeySecp256k1(privKeyBytes) -} - -// GenPrivKeySecp256k1 hashes the secret with SHA2, and uses -// that 32 byte output to create the private key. -// NOTE: secret should be the output of a KDF like bcrypt, -// if it's derived from user input. -func GenPrivKeySecp256k1(secret []byte) PrivKeySecp256k1 { - privKey32 := sha256.Sum256(secret) - // sha256.Sum256() is guaranteed to be 32 bytes long, so it can be - // casted to PrivKeySecp256k1. - return PrivKeySecp256k1(privKey32) -} - -//------------------------------------- - -var _ crypto.PubKey = PubKeySecp256k1{} - -// PubKeySecp256k1Size is comprised of 32 bytes for one field element -// (the x-coordinate), plus one byte for the parity of the y-coordinate. -const PubKeySecp256k1Size = 33 - -// PubKeySecp256k1 implements crypto.PubKey. -// It is the compressed form of the pubkey. The first byte depends is a 0x02 byte -// if the y-coordinate is the lexicographically largest of the two associated with -// the x-coordinate. Otherwise the first byte is a 0x03. -// This prefix is followed with the x-coordinate. -type PubKeySecp256k1 [PubKeySecp256k1Size]byte - -// Address returns a Bitcoin style addresses: RIPEMD160(SHA256(pubkey)) -func (pubKey PubKeySecp256k1) Address() crypto.Address { - hasherSHA256 := sha256.New() - hasherSHA256.Write(pubKey[:]) // does not error - sha := hasherSHA256.Sum(nil) - - hasherRIPEMD160 := ripemd160.New() - hasherRIPEMD160.Write(sha) // does not error - return crypto.Address(hasherRIPEMD160.Sum(nil)) -} - -// Bytes returns the pubkey marshalled with amino encoding. -func (pubKey PubKeySecp256k1) Bytes() []byte { - bz, err := cdc.MarshalBinaryBare(pubKey) - if err != nil { - panic(err) - } - return bz -} - -func (pubKey PubKeySecp256k1) VerifyBytes(msg []byte, sig []byte) bool { - pub, err := secp256k1.ParsePubKey(pubKey[:], secp256k1.S256()) - if err != nil { - return false - } - parsedSig, err := secp256k1.ParseSignature(sig[:], secp256k1.S256()) - if err != nil { - return false - } - // Underlying library ensures that this signature is in canonical form, to - // prevent Secp256k1 malleability from altering the sign of the s term. - return parsedSig.Verify(crypto.Sha256(msg), pub) -} - -func (pubKey PubKeySecp256k1) String() string { - return fmt.Sprintf("PubKeySecp256k1{%X}", pubKey[:]) -} - -func (pubKey PubKeySecp256k1) Equals(other crypto.PubKey) bool { - if otherSecp, ok := other.(PubKeySecp256k1); ok { - return bytes.Equal(pubKey[:], otherSecp[:]) - } - return false -} diff --git a/common/types/bank.go b/common/types/bank.go index adb2b35f..50bd62ea 100644 --- a/common/types/bank.go +++ b/common/types/bank.go @@ -1,27 +1,28 @@ package types import ( - "github.com/binance-chain/go-sdk/common/crypto" - "github.com/pkg/errors" "strings" + + "github.com/pkg/errors" + "github.com/tendermint/tendermint/crypto" ) // Token definition type Token struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - OrigSymbol string `json:"original_symbol"` - TotalSupply Fixed8 `json:"total_supply"` + Name string `json:"name"` + Symbol string `json:"symbol"` + OrigSymbol string `json:"original_symbol"` + TotalSupply Fixed8 `json:"total_supply"` Owner AccAddress `json:"owner"` - Mintable bool `json:"mintable"` + Mintable bool `json:"mintable"` } // AppAccount definition type AppAccount struct { BaseAccount `json:"base"` - Name string `json:"name"` - FrozenCoins []Coin `json:"frozen"` - LockedCoins []Coin `json:"locked"` + Name string `json:"name"` + FrozenCoins []Coin `json:"frozen"` + LockedCoins []Coin `json:"locked"` } // Coin def @@ -148,8 +149,6 @@ func (coins Coins) IsNotNegative() bool { return true } - - type Account interface { GetAddress() AccAddress SetAddress(address AccAddress) error // errors if already set. @@ -194,11 +193,11 @@ type NamedAcount interface { SetLockedCoins([]Coin) } -func (acc AppAccount) GetName() string { return acc.Name } -func (acc *AppAccount) SetName(name string) { acc.Name = name } -func (acc AppAccount) GetFrozenCoins() []Coin { return acc.FrozenCoins } +func (acc AppAccount) GetName() string { return acc.Name } +func (acc *AppAccount) SetName(name string) { acc.Name = name } +func (acc AppAccount) GetFrozenCoins() []Coin { return acc.FrozenCoins } func (acc *AppAccount) SetFrozenCoins(frozen []Coin) { acc.FrozenCoins = frozen } -func (acc AppAccount) GetLockedCoins() []Coin { return acc.LockedCoins } +func (acc AppAccount) GetLockedCoins() []Coin { return acc.LockedCoins } func (acc *AppAccount) SetLockedCoins(frozen []Coin) { acc.LockedCoins = frozen } func (acc *AppAccount) Clone() Account { @@ -229,11 +228,11 @@ func (acc *AppAccount) Clone() Account { } type BaseAccount struct { - Address AccAddress `json:"address"` - Coins Coins `json:"coins"` - PubKey crypto.PubKey `json:"public_key"` - AccountNumber int64 `json:"account_number"` - Sequence int64 `json:"sequence"` + Address AccAddress `json:"address"` + Coins Coins `json:"coins"` + PubKey crypto.PubKey `json:"public_key"` + AccountNumber int64 `json:"account_number"` + Sequence int64 `json:"sequence"` } // Implements sdk.Account. @@ -317,4 +316,4 @@ func (acc *BaseAccount) Clone() Account { } return clonedAcc -} \ No newline at end of file +} diff --git a/keys/keys.go b/keys/keys.go index fc7080a8..a181e7e3 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -14,10 +14,10 @@ import ( "golang.org/x/crypto/pbkdf2" "github.com/binance-chain/go-sdk/common" - "github.com/binance-chain/go-sdk/common/crypto" - "github.com/binance-chain/go-sdk/common/crypto/secp256k1" "github.com/binance-chain/go-sdk/common/uuid" "github.com/binance-chain/go-sdk/types/tx" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" ) const ( diff --git a/types/msg/wire.go b/types/msg/wire.go index d3963ce3..10b650e8 100644 --- a/types/msg/wire.go +++ b/types/msg/wire.go @@ -3,13 +3,14 @@ package msg import ( "github.com/tendermint/go-amino" - "github.com/binance-chain/go-sdk/common/crypto/encoding/amino" + cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) var MsgCdc = amino.NewCodec() func RegisterCodec(cdc *amino.Codec) { cryptoAmino.RegisterAmino(cdc) + cdc.RegisterInterface((*Msg)(nil), nil) cdc.RegisterConcrete(CreateOrderMsg{}, "dex/NewOrder", nil) diff --git a/types/tx/stdsign.go b/types/tx/stdsign.go index eb1e9fbc..7bf88e1d 100644 --- a/types/tx/stdsign.go +++ b/types/tx/stdsign.go @@ -2,7 +2,7 @@ package tx import ( "encoding/json" - "github.com/binance-chain/go-sdk/common/crypto" + "github.com/tendermint/tendermint/crypto" "github.com/binance-chain/go-sdk/types/msg" ) From 5a809e76083572da925186fb6e34a75fce9009bb Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Wed, 10 Apr 2019 21:10:12 +0800 Subject: [PATCH 04/14] add more interface --- client/query/get_markets.go | 13 +- client/query/query.go | 2 +- client/rpc/basic_client.go | 141 ++++++++++ client/rpc/client.go | 548 ------------------------------------ client/rpc/dex.go | 62 ---- client/rpc/dex_client.go | 193 +++++++++++++ client/rpc/ops_client.go | 14 + client/rpc/ws_client.go | 502 +++++++++++++++++++++++++++++---- common/types/address.go | 7 +- common/types/bank.go | 50 +++- common/types/fees.go | 98 +++++++ common/types/stake.go | 173 ++++++++++++ common/types/trade.go | 35 +++ common/types/wire.go | 5 + e2e/e2e_rpc_test.go | 106 ++++++- e2e/e2e_trans_test.go | 6 +- e2e/e2e_ws_test.go | 36 +-- 17 files changed, 1275 insertions(+), 716 deletions(-) create mode 100644 client/rpc/basic_client.go delete mode 100644 client/rpc/client.go delete mode 100644 client/rpc/dex.go create mode 100644 client/rpc/dex_client.go create mode 100644 client/rpc/ops_client.go create mode 100644 common/types/fees.go create mode 100644 common/types/stake.go create mode 100644 common/types/trade.go diff --git a/client/query/get_markets.go b/client/query/get_markets.go index 444c26a7..c64cccf1 100644 --- a/client/query/get_markets.go +++ b/client/query/get_markets.go @@ -2,6 +2,7 @@ package query import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/common" ) @@ -32,17 +33,9 @@ func (param *MarketsQuery) Check() error { return nil } -// SymbolPair definition -type SymbolPair struct { - TradeAsset string `json:"base_asset_symbol"` - QuoteAsset string `json:"quote_asset_symbol"` - Price string `json:"price"` - TickSize string `json:"tick_size"` - LotSize string `json:"lot_size"` -} // GetMarkets returns list of trading pairs -func (c *client) GetMarkets(query *MarketsQuery) ([]SymbolPair, error) { +func (c *client) GetMarkets(query *MarketsQuery) ([]types.TradingPair, error) { err := query.Check() if err != nil { return nil, err @@ -55,7 +48,7 @@ func (c *client) GetMarkets(query *MarketsQuery) ([]SymbolPair, error) { if err != nil { return nil, err } - var listOfPairs []SymbolPair + var listOfPairs []types.TradingPair if err := json.Unmarshal(resp, &listOfPairs); err != nil { return nil, err } diff --git a/client/query/query.go b/client/query/query.go index 4e7c92b0..6588849d 100644 --- a/client/query/query.go +++ b/client/query/query.go @@ -9,7 +9,7 @@ type QueryClient interface { GetClosedOrders(query *ClosedOrdersQuery) (*CloseOrders, error) GetDepth(query *DepthQuery) (*MarketDepth, error) GetKlines(query *KlineQuery) ([]Kline, error) - GetMarkets(query *MarketsQuery) ([]SymbolPair, error) + GetMarkets(query *MarketsQuery) ([]types.TradingPair, error) GetOrder(orderID string) (*Order, error) GetOpenOrders(query *OpenOrdersQuery) (*OpenOrders, error) GetTicker24h(query *Ticker24hQuery) ([]Ticker24h, error) diff --git a/client/rpc/basic_client.go b/client/rpc/basic_client.go new file mode 100644 index 00000000..9bd35c4e --- /dev/null +++ b/client/rpc/basic_client.go @@ -0,0 +1,141 @@ +package rpc + +import ( + "fmt" + "github.com/pkg/errors" + "time" + + ntypes "github.com/binance-chain/go-sdk/common/types" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/rpc/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/rpc/lib/client" + "github.com/tendermint/tendermint/types" +) + +const defaultTimeout = 5 * time.Second + +func NewRPCClient(nodeURI string) *HTTP { + return NewHTTP(nodeURI, "/websocket") +} + +type HTTP struct { + remote string + *WSEvents +} + +// NewHTTP takes a remote endpoint in the form tcp://: +// and the websocket path (which always seems to be "/websocket") +func NewHTTP(remote, wsEndpoint string) *HTTP { + rc := rpcclient.NewJSONRPCClient(remote) + cdc := rc.Codec() + ctypes.RegisterAmino(cdc) + ntypes.RegisterWire(cdc) + + + rc.SetCodec(cdc) + wsEvent := newWSEvents(cdc, remote, wsEndpoint) + client := &HTTP{ + remote: remote, + WSEvents: wsEvent, + } + client.Start() + return client +} + +func (c *HTTP) Status() (*ctypes.ResultStatus, error) { + return c.WSEvents.Status() +} + +func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + return c.WSEvents.ABCIInfo() +} + +func (c *HTTP) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { + return c.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) +} + +func (c *HTTP) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + return c.WSEvents.ABCIQueryWithOptions(path, data, opts) +} + +func (c *HTTP) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + return c.WSEvents.BroadcastTxCommit(tx) +} + +func (c *HTTP) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return c.WSEvents.BroadcastTx("broadcast_tx_async", tx) +} + +func (c *HTTP) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return c.WSEvents.BroadcastTx("broadcast_tx_sync", tx) +} + +func (c *HTTP) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { + return c.WSEvents.UnconfirmedTxs(limit) +} + +func (c *HTTP) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { + return c.WSEvents.NumUnconfirmedTxs() +} + +func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) { + return c.WSEvents.NetInfo() +} + +func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { + return c.WSEvents.DumpConsensusState() +} + +func (c *HTTP) ConsensusState() (*ctypes.ResultConsensusState, error) { + return c.WSEvents.ConsensusState() +} + +func (c *HTTP) Health() (*ctypes.ResultHealth, error) { + return c.WSEvents.Health() +} + +func (c *HTTP) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + return c.WSEvents.BlockchainInfo(minHeight, maxHeight) +} + +func (c *HTTP) Genesis() (*ctypes.ResultGenesis, error) { + return c.WSEvents.Genesis() +} + +func (c *HTTP) Block(height *int64) (*ctypes.ResultBlock, error) { + return c.WSEvents.Block(height) +} + +func (c *HTTP) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { + return c.WSEvents.BlockResults(height) +} + +func (c *HTTP) Commit(height *int64) (*ctypes.ResultCommit, error) { + return c.WSEvents.Commit(height) +} + +func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { + return c.WSEvents.Tx(hash, prove) +} + +func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { + return c.WSEvents.TxSearch(query, prove, page, perPage) +} + +func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { + return c.WSEvents.Validators(height) +} + +func (c *HTTP) QueryStore(key cmn.HexBytes, storeName string) ([]byte, error) { + path := fmt.Sprintf("/store/%s/%s", storeName, "key") + result, err := c.ABCIQuery(path, key) + if err != nil { + return nil, err + } + resp := result.Response + if !resp.IsOK() { + return nil, errors.Errorf(resp.Log) + } + return resp.Value, nil +} diff --git a/client/rpc/client.go b/client/rpc/client.go deleted file mode 100644 index 4c37e5fd..00000000 --- a/client/rpc/client.go +++ /dev/null @@ -1,548 +0,0 @@ -package rpc - -import ( - "context" - "fmt" - "github.com/pkg/errors" - "strings" - "sync" - "time" - - ntypes "github.com/binance-chain/go-sdk/common/types" - "github.com/tendermint/go-amino" - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/rpc/client" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/rpc/lib/client" - "github.com/tendermint/tendermint/rpc/lib/types" - "github.com/tendermint/tendermint/types" -) - -const defaultTimeout = 5 * time.Second - -func NewRPCClient(nodeURI string) *HTTP { - return NewHTTP(nodeURI, "/websocket") -} - -type HTTP struct { - remote string - *WSEvents -} - -// NewHTTP takes a remote endpoint in the form tcp://: -// and the websocket path (which always seems to be "/websocket") -func NewHTTP(remote, wsEndpoint string) *HTTP { - rc := rpcclient.NewJSONRPCClient(remote) - cdc := rc.Codec() - ctypes.RegisterAmino(cdc) - ntypes.RegisterWire(cdc) - - - rc.SetCodec(cdc) - wsEvent := newWSEvents(cdc, remote, wsEndpoint) - client := &HTTP{ - remote: remote, - WSEvents: wsEvent, - } - client.Start() - return client -} - -func (c *HTTP) Status() (*ctypes.ResultStatus, error) { - return c.WSEvents.Status() -} - -func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - return c.WSEvents.ABCIInfo() -} - -func (c *HTTP) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { - return c.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) -} - -func (c *HTTP) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { - return c.WSEvents.ABCIQueryWithOptions(path, data, opts) -} - -func (c *HTTP) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - return c.WSEvents.BroadcastTxCommit(tx) -} - -func (c *HTTP) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return c.WSEvents.BroadcastTx("broadcast_tx_async", tx) -} - -func (c *HTTP) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return c.WSEvents.BroadcastTx("broadcast_tx_sync", tx) -} - -func (c *HTTP) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { - return c.WSEvents.UnconfirmedTxs(limit) -} - -func (c *HTTP) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { - return c.WSEvents.NumUnconfirmedTxs() -} - -func (c *HTTP) NetInfo() (*ctypes.ResultNetInfo, error) { - return c.WSEvents.NetInfo() -} - -func (c *HTTP) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - return c.WSEvents.DumpConsensusState() -} - -func (c *HTTP) ConsensusState() (*ctypes.ResultConsensusState, error) { - return c.WSEvents.ConsensusState() -} - -func (c *HTTP) Health() (*ctypes.ResultHealth, error) { - return c.WSEvents.Health() -} - -func (c *HTTP) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { - return c.WSEvents.BlockchainInfo(minHeight, maxHeight) -} - -func (c *HTTP) Genesis() (*ctypes.ResultGenesis, error) { - return c.WSEvents.Genesis() -} - -func (c *HTTP) Block(height *int64) (*ctypes.ResultBlock, error) { - return c.WSEvents.Block(height) -} - -func (c *HTTP) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { - return c.WSEvents.BlockResults(height) -} - -func (c *HTTP) Commit(height *int64) (*ctypes.ResultCommit, error) { - return c.WSEvents.Commit(height) -} - -func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { - return c.WSEvents.Tx(hash, prove) -} - -func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { - return c.WSEvents.TxSearch(query, prove, page, perPage) -} - -func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { - return c.WSEvents.Validators(height) -} - -func (c *HTTP) QueryStore(key cmn.HexBytes, storeName string) ([]byte, error) { - path := fmt.Sprintf("/store/%s/%s", storeName, "key") - result, err := c.ABCIQuery(path, key) - if err != nil { - return nil, err - } - resp := result.Response - if !resp.IsOK() { - return nil, errors.Errorf(resp.Log) - } - return resp.Value, nil -} - -/** websocket event stuff here... **/ - -type WSEvents struct { - cmn.BaseService - cdc *amino.Codec - remote string - endpoint string - ws *WSClient - - mtx sync.RWMutex - // query -> chan - - subscriptionsQuitMap map[string]chan struct{} - subscriptionsIdMap map[string]rpctypes.JSONRPCStringID - subscriptionSet map[rpctypes.JSONRPCStringID]bool - - responseChanMap sync.Map - - timeout time.Duration -} - -func newWSEvents(cdc *amino.Codec, remote, endpoint string) *WSEvents { - wsEvents := &WSEvents{ - cdc: cdc, - endpoint: endpoint, - remote: remote, - subscriptionsQuitMap: make(map[string]chan struct{}), - subscriptionsIdMap: make(map[string]rpctypes.JSONRPCStringID), - subscriptionSet: make(map[rpctypes.JSONRPCStringID]bool), - timeout: defaultTimeout, - } - - wsEvents.BaseService = *cmn.NewBaseService(nil, "WSEvents", wsEvents) - return wsEvents -} - -// OnStart implements cmn.Service by starting WSClient and event loop. -func (w *WSEvents) OnStart() error { - w.ws = NewWSClient(w.remote, w.endpoint, OnReconnect(func() { - w.redoSubscriptionsAfter(0 * time.Second) - })) - w.ws.SetCodec(w.cdc) - - err := w.ws.Start() - if err != nil { - return err - } - - go w.eventListener() - return nil -} - -// OnStop implements cmn.Service by stopping WSClient. -func (w *WSEvents) OnStop() { - _ = w.ws.Stop() -} - -// Subscribe implements EventsClient by using WSClient to subscribe given -// subscriber to query. By default, returns a channel with cap=1. Error is -// returned if it fails to subscribe. -// Channel is never closed to prevent clients from seeing an erroneus event. -func (w *WSEvents) Subscribe(query string, - outCapacity ...int) (out chan ctypes.ResultEvent, err error) { - if _, ok := w.subscriptionsIdMap[query]; ok { - return nil, fmt.Errorf("already subscribe") - } - - id, err := w.ws.GenRequestId() - if err != nil { - return nil, err - } - outCap := 1 - if len(outCapacity) > 0 { - outCap = outCapacity[0] - } - outEvent := make(chan ctypes.ResultEvent, outCap) - outResp := make(chan rpctypes.RPCResponse, cap(outEvent)) - w.responseChanMap.Store(id, outResp) - ctx, cancel := w.NewContext() - defer cancel() - err = w.ws.Subscribe(ctx, id, query) - if err != nil { - w.responseChanMap.Delete(id) - return nil, err - } - - quit := make(chan struct{}) - w.mtx.Lock() - w.subscriptionsQuitMap[query] = quit - w.subscriptionsIdMap[query] = id - w.subscriptionSet[id] = true - w.mtx.Unlock() - go w.WaitForEventResponse(id, outResp, outEvent, quit) - - return outEvent, nil -} - -// Unsubscribe implements EventsClient by using WSClient to unsubscribe given -// subscriber from query. -func (w *WSEvents) Unsubscribe(query string) error { - ctx, cancel := w.NewContext() - defer cancel() - if err := w.ws.Unsubscribe(ctx, w.ws.EmptyRequest(), query); err != nil { - return err - } - - w.mtx.Lock() - if id, ok := w.subscriptionsIdMap[query]; ok { - delete(w.subscriptionSet, id) - w.responseChanMap.Delete(id) - } - if quit, ok := w.subscriptionsQuitMap[query]; ok { - close(quit) - } - delete(w.subscriptionsIdMap, query) - delete(w.subscriptionsQuitMap, query) - w.mtx.Unlock() - - return nil -} - -// UnsubscribeAll implements EventsClient by using WSClient to unsubscribe -// given subscriber from all the queries. -func (w *WSEvents) UnsubscribeAll() error { - ctx, cancel := w.NewContext() - defer cancel() - if err := w.ws.UnsubscribeAll(ctx, w.ws.EmptyRequest()); err != nil { - return err - } - - w.mtx.Lock() - for _, id := range w.subscriptionsIdMap { - w.responseChanMap.Delete(id) - } - for _, quit := range w.subscriptionsQuitMap { - close(quit) - } - w.subscriptionSet = make(map[rpctypes.JSONRPCStringID]bool) - w.subscriptionsQuitMap = make(map[string]chan struct{}) - w.subscriptionsIdMap = make(map[string]rpctypes.JSONRPCStringID) - w.mtx.Unlock() - - return nil -} - -func (w *WSEvents) WaitForEventResponse(requestId interface{}, in chan rpctypes.RPCResponse, eventOut chan ctypes.ResultEvent, quit chan struct{}) { - - for { - select { - case <-quit: - return - case resp, ok := <-in: - if !ok { - w.Logger.Info("channel of event stream is closed", "request id", requestId) - return - } - if resp.Error != nil { - w.Logger.Error("receive error from event stream", "error", resp.Error) - continue - } - res := new(ctypes.ResultEvent) - err := w.cdc.UnmarshalJSON(resp.Result, res) - if err != nil { - w.Logger.Debug("receive unexpected data from event stream", "result", resp.Result) - continue - } - eventOut <- *res - } - } -} - -func (w *WSEvents) WaitForResponse(ctx context.Context, outChan chan rpctypes.RPCResponse, result interface{}) error { - select { - case resp, ok := <-outChan: - if !ok { - return fmt.Errorf("response channel is closed") - } - if resp.Error != nil { - return resp.Error - } - return w.cdc.UnmarshalJSON(resp.Result, result) - case <-ctx.Done(): - return ctx.Err() - } -} - -func (w *WSEvents) SimpleCall(doRpc func(ctx context.Context, id rpctypes.JSONRPCStringID) error, proto interface{}) error { - id, err := w.ws.GenRequestId() - if err != nil { - return err - } - outChan := make(chan rpctypes.RPCResponse, 1) - w.responseChanMap.Store(id, outChan) - defer close(outChan) - defer w.responseChanMap.Delete(id) - ctx, cancel := w.NewContext() - defer cancel() - if err = doRpc(ctx, id); err != nil { - return err - } - return w.WaitForResponse(ctx, outChan, proto) -} - -func (w *WSEvents) Status() (*ctypes.ResultStatus, error) { - status := new(ctypes.ResultStatus) - err := w.SimpleCall(w.ws.Status, status) - return status, err -} - -func (w *WSEvents) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - info := new(ctypes.ResultABCIInfo) - err := w.SimpleCall(w.ws.ABCIInfo, info) - return info, err -} - -func (w *WSEvents) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { - abciQuery := new(ctypes.ResultABCIQuery) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.ABCIQueryWithOptions(ctx, id, path, data, opts) - }, abciQuery) - return abciQuery, err -} - -func (w *WSEvents) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - txCommit := new(ctypes.ResultBroadcastTxCommit) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.BroadcastTxCommit(ctx, id, tx) - }, txCommit) - return txCommit, err -} - -func (w *WSEvents) BroadcastTx(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - txRes := new(ctypes.ResultBroadcastTx) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.BroadcastTx(ctx, id, route, tx) - }, txRes) - return txRes, err -} - -func (w *WSEvents) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { - unConfirmTxs := new(ctypes.ResultUnconfirmedTxs) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.UnconfirmedTxs(ctx, id, limit) - }, unConfirmTxs) - return unConfirmTxs, err -} - -func (w *WSEvents) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { - numUnConfirmTxs := new(ctypes.ResultUnconfirmedTxs) - err := w.SimpleCall(w.ws.NumUnconfirmedTxs, numUnConfirmTxs) - return numUnConfirmTxs, err -} - -func (w *WSEvents) NetInfo() (*ctypes.ResultNetInfo, error) { - netInfo := new(ctypes.ResultNetInfo) - err := w.SimpleCall(w.ws.NetInfo, netInfo) - return netInfo, err -} - -func (w *WSEvents) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { - consensusState := new(ctypes.ResultDumpConsensusState) - err := w.SimpleCall(w.ws.DumpConsensusState, consensusState) - return consensusState, err -} - -func (w *WSEvents) ConsensusState() (*ctypes.ResultConsensusState, error) { - consensusState := new(ctypes.ResultConsensusState) - err := w.SimpleCall(w.ws.ConsensusState, consensusState) - return consensusState, err -} - -func (w *WSEvents) Health() (*ctypes.ResultHealth, error) { - health := new(ctypes.ResultHealth) - err := w.SimpleCall(w.ws.Health, health) - return health, err -} - -func (w *WSEvents) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { - - blocksInfo := new(ctypes.ResultBlockchainInfo) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.BlockchainInfo(ctx, id, minHeight, maxHeight) - }, blocksInfo) - return blocksInfo, err -} - -func (w *WSEvents) Genesis() (*ctypes.ResultGenesis, error) { - - genesis := new(ctypes.ResultGenesis) - err := w.SimpleCall(w.ws.Genesis, genesis) - return genesis, err -} - -func (w *WSEvents) Block(height *int64) (*ctypes.ResultBlock, error) { - block := new(ctypes.ResultBlock) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.Block(ctx, id, height) - }, block) - return block, err -} - -func (w *WSEvents) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { - - block := new(ctypes.ResultBlockResults) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.BlockResults(ctx, id, height) - }, block) - return block, err -} - -func (w *WSEvents) Commit(height *int64) (*ctypes.ResultCommit, error) { - commit := new(ctypes.ResultCommit) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.Commit(ctx, id, height) - }, commit) - return commit, err -} - -func (w *WSEvents) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { - - tx := new(ctypes.ResultTx) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.Tx(ctx, id, hash, prove) - }, tx) - return tx, err -} - -func (w *WSEvents) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { - - txs := new(ctypes.ResultTxSearch) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.TxSearch(ctx, id, query, prove, page, perPage) - }, txs) - return txs, err -} - -func (w *WSEvents) Validators(height *int64) (*ctypes.ResultValidators, error) { - validators := new(ctypes.ResultValidators) - err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { - return w.ws.Validators(ctx, id, height) - }, validators) - return validators, err -} - -func (w *WSEvents) SetTimeOut(timeout time.Duration) { - w.timeout = timeout -} - -func (w *WSEvents) NewContext() (context.Context, context.CancelFunc) { - return context.WithTimeout(context.Background(), w.timeout) -} - -// After being reconnected, it is necessary to redo subscription to server -// otherwise no data will be automatically received. -func (w *WSEvents) redoSubscriptionsAfter(d time.Duration) { - time.Sleep(d) - - for q, id := range w.subscriptionsIdMap { - ctx, _ := context.WithTimeout(context.Background(), w.timeout) - err := w.ws.Subscribe(ctx, id, q) - if err != nil { - w.Logger.Error("Failed to resubscribe", "err", err) - } - } -} - -func (w *WSEvents) eventListener() { - for { - select { - case resp, ok := <-w.ws.ResponsesCh: - if !ok { - return - } - id, ok := resp.ID.(rpctypes.JSONRPCStringID) - if !ok { - w.Logger.Error("unexpected request id type") - continue - } - if exist := w.subscriptionSet[id]; exist { - // receive ack event, need ignore it - continue - } - idParts := strings.Split(string(id), "#") - realId := rpctypes.JSONRPCStringID(idParts[0]) - if out, ok := w.responseChanMap.Load(realId); ok { - outChan, ok := out.(chan rpctypes.RPCResponse) - if !ok { - w.Logger.Error("unexpected data type in responseChanMap") - continue - } - select { - case outChan <- resp: - default: - w.Logger.Error("wanted to publish response, but out channel is full", "result", resp.Result) - } - } - case <-w.Quit(): - return - } - } -} diff --git a/client/rpc/dex.go b/client/rpc/dex.go deleted file mode 100644 index ba0f68d0..00000000 --- a/client/rpc/dex.go +++ /dev/null @@ -1,62 +0,0 @@ -package rpc - -import ( - "fmt" - "github.com/binance-chain/go-sdk/common/types" -) -const ( - MainStoreName = "main" - AccountStoreName = "acc" - ValAddrStoreName = "val" - TokenStoreName = "tokens" - DexStoreName = "dex" - PairStoreName = "pairs" - StakeStoreName = "stake" - ParamsStoreName = "params" - GovStoreName = "gov" - - StakeTransientStoreName = "transient_stake" - ParamsTransientStoreName = "transient_params" -) - -func (c *HTTP) ListAllTokens(offset int, limit int) ([]types.Token, error) { - path:=fmt.Sprintf("tokens/list/%d/%d", offset, limit) - result, err := c.ABCIQuery(path, nil) - if err != nil { - return nil, err - } - bz:=result.Response.GetValue() - tokens := make([]types.Token, 0) - err = c.cdc.UnmarshalBinaryLengthPrefixed(bz, &tokens) - return tokens, err -} - -func (c *HTTP) GetTokenInfo(symbol string) (*types.Token, error) { - path:=fmt.Sprintf("tokens/info/%s", symbol) - result, err := c.ABCIQuery(path, nil) - if err != nil { - return nil, err - } - bz:=result.Response.GetValue() - token := new(types.Token) - err = c.cdc.UnmarshalBinaryLengthPrefixed(bz, token) - return token, err -} - -func (c *HTTP) GetAccount(addr types.AccAddress)(acc types.Account, err error) { - key:=append([]byte("account:"), addr.Bytes()...) - bz, err := c.QueryStore(key, AccountStoreName) - if err != nil { - return nil, err - } - if bz == nil { - return nil, nil - } - err = c.cdc.UnmarshalBinaryBare(bz, &acc) - if err != nil { - return nil, err - } - return acc, err -} - - diff --git a/client/rpc/dex_client.go b/client/rpc/dex_client.go new file mode 100644 index 00000000..d84d0bbc --- /dev/null +++ b/client/rpc/dex_client.go @@ -0,0 +1,193 @@ +package rpc + +import ( + "errors" + "fmt" + "github.com/binance-chain/go-sdk/common/types" + "strings" +) + +const ( + MainStoreName = "main" + AccountStoreName = "acc" + ValAddrStoreName = "val" + TokenStoreName = "tokens" + DexStoreName = "dex" + PairStoreName = "pairs" + StakeStoreName = "stake" + ParamsStoreName = "params" + GovStoreName = "gov" + ParamABCIPrefix = "param" + + StakeTransientStoreName = "transient_stake" + ParamsTransientStoreName = "transient_params" +) + +func (c *HTTP) ListAllTokens(offset int, limit int) ([]types.Token, error) { + path := fmt.Sprintf("tokens/list/%d/%d", offset, limit) + result, err := c.ABCIQuery(path, nil) + if err != nil { + return nil, err + } + bz := result.Response.GetValue() + tokens := make([]types.Token, 0) + err = c.cdc.UnmarshalBinaryLengthPrefixed(bz, &tokens) + return tokens, err +} + +func (c *HTTP) GetTokenInfo(symbol string) (*types.Token, error) { + path := fmt.Sprintf("tokens/info/%s", symbol) + result, err := c.ABCIQuery(path, nil) + if err != nil { + return nil, err + } + bz := result.Response.GetValue() + token := new(types.Token) + err = c.cdc.UnmarshalBinaryLengthPrefixed(bz, token) + return token, err +} + +func (c *HTTP) GetAccount(addr types.AccAddress) (acc types.Account, err error) { + key := append([]byte("account:"), addr.Bytes()...) + bz, err := c.QueryStore(key, AccountStoreName) + if err != nil { + return nil, err + } + if bz == nil { + return nil, nil + } + err = c.cdc.UnmarshalBinaryBare(bz, &acc) + if err != nil { + return nil, err + } + return acc, err +} + +func (c *HTTP) GetBalances(addr types.AccAddress) ([]types.TokenBalance, error) { + account, err := c.GetAccount(addr) + if err != nil { + return nil, err + } + coins := account.GetCoins() + var denoms map[string]bool + denoms = map[string]bool{} + for _, coin := range coins { + denom := coin.Denom + exists := c.existsCC(denom) + // TODO: we probably actually want to show zero balances. + // if exists && !sdk.Int.IsZero(coins.AmountOf(denom)) { + if exists { + denoms[denom] = true + } + } + + symbs := make([]string, 0, len(denoms)) + bals := make([]types.TokenBalance, 0, len(denoms)) + for symb := range denoms { + symbs = append(symbs, symb) + // count locked and frozen coins + var locked, frozen int64 + nacc := account.(types.NamedAccount) + if nacc != nil { + locked = nacc.GetLockedCoins().AmountOf(symb) + frozen = nacc.GetFrozenCoins().AmountOf(symb) + } + bals = append(bals, types.TokenBalance{ + Symbol: symb, + Free: types.Fixed8(coins.AmountOf(symb)), + Locked: types.Fixed8(locked), + Frozen: types.Fixed8(frozen), + }) + } + return bals, nil +} + +func (c *HTTP) GetBalance(addr types.AccAddress, symbol string) (*types.TokenBalance, error) { + exist := c.existsCC(symbol) + if !exist { + return nil, errors.New("symbol not found") + } + acc, err := c.GetAccount(addr) + if err != nil { + return nil, err + } + var locked, frozen int64 + nacc := acc.(types.NamedAccount) + if nacc != nil { + locked = nacc.GetLockedCoins().AmountOf(symbol) + frozen = nacc.GetFrozenCoins().AmountOf(symbol) + } + return &types.TokenBalance{ + Symbol: symbol, + Free: types.Fixed8(nacc.GetCoins().AmountOf(symbol)), + Locked: types.Fixed8(locked), + Frozen: types.Fixed8(frozen), + }, nil +} + +func (c *HTTP) GetFee() ([]types.FeeParam, error) { + rawFee, err := c.ABCIQuery(fmt.Sprintf("%s/fees", ParamABCIPrefix), nil) + if err != nil { + return nil, err + } + var fees []types.FeeParam + err = c.cdc.UnmarshalBinaryLengthPrefixed(rawFee.Response.GetValue(), &fees) + return fees, err +} + +func (c *HTTP) GetOpenOrders(addr types.AccAddress, pair string) ([]types.OpenOrder, error) { + rawOrders, err := c.ABCIQuery(fmt.Sprintf("dex/openorders/%s/%s", pair, addr), nil) + if err != nil { + return nil, err + } + bz := rawOrders.Response.GetValue() + openOrders := make([]types.OpenOrder, 0) + if bz == nil { + return openOrders, nil + } + if err := c.cdc.UnmarshalBinaryLengthPrefixed(bz, &openOrders); err != nil { + return nil, err + } else { + return openOrders, nil + } +} + +func (c *HTTP) GetTradingPairs(offset int, limit int) ([]types.TradingPair, error) { + rawTradePairs, err := c.ABCIQuery(fmt.Sprintf("dex/pairs/%d/%d", offset, limit), nil) + if err != nil { + return nil, err + } + pairs := make([]types.TradingPair, 0) + if rawTradePairs.Response.GetValue() == nil { + return pairs, nil + } + err = c.cdc.UnmarshalBinaryLengthPrefixed(rawTradePairs.Response.GetValue(), &pairs) + return pairs, err +} + +func (c *HTTP) GetDepth(tradePair string) (*types.OrderBook, error) { + rawDepth, err := c.ABCIQuery(fmt.Sprintf("dex/orderbook/%s", tradePair), nil) + if err != nil { + return nil, err + } + var ob types.OrderBook + err = c.cdc.UnmarshalBinaryLengthPrefixed(rawDepth.Response.GetValue(), &ob) + if err != nil { + return nil, err + } + return &ob, nil +} + + + +func (c *HTTP) existsCC(symbol string) bool { + key := []byte(strings.ToUpper(symbol)) + bz, err := c.QueryStore(key, TokenStoreName) + if err != nil { + return false + } + if bz != nil { + return true + } + return false +} diff --git a/client/rpc/ops_client.go b/client/rpc/ops_client.go new file mode 100644 index 00000000..ebfca161 --- /dev/null +++ b/client/rpc/ops_client.go @@ -0,0 +1,14 @@ +package rpc + +import "github.com/binance-chain/go-sdk/common/types" + +func (c *HTTP) GetStakeValidators() ([]types.Validator, error) { + rawVal,err:= c.ABCIQuery("custom/stake/validators",nil) + if err!=nil{ + return nil,err + } + var validators []types.Validator + err = c.cdc.UnmarshalJSON(rawVal.Response.GetValue(), &validators) + return validators,err + +} diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index 88e55640..427edfed 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -3,24 +3,25 @@ package rpc import ( "context" "encoding/json" - "fmt" "net" "net/http" - "strings" - "sync" - "time" "github.com/gorilla/websocket" - "github.com/pkg/errors" "github.com/rcrowley/go-metrics" + "fmt" + "github.com/binance-chain/go-sdk/common/uuid" + "github.com/pkg/errors" + "strings" + "sync" + "time" + "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/rpc/client" - types "github.com/tendermint/tendermint/rpc/lib/types" - ctypes "github.com/tendermint/tendermint/types" - - "github.com/binance-chain/go-sdk/common/uuid" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/rpc/lib/types" + "github.com/tendermint/tendermint/types" ) const ( @@ -37,6 +38,407 @@ const ( protoTCP = "tcp" ) +/** websocket event stuff here... **/ +type WSEvents struct { + cmn.BaseService + cdc *amino.Codec + remote string + endpoint string + ws *WSClient + + mtx sync.RWMutex + // query -> chan + + subscriptionsQuitMap map[string]chan struct{} + subscriptionsIdMap map[string]rpctypes.JSONRPCStringID + subscriptionSet map[rpctypes.JSONRPCStringID]bool + + responseChanMap sync.Map + + timeout time.Duration +} + +func newWSEvents(cdc *amino.Codec, remote, endpoint string) *WSEvents { + wsEvents := &WSEvents{ + cdc: cdc, + endpoint: endpoint, + remote: remote, + subscriptionsQuitMap: make(map[string]chan struct{}), + subscriptionsIdMap: make(map[string]rpctypes.JSONRPCStringID), + subscriptionSet: make(map[rpctypes.JSONRPCStringID]bool), + timeout: defaultTimeout, + } + + wsEvents.BaseService = *cmn.NewBaseService(nil, "WSEvents", wsEvents) + return wsEvents +} + +// OnStart implements cmn.Service by starting WSClient and event loop. +func (w *WSEvents) OnStart() error { + w.ws = NewWSClient(w.remote, w.endpoint, OnReconnect(func() { + w.redoSubscriptionsAfter(0 * time.Second) + })) + w.ws.SetCodec(w.cdc) + + err := w.ws.Start() + if err != nil { + return err + } + + go w.eventListener() + return nil +} + +// OnStop implements cmn.Service by stopping WSClient. +func (w *WSEvents) OnStop() { + _ = w.ws.Stop() +} + +// Subscribe implements EventsClient by using WSClient to subscribe given +// subscriber to query. By default, returns a channel with cap=1. Error is +// returned if it fails to subscribe. +// Channel is never closed to prevent clients from seeing an erroneus event. +func (w *WSEvents) Subscribe(query string, + outCapacity ...int) (out chan ctypes.ResultEvent, err error) { + if _, ok := w.subscriptionsIdMap[query]; ok { + return nil, errors.New("already subscribe") + } + + id, err := w.ws.GenRequestId() + if err != nil { + return nil, err + } + outCap := 1 + if len(outCapacity) > 0 { + outCap = outCapacity[0] + } + outEvent := make(chan ctypes.ResultEvent, outCap) + outResp := make(chan rpctypes.RPCResponse, cap(outEvent)) + w.responseChanMap.Store(id, outResp) + ctx, cancel := w.NewContext() + defer cancel() + err = w.ws.Subscribe(ctx, id, query) + if err != nil { + w.responseChanMap.Delete(id) + return nil, err + } + + quit := make(chan struct{}) + w.mtx.Lock() + w.subscriptionsQuitMap[query] = quit + w.subscriptionsIdMap[query] = id + w.subscriptionSet[id] = true + w.mtx.Unlock() + go w.WaitForEventResponse(id, outResp, outEvent, quit) + + return outEvent, nil +} + +// Unsubscribe implements EventsClient by using WSClient to unsubscribe given +// subscriber from query. +func (w *WSEvents) Unsubscribe(query string) error { + ctx, cancel := w.NewContext() + defer cancel() + if err := w.ws.Unsubscribe(ctx, w.ws.EmptyRequest(), query); err != nil { + return err + } + + w.mtx.Lock() + if id, ok := w.subscriptionsIdMap[query]; ok { + delete(w.subscriptionSet, id) + w.responseChanMap.Delete(id) + } + if quit, ok := w.subscriptionsQuitMap[query]; ok { + close(quit) + } + delete(w.subscriptionsIdMap, query) + delete(w.subscriptionsQuitMap, query) + w.mtx.Unlock() + + return nil +} + +// UnsubscribeAll implements EventsClient by using WSClient to unsubscribe +// given subscriber from all the queries. +func (w *WSEvents) UnsubscribeAll() error { + ctx, cancel := w.NewContext() + defer cancel() + if err := w.ws.UnsubscribeAll(ctx, w.ws.EmptyRequest()); err != nil { + return err + } + + w.mtx.Lock() + for _, id := range w.subscriptionsIdMap { + w.responseChanMap.Delete(id) + } + for _, quit := range w.subscriptionsQuitMap { + close(quit) + } + w.subscriptionSet = make(map[rpctypes.JSONRPCStringID]bool) + w.subscriptionsQuitMap = make(map[string]chan struct{}) + w.subscriptionsIdMap = make(map[string]rpctypes.JSONRPCStringID) + w.mtx.Unlock() + + return nil +} + +func (w *WSEvents) WaitForEventResponse(requestId interface{}, in chan rpctypes.RPCResponse, eventOut chan ctypes.ResultEvent, quit chan struct{}) { + + for { + select { + case <-quit: + return + case resp, ok := <-in: + if !ok { + w.Logger.Info("channel of event stream is closed", "request id", requestId) + return + } + if resp.Error != nil { + w.Logger.Error("receive error from event stream", "error", resp.Error) + continue + } + res := new(ctypes.ResultEvent) + err := w.cdc.UnmarshalJSON(resp.Result, res) + if err != nil { + w.Logger.Debug("receive unexpected data from event stream", "result", resp.Result) + continue + } + eventOut <- *res + } + } +} + +func (w *WSEvents) WaitForResponse(ctx context.Context, outChan chan rpctypes.RPCResponse, result interface{}) error { + select { + case resp, ok := <-outChan: + if !ok { + return errors.New("response channel is closed") + } + if resp.Error != nil { + return resp.Error + } + return w.cdc.UnmarshalJSON(resp.Result, result) + case <-ctx.Done(): + return ctx.Err() + } +} + +func (w *WSEvents) SimpleCall(doRpc func(ctx context.Context, id rpctypes.JSONRPCStringID) error, proto interface{}) error { + id, err := w.ws.GenRequestId() + if err != nil { + return err + } + outChan := make(chan rpctypes.RPCResponse, 1) + w.responseChanMap.Store(id, outChan) + defer close(outChan) + defer w.responseChanMap.Delete(id) + ctx, cancel := w.NewContext() + defer cancel() + if err = doRpc(ctx, id); err != nil { + return err + } + return w.WaitForResponse(ctx, outChan, proto) +} + +func (w *WSEvents) Status() (*ctypes.ResultStatus, error) { + status := new(ctypes.ResultStatus) + err := w.SimpleCall(w.ws.Status, status) + return status, err +} + +func (w *WSEvents) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + info := new(ctypes.ResultABCIInfo) + err := w.SimpleCall(w.ws.ABCIInfo, info) + return info, err +} + +func (w *WSEvents) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + abciQuery := new(ctypes.ResultABCIQuery) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.ABCIQueryWithOptions(ctx, id, path, data, opts) + }, abciQuery) + return abciQuery, err +} + +func (w *WSEvents) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + txCommit := new(ctypes.ResultBroadcastTxCommit) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BroadcastTxCommit(ctx, id, tx) + }, txCommit) + return txCommit, err +} + +func (w *WSEvents) BroadcastTx(route string, tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + txRes := new(ctypes.ResultBroadcastTx) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BroadcastTx(ctx, id, route, tx) + }, txRes) + return txRes, err +} + +func (w *WSEvents) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { + unConfirmTxs := new(ctypes.ResultUnconfirmedTxs) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.UnconfirmedTxs(ctx, id, limit) + }, unConfirmTxs) + return unConfirmTxs, err +} + +func (w *WSEvents) NumUnconfirmedTxs() (*ctypes.ResultUnconfirmedTxs, error) { + numUnConfirmTxs := new(ctypes.ResultUnconfirmedTxs) + err := w.SimpleCall(w.ws.NumUnconfirmedTxs, numUnConfirmTxs) + return numUnConfirmTxs, err +} + +func (w *WSEvents) NetInfo() (*ctypes.ResultNetInfo, error) { + netInfo := new(ctypes.ResultNetInfo) + err := w.SimpleCall(w.ws.NetInfo, netInfo) + return netInfo, err +} + +func (w *WSEvents) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { + consensusState := new(ctypes.ResultDumpConsensusState) + err := w.SimpleCall(w.ws.DumpConsensusState, consensusState) + return consensusState, err +} + +func (w *WSEvents) ConsensusState() (*ctypes.ResultConsensusState, error) { + consensusState := new(ctypes.ResultConsensusState) + err := w.SimpleCall(w.ws.ConsensusState, consensusState) + return consensusState, err +} + +func (w *WSEvents) Health() (*ctypes.ResultHealth, error) { + health := new(ctypes.ResultHealth) + err := w.SimpleCall(w.ws.Health, health) + return health, err +} + +func (w *WSEvents) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + + blocksInfo := new(ctypes.ResultBlockchainInfo) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BlockchainInfo(ctx, id, minHeight, maxHeight) + }, blocksInfo) + return blocksInfo, err +} + +func (w *WSEvents) Genesis() (*ctypes.ResultGenesis, error) { + + genesis := new(ctypes.ResultGenesis) + err := w.SimpleCall(w.ws.Genesis, genesis) + return genesis, err +} + +func (w *WSEvents) Block(height *int64) (*ctypes.ResultBlock, error) { + block := new(ctypes.ResultBlock) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Block(ctx, id, height) + }, block) + return block, err +} + +func (w *WSEvents) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { + + block := new(ctypes.ResultBlockResults) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.BlockResults(ctx, id, height) + }, block) + return block, err +} + +func (w *WSEvents) Commit(height *int64) (*ctypes.ResultCommit, error) { + commit := new(ctypes.ResultCommit) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Commit(ctx, id, height) + }, commit) + return commit, err +} + +func (w *WSEvents) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { + + tx := new(ctypes.ResultTx) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Tx(ctx, id, hash, prove) + }, tx) + return tx, err +} + +func (w *WSEvents) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { + + txs := new(ctypes.ResultTxSearch) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.TxSearch(ctx, id, query, prove, page, perPage) + }, txs) + return txs, err +} + +func (w *WSEvents) Validators(height *int64) (*ctypes.ResultValidators, error) { + validators := new(ctypes.ResultValidators) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.Validators(ctx, id, height) + }, validators) + return validators, err +} + +func (w *WSEvents) SetTimeOut(timeout time.Duration) { + w.timeout = timeout +} + +func (w *WSEvents) NewContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), w.timeout) +} + +// After being reconnected, it is necessary to redo subscription to server +// otherwise no data will be automatically received. +func (w *WSEvents) redoSubscriptionsAfter(d time.Duration) { + time.Sleep(d) + + for q, id := range w.subscriptionsIdMap { + ctx, _ := context.WithTimeout(context.Background(), w.timeout) + err := w.ws.Subscribe(ctx, id, q) + if err != nil { + w.Logger.Error("Failed to resubscribe", "err", err) + } + } +} + +func (w *WSEvents) eventListener() { + for { + select { + case resp, ok := <-w.ws.ResponsesCh: + if !ok { + return + } + id, ok := resp.ID.(rpctypes.JSONRPCStringID) + if !ok { + w.Logger.Error("unexpected request id type") + continue + } + if exist := w.subscriptionSet[id]; exist { + // receive ack event, need ignore it + continue + } + idParts := strings.Split(string(id), "#") + realId := rpctypes.JSONRPCStringID(idParts[0]) + if out, ok := w.responseChanMap.Load(realId); ok { + outChan, ok := out.(chan rpctypes.RPCResponse) + if !ok { + w.Logger.Error("unexpected data type in responseChanMap") + continue + } + select { + case outChan <- resp: + default: + w.Logger.Error("wanted to publish response, but out channel is full", "result", resp.Result) + } + } + case <-w.Quit(): + return + } + } +} + // WSClient is a WebSocket client. The methods of WSClient are safe for use by // multiple goroutines. type WSClient struct { @@ -54,15 +456,15 @@ type WSClient struct { PingPongLatencyTimer metrics.Timer // Single user facing channel to read RPCResponses from, closed only when the client is being stopped. - ResponsesCh chan types.RPCResponse + ResponsesCh chan rpctypes.RPCResponse // Callback, which will be called each time after successful reconnect. onReconnect func() // internal channels - send chan types.RPCRequest // user requests - reconnectAfter chan error // reconnect requests - readRoutineQuit chan struct{} // a way for readRoutine to close writeRoutine + send chan rpctypes.RPCRequest // user requests + reconnectAfter chan error // reconnect requests + readRoutineQuit chan struct{} // a way for readRoutine to close writeRoutine wg sync.WaitGroup @@ -170,9 +572,9 @@ func (c *WSClient) OnStart() error { return err } - c.ResponsesCh = make(chan types.RPCResponse) + c.ResponsesCh = make(chan rpctypes.RPCResponse) - c.send = make(chan types.RPCRequest) + c.send = make(chan rpctypes.RPCRequest) // 1 additional error may come from the read/write // goroutine depending on which failed first. c.reconnectAfter = make(chan error, 1) @@ -213,7 +615,7 @@ func (c *WSClient) IsActive() bool { // Send the given RPC request to the server. Results will be available on // ResponsesCh, errors, if any, on ErrorsCh. Will block until send succeeds or // ctx.Done is closed. -func (c *WSClient) Send(ctx context.Context, request types.RPCRequest) error { +func (c *WSClient) Send(ctx context.Context, request rpctypes.RPCRequest) error { select { case c.send <- request: c.Logger.Info("sent a request", "req", request) @@ -224,11 +626,11 @@ func (c *WSClient) Send(ctx context.Context, request types.RPCRequest) error { } // Call the given method. See Send description. -func (c *WSClient) Call(ctx context.Context, method string, id types.JSONRPCStringID, params map[string]interface{}) error { - if c.IsReconnecting(){ - return fmt.Errorf("websocket is reconnecting, can't send any request") +func (c *WSClient) Call(ctx context.Context, method string, id rpctypes.JSONRPCStringID, params map[string]interface{}) error { + if c.IsReconnecting() { + return errors.New("websocket is reconnecting, can't send any request") } - request, err := types.MapToRequest(c.cdc, id, method, params) + request, err := rpctypes.MapToRequest(c.cdc, id, method, params) if err != nil { return err } @@ -238,7 +640,7 @@ func (c *WSClient) Call(ctx context.Context, method string, id types.JSONRPCStri // CallWithArrayParams the given method with params in a form of array. See // Send description. func (c *WSClient) CallWithArrayParams(ctx context.Context, method string, params []interface{}) error { - request, err := types.ArrayToRequest(c.cdc, types.JSONRPCStringID("ws-client"), method, params) + request, err := rpctypes.ArrayToRequest(c.cdc, rpctypes.JSONRPCStringID("ws-client"), method, params) if err != nil { return err } @@ -253,16 +655,16 @@ func (c *WSClient) SetCodec(cdc *amino.Codec) { c.cdc = cdc } -func (c *WSClient) GenRequestId() (types.JSONRPCStringID, error) { +func (c *WSClient) GenRequestId() (rpctypes.JSONRPCStringID, error) { id, err := uuid.NewV4() if err != nil { return "", err } - return types.JSONRPCStringID(id.String()), nil + return rpctypes.JSONRPCStringID(id.String()), nil } -func (c *WSClient) EmptyRequest() types.JSONRPCStringID { - return types.JSONRPCStringID("") +func (c *WSClient) EmptyRequest() rpctypes.JSONRPCStringID { + return rpctypes.JSONRPCStringID("") } /////////////////////////////////////////////////////////////////////////////// @@ -297,7 +699,7 @@ func (c *WSClient) reconnect() error { }() backOffDuration := 1 * time.Second for { - // will never overflow until doomsday + // will never overflow until doomsday backOffDuration := time.Duration(attempt)*time.Second + backOffDuration if backOffDuration > defaultMaxReconnectBackOffTime { backOffDuration = defaultMaxReconnectBackOffTime @@ -318,7 +720,7 @@ func (c *WSClient) reconnect() error { attempt++ - if c.maxReconnectAttempts >=0 && attempt > c.maxReconnectAttempts { + if c.maxReconnectAttempts >= 0 && attempt > c.maxReconnectAttempts { return errors.Wrap(err, "reached maximum reconnect attempts") } } @@ -461,7 +863,7 @@ func (c *WSClient) readRoutine() { return } - var response types.RPCResponse + var response rpctypes.RPCResponse err = json.Unmarshal(data, &response) if err != nil { c.Logger.Error("failed to parse response", "err", err, "data", string(data)) @@ -483,14 +885,14 @@ func (c *WSClient) readRoutine() { // Subscribe to a query. Note the server must have a "subscribe" route // defined. -func (c *WSClient) Subscribe(ctx context.Context, id types.JSONRPCStringID, query string) error { +func (c *WSClient) Subscribe(ctx context.Context, id rpctypes.JSONRPCStringID, query string) error { params := map[string]interface{}{"query": query} return c.Call(ctx, "subscribe", id, params) } // Unsubscribe from a query. Note the server must have a "unsubscribe" route // defined. -func (c *WSClient) Unsubscribe(ctx context.Context, id types.JSONRPCStringID, query string) error { +func (c *WSClient) Unsubscribe(ctx context.Context, id rpctypes.JSONRPCStringID, query string) error { params := map[string]interface{}{"query": query} return c.Call(ctx, "unsubscribe", id, params) @@ -498,76 +900,76 @@ func (c *WSClient) Unsubscribe(ctx context.Context, id types.JSONRPCStringID, qu // UnsubscribeAll from all. Note the server must have a "unsubscribe_all" route // defined. -func (c *WSClient) UnsubscribeAll(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) UnsubscribeAll(ctx context.Context, id rpctypes.JSONRPCStringID) error { params := map[string]interface{}{} return c.Call(ctx, "unsubscribe_all", id, params) } -func (c *WSClient) Status(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) Status(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "status", id, map[string]interface{}{}) } -func (c *WSClient) ABCIInfo(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) ABCIInfo(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "abci_info", id, map[string]interface{}{}) } -func (c *WSClient) ABCIQueryWithOptions(ctx context.Context, id types.JSONRPCStringID, path string, data cmn.HexBytes, opts client.ABCIQueryOptions) error { +func (c *WSClient) ABCIQueryWithOptions(ctx context.Context, id rpctypes.JSONRPCStringID, path string, data cmn.HexBytes, opts client.ABCIQueryOptions) error { return c.Call(ctx, "abci_query", id, map[string]interface{}{"path": path, "data": data, "height": opts.Height, "prove": opts.Prove}) } -func (c *WSClient) BroadcastTxCommit(ctx context.Context, id types.JSONRPCStringID, tx ctypes.Tx) error { +func (c *WSClient) BroadcastTxCommit(ctx context.Context, id rpctypes.JSONRPCStringID, tx types.Tx) error { return c.Call(ctx, "broadcast_tx_commit", id, map[string]interface{}{"tx": tx}) } -func (c *WSClient) BroadcastTx(ctx context.Context, id types.JSONRPCStringID, route string, tx ctypes.Tx) error { +func (c *WSClient) BroadcastTx(ctx context.Context, id rpctypes.JSONRPCStringID, route string, tx types.Tx) error { return c.Call(ctx, route, id, map[string]interface{}{"tx": tx}) } -func (c *WSClient) UnconfirmedTxs(ctx context.Context, id types.JSONRPCStringID, limit int) error { +func (c *WSClient) UnconfirmedTxs(ctx context.Context, id rpctypes.JSONRPCStringID, limit int) error { return c.Call(ctx, "unconfirmed_txs", id, map[string]interface{}{"limit": limit}) } -func (c *WSClient) NumUnconfirmedTxs(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) NumUnconfirmedTxs(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "num_unconfirmed_txs", id, map[string]interface{}{}) } -func (c *WSClient) NetInfo(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) NetInfo(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "net_info", id, map[string]interface{}{}) } -func (c *WSClient) DumpConsensusState(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) DumpConsensusState(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "dump_consensus_state", id, map[string]interface{}{}) } -func (c *WSClient) ConsensusState(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) ConsensusState(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "consensus_state", id, map[string]interface{}{}) } -func (c *WSClient) Health(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) Health(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "health", id, map[string]interface{}{}) } -func (c *WSClient) BlockchainInfo(ctx context.Context, id types.JSONRPCStringID, minHeight, maxHeight int64) error { +func (c *WSClient) BlockchainInfo(ctx context.Context, id rpctypes.JSONRPCStringID, minHeight, maxHeight int64) error { return c.Call(ctx, "blockchain", id, map[string]interface{}{"minHeight": minHeight, "maxHeight": maxHeight}) } -func (c *WSClient) Genesis(ctx context.Context, id types.JSONRPCStringID) error { +func (c *WSClient) Genesis(ctx context.Context, id rpctypes.JSONRPCStringID) error { return c.Call(ctx, "genesis", id, map[string]interface{}{}) } -func (c *WSClient) Block(ctx context.Context, id types.JSONRPCStringID, height *int64) error { +func (c *WSClient) Block(ctx context.Context, id rpctypes.JSONRPCStringID, height *int64) error { return c.Call(ctx, "block", id, map[string]interface{}{"height": height}) } -func (c *WSClient) BlockResults(ctx context.Context, id types.JSONRPCStringID, height *int64) error { +func (c *WSClient) BlockResults(ctx context.Context, id rpctypes.JSONRPCStringID, height *int64) error { return c.Call(ctx, "block_results", id, map[string]interface{}{"height": height}) } -func (c *WSClient) Commit(ctx context.Context, id types.JSONRPCStringID, height *int64) error { +func (c *WSClient) Commit(ctx context.Context, id rpctypes.JSONRPCStringID, height *int64) error { return c.Call(ctx, "commit", id, map[string]interface{}{"height": height}) } -func (c *WSClient) Tx(ctx context.Context, id types.JSONRPCStringID, hash []byte, prove bool) error { +func (c *WSClient) Tx(ctx context.Context, id rpctypes.JSONRPCStringID, hash []byte, prove bool) error { params := map[string]interface{}{ "hash": hash, "prove": prove, @@ -575,7 +977,7 @@ func (c *WSClient) Tx(ctx context.Context, id types.JSONRPCStringID, hash []byte return c.Call(ctx, "tx", id, params) } -func (c *WSClient) TxSearch(ctx context.Context, id types.JSONRPCStringID, query string, prove bool, page, perPage int) error { +func (c *WSClient) TxSearch(ctx context.Context, id rpctypes.JSONRPCStringID, query string, prove bool, page, perPage int) error { params := map[string]interface{}{ "query": query, "prove": prove, @@ -585,7 +987,7 @@ func (c *WSClient) TxSearch(ctx context.Context, id types.JSONRPCStringID, query return c.Call(ctx, "tx_search", id, params) } -func (c *WSClient) Validators(ctx context.Context, id types.JSONRPCStringID, height *int64) error { +func (c *WSClient) Validators(ctx context.Context, id rpctypes.JSONRPCStringID, height *int64) error { return c.Call(ctx, "validators", id, map[string]interface{}{"height": height}) } diff --git a/common/types/address.go b/common/types/address.go index f5d20e27..00753f83 100644 --- a/common/types/address.go +++ b/common/types/address.go @@ -33,6 +33,11 @@ func (this ChainNetwork) Bech32Prefixes() string { } } +func (this ChainNetwork) Bech32ValidatorAddrPrefix() string { + return "bva" +} + + // Marshal needed for protobuf compatibility func (bz AccAddress) Marshal() ([]byte, error) { return bz, nil @@ -114,4 +119,4 @@ func (bz AccAddress) String() string { panic(err) } return bech32Addr -} +} \ No newline at end of file diff --git a/common/types/bank.go b/common/types/bank.go index 50bd62ea..f1d8cc6a 100644 --- a/common/types/bank.go +++ b/common/types/bank.go @@ -21,8 +21,8 @@ type Token struct { type AppAccount struct { BaseAccount `json:"base"` Name string `json:"name"` - FrozenCoins []Coin `json:"frozen"` - LockedCoins []Coin `json:"locked"` + FrozenCoins Coins `json:"frozen"` + LockedCoins Coins `json:"locked"` } // Coin def @@ -149,6 +149,29 @@ func (coins Coins) IsNotNegative() bool { return true } +func (coins Coins) AmountOf(denom string) int64 { + switch len(coins) { + case 0: + return 0 + case 1: + coin := coins[0] + if coin.Denom == denom { + return coin.Amount + } + return 0 + default: + midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 + coin := coins[midIdx] + if denom < coin.Denom { + return coins[:midIdx].AmountOf(denom) + } else if denom == coin.Denom { + return coin.Amount + } else { + return coins[midIdx+1:].AmountOf(denom) + } + } +} + type Account interface { GetAddress() AccAddress SetAddress(address AccAddress) error // errors if already set. @@ -185,20 +208,20 @@ type NamedAcount interface { GetName() string SetName(string) - GetFrozenCoins() []Coin - SetFrozenCoins([]Coin) + GetFrozenCoins() Coins + SetFrozenCoins(Coins) //TODO: this should merge into Coin - GetLockedCoins() []Coin - SetLockedCoins([]Coin) + GetLockedCoins() Coins + SetLockedCoins(Coins) } func (acc AppAccount) GetName() string { return acc.Name } func (acc *AppAccount) SetName(name string) { acc.Name = name } -func (acc AppAccount) GetFrozenCoins() []Coin { return acc.FrozenCoins } -func (acc *AppAccount) SetFrozenCoins(frozen []Coin) { acc.FrozenCoins = frozen } -func (acc AppAccount) GetLockedCoins() []Coin { return acc.LockedCoins } -func (acc *AppAccount) SetLockedCoins(frozen []Coin) { acc.LockedCoins = frozen } +func (acc AppAccount) GetFrozenCoins() Coins { return acc.FrozenCoins } +func (acc *AppAccount) SetFrozenCoins(frozen Coins) { acc.FrozenCoins = frozen } +func (acc AppAccount) GetLockedCoins() Coins { return acc.LockedCoins } +func (acc *AppAccount) SetLockedCoins(frozen Coins) { acc.LockedCoins = frozen } func (acc *AppAccount) Clone() Account { baseAcc := acc.BaseAccount.Clone().(*BaseAccount) @@ -317,3 +340,10 @@ func (acc *BaseAccount) Clone() Account { return clonedAcc } + +type TokenBalance struct { + Symbol string `json:"symbol"` + Free Fixed8 `json:"free"` + Locked Fixed8 `json:"locked"` + Frozen Fixed8 `json:"frozen"` +} \ No newline at end of file diff --git a/common/types/fees.go b/common/types/fees.go new file mode 100644 index 00000000..b7d2c967 --- /dev/null +++ b/common/types/fees.go @@ -0,0 +1,98 @@ +package types + +import "fmt" +const ( + OperateFeeType = "operate" + TransferFeeType = "transfer" + DexFeeType = "dex" + + FeeForProposer = FeeDistributeType(0x01) + FeeForAll = FeeDistributeType(0x02) + FeeFree = FeeDistributeType(0x03) +) + +type FeeDistributeType int8 + +type FeeParam interface { + GetParamType() string + Check() error +} +// dexFee +type DexFeeParam struct { + DexFeeFields []DexFeeField `json:"dex_fee_fields"` +} + +type DexFeeField struct { + FeeName string `json:"fee_name"` + FeeValue int64 `json:"fee_value"` +} + +func (p *DexFeeParam) GetParamType() string { + return DexFeeType +} + +func (p *DexFeeParam) isNil() bool { + for _, d := range p.DexFeeFields { + if d.FeeValue < 0 { + return true + } + } + return false +} + +func (p *DexFeeParam) Check() error { + if p.isNil() { + return fmt.Errorf("Dex fee param is less than 0 ") + } + return nil +} + +// fixedFee +type FixedFeeParams struct { + MsgType string `json:"msg_type"` + Fee int64 `json:"fee"` + FeeFor FeeDistributeType `json:"fee_for"` +} + +func (p *FixedFeeParams) GetParamType() string { + return OperateFeeType +} + +func (p *FixedFeeParams) Check() error { + if p.FeeFor != FeeForProposer && p.FeeFor != FeeForAll && p.FeeFor != FeeFree { + return fmt.Errorf("fee_for %d is invalid", p.FeeFor) + } + + if p.Fee < 0 { + return fmt.Errorf("fee(%d) should not be negative", p.Fee) + } + return nil +} + + +type TransferFeeParam struct { + FixedFeeParams `json:"fixed_fee_params"` + MultiTransferFee int64 `json:"multi_transfer_fee"` + LowerLimitAsMulti int64 `json:"lower_limit_as_multi"` +} + +func (p *TransferFeeParam) GetParamType() string { + return TransferFeeType +} + +func (p *TransferFeeParam) Check() error { + err := p.FixedFeeParams.Check() + if err != nil { + return err + } + if p.Fee <= 0 || p.MultiTransferFee <= 0 { + return fmt.Errorf("both fee(%d) and multi_transfer_fee(%d) should be positive", p.Fee, p.MultiTransferFee) + } + if p.MultiTransferFee > p.Fee { + return fmt.Errorf("multi_transfer_fee(%d) should not be bigger than fee(%d)", p.MultiTransferFee, p.Fee) + } + if p.LowerLimitAsMulti <= 1 { + return fmt.Errorf("lower_limit_as_multi should > 1") + } + return nil +} \ No newline at end of file diff --git a/common/types/stake.go b/common/types/stake.go new file mode 100644 index 00000000..1dd8c6ac --- /dev/null +++ b/common/types/stake.go @@ -0,0 +1,173 @@ +package types + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/binance-chain/go-sdk/common/bech32" + "github.com/tendermint/tendermint/crypto" + "strconv" + "time" +) + +type ValAddress []byte + +type BondStatus byte + +// nolint +const ( + Unbonded BondStatus = 0x00 + Unbonding BondStatus = 0x01 + Bonded BondStatus = 0x02 +) + +// Description - description fields for a validator +type Description struct { + Moniker string `json:"moniker"` // name + Identity string `json:"identity"` // optional identity signature (ex. UPort or Keybase) + Website string `json:"website"` // optional website link + Details string `json:"details"` // optional details +} + +type Commission struct { + Rate Dec `json:"rate"` // the commission rate charged to delegators + MaxRate Dec `json:"max_rate"` // maximum commission rate which validator can ever charge + MaxChangeRate Dec `json:"max_change_rate"` // maximum daily increase of the validator commission + UpdateTime time.Time `json:"update_time"` // the last time the commission rate was changed +} + +func (c Commission) String() string { + return fmt.Sprintf("rate: %s, maxRate: %s, maxChangeRate: %s, updateTime: %s", + c.Rate, c.MaxRate, c.MaxChangeRate, c.UpdateTime, + ) +} + + +// Validator defines the total amount of bond shares and their exchange rate to +// coins. Accumulation of interest is modelled as an in increase in the +// exchange rate, and slashing as a decrease. When coins are delegated to this +// validator, the validator is credited with a Delegation whose number of +// bond shares is based on the amount of coins delegated divided by the current +// exchange rate. Voting power can be calculated as total bonds multiplied by +// exchange rate. +type Validator struct { + FeeAddr AccAddress `json:"fee_addr"` // address for fee collection + OperatorAddr ValAddress `json:"operator_address"` // address of the validator's operator; bech encoded in JSON + ConsPubKey crypto.PubKey `json:"consensus_pubkey"` // the consensus public key of the validator; bech encoded in JSON + Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? + + Status BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded) + Tokens Dec `json:"tokens"` // delegated tokens (incl. self-delegation) + DelegatorShares Dec `json:"delegator_shares"` // total shares issued to a validator's delegators + + Description Description `json:"description"` // description terms for the validator + BondHeight int64 `json:"bond_height"` // earliest height as a bonded validator + BondIntraTxCounter int16 `json:"bond_intra_tx_counter"` // block-local tx index of validator change + + UnbondingHeight int64 `json:"unbonding_height"` // if unbonding, height at which this validator has begun unbonding + UnbondingMinTime time.Time `json:"unbonding_time"` // if unbonding, min time for the validator to complete unbonding + + Commission Commission `json:"commission"` // commission parameters +} + +type Dec struct { + int64 `json:"int"` +} + +func (d Dec) String() string { + return strconv.FormatInt(d.int64, 10) +} + +func (d Dec) MarshalText() ([]byte, error) { + return []byte(strconv.FormatInt(d.int64, 10)), nil +} + +func (d *Dec) UnmarshalText(text []byte) error { + v, err := strconv.ParseInt(string(text), 10, 64) + d.int64 = v + return err +} + +// requires a valid JSON string - strings quotes and calls UnmarshalText +func (d *Dec) UnmarshalAmino(v int64) (err error) { + d.int64 = v + return nil +} +func (d Dec) MarshalAmino() (int64, error) { + return d.int64, nil +} + +// MarshalJSON marshals the decimal +func (d Dec) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +// UnmarshalJSON defines custom decoding scheme +func (d *Dec) UnmarshalJSON(bz []byte) error { + var text string + err := json.Unmarshal(bz, &text) + if err != nil { + return err + } + // TODO: Reuse dec allocation + newDec, err := NewDecFromStr(text) + if err != nil { + return err + } + d.int64 = newDec.int64 + return nil +} + +func NewDecFromStr(str string) (d Dec, err error) { + value, parseErr := strconv.ParseInt(str, 10, 64) + if parseErr != nil { + return d, errors.New(fmt.Sprintf("bad string to integer conversion, input string: %v, error: %v", str, parseErr)) + } + return Dec{value}, nil +} + +func (va ValAddress) String() string { + bech32PrefixValAddr := Network.Bech32ValidatorAddrPrefix() + bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixValAddr, va.Bytes()) + if err != nil { + panic(err) + } + + return bech32Addr +} + +func (va ValAddress) Bytes() []byte { + return va +} +// MarshalJSON marshals to JSON using Bech32. +func (va ValAddress) MarshalJSON() ([]byte, error) { + return json.Marshal(va.String()) +} + +// UnmarshalJSON unmarshals from JSON assuming Bech32 encoding. +func (va *ValAddress) UnmarshalJSON(data []byte) error { + var s string + + err := json.Unmarshal(data, &s) + if err != nil { + return nil + } + + va2, err := ValAddressFromBech32(s) + if err != nil { + return err + } + + *va = va2 + return nil +} + +func ValAddressFromBech32(address string) (addr ValAddress, err error) { + bech32PrefixValAddr := Network.Bech32ValidatorAddrPrefix() + bz, err := GetFromBech32(address, bech32PrefixValAddr) + if err != nil { + return nil, err + } + + return ValAddress(bz), nil +} diff --git a/common/types/trade.go b/common/types/trade.go new file mode 100644 index 00000000..648bec2d --- /dev/null +++ b/common/types/trade.go @@ -0,0 +1,35 @@ +package types + +type OpenOrder struct { + Id string `json:"id"` + Symbol string `json:"symbol"` + Price Fixed8 `json:"price"` + Quantity Fixed8 `json:"quantity"` + CumQty Fixed8 `json:"cumQty"` + CreatedHeight int64 `json:"createdHeight"` + CreatedTimestamp int64 `json:"createdTimestamp"` + LastUpdatedHeight int64 `json:"lastUpdatedHeight"` + LastUpdatedTimestamp int64 `json:"lastUpdatedTimestamp"` +} + +type TradingPair struct { + BaseAssetSymbol string `json:"base_asset_symbol"` + QuoteAssetSymbol string `json:"quote_asset_symbol"` + ListPrice Fixed8 `json:"list_price"` + TickSize Fixed8 `json:"tick_size"` + LotSize Fixed8 `json:"lot_size"` +} + + +type OrderBook struct { + Height int64 + Levels []OrderBookLevel +} + +// OrderBookLevel represents a single order book level. +type OrderBookLevel struct { + BuyQty Fixed8 `json:"buyQty"` + BuyPrice Fixed8 `json:"buyPrice"` + SellQty Fixed8 `json:"sellQty"` + SellPrice Fixed8 `json:"sellPrice"` +} diff --git a/common/types/wire.go b/common/types/wire.go index 9f641502..98a8e550 100644 --- a/common/types/wire.go +++ b/common/types/wire.go @@ -8,4 +8,9 @@ func RegisterWire(cdc *amino.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterInterface((*NamedAccount)(nil), nil) cdc.RegisterConcrete(&AppAccount{}, "bnbchain/Account", nil) + + cdc.RegisterInterface((*FeeParam)(nil), nil) + cdc.RegisterConcrete(&FixedFeeParams{}, "params/FixedFeeParams", nil) + cdc.RegisterConcrete(&TransferFeeParam{}, "params/TransferFeeParams", nil) + cdc.RegisterConcrete(&DexFeeParam{}, "params/DexFeeParam", nil) } diff --git a/e2e/e2e_rpc_test.go b/e2e/e2e_rpc_test.go index 30aa37b3..affe3ee3 100644 --- a/e2e/e2e_rpc_test.go +++ b/e2e/e2e_rpc_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" ctypes "github.com/binance-chain/go-sdk/common/types" + "github.com/tendermint/tendermint/types" "math/rand" "sync" "testing" @@ -16,10 +17,12 @@ import ( ) var ( - nodeAddr = "tcp://data-seed-pre-1-s3.binance.org:80" - badAddr = "tcp://127.0.0.1:80" - testTxHash = "A9DBDB2052FEEA13B953B40F8E6D3D0B0D0C592A9A0736A99BA4A4C31A3E33C8" - testTxHeight = 6064550 + nodeAddr = "tcp://seed-pre-s3.binance.org:80" + badAddr = "tcp://127.0.0.1:80" + testTxHash = "9E9E6EA3FA13684DD260DB627144EABDB50F2C205DE733447C5E8415311670C9" + testTxHeight = 960284 + testAddress = "tbnb1l6vgk5yyxcalm06gdsg55ay4pjkfueazkvwh58" + testTradePair = "X00-243_BNB" onceClient = sync.Once{} testClientInstance *rpc.HTTP @@ -147,18 +150,17 @@ func TestTx(t *testing.T) { fmt.Println(string(bz)) } - func TestReconnection(t *testing.T) { c := defaultClient() status, err := c.Status() assert.NoError(t, err) bz, err := json.Marshal(status) fmt.Println(string(bz)) - time.Sleep(10*time.Second) + time.Sleep(10 * time.Second) status, err = c.Status() assert.Error(t, err) fmt.Println(err) - time.Sleep(10*time.Second) + time.Sleep(10 * time.Second) status, err = c.Status() assert.Error(t, err) fmt.Println(err) @@ -166,7 +168,6 @@ func TestReconnection(t *testing.T) { fmt.Println(string(bz)) } - func TestTxSearch(t *testing.T) { c := defaultClient() @@ -274,13 +275,12 @@ func TestReceiveWithRequestId(t *testing.T) { func TestListAllTokens(t *testing.T) { c := defaultClient() - tokens, err := c.ListAllTokens(1,10) + tokens, err := c.ListAllTokens(1, 10) assert.NoError(t, err) bz, err := json.Marshal(tokens) fmt.Println(string(bz)) } - func TestGetTokenInfo(t *testing.T) { c := defaultClient() token, err := c.GetTokenInfo("BNB") @@ -292,11 +292,91 @@ func TestGetTokenInfo(t *testing.T) { func TestGetAccount(t *testing.T) { ctypes.Network = ctypes.TestNetwork c := defaultClient() - acc,err:=ctypes.AccAddressFromBech32("tbnb1z7sr92ar6njy9f80r4zl5rjtgm0hsej4686asa") + acc, err := ctypes.AccAddressFromBech32(testAddress) + assert.NoError(t, err) + account, err := c.GetAccount(acc) + assert.NoError(t, err) + bz, err := json.Marshal(account) + fmt.Println(string(bz)) +} + +func TestGetBalances(t *testing.T) { + ctypes.Network = ctypes.TestNetwork + c := defaultClient() + acc, err := ctypes.AccAddressFromBech32(testAddress) + assert.NoError(t, err) + balances, err := c.GetBalances(acc) + assert.NoError(t, err) + bz, err := json.Marshal(balances) + fmt.Println(string(bz)) +} + +func TestGetBalance(t *testing.T) { + ctypes.Network = ctypes.TestNetwork + c := defaultClient() + acc, err := ctypes.AccAddressFromBech32(testAddress) + assert.NoError(t, err) + balance, err := c.GetBalance(acc, "BNB") + assert.NoError(t, err) + bz, err := json.Marshal(balance) + fmt.Println(string(bz)) +} + +func TestGetFees(t *testing.T) { + c := defaultClient() + fees, err := c.GetFee() + assert.NoError(t, err) + bz, err := json.Marshal(fees) + fmt.Println(string(bz)) +} + +func TestGetOpenOrder(t *testing.T) { + ctypes.Network = ctypes.TestNetwork + acc, err := ctypes.AccAddressFromBech32(testAddress) + assert.NoError(t, err) + c := defaultClient() + openorders, err := c.GetOpenOrders(acc, testTradePair) + assert.NoError(t, err) + bz, err := json.Marshal(openorders) + assert.NoError(t, err) + fmt.Println(string(bz)) +} + +func TestGetTradePair(t *testing.T){ + c := defaultClient() + trades, err := c.GetTradingPairs(0,10) + assert.NoError(t, err) + bz, err := json.Marshal(trades) + fmt.Println(string(bz)) +} + +func TestGetDepth(t *testing.T){ + c := defaultClient() + depth, err := c.GetDepth(testTradePair) + assert.NoError(t, err) + bz, err := json.Marshal(depth) + fmt.Println(string(bz)) +} + +func TestBroadcastTxCommit(t *testing.T){ + c := defaultClient() + txstring:="cc01f0625dee0a4c2a2c87fa0a220a14443c2367e8e2edfc93aac1700bf843ef8be69c56120a0a03424e421080a3c34712220a1487dbcff17c64291c2b3538806c72a4d3a0ef6128120a0a03424e421080a3c34712700a26eb5ae9872102942fb6ffe96f001a15931e0702dd1c10370ffb568fd962039f0c4d2d45b53e9712408454253a4cf0e8f868276dfe2caa96b4ed7f94e8abace386b3fd69c454f7aa7d3a088e482328b94d991b6e6f1449cdb34e2a90bb81d102d0dac55488b35650ec18bcd82820021a04746573742001" + txbyte,err:=hex.DecodeString(txstring) assert.NoError(t,err) - account,err:=c.GetAccount(acc) + res,err:=c.BroadcastTxCommit(types.Tx(txbyte)) assert.NoError(t,err) - bz, err := json.Marshal(account) + fmt.Println(res) +} + +func TestGetStakeValidators(t *testing.T){ + c := defaultClient() + ctypes.Network = ctypes.TestNetwork + vals,err:=c.GetStakeValidators() + assert.NoError(t,err) + bz, err := json.Marshal(vals) fmt.Println(string(bz)) } + + + diff --git a/e2e/e2e_trans_test.go b/e2e/e2e_trans_test.go index 178e0a5c..2265c50b 100644 --- a/e2e/e2e_trans_test.go +++ b/e2e/e2e_trans_test.go @@ -47,9 +47,9 @@ func TestTransProcess(t *testing.T) { markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) - tradeSymbol := markets[0].TradeAsset - if markets[0].QuoteAsset != "BNB" { - tradeSymbol = markets[0].QuoteAsset + tradeSymbol := markets[0].QuoteAssetSymbol + if markets[0].QuoteAssetSymbol != "BNB" { + tradeSymbol = markets[0].QuoteAssetSymbol } //----- Get Depth ---------- diff --git a/e2e/e2e_ws_test.go b/e2e/e2e_ws_test.go index 48337468..b2de60cc 100644 --- a/e2e/e2e_ws_test.go +++ b/e2e/e2e_ws_test.go @@ -46,9 +46,9 @@ func TestSubscribeTickerEvent(t *testing.T) { markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) - tradeSymbol := markets[0].TradeAsset - if markets[0].QuoteAsset != types.NativeSymbol { - tradeSymbol = markets[0].QuoteAsset + tradeSymbol := markets[0].BaseAssetSymbol + if markets[0].QuoteAssetSymbol != types.NativeSymbol { + tradeSymbol = markets[0].QuoteAssetSymbol } quit := make(chan struct{}) err = client.SubscribeTickerEvent(tradeSymbol, types.NativeSymbol, quit, func(event *websocket.TickerEvent) { @@ -85,9 +85,9 @@ func TestSubscribeMiniTickersEvent(t *testing.T) { markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) - tradeSymbol := markets[0].TradeAsset - if markets[0].QuoteAsset != types.NativeSymbol { - tradeSymbol = markets[0].QuoteAsset + tradeSymbol := markets[0].BaseAssetSymbol + if markets[0].QuoteAssetSymbol != types.NativeSymbol { + tradeSymbol = markets[0].QuoteAssetSymbol } quit := make(chan struct{}) err = client.SubscribeMiniTickerEvent(tradeSymbol, types.NativeSymbol, quit, func(event *websocket.MiniTickerEvent) { @@ -109,9 +109,9 @@ func TestSubscribeTradeEvent(t *testing.T) { markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) - tradeSymbol := markets[0].TradeAsset - if markets[0].QuoteAsset != types.NativeSymbol { - tradeSymbol = markets[0].QuoteAsset + tradeSymbol := markets[0].BaseAssetSymbol + if markets[0].QuoteAssetSymbol != types.NativeSymbol { + tradeSymbol = markets[0].QuoteAssetSymbol } quit := make(chan struct{}) err = client.SubscribeTradeEvent(tradeSymbol, types.NativeSymbol, quit, func(events []*websocket.TradeEvent) { @@ -181,9 +181,9 @@ func TestSubscribeKlineEvent(t *testing.T) { markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) - tradeSymbol := markets[0].TradeAsset - if markets[0].QuoteAsset != types.NativeSymbol { - tradeSymbol = markets[0].QuoteAsset + tradeSymbol := markets[0].BaseAssetSymbol + if markets[0].QuoteAssetSymbol != types.NativeSymbol { + tradeSymbol = markets[0].QuoteAssetSymbol } quit := make(chan struct{}) err = client.SubscribeKlineEvent(tradeSymbol, types.NativeSymbol, websocket.OneMinuteInterval, quit, func(event *websocket.KlineEvent) { @@ -205,9 +205,9 @@ func TestSubscribeMarketDiffEvent(t *testing.T) { markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) - tradeSymbol := markets[0].TradeAsset - if markets[0].QuoteAsset != types.NativeSymbol { - tradeSymbol = markets[0].QuoteAsset + tradeSymbol := markets[0].BaseAssetSymbol + if markets[0].QuoteAssetSymbol != types.NativeSymbol { + tradeSymbol = markets[0].QuoteAssetSymbol } quit := make(chan struct{}) err = client.SubscribeMarketDiffEvent(tradeSymbol, types.NativeSymbol, quit, func(event *websocket.MarketDeltaEvent) { @@ -229,9 +229,9 @@ func TestSubscribeMarketDepthEvent(t *testing.T) { markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) - tradeSymbol := markets[0].TradeAsset - if markets[0].QuoteAsset != types.NativeSymbol { - tradeSymbol = markets[0].QuoteAsset + tradeSymbol := markets[0].BaseAssetSymbol + if markets[0].QuoteAssetSymbol != types.NativeSymbol { + tradeSymbol = markets[0].BaseAssetSymbol } quit := make(chan struct{}) err = client.SubscribeMarketDepthEvent(tradeSymbol, types.NativeSymbol, quit, func(event *websocket.MarketDepthEvent) { From 2793a388e60374e8277e5f82e6bac76549164390 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 11 Apr 2019 17:23:15 +0800 Subject: [PATCH 05/14] fomat all code --- client/basic/basic.go | 3 +- client/query/get_markets.go | 2 +- client/query/get_node_info.go | 14 ++--- client/query/get_tokens.go | 2 +- client/rpc/basic_client.go | 11 +++- client/rpc/dex_client.go | 7 +-- client/rpc/ops_client.go | 27 ++++++-- client/rpc/ws_client.go | 59 +++++++----------- client/transaction/submit_proposal.go | 3 +- client/websocket/orders.go | 1 + client/websocket/ticker.go | 1 + client/websocket/trades.go | 1 + common/common.go | 3 +- common/types/address.go | 3 +- common/types/bank.go | 12 ++-- common/types/fees.go | 9 +-- common/types/stake.go | 23 ++++--- common/types/trade.go | 17 +++-- e2e/e2e_rpc_test.go | 90 ++++++++++++++++++++------- e2e/e2e_ws_test.go | 5 +- keys/keys.go | 1 + types/msg/msg-burn.go | 1 + types/msg/msg-dexList.go | 1 + types/msg/msg-freeze.go | 1 + types/msg/msg-gov.go | 2 + types/msg/msg-issue.go | 3 +- types/msg/msg-mint.go | 1 + types/msg/msg-order.go | 2 +- types/msg/msg-send.go | 1 + types/msg/msg.go | 3 +- types/tx/stdsign.go | 3 +- 31 files changed, 196 insertions(+), 116 deletions(-) diff --git a/client/basic/basic.go b/client/basic/basic.go index c67b5f15..2c12a864 100644 --- a/client/basic/basic.go +++ b/client/basic/basic.go @@ -3,11 +3,12 @@ package basic import ( "encoding/json" "fmt" - "gopkg.in/resty.v1" "net/http" "net/url" "time" + "gopkg.in/resty.v1" + "github.com/binance-chain/go-sdk/types" "github.com/binance-chain/go-sdk/types/tx" "github.com/gorilla/websocket" diff --git a/client/query/get_markets.go b/client/query/get_markets.go index c64cccf1..5073b99b 100644 --- a/client/query/get_markets.go +++ b/client/query/get_markets.go @@ -2,6 +2,7 @@ package query import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/common" @@ -33,7 +34,6 @@ func (param *MarketsQuery) Check() error { return nil } - // GetMarkets returns list of trading pairs func (c *client) GetMarkets(query *MarketsQuery) ([]types.TradingPair, error) { err := query.Check() diff --git a/client/query/get_node_info.go b/client/query/get_node_info.go index c7fc7d29..c813b34d 100644 --- a/client/query/get_node_info.go +++ b/client/query/get_node_info.go @@ -22,8 +22,8 @@ type NodeInfo struct { // Check compatibility. // Channels are HexBytes so easier to read as JSON - Network string `json:"network"` // network/chain ID - Version string `json:"version"` // major.minor.revision + Network string `json:"network"` // network/chain ID + Version string `json:"version"` // major.minor.revision Channels cmn.HexBytes `json:"channels"` // channels this node knows about // ASCIIText fields @@ -33,15 +33,15 @@ type NodeInfo struct { type ValidatorInfo struct { Address cmn.HexBytes `json:"address"` - PubKey []uint8 `json:"pub_key"` - VotingPower int64 `json:"voting_power"` + PubKey []uint8 `json:"pub_key"` + VotingPower int64 `json:"voting_power"` } type SyncInfo struct { LatestBlockHash cmn.HexBytes `json:"latest_block_hash"` LatestAppHash cmn.HexBytes `json:"latest_app_hash"` - LatestBlockHeight int64 `json:"latest_block_height"` - LatestBlockTime time.Time `json:"latest_block_time"` - CatchingUp bool `json:"catching_up"` + LatestBlockHeight int64 `json:"latest_block_height"` + LatestBlockTime time.Time `json:"latest_block_time"` + CatchingUp bool `json:"catching_up"` } type NodeInfoOther struct { diff --git a/client/query/get_tokens.go b/client/query/get_tokens.go index 4baed4cf..ba981643 100644 --- a/client/query/get_tokens.go +++ b/client/query/get_tokens.go @@ -2,10 +2,10 @@ package query import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" ) - // GetTokens returns list of tokens func (c *client) GetTokens() ([]types.Token, error) { qp := map[string]string{} diff --git a/client/rpc/basic_client.go b/client/rpc/basic_client.go index 9bd35c4e..ccfa07b6 100644 --- a/client/rpc/basic_client.go +++ b/client/rpc/basic_client.go @@ -2,14 +2,16 @@ package rpc import ( "fmt" - "github.com/pkg/errors" "time" + "github.com/pkg/errors" + "github.com/tendermint/tendermint/p2p" + ntypes "github.com/binance-chain/go-sdk/common/types" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/rpc/lib/client" + rpcclient "github.com/tendermint/tendermint/rpc/lib/client" "github.com/tendermint/tendermint/types" ) @@ -32,7 +34,6 @@ func NewHTTP(remote, wsEndpoint string) *HTTP { ctypes.RegisterAmino(cdc) ntypes.RegisterWire(cdc) - rc.SetCodec(cdc) wsEvent := newWSEvents(cdc, remote, wsEndpoint) client := &HTTP{ @@ -47,6 +48,10 @@ func (c *HTTP) Status() (*ctypes.ResultStatus, error) { return c.WSEvents.Status() } +func (c *HTTP) NodeInfo() (*p2p.DefaultNodeInfo, error) { + return c.WSEvents.NodeInfo() +} + func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return c.WSEvents.ABCIInfo() } diff --git a/client/rpc/dex_client.go b/client/rpc/dex_client.go index d84d0bbc..9baff7f7 100644 --- a/client/rpc/dex_client.go +++ b/client/rpc/dex_client.go @@ -3,8 +3,9 @@ package rpc import ( "errors" "fmt" - "github.com/binance-chain/go-sdk/common/types" "strings" + + "github.com/binance-chain/go-sdk/common/types" ) const ( @@ -166,7 +167,7 @@ func (c *HTTP) GetTradingPairs(offset int, limit int) ([]types.TradingPair, erro } func (c *HTTP) GetDepth(tradePair string) (*types.OrderBook, error) { - rawDepth, err := c.ABCIQuery(fmt.Sprintf("dex/orderbook/%s", tradePair), nil) + rawDepth, err := c.ABCIQuery(fmt.Sprintf("dex/orderbook/%s", tradePair), nil) if err != nil { return nil, err } @@ -178,8 +179,6 @@ func (c *HTTP) GetDepth(tradePair string) (*types.OrderBook, error) { return &ob, nil } - - func (c *HTTP) existsCC(symbol string) bool { key := []byte(strings.ToUpper(symbol)) bz, err := c.QueryStore(key, TokenStoreName) diff --git a/client/rpc/ops_client.go b/client/rpc/ops_client.go index ebfca161..25b5eeb5 100644 --- a/client/rpc/ops_client.go +++ b/client/rpc/ops_client.go @@ -3,12 +3,31 @@ package rpc import "github.com/binance-chain/go-sdk/common/types" func (c *HTTP) GetStakeValidators() ([]types.Validator, error) { - rawVal,err:= c.ABCIQuery("custom/stake/validators",nil) - if err!=nil{ - return nil,err + rawVal, err := c.ABCIQuery("custom/stake/validators", nil) + if err != nil { + return nil, err } var validators []types.Validator err = c.cdc.UnmarshalJSON(rawVal.Response.GetValue(), &validators) - return validators,err + return validators, err + +} + +func (c *HTTP) GetDelegatorUnbondingDelegations(delegatorAddr types.AccAddress) ([]types.UnbondingDelegation, error) { + param := struct { + DelegatorAddr types.AccAddress + }{delegatorAddr} + bz, err := c.cdc.MarshalJSON(param) + if err != nil { + return nil, err + } + + rawDel, err := c.ABCIQuery("custom/stake/delegatorUnbondingDelegations", bz) + if err != nil { + return nil, err + } + var unbondingDelegations []types.UnbondingDelegation + err = c.cdc.UnmarshalJSON(rawDel.Response.GetValue(), &unbondingDelegations) + return unbondingDelegations, err } diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index 427edfed..04e25483 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -6,21 +6,24 @@ import ( "net" "net/http" + "github.com/tendermint/tendermint/p2p" + "github.com/gorilla/websocket" "github.com/rcrowley/go-metrics" "fmt" - "github.com/binance-chain/go-sdk/common/uuid" - "github.com/pkg/errors" "strings" "sync" "time" + "github.com/binance-chain/go-sdk/common/uuid" + "github.com/pkg/errors" + "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/rpc/lib/types" + rpctypes "github.com/tendermint/tendermint/rpc/lib/types" "github.com/tendermint/tendermint/types" ) @@ -94,6 +97,16 @@ func (w *WSEvents) OnStop() { _ = w.ws.Stop() } +// OnStop implements cmn.Service by stopping WSClient. +func (w *WSEvents) PendingRequest() int { + size := 0 + w.responseChanMap.Range(func(key, value interface{}) bool { + size++ + return true + }) + return size +} + // Subscribe implements EventsClient by using WSClient to subscribe given // subscriber to query. By default, returns a channel with cap=1. Error is // returned if it fails to subscribe. @@ -246,6 +259,14 @@ func (w *WSEvents) Status() (*ctypes.ResultStatus, error) { return status, err } +func (w *WSEvents) NodeInfo() (*p2p.DefaultNodeInfo, error) { + status, err := w.Status() + if err != nil { + return nil, err + } + return &status.NodeInfo, nil +} + func (w *WSEvents) ABCIInfo() (*ctypes.ResultABCIInfo, error) { info := new(ctypes.ResultABCIInfo) err := w.SimpleCall(w.ws.ABCIInfo, info) @@ -519,38 +540,6 @@ func NewWSClient(remoteAddr, endpoint string, options ...func(*WSClient)) *WSCli return c } -// MaxReconnectAttempts sets the maximum number of reconnect attempts before returning an error. -// It should only be used in the constructor and is not Goroutine-safe. -func MaxReconnectAttempts(max int) func(*WSClient) { - return func(c *WSClient) { - c.maxReconnectAttempts = max - } -} - -// ReadWait sets the amount of time to wait before a websocket read times out. -// It should only be used in the constructor and is not Goroutine-safe. -func ReadWait(readWait time.Duration) func(*WSClient) { - return func(c *WSClient) { - c.readWait = readWait - } -} - -// WriteWait sets the amount of time to wait before a websocket write times out. -// It should only be used in the constructor and is not Goroutine-safe. -func WriteWait(writeWait time.Duration) func(*WSClient) { - return func(c *WSClient) { - c.writeWait = writeWait - } -} - -// PingPeriod sets the duration for sending websocket pings. -// It should only be used in the constructor - not Goroutine-safe. -func PingPeriod(pingPeriod time.Duration) func(*WSClient) { - return func(c *WSClient) { - c.pingPeriod = pingPeriod - } -} - // OnReconnect sets the callback, which will be called every time after // successful reconnect. func OnReconnect(cb func()) func(*WSClient) { diff --git a/client/transaction/submit_proposal.go b/client/transaction/submit_proposal.go index 6d6b1c05..f5df0c2a 100644 --- a/client/transaction/submit_proposal.go +++ b/client/transaction/submit_proposal.go @@ -5,11 +5,10 @@ import ( "strconv" "time" + ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/types" "github.com/binance-chain/go-sdk/types/msg" "github.com/binance-chain/go-sdk/types/tx" - ctypes "github.com/binance-chain/go-sdk/common/types" - ) type SubmitProposalResult struct { diff --git a/client/websocket/orders.go b/client/websocket/orders.go index 15716e4b..e4e679fa 100644 --- a/client/websocket/orders.go +++ b/client/websocket/orders.go @@ -2,6 +2,7 @@ package websocket import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" ) diff --git a/client/websocket/ticker.go b/client/websocket/ticker.go index 561955f6..495c2bf8 100644 --- a/client/websocket/ticker.go +++ b/client/websocket/ticker.go @@ -3,6 +3,7 @@ package websocket import ( "encoding/json" "fmt" + "github.com/binance-chain/go-sdk/common" "github.com/binance-chain/go-sdk/common/types" ) diff --git a/client/websocket/trades.go b/client/websocket/trades.go index adc2ebfc..41f28cab 100644 --- a/client/websocket/trades.go +++ b/client/websocket/trades.go @@ -3,6 +3,7 @@ package websocket import ( "encoding/json" "fmt" + "github.com/binance-chain/go-sdk/common" "github.com/binance-chain/go-sdk/common/types" ) diff --git a/common/common.go b/common/common.go index c7235902..a30e8b0f 100644 --- a/common/common.go +++ b/common/common.go @@ -43,7 +43,6 @@ func GenerateRandomBytes(n int) ([]byte, error) { return b, nil } - func IsAlphaNum(s string) bool { return isAlphaNumFunc(s) -} \ No newline at end of file +} diff --git a/common/types/address.go b/common/types/address.go index 00753f83..73ac0547 100644 --- a/common/types/address.go +++ b/common/types/address.go @@ -37,7 +37,6 @@ func (this ChainNetwork) Bech32ValidatorAddrPrefix() string { return "bva" } - // Marshal needed for protobuf compatibility func (bz AccAddress) Marshal() ([]byte, error) { return bz, nil @@ -119,4 +118,4 @@ func (bz AccAddress) String() string { panic(err) } return bech32Addr -} \ No newline at end of file +} diff --git a/common/types/bank.go b/common/types/bank.go index f1d8cc6a..41ed7fba 100644 --- a/common/types/bank.go +++ b/common/types/bank.go @@ -21,8 +21,8 @@ type Token struct { type AppAccount struct { BaseAccount `json:"base"` Name string `json:"name"` - FrozenCoins Coins `json:"frozen"` - LockedCoins Coins `json:"locked"` + FrozenCoins Coins `json:"frozen"` + LockedCoins Coins `json:"locked"` } // Coin def @@ -216,8 +216,8 @@ type NamedAcount interface { SetLockedCoins(Coins) } -func (acc AppAccount) GetName() string { return acc.Name } -func (acc *AppAccount) SetName(name string) { acc.Name = name } +func (acc AppAccount) GetName() string { return acc.Name } +func (acc *AppAccount) SetName(name string) { acc.Name = name } func (acc AppAccount) GetFrozenCoins() Coins { return acc.FrozenCoins } func (acc *AppAccount) SetFrozenCoins(frozen Coins) { acc.FrozenCoins = frozen } func (acc AppAccount) GetLockedCoins() Coins { return acc.LockedCoins } @@ -342,8 +342,8 @@ func (acc *BaseAccount) Clone() Account { } type TokenBalance struct { - Symbol string `json:"symbol"` + Symbol string `json:"symbol"` Free Fixed8 `json:"free"` Locked Fixed8 `json:"locked"` Frozen Fixed8 `json:"frozen"` -} \ No newline at end of file +} diff --git a/common/types/fees.go b/common/types/fees.go index b7d2c967..e81f791c 100644 --- a/common/types/fees.go +++ b/common/types/fees.go @@ -1,6 +1,7 @@ package types import "fmt" + const ( OperateFeeType = "operate" TransferFeeType = "transfer" @@ -17,6 +18,7 @@ type FeeParam interface { GetParamType() string Check() error } + // dexFee type DexFeeParam struct { DexFeeFields []DexFeeField `json:"dex_fee_fields"` @@ -49,8 +51,8 @@ func (p *DexFeeParam) Check() error { // fixedFee type FixedFeeParams struct { - MsgType string `json:"msg_type"` - Fee int64 `json:"fee"` + MsgType string `json:"msg_type"` + Fee int64 `json:"fee"` FeeFor FeeDistributeType `json:"fee_for"` } @@ -69,7 +71,6 @@ func (p *FixedFeeParams) Check() error { return nil } - type TransferFeeParam struct { FixedFeeParams `json:"fixed_fee_params"` MultiTransferFee int64 `json:"multi_transfer_fee"` @@ -95,4 +96,4 @@ func (p *TransferFeeParam) Check() error { return fmt.Errorf("lower_limit_as_multi should > 1") } return nil -} \ No newline at end of file +} diff --git a/common/types/stake.go b/common/types/stake.go index 1dd8c6ac..f0979aa1 100644 --- a/common/types/stake.go +++ b/common/types/stake.go @@ -4,10 +4,10 @@ import ( "encoding/json" "errors" "fmt" - "github.com/binance-chain/go-sdk/common/bech32" - "github.com/tendermint/tendermint/crypto" "strconv" "time" + + "github.com/binance-chain/go-sdk/common/bech32" ) type ValAddress []byte @@ -42,7 +42,6 @@ func (c Commission) String() string { ) } - // Validator defines the total amount of bond shares and their exchange rate to // coins. Accumulation of interest is modelled as an in increase in the // exchange rate, and slashing as a decrease. When coins are delegated to this @@ -51,10 +50,10 @@ func (c Commission) String() string { // exchange rate. Voting power can be calculated as total bonds multiplied by // exchange rate. type Validator struct { - FeeAddr AccAddress `json:"fee_addr"` // address for fee collection - OperatorAddr ValAddress `json:"operator_address"` // address of the validator's operator; bech encoded in JSON - ConsPubKey crypto.PubKey `json:"consensus_pubkey"` // the consensus public key of the validator; bech encoded in JSON - Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? + FeeAddr AccAddress `json:"fee_addr"` // address for fee collection + OperatorAddr ValAddress `json:"operator_address"` // address of the validator's operator; bech encoded in JSON + ConsPubKey string `json:"consensus_pubkey"` // the consensus public key of the validator; bech encoded in JSON + Jailed bool `json:"jailed"` // has the validator been jailed from bonded status? Status BondStatus `json:"status"` // validator status (bonded/unbonding/unbonded) Tokens Dec `json:"tokens"` // delegated tokens (incl. self-delegation) @@ -70,6 +69,15 @@ type Validator struct { Commission Commission `json:"commission"` // commission parameters } +type UnbondingDelegation struct { + DelegatorAddr AccAddress `json:"delegator_addr"` // delegator + ValidatorAddr ValAddress `json:"validator_addr"` // validator unbonding from operator addr + CreationHeight int64 `json:"creation_height"` // height which the unbonding took place + MinTime time.Time `json:"min_time"` // unix time for unbonding completion + InitialBalance Coin `json:"initial_balance"` // atoms initially scheduled to receive at completion + Balance Coin `json:"balance"` // atoms to receive at completion +} + type Dec struct { int64 `json:"int"` } @@ -139,6 +147,7 @@ func (va ValAddress) String() string { func (va ValAddress) Bytes() []byte { return va } + // MarshalJSON marshals to JSON using Bech32. func (va ValAddress) MarshalJSON() ([]byte, error) { return json.Marshal(va.String()) diff --git a/common/types/trade.go b/common/types/trade.go index 648bec2d..44c091c5 100644 --- a/common/types/trade.go +++ b/common/types/trade.go @@ -1,26 +1,25 @@ package types type OpenOrder struct { - Id string `json:"id"` - Symbol string `json:"symbol"` + Id string `json:"id"` + Symbol string `json:"symbol"` Price Fixed8 `json:"price"` Quantity Fixed8 `json:"quantity"` CumQty Fixed8 `json:"cumQty"` - CreatedHeight int64 `json:"createdHeight"` - CreatedTimestamp int64 `json:"createdTimestamp"` - LastUpdatedHeight int64 `json:"lastUpdatedHeight"` - LastUpdatedTimestamp int64 `json:"lastUpdatedTimestamp"` + CreatedHeight int64 `json:"createdHeight"` + CreatedTimestamp int64 `json:"createdTimestamp"` + LastUpdatedHeight int64 `json:"lastUpdatedHeight"` + LastUpdatedTimestamp int64 `json:"lastUpdatedTimestamp"` } type TradingPair struct { - BaseAssetSymbol string `json:"base_asset_symbol"` - QuoteAssetSymbol string `json:"quote_asset_symbol"` + BaseAssetSymbol string `json:"base_asset_symbol"` + QuoteAssetSymbol string `json:"quote_asset_symbol"` ListPrice Fixed8 `json:"list_price"` TickSize Fixed8 `json:"tick_size"` LotSize Fixed8 `json:"lot_size"` } - type OrderBook struct { Height int64 Levels []OrderBookLevel diff --git a/e2e/e2e_rpc_test.go b/e2e/e2e_rpc_test.go index affe3ee3..132b67b9 100644 --- a/e2e/e2e_rpc_test.go +++ b/e2e/e2e_rpc_test.go @@ -4,26 +4,28 @@ import ( "encoding/hex" "encoding/json" "fmt" - ctypes "github.com/binance-chain/go-sdk/common/types" - "github.com/tendermint/tendermint/types" "math/rand" "sync" "testing" "time" + ctypes "github.com/binance-chain/go-sdk/common/types" + "github.com/tendermint/tendermint/types" + "github.com/binance-chain/go-sdk/client/rpc" "github.com/stretchr/testify/assert" tmquery "github.com/tendermint/tendermint/libs/pubsub/query" ) var ( - nodeAddr = "tcp://seed-pre-s3.binance.org:80" - badAddr = "tcp://127.0.0.1:80" - testTxHash = "9E9E6EA3FA13684DD260DB627144EABDB50F2C205DE733447C5E8415311670C9" - testTxHeight = 960284 - testAddress = "tbnb1l6vgk5yyxcalm06gdsg55ay4pjkfueazkvwh58" - testTradePair = "X00-243_BNB" - + nodeAddr = "tcp://seed-pre-s3.binance.org:80" + badAddr = "tcp://127.0.0.1:80" + testTxHash = "9E9E6EA3FA13684DD260DB627144EABDB50F2C205DE733447C5E8415311670C9" + testTxHeight = 960284 + testAddress = "tbnb1l6vgk5yyxcalm06gdsg55ay4pjkfueazkvwh58" + testDelAddr = "tbnb12hlquylu78cjylk5zshxpdj6hf3t0tahwjt3ex" + testTradePair = "X00-243_BNB" + testTxStr = "xxx" onceClient = sync.Once{} testClientInstance *rpc.HTTP ) @@ -43,6 +45,14 @@ func TestRPCStatus(t *testing.T) { fmt.Println(string(bz)) } +func TestRPCNodeInfo(t *testing.T) { + c := defaultClient() + nodeInfo, err := c.NodeInfo() + assert.NoError(t, err) + bz, err := json.Marshal(nodeInfo) + fmt.Println(string(bz)) +} + func TestRPCABCIInfo(t *testing.T) { c := defaultClient() info, err := c.ABCIInfo() @@ -342,15 +352,15 @@ func TestGetOpenOrder(t *testing.T) { fmt.Println(string(bz)) } -func TestGetTradePair(t *testing.T){ +func TestGetTradePair(t *testing.T) { c := defaultClient() - trades, err := c.GetTradingPairs(0,10) + trades, err := c.GetTradingPairs(0, 10) assert.NoError(t, err) bz, err := json.Marshal(trades) fmt.Println(string(bz)) } -func TestGetDepth(t *testing.T){ +func TestGetDepth(t *testing.T) { c := defaultClient() depth, err := c.GetDepth(testTradePair) assert.NoError(t, err) @@ -358,25 +368,61 @@ func TestGetDepth(t *testing.T){ fmt.Println(string(bz)) } -func TestBroadcastTxCommit(t *testing.T){ +func TestBroadcastTxCommit(t *testing.T) { c := defaultClient() - txstring:="cc01f0625dee0a4c2a2c87fa0a220a14443c2367e8e2edfc93aac1700bf843ef8be69c56120a0a03424e421080a3c34712220a1487dbcff17c64291c2b3538806c72a4d3a0ef6128120a0a03424e421080a3c34712700a26eb5ae9872102942fb6ffe96f001a15931e0702dd1c10370ffb568fd962039f0c4d2d45b53e9712408454253a4cf0e8f868276dfe2caa96b4ed7f94e8abace386b3fd69c454f7aa7d3a088e482328b94d991b6e6f1449cdb34e2a90bb81d102d0dac55488b35650ec18bcd82820021a04746573742001" - txbyte,err:=hex.DecodeString(txstring) - assert.NoError(t,err) - res,err:=c.BroadcastTxCommit(types.Tx(txbyte)) - assert.NoError(t,err) + txbyte, err := hex.DecodeString(testTxStr) + assert.NoError(t, err) + res, err := c.BroadcastTxCommit(types.Tx(txbyte)) + assert.NoError(t, err) fmt.Println(res) } -func TestGetStakeValidators(t *testing.T){ +func TestGetStakeValidators(t *testing.T) { c := defaultClient() ctypes.Network = ctypes.TestNetwork - vals,err:=c.GetStakeValidators() - assert.NoError(t,err) + vals, err := c.GetStakeValidators() + assert.NoError(t, err) bz, err := json.Marshal(vals) fmt.Println(string(bz)) } +func TestGetDelegatorUnbondingDelegations(t *testing.T) { + c := defaultClient() + ctypes.Network = ctypes.TestNetwork + acc, err := ctypes.AccAddressFromBech32(testDelAddr) + assert.NoError(t, err) + vals, err := c.GetDelegatorUnbondingDelegations(acc) + assert.NoError(t, err) + bz, err := json.Marshal(vals) + fmt.Println(string(bz)) +} +func TestNoRequestLeakInBadNetwork(t *testing.T) { + c := rpc.NewRPCClient(badAddr) + c.SetTimeOut(1 * time.Second) + w := sync.WaitGroup{} + w.Add(100) + for i := 0; i < 100; i++ { + go func() { + c.GetFee() + w.Done() + }() + } + w.Wait() + assert.Equal(t, c.PendingRequest(), 0) +} - +func TestNoRequestLeakInGoodNetwork(t *testing.T) { + c := defaultClient() + c.SetTimeOut(1 * time.Second) + w := sync.WaitGroup{} + w.Add(100) + for i := 0; i < 100; i++ { + go func() { + c.GetFee() + w.Done() + }() + } + w.Wait() + assert.Equal(t, c.PendingRequest(), 0) +} diff --git a/e2e/e2e_ws_test.go b/e2e/e2e_ws_test.go index b2de60cc..d714a9db 100644 --- a/e2e/e2e_ws_test.go +++ b/e2e/e2e_ws_test.go @@ -3,6 +3,9 @@ package e2e import ( "encoding/json" "fmt" + "testing" + "time" + sdk "github.com/binance-chain/go-sdk/client" "github.com/binance-chain/go-sdk/client/query" "github.com/binance-chain/go-sdk/client/websocket" @@ -10,8 +13,6 @@ import ( "github.com/binance-chain/go-sdk/keys" "github.com/binance-chain/go-sdk/types" "github.com/stretchr/testify/assert" - "testing" - "time" ) func NewClient(t *testing.T) sdk.DexClient { diff --git a/keys/keys.go b/keys/keys.go index a181e7e3..9696ee89 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "encoding/json" "fmt" + ctypes "github.com/binance-chain/go-sdk/common/types" "io/ioutil" diff --git a/types/msg/msg-burn.go b/types/msg/msg-burn.go index 8ab01917..0c42dcc1 100644 --- a/types/msg/msg-burn.go +++ b/types/msg/msg-burn.go @@ -3,6 +3,7 @@ package msg import ( "encoding/json" "fmt" + "github.com/binance-chain/go-sdk/common/types" ) diff --git a/types/msg/msg-dexList.go b/types/msg/msg-dexList.go index 0337e86a..485d4097 100644 --- a/types/msg/msg-dexList.go +++ b/types/msg/msg-dexList.go @@ -3,6 +3,7 @@ package msg import ( "encoding/json" "fmt" + "github.com/binance-chain/go-sdk/common/types" ) diff --git a/types/msg/msg-freeze.go b/types/msg/msg-freeze.go index af16c112..cbc0c189 100644 --- a/types/msg/msg-freeze.go +++ b/types/msg/msg-freeze.go @@ -3,6 +3,7 @@ package msg import ( "encoding/json" "fmt" + "github.com/binance-chain/go-sdk/common/types" ) diff --git a/types/msg/msg-gov.go b/types/msg/msg-gov.go index 1cf12c8f..0f283e26 100644 --- a/types/msg/msg-gov.go +++ b/types/msg/msg-gov.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "github.com/binance-chain/go-sdk/common/types" + "github.com/pkg/errors" "github.com/tendermint/go-amino" "github.com/binance-chain/go-sdk/common/types" diff --git a/types/msg/msg-issue.go b/types/msg/msg-issue.go index 93cfd5ee..443f3115 100644 --- a/types/msg/msg-issue.go +++ b/types/msg/msg-issue.go @@ -4,10 +4,11 @@ import ( "encoding/json" "errors" "fmt" - "github.com/binance-chain/go-sdk/common/types" "math" "strings" + "github.com/binance-chain/go-sdk/common/types" + "github.com/binance-chain/go-sdk/common" ) diff --git a/types/msg/msg-mint.go b/types/msg/msg-mint.go index b1be1e02..a3f54367 100644 --- a/types/msg/msg-mint.go +++ b/types/msg/msg-mint.go @@ -3,6 +3,7 @@ package msg import ( "encoding/json" "fmt" + "github.com/binance-chain/go-sdk/common/types" ) diff --git a/types/msg/msg-order.go b/types/msg/msg-order.go index ff653804..0a01d7c5 100644 --- a/types/msg/msg-order.go +++ b/types/msg/msg-order.go @@ -4,9 +4,9 @@ import ( "encoding/json" "errors" "fmt" - "github.com/binance-chain/go-sdk/common/types" "strings" + "github.com/binance-chain/go-sdk/common/types" ) // Order routes diff --git a/types/msg/msg-send.go b/types/msg/msg-send.go index 42ded943..053fb04e 100644 --- a/types/msg/msg-send.go +++ b/types/msg/msg-send.go @@ -3,6 +3,7 @@ package msg import ( "encoding/json" "fmt" + "github.com/binance-chain/go-sdk/common/types" ) diff --git a/types/msg/msg.go b/types/msg/msg.go index efb78fef..8a3a9fb8 100644 --- a/types/msg/msg.go +++ b/types/msg/msg.go @@ -2,10 +2,11 @@ package msg import ( "fmt" - "github.com/binance-chain/go-sdk/common/types" "regexp" "strings" + "github.com/binance-chain/go-sdk/common/types" + "github.com/pkg/errors" "github.com/binance-chain/go-sdk/common" diff --git a/types/tx/stdsign.go b/types/tx/stdsign.go index 7bf88e1d..5c56b264 100644 --- a/types/tx/stdsign.go +++ b/types/tx/stdsign.go @@ -2,8 +2,9 @@ package tx import ( "encoding/json" - "github.com/tendermint/tendermint/crypto" + "github.com/binance-chain/go-sdk/types/msg" + "github.com/tendermint/tendermint/crypto" ) // StdSignDoc def From ba219b98bc4a8e38bf1afcbf4fd290d17697bdff Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 11 Apr 2019 17:40:57 +0800 Subject: [PATCH 06/14] format import order --- client/client.go | 2 +- client/rpc/dex_client.go | 10 ---------- client/rpc/ws_client.go | 10 +++++----- keys/keys.go | 6 +++--- 4 files changed, 9 insertions(+), 19 deletions(-) diff --git a/client/client.go b/client/client.go index 01d69457..75bb3f2a 100644 --- a/client/client.go +++ b/client/client.go @@ -1,13 +1,13 @@ package client import ( - "github.com/binance-chain/go-sdk/common/types" "gopkg.in/resty.v1" "github.com/binance-chain/go-sdk/client/basic" "github.com/binance-chain/go-sdk/client/query" "github.com/binance-chain/go-sdk/client/transaction" "github.com/binance-chain/go-sdk/client/websocket" + "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/keys" ) diff --git a/client/rpc/dex_client.go b/client/rpc/dex_client.go index 9baff7f7..246afae6 100644 --- a/client/rpc/dex_client.go +++ b/client/rpc/dex_client.go @@ -9,19 +9,9 @@ import ( ) const ( - MainStoreName = "main" AccountStoreName = "acc" - ValAddrStoreName = "val" TokenStoreName = "tokens" - DexStoreName = "dex" - PairStoreName = "pairs" - StakeStoreName = "stake" - ParamsStoreName = "params" - GovStoreName = "gov" ParamABCIPrefix = "param" - - StakeTransientStoreName = "transient_stake" - ParamsTransientStoreName = "transient_params" ) func (c *HTTP) ListAllTokens(offset int, limit int) ([]types.Token, error) { diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index 04e25483..a3ccd95c 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -6,25 +6,25 @@ import ( "net" "net/http" - "github.com/tendermint/tendermint/p2p" - - "github.com/gorilla/websocket" - "github.com/rcrowley/go-metrics" "fmt" "strings" "sync" "time" - "github.com/binance-chain/go-sdk/common/uuid" "github.com/pkg/errors" + "github.com/gorilla/websocket" + "github.com/rcrowley/go-metrics" "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" rpctypes "github.com/tendermint/tendermint/rpc/lib/types" "github.com/tendermint/tendermint/types" + + "github.com/binance-chain/go-sdk/common/uuid" ) const ( diff --git a/keys/keys.go b/keys/keys.go index 9696ee89..55631f5a 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -6,19 +6,19 @@ import ( "encoding/json" "fmt" - ctypes "github.com/binance-chain/go-sdk/common/types" - "io/ioutil" "strings" "github.com/cosmos/go-bip39" "golang.org/x/crypto/pbkdf2" + "github.com/tendermint/tendermint/crypto/secp256k1" + "github.com/binance-chain/go-sdk/common" "github.com/binance-chain/go-sdk/common/uuid" "github.com/binance-chain/go-sdk/types/tx" "github.com/tendermint/tendermint/crypto" - "github.com/tendermint/tendermint/crypto/secp256k1" + ctypes "github.com/binance-chain/go-sdk/common/types" ) const ( From 7ad1e3b855abd425d977a1595f48f4e7d859a311 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 11 Apr 2019 18:09:55 +0800 Subject: [PATCH 07/14] update doc --- ReadMe.md | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 3b747e1c..4e548824 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -23,21 +23,10 @@ Add "github.com/binance-chain/go-sdk" dependency into your go.mod file. Example: require ( github.com/binance-chain/go-sdk latest ) +replace github.com/tendermint/go-amino => github.com/binance-chain/bnc-go-amino v0.14.1-binance.1 ``` -### Use go get - -Use go get to install sdk into your `GOPATH`: -```bash -go get github.com/binance-chain/go-sdk -``` - -### Use dep -Add dependency to your Gopkg.toml file. Example: -```bash -[[override]] - name = "github.com/binance-chain/go-sdk" -``` +**NOTE**: Please make sure you use binance-chain amino repo instead of tendermint amino. ## Usage @@ -149,4 +138,16 @@ Create a `buy` order: createOrderResult, err := client.CreateOrder(tradeSymbol, nativeSymbol, txmsg.OrderSide.BUY, 100000000, 100000000, true) ``` -For more API usage documentation, please check the [wiki](https://github.com/binance-chain/go-sdk/wiki).. \ No newline at end of file +For more API usage documentation, please check the [wiki](https://github.com/binance-chain/go-sdk/wiki).. + +## RPC Client(Beta) +RPC endpoints may be used to interact with a node directly over HTTP or websockets. Using RPC, you may perform low-level +operations like executing ABCI queries, viewing network/consensus state or broadcasting a transaction against full node or +light client. + +### Example +```go +nodeAddr := "tcp://127.0.0.1:27147" +testClientInstance := rpc.NewRPCClient(nodeAddr) +status, err := c.Status() +``` \ No newline at end of file From e872887866181096b99a2e9e7deead556b6b07a4 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 15 Apr 2019 17:32:27 +0800 Subject: [PATCH 08/14] update api of tx search --- client/rpc/basic_client.go | 52 ++++++++++++++++++++-- client/rpc/validate.go | 90 ++++++++++++++++++++++++++++++++++++++ client/rpc/ws_client.go | 80 +++++++++++++++++++++++---------- types/msg/wire.go | 3 -- types/tx/stdtx.go | 9 ++++ types/tx/wire.go | 5 ++- 6 files changed, 209 insertions(+), 30 deletions(-) create mode 100644 client/rpc/validate.go diff --git a/client/rpc/basic_client.go b/client/rpc/basic_client.go index ccfa07b6..c9b2b377 100644 --- a/client/rpc/basic_client.go +++ b/client/rpc/basic_client.go @@ -5,14 +5,16 @@ import ( "time" "github.com/pkg/errors" - "github.com/tendermint/tendermint/p2p" - ntypes "github.com/binance-chain/go-sdk/common/types" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" - rpcclient "github.com/tendermint/tendermint/rpc/lib/client" + "github.com/tendermint/tendermint/rpc/lib/client" "github.com/tendermint/tendermint/types" + + ntypes "github.com/binance-chain/go-sdk/common/types" + "github.com/binance-chain/go-sdk/types/tx" ) const defaultTimeout = 5 * time.Second @@ -33,6 +35,7 @@ func NewHTTP(remote, wsEndpoint string) *HTTP { cdc := rc.Codec() ctypes.RegisterAmino(cdc) ntypes.RegisterWire(cdc) + tx.RegisterCodec(cdc) rc.SetCodec(cdc) wsEvent := newWSEvents(cdc, remote, wsEndpoint) @@ -61,22 +64,40 @@ func (c *HTTP) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuer } func (c *HTTP) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + if err := ValidateABCIPath(path); err != nil { + return nil, err + } + if err := ValidateABCIData(data); err != nil { + return nil, err + } return c.WSEvents.ABCIQueryWithOptions(path, data, opts) } func (c *HTTP) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + if err := ValidateTx(tx); err != nil { + return nil, err + } return c.WSEvents.BroadcastTxCommit(tx) } func (c *HTTP) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + if err := ValidateTx(tx); err != nil { + return nil, err + } return c.WSEvents.BroadcastTx("broadcast_tx_async", tx) } func (c *HTTP) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + if err := ValidateTx(tx); err != nil { + return nil, err + } return c.WSEvents.BroadcastTx("broadcast_tx_sync", tx) } func (c *HTTP) UnconfirmedTxs(limit int) (*ctypes.ResultUnconfirmedTxs, error) { + if err := ValidateUnConfirmedTxsLimit(limit); err != nil { + return nil, err + } return c.WSEvents.UnconfirmedTxs(limit) } @@ -101,6 +122,9 @@ func (c *HTTP) Health() (*ctypes.ResultHealth, error) { } func (c *HTTP) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + if err := ValidateHeightRange(minHeight, maxHeight); err != nil { + return nil, err + } return c.WSEvents.BlockchainInfo(minHeight, maxHeight) } @@ -109,25 +133,47 @@ func (c *HTTP) Genesis() (*ctypes.ResultGenesis, error) { } func (c *HTTP) Block(height *int64) (*ctypes.ResultBlock, error) { + if err := ValidateHeight(height); err != nil { + return nil, err + } return c.WSEvents.Block(height) } func (c *HTTP) BlockResults(height *int64) (*ctypes.ResultBlockResults, error) { + if err := ValidateHeight(height); err != nil { + return nil, err + } return c.WSEvents.BlockResults(height) } func (c *HTTP) Commit(height *int64) (*ctypes.ResultCommit, error) { + if err := ValidateHeight(height); err != nil { + return nil, err + } return c.WSEvents.Commit(height) } func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { + if err := ValidateHash(hash); err != nil { + return nil, err + } return c.WSEvents.Tx(hash, prove) } func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { + if err := ValidateCommonStr(query); err != nil { + return nil, err + } return c.WSEvents.TxSearch(query, prove, page, perPage) } +func (c *HTTP) TxInfoSearch(query string, prove bool, page, perPage int) ([]tx.Info, error) { + if err := ValidateCommonStr(query); err != nil { + return nil, err + } + return c.WSEvents.TxInfoSearch(query, prove, page, perPage) +} + func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { return c.WSEvents.Validators(height) } diff --git a/client/rpc/validate.go b/client/rpc/validate.go new file mode 100644 index 00000000..4b9739ca --- /dev/null +++ b/client/rpc/validate.go @@ -0,0 +1,90 @@ +package rpc + +import ( + "crypto/sha256" + "fmt" + "github.com/pkg/errors" + "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/types" +) + +const ( + maxABCIPathLength = 1024 + maxABCIDataLength = 1024 * 1024 + maxTxLength = 1024 * 1024 + maxCommonStringLength = 1024 + maxUnConfirmedTxs = 100 +) + +var ( + ExceedABCIPathLengthError = errors.New(fmt.Sprintf("the abci path exceed max length %d ", maxABCIPathLength)) + ExceedABCIDataLengthError = errors.New(fmt.Sprintf("the abci data exceed max length %d ", maxABCIDataLength)) + ExceedTxLengthError = errors.New(fmt.Sprintf("the tx data exceed max length %d ", maxTxLength)) + LimitNegativeError = errors.New("the limit can't be negative") + ExceedMaxUnConfirmedTxsNumError = errors.New(fmt.Sprintf("the limit of unConfirmed tx exceed max limit %d ", maxUnConfirmedTxs)) + HeightNegativeError = errors.New("the height can't be negative") + MaxMinHeightConflictError = errors.New("the min height can't be larger than max height") + HashLengthError = errors.New("the length of hash is not 32") + ExceedCommonStrLengthError = errors.New(fmt.Sprintf("the query string exceed max length %d ", maxABCIPathLength)) +) + +func ValidateABCIPath(path string) error { + if len(path) > maxABCIPathLength { + return ExceedABCIPathLengthError + } + return nil +} + +func ValidateABCIData(data common.HexBytes) error { + if len(data) > maxABCIDataLength { + return ExceedABCIPathLengthError + } + return nil +} + +func ValidateTx(tx types.Tx) error { + if len(tx) > maxTxLength { + return ExceedTxLengthError + } + return nil +} + +func ValidateUnConfirmedTxsLimit(limit int) error { + if limit < 0 { + return LimitNegativeError + } else if limit > maxUnConfirmedTxs { + return ExceedMaxUnConfirmedTxsNumError + } + return nil +} + +func ValidateHeightRange(from int64, to int64) error { + if from < 0 || to < 0 { + return HeightNegativeError + } + if from < to { + return MaxMinHeightConflictError + } + return nil +} + +func ValidateHeight(height *int64) error { + if height != nil && *height < 0 { + return HeightNegativeError + } + return nil +} + +func ValidateHash(hash []byte) error { + if len(hash) != sha256.Size { + return HashLengthError + } + return nil +} + +func ValidateCommonStr(query string) error { + if len(query) > maxCommonStringLength { + return ExceedCommonStrLengthError + } + return nil +} diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index a3ccd95c..27327c13 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -6,30 +6,29 @@ import ( "net" "net/http" - "fmt" "strings" "sync" "time" - "github.com/pkg/errors" "github.com/gorilla/websocket" - "github.com/rcrowley/go-metrics" + "github.com/pkg/errors" "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" - rpctypes "github.com/tendermint/tendermint/rpc/lib/types" + "github.com/tendermint/tendermint/rpc/lib/types" "github.com/tendermint/tendermint/types" "github.com/binance-chain/go-sdk/common/uuid" + "github.com/binance-chain/go-sdk/types/tx" ) const ( defaultMaxReconnectAttempts = 25 - defaultMaxReconnectBackOffTime = 600 * time.Second + defaultMaxReconnectBackOffTime = 120 * time.Second defaultWriteWait = 100 * time.Millisecond defaultReadWait = 0 defaultPingPeriod = 0 @@ -394,6 +393,18 @@ func (w *WSEvents) TxSearch(query string, prove bool, page, perPage int) (*ctype return txs, err } +func (w *WSEvents) TxInfoSearch(query string, prove bool, page, perPage int) ([]tx.Info, error) { + + txs := new(ctypes.ResultTxSearch) + err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { + return w.ws.TxSearch(ctx, id, query, prove, page, perPage) + }, txs) + if err != nil { + return nil, err + } + return FormatTxResults(w.cdc, txs.Txs) +} + func (w *WSEvents) Validators(height *int64) (*ctypes.ResultValidators, error) { validators := new(ctypes.ResultValidators) err := w.SimpleCall(func(ctx context.Context, id rpctypes.JSONRPCStringID) error { @@ -472,10 +483,6 @@ type WSClient struct { Endpoint string // /websocket/url/endpoint Dialer func(string, string) (net.Conn, error) - // Time between sending a ping and receiving a pong. See - // https://godoc.org/github.com/rcrowley/go-metrics#Timer. - PingPongLatencyTimer metrics.Timer - // Single user facing channel to read RPCResponses from, closed only when the client is being stopped. ResponsesCh chan rpctypes.RPCResponse @@ -521,11 +528,10 @@ func NewWSClient(remoteAddr, endpoint string, options ...func(*WSClient)) *WSCli } c := &WSClient{ - cdc: amino.NewCodec(), - Address: addr, - Dialer: dialer, - Endpoint: endpoint, - PingPongLatencyTimer: metrics.NewTimer(), + cdc: amino.NewCodec(), + Address: addr, + Dialer: dialer, + Endpoint: endpoint, maxReconnectAttempts: defaultMaxReconnectAttempts, readWait: defaultReadWait, @@ -591,8 +597,6 @@ func (c *WSClient) Stop() error { // IsReconnecting returns true if the client is reconnecting right now. func (c *WSClient) IsReconnecting() bool { - c.mtx.RLock() - defer c.mtx.RUnlock() return c.reconnecting } @@ -823,12 +827,6 @@ func (c *WSClient) readRoutine() { }() c.conn.SetPongHandler(func(string) error { - // gather latency stats - c.mtx.RLock() - t := c.sentLastPingAt - c.mtx.RUnlock() - c.PingPongLatencyTimer.UpdateSince(t) - c.Logger.Debug("got pong") return nil }) @@ -1014,3 +1012,41 @@ func makeHTTPDialer(remoteAddr string) (string, string, func(string, string) (ne return net.Dial(protocol, address) } } + +// parse the indexed txs into an array of Info +func FormatTxResults(cdc *amino.Codec, res []*ctypes.ResultTx) ([]tx.Info, error) { + var err error + out := make([]tx.Info, len(res)) + for i := range res { + out[i], err = formatTxResult(cdc, res[i]) + if err != nil { + return nil, err + } + } + return out, nil +} + +func formatTxResult(cdc *amino.Codec, res *ctypes.ResultTx) (tx.Info, error) { + parsedTx, err := parseTx(cdc, res.Tx) + if err != nil { + return tx.Info{}, err + } + + return tx.Info{ + Hash: res.Hash, + Height: res.Height, + Tx: parsedTx, + Result: res.TxResult, + }, nil +} + +func parseTx(cdc *amino.Codec, txBytes []byte) (tx.Tx, error) { + var parsedTx tx.StdTx + + err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &parsedTx) + if err != nil { + return nil, err + } + + return parsedTx, nil +} diff --git a/types/msg/wire.go b/types/msg/wire.go index 10b650e8..b0f3d1c0 100644 --- a/types/msg/wire.go +++ b/types/msg/wire.go @@ -2,14 +2,11 @@ package msg import ( "github.com/tendermint/go-amino" - - cryptoAmino "github.com/tendermint/tendermint/crypto/encoding/amino" ) var MsgCdc = amino.NewCodec() func RegisterCodec(cdc *amino.Codec) { - cryptoAmino.RegisterAmino(cdc) cdc.RegisterInterface((*Msg)(nil), nil) diff --git a/types/tx/stdtx.go b/types/tx/stdtx.go index 2d6626be..fb60ee1c 100644 --- a/types/tx/stdtx.go +++ b/types/tx/stdtx.go @@ -2,6 +2,8 @@ package tx import ( "github.com/binance-chain/go-sdk/types/msg" + "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/common" ) const Source int64 = 2 @@ -34,3 +36,10 @@ func NewStdTx(msgs []msg.Msg, sigs []StdSignature, memo string, source int64, da // GetMsgs def func (tx StdTx) GetMsgs() []msg.Msg { return tx.Msgs } + +type Info struct { + Hash common.HexBytes `json:"hash"` + Height int64 `json:"height"` + Tx Tx `json:"tx"` + Result types.ResponseDeliverTx `json:"result"` +} diff --git a/types/tx/wire.go b/types/tx/wire.go index a23548ff..514c5da2 100644 --- a/types/tx/wire.go +++ b/types/tx/wire.go @@ -2,20 +2,21 @@ package tx import ( "github.com/tendermint/go-amino" + "github.com/tendermint/tendermint/crypto/encoding/amino" "github.com/binance-chain/go-sdk/types/msg" ) -// Cdc global variable +// cdc global variable var Cdc = amino.NewCodec() func RegisterCodec(cdc *amino.Codec) { cdc.RegisterInterface((*Tx)(nil), nil) cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) - msg.RegisterCodec(cdc) } func init() { + cryptoAmino.RegisterAmino(Cdc) RegisterCodec(Cdc) } From fb871999fd3d30bed1f26750dc1fd842f6959e41 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Thu, 18 Apr 2019 15:27:13 +0800 Subject: [PATCH 09/14] add mock --- client/rpc/basic_client.go | 24 +++++-- client/rpc/dex_client.go | 21 ++++++ client/rpc/mock/client.go | 139 +++++++++++++++++++++++++++++++++++++ client/rpc/ops_client.go | 5 ++ client/rpc/validate.go | 18 ++--- client/rpc/ws_client.go | 4 +- common/types/dec.go | 63 +++++++++++++++++ common/types/stake.go | 55 --------------- e2e/e2e_rpc_test.go | 35 ++++++---- go.mod | 1 - 10 files changed, 277 insertions(+), 88 deletions(-) create mode 100644 client/rpc/mock/client.go create mode 100644 common/types/dec.go diff --git a/client/rpc/basic_client.go b/client/rpc/basic_client.go index c9b2b377..ed8fdcbb 100644 --- a/client/rpc/basic_client.go +++ b/client/rpc/basic_client.go @@ -19,6 +19,23 @@ import ( const defaultTimeout = 5 * time.Second +type Client interface { + cmn.Service + client.ABCIClient + client.SignClient + client.HistoryClient + client.StatusClient + EventsClient + DexClient + OpsClient +} + +type EventsClient interface { + Subscribe(query string, outCapacity ...int) (out chan ctypes.ResultEvent, err error) + Unsubscribe(query string) error + UnsubscribeAll() error +} + func NewRPCClient(nodeURI string) *HTTP { return NewHTTP(nodeURI, "/websocket") } @@ -167,13 +184,6 @@ func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.Re return c.WSEvents.TxSearch(query, prove, page, perPage) } -func (c *HTTP) TxInfoSearch(query string, prove bool, page, perPage int) ([]tx.Info, error) { - if err := ValidateCommonStr(query); err != nil { - return nil, err - } - return c.WSEvents.TxInfoSearch(query, prove, page, perPage) -} - func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { return c.WSEvents.Validators(height) } diff --git a/client/rpc/dex_client.go b/client/rpc/dex_client.go index 246afae6..034e0bb6 100644 --- a/client/rpc/dex_client.go +++ b/client/rpc/dex_client.go @@ -3,6 +3,7 @@ package rpc import ( "errors" "fmt" + "github.com/binance-chain/go-sdk/types/tx" "strings" "github.com/binance-chain/go-sdk/common/types" @@ -14,6 +15,26 @@ const ( ParamABCIPrefix = "param" ) +type DexClient interface { + TxInfoSearch(query string, prove bool, page, perPage int) ([]tx.Info, error) + ListAllTokens(offset int, limit int) ([]types.Token, error) + GetTokenInfo(symbol string) (*types.Token, error) + GetAccount(addr types.AccAddress) (acc types.Account, err error) + GetBalances(addr types.AccAddress) ([]types.TokenBalance, error) + GetBalance(addr types.AccAddress, symbol string) (*types.TokenBalance, error) + GetFee() ([]types.FeeParam, error) + GetOpenOrders(addr types.AccAddress, pair string) ([]types.OpenOrder, error) + GetTradingPairs(offset int, limit int) ([]types.TradingPair, error) + GetDepth(tradePair string) (*types.OrderBook, error) +} + +func (c *HTTP) TxInfoSearch(query string, prove bool, page, perPage int) ([]tx.Info, error) { + if err := ValidateCommonStr(query); err != nil { + return nil, err + } + return c.WSEvents.TxInfoSearch(query, prove, page, perPage) +} + func (c *HTTP) ListAllTokens(offset int, limit int) ([]types.Token, error) { path := fmt.Sprintf("tokens/list/%d/%d", offset, limit) result, err := c.ABCIQuery(path, nil) diff --git a/client/rpc/mock/client.go b/client/rpc/mock/client.go new file mode 100644 index 00000000..6cd6c3c0 --- /dev/null +++ b/client/rpc/mock/client.go @@ -0,0 +1,139 @@ +package mock + +/* +package mock returns a Client implementation that +accepts various (mock) implementations of the various methods. + +This implementation is useful for using in tests, when you don't +need a real server, but want a high-level of control about +the server response you want to mock (eg. error handling), +or if you just want to record the calls to verify in your tests. + +For real clients, you probably want the "http" package. If you +want to directly call a tendermint node in process, you can use the +"local" package. +*/ + +import ( + "github.com/binance-chain/go-sdk/client/rpc" + "reflect" + + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/rpc/core" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" +) + +// Client wraps arbitrary implementations of the various interfaces. +// +// We provide a few choices to mock out each one in this package. +// Nothing hidden here, so no New function, just construct it from +// some parts, and swap them out them during the tests. +type Client struct { + cmn.Service + client.ABCIClient + client.SignClient + client.HistoryClient + client.StatusClient + rpc.EventsClient + rpc.DexClient + rpc.OpsClient +} + +var _ client.Client = Client{} + +// Call is used by recorders to save a call and response. +// It can also be used to configure mock responses. +// +type Call struct { + Name string + Args interface{} + Response interface{} + Error error +} + +// GetResponse will generate the apporiate response for us, when +// using the Call struct to configure a Mock handler. +// +// When configuring a response, if only one of Response or Error is +// set then that will always be returned. If both are set, then +// we return Response if the Args match the set args, Error otherwise. +func (c Call) GetResponse(args interface{}) (interface{}, error) { + // handle the case with no response + if c.Response == nil { + if c.Error == nil { + panic("Misconfigured call, you must set either Response or Error") + } + return nil, c.Error + } + // response without error + if c.Error == nil { + return c.Response, nil + } + // have both, we must check args.... + if reflect.DeepEqual(args, c.Args) { + return c.Response, nil + } + return nil, c.Error +} + +func (c Client) Status() (*ctypes.ResultStatus, error) { + return core.Status() +} + +func (c Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + return core.ABCIInfo() +} + +func (c Client) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { + return c.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) +} + +func (c Client) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + return core.ABCIQuery(path, data, opts.Height, opts.Prove) +} + +func (c Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + return core.BroadcastTxCommit(tx) +} + +func (c Client) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return core.BroadcastTxAsync(tx) +} + +func (c Client) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return core.BroadcastTxSync(tx) +} + +func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { + return core.NetInfo() +} + +func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + return core.UnsafeDialSeeds(seeds) +} + +func (c Client) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + return core.UnsafeDialPeers(peers, persistent) +} + +func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + return core.BlockchainInfo(minHeight, maxHeight) +} + +func (c Client) Genesis() (*ctypes.ResultGenesis, error) { + return core.Genesis() +} + +func (c Client) Block(height *int64) (*ctypes.ResultBlock, error) { + return core.Block(height) +} + +func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) { + return core.Commit(height) +} + +func (c Client) Validators(height *int64) (*ctypes.ResultValidators, error) { + return core.Validators(height) +} diff --git a/client/rpc/ops_client.go b/client/rpc/ops_client.go index 25b5eeb5..024eca78 100644 --- a/client/rpc/ops_client.go +++ b/client/rpc/ops_client.go @@ -2,6 +2,11 @@ package rpc import "github.com/binance-chain/go-sdk/common/types" +type OpsClient interface { + GetStakeValidators() ([]types.Validator, error) + GetDelegatorUnbondingDelegations(delegatorAddr types.AccAddress) ([]types.UnbondingDelegation, error) +} + func (c *HTTP) GetStakeValidators() ([]types.Validator, error) { rawVal, err := c.ABCIQuery("custom/stake/validators", nil) if err != nil { diff --git a/client/rpc/validate.go b/client/rpc/validate.go index 4b9739ca..97faef7e 100644 --- a/client/rpc/validate.go +++ b/client/rpc/validate.go @@ -17,15 +17,15 @@ const ( ) var ( - ExceedABCIPathLengthError = errors.New(fmt.Sprintf("the abci path exceed max length %d ", maxABCIPathLength)) - ExceedABCIDataLengthError = errors.New(fmt.Sprintf("the abci data exceed max length %d ", maxABCIDataLength)) - ExceedTxLengthError = errors.New(fmt.Sprintf("the tx data exceed max length %d ", maxTxLength)) - LimitNegativeError = errors.New("the limit can't be negative") - ExceedMaxUnConfirmedTxsNumError = errors.New(fmt.Sprintf("the limit of unConfirmed tx exceed max limit %d ", maxUnConfirmedTxs)) - HeightNegativeError = errors.New("the height can't be negative") - MaxMinHeightConflictError = errors.New("the min height can't be larger than max height") - HashLengthError = errors.New("the length of hash is not 32") - ExceedCommonStrLengthError = errors.New(fmt.Sprintf("the query string exceed max length %d ", maxABCIPathLength)) + ExceedABCIPathLengthError = fmt.Errorf("the abci path exceed max length %d ", maxABCIPathLength) + ExceedABCIDataLengthError = fmt.Errorf("the abci data exceed max length %d ", maxABCIDataLength) + ExceedTxLengthError = fmt.Errorf("the tx data exceed max length %d ", maxTxLength) + LimitNegativeError = fmt.Errorf("the limit can't be negative") + ExceedMaxUnConfirmedTxsNumError = fmt.Errorf("the limit of unConfirmed tx exceed max limit %d ", maxUnConfirmedTxs) + HeightNegativeError = fmt.Errorf("the height can't be negative") + MaxMinHeightConflictError = fmt.Errorf("the min height can't be larger than max height") + HashLengthError = fmt.Errorf("the length of hash is not 32") + ExceedCommonStrLengthError = fmt.Errorf("the query string exceed max length %d ", maxABCIPathLength) ) func ValidateABCIPath(path string) error { diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index 27327c13..a4753189 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -2,6 +2,7 @@ package rpc import ( "context" + "encoding/hex" "encoding/json" "net" "net/http" @@ -1042,8 +1043,9 @@ func formatTxResult(cdc *amino.Codec, res *ctypes.ResultTx) (tx.Info, error) { func parseTx(cdc *amino.Codec, txBytes []byte) (tx.Tx, error) { var parsedTx tx.StdTx - + fmt.Println(hex.EncodeToString(txBytes)) err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &parsedTx) + if err != nil { return nil, err } diff --git a/common/types/dec.go b/common/types/dec.go new file mode 100644 index 00000000..3a1f8cb7 --- /dev/null +++ b/common/types/dec.go @@ -0,0 +1,63 @@ +package types + +import ( + "encoding/json" + "fmt" + "strconv" +) + +type Dec struct { + int64 `json:"int"` +} + +func (d Dec) String() string { + return strconv.FormatInt(d.int64, 10) +} + +func (d Dec) MarshalText() ([]byte, error) { + return []byte(strconv.FormatInt(d.int64, 10)), nil +} + +func (d *Dec) UnmarshalText(text []byte) error { + v, err := strconv.ParseInt(string(text), 10, 64) + d.int64 = v + return err +} + +// requires a valid JSON string - strings quotes and calls UnmarshalText +func (d *Dec) UnmarshalAmino(v int64) (err error) { + d.int64 = v + return nil +} +func (d Dec) MarshalAmino() (int64, error) { + return d.int64, nil +} + +// MarshalJSON marshals the decimal +func (d Dec) MarshalJSON() ([]byte, error) { + return json.Marshal(d.String()) +} + +// UnmarshalJSON defines custom decoding scheme +func (d *Dec) UnmarshalJSON(bz []byte) error { + var text string + err := json.Unmarshal(bz, &text) + if err != nil { + return err + } + // TODO: Reuse dec allocation + newDec, err := NewDecFromStr(text) + if err != nil { + return err + } + d.int64 = newDec.int64 + return nil +} + +func NewDecFromStr(str string) (d Dec, err error) { + value, parseErr := strconv.ParseInt(str, 10, 64) + if parseErr != nil { + return d, fmt.Errorf("bad string to integer conversion, input string: %v, error: %v", str, parseErr) + } + return Dec{value}, nil +} \ No newline at end of file diff --git a/common/types/stake.go b/common/types/stake.go index f0979aa1..128e789d 100644 --- a/common/types/stake.go +++ b/common/types/stake.go @@ -78,61 +78,6 @@ type UnbondingDelegation struct { Balance Coin `json:"balance"` // atoms to receive at completion } -type Dec struct { - int64 `json:"int"` -} - -func (d Dec) String() string { - return strconv.FormatInt(d.int64, 10) -} - -func (d Dec) MarshalText() ([]byte, error) { - return []byte(strconv.FormatInt(d.int64, 10)), nil -} - -func (d *Dec) UnmarshalText(text []byte) error { - v, err := strconv.ParseInt(string(text), 10, 64) - d.int64 = v - return err -} - -// requires a valid JSON string - strings quotes and calls UnmarshalText -func (d *Dec) UnmarshalAmino(v int64) (err error) { - d.int64 = v - return nil -} -func (d Dec) MarshalAmino() (int64, error) { - return d.int64, nil -} - -// MarshalJSON marshals the decimal -func (d Dec) MarshalJSON() ([]byte, error) { - return json.Marshal(d.String()) -} - -// UnmarshalJSON defines custom decoding scheme -func (d *Dec) UnmarshalJSON(bz []byte) error { - var text string - err := json.Unmarshal(bz, &text) - if err != nil { - return err - } - // TODO: Reuse dec allocation - newDec, err := NewDecFromStr(text) - if err != nil { - return err - } - d.int64 = newDec.int64 - return nil -} - -func NewDecFromStr(str string) (d Dec, err error) { - value, parseErr := strconv.ParseInt(str, 10, 64) - if parseErr != nil { - return d, errors.New(fmt.Sprintf("bad string to integer conversion, input string: %v, error: %v", str, parseErr)) - } - return Dec{value}, nil -} func (va ValAddress) String() string { bech32PrefixValAddr := Network.Bech32ValidatorAddrPrefix() diff --git a/e2e/e2e_rpc_test.go b/e2e/e2e_rpc_test.go index 132b67b9..942c65e0 100644 --- a/e2e/e2e_rpc_test.go +++ b/e2e/e2e_rpc_test.go @@ -18,10 +18,10 @@ import ( ) var ( - nodeAddr = "tcp://seed-pre-s3.binance.org:80" + nodeAddr = "tcp://data-seed-pre-0-s3.binance.org:80" badAddr = "tcp://127.0.0.1:80" - testTxHash = "9E9E6EA3FA13684DD260DB627144EABDB50F2C205DE733447C5E8415311670C9" - testTxHeight = 960284 + testTxHash = "A27C20143E6B7D8160B50883F81132C1DFD0072FF2C1FE71E0158FBD001E23E4" + testTxHeight = 8669273 testAddress = "tbnb1l6vgk5yyxcalm06gdsg55ay4pjkfueazkvwh58" testDelAddr = "tbnb12hlquylu78cjylk5zshxpdj6hf3t0tahwjt3ex" testTradePair = "X00-243_BNB" @@ -180,8 +180,7 @@ func TestReconnection(t *testing.T) { func TestTxSearch(t *testing.T) { c := defaultClient() - - tx, err := c.TxSearch(fmt.Sprintf("tx.height=%d", testTxHeight), false, 1, 10) + tx, err := c.TxInfoSearch(fmt.Sprintf("tx.height=%d", testTxHeight), false, 1, 10) assert.NoError(t, err) bz, err := json.Marshal(tx) fmt.Println(string(bz)) @@ -252,9 +251,9 @@ func TestSubscribeEventTwice(t *testing.T) { func TestReceiveWithRequestId(t *testing.T) { c := defaultClient() - c.SetTimeOut(5 * time.Second) + c.SetTimeOut(1 * time.Second) w := sync.WaitGroup{} - w.Add(10) + w.Add(2000) testCases := []func(t *testing.T){ TestRPCStatus, TestRPCABCIInfo, @@ -267,13 +266,13 @@ func TestReceiveWithRequestId(t *testing.T) { TestBlockchainInfo, TestGenesis, TestBlock, - TestBlockResults, + //TestBlockResults, TestCommit, - TestTx, - TestTxSearch, - //TestValidators, + //TestTx, + //TestTxSearch, + TestValidators, } - for i := 0; i < 10; i++ { + for i := 0; i < 2000; i++ { testFuncIndex := rand.Intn(len(testCases)) go func() { testCases[testFuncIndex](t) @@ -308,6 +307,8 @@ func TestGetAccount(t *testing.T) { assert.NoError(t, err) bz, err := json.Marshal(account) fmt.Println(string(bz)) + fmt.Println(hex.EncodeToString(account.GetAddress().Bytes())) + } func TestGetBalances(t *testing.T) { @@ -416,13 +417,17 @@ func TestNoRequestLeakInGoodNetwork(t *testing.T) { c := defaultClient() c.SetTimeOut(1 * time.Second) w := sync.WaitGroup{} - w.Add(100) - for i := 0; i < 100; i++ { + w.Add(3000) + for i := 0; i < 3000; i++ { go func() { - c.GetFee() + _, err := c.Block(nil) + assert.NoError(t, err) + //bz, err := json.Marshal(fees) + //fmt.Println(string(bz)) w.Done() }() } w.Wait() assert.Equal(t, c.PendingRequest(), 0) } + diff --git a/go.mod b/go.mod index de3435a1..3937b1c8 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,6 @@ require ( github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tendermint/btcd v0.0.0-20180816174608-e5840949ff4f github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9 // indirect - github.com/tendermint/go-amino v0.14.1 github.com/tendermint/tendermint v0.31.2-rc0 golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 golang.org/x/sys v0.0.0-20181116161606-93218def8b18 // indirect From ef13ce62ed3fe6a3d40d43457930e9e26792575f Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Fri, 19 Apr 2019 14:44:13 +0800 Subject: [PATCH 10/14] move type definition to common --- client/query/errors.go | 19 -- client/query/get_account.go | 25 +-- client/query/get_closed_orders.go | 86 +-------- client/query/get_depth.go | 37 +--- client/query/get_kline.go | 70 +------ client/query/get_markets.go | 31 +--- client/query/get_node_info.go | 54 +----- client/query/get_order.go | 81 +-------- client/query/get_orders_open.go | 52 +----- client/query/get_ticker24h.go | 44 +---- client/query/get_time.go | 11 +- client/query/get_trades.go | 35 +--- client/query/query.go | 22 +-- client/rpc/basic_client.go | 10 +- client/rpc/dex_client.go | 26 ++- client/rpc/mock/client.go | 139 -------------- client/rpc/validate.go | 80 ++++++-- client/rpc/ws_client.go | 11 -- common/types/{bank.go => account.go} | 171 +---------------- common/types/coins.go | 149 +++++++++++++++ common/types/depth.go | 7 + common/types/kline.go | 13 ++ common/types/nodeinfo.go | 53 ++++++ common/types/query.go | 263 +++++++++++++++++++++++++++ common/types/stake.go | 3 - common/types/ticker.go | 25 +++ common/types/time.go | 6 + common/types/tokens.go | 18 ++ common/types/trade.go | 106 +++++++++++ e2e/e2e_rpc_test.go | 45 ++--- e2e/e2e_trans_test.go | 25 ++- e2e/e2e_ws_test.go | 13 +- go.mod | 1 + keys/keys.go | 3 +- types/msg/msg-gov.go | 4 +- 35 files changed, 827 insertions(+), 911 deletions(-) delete mode 100644 client/query/errors.go delete mode 100644 client/rpc/mock/client.go rename common/types/{bank.go => account.go} (57%) create mode 100644 common/types/coins.go create mode 100644 common/types/depth.go create mode 100644 common/types/kline.go create mode 100644 common/types/nodeinfo.go create mode 100644 common/types/query.go create mode 100644 common/types/ticker.go create mode 100644 common/types/time.go create mode 100644 common/types/tokens.go diff --git a/client/query/errors.go b/client/query/errors.go deleted file mode 100644 index 56cb472b..00000000 --- a/client/query/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -package query - -import ( - "errors" -) - -var ( - // Param error - AddressMissingError = errors.New("Address is required ") - SymbolMissingError = errors.New("Symbol is required ") - OffsetOutOfRangeError = errors.New("offset out of range ") - LimitOutOfRangeError = errors.New("limit out of range ") - TradeSideMisMatchError = errors.New("Trade side is invalid ") - StartTimeOutOfRangeError = errors.New("start time out of range ") - EndTimeOutOfRangeError = errors.New("end time out of range ") - IntervalMissingError = errors.New("interval is required ") - EndTimeLessThanStartTimeError = errors.New("end time should great than start time") - OrderIdMissingError = errors.New("order id is required ") -) diff --git a/client/query/get_account.go b/client/query/get_account.go index 8f2c4310..a437211c 100644 --- a/client/query/get_account.go +++ b/client/query/get_account.go @@ -2,29 +2,13 @@ package query import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" ) -// Account definition -type Account struct { - Number int64 `json:"account_number"` - Address string `json:"address"` - Balances []Coin `json:"balances"` - PublicKey []uint8 `json:"public_key"` - Sequence int64 `json:"sequence"` -} - -// Coin def -type Coin struct { - Symbol string `json:"symbol"` // ex: BNB - Free string `json:"free"` // in decimal, ex: 0.00000000 - Locked string `json:"locked"` // in decimal, ex: 0.00000000 - Frozen string `json:"frozen"` // in decimal, ex: 0.00000000 -} - // GetAccount returns list of trading pairs -func (c *client) GetAccount(address string) (*Account, error) { +func (c *client) GetAccount(address string) (*types.BalanceAccount, error) { if address == "" { - return nil, AddressMissingError + return nil, types.AddressMissingError } qp := map[string]string{} @@ -32,10 +16,9 @@ func (c *client) GetAccount(address string) (*Account, error) { if err != nil { return nil, err } - var account Account + var account types.BalanceAccount if err := json.Unmarshal(resp, &account); err != nil { return nil, err } - return &account, nil } diff --git a/client/query/get_closed_orders.go b/client/query/get_closed_orders.go index ece148bd..972c8ec2 100644 --- a/client/query/get_closed_orders.go +++ b/client/query/get_closed_orders.go @@ -4,91 +4,11 @@ import ( "encoding/json" "github.com/binance-chain/go-sdk/common" + "github.com/binance-chain/go-sdk/common/types" ) -const ( - SideBuy = "BUY" - SideSell = "SELL" -) - -type ClosedOrdersQuery struct { - SenderAddress string `json:"address"` // required - Symbol string `json:"symbol,omitempty"` //option - Offset *uint32 `json:"offset,omitempty,string"` //option - Limit *uint32 `json:"limit,omitempty,string"` //option - Start *int64 `json:"start,omitempty,string"` //option - End *int64 `json:"end,omitempty,string"` //option - Side string `json:"side,omitempty"` //option - Total int `json:"total,string"` //0 for not required and 1 for required; default not required, return total=-1 in response -} - -func NewClosedOrdersQuery(senderAddress string, withTotal bool) *ClosedOrdersQuery { - totalQuery := 0 - if withTotal { - totalQuery = 1 - } - return &ClosedOrdersQuery{SenderAddress: senderAddress, Total: totalQuery} -} - -func (param *ClosedOrdersQuery) Check() error { - if param.SenderAddress == "" { - return AddressMissingError - } - if param.Side != SideBuy && param.Side != SideSell && param.Side != "" { - return TradeSideMisMatchError - } - if param.Limit != nil && *param.Limit <= 0 { - return LimitOutOfRangeError - } - if param.Start != nil && *param.Start <= 0 { - return StartTimeOutOfRangeError - } - if param.End != nil && *param.End <= 0 { - return EndTimeOutOfRangeError - } - if param.Start != nil && param.End != nil && *param.Start > *param.End { - return EndTimeLessThanStartTimeError - } - return nil -} - -func (param *ClosedOrdersQuery) WithSymbol(baseAssetSymbol, quoteAssetSymbol string) *ClosedOrdersQuery { - param.Symbol = common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol) - return param -} - -func (param *ClosedOrdersQuery) WithOffset(offset uint32) *ClosedOrdersQuery { - param.Offset = &offset - return param -} - -func (param *ClosedOrdersQuery) WithLimit(limit uint32) *ClosedOrdersQuery { - param.Limit = &limit - return param -} - -func (param *ClosedOrdersQuery) WithStart(start int64) *ClosedOrdersQuery { - param.Start = &start - return param -} - -func (param *ClosedOrdersQuery) WithEnd(end int64) *ClosedOrdersQuery { - param.End = &end - return param -} - -func (param *ClosedOrdersQuery) WithSide(side string) *ClosedOrdersQuery { - param.Side = side - return param -} - -type CloseOrders struct { - Order []Order `json:"order"` - Total int `json:"total"` -} - // GetClosedOrders returns array of open orders -func (c *client) GetClosedOrders(query *ClosedOrdersQuery) (*CloseOrders, error) { +func (c *client) GetClosedOrders(query *types.ClosedOrdersQuery) (*types.CloseOrders, error) { err := query.Check() if err != nil { return nil, err @@ -102,7 +22,7 @@ func (c *client) GetClosedOrders(query *ClosedOrdersQuery) (*CloseOrders, error) return nil, err } - var orders CloseOrders + var orders types.CloseOrders if err := json.Unmarshal(resp, &orders); err != nil { return nil, err } diff --git a/client/query/get_depth.go b/client/query/get_depth.go index f81fc5d3..2ad4160c 100644 --- a/client/query/get_depth.go +++ b/client/query/get_depth.go @@ -4,42 +4,11 @@ import ( "encoding/json" "github.com/binance-chain/go-sdk/common" + "github.com/binance-chain/go-sdk/common/types" ) -// DepthQuery -type DepthQuery struct { - Symbol string `json:"symbol"` - Limit *uint32 `json:"limit,omitempty,string"` -} - -func NewDepthQuery(baseAssetSymbol, quoteAssetSymbol string) *DepthQuery { - return &DepthQuery{Symbol: common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol)} -} - -func (param *DepthQuery) WithLimit(limit uint32) *DepthQuery { - param.Limit = &limit - return param -} - -func (param *DepthQuery) Check() error { - if param.Symbol == "" { - return SymbolMissingError - } - if param.Limit != nil && *param.Limit <= 0 { - return LimitOutOfRangeError - } - return nil -} - -// MarketDepth broad caste to the user -type MarketDepth struct { - Bids [][]string `json:"bids"` // "bids": [ [ "0.0024", "10" ] ] - Asks [][]string `json:"asks"` // "asks": [ [ "0.0024", "10" ] ] - Height int64 `json:"height"` -} - // GetDepth returns market depth records -func (c *client) GetDepth(query *DepthQuery) (*MarketDepth, error) { +func (c *client) GetDepth(query *types.DepthQuery) (*types.MarketDepth, error) { err := query.Check() if err != nil { return nil, err @@ -53,7 +22,7 @@ func (c *client) GetDepth(query *DepthQuery) (*MarketDepth, error) { return nil, err } - var MarketDepth MarketDepth + var MarketDepth types.MarketDepth if err := json.Unmarshal(resp, &MarketDepth); err != nil { return nil, err } diff --git a/client/query/get_kline.go b/client/query/get_kline.go index 9e4e7ea5..780971e9 100644 --- a/client/query/get_kline.go +++ b/client/query/get_kline.go @@ -5,73 +5,11 @@ import ( "fmt" "github.com/binance-chain/go-sdk/common" + "github.com/binance-chain/go-sdk/common/types" ) -// KlineQuery def -type KlineQuery struct { - Symbol string `json:"symbol"` // required - Interval string `json:"interval"` // required, interval (5m, 1h, 1d, 1w, etc.) - Limit *uint32 `json:"limit,omitempty,string"` - StartTime *int64 `json:"start_time,omitempty,string"` - EndTime *int64 `json:"end_time,omitempty,string"` -} - -func NewKlineQuery(baseAssetSymbol, quoteAssetSymbol, interval string) *KlineQuery { - return &KlineQuery{Symbol: common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol), Interval: interval} -} - -func (param *KlineQuery) WithStartTime(start int64) *KlineQuery { - param.StartTime = &start - return param -} - -func (param *KlineQuery) WithEndTime(end int64) *KlineQuery { - param.EndTime = &end - return param -} - -func (param *KlineQuery) WithLimit(limit uint32) *KlineQuery { - param.Limit = &limit - return param -} - -func (param *KlineQuery) Check() error { - if param.Symbol == "" { - return SymbolMissingError - } - if param.Interval == "" { - return IntervalMissingError - } - if param.Limit != nil && *param.Limit <= 0 { - return LimitOutOfRangeError - } - if param.StartTime != nil && *param.StartTime <= 0 { - return StartTimeOutOfRangeError - } - if param.EndTime != nil && *param.EndTime <= 0 { - return EndTimeOutOfRangeError - } - if param.StartTime != nil && param.EndTime != nil && *param.StartTime > *param.EndTime { - return EndTimeLessThanStartTimeError - } - return nil -} - -// Kline def -type Kline struct { - Close float64 `json:"close,string"` - CloseTime int64 `json:"closeTime"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - NumberOfTrades int32 `json:"numberOfTrades"` - Open float64 `json:"open,string"` - OpenTime int64 `json:"openTime"` - QuoteAssetVolume float64 `json:"quoteAssetVolume,string"` - Volume float64 `json:"volume,string"` -} - // GetKlines returns transaction details -func (c *client) GetKlines(query *KlineQuery) ([]Kline, error) { +func (c *client) GetKlines(query *types.KlineQuery) ([]types.Kline, error) { err := query.Check() if err != nil { return nil, err @@ -90,10 +28,10 @@ func (c *client) GetKlines(query *KlineQuery) ([]Kline, error) { if err := json.Unmarshal(resp, &iklines); err != nil { return nil, err } - klines := make([]Kline, len(iklines)) + klines := make([]types.Kline, len(iklines)) // Todo for index, ikline := range iklines { - kl := Kline{} + kl := types.Kline{} imap := make(map[string]interface{}, 9) if len(ikline) >= 9 { imap["openTime"] = ikline[0] diff --git a/client/query/get_markets.go b/client/query/get_markets.go index 5073b99b..c472a74d 100644 --- a/client/query/get_markets.go +++ b/client/query/get_markets.go @@ -3,39 +3,12 @@ package query import ( "encoding/json" - "github.com/binance-chain/go-sdk/common/types" - "github.com/binance-chain/go-sdk/common" + "github.com/binance-chain/go-sdk/common/types" ) -type MarketsQuery struct { - Offset *uint32 `json:"offset,omitempty,string"` //Option - Limit *uint32 `json:"limit,omitempty,string"` //Option -} - -func NewMarketsQuery() *MarketsQuery { - return &MarketsQuery{} -} - -func (param *MarketsQuery) WithOffset(offset uint32) *MarketsQuery { - param.Offset = &offset - return param -} - -func (param *MarketsQuery) WithLimit(limit uint32) *MarketsQuery { - param.Limit = &limit - return param -} - -func (param *MarketsQuery) Check() error { - if param.Limit != nil && *param.Limit <= 0 { - return LimitOutOfRangeError - } - return nil -} - // GetMarkets returns list of trading pairs -func (c *client) GetMarkets(query *MarketsQuery) ([]types.TradingPair, error) { +func (c *client) GetMarkets(query *types.MarketsQuery) ([]types.TradingPair, error) { err := query.Check() if err != nil { return nil, err diff --git a/client/query/get_node_info.go b/client/query/get_node_info.go index c813b34d..711fec95 100644 --- a/client/query/get_node_info.go +++ b/client/query/get_node_info.go @@ -2,64 +2,16 @@ package query import ( "encoding/json" - "time" - - cmn "github.com/tendermint/tendermint/libs/common" + "github.com/binance-chain/go-sdk/common/types" ) -// Account definition -type ResultStatus struct { - NodeInfo NodeInfo `json:"node_info"` - SyncInfo SyncInfo `json:"sync_info"` - ValidatorInfo ValidatorInfo `json:"validator_info"` -} - -type NodeInfo struct { - // Authenticate - // TODO: replace with NetAddress - ID string `json:"id"` // authenticated identifier - ListenAddr string `json:"listen_addr"` // accepting incoming - - // Check compatibility. - // Channels are HexBytes so easier to read as JSON - Network string `json:"network"` // network/chain ID - Version string `json:"version"` // major.minor.revision - Channels cmn.HexBytes `json:"channels"` // channels this node knows about - - // ASCIIText fields - Moniker string `json:"moniker"` // arbitrary moniker - Other NodeInfoOther `json:"other"` // other application specific data -} - -type ValidatorInfo struct { - Address cmn.HexBytes `json:"address"` - PubKey []uint8 `json:"pub_key"` - VotingPower int64 `json:"voting_power"` -} -type SyncInfo struct { - LatestBlockHash cmn.HexBytes `json:"latest_block_hash"` - LatestAppHash cmn.HexBytes `json:"latest_app_hash"` - LatestBlockHeight int64 `json:"latest_block_height"` - LatestBlockTime time.Time `json:"latest_block_time"` - CatchingUp bool `json:"catching_up"` -} - -type NodeInfoOther struct { - AminoVersion string `json:"amino_version"` - P2PVersion string `json:"p2p_version"` - ConsensusVersion string `json:"consensus_version"` - RPCVersion string `json:"rpc_version"` - TxIndex string `json:"tx_index"` - RPCAddress string `json:"rpc_address"` -} - -func (c *client) GetNodeInfo() (*ResultStatus, error) { +func (c *client) GetNodeInfo() (*types.ResultStatus, error) { qp := map[string]string{} resp, err := c.baseClient.Get("/node-info", qp) if err != nil { return nil, err } - var resultStatus ResultStatus + var resultStatus types.ResultStatus if err := json.Unmarshal(resp, &resultStatus); err != nil { return nil, err } diff --git a/client/query/get_order.go b/client/query/get_order.go index 8a58870a..745f031f 100644 --- a/client/query/get_order.go +++ b/client/query/get_order.go @@ -2,86 +2,13 @@ package query import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" ) -// OrderSide enum -var OrderSide = struct { - BUY string - SELL string -}{ - "BUY", - "SELL", -} - -// TimeInForce enum -var TimeInForce = struct { - GTC string - IOC string -}{"GTC", "IOC"} - -// OrderStatus enum -var OrderStatus = struct { - ACK string - PARTIALLY_FILLED string - IOC_NO_FILL string - FULLY_FILLED string - CANCELED string - EXPIRED string - UNKNOWN string -}{ - "ACK", - "PARTIALLY_FILLED", - "IOC_NO_FILL", - "FULLY_FILLED", - "CANCELED", - "EXPIRED", - "UNKNOWN", -} - -// OrderType enum -var OrderType = struct { - LIMIT string - MARKET string - STOP_LOSS string - STOP_LOSS_LIMIT string - TAKE_PROFIT string - TAKE_PROFIT_LIMIT string - LIMIT_MAKER string -}{ - "LIMIT", - "MARKET", - "STOP_LOSS", - "STOP_LOSS_LIMIT", - "TAKE_PROFIT", - "TAKE_PROFIT_LIMIT", - "LIMIT_MAKER", -} - -// Order def -type Order struct { - ID string `json:"orderId"` - Owner string `json:"owner"` - Symbol string `json:"symbol"` - Price string `json:"price"` - Quantity string `json:"quantity"` - CumulateQuantity string `json:"cumulateQuantity"` - Fee string `json:"fee"` - Side int `json:"side"` // BUY or SELL - Status string `json:"status"` - TimeInForce int `json:"timeInForce"` - Type int `json:"type"` - TradeId string `json:"tradeId"` - LastExecutedPrice string `json:"last_executed_price"` - LastExecutedQuantity string `json:"lastExecutedQuantity"` - TransactionHash string `json:"transactionHash"` - OrderCreateTime string `json:"orderCreateTime"` - TransactionTime string `json:"transactionTime"` -} - // GetOrder returns transaction details -func (c *client) GetOrder(orderID string) (*Order, error) { +func (c *client) GetOrder(orderID string) (*types.Order, error) { if orderID == "" { - return nil, OrderIdMissingError + return nil, types.OrderIdMissingError } qp := map[string]string{} @@ -90,7 +17,7 @@ func (c *client) GetOrder(orderID string) (*Order, error) { return nil, err } - var order Order + var order types.Order if err := json.Unmarshal(resp, &order); err != nil { return nil, err } diff --git a/client/query/get_orders_open.go b/client/query/get_orders_open.go index b5b4273e..999b42ad 100644 --- a/client/query/get_orders_open.go +++ b/client/query/get_orders_open.go @@ -4,57 +4,11 @@ import ( "encoding/json" "github.com/binance-chain/go-sdk/common" + "github.com/binance-chain/go-sdk/common/types" ) -// OpenOrdersQuery def -type OpenOrdersQuery struct { - SenderAddress string `json:"address"` // required - Symbol string `json:"symbol,omitempty"` - Offset *uint32 `json:"offset,omitempty,string"` - Limit *uint32 `json:"limit,omitempty,string"` - Total int `json:"total,string"` //0 for not required and 1 for required; default not required, return total=-1 in response -} - -func NewOpenOrdersQuery(senderAddress string, withTotal bool) *OpenOrdersQuery { - totalQuery := 0 - if withTotal { - totalQuery = 1 - } - return &OpenOrdersQuery{SenderAddress: senderAddress, Total: totalQuery} -} - -func (param *OpenOrdersQuery) WithSymbol(symbol string) *OpenOrdersQuery { - param.Symbol = symbol - return param -} - -func (param *OpenOrdersQuery) WithOffset(offset uint32) *OpenOrdersQuery { - param.Offset = &offset - return param -} - -func (param *OpenOrdersQuery) WithLimit(limit uint32) *OpenOrdersQuery { - param.Limit = &limit - return param -} - -func (param *OpenOrdersQuery) Check() error { - if param.SenderAddress == "" { - return AddressMissingError - } - if param.Limit != nil && *param.Limit <= 0 { - return LimitOutOfRangeError - } - return nil -} - -type OpenOrders struct { - Order []Order `json:"order"` - Total int `json:"total"` -} - // GetOpenOrders returns array of open orders -func (c *client) GetOpenOrders(query *OpenOrdersQuery) (*OpenOrders, error) { +func (c *client) GetOpenOrders(query *types.OpenOrdersQuery) (*types.OpenOrders, error) { err := query.Check() if err != nil { return nil, err @@ -69,7 +23,7 @@ func (c *client) GetOpenOrders(query *OpenOrdersQuery) (*OpenOrders, error) { return nil, err } - var openOrders OpenOrders + var openOrders types.OpenOrders if err := json.Unmarshal(resp, &openOrders); err != nil { return nil, err } diff --git a/client/query/get_ticker24h.go b/client/query/get_ticker24h.go index 7411cece..ef71c050 100644 --- a/client/query/get_ticker24h.go +++ b/client/query/get_ticker24h.go @@ -2,51 +2,13 @@ package query import ( "encoding/json" + "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/common" ) -type Ticker24hQuery struct { - Symbol string `json:"symbol,omitempty"` -} - -func NewTicker24hQuery() *Ticker24hQuery { - return &Ticker24hQuery{} -} - -func (param *Ticker24hQuery) WithSymbol(baseAssetSymbol, quoteAssetSymbol string) *Ticker24hQuery { - param.Symbol = common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol) - return param -} - -// Ticker24h def -type Ticker24h struct { - Symbol string `json:"symbol"` - AskPrice string `json:"askPrice"` // In decimal form, e.g. 1.00000000 - AskQuantity string `json:"askQuantity"` // In decimal form, e.g. 1.00000000 - BidPrice string `json:"bidPrice"` // In decimal form, e.g. 1.00000000 - BidQuantity string `json:"bidQuantity"` // In decimal form, e.g. 1.00000000 - CloseTime int64 `json:"closeTime"` - Count int64 `json:"count"` - FirstID string `json:"firstId"` - HighPrice string `json:"highPrice"` // In decimal form, e.g. 1.00000000 - LastID string `json:"lastId"` - LastPrice string `json:"lastPrice"` // In decimal form, e.g. 1.00000000 - LastQuantity string `json:"lastQuantity"` // In decimal form, e.g. 1.00000000 - LowPrice string `json:"lowPrice"` // In decimal form, e.g. 1.00000000 - OpenPrice string `json:"openPrice"` // In decimal form, e.g. 1.00000000 - OpenTime int64 `json:"openTime"` - PrevClosePrice string `json:"prevClosePrice"` // In decimal form, e.g. 1.00000000 - PriceChange string `json:"priceChange"` // In decimal form, e.g. 1.00000000 - PriceChangePercent string `json:"priceChangePercent"` - QuoteVolume string `json:"quoteVolume"` // In decimal form, e.g. 1.00000000 - Volume string `json:"volume"` // In decimal form, e.g. 1.00000000 - WeightedAvgPrice string `json:"weightedAvgPrice"` // In decimal form, e.g. 1.00000000 -} - // GetTicker24h returns ticker 24h -func (c *client) GetTicker24h(query *Ticker24hQuery) ([]Ticker24h, error) { - +func (c *client) GetTicker24h(query *types.Ticker24hQuery) ([]types.Ticker24h, error) { qp, err := common.QueryParamToMap(query) if err != nil { return nil, err @@ -57,7 +19,7 @@ func (c *client) GetTicker24h(query *Ticker24hQuery) ([]Ticker24h, error) { return nil, err } - tickers := []Ticker24h{} + tickers := []types.Ticker24h{} if err := json.Unmarshal(resp, &tickers); err != nil { return nil, err } diff --git a/client/query/get_time.go b/client/query/get_time.go index c93b56b8..deae809a 100644 --- a/client/query/get_time.go +++ b/client/query/get_time.go @@ -2,22 +2,19 @@ package query import ( "encoding/json" -) -type Time struct { - ApTime string `json:"ap_time"` - BlockTime string `json:"block_time"` -} + "github.com/binance-chain/go-sdk/common/types" +) // GetTime returns market depth records -func (c *client) GetTime() (*Time, error) { +func (c *client) GetTime() (*types.Time, error) { qp := map[string]string{} resp, err := c.baseClient.Get("/time", qp) if err != nil { return nil, err } - var t Time + var t types.Time if err := json.Unmarshal(resp, &t); err != nil { return nil, err } diff --git a/client/query/get_trades.go b/client/query/get_trades.go index ce19fa5a..7d9fbf4e 100644 --- a/client/query/get_trades.go +++ b/client/query/get_trades.go @@ -4,40 +4,11 @@ import ( "encoding/json" "github.com/binance-chain/go-sdk/common" + "github.com/binance-chain/go-sdk/common/types" ) -// TradesQuery def -type TradesQuery = ClosedOrdersQuery - -type Trades struct { - Trade []Trade `json:"trade"` - Total int `json:"total"` -} - -func NewTradesQuery(senderAddres string, withTotal bool) *TradesQuery { - return NewClosedOrdersQuery(senderAddres, withTotal) -} - -// Trade def -type Trade struct { - BuyerOrderID string `json:"buyerOrderId"` - BuyFee string `json:"buyFee"` - BuyerId string `json:"buyerId"` - Price string `json:"price"` - Quantity string `json:"quantity"` - SellFee string `json:"sellFee"` - SellerId string `json:"sellerId"` - SellerOrderID string `json:"sellerOrderId"` - Symbol string `json:"symbol"` - Time int64 `json:"time"` - TradeID string `json:"tradeId"` - BlockHeight int64 `json:"blockHeight"` - BaseAsset string `json:"baseAsset"` - QuoteAsset string `json:"quoteAsset"` -} - // GetTrades returns transaction details -func (c *client) GetTrades(query *TradesQuery) (*Trades, error) { +func (c *client) GetTrades(query *types.TradesQuery) (*types.Trades, error) { err := query.Check() if err != nil { return nil, err @@ -52,7 +23,7 @@ func (c *client) GetTrades(query *TradesQuery) (*Trades, error) { return nil, err } - var trades Trades + var trades types.Trades if err := json.Unmarshal(resp, &trades); err != nil { return nil, err } diff --git a/client/query/query.go b/client/query/query.go index 6588849d..6f86a037 100644 --- a/client/query/query.go +++ b/client/query/query.go @@ -6,18 +6,18 @@ import ( ) type QueryClient interface { - GetClosedOrders(query *ClosedOrdersQuery) (*CloseOrders, error) - GetDepth(query *DepthQuery) (*MarketDepth, error) - GetKlines(query *KlineQuery) ([]Kline, error) - GetMarkets(query *MarketsQuery) ([]types.TradingPair, error) - GetOrder(orderID string) (*Order, error) - GetOpenOrders(query *OpenOrdersQuery) (*OpenOrders, error) - GetTicker24h(query *Ticker24hQuery) ([]Ticker24h, error) - GetTrades(query *TradesQuery) (*Trades, error) - GetAccount(string) (*Account, error) - GetTime() (*Time, error) + GetClosedOrders(query *types.ClosedOrdersQuery) (*types.CloseOrders, error) + GetDepth(query *types.DepthQuery) (*types.MarketDepth, error) + GetKlines(query *types.KlineQuery) ([]types.Kline, error) + GetMarkets(query *types.MarketsQuery) ([]types.TradingPair, error) + GetOrder(orderID string) (*types.Order, error) + GetOpenOrders(query *types.OpenOrdersQuery) (*types.OpenOrders, error) + GetTicker24h(query *types.Ticker24hQuery) ([]types.Ticker24h, error) + GetTrades(query *types.TradesQuery) (*types.Trades, error) + GetAccount(string) (*types.BalanceAccount, error) + GetTime() (*types.Time, error) GetTokens() ([]types.Token, error) - GetNodeInfo() (*ResultStatus, error) + GetNodeInfo() (*types.ResultStatus, error) } type client struct { diff --git a/client/rpc/basic_client.go b/client/rpc/basic_client.go index ed8fdcbb..e807f9a0 100644 --- a/client/rpc/basic_client.go +++ b/client/rpc/basic_client.go @@ -7,7 +7,6 @@ import ( "github.com/pkg/errors" cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/rpc/lib/client" @@ -68,10 +67,6 @@ func (c *HTTP) Status() (*ctypes.ResultStatus, error) { return c.WSEvents.Status() } -func (c *HTTP) NodeInfo() (*p2p.DefaultNodeInfo, error) { - return c.WSEvents.NodeInfo() -} - func (c *HTTP) ABCIInfo() (*ctypes.ResultABCIInfo, error) { return c.WSEvents.ABCIInfo() } @@ -178,13 +173,16 @@ func (c *HTTP) Tx(hash []byte, prove bool) (*ctypes.ResultTx, error) { } func (c *HTTP) TxSearch(query string, prove bool, page, perPage int) (*ctypes.ResultTxSearch, error) { - if err := ValidateCommonStr(query); err != nil { + if err := ValidateABCIQueryStr(query); err != nil { return nil, err } return c.WSEvents.TxSearch(query, prove, page, perPage) } func (c *HTTP) Validators(height *int64) (*ctypes.ResultValidators, error) { + if err := ValidateHeight(height); err != nil { + return nil, err + } return c.WSEvents.Validators(height) } diff --git a/client/rpc/dex_client.go b/client/rpc/dex_client.go index 034e0bb6..f1ce0203 100644 --- a/client/rpc/dex_client.go +++ b/client/rpc/dex_client.go @@ -29,13 +29,19 @@ type DexClient interface { } func (c *HTTP) TxInfoSearch(query string, prove bool, page, perPage int) ([]tx.Info, error) { - if err := ValidateCommonStr(query); err != nil { + if err := ValidateTxSearchQueryStr(query); err != nil { return nil, err } return c.WSEvents.TxInfoSearch(query, prove, page, perPage) } func (c *HTTP) ListAllTokens(offset int, limit int) ([]types.Token, error) { + if err := ValidateOffset(offset); err != nil { + return nil, err + } + if err := ValidateLimit(limit); err != nil { + return nil, err + } path := fmt.Sprintf("tokens/list/%d/%d", offset, limit) result, err := c.ABCIQuery(path, nil) if err != nil { @@ -48,6 +54,9 @@ func (c *HTTP) ListAllTokens(offset int, limit int) ([]types.Token, error) { } func (c *HTTP) GetTokenInfo(symbol string) (*types.Token, error) { + if err := ValidateSymbol(symbol); err != nil { + return nil, err + } path := fmt.Sprintf("tokens/info/%s", symbol) result, err := c.ABCIQuery(path, nil) if err != nil { @@ -115,6 +124,9 @@ func (c *HTTP) GetBalances(addr types.AccAddress) ([]types.TokenBalance, error) } func (c *HTTP) GetBalance(addr types.AccAddress, symbol string) (*types.TokenBalance, error) { + if err := ValidateSymbol(symbol); err != nil { + return nil, err + } exist := c.existsCC(symbol) if !exist { return nil, errors.New("symbol not found") @@ -148,6 +160,9 @@ func (c *HTTP) GetFee() ([]types.FeeParam, error) { } func (c *HTTP) GetOpenOrders(addr types.AccAddress, pair string) ([]types.OpenOrder, error) { + if err := ValidatePair(pair); err != nil { + return nil, err + } rawOrders, err := c.ABCIQuery(fmt.Sprintf("dex/openorders/%s/%s", pair, addr), nil) if err != nil { return nil, err @@ -165,6 +180,12 @@ func (c *HTTP) GetOpenOrders(addr types.AccAddress, pair string) ([]types.OpenOr } func (c *HTTP) GetTradingPairs(offset int, limit int) ([]types.TradingPair, error) { + if err := ValidateLimit(limit); err != nil { + return nil, err + } + if err := ValidateOffset(offset); err != nil { + return nil, err + } rawTradePairs, err := c.ABCIQuery(fmt.Sprintf("dex/pairs/%d/%d", offset, limit), nil) if err != nil { return nil, err @@ -178,6 +199,9 @@ func (c *HTTP) GetTradingPairs(offset int, limit int) ([]types.TradingPair, erro } func (c *HTTP) GetDepth(tradePair string) (*types.OrderBook, error) { + if err := ValidatePair(tradePair); err != nil { + return nil, err + } rawDepth, err := c.ABCIQuery(fmt.Sprintf("dex/orderbook/%s", tradePair), nil) if err != nil { return nil, err diff --git a/client/rpc/mock/client.go b/client/rpc/mock/client.go deleted file mode 100644 index 6cd6c3c0..00000000 --- a/client/rpc/mock/client.go +++ /dev/null @@ -1,139 +0,0 @@ -package mock - -/* -package mock returns a Client implementation that -accepts various (mock) implementations of the various methods. - -This implementation is useful for using in tests, when you don't -need a real server, but want a high-level of control about -the server response you want to mock (eg. error handling), -or if you just want to record the calls to verify in your tests. - -For real clients, you probably want the "http" package. If you -want to directly call a tendermint node in process, you can use the -"local" package. -*/ - -import ( - "github.com/binance-chain/go-sdk/client/rpc" - "reflect" - - cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/rpc/client" - "github.com/tendermint/tendermint/rpc/core" - ctypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/tendermint/tendermint/types" -) - -// Client wraps arbitrary implementations of the various interfaces. -// -// We provide a few choices to mock out each one in this package. -// Nothing hidden here, so no New function, just construct it from -// some parts, and swap them out them during the tests. -type Client struct { - cmn.Service - client.ABCIClient - client.SignClient - client.HistoryClient - client.StatusClient - rpc.EventsClient - rpc.DexClient - rpc.OpsClient -} - -var _ client.Client = Client{} - -// Call is used by recorders to save a call and response. -// It can also be used to configure mock responses. -// -type Call struct { - Name string - Args interface{} - Response interface{} - Error error -} - -// GetResponse will generate the apporiate response for us, when -// using the Call struct to configure a Mock handler. -// -// When configuring a response, if only one of Response or Error is -// set then that will always be returned. If both are set, then -// we return Response if the Args match the set args, Error otherwise. -func (c Call) GetResponse(args interface{}) (interface{}, error) { - // handle the case with no response - if c.Response == nil { - if c.Error == nil { - panic("Misconfigured call, you must set either Response or Error") - } - return nil, c.Error - } - // response without error - if c.Error == nil { - return c.Response, nil - } - // have both, we must check args.... - if reflect.DeepEqual(args, c.Args) { - return c.Response, nil - } - return nil, c.Error -} - -func (c Client) Status() (*ctypes.ResultStatus, error) { - return core.Status() -} - -func (c Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) { - return core.ABCIInfo() -} - -func (c Client) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { - return c.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) -} - -func (c Client) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { - return core.ABCIQuery(path, data, opts.Height, opts.Prove) -} - -func (c Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { - return core.BroadcastTxCommit(tx) -} - -func (c Client) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return core.BroadcastTxAsync(tx) -} - -func (c Client) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { - return core.BroadcastTxSync(tx) -} - -func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { - return core.NetInfo() -} - -func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(seeds) -} - -func (c Client) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { - return core.UnsafeDialPeers(peers, persistent) -} - -func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { - return core.BlockchainInfo(minHeight, maxHeight) -} - -func (c Client) Genesis() (*ctypes.ResultGenesis, error) { - return core.Genesis() -} - -func (c Client) Block(height *int64) (*ctypes.ResultBlock, error) { - return core.Block(height) -} - -func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) { - return core.Commit(height) -} - -func (c Client) Validators(height *int64) (*ctypes.ResultValidators, error) { - return core.Validators(height) -} diff --git a/client/rpc/validate.go b/client/rpc/validate.go index 97faef7e..4bcb57eb 100644 --- a/client/rpc/validate.go +++ b/client/rpc/validate.go @@ -3,29 +3,37 @@ package rpc import ( "crypto/sha256" "fmt" - "github.com/pkg/errors" "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/types" + "strings" ) const ( maxABCIPathLength = 1024 maxABCIDataLength = 1024 * 1024 maxTxLength = 1024 * 1024 - maxCommonStringLength = 1024 + maxABCIQueryStrLength = 1024 + maxTxSearchStrLength = 1024 maxUnConfirmedTxs = 100 + + tokenSymbolMaxLen = 11 + tokenSymbolMinLen = 3 ) var ( - ExceedABCIPathLengthError = fmt.Errorf("the abci path exceed max length %d ", maxABCIPathLength) - ExceedABCIDataLengthError = fmt.Errorf("the abci data exceed max length %d ", maxABCIDataLength) - ExceedTxLengthError = fmt.Errorf("the tx data exceed max length %d ", maxTxLength) - LimitNegativeError = fmt.Errorf("the limit can't be negative") - ExceedMaxUnConfirmedTxsNumError = fmt.Errorf("the limit of unConfirmed tx exceed max limit %d ", maxUnConfirmedTxs) - HeightNegativeError = fmt.Errorf("the height can't be negative") - MaxMinHeightConflictError = fmt.Errorf("the min height can't be larger than max height") - HashLengthError = fmt.Errorf("the length of hash is not 32") - ExceedCommonStrLengthError = fmt.Errorf("the query string exceed max length %d ", maxABCIPathLength) + ExceedABCIPathLengthError = fmt.Errorf("the abci path exceed max length %d ", maxABCIPathLength) + ExceedABCIDataLengthError = fmt.Errorf("the abci data exceed max length %d ", maxABCIDataLength) + ExceedTxLengthError = fmt.Errorf("the tx data exceed max length %d ", maxTxLength) + LimitNegativeError = fmt.Errorf("the limit can't be negative") + ExceedMaxUnConfirmedTxsNumError = fmt.Errorf("the limit of unConfirmed tx exceed max limit %d ", maxUnConfirmedTxs) + HeightNegativeError = fmt.Errorf("the height can't be negative") + MaxMinHeightConflictError = fmt.Errorf("the min height can't be larger than max height") + HashLengthError = fmt.Errorf("the length of hash is not 32") + ExceedABCIQueryStrLengthError = fmt.Errorf("the query string exceed max length %d ", maxABCIPathLength) + ExceedTxSearchQueryStrLengthError = fmt.Errorf("the query string exceed max length %d ", maxTxSearchStrLength) + OffsetNegativeError = fmt.Errorf("offset can't be less than 0") + SymbolLengthExceedRangeError = fmt.Errorf("length of symbol should be in range [%d,%d]", tokenSymbolMinLen, tokenSymbolMaxLen) + PairFormatError = fmt.Errorf("the pair should in format 'symbol1_symbol2'") ) func ValidateABCIPath(path string) error { @@ -62,7 +70,7 @@ func ValidateHeightRange(from int64, to int64) error { if from < 0 || to < 0 { return HeightNegativeError } - if from < to { + if from > to { return MaxMinHeightConflictError } return nil @@ -82,9 +90,51 @@ func ValidateHash(hash []byte) error { return nil } -func ValidateCommonStr(query string) error { - if len(query) > maxCommonStringLength { - return ExceedCommonStrLengthError +func ValidateABCIQueryStr(query string) error { + if len(query) > maxABCIQueryStrLength { + return ExceedABCIQueryStrLengthError + } + return nil +} + +func ValidateTxSearchQueryStr(query string) error { + if len(query) > maxTxSearchStrLength { + return ExceedTxSearchQueryStrLengthError + } + return nil +} + +func ValidateOffset(offset int) error { + if offset < 0 { + return OffsetNegativeError + } + return nil +} + +func ValidateLimit(limit int) error { + if limit < 0 { + return LimitNegativeError + } + return nil +} + +func ValidateSymbol(symbol string) error { + if len(symbol) > tokenSymbolMaxLen || len(symbol) < tokenSymbolMinLen { + return SymbolLengthExceedRangeError + } + return nil +} + +func ValidatePair(pair string) error { + symbols := strings.Split(pair, "_") + if len(symbols) != 2 { + return PairFormatError + } + if err := ValidateSymbol(symbols[0]); err != nil { + return err + } + if err := ValidateSymbol(symbols[1]); err != nil { + return err } return nil } diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index a4753189..f676ccef 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -2,7 +2,6 @@ package rpc import ( "context" - "encoding/hex" "encoding/json" "net" "net/http" @@ -17,7 +16,6 @@ import ( "github.com/tendermint/go-amino" cmn "github.com/tendermint/tendermint/libs/common" - "github.com/tendermint/tendermint/p2p" "github.com/tendermint/tendermint/rpc/client" ctypes "github.com/tendermint/tendermint/rpc/core/types" "github.com/tendermint/tendermint/rpc/lib/types" @@ -259,14 +257,6 @@ func (w *WSEvents) Status() (*ctypes.ResultStatus, error) { return status, err } -func (w *WSEvents) NodeInfo() (*p2p.DefaultNodeInfo, error) { - status, err := w.Status() - if err != nil { - return nil, err - } - return &status.NodeInfo, nil -} - func (w *WSEvents) ABCIInfo() (*ctypes.ResultABCIInfo, error) { info := new(ctypes.ResultABCIInfo) err := w.SimpleCall(w.ws.ABCIInfo, info) @@ -1043,7 +1033,6 @@ func formatTxResult(cdc *amino.Codec, res *ctypes.ResultTx) (tx.Info, error) { func parseTx(cdc *amino.Codec, txBytes []byte) (tx.Tx, error) { var parsedTx tx.StdTx - fmt.Println(hex.EncodeToString(txBytes)) err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &parsedTx) if err != nil { diff --git a/common/types/bank.go b/common/types/account.go similarity index 57% rename from common/types/bank.go rename to common/types/account.go index 41ed7fba..67627c9d 100644 --- a/common/types/bank.go +++ b/common/types/account.go @@ -1,22 +1,10 @@ package types import ( - "strings" - "github.com/pkg/errors" "github.com/tendermint/tendermint/crypto" ) -// Token definition -type Token struct { - Name string `json:"name"` - Symbol string `json:"symbol"` - OrigSymbol string `json:"original_symbol"` - TotalSupply Fixed8 `json:"total_supply"` - Owner AccAddress `json:"owner"` - Mintable bool `json:"mintable"` -} - // AppAccount definition type AppAccount struct { BaseAccount `json:"base"` @@ -25,153 +13,6 @@ type AppAccount struct { LockedCoins Coins `json:"locked"` } -// Coin def -// Coin def -type Coin struct { - Denom string `json:"denom"` - Amount int64 `json:"amount"` -} - -func (coin Coin) IsZero() bool { - return coin.Amount == 0 -} - -func (coin Coin) IsPositive() bool { - return coin.Amount > 0 -} - -func (coin Coin) IsNotNegative() bool { - return coin.Amount >= 0 -} - -func (coin Coin) SameDenomAs(other Coin) bool { - return (coin.Denom == other.Denom) -} - -func (coin Coin) Plus(coinB Coin) Coin { - if !coin.SameDenomAs(coinB) { - return coin - } - return Coin{coin.Denom, coin.Amount + coinB.Amount} -} - -// Coins def -type Coins []Coin - -func (coins Coins) IsValid() bool { - switch len(coins) { - case 0: - return true - case 1: - return !coins[0].IsZero() - default: - lowDenom := coins[0].Denom - for _, coin := range coins[1:] { - if coin.Denom <= lowDenom { - return false - } - if coin.IsZero() { - return false - } - lowDenom = coin.Denom - } - return true - } -} - -func (coins Coins) IsPositive() bool { - if len(coins) == 0 { - return false - } - for _, coin := range coins { - if !coin.IsPositive() { - return false - } - } - return true -} - -func (coins Coins) Plus(coinsB Coins) Coins { - sum := ([]Coin)(nil) - indexA, indexB := 0, 0 - lenA, lenB := len(coins), len(coinsB) - for { - if indexA == lenA { - if indexB == lenB { - return sum - } - return append(sum, coinsB[indexB:]...) - } else if indexB == lenB { - return append(sum, coins[indexA:]...) - } - coinA, coinB := coins[indexA], coinsB[indexB] - switch strings.Compare(coinA.Denom, coinB.Denom) { - case -1: - sum = append(sum, coinA) - indexA++ - case 0: - if coinA.Amount+coinB.Amount == 0 { - // ignore 0 sum coin type - } else { - sum = append(sum, coinA.Plus(coinB)) - } - indexA++ - indexB++ - case 1: - sum = append(sum, coinB) - indexB++ - } - } -} - -// IsEqual returns true if the two sets of Coins have the same value -func (coins Coins) IsEqual(coinsB Coins) bool { - if len(coins) != len(coinsB) { - return false - } - for i := 0; i < len(coins); i++ { - if coins[i].Denom != coinsB[i].Denom || !(coins[i].Amount == coinsB[i].Amount) { - return false - } - } - return true -} - -func (coins Coins) IsNotNegative() bool { - if len(coins) == 0 { - return true - } - for _, coin := range coins { - if !coin.IsNotNegative() { - return false - } - } - return true -} - -func (coins Coins) AmountOf(denom string) int64 { - switch len(coins) { - case 0: - return 0 - case 1: - coin := coins[0] - if coin.Denom == denom { - return coin.Amount - } - return 0 - default: - midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 - coin := coins[midIdx] - if denom < coin.Denom { - return coins[:midIdx].AmountOf(denom) - } else if denom == coin.Denom { - return coin.Amount - } else { - return coins[midIdx+1:].AmountOf(denom) - } - } -} - type Account interface { GetAddress() AccAddress SetAddress(address AccAddress) error // errors if already set. @@ -341,9 +182,11 @@ func (acc *BaseAccount) Clone() Account { return clonedAcc } -type TokenBalance struct { - Symbol string `json:"symbol"` - Free Fixed8 `json:"free"` - Locked Fixed8 `json:"locked"` - Frozen Fixed8 `json:"frozen"` +// Balance Account definition +type BalanceAccount struct { + Number int64 `json:"account_number"` + Address string `json:"address"` + Balances []TokenBalance `json:"balances"` + PublicKey []uint8 `json:"public_key"` + Sequence int64 `json:"sequence"` } diff --git a/common/types/coins.go b/common/types/coins.go new file mode 100644 index 00000000..f4007c8b --- /dev/null +++ b/common/types/coins.go @@ -0,0 +1,149 @@ +package types + +import "strings" + +// Coin def +type Coin struct { + Denom string `json:"denom"` + Amount int64 `json:"amount"` +} + +func (coin Coin) IsZero() bool { + return coin.Amount == 0 +} + +func (coin Coin) IsPositive() bool { + return coin.Amount > 0 +} + +func (coin Coin) IsNotNegative() bool { + return coin.Amount >= 0 +} + +func (coin Coin) SameDenomAs(other Coin) bool { + return (coin.Denom == other.Denom) +} + +func (coin Coin) Plus(coinB Coin) Coin { + if !coin.SameDenomAs(coinB) { + return coin + } + return Coin{coin.Denom, coin.Amount + coinB.Amount} +} + +// Coins def +type Coins []Coin + +func (coins Coins) IsValid() bool { + switch len(coins) { + case 0: + return true + case 1: + return !coins[0].IsZero() + default: + lowDenom := coins[0].Denom + for _, coin := range coins[1:] { + if coin.Denom <= lowDenom { + return false + } + if coin.IsZero() { + return false + } + lowDenom = coin.Denom + } + return true + } +} + +func (coins Coins) IsPositive() bool { + if len(coins) == 0 { + return false + } + for _, coin := range coins { + if !coin.IsPositive() { + return false + } + } + return true +} + +func (coins Coins) Plus(coinsB Coins) Coins { + sum := ([]Coin)(nil) + indexA, indexB := 0, 0 + lenA, lenB := len(coins), len(coinsB) + for { + if indexA == lenA { + if indexB == lenB { + return sum + } + return append(sum, coinsB[indexB:]...) + } else if indexB == lenB { + return append(sum, coins[indexA:]...) + } + coinA, coinB := coins[indexA], coinsB[indexB] + switch strings.Compare(coinA.Denom, coinB.Denom) { + case -1: + sum = append(sum, coinA) + indexA++ + case 0: + if coinA.Amount+coinB.Amount == 0 { + // ignore 0 sum coin type + } else { + sum = append(sum, coinA.Plus(coinB)) + } + indexA++ + indexB++ + case 1: + sum = append(sum, coinB) + indexB++ + } + } +} + +// IsEqual returns true if the two sets of Coins have the same value +func (coins Coins) IsEqual(coinsB Coins) bool { + if len(coins) != len(coinsB) { + return false + } + for i := 0; i < len(coins); i++ { + if coins[i].Denom != coinsB[i].Denom || !(coins[i].Amount == coinsB[i].Amount) { + return false + } + } + return true +} + +func (coins Coins) IsNotNegative() bool { + if len(coins) == 0 { + return true + } + for _, coin := range coins { + if !coin.IsNotNegative() { + return false + } + } + return true +} + +func (coins Coins) AmountOf(denom string) int64 { + switch len(coins) { + case 0: + return 0 + case 1: + coin := coins[0] + if coin.Denom == denom { + return coin.Amount + } + return 0 + default: + midIdx := len(coins) / 2 // 2:1, 3:1, 4:2 + coin := coins[midIdx] + if denom < coin.Denom { + return coins[:midIdx].AmountOf(denom) + } else if denom == coin.Denom { + return coin.Amount + } else { + return coins[midIdx+1:].AmountOf(denom) + } + } +} diff --git a/common/types/depth.go b/common/types/depth.go new file mode 100644 index 00000000..c845c3fe --- /dev/null +++ b/common/types/depth.go @@ -0,0 +1,7 @@ +package types + +type MarketDepth struct { + Bids [][]string `json:"bids"` // "bids": [ [ "0.0024", "10" ] ] + Asks [][]string `json:"asks"` // "asks": [ [ "0.0024", "10" ] ] + Height int64 `json:"height"` +} diff --git a/common/types/kline.go b/common/types/kline.go new file mode 100644 index 00000000..5e460694 --- /dev/null +++ b/common/types/kline.go @@ -0,0 +1,13 @@ +package types + +type Kline struct { + Close float64 `json:"close,string"` + CloseTime int64 `json:"closeTime"` + High float64 `json:"high,string"` + Low float64 `json:"low,string"` + NumberOfTrades int32 `json:"numberOfTrades"` + Open float64 `json:"open,string"` + OpenTime int64 `json:"openTime"` + QuoteAssetVolume float64 `json:"quoteAssetVolume,string"` + Volume float64 `json:"volume,string"` +} diff --git a/common/types/nodeinfo.go b/common/types/nodeinfo.go new file mode 100644 index 00000000..9ad675ee --- /dev/null +++ b/common/types/nodeinfo.go @@ -0,0 +1,53 @@ +package types + +import ( + "time" + + "github.com/tendermint/tendermint/libs/common" +) + +// Account definition +type ResultStatus struct { + NodeInfo NodeInfo `json:"node_info"` + SyncInfo SyncInfo `json:"sync_info"` + ValidatorInfo ValidatorInfo `json:"validator_info"` +} + +type NodeInfo struct { + // Authenticate + // TODO: replace with NetAddress + ID string `json:"id"` // authenticated identifier + ListenAddr string `json:"listen_addr"` // accepting incoming + + // Check compatibility. + // Channels are HexBytes so easier to read as JSON + Network string `json:"network"` // network/chain ID + Version string `json:"version"` // major.minor.revision + Channels common.HexBytes `json:"channels"` // channels this node knows about + + // ASCIIText fields + Moniker string `json:"moniker"` // arbitrary moniker + Other NodeInfoOther `json:"other"` // other application specific data +} + +type ValidatorInfo struct { + Address common.HexBytes `json:"address"` + PubKey []uint8 `json:"pub_key"` + VotingPower int64 `json:"voting_power"` +} +type SyncInfo struct { + LatestBlockHash common.HexBytes `json:"latest_block_hash"` + LatestAppHash common.HexBytes `json:"latest_app_hash"` + LatestBlockHeight int64 `json:"latest_block_height"` + LatestBlockTime time.Time `json:"latest_block_time"` + CatchingUp bool `json:"catching_up"` +} + +type NodeInfoOther struct { + AminoVersion string `json:"amino_version"` + P2PVersion string `json:"p2p_version"` + ConsensusVersion string `json:"consensus_version"` + RPCVersion string `json:"rpc_version"` + TxIndex string `json:"tx_index"` + RPCAddress string `json:"rpc_address"` +} diff --git a/common/types/query.go b/common/types/query.go new file mode 100644 index 00000000..ebcd8dbc --- /dev/null +++ b/common/types/query.go @@ -0,0 +1,263 @@ +package types + +import ( + "errors" + + "github.com/binance-chain/go-sdk/common" +) + +const ( + SideBuy = "BUY" + SideSell = "SELL" +) + +var ( + // Param error + AddressMissingError = errors.New("Address is required ") + SymbolMissingError = errors.New("Symbol is required ") + OffsetOutOfRangeError = errors.New("offset out of range ") + LimitOutOfRangeError = errors.New("limit out of range ") + TradeSideMisMatchError = errors.New("Trade side is invalid ") + StartTimeOutOfRangeError = errors.New("start time out of range ") + EndTimeOutOfRangeError = errors.New("end time out of range ") + IntervalMissingError = errors.New("interval is required ") + EndTimeLessThanStartTimeError = errors.New("end time should great than start time") + OrderIdMissingError = errors.New("order id is required ") +) + +// ClosedOrdersQuery definition +type ClosedOrdersQuery struct { + SenderAddress string `json:"address"` // required + Symbol string `json:"symbol,omitempty"` //option + Offset *uint32 `json:"offset,omitempty,string"` //option + Limit *uint32 `json:"limit,omitempty,string"` //option + Start *int64 `json:"start,omitempty,string"` //option + End *int64 `json:"end,omitempty,string"` //option + Side string `json:"side,omitempty"` //option + Total int `json:"total,string"` //0 for not required and 1 for required; default not required, return total=-1 in response +} + +func NewClosedOrdersQuery(senderAddress string, withTotal bool) *ClosedOrdersQuery { + totalQuery := 0 + if withTotal { + totalQuery = 1 + } + return &ClosedOrdersQuery{SenderAddress: senderAddress, Total: totalQuery} +} + +func (param *ClosedOrdersQuery) Check() error { + if param.SenderAddress == "" { + return AddressMissingError + } + if param.Side != SideBuy && param.Side != SideSell && param.Side != "" { + return TradeSideMisMatchError + } + if param.Limit != nil && *param.Limit <= 0 { + return LimitOutOfRangeError + } + if param.Start != nil && *param.Start <= 0 { + return StartTimeOutOfRangeError + } + if param.End != nil && *param.End <= 0 { + return EndTimeOutOfRangeError + } + if param.Start != nil && param.End != nil && *param.Start > *param.End { + return EndTimeLessThanStartTimeError + } + return nil +} + +func (param *ClosedOrdersQuery) WithSymbol(baseAssetSymbol, quoteAssetSymbol string) *ClosedOrdersQuery { + param.Symbol = common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol) + return param +} + +func (param *ClosedOrdersQuery) WithOffset(offset uint32) *ClosedOrdersQuery { + param.Offset = &offset + return param +} + +func (param *ClosedOrdersQuery) WithLimit(limit uint32) *ClosedOrdersQuery { + param.Limit = &limit + return param +} + +func (param *ClosedOrdersQuery) WithStart(start int64) *ClosedOrdersQuery { + param.Start = &start + return param +} + +func (param *ClosedOrdersQuery) WithEnd(end int64) *ClosedOrdersQuery { + param.End = &end + return param +} + +func (param *ClosedOrdersQuery) WithSide(side string) *ClosedOrdersQuery { + param.Side = side + return param +} + +// TradesQuery definition +type TradesQuery = ClosedOrdersQuery + +func NewTradesQuery(senderAddres string, withTotal bool) *TradesQuery { + return NewClosedOrdersQuery(senderAddres, withTotal) +} + +// Ticker24hQuery definition +type Ticker24hQuery struct { + Symbol string `json:"symbol,omitempty"` +} + +func NewTicker24hQuery() *Ticker24hQuery { + return &Ticker24hQuery{} +} + +func (param *Ticker24hQuery) WithSymbol(baseAssetSymbol, quoteAssetSymbol string) *Ticker24hQuery { + param.Symbol = common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol) + return param +} + +// OpenOrdersQuery definition +type OpenOrdersQuery struct { + SenderAddress string `json:"address"` // required + Symbol string `json:"symbol,omitempty"` + Offset *uint32 `json:"offset,omitempty,string"` + Limit *uint32 `json:"limit,omitempty,string"` + Total int `json:"total,string"` //0 for not required and 1 for required; default not required, return total=-1 in response +} + +func NewOpenOrdersQuery(senderAddress string, withTotal bool) *OpenOrdersQuery { + totalQuery := 0 + if withTotal { + totalQuery = 1 + } + return &OpenOrdersQuery{SenderAddress: senderAddress, Total: totalQuery} +} + +func (param *OpenOrdersQuery) WithSymbol(symbol string) *OpenOrdersQuery { + param.Symbol = symbol + return param +} + +func (param *OpenOrdersQuery) WithOffset(offset uint32) *OpenOrdersQuery { + param.Offset = &offset + return param +} + +func (param *OpenOrdersQuery) WithLimit(limit uint32) *OpenOrdersQuery { + param.Limit = &limit + return param +} + +func (param *OpenOrdersQuery) Check() error { + if param.SenderAddress == "" { + return AddressMissingError + } + if param.Limit != nil && *param.Limit <= 0 { + return LimitOutOfRangeError + } + return nil +} + +// DepthQuery +type DepthQuery struct { + Symbol string `json:"symbol"` + Limit *uint32 `json:"limit,omitempty,string"` +} + +func NewDepthQuery(baseAssetSymbol, quoteAssetSymbol string) *DepthQuery { + return &DepthQuery{Symbol: common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol)} +} + +func (param *DepthQuery) WithLimit(limit uint32) *DepthQuery { + param.Limit = &limit + return param +} + +func (param *DepthQuery) Check() error { + if param.Symbol == "" { + return SymbolMissingError + } + if param.Limit != nil && *param.Limit <= 0 { + return LimitOutOfRangeError + } + return nil +} + +// KlineQuery definition +type KlineQuery struct { + Symbol string `json:"symbol"` // required + Interval string `json:"interval"` // required, interval (5m, 1h, 1d, 1w, etc.) + Limit *uint32 `json:"limit,omitempty,string"` + StartTime *int64 `json:"start_time,omitempty,string"` + EndTime *int64 `json:"end_time,omitempty,string"` +} + +func NewKlineQuery(baseAssetSymbol, quoteAssetSymbol, interval string) *KlineQuery { + return &KlineQuery{Symbol: common.CombineSymbol(baseAssetSymbol, quoteAssetSymbol), Interval: interval} +} + +func (param *KlineQuery) WithStartTime(start int64) *KlineQuery { + param.StartTime = &start + return param +} + +func (param *KlineQuery) WithEndTime(end int64) *KlineQuery { + param.EndTime = &end + return param +} + +func (param *KlineQuery) WithLimit(limit uint32) *KlineQuery { + param.Limit = &limit + return param +} + +func (param *KlineQuery) Check() error { + if param.Symbol == "" { + return SymbolMissingError + } + if param.Interval == "" { + return IntervalMissingError + } + if param.Limit != nil && *param.Limit <= 0 { + return LimitOutOfRangeError + } + if param.StartTime != nil && *param.StartTime <= 0 { + return StartTimeOutOfRangeError + } + if param.EndTime != nil && *param.EndTime <= 0 { + return EndTimeOutOfRangeError + } + if param.StartTime != nil && param.EndTime != nil && *param.StartTime > *param.EndTime { + return EndTimeLessThanStartTimeError + } + return nil +} + +// MarketsQuery definition +type MarketsQuery struct { + Offset *uint32 `json:"offset,omitempty,string"` //Option + Limit *uint32 `json:"limit,omitempty,string"` //Option +} + +func NewMarketsQuery() *MarketsQuery { + return &MarketsQuery{} +} + +func (param *MarketsQuery) WithOffset(offset uint32) *MarketsQuery { + param.Offset = &offset + return param +} + +func (param *MarketsQuery) WithLimit(limit uint32) *MarketsQuery { + param.Limit = &limit + return param +} + +func (param *MarketsQuery) Check() error { + if param.Limit != nil && *param.Limit <= 0 { + return LimitOutOfRangeError + } + return nil +} diff --git a/common/types/stake.go b/common/types/stake.go index 128e789d..fe86ff06 100644 --- a/common/types/stake.go +++ b/common/types/stake.go @@ -2,9 +2,7 @@ package types import ( "encoding/json" - "errors" "fmt" - "strconv" "time" "github.com/binance-chain/go-sdk/common/bech32" @@ -78,7 +76,6 @@ type UnbondingDelegation struct { Balance Coin `json:"balance"` // atoms to receive at completion } - func (va ValAddress) String() string { bech32PrefixValAddr := Network.Bech32ValidatorAddrPrefix() bech32Addr, err := bech32.ConvertAndEncode(bech32PrefixValAddr, va.Bytes()) diff --git a/common/types/ticker.go b/common/types/ticker.go new file mode 100644 index 00000000..71977488 --- /dev/null +++ b/common/types/ticker.go @@ -0,0 +1,25 @@ +package types + +type Ticker24h struct { + Symbol string `json:"symbol"` + AskPrice string `json:"askPrice"` // In decimal form, e.g. 1.00000000 + AskQuantity string `json:"askQuantity"` // In decimal form, e.g. 1.00000000 + BidPrice string `json:"bidPrice"` // In decimal form, e.g. 1.00000000 + BidQuantity string `json:"bidQuantity"` // In decimal form, e.g. 1.00000000 + CloseTime int64 `json:"closeTime"` + Count int64 `json:"count"` + FirstID string `json:"firstId"` + HighPrice string `json:"highPrice"` // In decimal form, e.g. 1.00000000 + LastID string `json:"lastId"` + LastPrice string `json:"lastPrice"` // In decimal form, e.g. 1.00000000 + LastQuantity string `json:"lastQuantity"` // In decimal form, e.g. 1.00000000 + LowPrice string `json:"lowPrice"` // In decimal form, e.g. 1.00000000 + OpenPrice string `json:"openPrice"` // In decimal form, e.g. 1.00000000 + OpenTime int64 `json:"openTime"` + PrevClosePrice string `json:"prevClosePrice"` // In decimal form, e.g. 1.00000000 + PriceChange string `json:"priceChange"` // In decimal form, e.g. 1.00000000 + PriceChangePercent string `json:"priceChangePercent"` + QuoteVolume string `json:"quoteVolume"` // In decimal form, e.g. 1.00000000 + Volume string `json:"volume"` // In decimal form, e.g. 1.00000000 + WeightedAvgPrice string `json:"weightedAvgPrice"` // In decimal form, e.g. 1.00000000 +} diff --git a/common/types/time.go b/common/types/time.go new file mode 100644 index 00000000..169b365b --- /dev/null +++ b/common/types/time.go @@ -0,0 +1,6 @@ +package types + +type Time struct { + ApTime string `json:"ap_time"` + BlockTime string `json:"block_time"` +} diff --git a/common/types/tokens.go b/common/types/tokens.go new file mode 100644 index 00000000..733a4a91 --- /dev/null +++ b/common/types/tokens.go @@ -0,0 +1,18 @@ +package types + +// Token definition +type Token struct { + Name string `json:"name"` + Symbol string `json:"symbol"` + OrigSymbol string `json:"original_symbol"` + TotalSupply Fixed8 `json:"total_supply"` + Owner AccAddress `json:"owner"` + Mintable bool `json:"mintable"` +} + +type TokenBalance struct { + Symbol string `json:"symbol"` + Free Fixed8 `json:"free"` + Locked Fixed8 `json:"locked"` + Frozen Fixed8 `json:"frozen"` +} diff --git a/common/types/trade.go b/common/types/trade.go index 44c091c5..7eff4881 100644 --- a/common/types/trade.go +++ b/common/types/trade.go @@ -1,5 +1,68 @@ package types +// OrderSide enum +var OrderSide = struct { + BUY string + SELL string +}{ + "BUY", + "SELL", +} + +// TimeInForce enum +var TimeInForce = struct { + GTC string + IOC string +}{"GTC", "IOC"} + +// OrderStatus enum +var OrderStatus = struct { + ACK string + PARTIALLY_FILLED string + IOC_NO_FILL string + FULLY_FILLED string + CANCELED string + EXPIRED string + UNKNOWN string +}{ + "ACK", + "PARTIALLY_FILLED", + "IOC_NO_FILL", + "FULLY_FILLED", + "CANCELED", + "EXPIRED", + "UNKNOWN", +} + +// OrderType enum +var OrderType = struct { + LIMIT string + MARKET string + STOP_LOSS string + STOP_LOSS_LIMIT string + TAKE_PROFIT string + TAKE_PROFIT_LIMIT string + LIMIT_MAKER string +}{ + "LIMIT", + "MARKET", + "STOP_LOSS", + "STOP_LOSS_LIMIT", + "TAKE_PROFIT", + "TAKE_PROFIT_LIMIT", + "LIMIT_MAKER", +} + +type CloseOrders struct { + Order []Order `json:"order"` + Total int `json:"total"` +} + +type OpenOrders struct { + Order []Order `json:"order"` + Total int `json:"total"` +} + type OpenOrder struct { Id string `json:"id"` Symbol string `json:"symbol"` @@ -32,3 +95,46 @@ type OrderBookLevel struct { SellQty Fixed8 `json:"sellQty"` SellPrice Fixed8 `json:"sellPrice"` } + +type Trades struct { + Trade []Trade `json:"trade"` + Total int `json:"total"` +} + +// Trade def +type Trade struct { + BuyerOrderID string `json:"buyerOrderId"` + BuyFee string `json:"buyFee"` + BuyerId string `json:"buyerId"` + Price string `json:"price"` + Quantity string `json:"quantity"` + SellFee string `json:"sellFee"` + SellerId string `json:"sellerId"` + SellerOrderID string `json:"sellerOrderId"` + Symbol string `json:"symbol"` + Time int64 `json:"time"` + TradeID string `json:"tradeId"` + BlockHeight int64 `json:"blockHeight"` + BaseAsset string `json:"baseAsset"` + QuoteAsset string `json:"quoteAsset"` +} + +type Order struct { + ID string `json:"orderId"` + Owner string `json:"owner"` + Symbol string `json:"symbol"` + Price string `json:"price"` + Quantity string `json:"quantity"` + CumulateQuantity string `json:"cumulateQuantity"` + Fee string `json:"fee"` + Side int `json:"side"` // BUY or SELL + Status string `json:"status"` + TimeInForce int `json:"timeInForce"` + Type int `json:"type"` + TradeId string `json:"tradeId"` + LastExecutedPrice string `json:"last_executed_price"` + LastExecutedQuantity string `json:"lastExecutedQuantity"` + TransactionHash string `json:"transactionHash"` + OrderCreateTime string `json:"orderCreateTime"` + TransactionTime string `json:"transactionTime"` +} diff --git a/e2e/e2e_rpc_test.go b/e2e/e2e_rpc_test.go index 942c65e0..e928d5df 100644 --- a/e2e/e2e_rpc_test.go +++ b/e2e/e2e_rpc_test.go @@ -45,14 +45,6 @@ func TestRPCStatus(t *testing.T) { fmt.Println(string(bz)) } -func TestRPCNodeInfo(t *testing.T) { - c := defaultClient() - nodeInfo, err := c.NodeInfo() - assert.NoError(t, err) - bz, err := json.Marshal(nodeInfo) - fmt.Println(string(bz)) -} - func TestRPCABCIInfo(t *testing.T) { c := defaultClient() info, err := c.ABCIInfo() @@ -160,23 +152,23 @@ func TestTx(t *testing.T) { fmt.Println(string(bz)) } -func TestReconnection(t *testing.T) { - c := defaultClient() - status, err := c.Status() - assert.NoError(t, err) - bz, err := json.Marshal(status) - fmt.Println(string(bz)) - time.Sleep(10 * time.Second) - status, err = c.Status() - assert.Error(t, err) - fmt.Println(err) - time.Sleep(10 * time.Second) - status, err = c.Status() - assert.Error(t, err) - fmt.Println(err) - bz, err = json.Marshal(status) - fmt.Println(string(bz)) -} +//func TestReconnection(t *testing.T) { +// c := defaultClient() +// status, err := c.Status() +// assert.NoError(t, err) +// bz, err := json.Marshal(status) +// fmt.Println(string(bz)) +// time.Sleep(10 * time.Second) +// status, err = c.Status() +// assert.Error(t, err) +// fmt.Println(err) +// time.Sleep(10 * time.Second) +// status, err = c.Status() +// assert.Error(t, err) +// fmt.Println(err) +// bz, err = json.Marshal(status) +// fmt.Println(string(bz)) +//} func TestTxSearch(t *testing.T) { c := defaultClient() @@ -231,7 +223,7 @@ func TestSubscribeEvent(t *testing.T) { } } }() - time.Sleep(100 * time.Second) + time.Sleep(10 * time.Second) err = c.Unsubscribe(query) noMoreEvent <- struct{}{} assert.NoError(t, err) @@ -430,4 +422,3 @@ func TestNoRequestLeakInGoodNetwork(t *testing.T) { w.Wait() assert.Equal(t, c.PendingRequest(), 0) } - diff --git a/e2e/e2e_trans_test.go b/e2e/e2e_trans_test.go index 2265c50b..41fa5453 100644 --- a/e2e/e2e_trans_test.go +++ b/e2e/e2e_trans_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/assert" sdk "github.com/binance-chain/go-sdk/client" - "github.com/binance-chain/go-sdk/client/query" "github.com/binance-chain/go-sdk/common" ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/keys" @@ -20,7 +19,7 @@ import ( func TestTransProcess(t *testing.T) { //----- Recover account --------- mnemonic := "test mnemonic" - baeUrl := "test basic url" + baeUrl := "test base url" keyManager, err := keys.NewMnemonicKeyManager(mnemonic) assert.NoError(t, err) validatorMnemonics := []string{ @@ -33,7 +32,7 @@ func TestTransProcess(t *testing.T) { testAccount3 := testKeyManager3.GetAddr() //----- Init sdk ------------- - client, err := sdk.NewDexClient(baeUrl, types.TestNetwork, keyManager) + client, err := sdk.NewDexClient(baeUrl, ctypes.TestNetwork, keyManager) assert.NoError(t, err) nativeSymbol := msg.NativeToken @@ -44,26 +43,26 @@ func TestTransProcess(t *testing.T) { assert.True(t, len(account.Balances) > 0) //----- Get Markets ------------ - markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) + markets, err := client.GetMarkets(ctypes.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) tradeSymbol := markets[0].QuoteAssetSymbol - if markets[0].QuoteAssetSymbol != "BNB" { - tradeSymbol = markets[0].QuoteAssetSymbol + if tradeSymbol == "BNB" { + tradeSymbol = markets[0].BaseAssetSymbol } //----- Get Depth ---------- - depth, err := client.GetDepth(query.NewDepthQuery(tradeSymbol, nativeSymbol)) + depth, err := client.GetDepth(ctypes.NewDepthQuery(tradeSymbol, nativeSymbol)) assert.NoError(t, err) assert.True(t, depth.Height > 0) //----- Get Kline - kline, err := client.GetKlines(query.NewKlineQuery(tradeSymbol, nativeSymbol, "1h").WithLimit(1)) + kline, err := client.GetKlines(ctypes.NewKlineQuery(tradeSymbol, nativeSymbol, "1h").WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(kline)) //----- Get Ticker 24h ----------- - ticker24h, err := client.GetTicker24h(query.NewTicker24hQuery().WithSymbol(tradeSymbol, nativeSymbol)) + ticker24h, err := client.GetTicker24h(ctypes.NewTicker24hQuery().WithSymbol(tradeSymbol, nativeSymbol)) assert.NoError(t, err) assert.True(t, len(ticker24h) > 0) @@ -74,7 +73,7 @@ func TestTransProcess(t *testing.T) { //----- Get Trades ----------- fmt.Println(testAccount1.String()) - trades, err := client.GetTrades(query.NewTradesQuery(testAccount1.String(), true).WithSymbol(tradeSymbol, nativeSymbol)) + trades, err := client.GetTrades(ctypes.NewTradesQuery(testAccount1.String(), true).WithSymbol(tradeSymbol, nativeSymbol)) assert.NoError(t, err) fmt.Printf("GetTrades: %v \n", trades) @@ -90,7 +89,7 @@ func TestTransProcess(t *testing.T) { assert.True(t, true, createOrderResult.Ok) //---- Get Open Order --------- - openOrders, err := client.GetOpenOrders(query.NewOpenOrdersQuery(testAccount1.String(), true)) + openOrders, err := client.GetOpenOrders(ctypes.NewOpenOrdersQuery(testAccount1.String(), true)) assert.NoError(t, err) assert.True(t, len(openOrders.Order) > 0) orderId := openOrders.Order[0].ID @@ -109,7 +108,7 @@ func TestTransProcess(t *testing.T) { fmt.Printf("cancleOrderResult: %v \n", cancleOrderResult) //---- Get Close Order--------- - closedOrders, err := client.GetClosedOrders(query.NewClosedOrdersQuery(testAccount1.String(), true).WithSymbol(tradeSymbol, nativeSymbol)) + closedOrders, err := client.GetClosedOrders(ctypes.NewClosedOrdersQuery(testAccount1.String(), true).WithSymbol(tradeSymbol, nativeSymbol)) assert.NoError(t, err) assert.True(t, len(closedOrders.Order) > 0) fmt.Printf("GetClosedOrders: %v \n", closedOrders) @@ -180,7 +179,7 @@ func TestTransProcess(t *testing.T) { time2.Sleep(1 * time2.Second) k, err := keys.NewMnemonicKeyManager(m) assert.NoError(t, err) - client, err := sdk.NewDexClient(baeUrl, types.TestNetwork, k) + client, err := sdk.NewDexClient(baeUrl, ctypes.TestNetwork, k) assert.NoError(t, err) vote, err := client.VoteProposal(listTradingProposal.ProposalId, msg.OptionYes, true) assert.NoError(t, err) diff --git a/e2e/e2e_ws_test.go b/e2e/e2e_ws_test.go index d714a9db..d746667d 100644 --- a/e2e/e2e_ws_test.go +++ b/e2e/e2e_ws_test.go @@ -7,7 +7,6 @@ import ( "time" sdk "github.com/binance-chain/go-sdk/client" - "github.com/binance-chain/go-sdk/client/query" "github.com/binance-chain/go-sdk/client/websocket" ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/keys" @@ -44,7 +43,7 @@ func TestSubscribeAllTickerEvent(t *testing.T) { func TestSubscribeTickerEvent(t *testing.T) { client := NewClient(t) - markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) + markets, err := client.GetMarkets(ctypes.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) tradeSymbol := markets[0].BaseAssetSymbol @@ -83,7 +82,7 @@ func TestSubscribeAllMiniTickersEvent(t *testing.T) { func TestSubscribeMiniTickersEvent(t *testing.T) { client := NewClient(t) - markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) + markets, err := client.GetMarkets(ctypes.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) tradeSymbol := markets[0].BaseAssetSymbol @@ -107,7 +106,7 @@ func TestSubscribeMiniTickersEvent(t *testing.T) { func TestSubscribeTradeEvent(t *testing.T) { client := NewClient(t) - markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) + markets, err := client.GetMarkets(ctypes.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) tradeSymbol := markets[0].BaseAssetSymbol @@ -179,7 +178,7 @@ func TestSubscribeBlockHeightEvent(t *testing.T) { func TestSubscribeKlineEvent(t *testing.T) { client := NewClient(t) - markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) + markets, err := client.GetMarkets(ctypes.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) tradeSymbol := markets[0].BaseAssetSymbol @@ -203,7 +202,7 @@ func TestSubscribeKlineEvent(t *testing.T) { func TestSubscribeMarketDiffEvent(t *testing.T) { client := NewClient(t) - markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) + markets, err := client.GetMarkets(ctypes.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) tradeSymbol := markets[0].BaseAssetSymbol @@ -227,7 +226,7 @@ func TestSubscribeMarketDiffEvent(t *testing.T) { func TestSubscribeMarketDepthEvent(t *testing.T) { client := NewClient(t) - markets, err := client.GetMarkets(query.NewMarketsQuery().WithLimit(1)) + markets, err := client.GetMarkets(ctypes.NewMarketsQuery().WithLimit(1)) assert.NoError(t, err) assert.Equal(t, 1, len(markets)) tradeSymbol := markets[0].BaseAssetSymbol diff --git a/go.mod b/go.mod index 3937b1c8..de3435a1 100644 --- a/go.mod +++ b/go.mod @@ -19,6 +19,7 @@ require ( github.com/syndtr/goleveldb v1.0.0 // indirect github.com/tendermint/btcd v0.0.0-20180816174608-e5840949ff4f github.com/tendermint/ed25519 v0.0.0-20171027050219-d8387025d2b9 // indirect + github.com/tendermint/go-amino v0.14.1 github.com/tendermint/tendermint v0.31.2-rc0 golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869 golang.org/x/sys v0.0.0-20181116161606-93218def8b18 // indirect diff --git a/keys/keys.go b/keys/keys.go index 55631f5a..0725ddd7 100644 --- a/keys/keys.go +++ b/keys/keys.go @@ -15,10 +15,10 @@ import ( "github.com/tendermint/tendermint/crypto/secp256k1" "github.com/binance-chain/go-sdk/common" + ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/common/uuid" "github.com/binance-chain/go-sdk/types/tx" "github.com/tendermint/tendermint/crypto" - ctypes "github.com/binance-chain/go-sdk/common/types" ) const ( @@ -173,7 +173,6 @@ func (m *keyManager) Sign(msg tx.StdSignMsg) ([]byte, error) { if err != nil { return nil, err } - fmt.Println(string(bz)) //return bz, nil return []byte(hex.EncodeToString(bz)), nil } diff --git a/types/msg/msg-gov.go b/types/msg/msg-gov.go index 0f283e26..c937f517 100644 --- a/types/msg/msg-gov.go +++ b/types/msg/msg-gov.go @@ -6,10 +6,8 @@ import ( "time" "github.com/binance-chain/go-sdk/common/types" - "github.com/pkg/errors" "github.com/tendermint/go-amino" - "github.com/binance-chain/go-sdk/common/types" ) // name to idetify transaction types @@ -280,7 +278,7 @@ func (msg SubmitProposalMsg) ValidateBasic() error { return fmt.Errorf("initial deposit %v is negative. ", msg.InitialDeposit) } if msg.VotingPeriod <= 0 || msg.VotingPeriod > MaxVotingPeriod { - return fmt.Errorf("voting period should between 0 and %d weeks", MaxVotingPeriod/(7 * 24 * 60 * 60 * time.Second)) + return fmt.Errorf("voting period should between 0 and %d weeks", MaxVotingPeriod/(7*24*60*60*time.Second)) } return nil } From b5786aeb8bad4ca2c703eac941591d0bf57e335c Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 22 Apr 2019 11:34:24 +0800 Subject: [PATCH 11/14] update parse tx --- client/rpc/ws_client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index f676ccef..d194e389 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -1018,7 +1018,7 @@ func FormatTxResults(cdc *amino.Codec, res []*ctypes.ResultTx) ([]tx.Info, error } func formatTxResult(cdc *amino.Codec, res *ctypes.ResultTx) (tx.Info, error) { - parsedTx, err := parseTx(cdc, res.Tx) + parsedTx, err := ParseTx(cdc, res.Tx) if err != nil { return tx.Info{}, err } @@ -1031,7 +1031,7 @@ func formatTxResult(cdc *amino.Codec, res *ctypes.ResultTx) (tx.Info, error) { }, nil } -func parseTx(cdc *amino.Codec, txBytes []byte) (tx.Tx, error) { +func ParseTx(cdc *amino.Codec, txBytes []byte) (tx.Tx, error) { var parsedTx tx.StdTx err := cdc.UnmarshalBinaryLengthPrefixed(txBytes, &parsedTx) From faab0f863694863380aa9240272f18939f032ec0 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Mon, 22 Apr 2019 16:06:58 +0800 Subject: [PATCH 12/14] add new codec func --- client/rpc/mock/client.go | 135 ++++++++++++++++++++++++++++++++++++++ client/rpc/ws_client.go | 6 +- types/wire.go | 16 +++++ 3 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 client/rpc/mock/client.go create mode 100644 types/wire.go diff --git a/client/rpc/mock/client.go b/client/rpc/mock/client.go new file mode 100644 index 00000000..bf1e6909 --- /dev/null +++ b/client/rpc/mock/client.go @@ -0,0 +1,135 @@ +package mock + +import ( + "reflect" + + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/rpc/client" + "github.com/tendermint/tendermint/rpc/core" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + rpctypes "github.com/tendermint/tendermint/rpc/lib/types" + "github.com/tendermint/tendermint/types" +) + +// Client wraps arbitrary implementations of the various interfaces. +// +// We provide a few choices to mock out each one in this package. +// Nothing hidden here, so no New function, just construct it from +// some parts, and swap them out them during the tests. +type Client struct { + client.ABCIClient + client.SignClient + client.HistoryClient + client.StatusClient + client.EventsClient + cmn.Service +} + +var _ client.Client = Client{} + +// Call is used by recorders to save a call and response. +// It can also be used to configure mock responses. +// +type Call struct { + Name string + Args interface{} + Response interface{} + Error error +} + +// GetResponse will generate the apporiate response for us, when +// using the Call struct to configure a Mock handler. +// +// When configuring a response, if only one of Response or Error is +// set then that will always be returned. If both are set, then +// we return Response if the Args match the set args, Error otherwise. +func (c Call) GetResponse(args interface{}) (interface{}, error) { + // handle the case with no response + if c.Response == nil { + if c.Error == nil { + panic("Misconfigured call, you must set either Response or Error") + } + return nil, c.Error + } + // response without error + if c.Error == nil { + return c.Response, nil + } + // have both, we must check args.... + if reflect.DeepEqual(args, c.Args) { + return c.Response, nil + } + return nil, c.Error +} + +func (c Client) Status() (*ctypes.ResultStatus, error) { + return core.Status(&rpctypes.Context{}) +} + +func (c Client) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + return core.ABCIInfo(&rpctypes.Context{}) +} + +func (c Client) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { + return c.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) +} + +func (c Client) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + return core.ABCIQuery(&rpctypes.Context{}, path, data, opts.Height, opts.Prove) +} + +func (c Client) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + return core.BroadcastTxCommit(&rpctypes.Context{}, tx) +} + +func (c Client) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return core.BroadcastTxAsync(&rpctypes.Context{}, tx) +} + +func (c Client) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + return core.BroadcastTxSync(&rpctypes.Context{}, tx) +} + +func (c Client) NetInfo() (*ctypes.ResultNetInfo, error) { + return core.NetInfo(&rpctypes.Context{}) +} + +func (c Client) ConsensusState() (*ctypes.ResultConsensusState, error) { + return core.ConsensusState(&rpctypes.Context{}) +} + +func (c Client) DumpConsensusState() (*ctypes.ResultDumpConsensusState, error) { + return core.DumpConsensusState(&rpctypes.Context{}) +} + +func (c Client) Health() (*ctypes.ResultHealth, error) { + return core.Health(&rpctypes.Context{}) +} + +func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { + return core.UnsafeDialSeeds(&rpctypes.Context{}, seeds) +} + +func (c Client) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { + return core.UnsafeDialPeers(&rpctypes.Context{}, peers, persistent) +} + +func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { + return core.BlockchainInfo(&rpctypes.Context{}, minHeight, maxHeight) +} + +func (c Client) Genesis() (*ctypes.ResultGenesis, error) { + return core.Genesis(&rpctypes.Context{}) +} + +func (c Client) Block(height *int64) (*ctypes.ResultBlock, error) { + return core.Block(&rpctypes.Context{}, height) +} + +func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) { + return core.Commit(&rpctypes.Context{}, height) +} + +func (c Client) Validators(height *int64) (*ctypes.ResultValidators, error) { + return core.Validators(&rpctypes.Context{}, height) +} diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index d194e389..7de0d166 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -26,7 +26,7 @@ import ( ) const ( - defaultMaxReconnectAttempts = 25 + defaultMaxReconnectAttempts = -1 defaultMaxReconnectBackOffTime = 120 * time.Second defaultWriteWait = 100 * time.Millisecond defaultReadWait = 0 @@ -673,13 +673,9 @@ func (c *WSClient) dial() error { func (c *WSClient) reconnect() error { attempt := 0 - c.mtx.Lock() c.reconnecting = true - c.mtx.Unlock() defer func() { - c.mtx.Lock() c.reconnecting = false - c.mtx.Unlock() }() backOffDuration := 1 * time.Second for { diff --git a/types/wire.go b/types/wire.go new file mode 100644 index 00000000..54ff5517 --- /dev/null +++ b/types/wire.go @@ -0,0 +1,16 @@ +package types + +import ( + ntypes "github.com/binance-chain/go-sdk/common/types" + "github.com/binance-chain/go-sdk/types/tx" + "github.com/tendermint/go-amino" + types "github.com/tendermint/tendermint/rpc/core/types" +) + +func NewCodec() *amino.Codec { + cdc := amino.NewCodec() + types.RegisterAmino(cdc) + ntypes.RegisterWire(cdc) + tx.RegisterCodec(cdc) + return cdc +} From 7e8f7642fef35d13900f7b78a1aa9a30b21fb5d5 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Wed, 24 Apr 2019 21:23:41 +0800 Subject: [PATCH 13/14] change reconnection policy --- client/rpc/dex_client.go | 2 +- client/rpc/mock/abci.go | 211 ++++++++++++++++++++++++++++++++++++++ client/rpc/mock/client.go | 22 ++-- client/rpc/mock/ops.go | 5 + client/rpc/mock/status.go | 52 ++++++++++ client/rpc/ops_client.go | 5 + client/rpc/validate.go | 3 +- client/rpc/ws_client.go | 96 +++++++++-------- common/types/dec.go | 2 +- common/types/fixed8.go | 2 +- e2e/e2e_rpc_test.go | 88 ++++++++++++---- e2e/e2e_ws_test.go | 3 +- keys/keys_test.go | 12 ++- 13 files changed, 419 insertions(+), 84 deletions(-) create mode 100644 client/rpc/mock/abci.go create mode 100644 client/rpc/mock/ops.go create mode 100644 client/rpc/mock/status.go diff --git a/client/rpc/dex_client.go b/client/rpc/dex_client.go index f1ce0203..010b5c9f 100644 --- a/client/rpc/dex_client.go +++ b/client/rpc/dex_client.go @@ -3,10 +3,10 @@ package rpc import ( "errors" "fmt" - "github.com/binance-chain/go-sdk/types/tx" "strings" "github.com/binance-chain/go-sdk/common/types" + "github.com/binance-chain/go-sdk/types/tx" ) const ( diff --git a/client/rpc/mock/abci.go b/client/rpc/mock/abci.go new file mode 100644 index 00000000..2ab62a42 --- /dev/null +++ b/client/rpc/mock/abci.go @@ -0,0 +1,211 @@ +package mock + +import ( + abci "github.com/tendermint/tendermint/abci/types" + cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/proxy" + "github.com/tendermint/tendermint/rpc/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" + "github.com/tendermint/tendermint/types" +) + +// ABCIApp will send all abci related request to the named app, +// so you can test app behavior from a client without needing +// an entire tendermint node +type ABCIApp struct { + App abci.Application +} + +var ( + _ client.ABCIClient = ABCIApp{} + _ client.ABCIClient = ABCIMock{} + _ client.ABCIClient = (*ABCIRecorder)(nil) +) + +func (a ABCIApp) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + return &ctypes.ResultABCIInfo{Response: a.App.Info(proxy.RequestInfo)}, nil +} + +func (a ABCIApp) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { + return a.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) +} + +func (a ABCIApp) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + q := a.App.Query(abci.RequestQuery{ + Data: data, + Path: path, + Height: opts.Height, + Prove: opts.Prove, + }) + return &ctypes.ResultABCIQuery{Response: q}, nil +} + +// NOTE: Caller should call a.App.Commit() separately, +// this function does not actually wait for a commit. +// TODO: Make it wait for a commit and set res.Height appropriately. +func (a ABCIApp) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res := ctypes.ResultBroadcastTxCommit{} + res.CheckTx = a.App.CheckTx(tx) + if res.CheckTx.IsErr() { + return &res, nil + } + res.DeliverTx = a.App.DeliverTx(tx) + res.Height = -1 // TODO + return &res, nil +} + +func (a ABCIApp) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + c := a.App.CheckTx(tx) + // and this gets written in a background thread... + if !c.IsErr() { + go func() { a.App.DeliverTx(tx) }() // nolint: errcheck + } + return &ctypes.ResultBroadcastTx{Code: c.Code, Data: c.Data, Log: c.Log, Hash: tx.Hash()}, nil +} + +func (a ABCIApp) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + c := a.App.CheckTx(tx) + // and this gets written in a background thread... + if !c.IsErr() { + go func() { a.App.DeliverTx(tx) }() // nolint: errcheck + } + return &ctypes.ResultBroadcastTx{Code: c.Code, Data: c.Data, Log: c.Log, Hash: tx.Hash()}, nil +} + +// ABCIMock will send all abci related request to the named app, +// so you can test app behavior from a client without needing +// an entire tendermint node +type ABCIMock struct { + Info Call + Query Call + BroadcastCommit Call + Broadcast Call +} + +func (m ABCIMock) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + res, err := m.Info.GetResponse(nil) + if err != nil { + return nil, err + } + return &ctypes.ResultABCIInfo{Response: res.(abci.ResponseInfo)}, nil +} + +func (m ABCIMock) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { + return m.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) +} + +func (m ABCIMock) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + res, err := m.Query.GetResponse(QueryArgs{path, data, opts.Height, opts.Prove}) + if err != nil { + return nil, err + } + resQuery := res.(abci.ResponseQuery) + return &ctypes.ResultABCIQuery{Response: resQuery}, nil +} + +func (m ABCIMock) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res, err := m.BroadcastCommit.GetResponse(tx) + if err != nil { + return nil, err + } + return res.(*ctypes.ResultBroadcastTxCommit), nil +} + +func (m ABCIMock) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + res, err := m.Broadcast.GetResponse(tx) + if err != nil { + return nil, err + } + return res.(*ctypes.ResultBroadcastTx), nil +} + +func (m ABCIMock) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + res, err := m.Broadcast.GetResponse(tx) + if err != nil { + return nil, err + } + return res.(*ctypes.ResultBroadcastTx), nil +} + +// ABCIRecorder can wrap another type (ABCIApp, ABCIMock, or Client) +// and record all ABCI related calls. +type ABCIRecorder struct { + Client client.ABCIClient + Calls []Call +} + +func NewABCIRecorder(client client.ABCIClient) *ABCIRecorder { + return &ABCIRecorder{ + Client: client, + Calls: []Call{}, + } +} + +type QueryArgs struct { + Path string + Data cmn.HexBytes + Height int64 + Prove bool +} + +func (r *ABCIRecorder) addCall(call Call) { + r.Calls = append(r.Calls, call) +} + +func (r *ABCIRecorder) ABCIInfo() (*ctypes.ResultABCIInfo, error) { + res, err := r.Client.ABCIInfo() + r.addCall(Call{ + Name: "abci_info", + Response: res, + Error: err, + }) + return res, err +} + +func (r *ABCIRecorder) ABCIQuery(path string, data cmn.HexBytes) (*ctypes.ResultABCIQuery, error) { + return r.ABCIQueryWithOptions(path, data, client.DefaultABCIQueryOptions) +} + +func (r *ABCIRecorder) ABCIQueryWithOptions(path string, data cmn.HexBytes, opts client.ABCIQueryOptions) (*ctypes.ResultABCIQuery, error) { + res, err := r.Client.ABCIQueryWithOptions(path, data, opts) + r.addCall(Call{ + Name: "abci_query", + Args: QueryArgs{path, data, opts.Height, opts.Prove}, + Response: res, + Error: err, + }) + return res, err +} + +func (r *ABCIRecorder) BroadcastTxCommit(tx types.Tx) (*ctypes.ResultBroadcastTxCommit, error) { + res, err := r.Client.BroadcastTxCommit(tx) + r.addCall(Call{ + Name: "broadcast_tx_commit", + Args: tx, + Response: res, + Error: err, + }) + return res, err +} + +func (r *ABCIRecorder) BroadcastTxAsync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + res, err := r.Client.BroadcastTxAsync(tx) + r.addCall(Call{ + Name: "broadcast_tx_async", + Args: tx, + Response: res, + Error: err, + }) + return res, err +} + +func (r *ABCIRecorder) BroadcastTxSync(tx types.Tx) (*ctypes.ResultBroadcastTx, error) { + res, err := r.Client.BroadcastTxSync(tx) + r.addCall(Call{ + Name: "broadcast_tx_sync", + Args: tx, + Response: res, + Error: err, + }) + return res, err +} diff --git a/client/rpc/mock/client.go b/client/rpc/mock/client.go index bf1e6909..762ea66c 100644 --- a/client/rpc/mock/client.go +++ b/client/rpc/mock/client.go @@ -1,9 +1,11 @@ package mock import ( + "github.com/binance-chain/go-sdk/client/rpc" "reflect" cmn "github.com/tendermint/tendermint/libs/common" + "github.com/tendermint/tendermint/libs/log" "github.com/tendermint/tendermint/rpc/client" "github.com/tendermint/tendermint/rpc/core" ctypes "github.com/tendermint/tendermint/rpc/core/types" @@ -17,15 +19,17 @@ import ( // Nothing hidden here, so no New function, just construct it from // some parts, and swap them out them during the tests. type Client struct { + cmn.Service client.ABCIClient client.SignClient client.HistoryClient client.StatusClient - client.EventsClient - cmn.Service + rpc.EventsClient + rpc.DexClient + rpc.OpsClient } -var _ client.Client = Client{} +var _ rpc.Client = Client{} // Call is used by recorders to save a call and response. // It can also be used to configure mock responses. @@ -106,14 +110,6 @@ func (c Client) Health() (*ctypes.ResultHealth, error) { return core.Health(&rpctypes.Context{}) } -func (c Client) DialSeeds(seeds []string) (*ctypes.ResultDialSeeds, error) { - return core.UnsafeDialSeeds(&rpctypes.Context{}, seeds) -} - -func (c Client) DialPeers(peers []string, persistent bool) (*ctypes.ResultDialPeers, error) { - return core.UnsafeDialPeers(&rpctypes.Context{}, peers, persistent) -} - func (c Client) BlockchainInfo(minHeight, maxHeight int64) (*ctypes.ResultBlockchainInfo, error) { return core.BlockchainInfo(&rpctypes.Context{}, minHeight, maxHeight) } @@ -133,3 +129,7 @@ func (c Client) Commit(height *int64) (*ctypes.ResultCommit, error) { func (c Client) Validators(height *int64) (*ctypes.ResultValidators, error) { return core.Validators(&rpctypes.Context{}, height) } + +func (c Client) SetLogger(log.Logger) { + return +} diff --git a/client/rpc/mock/ops.go b/client/rpc/mock/ops.go new file mode 100644 index 00000000..390954e8 --- /dev/null +++ b/client/rpc/mock/ops.go @@ -0,0 +1,5 @@ +package mock + +func (c Client) IsActive() bool { + return true +} diff --git a/client/rpc/mock/status.go b/client/rpc/mock/status.go new file mode 100644 index 00000000..58b29d57 --- /dev/null +++ b/client/rpc/mock/status.go @@ -0,0 +1,52 @@ +package mock + +import ( + "github.com/tendermint/tendermint/rpc/client" + ctypes "github.com/tendermint/tendermint/rpc/core/types" +) + +// StatusMock returns the result specified by the Call +type StatusMock struct { + Call +} + +var ( + _ client.StatusClient = (*StatusMock)(nil) + _ client.StatusClient = (*StatusRecorder)(nil) +) + +func (m *StatusMock) Status() (*ctypes.ResultStatus, error) { + res, err := m.GetResponse(nil) + if err != nil { + return nil, err + } + return res.(*ctypes.ResultStatus), nil +} + +// StatusRecorder can wrap another type (StatusMock, full client) +// and record the status calls +type StatusRecorder struct { + Client client.StatusClient + Calls []Call +} + +func NewStatusRecorder(client client.StatusClient) *StatusRecorder { + return &StatusRecorder{ + Client: client, + Calls: []Call{}, + } +} + +func (r *StatusRecorder) addCall(call Call) { + r.Calls = append(r.Calls, call) +} + +func (r *StatusRecorder) Status() (*ctypes.ResultStatus, error) { + res, err := r.Client.Status() + r.addCall(Call{ + Name: "status", + Response: res, + Error: err, + }) + return res, err +} diff --git a/client/rpc/ops_client.go b/client/rpc/ops_client.go index 024eca78..7c204461 100644 --- a/client/rpc/ops_client.go +++ b/client/rpc/ops_client.go @@ -3,10 +3,15 @@ package rpc import "github.com/binance-chain/go-sdk/common/types" type OpsClient interface { + IsActive() bool GetStakeValidators() ([]types.Validator, error) GetDelegatorUnbondingDelegations(delegatorAddr types.AccAddress) ([]types.UnbondingDelegation, error) } +func (c *HTTP) IsActive() bool { + return c.WSEvents.IsActive() +} + func (c *HTTP) GetStakeValidators() ([]types.Validator, error) { rawVal, err := c.ABCIQuery("custom/stake/validators", nil) if err != nil { diff --git a/client/rpc/validate.go b/client/rpc/validate.go index 4bcb57eb..805fe4d0 100644 --- a/client/rpc/validate.go +++ b/client/rpc/validate.go @@ -3,9 +3,10 @@ package rpc import ( "crypto/sha256" "fmt" + "strings" + "github.com/tendermint/tendermint/libs/common" "github.com/tendermint/tendermint/types" - "strings" ) const ( diff --git a/client/rpc/ws_client.go b/client/rpc/ws_client.go index 7de0d166..d27c1646 100644 --- a/client/rpc/ws_client.go +++ b/client/rpc/ws_client.go @@ -27,10 +27,11 @@ import ( const ( defaultMaxReconnectAttempts = -1 - defaultMaxReconnectBackOffTime = 120 * time.Second + defaultMaxReconnectBackOffTime = 10 * time.Second defaultWriteWait = 100 * time.Millisecond defaultReadWait = 0 defaultPingPeriod = 0 + defaultDialPeriod = 1 * time.Second protoHTTP = "http" protoHTTPS = "https" @@ -105,6 +106,10 @@ func (w *WSEvents) PendingRequest() int { return size } +func (c *WSEvents) IsActive() bool { + return c.ws.IsActive() +} + // Subscribe implements EventsClient by using WSClient to subscribe given // subscriber to query. By default, returns a channel with cap=1. Error is // returned if it fails to subscribe. @@ -481,15 +486,13 @@ type WSClient struct { onReconnect func() // internal channels - send chan rpctypes.RPCRequest // user requests - reconnectAfter chan error // reconnect requests - readRoutineQuit chan struct{} // a way for readRoutine to close writeRoutine + send chan rpctypes.RPCRequest // user requests + reconnectAfter chan error // reconnect requests wg sync.WaitGroup - mtx sync.RWMutex - sentLastPingAt time.Time - reconnecting bool + mtx sync.RWMutex + dialing bool // Maximum reconnect attempts (0 or greater; default: 25). // Less than 0 means always try to reconnect. @@ -529,6 +532,7 @@ func NewWSClient(remoteAddr, endpoint string, options ...func(*WSClient)) *WSCli writeWait: defaultWriteWait, pingPeriod: defaultPingPeriod, protocol: protocol, + dialing: true, } c.BaseService = *cmn.NewBaseService(nil, "WSClient", c) for _, option := range options { @@ -553,10 +557,6 @@ func (c *WSClient) String() string { // OnStart implements cmn.Service by dialing a server and creating read and // write routines. func (c *WSClient) OnStart() error { - err := c.dial() - if err != nil { - return err - } c.ResponsesCh = make(chan rpctypes.RPCResponse) @@ -567,6 +567,14 @@ func (c *WSClient) OnStart() error { // capacity for 1 request. a user won't be able to send more because the send // channel is unbuffered. + err := c.dial() + if err != nil { + c.wg.Add(1) + go c.dialRoutine() + } else { + c.dialing = false + } + c.startReadWriteRoutines() go c.reconnectRoutine() @@ -586,14 +594,14 @@ func (c *WSClient) Stop() error { return nil } -// IsReconnecting returns true if the client is reconnecting right now. -func (c *WSClient) IsReconnecting() bool { - return c.reconnecting +// IsDialing returns true if the client is dialing right now. +func (c *WSClient) IsDialing() bool { + return c.dialing } -// IsActive returns true if the client is running and not reconnecting. +// IsActive returns true if the client is running and not dialing. func (c *WSClient) IsActive() bool { - return c.IsRunning() && !c.IsReconnecting() + return c.IsRunning() && !c.IsDialing() } // Send the given RPC request to the server. Results will be available on @@ -611,8 +619,8 @@ func (c *WSClient) Send(ctx context.Context, request rpctypes.RPCRequest) error // Call the given method. See Send description. func (c *WSClient) Call(ctx context.Context, method string, id rpctypes.JSONRPCStringID, params map[string]interface{}) error { - if c.IsReconnecting() { - return errors.New("websocket is reconnecting, can't send any request") + if c.IsDialing() { + return errors.New("websocket is dialing, can't send any request") } request, err := rpctypes.MapToRequest(c.cdc, id, method, params) if err != nil { @@ -673,9 +681,9 @@ func (c *WSClient) dial() error { func (c *WSClient) reconnect() error { attempt := 0 - c.reconnecting = true + c.dialing = true defer func() { - c.reconnecting = false + c.dialing = false }() backOffDuration := 1 * time.Second for { @@ -707,18 +715,35 @@ func (c *WSClient) reconnect() error { } func (c *WSClient) startReadWriteRoutines() { - c.wg.Add(2) - c.readRoutineQuit = make(chan struct{}) go c.readRoutine() go c.writeRoutine() } +func (c *WSClient) dialRoutine() { + dialTicker := time.NewTicker(defaultDialPeriod) + defer dialTicker.Stop() + c.dialing = true + defer func() { + c.dialing = false + c.wg.Done() + }() + for { + select { + case <-c.Quit(): + return + case <-dialTicker.C: + err := c.dial() + if err == nil { + return + } + } + } +} + func (c *WSClient) reconnectRoutine() { for { select { case originalError := <-c.reconnectAfter: - // wait until writeRoutine and readRoutine finish - c.wg.Wait() if err := c.reconnect(); err != nil { c.Logger.Error("failed to reconnect", "err", err, "original_err", originalError) c.Stop() @@ -743,6 +768,7 @@ func (c *WSClient) reconnectRoutine() { // The client ensures that there is at most one writer to a connection by // executing all writes from this goroutine. func (c *WSClient) writeRoutine() { + c.wg.Wait() var ticker *time.Ticker if c.pingPeriod > 0 { // ticker with a predefined period @@ -758,7 +784,6 @@ func (c *WSClient) writeRoutine() { // ignore error; it will trigger in tests // likely because it's closing an already closed connection } - c.wg.Done() }() for { @@ -772,9 +797,6 @@ func (c *WSClient) writeRoutine() { if err := c.conn.WriteJSON(request); err != nil { c.Logger.Error("failed to send request", "err", err) c.reconnectAfter <- err - // add request to the backlog, so we don't lose it - //c.backlog <- request - return } case <-ticker.C: if c.writeWait > 0 { @@ -785,14 +807,9 @@ func (c *WSClient) writeRoutine() { if err := c.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil { c.Logger.Error("failed to write ping", "err", err) c.reconnectAfter <- err - return + continue } - c.mtx.Lock() - c.sentLastPingAt = time.Now() - c.mtx.Unlock() c.Logger.Debug("sent ping") - case <-c.readRoutineQuit: - return case <-c.Quit(): if err := c.conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil { c.Logger.Error("failed to write message", "err", err) @@ -810,9 +827,8 @@ func (c *WSClient) readRoutine() { // ignore error; it will trigger in tests // likely because it's closing an already closed connection } - c.wg.Done() }() - + c.wg.Wait() c.conn.SetPongHandler(func(string) error { c.Logger.Debug("got pong") return nil @@ -827,14 +843,9 @@ func (c *WSClient) readRoutine() { } _, data, err := c.conn.ReadMessage() if err != nil { - if !websocket.IsUnexpectedCloseError(err, websocket.CloseNormalClosure) { - return - } - c.Logger.Error("failed to read response", "err", err) - close(c.readRoutineQuit) c.reconnectAfter <- err - return + continue } var response rpctypes.RPCResponse @@ -849,6 +860,7 @@ func (c *WSClient) readRoutine() { // both readRoutine and writeRoutine select { case <-c.Quit(): + return case c.ResponsesCh <- response: } } diff --git a/common/types/dec.go b/common/types/dec.go index 3a1f8cb7..6f400762 100644 --- a/common/types/dec.go +++ b/common/types/dec.go @@ -60,4 +60,4 @@ func NewDecFromStr(str string) (d Dec, err error) { return d, fmt.Errorf("bad string to integer conversion, input string: %v, error: %v", str, parseErr) } return Dec{value}, nil -} \ No newline at end of file +} diff --git a/common/types/fixed8.go b/common/types/fixed8.go index db595a95..6628cf2d 100644 --- a/common/types/fixed8.go +++ b/common/types/fixed8.go @@ -126,4 +126,4 @@ func (n *Double) UnmarshalJSON(data []byte) error { func (n *Double) MarshalJSON() ([]byte, error) { return json.Marshal(fmt.Sprintf("%.8f", float64(*n))) -} \ No newline at end of file +} diff --git a/e2e/e2e_rpc_test.go b/e2e/e2e_rpc_test.go index e928d5df..84435f8b 100644 --- a/e2e/e2e_rpc_test.go +++ b/e2e/e2e_rpc_test.go @@ -1,24 +1,26 @@ package e2e import ( + "bytes" "encoding/hex" "encoding/json" "fmt" "math/rand" + "os/exec" "sync" "testing" "time" - ctypes "github.com/binance-chain/go-sdk/common/types" + "github.com/stretchr/testify/assert" + tmquery "github.com/tendermint/tendermint/libs/pubsub/query" "github.com/tendermint/tendermint/types" "github.com/binance-chain/go-sdk/client/rpc" - "github.com/stretchr/testify/assert" - tmquery "github.com/tendermint/tendermint/libs/pubsub/query" + ctypes "github.com/binance-chain/go-sdk/common/types" ) var ( - nodeAddr = "tcp://data-seed-pre-0-s3.binance.org:80" + nodeAddr = "tcp://127.0.0.1:26657" badAddr = "tcp://127.0.0.1:80" testTxHash = "A27C20143E6B7D8160B50883F81132C1DFD0072FF2C1FE71E0158FBD001E23E4" testTxHeight = 8669273 @@ -30,6 +32,18 @@ var ( testClientInstance *rpc.HTTP ) +func startBnbchaind(t *testing.T) *exec.Cmd { + cmd := exec.Command("bnbchaind", "start", "--home", "testnoded") + var out bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &out + err := cmd.Start() + assert.NoError(t, err) + // wait for completely start + time.Sleep(15 * time.Second) + return cmd +} + func defaultClient() *rpc.HTTP { onceClient.Do(func() { testClientInstance = rpc.NewRPCClient(nodeAddr) @@ -152,23 +166,55 @@ func TestTx(t *testing.T) { fmt.Println(string(bz)) } -//func TestReconnection(t *testing.T) { -// c := defaultClient() -// status, err := c.Status() -// assert.NoError(t, err) -// bz, err := json.Marshal(status) -// fmt.Println(string(bz)) -// time.Sleep(10 * time.Second) -// status, err = c.Status() -// assert.Error(t, err) -// fmt.Println(err) -// time.Sleep(10 * time.Second) -// status, err = c.Status() -// assert.Error(t, err) -// fmt.Println(err) -// bz, err = json.Marshal(status) -// fmt.Println(string(bz)) -//} +func TestReconnection(t *testing.T) { + repeatNum := 10 + c := defaultClient() + + // Find error + time.Sleep(1 * time.Second) + for i := 0; i < repeatNum; i++ { + _, err := c.Status() + assert.Error(t, err) + } + + // Reconnect and find no error + cmd := startBnbchaind(t) + + for i := 0; i < repeatNum; i++ { + status, err := c.Status() + assert.NoError(t, err) + bz, err := json.Marshal(status) + fmt.Println(string(bz)) + } + + // kill process + err := cmd.Process.Kill() + assert.NoError(t, err) + err = cmd.Process.Release() + assert.NoError(t, err) + time.Sleep(1 * time.Second) + + // Find error + for i := 0; i < repeatNum; i++ { + _, err := c.Status() + assert.Error(t, err) + } + + // Restart bnbchain + cmd = startBnbchaind(t) + + // Find no error + for i := 0; i < repeatNum; i++ { + status, err := c.Status() + assert.NoError(t, err) + bz, _ := json.Marshal(status) + fmt.Println(string(bz)) + } + + // Stop bnbchain + cmd.Process.Kill() + cmd.Process.Release() +} func TestTxSearch(t *testing.T) { c := defaultClient() diff --git a/e2e/e2e_ws_test.go b/e2e/e2e_ws_test.go index d746667d..874cb009 100644 --- a/e2e/e2e_ws_test.go +++ b/e2e/e2e_ws_test.go @@ -6,12 +6,13 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + sdk "github.com/binance-chain/go-sdk/client" "github.com/binance-chain/go-sdk/client/websocket" ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/keys" "github.com/binance-chain/go-sdk/types" - "github.com/stretchr/testify/assert" ) func NewClient(t *testing.T) sdk.DexClient { diff --git a/keys/keys_test.go b/keys/keys_test.go index 6434a053..3684fe50 100644 --- a/keys/keys_test.go +++ b/keys/keys_test.go @@ -6,11 +6,13 @@ import ( "io/ioutil" "os" "testing" + "time" - "github.com/binance-chain/go-sdk/types" + "github.com/stretchr/testify/assert" + + ctypes "github.com/binance-chain/go-sdk/common/types" "github.com/binance-chain/go-sdk/types/msg" "github.com/binance-chain/go-sdk/types/tx" - "github.com/stretchr/testify/assert" ) func TestRecoveryFromKeyWordsNoError(t *testing.T) { @@ -69,7 +71,7 @@ func TestSignTxNoError(t *testing.T) { expectHexTx string errMsg string }{ - {msg.CreateSendMsg(test1Addr, types.Coins{types.Coin{Denom: "BNB", Amount: 100000000000000}}, []msg.Transfer{{test2KeyManagr.GetAddr(), types.Coins{types.Coin{Denom: "BNB", Amount: 100000000000000}}}}), + {msg.CreateSendMsg(test1Addr, ctypes.Coins{ctypes.Coin{Denom: "BNB", Amount: 100000000000000}}, []msg.Transfer{{test2KeyManagr.GetAddr(), ctypes.Coins{ctypes.Coin{Denom: "BNB", Amount: 100000000000000}}}}), test1KeyManger, 0, 1, @@ -84,11 +86,11 @@ func TestSignTxNoError(t *testing.T) { "a701f0625dee0a3317efab800a146b571fc0a9961a7ddf45e49a88a4d83941fcabbe1207426974636f696e1a034254432080809aa6eaafe3012801126c0a26eb5ae9872103d8f33449356d58b699f6b16a498bd391aa5e051085415d0fe1873939bc1d2e3a12403686586a55f8c50a11ae6f09c35734f09830a566823846f9333c3e53f6d83d4a2cd3a0542c37a8d28b474f563d44223ba6c2b7cf260539b7b85020999ebe2c001801", "issue message sign error", }, - {msg.NewMsgSubmitProposal("list BTC/BNB", "{\"base_asset_symbol\":\"BTC-86A\",\"quote_asset_symbol\":\"BNB\",\"init_price\":100000000,\"description\":\"list BTC/BNB\",\"expire_time\":\"2018-12-24T00:46:05+08:00\"}", msg.ProposalTypeListTradingPair, test1Addr, types.Coins{types.Coin{Denom: "BNB", Amount: 200000000000}}), + {msg.NewMsgSubmitProposal("list BTC/BNB", "{\"base_asset_symbol\":\"BTC-86A\",\"quote_asset_symbol\":\"BNB\",\"init_price\":100000000,\"description\":\"list BTC/BNB\",\"expire_time\":\"2018-12-24T00:46:05+08:00\"}", msg.ProposalTypeListTradingPair, test1Addr, ctypes.Coins{ctypes.Coin{Denom: "BNB", Amount: 200000000000}}, time.Second), test1KeyManger, 0, 2, - "c802f0625dee0ad301b42d614e0a0c6c697374204254432f424e421298017b22626173655f61737365745f73796d626f6c223a224254432d383641222c2271756f74655f61737365745f73796d626f6c223a22424e42222c22696e69745f7072696365223a3130303030303030302c226465736372697074696f6e223a226c697374204254432f424e42222c226578706972655f74696d65223a22323031382d31322d32345430303a34363a30352b30383a3030227d180422141d0e3086e8e4e0a53c38a90d55bd58b34d57d2fa2a0c0a03424e421080a0b787e905126c0a26eb5ae98721027e69d96640300433654e016d218a8d7ffed751023d8efe81e55dedbd6754c971124053292ece8b9d2258ea6d22399ec988d75aab526056219174373b45bb1372d0a12f5853a196f56b1cc9e13fef7ab6debfdb43a36c175f40de99193ac7b319a53d2002", + "ce02f0625dee0ad901b42d614e0a0c6c697374204254432f424e421298017b22626173655f61737365745f73796d626f6c223a224254432d383641222c2271756f74655f61737365745f73796d626f6c223a22424e42222c22696e69745f7072696365223a3130303030303030302c226465736372697074696f6e223a226c697374204254432f424e42222c226578706972655f74696d65223a22323031382d31322d32345430303a34363a30352b30383a3030227d180422141d0e3086e8e4e0a53c38a90d55bd58b34d57d2fa2a0c0a03424e421080a0b787e905308094ebdc03126c0a26eb5ae98721027e69d96640300433654e016d218a8d7ffed751023d8efe81e55dedbd6754c9711240ebac8c34f27e9dc0719167c4ad87bc2e3e1437022c3287030425db8f4233c3b80938dfa16f555738ba97e92aa7a15ebb6ac8baa5d799118cccf503302d166df92002", "submit proposal sign error", }, { From fc67bbd91c8c26e944a28f505d833c66590fdda9 Mon Sep 17 00:00:00 2001 From: fudongbai <296179868@qq.com> Date: Tue, 30 Apr 2019 12:03:41 +0800 Subject: [PATCH 14/14] delete unused field --- client/rpc/basic_client.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/rpc/basic_client.go b/client/rpc/basic_client.go index e807f9a0..49a181b0 100644 --- a/client/rpc/basic_client.go +++ b/client/rpc/basic_client.go @@ -40,7 +40,6 @@ func NewRPCClient(nodeURI string) *HTTP { } type HTTP struct { - remote string *WSEvents } @@ -56,7 +55,6 @@ func NewHTTP(remote, wsEndpoint string) *HTTP { rc.SetCodec(cdc) wsEvent := newWSEvents(cdc, remote, wsEndpoint) client := &HTTP{ - remote: remote, WSEvents: wsEvent, } client.Start()