Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add pairs #495

Merged
merged 13 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions cmd/swapcli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ func cliApp() *cli.App {
swapdPortFlag,
},
},
{
Name: "pairs",
Aliases: []string{"p"},
Usage: "List active pairs",
Action: runPairs,
Flags: []cli.Flag{
swapdPortFlag,
&cli.Uint64Flag{
Name: flagSearchTime,
Usage: "Duration of time to search for, in seconds",
Value: defaultDiscoverSearchTimeSecs,
},
},
},
{
Name: "balances",
Aliases: []string{"b"},
Expand Down Expand Up @@ -536,6 +550,39 @@ func runPeers(ctx *cli.Context) error {
return nil
}

func runPairs(ctx *cli.Context) error {
searchTime := ctx.Uint64(flagSearchTime)

c := newClient(ctx)
resp, err := c.Pairs(searchTime)
if err != nil {
return err
}

for i, a := range resp.Pairs {
var verified string
if a.Verified {
verified = "Yes"
} else {
verified = "No"
}

fmt.Printf("Pair %d:\n", i+1)
fmt.Printf(" Name: %s\n", a.Token.Symbol)
fmt.Printf(" Token: %s\n", a.Token.Address)
fmt.Printf(" Verified: %s\n", verified)
fmt.Printf(" Offers: %d\n", a.Offers)
fmt.Printf(" Reported Liquidity XMR: %f\n", a.ReportedLiquidityXMR)
fmt.Println()
}

if len(resp.Pairs) == 0 {
fmt.Println("[none]")
}

return nil
}

func runBalances(ctx *cli.Context) error {
c := newClient(ctx)

Expand Down
10 changes: 10 additions & 0 deletions common/rpctypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,13 @@ type AddressesResponse struct {
type PeersResponse struct {
Addrs []string `json:"addresses" validate:"dive,required"`
}

// PairsRequest ...
type PairsRequest struct {
SearchTime uint64 `json:"searchTime"` // in seconds
}

// PairsResponse ...
type PairsResponse struct {
Pairs []*types.Pair
}
44 changes: 44 additions & 0 deletions common/types/pairs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only

package types

import (
"github.com/cockroachdb/apd/v3"

"github.com/athanorlabs/atomic-swap/coins"
)

// Pair represents a pair (Such as ETH / XMR)
type Pair struct {
ReportedLiquidityXMR *apd.Decimal `json:"reportedLiquidityXmr" validate:"required"`
EthAsset EthAsset `json:"ethAsset" validate:"required"`
Token coins.ERC20TokenInfo `json:"token" validate:"required"`
Offers uint64 `json:"offers" validate:"required"`
Verified bool `json:"verified" valdate:"required"`
}

// NewPair creates and returns a Pair
func NewPair(EthAsset EthAsset) *Pair {
pair := &Pair{
ReportedLiquidityXMR: apd.New(0, 0),
EthAsset: EthAsset,

// Always set to false for now until the verified-list
// is implemented
Verified: false,
}
return pair
}

// AddOffer adds an offer to a pair
func (pair *Pair) AddOffer(o *Offer) error {
_, err := coins.DecimalCtx().Add(pair.ReportedLiquidityXMR, pair.ReportedLiquidityXMR, o.MaxAmount)
if err != nil {
return err
}

pair.Offers++

return nil
}
86 changes: 85 additions & 1 deletion rpc/net.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package rpc

import (
"context"
"fmt"
"net/http"
"time"
Expand All @@ -18,6 +19,8 @@ import (
"github.com/athanorlabs/atomic-swap/common/types"
"github.com/athanorlabs/atomic-swap/net/message"
"github.com/athanorlabs/atomic-swap/protocol/swap"

ethcommon "github.com/ethereum/go-ethereum/common"
)

const defaultSearchTime = time.Second * 12
Expand All @@ -35,19 +38,31 @@ type Net interface {

// NetService is the RPC service prefixed by net_.
type NetService struct {
ctx context.Context
net Net
xmrtaker XMRTaker
xmrmaker XMRMaker
pb ProtocolBackend
sm swap.Manager
isBootnode bool
}

// NewNetService ...
func NewNetService(net Net, xmrtaker XMRTaker, xmrmaker XMRMaker, sm swap.Manager, isBootnode bool) *NetService {
func NewNetService(
ctx context.Context,
net Net,
xmrtaker XMRTaker,
xmrmaker XMRMaker,
pb ProtocolBackend,
sm swap.Manager,
isBootnode bool,
) *NetService {
return &NetService{
ctx: ctx,
net: net,
xmrtaker: xmrtaker,
xmrmaker: xmrmaker,
pb: pb,
sm: sm,
isBootnode: isBootnode,
}
Expand All @@ -73,6 +88,75 @@ func (s *NetService) Peers(_ *http.Request, _ *interface{}, resp *rpctypes.Peers
return nil
}

// Pairs returns all currently available pairs from offers of all peers
func (s *NetService) Pairs(_ *http.Request, req *rpctypes.PairsRequest, resp *rpctypes.PairsResponse) error {
if s.isBootnode {
return errUnsupportedForBootnode
}

peerIDs, err := s.discover(&rpctypes.DiscoverRequest{
Provides: "",
SearchTime: req.SearchTime,
})
if err != nil {
return err
}

pairs := make(map[ethcommon.Address]*types.Pair)

for _, p := range peerIDs {
msg, err := s.net.Query(p)
if err != nil {
log.Debugf("Failed to query peer ID %s", p)
continue
}

if len(msg.Offers) == 0 {
continue
}

for _, o := range msg.Offers {
address := o.EthAsset.Address()
pair, exists := pairs[address]

if !exists {
pair = types.NewPair(o.EthAsset)
if pair.EthAsset.IsToken() {
tokenInfo, tokenInfoErr := s.pb.ETHClient().ERC20Info(s.ctx, address)
if tokenInfoErr != nil {
log.Debugf("Error while reading token info: %s", tokenInfoErr)
continue
}
pair.Token = *tokenInfo
} else {
pair.Token.Name = "Ether"
pair.Token.Symbol = "ETH"
pair.Token.NumDecimals = 18
pair.Verified = true
}
pairs[address] = pair
}

err = pair.AddOffer(o)
if err != nil {
return err
}
}
}

pairsArray := make([]*types.Pair, 0, len(pairs))
for _, pair := range pairs {
if pair.EthAsset.IsETH() {
pairsArray = append([]*types.Pair{pair}, pairsArray...)
} else {
pairsArray = append(pairsArray, pair)
}
}

resp.Pairs = pairsArray
return nil
}

// QueryAll discovers peers who provide a certain coin and queries all of them for their current offers.
func (s *NetService) QueryAll(_ *http.Request, req *rpctypes.QueryAllRequest, resp *rpctypes.QueryAllResponse) error {
if s.isBootnode {
Expand Down
10 changes: 9 additions & 1 deletion rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,15 @@ func NewServer(cfg *Config) (*Server, error) {
case DatabaseNamespace:
err = rpcServer.RegisterService(NewDatabaseService(cfg.RecoveryDB), DatabaseNamespace)
case NetNamespace:
netService = NewNetService(cfg.Net, cfg.XMRTaker, cfg.XMRMaker, swapManager, isBootnode)
netService = NewNetService(
serverCtx,
cfg.Net,
cfg.XMRTaker,
cfg.XMRMaker,
cfg.ProtocolBackend,
swapManager,
isBootnode,
)
err = rpcServer.RegisterService(netService, NetNamespace)
case PersonalName:
err = rpcServer.RegisterService(NewPersonalService(serverCtx, cfg.XMRMaker, cfg.ProtocolBackend), PersonalName)
Expand Down
22 changes: 19 additions & 3 deletions rpcclient/net_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package rpcclient

import (
"context"
"testing"

"github.com/cockroachdb/apd/v3"
Expand All @@ -15,7 +16,12 @@ import (
)

func TestNet_Discover(t *testing.T) {
ns := rpc.NewNetService(new(mockNet), new(mockXMRTaker), nil, mockSwapManager(t), false)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})

ns := rpc.NewNetService(ctx, new(mockNet), new(mockXMRTaker), nil, new(mockProtocolBackend), mockSwapManager(t), false)

req := &rpctypes.DiscoverRequest{
Provides: "",
Expand All @@ -29,7 +35,12 @@ func TestNet_Discover(t *testing.T) {
}

func TestNet_Query(t *testing.T) {
ns := rpc.NewNetService(new(mockNet), new(mockXMRTaker), nil, mockSwapManager(t), false)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})

ns := rpc.NewNetService(ctx, new(mockNet), new(mockXMRTaker), nil, new(mockProtocolBackend), mockSwapManager(t), false)

req := &rpctypes.QueryPeerRequest{
PeerID: "12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
Expand All @@ -43,7 +54,12 @@ func TestNet_Query(t *testing.T) {
}

func TestNet_TakeOffer(t *testing.T) {
ns := rpc.NewNetService(new(mockNet), new(mockXMRTaker), nil, mockSwapManager(t), false)
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(func() {
cancel()
})

ns := rpc.NewNetService(ctx, new(mockNet), new(mockXMRTaker), nil, new(mockProtocolBackend), mockSwapManager(t), false)

req := &rpctypes.TakeOfferRequest{
PeerID: "12D3KooWDqCzbjexHEa8Rut7bzxHFpRMZyDRW1L6TGkL1KY24JH5",
Expand Down
27 changes: 27 additions & 0 deletions rpcclient/pairs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2023 The AthanorLabs/atomic-swap Authors
// SPDX-License-Identifier: LGPL-3.0-only

package rpcclient

import (
"github.com/athanorlabs/atomic-swap/common/rpctypes"
)

// Pairs calls net_pairs to get pairs from all offers.
func (c *Client) Pairs(searchTime uint64) (*rpctypes.PairsResponse, error) {
const (
method = "net_pairs"
)

req := &rpctypes.PairsRequest{
SearchTime: searchTime,
}

res := &rpctypes.PairsResponse{}

if err := c.post(method, req, res); err != nil {
return nil, err
}

return res, nil
}
48 changes: 48 additions & 0 deletions tests/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,54 @@ func (s *IntegrationTestSuite) TestXMRMaker_Discover() {
require.Equal(s.T(), 0, len(peerIDs))
}

func (s *IntegrationTestSuite) TestXMRMaker_Pairs() {
ctx := context.Background()
bc := rpcclient.NewClient(ctx, defaultXMRMakerSwapdPort)

_, err := bc.MakeOffer(
coins.StrToDecimal("1"),
coins.StrToDecimal("2"),
coins.StrToExchangeRate("200"),
s.testToken,
false)

require.NoError(s.T(), err)

_, err = bc.MakeOffer(
coins.StrToDecimal("1"),
coins.StrToDecimal("2"),
coins.StrToExchangeRate("200"),
types.EthAssetETH,
false)

require.NoError(s.T(), err)

_, err = bc.MakeOffer(
coins.StrToDecimal("1"),
coins.StrToDecimal("2"),
coins.StrToExchangeRate("200"),
types.EthAssetETH,
false)

require.NoError(s.T(), err)

// Give offer advertisement time to propagate
require.NoError(s.T(), common.SleepWithContext(ctx, time.Second))

ac := rpcclient.NewClient(ctx, defaultXMRTakerSwapdPort)
pairs, err := ac.Pairs(3)

require.Equal(s.T(), len(pairs.Pairs), 2)

p1 := pairs.Pairs[0]
p2 := pairs.Pairs[1]

require.Equal(s.T(), p1.Offers, uint64(2))
require.Equal(s.T(), p2.Offers, uint64(1))

require.NoError(s.T(), err)
}

func (s *IntegrationTestSuite) TestXMRTaker_Query() {
s.testXMRTakerQuery(types.EthAssetETH)
}
Expand Down
Loading