Files
atomic-swap/rpcclient/wsclient/wsclient.go
Dmitry Holodov 5698e25239 RPC and WS endpoints share the same port (#187)
Combine our HTTP RPC and Websocket services into a single HTTP server, eliminating the --ws-port flag to swapd and using --swapd-port flag for swapcli.
2022-09-10 14:54:43 -05:00

405 lines
9.2 KiB
Go

package wsclient
import (
"context"
"encoding/json"
"fmt"
"sync"
"github.com/athanorlabs/atomic-swap/common/rpctypes"
"github.com/athanorlabs/atomic-swap/common/types"
"github.com/gorilla/websocket"
logging "github.com/ipfs/go-log"
)
var log = logging.Logger("rpcclient")
// WsClient ...
type WsClient interface {
Close()
Discover(provides types.ProvidesCoin, searchTime uint64) ([][]string, error)
Query(maddr string) (*rpctypes.QueryPeerResponse, error)
SubscribeSwapStatus(id types.Hash) (<-chan types.Status, error)
TakeOfferAndSubscribe(multiaddr, offerID string, providesAmount float64) (ch <-chan types.Status, err error)
MakeOfferAndSubscribe(min, max float64,
exchangeRate types.ExchangeRate, ethAsset types.EthAsset) (string, <-chan types.Status, error)
}
type wsClient struct {
wmu sync.Mutex
rmu sync.Mutex
conn *websocket.Conn
}
// NewWsClient ...
func NewWsClient(ctx context.Context, endpoint string) (*wsClient, error) { ///nolint:revive
conn, resp, err := websocket.DefaultDialer.DialContext(ctx, endpoint, nil)
if err != nil {
return nil, fmt.Errorf("failed to dial WS endpoint: %w", err)
}
if err = resp.Body.Close(); err != nil {
return nil, err
}
return &wsClient{
conn: conn,
}, nil
}
func (c *wsClient) Close() {
_ = c.conn.Close()
}
func (c *wsClient) writeJSON(msg *rpctypes.Request) error {
c.wmu.Lock()
defer c.wmu.Unlock()
return c.conn.WriteJSON(msg)
}
func (c *wsClient) read() ([]byte, error) {
c.rmu.Lock()
defer c.rmu.Unlock()
_, message, err := c.conn.ReadMessage()
if err != nil {
return nil, err
}
return message, nil
}
func (c *wsClient) Discover(provides types.ProvidesCoin, searchTime uint64) ([][]string, error) {
params := &rpctypes.DiscoverRequest{
Provides: provides,
SearchTime: searchTime,
}
bz, err := json.Marshal(params)
if err != nil {
return nil, err
}
req := &rpctypes.Request{
JSONRPC: rpctypes.DefaultJSONRPCVersion,
Method: rpctypes.NetDiscover,
Params: bz,
ID: 0,
}
if err = c.writeJSON(req); err != nil {
return nil, err
}
message, err := c.read()
if err != nil {
return nil, fmt.Errorf("failed to read websockets message: %s", err)
}
var resp *rpctypes.Response
err = json.Unmarshal(message, &resp)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if resp.Error != nil {
return nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
}
log.Debugf("received message over websockets: %s", message)
var dresp *rpctypes.DiscoverResponse
if err := json.Unmarshal(resp.Result, &dresp); err != nil {
return nil, fmt.Errorf("failed to unmarshal swap ID response: %s", err)
}
return dresp.Peers, nil
}
func (c *wsClient) Query(maddr string) (*rpctypes.QueryPeerResponse, error) {
params := &rpctypes.QueryPeerRequest{
Multiaddr: maddr,
}
bz, err := json.Marshal(params)
if err != nil {
return nil, err
}
req := &rpctypes.Request{
JSONRPC: rpctypes.DefaultJSONRPCVersion,
Method: rpctypes.NetQueryPeer,
Params: bz,
ID: 0,
}
if err = c.writeJSON(req); err != nil {
return nil, err
}
// read ID from connection
message, err := c.read()
if err != nil {
return nil, fmt.Errorf("failed to read websockets message: %s", err)
}
var resp *rpctypes.Response
err = json.Unmarshal(message, &resp)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if resp.Error != nil {
return nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
}
log.Debugf("received message over websockets: %s", message)
var dresp *rpctypes.QueryPeerResponse
if err := json.Unmarshal(resp.Result, &dresp); err != nil {
return nil, fmt.Errorf("failed to unmarshal swap ID response: %s", err)
}
return dresp, nil
}
// SubscribeSwapStatus returns a channel that is written to each time the swap's status updates.
// If there is no swap with the given ID, it returns an error.
func (c *wsClient) SubscribeSwapStatus(id types.Hash) (<-chan types.Status, error) {
params := &rpctypes.SubscribeSwapStatusRequest{
ID: id,
}
bz, err := json.Marshal(params)
if err != nil {
return nil, err
}
req := &rpctypes.Request{
JSONRPC: rpctypes.DefaultJSONRPCVersion,
Method: rpctypes.SubscribeSwapStatus,
Params: bz,
ID: 0,
}
if err = c.writeJSON(req); err != nil {
return nil, err
}
respCh := make(chan types.Status)
go func() {
defer close(respCh)
for {
message, err := c.read()
if err != nil {
log.Warnf("failed to read websockets message: %s", err)
break
}
var resp *rpctypes.Response
err = json.Unmarshal(message, &resp)
if err != nil {
log.Warnf("failed to unmarshal response: %s", err)
break
}
if resp.Error != nil {
log.Warnf("websocket server returned error: %s", resp.Error)
break
}
log.Debugf("received message over websockets: %s", message)
var status *rpctypes.SubscribeSwapStatusResponse
if err := json.Unmarshal(resp.Result, &status); err != nil {
log.Warnf("failed to unmarshal response: %s", err)
break
}
s := types.NewStatus(status.Status)
respCh <- s
if !s.IsOngoing() {
return
}
}
}()
return respCh, nil
}
func (c *wsClient) TakeOfferAndSubscribe(multiaddr, offerID string,
providesAmount float64) (ch <-chan types.Status, err error) {
params := &rpctypes.TakeOfferRequest{
Multiaddr: multiaddr,
OfferID: offerID,
ProvidesAmount: providesAmount,
}
bz, err := json.Marshal(params)
if err != nil {
return nil, err
}
req := &rpctypes.Request{
JSONRPC: rpctypes.DefaultJSONRPCVersion,
Method: rpctypes.SubscribeTakeOffer,
Params: bz,
ID: 0,
}
if err = c.writeJSON(req); err != nil {
return nil, err
}
// read ID from connection
message, err := c.read()
if err != nil {
return nil, fmt.Errorf("failed to read websockets message: %s", err)
}
var resp *rpctypes.Response
err = json.Unmarshal(message, &resp)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if resp.Error != nil {
return nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
}
log.Debugf("received message over websockets: %s", message)
var idResp *rpctypes.TakeOfferResponse
if err := json.Unmarshal(resp.Result, &idResp); err != nil {
return nil, fmt.Errorf("failed to unmarshal swap ID response: %s", err)
}
respCh := make(chan types.Status)
go func() {
defer close(respCh)
for {
message, err := c.read()
if err != nil {
log.Warnf("failed to read websockets message: %s", err)
break
}
var resp *rpctypes.Response
err = json.Unmarshal(message, &resp)
if err != nil {
log.Warnf("failed to unmarshal response: %s", err)
break
}
if resp.Error != nil {
log.Warnf("websocket server returned error: %s", resp.Error)
break
}
log.Debugf("received message over websockets: %s", message)
var status *rpctypes.SubscribeSwapStatusResponse
if err := json.Unmarshal(resp.Result, &status); err != nil {
log.Warnf("failed to unmarshal swap status response: %s", err)
break
}
s := types.NewStatus(status.Status)
respCh <- s
if !s.IsOngoing() {
return
}
}
}()
return respCh, nil
}
func (c *wsClient) MakeOfferAndSubscribe(min, max float64,
exchangeRate types.ExchangeRate, ethAsset types.EthAsset) (string, <-chan types.Status, error) {
params := &rpctypes.MakeOfferRequest{
MinimumAmount: min,
MaximumAmount: max,
ExchangeRate: exchangeRate,
EthAsset: ethAsset.Address().String(),
}
bz, err := json.Marshal(params)
if err != nil {
return "", nil, err
}
req := &rpctypes.Request{
JSONRPC: rpctypes.DefaultJSONRPCVersion,
Method: rpctypes.SubscribeMakeOffer,
Params: bz,
ID: 0,
}
if err = c.writeJSON(req); err != nil {
return "", nil, err
}
// read ID from connection
message, err := c.read()
if err != nil {
return "", nil, fmt.Errorf("failed to read websockets message: %s", err)
}
var resp *rpctypes.Response
err = json.Unmarshal(message, &resp)
if err != nil {
return "", nil, fmt.Errorf("failed to unmarshal response: %w", err)
}
if resp.Error != nil {
return "", nil, fmt.Errorf("websocket server returned error: %w", resp.Error)
}
// read synchronous response (offer ID and infofile)
var respData *rpctypes.MakeOfferResponse
if err := json.Unmarshal(resp.Result, &respData); err != nil {
return "", nil, fmt.Errorf("failed to unmarshal response: %s", err)
}
respCh := make(chan types.Status)
go func() {
defer close(respCh)
for {
message, err := c.read()
if err != nil {
log.Warnf("failed to read websockets message: %s", err)
break
}
var resp *rpctypes.Response
err = json.Unmarshal(message, &resp)
if err != nil {
log.Warnf("failed to unmarshal response: %s", err)
break
}
if resp.Error != nil {
log.Warnf("websocket server returned error: %s", resp.Error)
break
}
log.Debugf("received message over websockets: %s", message)
var status *rpctypes.SubscribeSwapStatusResponse
if err := json.Unmarshal(resp.Result, &status); err != nil {
log.Warnf("failed to unmarshal response: %s", err)
break
}
s := types.NewStatus(status.Status)
respCh <- s
if !s.IsOngoing() {
return
}
}
}()
return respData.ID, respCh, nil
}