diff --git a/lib/Xud.ts b/lib/Xud.ts index 490e252c0..d83880e0a 100644 --- a/lib/Xud.ts +++ b/lib/Xud.ts @@ -35,9 +35,9 @@ class Xud extends EventEmitter { public service!: Service; private logger!: Logger; private config: Config; - private db!: DB; - private pool!: Pool; - private orderBook!: OrderBook; + private db?: DB; + private pool?: Pool; + private orderBook?: OrderBook; private rpcServer?: GrpcServer; private httpServer?: HttpServer; private grpcAPIProxy?: GrpcWebProxyServer; @@ -46,6 +46,7 @@ class Xud extends EventEmitter { private swapClientManager?: SwapClientManager; private unitConverter?: UnitConverter; private simnetChannels$?: Subscription; + private lockFilePath?: string; /** * Create an Exchange Union daemon. @@ -88,6 +89,24 @@ class Xud extends EventEmitter { } try { + if (this.config.instanceid === 0) { + // if we're not running with multiple instances as indicated by an instance id of 0 + // create a lock file to prevent multiple xud instances from trying to run + // with the same node key and/or database + const lockFilePath = path.join(this.config.xudir, `${this.config.network}.lock`); + try { + await (await fs.open(lockFilePath, 'wx')).close(); + this.lockFilePath = lockFilePath; + } catch (err) { + if (err.code === 'EEXIST') { + this.logger.error(`A lock file exists at ${lockFilePath}. Another xud process is running or a previous process exited ungracefully.`); + return; + } else { + throw err; + } + } + } + if (!this.config.rpc.disable) { // start rpc server first, it will respond with UNAVAILABLE error // indicating xud is starting until xud finishes initializing @@ -296,6 +315,10 @@ class Xud extends EventEmitter { // TODO: ensure we are not in the middle of executing any trades const closePromises: Promise[] = []; + if (this.lockFilePath) { + closePromises.push(fs.unlink(this.lockFilePath).catch(this.logger.error)); + } + this.simnetChannels$?.unsubscribe(); if (this.swapClientManager) { @@ -318,7 +341,7 @@ class Xud extends EventEmitter { } await Promise.all(closePromises); - await this.db.close(); + await this.db?.close(); this.logger.info('XUD shutdown gracefully'); this.emit('shutdown'); diff --git a/lib/service/Service.ts b/lib/service/Service.ts index f9ded99a7..8e3898a0d 100644 --- a/lib/service/Service.ts +++ b/lib/service/Service.ts @@ -5,6 +5,7 @@ import { ProvidePreimageEvent, TransferReceivedEvent } from '../connextclient/ty import { OrderSide, Owner, SwapClientType, SwapRole } from '../constants/enums'; import { OrderAttributes, TradeInstance } from '../db/types'; import Logger, { Level, LevelPriority } from '../Logger'; +import NodeKey from '../nodekey/NodeKey'; import OrderBook from '../orderbook/OrderBook'; import { Currency, isOwnOrder, Order, OrderPortion, OwnLimitOrder, OwnMarketOrder, OwnOrder, PeerOrder, PlaceOrderEvent } from '../orderbook/types'; import Pool from '../p2p/Pool'; @@ -19,7 +20,6 @@ import { checkDecimalPlaces, sortOrders, toEip55Address } from '../utils/utils'; import commitHash from '../Version'; import errors from './errors'; import { NodeIdentifier, ServiceComponents, ServiceOrder, ServiceOrderSidesArrays, ServicePlaceOrderEvent, ServiceTrade, XudInfo } from './types'; -import NodeKey from 'lib/nodekey/NodeKey'; /** Functions to check argument validity and throw [[INVALID_ARGUMENT]] when invalid. */ const argChecks = { diff --git a/test/integration/Service.spec.ts b/test/integration/Service.spec.ts index 128511dbc..f9b4ed349 100644 --- a/test/integration/Service.spec.ts +++ b/test/integration/Service.spec.ts @@ -111,7 +111,7 @@ describe('API Service', () => { }); it('should remove an order', () => { - const tp = xud['orderBook'].tradingPairs.get('LTC/BTC')!; + const tp = xud['orderBook']!.tradingPairs.get('LTC/BTC')!; expect(tp.ownOrders.buyMap.has(orderId!)).to.be.true; const args = { orderId: '1', diff --git a/test/p2p/networks.spec.ts b/test/p2p/networks.spec.ts index 81f5dacbe..7e67ec7c2 100644 --- a/test/p2p/networks.spec.ts +++ b/test/p2p/networks.spec.ts @@ -18,8 +18,8 @@ describe('P2P Networks Tests', () => { await Promise.all([srcNode.start(srcNodeConfig), destNode.start(destNodeConfig)]); const host = 'localhost'; - const port = destNode['pool']['listenPort']!; - const nodePubKey = destNode['pool'].nodePubKey; + const port = destNode['pool']!['listenPort']!; + const nodePubKey = destNode['pool']!.nodePubKey; const nodeTwoUri = toUri({ host, port, nodePubKey }); const rejectionMsg = `Peer ${nodePubKey}@${host}:${port} closed due to WireProtocolErr framer: incompatible msg origin network (expected: ${srcNodeNetwork}, found: ${destNodeNetwork})`; @@ -39,11 +39,11 @@ describe('P2P Networks Tests', () => { const srcNode = new Xud(); const destNode = new Xud(); await Promise.all([srcNode.start(srcNodeConfig), destNode.start(destNodeConfig)]); - const srcNodePubKey = srcNode['pool'].nodePubKey; - const destNodePubKey = destNode['pool'].nodePubKey; + const srcNodePubKey = srcNode['pool']!.nodePubKey; + const destNodePubKey = destNode['pool']!.nodePubKey; const host = 'localhost'; - const port = destNode['pool']['listenPort']!; + const port = destNode['pool']!['listenPort']!; const nodeTwoUri = toUri({ host, port, nodePubKey: destNodePubKey }); await expect(srcNode.service.connect({ nodeUri: nodeTwoUri, retryConnecting: false })).to.be.fulfilled; diff --git a/test/p2p/sanity.spec.ts b/test/p2p/sanity.spec.ts index 161181ec7..19dac5df0 100644 --- a/test/p2p/sanity.spec.ts +++ b/test/p2p/sanity.spec.ts @@ -61,11 +61,11 @@ describe('P2P Sanity Tests', () => { await Promise.all([nodeOne.start(nodeOneConfig), nodeTwo.start(nodeTwoConfig)]); - nodeOnePubKey = nodeOne['pool'].nodePubKey; - nodeTwoPubKey = nodeTwo['pool'].nodePubKey; + nodeOnePubKey = nodeOne['pool']!.nodePubKey; + nodeTwoPubKey = nodeTwo['pool']!.nodePubKey; - nodeTwoPort = nodeTwo['pool']['listenPort']!; - nodeOneUri = toUri({ nodePubKey: nodeOnePubKey, host: 'localhost', port: nodeOne['pool']['listenPort']! }); + nodeTwoPort = nodeTwo['pool']!['listenPort']!; + nodeOneUri = toUri({ nodePubKey: nodeOnePubKey, host: 'localhost', port: nodeOne['pool']!['listenPort']! }); nodeTwoUri = toUri({ nodePubKey: nodeTwoPubKey, host: 'localhost', port: nodeTwoPort }); unusedPort = await getUnusedPort(); @@ -82,13 +82,13 @@ describe('P2P Sanity Tests', () => { it('should update the node state', (done) => { const btcPubKey = '0395033b252c6f40e3756984162d68174e2bd8060a129c0d3462a9370471c6d28f'; - const nodeTwoPeer = nodeOne['pool'].getPeer(nodeTwoPubKey); + const nodeTwoPeer = nodeOne['pool']!.getPeer(nodeTwoPubKey); nodeTwoPeer.on('nodeStateUpdate', () => { expect(nodeTwoPeer['nodeState']!.lndPubKeys['BTC']).to.equal(btcPubKey); done(); }); - nodeTwo['pool'].updateLndState({ + nodeTwo['pool']!.updateLndState({ currency: 'BTC', pubKey: btcPubKey, }); @@ -100,7 +100,7 @@ describe('P2P Sanity Tests', () => { }); it('should disconnect successfully', async () => { - await nodeOne['pool']['closePeer'](nodeTwoPubKey, DisconnectionReason.NotAcceptingConnections); + await nodeOne['pool']!['closePeer'](nodeTwoPubKey, DisconnectionReason.NotAcceptingConnections); const listPeersResult = nodeOne.service.listPeers(); expect(listPeersResult).to.be.empty;