Skip to content

Commit

Permalink
feat: implement net_pairs (#495)
Browse files Browse the repository at this point in the history
  • Loading branch information
stubbrn authored Aug 28, 2023
1 parent f7c6a1d commit 830c263
Show file tree
Hide file tree
Showing 128 changed files with 1,972 additions and 340 deletions.
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

0 comments on commit 830c263

Please sign in to comment.