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.
This commit is contained in:
Dmitry Holodov
2022-09-10 14:54:43 -05:00
committed by GitHub
parent 476bc69b82
commit 5698e25239
22 changed files with 484 additions and 388 deletions

View File

@@ -1,28 +1,32 @@
package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/ethereum/go-ethereum/common"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/urfave/cli/v2"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/types"
"github.com/athanorlabs/atomic-swap/rpcclient"
"github.com/athanorlabs/atomic-swap/rpcclient/wsclient"
)
const (
defaultSwapdAddress = "http://127.0.0.1:5001"
defaultSwapdPort = 5001
defaultDiscoverSearchTimeSecs = 12
flagSwapdPort = "swapd-port"
)
var (
app = &cli.App{
Name: "swapcli",
Usage: "Client for swapd",
Name: "swapcli",
Usage: "Client for swapd",
EnableBashCompletion: true,
Suggest: true,
Commands: []*cli.Command{
{
Name: "addresses",
@@ -30,7 +34,7 @@ var (
Usage: "List our daemon's libp2p listening addresses",
Action: runAddresses,
Flags: []cli.Flag{
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -50,7 +54,7 @@ var (
Usage: "Duration of time to search for, in seconds",
Value: defaultDiscoverSearchTimeSecs,
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -64,7 +68,7 @@ var (
Usage: "Peer's multiaddress, as provided by discover",
Required: true,
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -84,7 +88,7 @@ var (
Usage: "Duration of time to search for, in seconds",
Value: defaultDiscoverSearchTimeSecs,
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -116,7 +120,7 @@ var (
Name: "eth-asset",
Usage: "Ethereum ERC-20 token address to receive, or the zero address for regular ETH",
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -144,14 +148,14 @@ var (
Name: "subscribe",
Usage: "Subscribe to push notifications about the swap's status",
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
Name: "get-past-swap-ids",
Usage: "Get past swap IDs",
Action: runGetPastSwapIDs,
Flags: []cli.Flag{daemonAddrFlag},
Flags: []cli.Flag{swapdPortFlag},
},
{
Name: "get-ongoing-swap",
@@ -163,7 +167,7 @@ var (
Usage: "ID of swap to retrieve info for",
Required: true,
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -176,7 +180,7 @@ var (
Usage: "ID of swap to retrieve info for",
Required: true,
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -189,7 +193,7 @@ var (
Usage: "ID of swap to retrieve info for",
Required: true,
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -201,7 +205,7 @@ var (
Name: "offer-id",
Usage: "ID of swap to retrieve info for",
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -213,7 +217,7 @@ var (
Name: "offer-ids",
Usage: "A comma-separated list of offer IDs to delete",
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -226,7 +230,7 @@ var (
Usage: "ID of swap to retrieve info for",
Required: true,
},
daemonAddrFlag,
swapdPortFlag,
},
},
{
@@ -239,17 +243,17 @@ var (
Usage: "Duration of timeout, in seconds",
Required: true,
},
daemonAddrFlag,
swapdPortFlag,
},
},
},
Flags: []cli.Flag{daemonAddrFlag},
}
daemonAddrFlag = &cli.StringFlag{
Name: "daemon-addr",
Usage: "Address of swap daemon",
Value: defaultSwapdAddress,
swapdPortFlag = &cli.UintFlag{
Name: flagSwapdPort,
Usage: "RPC port of swap daemon",
Value: defaultSwapdPort,
EnvVars: []string{"SWAPD_PORT"},
}
)
@@ -260,13 +264,20 @@ func main() {
}
}
func runAddresses(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
if endpoint == "" {
endpoint = defaultSwapdAddress
}
func newRRPClient(ctx *cli.Context) *rpcclient.Client {
swapdPort := ctx.Uint(flagSwapdPort)
endpoint := fmt.Sprintf("http://127.0.0.1:%d", swapdPort)
return rpcclient.NewClient(endpoint)
}
c := rpcclient.NewClient(endpoint)
func newWSClient(ctx *cli.Context) (wsclient.WsClient, error) {
swapdPort := ctx.Uint(flagSwapdPort)
endpoint := fmt.Sprintf("ws://127.0.0.1:%d/ws", swapdPort)
return wsclient.NewWsClient(ctx.Context, endpoint)
}
func runAddresses(ctx *cli.Context) error {
c := newRRPClient(ctx)
addrs, err := c.Addresses()
if err != nil {
return err
@@ -282,10 +293,9 @@ func runDiscover(ctx *cli.Context) error {
return err
}
endpoint := ctx.String("daemon-addr")
searchTime := ctx.Uint("search-time")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
peers, err := c.Discover(provides, uint64(searchTime))
if err != nil {
return err
@@ -300,9 +310,8 @@ func runDiscover(ctx *cli.Context) error {
func runQuery(ctx *cli.Context) error {
maddr := ctx.String("multiaddr")
endpoint := ctx.String("daemon-addr")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
res, err := c.Query(maddr)
if err != nil {
return err
@@ -320,14 +329,9 @@ func runQueryAll(ctx *cli.Context) error {
return err
}
endpoint := ctx.String("daemon-addr")
if endpoint == "" {
endpoint = defaultSwapdAddress
}
searchTime := ctx.Uint("search-time")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
peers, err := c.QueryAll(provides, uint64(searchTime))
if err != nil {
return err
@@ -360,27 +364,41 @@ func runMake(ctx *cli.Context) error {
if exchangeRate == 0 {
return errNoExchangeRate
}
endpoint := ctx.String("daemon-addr")
otherMin := min * exchangeRate
otherMax := max * exchangeRate
ethAssetStr := ctx.String("eth-asset")
ethAsset := types.EthAssetETH
if ethAssetStr != "" {
ethAsset = types.EthAsset(common.HexToAddress(ethAssetStr))
ethAsset = types.EthAsset(ethcommon.HexToAddress(ethAssetStr))
}
c := newRRPClient(ctx)
ourAddresses, err := c.Addresses()
if err != nil {
return err
}
printOfferSummary := func(offerID string) {
fmt.Printf("Published offer with ID: %s\n", offerID)
fmt.Printf("On addresses: %v\n", ourAddresses)
fmt.Printf("Takers can provide between %s to %s %s\n",
common.FmtFloat(otherMin), common.FmtFloat(otherMax), ethAsset)
}
if ctx.Bool("subscribe") {
c, err := wsclient.NewWsClient(context.Background(), endpoint)
wsc, err := newWSClient(ctx) //nolint:govet
if err != nil {
return err
}
defer wsc.Close()
id, statusCh, err := wsc.MakeOfferAndSubscribe(min, max, types.ExchangeRate(exchangeRate), ethAsset)
if err != nil {
return err
}
id, statusCh, err := c.MakeOfferAndSubscribe(min, max, types.ExchangeRate(exchangeRate), ethAsset)
if err != nil {
return err
}
fmt.Printf("Made offer with ID %s\n", id)
printOfferSummary(id)
for stage := range statusCh {
fmt.Printf("> Stage updated: %s\n", stage)
@@ -392,18 +410,12 @@ func runMake(ctx *cli.Context) error {
return nil
}
c := rpcclient.NewClient(endpoint)
id, err := c.MakeOffer(min, max, exchangeRate, ethAsset)
if err != nil {
return err
}
fmt.Printf("Published offer with ID %s\n", id)
addrs, err := c.Addresses()
if err != nil {
return err
}
fmt.Printf("On addresses: %v\n", addrs)
printOfferSummary(id)
return nil
}
@@ -415,15 +427,14 @@ func runTake(ctx *cli.Context) error {
return errNoProvidesAmount
}
endpoint := ctx.String("daemon-addr")
if ctx.Bool("subscribe") {
c, err := wsclient.NewWsClient(context.Background(), endpoint)
wsc, err := newWSClient(ctx)
if err != nil {
return err
}
defer wsc.Close()
statusCh, err := c.TakeOfferAndSubscribe(maddr, offerID, providesAmount)
statusCh, err := wsc.TakeOfferAndSubscribe(maddr, offerID, providesAmount)
if err != nil {
return err
}
@@ -440,7 +451,7 @@ func runTake(ctx *cli.Context) error {
return nil
}
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
err := c.TakeOffer(maddr, offerID, providesAmount)
if err != nil {
return err
@@ -451,12 +462,7 @@ func runTake(ctx *cli.Context) error {
}
func runGetPastSwapIDs(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
if endpoint == "" {
endpoint = defaultSwapdAddress
}
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
ids, err := c.GetPastSwapIDs()
if err != nil {
return err
@@ -467,10 +473,9 @@ func runGetPastSwapIDs(ctx *cli.Context) error {
}
func runGetOngoingSwap(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
offerID := ctx.String("offer-id")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
info, err := c.GetOngoingSwap(offerID)
if err != nil {
return err
@@ -487,10 +492,9 @@ func runGetOngoingSwap(ctx *cli.Context) error {
}
func runGetPastSwap(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
offerID := ctx.String("offer-id")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
info, err := c.GetPastSwap(offerID)
if err != nil {
return err
@@ -507,10 +511,9 @@ func runGetPastSwap(ctx *cli.Context) error {
}
func runRefund(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
offerID := ctx.String("offer-id")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
resp, err := c.Refund(offerID)
if err != nil {
return err
@@ -521,10 +524,9 @@ func runRefund(ctx *cli.Context) error {
}
func runCancel(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
offerID := ctx.String("offer-id")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
resp, err := c.Cancel(offerID)
if err != nil {
return err
@@ -535,8 +537,7 @@ func runCancel(ctx *cli.Context) error {
}
func runClearOffers(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
ids := ctx.String("offer-ids")
if ids == "" {
@@ -559,10 +560,9 @@ func runClearOffers(ctx *cli.Context) error {
}
func runGetStage(ctx *cli.Context) error {
endpoint := ctx.String("daemon-addr")
offerID := ctx.String("offer-id")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
resp, err := c.GetStage(offerID)
if err != nil {
return err
@@ -577,9 +577,8 @@ func runSetSwapTimeout(ctx *cli.Context) error {
if duration == 0 {
return errNoDuration
}
endpoint := ctx.String("daemon-addr")
c := rpcclient.NewClient(endpoint)
c := newRRPClient(ctx)
err := c.SetSwapTimeout(uint64(duration))
if err != nil {
return err

View File

@@ -40,10 +40,6 @@ const (
defaultRPCPort = 5005
defaultXMRTakerRPCPort = 5001
defaultXMRMakerRPCPort = 5002
defaultWSPort = 6005
defaultXMRTakerWSPort = 8081
defaultXMRMakerWSPort = 8082
)
var (
@@ -59,7 +55,6 @@ var (
const (
flagRPCPort = "rpc-port"
flagWSPort = "ws-port"
flagDataDir = "data-dir"
flagLibp2pKey = "libp2p-key"
flagLibp2pPort = "libp2p-port"
@@ -88,20 +83,17 @@ const (
var (
app = &cli.App{
Name: "swapd",
Usage: "A program for doing atomic swaps between ETH and XMR",
Action: runDaemon,
Name: "swapd",
Usage: "A program for doing atomic swaps between ETH and XMR",
Action: runDaemon,
EnableBashCompletion: true,
Suggest: true,
Flags: []cli.Flag{
&cli.UintFlag{
Name: flagRPCPort,
Usage: "Port for the daemon RPC server to run on",
Value: defaultRPCPort,
},
&cli.UintFlag{
Name: flagWSPort,
Usage: "Port for the daemon RPC websockets server to run on",
Value: defaultWSPort,
},
&cli.StringFlag{
Name: flagDataDir,
Usage: "Path to store swap artifacts", //nolint:misspell
@@ -198,8 +190,7 @@ var (
func main() {
if err := app.Run(os.Args); err != nil {
log.Error(err)
os.Exit(1)
log.Fatal(err)
}
}
@@ -387,21 +378,11 @@ func (d *daemon) make(c *cli.Context) error {
rpcPort = defaultXMRMakerRPCPort
}
}
wsPort := uint16(c.Uint(flagWSPort))
if !c.IsSet(flagWSPort) {
switch {
case devXMRTaker:
wsPort = defaultXMRTakerWSPort
case devXMRMaker:
wsPort = defaultXMRMakerWSPort
}
}
listenAddr := fmt.Sprintf("127.0.0.1:%d", rpcPort)
rpcCfg := &rpc.Config{
Ctx: d.ctx,
Port: rpcPort,
WsPort: wsPort,
Address: listenAddr,
Net: host,
XMRTaker: a,
XMRMaker: b,
@@ -413,20 +394,8 @@ func (d *daemon) make(c *cli.Context) error {
return err
}
errCh := s.Start()
go func() {
select {
case <-d.ctx.Done():
return
case err := <-errCh:
log.Errorf("failed to start RPC server: %s", err)
d.cancel()
os.Exit(1)
}
}()
log.Infof("started swapd with data-dir %s", cfg.DataDir)
return nil
log.Infof("starting swapd with data-dir %s", cfg.DataDir)
return s.Start()
}
func errFlagsMutuallyExclusive(flag1, flag2 string) error {

View File

@@ -4,7 +4,9 @@ import (
"context"
"flag"
"fmt"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
@@ -65,8 +67,17 @@ func TestDaemon_DevXMRTaker(t *testing.T) {
cancel: cancel,
}
err := d.make(c)
require.NoError(t, err)
var wg sync.WaitGroup
wg.Add(1)
go func() {
err := d.make(c) // blocks on RPC server start
require.ErrorIs(t, err, context.Canceled)
wg.Done()
}()
time.Sleep(500 * time.Millisecond) // let the server start
cancel()
wg.Wait()
}
func TestDaemon_DevXMRMaker(t *testing.T) {
@@ -88,8 +99,17 @@ func TestDaemon_DevXMRMaker(t *testing.T) {
cancel: cancel,
}
err := d.make(c)
require.NoError(t, err)
var wg sync.WaitGroup
wg.Add(1)
go func() {
err := d.make(c) // blocks on RPC server start
require.ErrorIs(t, err, context.Canceled)
wg.Done()
}()
time.Sleep(500 * time.Millisecond) // let the server start
cancel()
wg.Wait()
}
func Test_expandBootnodes(t *testing.T) {

View File

@@ -48,9 +48,11 @@ var (
var (
app = &cli.App{
Name: "swaprecover",
Usage: "A program for recovering swap funds due to unexpected shutdowns",
Action: runRecover,
Name: "swaprecover",
Usage: "A program for recovering swap funds due to unexpected shutdowns",
Action: runRecover,
EnableBashCompletion: true,
Suggest: true,
Flags: []cli.Flag{
&cli.StringFlag{
Name: flagEnv,

View File

@@ -3,6 +3,7 @@ package common
import (
"math"
"math/big"
"strconv"
)
var (
@@ -73,3 +74,9 @@ func (a EtherAmount) ToDecimals(decimals uint8) float64 {
func (a EtherAmount) String() string {
return a.BigInt().String()
}
// FmtFloat creates a string from a floating point value that keeps maximum precision,
// does not use exponent notation, and has no trailing zeros after the decimal point.
func FmtFloat(f float64) string {
return strconv.FormatFloat(f, 'f', -1, 64)
}

View File

@@ -29,7 +29,7 @@ func TestEtherAmount(t *testing.T) {
func TestToDecimals(t *testing.T) {
val := NewEtherAmount(123456)
require.Equal(t, fmt.Sprint(val.ToDecimals(5)), "1.23456")
require.Equal(t, "1.23456", FmtFloat(val.ToDecimals(5)))
val = NewEtherAmount(1234567890)
require.Equal(t, fmt.Sprint(val.ToDecimals(6)), "1234.56789")
require.Equal(t, "1234.56789", FmtFloat(val.ToDecimals(6)))
}

View File

@@ -34,7 +34,7 @@ Create a wallet for "Bob", who will own XMR later on:
./monero-wallet-cli // you will be prompted to create a wallet. In the next steps, we will go with "Bob", without password. Remember the name and optionally the password for the upcoming steps
```
You do not need to mine blocks, and you can exit the the wallet-cli once Bob's account has been created by typing "exit".
You do not need to mine blocks, and you can exit the wallet-cli once Bob's account has been created by typing "exit".
Start monero-wallet-rpc for Bob on port 18083. Make sure `--wallet-dir` corresponds to the directory the wallet from the previous step is in:
```bash
@@ -98,13 +98,13 @@ In terminal 3, we will interact with the swap daemon using `swapcli`.
Firstly, we need Bob to make an offer and advertise it, so that Alice can take it:
```bash
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.05 --daemon-addr=http://localhost:5002
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.05 --swapd-port 5002
# Published offer with ID cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9
```
Alternatively, you can make the offer via websockets and get notified when the swap is taken:
```bash
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.05 --daemon-addr=ws://localhost:8082 --subscribe
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.05 --swapd-port 5002 --subscribe
```
Now, we can have Alice begin discovering peers who have offers advertised.
@@ -127,7 +127,7 @@ Now, we can tell Alice to initiate the protocol w/ the peer (Bob), the offer (co
Alternatively, you can take the offer via websockets and get notified when the swap status updates:
```bash
./swapcli take --multiaddr /ip4/127.0.0.1/tcp/9934/p2p/12D3KooWHLUrLnJtUbaGzTSi6azZavKhNgUZTtSiUZ9Uy12v1eZ7 --offer-id cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9 --provides-amount 0.05 --subscribe --daemon-addr=ws://localhost:8081
./swapcli take --multiaddr /ip4/127.0.0.1/tcp/9934/p2p/12D3KooWHLUrLnJtUbaGzTSi6azZavKhNgUZTtSiUZ9Uy12v1eZ7 --offer-id cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9 --provides-amount 0.05 --subscribe --swapd-port 5001
```
If all goes well, you should see Alice and Bob successfully exchange messages and execute the swap protocol. The result is that Alice now owns the private key to a Monero account (and is the only owner of that key) and Bob has the ETH transferred to him. On Alice's side, a Monero wallet will be generated in the `--wallet-dir` provided in the `monero-wallet-rpc` step for Alice.

View File

@@ -258,7 +258,7 @@ Returns:
Example:
```bash
wscat -c ws://localhost:8081
wscat -c ws://localhost:5001/ws
# Connected (press CTRL+C to quit)
# > {"jsonrpc":"2.0", "method":"swap_subscribeStatus", "params": {"id": "7492ceb4d0f5f45ecd5d06923b35cae406d1406cd685ce1ba184f2a40c683ac2"}, "id": 0}
# < {"jsonrpc":"2.0","result":{"stage":"ETHLocked"},"error":null,"id":null}
@@ -282,7 +282,7 @@ Returns:
Example (including notifications when swap is taken):
```bash
wscat -c ws://localhost:8082
wscat -c ws://localhost:5002/ws
# Connected (press CTRL+C to quit)
# > {"jsonrpc":"2.0", "method":"net_makeOfferAndSubscribe", "params": {"minimumAmount": 0.1, "maximumAmount": 1, "exchangeRate": 0.05}, "id": 0}
# < {"jsonrpc":"2.0","result":{"offerID":"cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9"},"error":null,"id":null}
@@ -308,7 +308,7 @@ Returns:
Example:
```bash
wscat -c ws://localhost:8081
wscat -c ws://localhost:5001/ws
# Connected (press CTRL+C to quit)
# > {"jsonrpc":"2.0", "method":"net_takeOfferAndSubscribe", "params": {"multiaddr": "/ip4/192.168.0.101/tcp/9934/p2p/12D3KooWHLUrLnJtUbaGzTSi6azZavKhNgUZTtSiUZ9Uy12v1eZ7", "offerID": "cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9", "providesAmount": 0.05}, "id": 0}
# < {"jsonrpc":"2.0","result":{"id":0},"error":null,"id":null}
@@ -316,4 +316,4 @@ wscat -c ws://localhost:8081
# < {"jsonrpc":"2.0","result":{"stage":"ETHLocked"},"error":null,"id":null}
# < {"jsonrpc":"2.0","result":{"stage":"ContractReady"},"error":null,"id":null}
# < {"jsonrpc":"2.0","result":{"stage":"Success"},"error":null,"id":null}
```
```

View File

@@ -93,7 +93,7 @@ yarn start
1. Search for existing XMR offers using `swapcli`:
```bash
./swapcli discover --provides XMR --search-time 3 --daemon-addr=http://localhost:5005
./swapcli discover --provides XMR --search-time 3 --swapd-port 5001
# [[/ip4/127.0.0.1/tcp/9934/p2p/12D3KooWC547RfLcveQi1vBxACjnT6Uv15V11ortDTuxRWuhubGv /ip4/127.0.0.1/tcp/9934/p2p/12D3KooWC547RfLcveQi1vBxACjnT6Uv15V11ortDTuxRWuhubGv]]
```
@@ -113,7 +113,7 @@ yarn start
3. b. Alternatively, you can take the offer via websockets and get notified when the swap status updates:
```bash
./swapcli take --multiaddr /ip4/127.0.0.1/tcp/9934/p2p/12D3KooWHLUrLnJtUbaGzTSi6azZavKhNgUZTtSiUZ9Uy12v1eZ7 --offer-id cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9 --provides-amount 0.05 --subscribe --daemon-addr=ws://localhost:8081
./swapcli take --multiaddr /ip4/127.0.0.1/tcp/9934/p2p/12D3KooWHLUrLnJtUbaGzTSi6azZavKhNgUZTtSiUZ9Uy12v1eZ7 --offer-id cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9 --provides-amount 0.05 --subscribe --swapd-port 5001
```
If all goes well, you should see the node execute the swap protocol. If the swap ends successfully, a Monero wallet will be generated in the `--wallet-dir` provided in the `monero-wallet-rpc` step (so `./node-keys`) named `swap-deposit-wallet`. This wallet will contained the received XMR.
@@ -140,13 +140,13 @@ If you don't have any luck with these, please message me on twitter/reddit (@eli
4. a. Make an offer with `swapcli`:
```bash
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.5 --daemon-addr http://localhost:5005
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.5 --swapd-port 5001
# Published offer with ID cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9
```
4. b. Alternatively, make an offer and subscribe to updates on it with `swapcli`:
```bash
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.5 --daemon-addr ws://localhost:6005 --subscribe
./swapcli make --min-amount 0.1 --max-amount 1 --exchange-rate 0.5 --swapd-port 5001 --subscribe
# Published offer with ID cf4bf01a0775a0d13fa41b14516e4b89034300707a1754e0d99b65f6cb6fffb9
```

View File

@@ -25,9 +25,9 @@ func TestClient_Transfer(t *testing.T) {
require.NoError(t, err)
daemon := NewDaemonClient(common.DefaultMoneroDaemonEndpoint)
_ = daemon.GenerateBlocks(xmrmakerAddr.Address, 512)
time.Sleep(time.Second * 10)
err = daemon.GenerateBlocks(xmrmakerAddr.Address, 512)
require.NoError(t, err)
require.NoError(t, cXMRMaker.Refresh())
balance, err := cXMRMaker.GetBalance(0)
require.NoError(t, err)
@@ -76,12 +76,14 @@ func TestClient_Transfer(t *testing.T) {
break
}
_ = daemon.GenerateBlocks(xmrmakerAddr.Address, 1)
time.Sleep(time.Second)
err = daemon.GenerateBlocks(xmrmakerAddr.Address, 1)
require.NoError(t, err)
require.NoError(t, cXMRMaker.Refresh())
}
err = daemon.GenerateBlocks(xmrmakerAddr.Address, 16)
require.NoError(t, err)
require.NoError(t, cXMRMaker.Refresh())
// generate spend account for A+B
skAKPriv := mcrypto.SumPrivateSpendKeys(kpA.SpendKey(), kpB.SpendKey())

View File

@@ -37,6 +37,19 @@ func newDiscovery(ctx context.Context, h libp2phost.Host, bnsFunc func() []peer.
dual.DHTOption(kaddht.Mode(kaddht.ModeAutoServer)),
}
//
// There is libp2p bug when calling `dual.New` with a cancelled context creating a panic,
// so we added the extra guard below:
// Panic: https://github.com/jbenet/goprocess/blob/v0.1.4/impl-mutex.go#L99
// Caller: https://github.com/libp2p/go-libp2p-kad-dht/blob/v0.17.0/dht.go#L222
//
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
// not cancelled, continue on
}
dht, err := dual.New(ctx, h, dhtOpts...)
if err != nil {
return nil, err

View File

@@ -4,6 +4,8 @@ import (
"errors"
"fmt"
"strconv"
"github.com/athanorlabs/atomic-swap/common"
)
var (
@@ -41,8 +43,8 @@ type errBalanceTooLow struct {
func (e errBalanceTooLow) Error() string {
return fmt.Sprintf("balance of %s XMR is below provided %s XMR",
strconv.FormatFloat(e.unlockedBalance, 'f', -1, 64),
strconv.FormatFloat(e.providedAmount, 'f', -1, 64),
common.FmtFloat(e.unlockedBalance),
common.FmtFloat(e.providedAmount),
)
}
@@ -53,8 +55,8 @@ type errAmountProvidedTooLow struct {
func (e errAmountProvidedTooLow) Error() string {
return fmt.Sprintf("%s XMR provided by taker is under offer minimum of %s XMR",
strconv.FormatFloat(e.providedAmount, 'f', -1, 64),
strconv.FormatFloat(e.minAmount, 'f', -1, 64),
common.FmtFloat(e.providedAmount),
common.FmtFloat(e.minAmount),
)
}
@@ -65,8 +67,8 @@ type errAmountProvidedTooHigh struct {
func (e errAmountProvidedTooHigh) Error() string {
return fmt.Sprintf("%s XMR provided by taker is over offer maximum of %s XMR",
strconv.FormatFloat(e.providedAmount, 'f', -1, 64),
strconv.FormatFloat(e.maxAmount, 'f', -1, 64),
common.FmtFloat(e.providedAmount),
common.FmtFloat(e.maxAmount),
)
}

208
rpc/mocks_test.go Normal file
View File

@@ -0,0 +1,208 @@
package rpc
import (
"encoding/json"
"fmt"
"os"
"time"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/types"
mcrypto "github.com/athanorlabs/atomic-swap/crypto/monero"
"github.com/athanorlabs/atomic-swap/net"
"github.com/athanorlabs/atomic-swap/net/message"
"github.com/athanorlabs/atomic-swap/protocol/swap"
"github.com/athanorlabs/atomic-swap/protocol/txsender"
)
//
// This file only contains mock definitions used by other test files
//
type mockNet struct{}
func (*mockNet) Addresses() []string {
panic("not implemented")
}
func (*mockNet) Advertise() {
}
func (*mockNet) Discover(provides types.ProvidesCoin, searchTime time.Duration) ([]peer.AddrInfo, error) {
return nil, nil
}
func (*mockNet) Query(who peer.AddrInfo) (*net.QueryResponse, error) {
var offer types.Offer
offerJSON := fmt.Sprintf(`{"ID":%q}`, testSwapID.String())
if err := json.Unmarshal([]byte(offerJSON), &offer); err != nil {
panic(err)
}
return &net.QueryResponse{Offers: []*types.Offer{&offer}}, nil
}
func (*mockNet) Initiate(who peer.AddrInfo, msg *net.SendKeysMessage, s common.SwapStateNet) error {
return nil
}
func (*mockNet) CloseProtocolStream(types.Hash) {
panic("not implemented")
}
type mockSwapManager struct{}
func (*mockSwapManager) GetPastIDs() []types.Hash {
panic("not implemented")
}
func (*mockSwapManager) GetPastSwap(id types.Hash) *swap.Info {
return &swap.Info{}
}
func (*mockSwapManager) GetOngoingSwap(id types.Hash) *swap.Info {
statusCh := make(chan types.Status, 1)
statusCh <- types.CompletedSuccess
return swap.NewInfo(
id,
types.ProvidesETH,
1,
1,
1,
types.EthAssetETH,
types.CompletedSuccess,
statusCh,
)
}
func (*mockSwapManager) AddSwap(*swap.Info) error {
panic("not implemented")
}
func (*mockSwapManager) CompleteOngoingSwap(types.Hash) {
panic("not implemented")
}
type mockXMRTaker struct{}
func (*mockXMRTaker) Provides() types.ProvidesCoin {
panic("not implemented")
}
func (*mockXMRTaker) SetGasPrice(gasPrice uint64) {
panic("not implemented")
}
func (*mockXMRTaker) GetOngoingSwapState(types.Hash) common.SwapState {
return new(mockSwapState)
}
func (*mockXMRTaker) InitiateProtocol(providesAmount float64, _ *types.Offer) (common.SwapState, error) {
return new(mockSwapState), nil
}
func (*mockXMRTaker) Refund(types.Hash) (ethcommon.Hash, error) {
panic("not implemented")
}
func (*mockXMRTaker) SetSwapTimeout(_ time.Duration) {
panic("not implemented")
}
func (*mockXMRTaker) ExternalSender(_ types.Hash) (*txsender.ExternalSender, error) {
panic("not implemented")
}
type mockXMRMaker struct{}
func (m *mockXMRMaker) Provides() types.ProvidesCoin {
panic("not implemented")
}
func (m *mockXMRMaker) GetOngoingSwapState(hash types.Hash) common.SwapState {
panic("not implemented")
}
func (*mockXMRMaker) MakeOffer(offer *types.Offer) (*types.OfferExtra, error) {
offerExtra := &types.OfferExtra{
StatusCh: make(chan types.Status, 1),
InfoFile: "/dev/null",
}
offerExtra.StatusCh <- types.CompletedSuccess
return offerExtra, nil
}
func (*mockXMRMaker) SetMoneroWalletFile(file string, password string) error {
panic("not implemented")
}
func (*mockXMRMaker) GetOffers() []*types.Offer {
panic("not implemented")
}
func (*mockXMRMaker) ClearOffers([]string) error {
panic("not implemented")
}
type mockSwapState struct{}
func (*mockSwapState) HandleProtocolMessage(msg message.Message) (resp message.Message, done bool, err error) {
return nil, true, nil
}
func (*mockSwapState) Exit() error {
return nil
}
func (*mockSwapState) SendKeysMessage() (*message.SendKeysMessage, error) {
return &message.SendKeysMessage{}, nil
}
func (*mockSwapState) ID() types.Hash {
return testSwapID
}
func (*mockSwapState) InfoFile() string {
return os.TempDir() + "test.infofile"
}
type mockProtocolBackend struct {
sm *mockSwapManager
}
func newMockProtocolBackend() *mockProtocolBackend {
return &mockProtocolBackend{
sm: new(mockSwapManager),
}
}
func (*mockProtocolBackend) Env() common.Environment {
return common.Development
}
func (*mockProtocolBackend) SetGasPrice(uint64) {
panic("not implemented")
}
func (*mockProtocolBackend) SetSwapTimeout(timeout time.Duration) {
panic("not implemented")
}
func (b *mockProtocolBackend) SwapManager() swap.Manager {
return b.sm
}
func (*mockProtocolBackend) SetEthAddress(ethcommon.Address) {
panic("not implemented")
}
func (*mockProtocolBackend) SetXMRDepositAddress(mcrypto.Address, types.Hash) {
panic("not implemented")
}
func (*mockProtocolBackend) ClearXMRDepositAddress(types.Hash) {
panic("not implemented")
}

View File

@@ -5,11 +5,12 @@ import (
"net/http"
"time"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/rpctypes"
"github.com/athanorlabs/atomic-swap/common/types"
"github.com/athanorlabs/atomic-swap/net"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/libp2p/go-libp2p-core/peer"
)

View File

@@ -3,6 +3,7 @@ package rpc
import (
"context"
"fmt"
"net"
"net/http"
"time"
@@ -24,17 +25,15 @@ var log = logging.Logger("rpc")
// Server represents the JSON-RPC server
type Server struct {
s *rpc.Server
wsServer *wsServer
port uint16
wsPort uint16
ctx context.Context
listener net.Listener
httpServer *http.Server
}
// Config ...
type Config struct {
Ctx context.Context
Port uint16
WsPort uint16
Address string // "IP:port"
Net Net
XMRTaker XMRTaker
XMRMaker XMRMaker
@@ -43,67 +42,70 @@ type Config struct {
// NewServer ...
func NewServer(cfg *Config) (*Server, error) {
s := rpc.NewServer()
s.RegisterCodec(NewCodec(), "application/json")
rpcServer := rpc.NewServer()
rpcServer.RegisterCodec(NewCodec(), "application/json")
ns := NewNetService(cfg.Net, cfg.XMRTaker, cfg.XMRMaker, cfg.ProtocolBackend.SwapManager())
if err := s.RegisterService(ns, "net"); err != nil {
if err := rpcServer.RegisterService(ns, "net"); err != nil {
return nil, err
}
if err := s.RegisterService(NewPersonalService(cfg.XMRMaker, cfg.ProtocolBackend), "personal"); err != nil {
if err := rpcServer.RegisterService(NewPersonalService(cfg.XMRMaker, cfg.ProtocolBackend), "personal"); err != nil {
return nil, err
}
if err := s.RegisterService(NewSwapService(cfg.ProtocolBackend.SwapManager(), cfg.XMRTaker, cfg.XMRMaker, cfg.Net), "swap"); err != nil { //nolint:lll
if err := rpcServer.RegisterService(NewSwapService(cfg.ProtocolBackend.SwapManager(), cfg.XMRTaker, cfg.XMRMaker, cfg.Net), "swap"); err != nil { //nolint:lll
return nil, err
}
wsServer := newWsServer(cfg.Ctx, cfg.ProtocolBackend.SwapManager(), ns, cfg.ProtocolBackend, cfg.XMRTaker)
lc := net.ListenConfig{}
ln, err := lc.Listen(cfg.Ctx, "tcp", cfg.Address)
if err != nil {
return nil, err
}
r := mux.NewRouter()
r.Handle("/", rpcServer)
r.Handle("/ws", wsServer)
headersOk := handlers.AllowedHeaders([]string{"content-type", "username", "password"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
originsOk := handlers.AllowedOrigins([]string{"*"})
server := &http.Server{
Addr: ln.Addr().String(),
ReadHeaderTimeout: time.Second,
Handler: handlers.CORS(headersOk, methodsOk, originsOk)(r),
BaseContext: func(listener net.Listener) context.Context {
return cfg.Ctx
},
}
return &Server{
s: s,
wsServer: newWsServer(cfg.Ctx, cfg.ProtocolBackend.SwapManager(), ns, cfg.ProtocolBackend, cfg.XMRTaker),
port: cfg.Port,
wsPort: cfg.WsPort,
ctx: cfg.Ctx,
listener: ln,
httpServer: server,
}, nil
}
// Start starts the JSON-RPC server.
func (s *Server) Start() <-chan error {
errCh := make(chan error)
// HttpURL returns the URL used for HTTP requests
func (s *Server) HttpURL() string { //nolint:revive
return fmt.Sprintf("http://%s", s.httpServer.Addr)
}
go func() {
r := mux.NewRouter()
r.Handle("/", s.s)
// WsURL returns the URL used for websocket requests
func (s *Server) WsURL() string {
return fmt.Sprintf("ws://%s/ws", s.httpServer.Addr)
}
headersOk := handlers.AllowedHeaders([]string{"content-type", "username", "password"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
originsOk := handlers.AllowedOrigins([]string{"*"})
// Start starts the JSON-RPC and Websocket server.
func (s *Server) Start() error {
log.Infof("Starting RPC server on %s", s.HttpURL())
log.Infof("Starting websockets server on %s", s.WsURL())
log.Infof("starting RPC server on http://127.0.0.1:%d", s.port)
if err := http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", s.port), handlers.CORS(headersOk, methodsOk, originsOk)(r)); err != nil { //nolint:lll
log.Errorf("failed to start http RPC server: %s", err)
errCh <- err
}
}()
go func() {
r := mux.NewRouter()
r.Handle("/", s.wsServer)
headersOk := handlers.AllowedHeaders([]string{"content-type", "username", "password"})
methodsOk := handlers.AllowedMethods([]string{"GET", "HEAD", "POST", "PUT", "OPTIONS"})
originsOk := handlers.AllowedOrigins([]string{"*"})
log.Infof("starting websockets server on ws://127.0.0.1:%d", s.wsPort)
if err := http.ListenAndServe(fmt.Sprintf("127.0.0.1:%d", s.wsPort), handlers.CORS(headersOk, methodsOk, originsOk)(r)); err != nil { //nolint:lll
log.Errorf("failed to start websockets RPC server: %s", err)
errCh <- err
}
}()
return errCh
err := s.httpServer.Serve(s.listener) // Serve never returns nil
return fmt.Errorf("RPC server failed: %w", err)
}
// Protocol represents the functions required by the rpc service into the protocol handler.

View File

@@ -2,23 +2,14 @@ package rpc
import (
"context"
"encoding/json"
"fmt"
"os"
"net/http"
"sync"
"testing"
"time"
"github.com/athanorlabs/atomic-swap/common"
"github.com/athanorlabs/atomic-swap/common/types"
mcrypto "github.com/athanorlabs/atomic-swap/crypto/monero"
"github.com/athanorlabs/atomic-swap/net"
"github.com/athanorlabs/atomic-swap/net/message"
"github.com/athanorlabs/atomic-swap/protocol/swap"
"github.com/athanorlabs/atomic-swap/protocol/txsender"
"github.com/athanorlabs/atomic-swap/rpcclient/wsclient"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/stretchr/testify/require"
)
@@ -27,163 +18,50 @@ const (
)
var (
testSwapID = types.Hash{99}
testTImeout = time.Second * 5
defaultRPCPort uint16 = 3001
defaultWSPort uint16 = 4002
testSwapID = types.Hash{99}
testTimeout = time.Second * 5
)
func defaultWSEndpoint() string {
return fmt.Sprintf("ws://localhost:%d", defaultWSPort)
}
type mockNet struct{}
func (*mockNet) Addresses() []string {
return nil
}
func (*mockNet) Advertise() {}
func (*mockNet) Discover(provides types.ProvidesCoin, searchTime time.Duration) ([]peer.AddrInfo, error) {
return nil, nil
}
func (*mockNet) Query(who peer.AddrInfo) (*net.QueryResponse, error) {
var offer types.Offer
offerJSON := fmt.Sprintf(`{"ID":%q}`, testSwapID.String())
if err := json.Unmarshal([]byte(offerJSON), &offer); err != nil {
panic(err)
}
return &net.QueryResponse{Offers: []*types.Offer{&offer}}, nil
}
func (*mockNet) Initiate(who peer.AddrInfo, msg *net.SendKeysMessage, s common.SwapStateNet) error {
return nil
}
func (*mockNet) CloseProtocolStream(types.Hash) {}
type mockSwapManager struct{}
func (*mockSwapManager) GetPastIDs() []types.Hash {
return []types.Hash{}
}
func (*mockSwapManager) GetPastSwap(id types.Hash) *swap.Info {
return &swap.Info{}
}
func (*mockSwapManager) GetOngoingSwap(id types.Hash) *swap.Info {
statusCh := make(chan types.Status, 1)
statusCh <- types.CompletedSuccess
return swap.NewInfo(
id,
types.ProvidesETH,
1,
1,
1,
types.EthAssetETH,
types.CompletedSuccess,
statusCh,
)
}
func (*mockSwapManager) AddSwap(*swap.Info) error {
return nil
}
func (*mockSwapManager) CompleteOngoingSwap(types.Hash) {}
type mockXMRTaker struct{}
func (*mockXMRTaker) Provides() types.ProvidesCoin {
return types.ProvidesETH
}
func (*mockXMRTaker) SetGasPrice(gasPrice uint64) {}
func (*mockXMRTaker) GetOngoingSwapState(types.Hash) common.SwapState {
return new(mockSwapState)
}
func (*mockXMRTaker) InitiateProtocol(providesAmount float64, _ *types.Offer) (common.SwapState, error) {
return new(mockSwapState), nil
}
func (*mockXMRTaker) Refund(types.Hash) (ethcommon.Hash, error) {
return ethcommon.Hash{}, nil
}
func (*mockXMRTaker) SetSwapTimeout(_ time.Duration) {}
func (*mockXMRTaker) ExternalSender(_ types.Hash) (*txsender.ExternalSender, error) {
return nil, fmt.Errorf("unimplemented")
}
type mockSwapState struct{}
func (*mockSwapState) HandleProtocolMessage(msg message.Message) (resp message.Message, done bool, err error) {
return nil, true, nil
}
func (*mockSwapState) Exit() error {
return nil
}
func (*mockSwapState) SendKeysMessage() (*message.SendKeysMessage, error) {
return &message.SendKeysMessage{}, nil
}
func (*mockSwapState) ID() types.Hash {
return testSwapID
}
func (*mockSwapState) InfoFile() string {
return os.TempDir() + "test.infofile"
}
type mockProtocolBackend struct {
sm *mockSwapManager
}
func newMockProtocolBackend() *mockProtocolBackend {
return &mockProtocolBackend{
sm: new(mockSwapManager),
}
}
func (*mockProtocolBackend) Env() common.Environment {
return common.Development
}
func (*mockProtocolBackend) SetGasPrice(uint64) {}
func (*mockProtocolBackend) SetSwapTimeout(timeout time.Duration) {}
func (b *mockProtocolBackend) SwapManager() swap.Manager {
return b.sm
}
func (*mockProtocolBackend) SetEthAddress(ethcommon.Address) {}
func (*mockProtocolBackend) SetXMRDepositAddress(mcrypto.Address, types.Hash) {}
func (*mockProtocolBackend) ClearXMRDepositAddress(types.Hash) {}
func newServer(t *testing.T) *Server {
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})
defaultRPCPort++
defaultWSPort++
cfg := &Config{
Ctx: ctx,
Port: defaultRPCPort,
WsPort: defaultWSPort,
Address: "127.0.0.1:0", // OS assigned port
Net: new(mockNet),
ProtocolBackend: newMockProtocolBackend(),
XMRTaker: new(mockXMRTaker),
XMRMaker: new(mockXMRMaker),
}
s, err := NewServer(cfg)
require.NoError(t, err)
errCh := s.Start()
var wg sync.WaitGroup
wg.Add(1)
go func() {
err := <-errCh
require.NoError(t, err)
err := s.Start()
require.ErrorIs(t, err, http.ErrServerClosed)
wg.Done()
}()
time.Sleep(time.Millisecond * 300) // let server start up
t.Cleanup(func() {
cancel()
// Using non-cancelled context, so shutdown waits for clients to disconnect before unblocking
err := s.httpServer.Shutdown(context.Background())
require.NoError(t, err)
wg.Wait() // unblocks when server exits
})
return s
}
func TestSubscribeSwapStatus(t *testing.T) {
_ = newServer(t)
s := newServer(t)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})
c, err := wsclient.NewWsClient(ctx, defaultWSEndpoint())
c, err := wsclient.NewWsClient(s.ctx, s.WsURL())
require.NoError(t, err)
ch, err := c.SubscribeSwapStatus(testSwapID)
@@ -192,41 +70,36 @@ func TestSubscribeSwapStatus(t *testing.T) {
select {
case status := <-ch:
require.Equal(t, types.CompletedSuccess, status)
case <-time.After(testTImeout):
case <-time.After(testTimeout):
t.Fatal("test timed out")
}
}
// TODO: add unit test
// func TestSubscribeMakeOffer(t *testing.T) {
// _ = newServer(t)
func TestSubscribeMakeOffer(t *testing.T) {
s := newServer(t)
// ctx, cancel := context.WithCancel(context.Background())
// t.Cleanup(func() {
// cancel()
// })
// c, err := rpcclient.NewWsClient(ctx, defaultWSEndpoint())
// require.NoError(t, err)
c, err := wsclient.NewWsClient(s.ctx, s.WsURL())
require.NoError(t, err)
// id, ch, err := c.MakeOfferAndSubscribe(0.1, 1, 0.05)
// require.NoError(t, err)
// require.Equal(t, id, testSwapID)
// select {
// case status := <-ch:
// require.Equal(t, types.CompletedSuccess, status)
// case <-time.After(testTImeout):
// t.Fatal("test timed out")
// }
// }
id, ch, err := c.MakeOfferAndSubscribe(0.1, 1, 0.05, types.EthAssetETH)
require.NoError(t, err)
require.NotEqual(t, id, testSwapID)
select {
case status := <-ch:
require.Equal(t, types.CompletedSuccess, status)
case <-time.After(testTimeout):
t.Fatal("test timed out")
}
}
func TestSubscribeTakeOffer(t *testing.T) {
_ = newServer(t)
s := newServer(t)
ctx, cancel := context.WithCancel(context.Background())
cliCtx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})
c, err := wsclient.NewWsClient(ctx, defaultWSEndpoint())
c, err := wsclient.NewWsClient(cliCtx, s.WsURL())
require.NoError(t, err)
ch, err := c.TakeOfferAndSubscribe(testMultiaddr, testSwapID.String(), 1)
@@ -235,7 +108,7 @@ func TestSubscribeTakeOffer(t *testing.T) {
select {
case status := <-ch:
require.Equal(t, types.CompletedSuccess, status)
case <-time.After(testTImeout):
case <-time.After(testTimeout):
t.Fatal("test timed out")
}
}

View File

@@ -20,9 +20,8 @@ type WsClient interface {
Close()
Discover(provides types.ProvidesCoin, searchTime uint64) ([][]string, error)
Query(maddr string) (*rpctypes.QueryPeerResponse, error)
SubscribeSwapStatus(id uint64) (<-chan types.Status, error)
TakeOfferAndSubscribe(multiaddr, offerID string,
providesAmount float64) (id uint64, ch <-chan types.Status, err 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)
}
@@ -37,7 +36,7 @@ type wsClient struct {
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 endpoint: %w", err)
return nil, fmt.Errorf("failed to dial WS endpoint: %w", err)
}
if err = resp.Body.Close(); err != nil {

View File

@@ -48,7 +48,6 @@ start-swapd charlie \
--ethereum-privkey "${CHARLIE_ETH_KEY}" \
--libp2p-port 9955 \
--rpc-port 5003 \
--ws-port 8083 \
--bootnodes /ip4/127.0.0.1/tcp/9933/p2p/12D3KooWAYn1T8Lu122Pav4zAogjpeU61usLTNZpLRNh9gCqY6X2 \
--deploy

View File

@@ -1,5 +1,5 @@
[
"ws://localhost:8081",
"ws://localhost:8082",
"ws://localhost:8080"
]
"ws://localhost:5001/ws",
"ws://localhost:5002/ws",
"ws://localhost:5000/ws"
]

View File

@@ -28,10 +28,10 @@ const (
falseStr = "false"
defaultXMRTakerDaemonEndpoint = "http://localhost:5001"
defaultXMRTakerDaemonWSEndpoint = "ws://localhost:8081"
defaultXMRTakerDaemonWSEndpoint = "ws://localhost:5001/ws"
defaultXMRMakerDaemonEndpoint = "http://localhost:5002"
defaultXMRMakerDaemonWSEndpoint = "ws://localhost:8082"
defaultCharlieDaemonWSEndpoint = "ws://localhost:8083"
defaultXMRMakerDaemonWSEndpoint = "ws://localhost:5002/ws"
defaultCharlieDaemonWSEndpoint = "ws://localhost:5003/ws"
defaultDiscoverTimeout = 2 // 2 seconds

View File

@@ -1,6 +1,6 @@
# Atomic swap UI
This is a draft UI to interract with the atomic swap nodes
This is a draft UI to interact with the atomic swap nodes
## Get started

View File

@@ -14,7 +14,7 @@
import HelperText from '@smui/textfield/helper-text'
import { currentAccount, sign } from '../stores/metamask'
const WS_ADDRESS = 'ws://127.0.0.1:8081'
const WS_ADDRESS = 'ws://127.0.0.1:5001/ws'
let amountProvided: number | null = null
let xmrAddress = ''