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

feat(rpc): runtime addcurrency for lnd & connext #1746

Merged
merged 13 commits into from
Nov 5, 2020
13 changes: 11 additions & 2 deletions lib/orderbook/OrderBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,11 @@ class OrderBook extends EventEmitter {
this.pairInstances.set(pairInstance.id, pairInstance);
this.addTradingPair(pairInstance.id);

this.pool.rawPeers().forEach(async (peer) => {
this.checkPeerCurrencies(peer);
await this.verifyPeerPairs(peer);
});

this.pool.updatePairs(this.pairIds);
return pairInstance;
}
Expand Down Expand Up @@ -332,7 +337,7 @@ class OrderBook extends EventEmitter {
}
const currencyInstance = await this.repository.addCurrency({ ...currency, decimalPlaces: currency.decimalPlaces || 8 });
this.currencyInstances.set(currencyInstance.id, currencyInstance);
this.swaps.swapClientManager.add(currencyInstance);
await this.swaps.swapClientManager.add(currencyInstance);
}

public removeCurrency = async (currencyId: string) => {
Expand All @@ -344,7 +349,6 @@ class OrderBook extends EventEmitter {
}
}
this.currencyInstances.delete(currencyId);
this.swaps.swapClientManager.remove(currencyId);
await currency.destroy();
} else {
throw errors.CURRENCY_DOES_NOT_EXIST(currencyId);
Expand All @@ -360,6 +364,11 @@ class OrderBook extends EventEmitter {
this.pairInstances.delete(pairId);
this.tradingPairs.delete(pairId);

this.pool.rawPeers().forEach(async (peer) => {
this.checkPeerCurrencies(peer);
await this.verifyPeerPairs(peer);
});

this.pool.updatePairs(this.pairIds);
return pair.destroy();
}
Expand Down
8 changes: 7 additions & 1 deletion lib/p2p/Pool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,9 @@ class Pool extends EventEmitter {
* packet to currently connected peers to notify them of the change.
*/
public updateConnextState = (tokenAddresses: Map<string, string>, pubKey?: string) => {
this.nodeState.connextIdentifier = pubKey || '';
if (pubKey) {
this.nodeState.connextIdentifier = pubKey;
}
tokenAddresses.forEach((tokenAddress, currency) => {
this.nodeState.tokenIdentifiers[currency] = tokenAddress;
});
Expand Down Expand Up @@ -476,6 +478,10 @@ class Pool extends EventEmitter {
return peerInfos;
}

public rawPeers = (): Map<string, Peer> => {
return this.peers;
}

private addressIsSelf = (address: Address): boolean => {
if (address.port === this.listenPort) {
switch (address.host) {
Expand Down
24 changes: 13 additions & 11 deletions lib/swaps/SwapClientManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,19 +338,21 @@ class SwapClientManager extends EventEmitter {
* @param currency a currency that should be linked with a swap client.
* @returns Nothing upon success, throws otherwise.
*/
public add = (currency: Currency): void => {
if (currency.swapClient === SwapClientType.Lnd) {
// in case of lnd we check if the configuration includes swap client
// for the specified currency
let isCurrencyConfigured = false;
for (const lndCurrency in this.config.lnd) {
if (lndCurrency === currency.id) {
isCurrencyConfigured = true;
break;
public add = async (currency: Currency) => {
if (currency.tokenAddress) {
if (currency.swapClient === SwapClientType.Connext) {
if (!this.connextClient) {
throw errors.SWAP_CLIENT_NOT_CONFIGURED(currency.id);
}
this.swapClients.set(currency.id, this.connextClient);
this.connextClient.tokenAddresses.set(currency.id, currency.tokenAddress);
this.emit('connextUpdate', this.connextClient.tokenAddresses);
}
// adding a new lnd client at runtime is currently not supported
if (!isCurrencyConfigured) {
} else if (currency.swapClient === SwapClientType.Lnd) {
// in case of lnd we check if the configuration includes swap client
// for the specified currency
const config = this.config.lnd[currency.id];
if (!config) {
throw errors.SWAP_CLIENT_NOT_CONFIGURED(currency.id);
}
}
Expand Down
1 change: 1 addition & 0 deletions test/simulation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ Below is a list of implemented (checked) and planned (unchecked) test cases.

- [x] Placed order should get broadcasted over the network, and added to connected peers' order books.
- [x] Removed order should get invalidated over the network, and removed from connected peers' order books.
- [x] Added trading pairs and currencies should trigger broadcast of active orders from connected peers' order books.
- [ ] Placed order should get internal matches, and trigger order invalidation over the network.
- [ ] Peer disconnection should trigger orders removal to all his orders.

Expand Down
145 changes: 95 additions & 50 deletions test/simulation/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"github.com/ExchangeUnion/xud-simulation/connexttest"
"math/big"
"time"

Expand All @@ -27,69 +28,89 @@ type actions struct {
}

func (a *actions) init(node *xudtest.HarnessNode) {
// Verify connectivity.
timeout := time.Now().Add(10 * time.Second)
for {
req := &xudrpc.GetInfoRequest{}
res, err := node.Client.GetInfo(a.ctx, req)
var info *xudrpc.GetInfoResponse
isReady := func() bool {
var err error
info, err = node.Client.GetInfo(context.Background(), &xudrpc.GetInfoRequest{})
a.assert.NoError(err)
a.assert.NotNil(res.Lnd["BTC"])
a.assert.NotNil(res.Lnd["LTC"])
if len(res.Lnd["BTC"].Chains) == 1 && len(res.Lnd["LTC"].Chains) == 1 {
a.assert.Equal(res.Lnd["BTC"].Chains[0].Chain, "bitcoin")
a.assert.Equal(res.Lnd["BTC"].Chains[0].Network, "simnet")
a.assert.Equal(res.Lnd["LTC"].Chains[0].Chain, "litecoin")
a.assert.Equal(res.Lnd["LTC"].Chains[0].Network, "simnet")
return len(info.Lnd["BTC"].Chains) == 1 && len(info.Lnd["LTC"].Chains) == 1
}

// Set the node public key.
node.SetPubKey(res.NodePubKey)
timeout := time.After(10 * time.Second)
for !isReady() {
select {
case <-timeout:
a.assert.Fail("timeout waiting for LND synced chains")
case <-time.After(1 * time.Second):
}
}

// Add currencies
a.addCurrency(node, "BTC", xudrpc.Currency_LND, "", 8)
a.addCurrency(node, "LTC", xudrpc.Currency_LND, "", 8)
a.assert.Equal(info.Lnd["BTC"].Chains[0].Chain, "bitcoin")
a.assert.Equal(info.Lnd["BTC"].Chains[0].Network, "simnet")
a.assert.Equal(info.Lnd["LTC"].Chains[0].Chain, "litecoin")
a.assert.Equal(info.Lnd["LTC"].Chains[0].Network, "simnet")

// Add pairs to the node.
a.addPair(node, "LTC", "BTC")
// Set the node public key.
node.SetPubKey(info.NodePubKey)

break
}
a.assert.False(time.Now().After(timeout), "waiting for synced chains timeout")
// retry interval
time.Sleep(100 * time.Millisecond)
}
// Add currencies.
a.addCurrency(node, "BTC", xudrpc.Currency_LND, "", 8)
a.addCurrency(node, "LTC", xudrpc.Currency_LND, "", 8)
a.addCurrency(node, "ETH", xudrpc.Currency_CONNEXT, connexttest.ETHTokenAddress, 18)

// Add pairs.
a.addPair(node, "LTC", "BTC")
a.addPair(node, "BTC", "ETH")
}

func (a *actions) initConnext(net *xudtest.NetworkHarness, node *xudtest.HarnessNode, fund bool) {
func (a *actions) FundETH(net *xudtest.NetworkHarness, node *xudtest.HarnessNode) {
// Wait for node's connext connection to catch-up.
err := waitConnextReady(node)
a.assert.NoError(err)
a.waitConnextReady(node)

// Fund node's wallet.
if fund {
resInfo, err := node.Client.GetInfo(context.Background(), &xudrpc.GetInfoRequest{})
a.assert.NoError(err)
amount := big.NewInt(2000000000000000000)
err = net.ConnextNetwork.Wallet.SendEth(resInfo.Connext.Address, amount)
a.assert.NoError(err)
resInfo, err := node.Client.GetInfo(context.Background(), &xudrpc.GetInfoRequest{})
a.assert.NoError(err)
amount := big.NewInt(2000000000000000000)
err = net.ConnextNetwork.Wallet.SendEth(resInfo.Connext.Address, amount)
a.assert.NoError(err)

time.Sleep(15 * time.Second)
}
time.Sleep(15 * time.Second)

// Init node.
ETHTokenAddress := "0x0000000000000000000000000000000000000000"
a.addCurrency(node, "ETH", 2, ETHTokenAddress, 18)
a.addPair(node, "BTC", "ETH")
err = net.RestartNode(node)
// Verify node's ETH balance.
bal, err := node.Client.GetBalance(a.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"})
ethBal := bal.Balances["ETH"]
a.assert.NoError(err)
a.assert.Equal(uint64(200000000), ethBal.TotalBalance)
a.assert.Equal(uint64(200000000), ethBal.WalletBalance)
a.assert.Equal(uint64(0), ethBal.ChannelBalance)
}

// Verify node's ETH balance.
if fund {
resBal, err := node.Client.GetBalance(a.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"})
a.assert.NoError(err)
a.assert.Equal(uint64(200000000), resBal.Balances["ETH"].WalletBalance)
func (a *actions) waitConnextReady(node *xudtest.HarnessNode) {
isReady := func() bool {
info, err := node.Client.GetInfo(context.Background(), &xudrpc.GetInfoRequest{})
if err != nil {
return false
}

return info.Connext.Address != ""
}

timeout := time.After(30 * time.Second)
for !isReady() {
select {
case <-timeout:
a.assert.Fail("timeout waiting for connext to be ready")
case <-time.After(1 * time.Second):
}
}
}

func (a *actions) removeCurrency(node *xudtest.HarnessNode, currency string) {
req := &xudrpc.RemoveCurrencyRequest{Currency: currency}
_, err := node.Client.RemoveCurrency(a.ctx, req)
a.assert.NoError(err)
}

func (a *actions) addCurrency(node *xudtest.HarnessNode, currency string, swapClient xudrpc.Currency_SwapClient, tokenAddress string, decimalPlaces uint32) {
if len(tokenAddress) > 0 {
req := &xudrpc.Currency{Currency: currency, SwapClient: swapClient, TokenAddress: tokenAddress, DecimalPlaces: decimalPlaces}
Expand All @@ -102,6 +123,24 @@ func (a *actions) addCurrency(node *xudtest.HarnessNode, currency string, swapCl
}
}

func (a *actions) removePair(node *xudtest.HarnessNode, pairId string) {
// Check the current number of pairs.
res, err := node.Client.GetInfo(a.ctx, &xudrpc.GetInfoRequest{})
a.assert.NoError(err)

prevNumPairs := res.NumPairs

// Remove the pair.
req := &xudrpc.RemovePairRequest{PairId: pairId}
_, err = node.Client.RemovePair(a.ctx, req)
a.assert.NoError(err)

// Verify that the pair was removed.
res, err = node.Client.GetInfo(a.ctx, &xudrpc.GetInfoRequest{})
a.assert.NoError(err)
a.assert.Equal(res.NumPairs, prevNumPairs-1)
}

func (a *actions) addPair(node *xudtest.HarnessNode, baseCurrency string, quoteCurrency string) {
// Check the current number of pairs.
res, err := node.Client.GetInfo(a.ctx, &xudrpc.GetInfoRequest{})
Expand Down Expand Up @@ -232,10 +271,16 @@ func (a *actions) placeOrderAndBroadcast(srcNode, destNode *xudtest.HarnessNode,
a.assert.Equal(res.RemainingOrder.LocalId, req.OrderId)

// Retrieve and verify the added order event on destNode.
e := <-destNodeOrderChan
a.assert.NoError(e.err)
a.assert.NotNil(e.orderUpdate)
peerOrder := e.orderUpdate.GetOrder()

var peerOrder *xudrpc.Order
select {
case e := <-destNodeOrderChan:
a.assert.NoError(e.err)
a.assert.NotNil(e.orderUpdate)
peerOrder = e.orderUpdate.GetOrder()
case <-time.After(5 * time.Second):
a.assert.Fail("timeout waiting for broadcasted order on destNode")
}

// Verify the peer order.
a.assert.NotEqual(peerOrder.Id, req.OrderId) // Local id should not equal the global id.
Expand Down
2 changes: 2 additions & 0 deletions test/simulation/connexttest/harness.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ var (
// the `localhost` of the host machine.
EthProviderURL = "http://172.17.0.1:8545"
NodeURL = "http://172.17.0.1:8888"

ETHTokenAddress = "0x0000000000000000000000000000000000000000"
)

type NetworkHarness struct {
Expand Down
Loading