From ab03e5914f23c0aaa1c3ff28072f64f7238f41b2 Mon Sep 17 00:00:00 2001 From: Daniel McNally Date: Thu, 23 Jul 2020 00:41:24 -0400 Subject: [PATCH 01/11] feat(rpc): runtime addcurrency for lnd & connext This PR allows for adding Connext and Lnd currencies via the `AddCurrency` rpc call and have them available for trading immediately without requiring a restart of xud. In the case of Connext it merely registers the tokenaddress and currency symbol for the new currency with the existing ConnextClient, as we had previously done with Raiden. In the case of Lnd, we read from the configuration and use that to instantiate and initialize a new `LndClient` for the specified currency. Closes #1111. --- lib/orderbook/OrderBook.ts | 2 +- lib/swaps/SwapClientManager.ts | 41 ++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index 967b80bb7..f4e734adf 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -295,7 +295,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) => { diff --git a/lib/swaps/SwapClientManager.ts b/lib/swaps/SwapClientManager.ts index 4a09f9faf..a4a2d5901 100644 --- a/lib/swaps/SwapClientManager.ts +++ b/lib/swaps/SwapClientManager.ts @@ -330,25 +330,38 @@ 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.Raiden && currency.tokenAddress && this.raidenClient) { - this.swapClients.set(currency.id, this.raidenClient); - this.raidenClient.tokenAddresses.set(currency.id, currency.tokenAddress); - this.emit('raidenUpdate', this.raidenClient.tokenAddresses, this.raidenClient.address); + 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, this.connextClient.address); + } else if (currency.swapClient === SwapClientType.Raiden) { + if (!this.raidenClient) { + throw errors.SWAP_CLIENT_NOT_CONFIGURED(currency.id); + } + this.swapClients.set(currency.id, this.raidenClient); + this.raidenClient.tokenAddresses.set(currency.id, currency.tokenAddress); + this.emit('raidenUpdate', this.raidenClient.tokenAddresses, this.raidenClient.address); + } } else 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; - } - } - // adding a new lnd client at runtime is currently not supported - if (!isCurrencyConfigured) { + const config = this.config.lnd[currency.id]; + if (!config) { throw errors.SWAP_CLIENT_NOT_CONFIGURED(currency.id); } + + const lndClient = new LndClient({ + config, + logger: this.loggers.lnd.createSubLogger(currency.id), + currency: currency.id, + }); + this.swapClients.set(currency.id, lndClient); + await lndClient.init(); } } From 0c60419489c8ae185ae39586d3a97a77165f3070 Mon Sep 17 00:00:00 2001 From: Daniel McNally Date: Fri, 24 Jul 2020 03:16:11 -0400 Subject: [PATCH 02/11] log remove order error --- lib/orderbook/OrderBook.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index f4e734adf..3f28fe14f 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -290,7 +290,8 @@ class OrderBook extends EventEmitter { if (this.currencyInstances.has(currency.id)) { throw errors.CURRENCY_ALREADY_EXISTS(currency.id); } - if (currency.swapClient === SwapClientType.Raiden && !currency.tokenAddress) { + if ((currency.swapClient === SwapClientType.Raiden || currency.swapClient === SwapClientType.Connext) + && !currency.tokenAddress) { throw errors.CURRENCY_MISSING_ETHEREUM_CONTRACT_ADDRESS(currency.id); } const currencyInstance = await this.repository.addCurrency({ ...currency, decimalPlaces: currency.decimalPlaces || 8 }); @@ -968,8 +969,8 @@ class OrderBook extends EventEmitter { try { const removeResult = this.removePeerOrder(oi.id, oi.pairId, peerPubKey, oi.quantity); this.emit('peerOrder.invalidation', removeResult.order); - } catch { - this.logger.error(`failed to remove order (${oi.id}) of peer ${peerPubKey} (${pubKeyToAlias(peerPubKey)})`); + } catch (err) { + this.logger.error(`failed to remove order (${oi.id}) of peer ${peerPubKey} (${pubKeyToAlias(peerPubKey)})`, err); // TODO: Penalize peer } } From afcc0fc6b8a583f1f78d668200d2d0fe01f9df41 Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Tue, 6 Oct 2020 14:32:58 +0300 Subject: [PATCH 03/11] Merge fix: remove raiden --- lib/orderbook/OrderBook.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index d2e7b00d4..a653654b1 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -327,8 +327,7 @@ class OrderBook extends EventEmitter { if (this.currencyInstances.has(currency.id)) { throw errors.CURRENCY_ALREADY_EXISTS(currency.id); } - if ((currency.swapClient === SwapClientType.Raiden || currency.swapClient === SwapClientType.Connext) - && !currency.tokenAddress) { + if ((currency.swapClient === SwapClientType.Connext) && !currency.tokenAddress) { throw errors.CURRENCY_MISSING_ETHEREUM_CONTRACT_ADDRESS(currency.id); } const currencyInstance = await this.repository.addCurrency({ ...currency, decimalPlaces: currency.decimalPlaces || 8 }); From f6d1b6c7ca4d7527efa5cd8bc8da65ecee84148c Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Wed, 7 Oct 2020 11:55:32 +0300 Subject: [PATCH 04/11] Fix: don't re-create lnd swap client instance --- lib/orderbook/OrderBook.ts | 2 +- lib/swaps/SwapClientManager.ts | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index a653654b1..ee5c24566 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -327,7 +327,7 @@ class OrderBook extends EventEmitter { if (this.currencyInstances.has(currency.id)) { throw errors.CURRENCY_ALREADY_EXISTS(currency.id); } - if ((currency.swapClient === SwapClientType.Connext) && !currency.tokenAddress) { + if (currency.swapClient === SwapClientType.Connext && !currency.tokenAddress) { throw errors.CURRENCY_MISSING_ETHEREUM_CONTRACT_ADDRESS(currency.id); } const currencyInstance = await this.repository.addCurrency({ ...currency, decimalPlaces: currency.decimalPlaces || 8 }); diff --git a/lib/swaps/SwapClientManager.ts b/lib/swaps/SwapClientManager.ts index 03bac4a5e..16a32458b 100644 --- a/lib/swaps/SwapClientManager.ts +++ b/lib/swaps/SwapClientManager.ts @@ -326,14 +326,6 @@ class SwapClientManager extends EventEmitter { if (!config) { throw errors.SWAP_CLIENT_NOT_CONFIGURED(currency.id); } - - const lndClient = new LndClient({ - config, - logger: this.loggers.lnd.createSubLogger(currency.id), - currency: currency.id, - }); - this.swapClients.set(currency.id, lndClient); - await lndClient.init(); } } From 728fd9cb656151e1fe1ef0e69af93d6f7f5bbd20 Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Mon, 19 Oct 2020 14:03:49 +0300 Subject: [PATCH 05/11] Fix: add/remove pair to trigger checkPeerCurrencies/verifyPeerPairs for all connected peers --- lib/orderbook/OrderBook.ts | 10 ++++++++++ lib/p2p/Pool.ts | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index ee5c24566..21dbf2388 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -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; } @@ -360,6 +365,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(); } diff --git a/lib/p2p/Pool.ts b/lib/p2p/Pool.ts index d4cc7c0c2..7ba2769d0 100644 --- a/lib/p2p/Pool.ts +++ b/lib/p2p/Pool.ts @@ -476,6 +476,10 @@ class Pool extends EventEmitter { return peerInfos; } + public rawPeers = (): Map => { + return this.peers; + } + private addressIsSelf = (address: Address): boolean => { if (address.port === this.listenPort) { switch (address.host) { From 07922b7b63e78ad7354b5b260dba856836037ea3 Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Mon, 19 Oct 2020 14:05:43 +0300 Subject: [PATCH 06/11] Fix: remove currency to not remove the active swap client --- lib/orderbook/OrderBook.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index 21dbf2388..23f5c13d2 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -349,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); From 815df36d0da9d703ca6cac218c55cbdcf8bd3949 Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Mon, 19 Oct 2020 14:09:01 +0300 Subject: [PATCH 07/11] Fix: connextUpdate event to not update the faulty pubkey string --- lib/p2p/Pool.ts | 4 +++- lib/swaps/SwapClientManager.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/p2p/Pool.ts b/lib/p2p/Pool.ts index 7ba2769d0..dc41b5867 100644 --- a/lib/p2p/Pool.ts +++ b/lib/p2p/Pool.ts @@ -238,7 +238,9 @@ class Pool extends EventEmitter { * packet to currently connected peers to notify them of the change. */ public updateConnextState = (tokenAddresses: Map, pubKey?: string) => { - this.nodeState.connextIdentifier = pubKey || ''; + if (pubKey) { + this.nodeState.connextIdentifier = pubKey; + } tokenAddresses.forEach((tokenAddress, currency) => { this.nodeState.tokenIdentifiers[currency] = tokenAddress; }); diff --git a/lib/swaps/SwapClientManager.ts b/lib/swaps/SwapClientManager.ts index 16a32458b..9921b8146 100644 --- a/lib/swaps/SwapClientManager.ts +++ b/lib/swaps/SwapClientManager.ts @@ -317,7 +317,7 @@ class SwapClientManager extends EventEmitter { } this.swapClients.set(currency.id, this.connextClient); this.connextClient.tokenAddresses.set(currency.id, currency.tokenAddress); - this.emit('connextUpdate', this.connextClient.tokenAddresses, this.connextClient.address); + this.emit('connextUpdate', this.connextClient.tokenAddresses); } } else if (currency.swapClient === SwapClientType.Lnd) { // in case of lnd we check if the configuration includes swap client From 767f3d68a1b0f0fca1f0e9b8bd8f42c11e43f9e8 Mon Sep 17 00:00:00 2001 From: Daniel McNally Date: Mon, 19 Oct 2020 11:46:23 -0400 Subject: [PATCH 08/11] feat(rpc): runtime addcurrency for lnd & connext This PR allows for adding Connext and Lnd currencies via the `AddCurrency` rpc call and have them available for trading immediately without requiring a restart of xud. In the case of Connext it merely registers the tokenaddress and currency symbol for the new currency with the existing ConnextClient, as we had previously done with Raiden. In the case of Lnd, we read from the configuration and use that to instantiate and initialize a new `LndClient` for the specified currency. Closes #1111. Co-authored-by: Le Premier Homme --- lib/orderbook/OrderBook.ts | 13 +++++++++++-- lib/p2p/Pool.ts | 8 +++++++- lib/swaps/SwapClientManager.ts | 24 +++++++++++++----------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index ed8b42de7..98c29505c 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -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; } @@ -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) => { @@ -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); @@ -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(); } diff --git a/lib/p2p/Pool.ts b/lib/p2p/Pool.ts index d4cc7c0c2..dc41b5867 100644 --- a/lib/p2p/Pool.ts +++ b/lib/p2p/Pool.ts @@ -238,7 +238,9 @@ class Pool extends EventEmitter { * packet to currently connected peers to notify them of the change. */ public updateConnextState = (tokenAddresses: Map, pubKey?: string) => { - this.nodeState.connextIdentifier = pubKey || ''; + if (pubKey) { + this.nodeState.connextIdentifier = pubKey; + } tokenAddresses.forEach((tokenAddress, currency) => { this.nodeState.tokenIdentifiers[currency] = tokenAddress; }); @@ -476,6 +478,10 @@ class Pool extends EventEmitter { return peerInfos; } + public rawPeers = (): Map => { + return this.peers; + } + private addressIsSelf = (address: Address): boolean => { if (address.port === this.listenPort) { switch (address.host) { diff --git a/lib/swaps/SwapClientManager.ts b/lib/swaps/SwapClientManager.ts index 4e0877287..b2bba6d37 100644 --- a/lib/swaps/SwapClientManager.ts +++ b/lib/swaps/SwapClientManager.ts @@ -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); } } From 29a18698f4467f4df48416db63035993ea433a0f Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Wed, 21 Oct 2020 13:04:56 +0300 Subject: [PATCH 09/11] simtest: add scenario, refactoring --- test/simulation/README.md | 1 + test/simulation/actions.go | 145 ++++++++++++------- test/simulation/connexttest/harness.go | 2 + test/simulation/tests-instability.go | 33 ++--- test/simulation/tests-integration.go | 191 +++++++++++++++---------- 5 files changed, 227 insertions(+), 145 deletions(-) diff --git a/test/simulation/README.md b/test/simulation/README.md index c838f0625..1a2aea75a 100644 --- a/test/simulation/README.md +++ b/test/simulation/README.md @@ -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 already-connected peers. - [ ] Placed order should get internal matches, and trigger order invalidation over the network. - [ ] Peer disconnection should trigger orders removal to all his orders. diff --git a/test/simulation/actions.go b/test/simulation/actions.go index d3937098d..dac411f9d 100644 --- a/test/simulation/actions.go +++ b/test/simulation/actions.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/ExchangeUnion/xud-simulation/connexttest" "math/big" "time" @@ -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} @@ -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{}) @@ -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. diff --git a/test/simulation/connexttest/harness.go b/test/simulation/connexttest/harness.go index 15163a637..6823f561c 100644 --- a/test/simulation/connexttest/harness.go +++ b/test/simulation/connexttest/harness.go @@ -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 { diff --git a/test/simulation/tests-instability.go b/test/simulation/tests-instability.go index 9274cff72..6881eb420 100644 --- a/test/simulation/tests-instability.go +++ b/test/simulation/tests-instability.go @@ -61,7 +61,7 @@ func testMakerCrashedAfterSendBeforePreimageResolved(net *xudtest.NetworkHarness } func testMakerCrashedAfterSendBeforePreimageResolvedConnextIn(net *xudtest.NetworkHarness, ht *harnessTest) { - ht.act.initConnext(net, net.Bob, true) + ht.act.FundETH(net, net.Bob) testMakerCrashedDuringSwapConnextIn(net, ht, []string{"CUSTOM_SCENARIO=INSTABILITY::MAKER_CRASH_AFTER_SEND_BEFORE_PREIMAGE_RESOLVED"}) } @@ -130,7 +130,7 @@ func testMakerCrashedDuringSwapConnextIn(net *xudtest.NetworkHarness, ht *harnes net.Alice, err = net.SetCustomXud(ht.ctx, ht, net.Alice, makerEnvArgs) ht.assert.NoError(err) ht.act.init(net.Alice) - ht.act.initConnext(net, net.Alice, false) + ht.act.waitConnextReady(net.Alice) // Connect Alice to Bob. ht.act.connect(net.Alice, net.Bob) @@ -170,9 +170,7 @@ func testMakerCrashedDuringSwapConnextIn(net *xudtest.NetworkHarness, ht *harnes err = net.Alice.Start(nil) ht.assert.NoError(err) - - err = waitConnextReady(net.Alice) - ht.assert.NoError(err) + ht.act.waitConnextReady(net.Alice) // Brief delay to allow for swap to be recovered consistently. time.Sleep(3 * time.Second) @@ -257,9 +255,8 @@ func testMakerConnextClientCrashedBeforeSettlement(net *xudtest.NetworkHarness, ht.assert.NoError(err) ht.act.init(net.Alice) - - ht.act.initConnext(net, net.Alice, false) - ht.assert.NoError(waitConnextReady(net.Bob)) + ht.act.waitConnextReady(net.Alice) + ht.act.waitConnextReady(net.Bob) // Connect Alice to Bob. ht.act.connect(net.Alice, net.Bob) @@ -306,9 +303,7 @@ func testMakerConnextClientCrashedBeforeSettlement(net *xudtest.NetworkHarness, // Restart Alice's connext-client. err = net.Alice.ConnextClient.Start(nil) ht.assert.NoError(err) - - err = waitConnextReady(net.Alice) - ht.assert.NoError(err) + ht.act.waitConnextReady(net.Alice) // Brief delay to allow for swap to be recovered consistently. // The pending swap recheck interval is usually 5m, but was adjusted in @@ -400,10 +395,10 @@ func testMakerCrashedAfterSendDelayedSettlementConnextOut(net *xudtest.NetworkHa ht.assert.NoError(err) ht.act.init(net.Alice) - ht.act.initConnext(net, net.Alice, true) + ht.act.FundETH(net, net.Alice) ht.act.init(net.Bob) - ht.act.initConnext(net, net.Bob, false) + ht.act.waitConnextReady(net.Bob) // Connect Alice to Bob. ht.act.connect(net.Alice, net.Bob) @@ -445,9 +440,7 @@ func testMakerCrashedAfterSendDelayedSettlementConnextOut(net *xudtest.NetworkHa err = net.Alice.Start(nil) ht.assert.NoError(err) - - err = waitConnextReady(net.Alice) - ht.assert.NoError(err) + ht.act.waitConnextReady(net.Alice) // Verify that alice hasn't claimed her BTC yet. The incoming BTC payment // cannot be settled until the outgoing ETH payment is settled by bob, @@ -488,10 +481,10 @@ func testMakerCrashedAfterSendDelayedSettlementConnextIn(net *xudtest.NetworkHar ht.assert.NoError(err) ht.act.init(net.Alice) - ht.act.initConnext(net, net.Alice, false) + ht.act.waitConnextReady(net.Alice) ht.act.init(net.Bob) - ht.act.initConnext(net, net.Bob, true) + ht.act.FundETH(net, net.Bob) // Connect Alice to Bob. ht.act.connect(net.Alice, net.Bob) @@ -533,9 +526,7 @@ func testMakerCrashedAfterSendDelayedSettlementConnextIn(net *xudtest.NetworkHar err = net.Alice.Start(nil) ht.assert.NoError(err) - - err = waitConnextReady(net.Alice) - ht.assert.NoError(err) + ht.act.waitConnextReady(net.Alice) // Verify that alice hasn't claimed her ETH yet. The incoming ETH payment // cannot be settled until the outgoing BTC payment is settled by bob, diff --git a/test/simulation/tests-integration.go b/test/simulation/tests-integration.go index 86ad4d804..376dd9522 100644 --- a/test/simulation/tests-integration.go +++ b/test/simulation/tests-integration.go @@ -1,9 +1,8 @@ package main import ( - "context" "fmt" - "math/big" + "github.com/ExchangeUnion/xud-simulation/connexttest" "time" "github.com/ExchangeUnion/xud-simulation/xudrpc" @@ -55,6 +54,10 @@ var integrationTestCases = []*testCase{ name: "order broadcast and invalidation", test: testOrderBroadcastAndInvalidation, }, + { + name: "runtime add pair of active orders", + test: testRuntimeAddPairActiveOrders, + }, { name: "multiple hop swap", test: testMultiHopSwap, @@ -231,6 +234,95 @@ func testInternalMatchAndInvalidation(net *xudtest.NetworkHarness, ht *harnessTe ht.act.disconnect(net.Alice, net.Bob) } +// testRuntimeAddPairActiveOrders implements: +// Added trading pairs and currencies should trigger broadcast of active orders from already-connected peers. +func testRuntimeAddPairActiveOrders(net *xudtest.NetworkHarness, ht *harnessTest) { + // Remove a previously-added pairs/currencies from both Alice and Bob. + ht.act.removePair(net.Alice, "LTC/BTC") + ht.act.removePair(net.Alice, "BTC/ETH") + ht.act.removeCurrency(net.Alice, "LTC") + ht.act.removeCurrency(net.Alice, "BTC") + ht.act.removeCurrency(net.Alice, "ETH") + ht.act.removePair(net.Bob, "LTC/BTC") + ht.act.removePair(net.Bob, "BTC/ETH") + ht.act.removeCurrency(net.Bob, "LTC") + ht.act.removeCurrency(net.Bob, "BTC") + ht.act.removeCurrency(net.Bob, "ETH") + + // Connect Alice to Bob. + ht.act.connect(net.Alice, net.Bob) + ht.act.verifyConnectivity(net.Alice, net.Bob) + + // Re-add the pairs/currencies to Alice after peer connection was already established. + ht.act.addCurrency(net.Alice, "BTC", xudrpc.Currency_LND, "", 8) + ht.act.addCurrency(net.Alice, "LTC", xudrpc.Currency_LND, "", 8) + ht.act.addCurrency(net.Alice, "ETH", xudrpc.Currency_CONNEXT, connexttest.ETHTokenAddress, 18) + ht.act.addPair(net.Alice, "LTC", "BTC") + ht.act.addPair(net.Alice, "BTC", "ETH") + + // Place LTC/BTC order on Alice. + req := &xudrpc.PlaceOrderRequest{ + OrderId: "maker_order_id", + Price: 0.02, + Quantity: 1000000, + PairId: "LTC/BTC", + Side: xudrpc.OrderSide_BUY, + } + res, err := net.Alice.Client.PlaceOrderSync(ht.ctx, req) + ht.assert.NoError(err) + ht.assert.Len(res.InternalMatches, 0) + ht.assert.Len(res.SwapSuccesses, 0) + ht.assert.Len(res.SwapFailures, 0) + ht.assert.NotNil(res.RemainingOrder) + + // Bob should receive the order once his LTC/BTC is re-added. + bobOrdersChan := subscribeOrders(ht.ctx, net.Bob) + ht.act.addCurrency(net.Bob, "BTC", xudrpc.Currency_LND, "", 8) + ht.act.addCurrency(net.Bob, "LTC", xudrpc.Currency_LND, "", 8) + ht.act.addPair(net.Bob, "LTC", "BTC") + + e := <-bobOrdersChan + ht.assert.NoError(e.err) + ht.assert.NotNil(e.orderUpdate) + peerOrder := e.orderUpdate.GetOrder() + ht.assert.Equal(peerOrder.Id, res.RemainingOrder.Id) + ht.assert.Equal(peerOrder.PairId, req.PairId) + ht.assert.Equal(peerOrder.NodeIdentifier.NodePubKey, net.Alice.PubKey()) + ht.act.removeOrderAndInvalidate(net.Alice, net.Bob, res.RemainingOrder) + + // Place BTC/ETH order on Alice. + req = &xudrpc.PlaceOrderRequest{ + OrderId: "maker_order_id", + Price: 40, + Quantity: 100, + PairId: "BTC/ETH", + Side: xudrpc.OrderSide_BUY, + } + res, err = net.Alice.Client.PlaceOrderSync(ht.ctx, req) + ht.assert.NoError(err) + ht.assert.Len(res.InternalMatches, 0) + ht.assert.Len(res.SwapSuccesses, 0) + ht.assert.Len(res.SwapFailures, 0) + ht.assert.NotNil(res.RemainingOrder) + + // Bob should receive the order once his BTC/ETH is re-added. + bobOrdersChan = subscribeOrders(ht.ctx, net.Bob) + ht.act.addCurrency(net.Bob, "ETH", xudrpc.Currency_CONNEXT, connexttest.ETHTokenAddress, 18) + ht.act.addPair(net.Bob, "BTC", "ETH") + + e = <-bobOrdersChan + ht.assert.NoError(e.err) + ht.assert.NotNil(e.orderUpdate) + peerOrder = e.orderUpdate.GetOrder() + ht.assert.Equal(peerOrder.Id, res.RemainingOrder.Id) + ht.assert.Equal(peerOrder.PairId, req.PairId) + ht.assert.Equal(peerOrder.NodeIdentifier.NodePubKey, net.Alice.PubKey()) + ht.act.removeOrderAndInvalidate(net.Alice, net.Bob, res.RemainingOrder) + + // Cleanup. + ht.act.disconnect(net.Alice, net.Bob) +} + func testOrderMatchingAndSwap(net *xudtest.NetworkHarness, ht *harnessTest) { // Connect Alice to Bob. ht.act.connect(net.Alice, net.Bob) @@ -394,70 +486,16 @@ func testOrderReplacement(net *xudtest.NetworkHarness, ht *harnessTest) { ht.act.disconnect(net.Alice, net.Bob) } -func waitConnextReady(node *xudtest.HarnessNode) error { - 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: - return fmt.Errorf("timeout waiting for connext to be ready") - case <-time.After(1 * time.Second): - } - } - - return nil -} - func testOrderMatchingAndSwapConnext(net *xudtest.NetworkHarness, ht *harnessTest) { - // Wait for Alice's connext connection to catch-up. - err := waitConnextReady(net.Alice) - ht.assert.NoError(err) - - // Fund Alice's wallet. - resInfo, err := net.Alice.Client.GetInfo(context.Background(), &xudrpc.GetInfoRequest{}) - ht.assert.NoError(err) - amount := big.NewInt(2000000000000000000) - err = net.ConnextNetwork.Wallet.SendEth(resInfo.Connext.Address, amount) - ht.assert.NoError(err) - - time.Sleep(15 * time.Second) - - // Init Alice. - ETHTokenAddress := "0x0000000000000000000000000000000000000000" - ht.act.addCurrency(net.Alice, "ETH", 2, ETHTokenAddress, 18) - ht.act.addPair(net.Alice, "BTC", "ETH") - err = net.RestartNode(net.Alice) - ht.assert.NoError(err) + // Connect Alice to Bob. + ht.act.connect(net.Alice, net.Bob) + ht.act.verifyConnectivity(net.Alice, net.Bob) - // Verify Alice ETH balance. - resBal, err := net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) - ht.assert.Equal(uint64(200000000), resBal.Balances["ETH"].TotalBalance) - ht.assert.Equal(uint64(200000000), resBal.Balances["ETH"].WalletBalance) - ht.assert.Equal(uint64(0), resBal.Balances["ETH"].ChannelBalance) + ht.act.FundETH(net, net.Alice) - // Wait for Bob's connext connection to catch-up. - err = waitConnextReady(net.Bob) + preChanAliceBal, err := net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) ht.assert.NoError(err) - - // Init Bob. - ht.act.addCurrency(net.Bob, "ETH", 2, ETHTokenAddress, 18) - ht.act.addPair(net.Bob, "BTC", "ETH") - err = net.RestartNode(net.Bob) - ht.assert.NoError(err) - - // Verify Bob ETH balance. - resBal, err = net.Bob.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) - ht.assert.Equal(uint64(0), resBal.Balances["ETH"].TotalBalance) - ht.assert.Equal(uint64(0), resBal.Balances["ETH"].WalletBalance) - ht.assert.Equal(uint64(0), resBal.Balances["ETH"].ChannelBalance) + preChanAliceEthBal := preChanAliceBal.Balances["ETH"] // Open channel from Alice. err = openETHChannel(ht.ctx, net.Alice, 40000, 0) @@ -466,10 +504,12 @@ func testOrderMatchingAndSwapConnext(net *xudtest.NetworkHarness, ht *harnessTes time.Sleep(15 * time.Second) // Verify Alice ETH balance. - resBal, err = net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) - ht.assert.Equal(uint64(199997900), resBal.Balances["ETH"].TotalBalance) - ht.assert.Equal(resBal.Balances["ETH"].TotalBalance-40000, resBal.Balances["ETH"].WalletBalance) - ht.assert.Equal(uint64(40000), resBal.Balances["ETH"].ChannelBalance) + chanFeesThreshold := uint64(2100) + preSwapAliceBal, err := net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) + preSwapAliceEthBal := preSwapAliceBal.Balances["ETH"] + ht.assert.True(preChanAliceEthBal.TotalBalance-preSwapAliceEthBal.TotalBalance <= chanFeesThreshold) + ht.assert.Equal(preSwapAliceEthBal.TotalBalance-preSwapAliceEthBal.ChannelBalance, preSwapAliceEthBal.WalletBalance) + ht.assert.Equal(uint64(40000), preSwapAliceEthBal.ChannelBalance) // wait for 1 block for node to collateralize ETH channel time.Sleep(15 * time.Second) @@ -497,16 +537,19 @@ func testOrderMatchingAndSwapConnext(net *xudtest.NetworkHarness, ht *harnessTes time.Sleep(5 * time.Second) // Verify Alice ETH balance. - resBal, err = net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) - ht.assert.Equal(uint64(199993900), resBal.Balances["ETH"].TotalBalance) - ht.assert.Equal(resBal.Balances["ETH"].TotalBalance-36000, resBal.Balances["ETH"].WalletBalance) - ht.assert.Equal(uint64(36000), resBal.Balances["ETH"].ChannelBalance) + amt := uint64(req.Price * float64(req.Quantity)) + aliceBal, err := net.Alice.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) + aliceEthBal := aliceBal.Balances["ETH"] + ht.assert.Equal(preSwapAliceEthBal.TotalBalance-amt, aliceEthBal.TotalBalance) + ht.assert.Equal(aliceEthBal.TotalBalance-aliceEthBal.ChannelBalance, aliceEthBal.WalletBalance) + ht.assert.Equal(preSwapAliceEthBal.ChannelBalance-amt, aliceEthBal.ChannelBalance) // Verify Bob ETH balance. - resBal, err = net.Bob.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) - ht.assert.Equal(uint64(4000), resBal.Balances["ETH"].TotalBalance) - ht.assert.Equal(uint64(0), resBal.Balances["ETH"].WalletBalance) - ht.assert.Equal(uint64(4000), resBal.Balances["ETH"].ChannelBalance) + bobBalance, err := net.Bob.Client.GetBalance(ht.ctx, &xudrpc.GetBalanceRequest{Currency: "ETH"}) + bobEthBalance := bobBalance.Balances["ETH"] + ht.assert.Equal(amt, bobEthBalance.TotalBalance) + ht.assert.Equal(uint64(0), bobEthBalance.WalletBalance) + ht.assert.Equal(amt, bobEthBalance.ChannelBalance) // Cleanup. ht.act.disconnect(net.Alice, net.Bob) From 84ccbf2b64b8c81cd7db55699f6394bee77c4116 Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Wed, 21 Oct 2020 13:17:30 +0300 Subject: [PATCH 10/11] typo fix --- test/simulation/README.md | 2 +- test/simulation/tests-integration.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/simulation/README.md b/test/simulation/README.md index 1a2aea75a..68542a131 100644 --- a/test/simulation/README.md +++ b/test/simulation/README.md @@ -117,7 +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 already-connected peers. +- [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. diff --git a/test/simulation/tests-integration.go b/test/simulation/tests-integration.go index 2509e097a..8c19a6eb2 100644 --- a/test/simulation/tests-integration.go +++ b/test/simulation/tests-integration.go @@ -242,7 +242,7 @@ func testInternalMatchAndInvalidation(net *xudtest.NetworkHarness, ht *harnessTe // testRuntimeAddPairActiveOrders implements: // Added trading pairs and currencies should trigger broadcast of active orders from already-connected peers. func testRuntimeAddPairActiveOrders(net *xudtest.NetworkHarness, ht *harnessTest) { - // Remove a previously-added pairs/currencies from both Alice and Bob. + // Remove previously-added pairs/currencies from both Alice and Bob. ht.act.removePair(net.Alice, "LTC/BTC") ht.act.removePair(net.Alice, "BTC/ETH") ht.act.removeCurrency(net.Alice, "LTC") @@ -280,7 +280,7 @@ func testRuntimeAddPairActiveOrders(net *xudtest.NetworkHarness, ht *harnessTest ht.assert.Len(res.SwapFailures, 0) ht.assert.NotNil(res.RemainingOrder) - // Bob should receive the order once his LTC/BTC is re-added. + // Bob should receive the order once his LTC/BTC pair is re-added. bobOrdersChan := subscribeOrders(ht.ctx, net.Bob) ht.act.addCurrency(net.Bob, "BTC", xudrpc.Currency_LND, "", 8) ht.act.addCurrency(net.Bob, "LTC", xudrpc.Currency_LND, "", 8) @@ -310,7 +310,7 @@ func testRuntimeAddPairActiveOrders(net *xudtest.NetworkHarness, ht *harnessTest ht.assert.Len(res.SwapFailures, 0) ht.assert.NotNil(res.RemainingOrder) - // Bob should receive the order once his BTC/ETH is re-added. + // Bob should receive the order once his BTC/ETH pair is re-added. bobOrdersChan = subscribeOrders(ht.ctx, net.Bob) ht.act.addCurrency(net.Bob, "ETH", xudrpc.Currency_CONNEXT, connexttest.ETHTokenAddress, 18) ht.act.addPair(net.Bob, "BTC", "ETH") From e52ba4fd69f6aa36856eb9281effd3c2d32f5f05 Mon Sep 17 00:00:00 2001 From: Le Premier Homme Date: Sun, 1 Nov 2020 12:36:30 +0200 Subject: [PATCH 11/11] removePeerPair to support non-existing pair --- lib/orderbook/OrderBook.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/orderbook/OrderBook.ts b/lib/orderbook/OrderBook.ts index 98c29505c..a9c663326 100644 --- a/lib/orderbook/OrderBook.ts +++ b/lib/orderbook/OrderBook.ts @@ -1015,7 +1015,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);