Skip to content

Commit

Permalink
feat(rpc): runtime addcurrency for lnd & connext (#1746)
Browse files Browse the repository at this point in the history
Co-authored-by: Le Premier Homme <[email protected]>
  • Loading branch information
sangaman and Le Premier Homme authored Nov 5, 2020
1 parent a1287dd commit fc83823
Show file tree
Hide file tree
Showing 8 changed files with 263 additions and 160 deletions.
19 changes: 16 additions & 3 deletions lib/orderbook/OrderBook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,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 @@ -335,7 +340,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 @@ -347,7 +352,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 @@ -363,6 +367,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 Expand Up @@ -989,7 +998,11 @@ class OrderBook extends EventEmitter {
}

private removePeerPair = (peerPubKey: string, pairId: string) => {
const tp = this.getTradingPair(pairId);
const tp = this.tradingPairs.get(pairId);
if (!tp) {
return;
}

const orders = tp.removePeerOrders(peerPubKey);
orders.forEach((order) => {
this.emit('peerOrder.invalidation', order);
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 @@ -482,6 +484,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 @@ -424,19 +424,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

0 comments on commit fc83823

Please sign in to comment.