Coinut: Fix websocket not parsing instruments, improve nonce matching and error handling (#1495)

WsConnect is calling GetInstruments, and when that fails, erroring out
and not subscribing to anything.

The response to get instruments is an object {}, which skips the
IncomingWithData check in wsReadData for arrays [].
The check in wsHandleData depended on client_ord_id, but I figure so
long as there's a nonce which matches, we can palm it off.

This results simutaneously in having to move the login handler back to
it's waiting nonce-parser, and also simplifying/deduping it.
This commit is contained in:
Gareth Kirwan
2024-03-15 05:43:19 +01:00
committed by GitHub
parent d679a76460
commit 9d1476d4f1
2 changed files with 48 additions and 77 deletions

View File

@@ -608,6 +608,15 @@ type WsTradeHistoryTradeData struct {
TransactionID int64 `json:"trans_id"`
}
// WsLoginReq Login request message
type WsLoginReq struct {
Request string `json:"request"`
Username string `json:"username"`
Nonce int64 `json:"nonce"`
Hmac string `json:"hmac_sha256"`
Timestamp int64 `json:"timestamp"`
}
// WsLoginResponse ws response data
type WsLoginResponse struct {
APIKey string `json:"api_key"`

View File

@@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/buger/jsonparser"
"github.com/gorilla/websocket"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
@@ -58,10 +59,12 @@ func (c *COINUT) WsConnect() error {
return err
}
}
err = c.wsAuthenticate(context.TODO())
if err != nil {
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
log.Errorln(log.WebsocketMgr, err)
if c.IsWebsocketAuthenticationSupported() {
if err = c.wsAuthenticate(context.TODO()); err != nil {
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
log.Errorln(log.WebsocketMgr, c.Name+" "+err.Error())
}
}
// define bi-directional communication
@@ -75,6 +78,8 @@ func (c *COINUT) WsConnect() error {
func (c *COINUT) wsReadData() {
defer c.Websocket.Wg.Done()
ctx := context.TODO()
for {
resp := c.Websocket.Conn.ReadMessage()
if resp.Raw == nil {
@@ -100,7 +105,7 @@ func (c *COINUT) wsReadData() {
c.Websocket.DataHandler <- err
continue
}
err = c.wsHandleData(context.TODO(), individualJSON)
err = c.wsHandleData(ctx, individualJSON)
if err != nil {
c.Websocket.DataHandler <- err
}
@@ -112,7 +117,7 @@ func (c *COINUT) wsReadData() {
c.Websocket.DataHandler <- err
continue
}
err = c.wsHandleData(context.TODO(), resp.Raw)
err = c.wsHandleData(ctx, resp.Raw)
if err != nil {
c.Websocket.DataHandler <- err
}
@@ -120,7 +125,7 @@ func (c *COINUT) wsReadData() {
}
}
func (c *COINUT) wsHandleData(ctx context.Context, respRaw []byte) error {
func (c *COINUT) wsHandleData(_ context.Context, respRaw []byte) error {
if strings.HasPrefix(string(respRaw), "[") {
var orders []wsOrderContainer
err := json.Unmarshal(respRaw, &orders)
@@ -142,10 +147,8 @@ func (c *COINUT) wsHandleData(ctx context.Context, respRaw []byte) error {
if err != nil {
return err
}
if strings.Contains(string(respRaw), "client_ord_id") {
if c.Websocket.Match.IncomingWithData(incoming.Nonce, respRaw) {
return nil
}
if c.Websocket.Match.IncomingWithData(incoming.Nonce, respRaw) {
return nil
}
format, err := c.GetPairFormat(asset.Spot, true)
@@ -156,27 +159,6 @@ func (c *COINUT) wsHandleData(ctx context.Context, respRaw []byte) error {
switch incoming.Reply {
case "hb":
channels["hb"] <- respRaw
case "login":
var login WsLoginResponse
err := json.Unmarshal(respRaw, &login)
if err != nil {
return err
}
creds, err := c.GetCredentials(ctx)
if err != nil {
return err
}
var endpointFailure []byte
if login.APIKey != creds.Key {
endpointFailure = []byte("failed to authenticate")
}
if c.Websocket.Match.IncomingWithData(login.Nonce, endpointFailure) {
return nil
}
case "user_balance":
var userBalance WsUserBalanceResponse
err := json.Unmarshal(respRaw, &userBalance)
@@ -662,8 +644,7 @@ func (c *COINUT) Unsubscribe(channelToUnsubscribe []subscription.Subscription) e
Subscribe: false,
Nonce: getNonce(),
}
resp, err := c.Websocket.Conn.SendMessageReturnResponse(subscribe.Nonce,
subscribe)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(subscribe.Nonce, subscribe)
if err != nil {
errs = common.AppendError(errs, err)
continue
@@ -695,43 +676,31 @@ func (c *COINUT) wsAuthenticate(ctx context.Context) error {
if err != nil {
return err
}
timestamp := time.Now().Unix()
nonce := getNonce()
payload := creds.ClientID + "|" +
strconv.FormatInt(timestamp, 10) + "|" +
strconv.FormatInt(nonce, 10)
hmac, err := crypto.GetHMAC(crypto.HashSHA256,
[]byte(payload),
[]byte(creds.Key))
if err != nil {
return err
}
loginRequest := struct {
Request string `json:"request"`
Username string `json:"username"`
Nonce int64 `json:"nonce"`
Hmac string `json:"hmac_sha256"`
Timestamp int64 `json:"timestamp"`
}{
r := WsLoginReq{
Request: "login",
Username: creds.ClientID,
Nonce: nonce,
Hmac: crypto.HexEncodeToString(hmac),
Timestamp: timestamp,
Nonce: getNonce(),
Timestamp: time.Now().Unix(),
}
resp, err := c.Websocket.Conn.SendMessageReturnResponse(loginRequest.Nonce,
loginRequest)
payload := creds.ClientID + "|" + strconv.FormatInt(r.Timestamp, 10) + "|" + strconv.FormatInt(r.Nonce, 10)
hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(payload), []byte(creds.Key))
if err != nil {
return err
}
if resp != nil {
c.Websocket.SetCanUseAuthenticatedEndpoints(false)
return fmt.Errorf("%v %s", c.Name, resp)
r.Hmac = crypto.HexEncodeToString(hmac)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(r.Nonce, r)
if err != nil {
return err
}
respKey, err := jsonparser.GetUnsafeString(resp, "api_key")
if err != nil || respKey != creds.Key {
return errors.New("failed to authenticate")
}
c.Websocket.SetCanUseAuthenticatedEndpoints(true)
return nil
}
@@ -743,8 +712,7 @@ func (c *COINUT) wsGetAccountBalance() (*UserBalance, error) {
Request: "user_balance",
Nonce: getNonce(),
}
resp, err := c.Websocket.Conn.SendMessageReturnResponse(accBalance.Nonce,
accBalance)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(accBalance.Nonce, accBalance)
if err != nil {
return nil, err
}
@@ -780,8 +748,7 @@ func (c *COINUT) wsSubmitOrder(o *WsSubmitOrderParameters) (*order.Detail, error
if o.OrderID > 0 {
orderSubmissionRequest.OrderID = o.OrderID
}
resp, err := c.Websocket.Conn.SendMessageReturnResponse(orderSubmissionRequest.Nonce,
orderSubmissionRequest)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(orderSubmissionRequest.Nonce, orderSubmissionRequest)
if err != nil {
return nil, err
}
@@ -824,8 +791,7 @@ func (c *COINUT) wsSubmitOrders(orders []WsSubmitOrderParameters) ([]order.Detai
orderRequest.Nonce = getNonce()
orderRequest.Request = "new_orders"
resp, err := c.Websocket.Conn.SendMessageReturnResponse(orderRequest.Nonce,
orderRequest)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(orderRequest.Nonce, orderRequest)
if err != nil {
errs = append(errs, err)
return nil, errs
@@ -861,8 +827,7 @@ func (c *COINUT) wsGetOpenOrders(curr string) (*WsUserOpenOrdersResponse, error)
openOrdersRequest.Nonce = getNonce()
openOrdersRequest.InstrumentID = c.instrumentMap.LookupID(curr)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(openOrdersRequest.Nonce,
openOrdersRequest)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(openOrdersRequest.Nonce, openOrdersRequest)
if err != nil {
return response, err
}
@@ -895,8 +860,7 @@ func (c *COINUT) wsCancelOrder(cancellation *WsCancelOrderParameters) (*CancelOr
cancellationRequest.OrderID = cancellation.OrderID
cancellationRequest.Nonce = getNonce()
resp, err := c.Websocket.Conn.SendMessageReturnResponse(cancellationRequest.Nonce,
cancellationRequest)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(cancellationRequest.Nonce, cancellationRequest)
if err != nil {
return response, err
}
@@ -937,8 +901,7 @@ func (c *COINUT) wsCancelOrders(cancellations []WsCancelOrderParameters) (*Cance
cancelOrderRequest.Request = "cancel_orders"
cancelOrderRequest.Nonce = getNonce()
resp, err := c.Websocket.Conn.SendMessageReturnResponse(cancelOrderRequest.Nonce,
cancelOrderRequest)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(cancelOrderRequest.Nonce, cancelOrderRequest)
if err != nil {
return response, err
}
@@ -968,8 +931,7 @@ func (c *COINUT) wsGetTradeHistory(p currency.Pair, start, limit int64) (*WsTrad
request.Start = start
request.Limit = limit
resp, err := c.Websocket.Conn.SendMessageReturnResponse(request.Nonce,
request)
resp, err := c.Websocket.Conn.SendMessageReturnResponse(request.Nonce, request)
if err != nil {
return response, err
}