2020-11-09 05:53:09 -08:00
|
|
|
package rpc
|
|
|
|
|
|
|
|
import (
|
2020-11-09 06:57:03 -08:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2020-11-09 05:53:09 -08:00
|
|
|
"fmt"
|
2020-11-09 06:57:03 -08:00
|
|
|
"io"
|
|
|
|
"math/rand"
|
2020-11-09 09:39:51 -08:00
|
|
|
"reflect"
|
|
|
|
"sync"
|
2020-11-09 05:53:09 -08:00
|
|
|
|
|
|
|
"github.com/gorilla/rpc/v2/json2"
|
|
|
|
"github.com/gorilla/websocket"
|
2020-11-09 09:39:51 -08:00
|
|
|
"github.com/tidwall/gjson"
|
|
|
|
"go.uber.org/zap"
|
2020-11-09 05:53:09 -08:00
|
|
|
)
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
type reqID uint64
|
|
|
|
type subscription int
|
|
|
|
type result interface {
|
|
|
|
}
|
|
|
|
type callBack struct {
|
|
|
|
f func(result)
|
|
|
|
reflectType reflect.Type
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
type WSClient struct {
|
|
|
|
currentID uint64
|
|
|
|
rpcURL string
|
|
|
|
conn *websocket.Conn
|
|
|
|
lock sync.Mutex
|
|
|
|
pendingCallbacks map[reqID]*callBack
|
|
|
|
callbacks map[subscription]*callBack
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
func NewWSClient(rpcURL string) (*WSClient, error) {
|
|
|
|
c := &WSClient{
|
|
|
|
currentID: 0,
|
|
|
|
rpcURL: rpcURL,
|
|
|
|
pendingCallbacks: map[reqID]*callBack{},
|
|
|
|
callbacks: map[subscription]*callBack{},
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
2020-11-09 09:39:51 -08:00
|
|
|
|
|
|
|
conn, _, err := websocket.DefaultDialer.Dial(rpcURL, nil)
|
2020-11-09 05:53:09 -08:00
|
|
|
if err != nil {
|
2020-11-09 09:39:51 -08:00
|
|
|
return nil, fmt.Errorf("new ws client: dial: %w", err)
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
2020-11-09 09:39:51 -08:00
|
|
|
c.conn = conn
|
|
|
|
|
|
|
|
c.receiveMessages()
|
|
|
|
|
2020-11-09 05:53:09 -08:00
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
func (c *WSClient) receiveMessages() {
|
|
|
|
zlog.Info("ready to receive message")
|
|
|
|
go func() {
|
|
|
|
k := 0
|
|
|
|
for {
|
|
|
|
k++
|
|
|
|
_, message, err := c.conn.ReadMessage()
|
|
|
|
zlog.Info("")
|
|
|
|
if err != nil {
|
|
|
|
zlog.Error("message reception", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
//ioutil.WriteFile(fmt.Sprintf("/tmp/t%d.t", k), message, 775)
|
|
|
|
|
|
|
|
if gjson.GetBytes(message, "id").Exists() {
|
|
|
|
id := reqID(gjson.GetBytes(message, "id").Int())
|
|
|
|
sub := subscription(gjson.GetBytes(message, "result").Int())
|
|
|
|
|
|
|
|
c.lock.Lock()
|
|
|
|
callBack := c.pendingCallbacks[id]
|
|
|
|
delete(c.pendingCallbacks, id)
|
|
|
|
c.callbacks[sub] = callBack
|
|
|
|
c.lock.Unlock()
|
|
|
|
|
|
|
|
zlog.Info("move sub from pending to callback", zap.Uint64("id", uint64(id)), zap.Uint64("subscription", uint64(sub)))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
sub := subscription(gjson.GetBytes(message, "params.subscription").Int())
|
|
|
|
cb := c.callbacks[sub]
|
|
|
|
|
|
|
|
result := reflect.New(cb.reflectType)
|
|
|
|
i := result.Interface()
|
|
|
|
err = decodeClientResponse(bytes.NewReader(message), &i)
|
|
|
|
if err != nil {
|
|
|
|
zlog.Error("failed to decode result", zap.Uint64("subscription", uint64(sub)), zap.Error(err))
|
|
|
|
}
|
|
|
|
cb.f(i)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
type ProgramResult struct {
|
|
|
|
Context struct {
|
|
|
|
Slot uint64
|
|
|
|
} `json:"context"`
|
|
|
|
Value struct {
|
|
|
|
Account Account `json:"account"`
|
|
|
|
} `json:"value"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *WSClient) ProgramSubscribe(programID string, commitment CommitmentType, resultCallBack func(programResult result)) error {
|
|
|
|
c.lock.Lock()
|
|
|
|
defer c.lock.Unlock()
|
|
|
|
|
2020-11-09 05:53:09 -08:00
|
|
|
params := []interface{}{programID}
|
|
|
|
conf := map[string]interface{}{
|
2020-11-09 06:57:03 -08:00
|
|
|
"encoding": "jsonParsed",
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
|
|
|
if commitment != "" {
|
|
|
|
conf["commitment"] = string(commitment)
|
|
|
|
}
|
|
|
|
|
|
|
|
params = append(params, conf)
|
2020-11-09 09:39:51 -08:00
|
|
|
data, id, err := encodeClientRequest("programSubscribe", params)
|
2020-11-09 05:53:09 -08:00
|
|
|
if err != nil {
|
2020-11-09 09:39:51 -08:00
|
|
|
return fmt.Errorf("program subscribe: encode request: %c", err)
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
err = c.conn.WriteMessage(websocket.TextMessage, data)
|
2020-11-09 05:53:09 -08:00
|
|
|
if err != nil {
|
2020-11-09 09:39:51 -08:00
|
|
|
return fmt.Errorf("program subscribe: write message: %c", err)
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
c.pendingCallbacks[reqID(id)] = &callBack{
|
|
|
|
f: resultCallBack,
|
|
|
|
reflectType: reflect.TypeOf(ProgramResult{}),
|
2020-11-09 05:53:09 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-11-09 06:57:03 -08:00
|
|
|
|
|
|
|
type clientRequest struct {
|
2020-11-09 09:39:51 -08:00
|
|
|
Version string `json:"jsonrpc"`
|
|
|
|
Method string `json:"method"`
|
|
|
|
Params interface{} `json:"params"`
|
|
|
|
Id uint64 `json:"id"`
|
2020-11-09 06:57:03 -08:00
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
func encodeClientRequest(method string, args interface{}) ([]byte, uint64, error) {
|
2020-11-09 06:57:03 -08:00
|
|
|
c := &clientRequest{
|
|
|
|
Version: "2.0",
|
|
|
|
Method: method,
|
|
|
|
Params: args,
|
|
|
|
Id: uint64(rand.Int63()),
|
|
|
|
}
|
2020-11-09 09:39:51 -08:00
|
|
|
data, err := json.Marshal(c)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, fmt.Errorf("encode request: json marshall: %w", err)
|
|
|
|
}
|
|
|
|
return data, c.Id, nil
|
2020-11-09 06:57:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
type wsClientResponse struct {
|
|
|
|
Version string `json:"jsonrpc"`
|
|
|
|
Params *wsClientResponseParams `json:"params"`
|
|
|
|
Error *json.RawMessage `json:"error"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type wsClientResponseParams struct {
|
|
|
|
Result *json.RawMessage `json:"result"`
|
|
|
|
Subscription int `json:"subscription"`
|
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
func decodeClientResponse(r io.Reader, reply interface{}) (err error) {
|
2020-11-09 06:57:03 -08:00
|
|
|
var c *wsClientResponse
|
|
|
|
if err := json.NewDecoder(r).Decode(&c); err != nil {
|
2020-11-09 09:39:51 -08:00
|
|
|
return err
|
2020-11-09 06:57:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.Error != nil {
|
|
|
|
jsonErr := &json2.Error{}
|
|
|
|
if err := json.Unmarshal(*c.Error, jsonErr); err != nil {
|
2020-11-09 09:39:51 -08:00
|
|
|
return &json2.Error{
|
2020-11-09 06:57:03 -08:00
|
|
|
Code: json2.E_SERVER,
|
|
|
|
Message: string(*c.Error),
|
|
|
|
}
|
|
|
|
}
|
2020-11-09 09:39:51 -08:00
|
|
|
return jsonErr
|
2020-11-09 06:57:03 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
if c.Params == nil {
|
2020-11-09 09:39:51 -08:00
|
|
|
return json2.ErrNullResult
|
2020-11-09 06:57:03 -08:00
|
|
|
}
|
|
|
|
|
2020-11-09 09:39:51 -08:00
|
|
|
return json.Unmarshal(*c.Params.Result, &reply)
|
2020-11-09 06:57:03 -08:00
|
|
|
}
|