diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 5cbff22a3a..c4b8c7ef0a 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -96,7 +96,8 @@ }, "BISQ": { "ENABLED": true, - "DATA_PATH": "__BISQ_DATA_PATH__" + "HOST": "127.0.0.1", + "PORT": 8081 }, "SOCKS5PROXY": { "ENABLED": true, diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index e261e2adc4..a0a6011dcc 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -108,7 +108,7 @@ describe('Mempool Backend Config', () => { expect(config.STATISTICS).toStrictEqual({ ENABLED: true, TX_PER_SECOND_SAMPLE_PERIOD: 150 }); - expect(config.BISQ).toStrictEqual({ ENABLED: false, DATA_PATH: '/bisq/statsnode-data/btc_mainnet/db' }); + expect(config.BISQ).toStrictEqual({ ENABLED: false, 'HOST': '127.0.0.1', 'PORT': 8081 }); expect(config.SOCKS5PROXY).toStrictEqual({ ENABLED: false, diff --git a/backend/src/api/bisq/bisq.routes.ts b/backend/src/api/bisq/bisq.routes.ts index 8f002836f7..137013663b 100644 --- a/backend/src/api/bisq/bisq.routes.ts +++ b/backend/src/api/bisq/bisq.routes.ts @@ -39,8 +39,8 @@ class BisqRoutes { res.send(result.toString()); } - private getBisqTransaction(req: Request, res: Response) { - const result = bisq.getTransaction(req.params.txId); + private async getBisqTransaction(req: Request, res: Response) { + const result = await bisq.$getTransaction(req.params.txId); if (result) { res.json(result); } else { @@ -48,7 +48,7 @@ class BisqRoutes { } } - private getBisqTransactions(req: Request, res: Response) { + private async getBisqTransactions(req: Request, res: Response) { const types: string[] = []; req.query.types = req.query.types || []; if (!Array.isArray(req.query.types)) { @@ -64,13 +64,13 @@ class BisqRoutes { const index = parseInt(req.params.index, 10) || 0; const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25; - const [transactions, count] = bisq.getTransactions(index, length, types); + const [transactions, count] = await bisq.$getTransactions(index, length, types); res.header('X-Total-Count', count.toString()); res.json(transactions); } - private getBisqBlock(req: Request, res: Response) { - const result = bisq.getBlock(req.params.hash); + private async getBisqBlock(req: Request, res: Response) { + const result = await bisq.$getBlock(req.params.hash); if (result) { res.json(result); } else { @@ -78,16 +78,16 @@ class BisqRoutes { } } - private getBisqBlocks(req: Request, res: Response) { + private async getBisqBlocks(req: Request, res: Response) { const index = parseInt(req.params.index, 10) || 0; const length = parseInt(req.params.length, 10) > 100 ? 100 : parseInt(req.params.length, 10) || 25; - const [transactions, count] = bisq.getBlocks(index, length); + const [transactions, count] = await bisq.$getBlocks(index, length); res.header('X-Total-Count', count.toString()); res.json(transactions); } - private getBisqAddress(req: Request, res: Response) { - const result = bisq.getAddress(req.params.address.substr(1)); + private async getBisqAddress(req: Request, res: Response) { + const result = await bisq.$getAddress(req.params.address.substr(1)); if (result) { res.json(result); } else { diff --git a/backend/src/api/bisq/bisq.ts b/backend/src/api/bisq/bisq.ts index 4171284bb8..ae2df247b5 100644 --- a/backend/src/api/bisq/bisq.ts +++ b/backend/src/api/bisq/bisq.ts @@ -1,358 +1,345 @@ import config from '../../config'; -import * as fs from 'fs'; -import axios, { AxiosResponse } from 'axios'; import * as http from 'http'; import * as https from 'https'; -import { SocksProxyAgent } from 'socks-proxy-agent'; import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces'; -import { Common } from '../common'; -import { BlockExtended } from '../../mempool.interfaces'; +import { Currency, OffersData, TradesData } from './interfaces'; +import bisqMarket from './markets-api'; +import pricesUpdater from '../../tasks/price-updater'; import backendInfo from '../backend-info'; import logger from '../../logger'; class Bisq { - private static BLOCKS_JSON_FILE_PATH = config.BISQ.DATA_PATH + '/json/all/blocks.json'; - private latestBlockHeight = 0; - private blocks: BisqBlock[] = []; - private allBlocks: BisqBlock[] = []; - private transactions: BisqTransaction[] = []; - private transactionIndex: { [txId: string]: BisqTransaction } = {}; - private blockIndex: { [hash: string]: BisqBlock } = {}; - private addressIndex: { [address: string]: BisqTransaction[] } = {}; - private stats: BisqStats = { + private stats: BisqStats = ({ minted: 0, burnt: 0, addresses: 0, unspent_txos: 0, spent_txos: 0, - }; - private price: number = 0; - private priceUpdateCallbackFunction: ((price: number) => void) | undefined; - private topDirectoryWatcher: fs.FSWatcher | undefined; - private subdirectoryWatcher: fs.FSWatcher | undefined; + height: 0, + genesisHeight: 0, + _bsqPrice: 0, + _usdPrice: 0, + _marketCap: 0 + }); + private blocks: BisqBlock[] = []; + private allBlocks: BisqBlock[] = []; + private blockIndex: { [hash: string]: BisqBlock } = {}; + private lastPollTimestamp: number = 0; + private pendingQueries: Promise[] = []; constructor() {} - startBisqService(): void { - try { - this.checkForBisqDataFolder(); - } catch (e) { - logger.info('Retrying to start bisq service in 3 minutes'); - setTimeout(this.startBisqService.bind(this), 180000); - return; - } - this.loadBisqDumpFile(); - setInterval(this.updatePrice.bind(this), 1000 * 60 * 60); - this.updatePrice(); - this.startTopDirectoryWatcher(); - this.startSubDirectoryWatcher(); + setPriceCallbackFunction(fn: (price: number) => void) { + bisqMarket.setPriceCallbackFunction(fn); } - handleNewBitcoinBlock(block: BlockExtended): void { - if (block.height - 10 > this.latestBlockHeight && this.latestBlockHeight !== 0) { - logger.warn(`Bitcoin block height (#${block.height}) has diverged from the latest Bisq block height (#${this.latestBlockHeight}). Restarting watchers...`); - this.startTopDirectoryWatcher(); - this.startSubDirectoryWatcher(); - } + public startBisqService(): void { + logger.info('start bisq service (start)'); + this.$pollForNewData(); // obtains the current block height + logger.info('start bisq service (end)'); } - getTransaction(txId: string): BisqTransaction | undefined { - return this.transactionIndex[txId]; + public async $getTransaction(txId: string): Promise { + logger.info("getTransaction called from frontend"); + if (!this.isBisqConnected()) return undefined; + var queriedTx = await this.$lookupBsqTx(txId); + if (queriedTx !== undefined) { + this.$fillMissingBlocksFromCache(queriedTx.blockHeight, 1); + } + return queriedTx; } - getTransactions(start: number, length: number, types: string[]): [BisqTransaction[], number] { - let transactions = this.transactions; - if (types.length) { - transactions = transactions.filter((tx) => types.indexOf(tx.txType) > -1); - } - return [transactions.slice(start, length + start), transactions.length]; + public async $getTransactions(start: number, length: number, types: string[]): Promise<[BisqTransaction[], number]> { + logger.info("getTransactions called from frontend"); + if (!this.isBisqConnected()) return [[], 0]; + var types2 = types.join("~") + if (types2.length === 0) { types2 = "~"; } + var queriedTx = await this.$lookupBsqTx2(start, length, types2); + return [queriedTx, this.stats.unspent_txos+this.stats.spent_txos]; } - getBlock(hash: string): BisqBlock | undefined { - return this.blockIndex[hash]; + public async $getBlock(hash: string): Promise { + logger.info(`getBlock called from frontend ${hash}`); + var cached = this.blockIndex[hash]; + if (cached) { + return cached; + } + if (!this.isBisqConnected()) return undefined; + var queried = await this.$lookupBsqBlockByHash(hash); + return queried; } - getAddress(hash: string): BisqTransaction[] { - return this.addressIndex[hash]; + public async $getAddress(hash: string): Promise { + logger.info(`getAddress called from frontend ${hash}`); + if (!this.isBisqConnected()) return []; + var queriedTx: BisqTransaction[] = await this.$lookupBsqTxForAddr(hash); + return queriedTx; } - getBlocks(start: number, length: number): [BisqBlock[], number] { - return [this.blocks.slice(start, length + start), this.blocks.length]; + public getLatestBlockHeight(): number { + logger.info(`getLatestBlockHeight called from frontend`); + return this.stats.height; } - getStats(): BisqStats { + public getStats(): BisqStats { + logger.info("getStats called from frontend"); return this.stats; } - setPriceCallbackFunction(fn: (price: number) => void) { - this.priceUpdateCallbackFunction = fn; - } + public async $getBlocks(fromHeight: number, limit: number): Promise<[BisqBlock[], number]> { + logger.info(`getBlocks called from frontend ${fromHeight} ${limit}`); + let currentHeight = this.getLatestBlockHeight()-fromHeight; + if (currentHeight > this.getLatestBlockHeight()) { + limit -= currentHeight - this.getLatestBlockHeight(); + currentHeight = this.getLatestBlockHeight(); + } + var returnBlocks: BisqBlock[] = []; + if (currentHeight < 0) { + return [returnBlocks, this.blocks.length]; + } + returnBlocks = this.getRequiredBlocksFromCache(fromHeight, currentHeight, limit); + if (returnBlocks.length === limit) { + return [returnBlocks, this.stats.height - this.stats.genesisHeight]; + } - getLatestBlockHeight(): number { - return this.latestBlockHeight; + await this.$fillMissingBlocksFromCache(currentHeight, limit); + // now the cache should contain all the results needed + returnBlocks = this.getRequiredBlocksFromCache(fromHeight, currentHeight, limit); + return [returnBlocks, this.stats.height - this.stats.genesisHeight]; } - private checkForBisqDataFolder() { - if (!fs.existsSync(Bisq.BLOCKS_JSON_FILE_PATH)) { - logger.warn(Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`); - throw new Error(`Cannot load BISQ ${Bisq.BLOCKS_JSON_FILE_PATH} file`); + private async $pollForNewData() { + this.lookupStats(); // obtains the current block height + + if (this.isBisqConnected() && new Date().getTime() - this.lastPollTimestamp > 60000) { // 1 minute + this.lastPollTimestamp = new Date().getTime(); + this.pendingQueries.push(this.getCurrencies()); + this.pendingQueries.push(this.getOffers()); + this.pendingQueries.push(this.getTrades()); + Promise.allSettled(this.pendingQueries).then(results => { + this.pendingQueries.length = 0; + bisqMarket.updateCache(); + }); } - } - private startTopDirectoryWatcher() { - if (this.topDirectoryWatcher) { - this.topDirectoryWatcher.close(); - } - let fsWait: NodeJS.Timeout | null = null; - this.topDirectoryWatcher = fs.watch(config.BISQ.DATA_PATH + '/json', () => { - if (fsWait) { - clearTimeout(fsWait); - } - if (this.subdirectoryWatcher) { - this.subdirectoryWatcher.close(); - } - fsWait = setTimeout(() => { - logger.debug(`Bisq restart detected. Resetting both watchers in 3 minutes.`); - setTimeout(() => { - this.startTopDirectoryWatcher(); - this.startSubDirectoryWatcher(); - this.loadBisqDumpFile(); - }, 180000); - }, 15000); - }); + setTimeout(() => this.$pollForNewData(), 20000); } - private startSubDirectoryWatcher() { - if (this.subdirectoryWatcher) { - this.subdirectoryWatcher.close(); - } - if (!fs.existsSync(Bisq.BLOCKS_JSON_FILE_PATH)) { - logger.warn(Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`); - setTimeout(() => this.startSubDirectoryWatcher(), 180000); - return; - } - let fsWait: NodeJS.Timeout | null = null; - this.subdirectoryWatcher = fs.watch(config.BISQ.DATA_PATH + '/json/all', () => { - if (fsWait) { - clearTimeout(fsWait); - } - fsWait = setTimeout(() => { - logger.debug(`Change detected in the Bisq data folder.`); - this.loadBisqDumpFile(); - }, 2000); - }); + private isBisqConnected() : boolean { + if (this.stats.height > 0) + return true; + logger.warn("bisq not connected!"); + return false; } - private async updatePrice() { - type axiosOptions = { - headers: { - 'User-Agent': string - }; - timeout: number; - httpAgent?: http.Agent; - httpsAgent?: https.Agent; - } - const setDelay = (secs: number = 1): Promise => new Promise(resolve => setTimeout(() => resolve(), secs * 1000)); - const BISQ_URL = (config.SOCKS5PROXY.ENABLED === true) && (config.SOCKS5PROXY.USE_ONION === true) ? config.EXTERNAL_DATA_SERVER.BISQ_ONION : config.EXTERNAL_DATA_SERVER.BISQ_URL; - const isHTTP = (new URL(BISQ_URL).protocol.split(':')[0] === 'http') ? true : false; - const axiosOptions: axiosOptions = { - headers: { - 'User-Agent': (config.MEMPOOL.USER_AGENT === 'mempool') ? `mempool/v${backendInfo.getBackendInfo().version}` : `${config.MEMPOOL.USER_AGENT}` - }, - timeout: config.SOCKS5PROXY.ENABLED ? 30000 : 10000 - }; - let retry = 0; - while(retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) { - try { - if (config.SOCKS5PROXY.ENABLED) { - const socksOptions: any = { - agentOptions: { - keepAlive: true, - }, - hostname: config.SOCKS5PROXY.HOST, - port: config.SOCKS5PROXY.PORT - }; - - if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) { - socksOptions.username = config.SOCKS5PROXY.USERNAME; - socksOptions.password = config.SOCKS5PROXY.PASSWORD; - } else { - // Retry with different tor circuits https://stackoverflow.com/a/64960234 - socksOptions.username = `circuit${retry}`; - } - - // Handle proxy agent for onion addresses - if (isHTTP) { - axiosOptions.httpAgent = new SocksProxyAgent(socksOptions); - } else { - axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions); - } - } - - const data: AxiosResponse = await axios.get(`${BISQ_URL}/trades/?market=bsq_btc`, axiosOptions); - if (data.statusText === 'error' || !data.data) { - throw new Error(`Could not fetch data from Bisq market, Error: ${data.status}`); - } - const prices: number[] = []; - data.data.forEach((trade) => { - prices.push(parseFloat(trade.price) * 100000000); + private async $fillMissingBlocksFromCache(currentHeight: number, limit: number) { + // now we must fill the missing cache elements + for (let i = 0; i < limit && currentHeight >= 0; i++) { + logger.info(`blocks in cache:${this.blocks.length}, looking for one with height=${currentHeight}`); + let block = this.blocks.find((b) => b.height === currentHeight); + if (!block) { + // find by height, index on the fly, save in database + logger.info(`not found in cache, calling lookupBsqBlockByHeight ${currentHeight} ${i}`); + const block = await this.$lookupBsqBlockByHeight(currentHeight); + this.allBlocks.push(block); + this.allBlocks = this.allBlocks.sort((a,b) => { + return b['height'] >= a['height'] ? 1 : -1; }); - prices.sort((a, b) => a - b); - this.price = Common.median(prices); - if (this.priceUpdateCallbackFunction) { - this.priceUpdateCallbackFunction(this.price); - } - logger.debug('Successfully updated Bisq market price'); - break; - } catch (e) { - logger.err('Error updating Bisq market price: ' + (e instanceof Error ? e.message : e)); - await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL); - retry++; + this.blocks = this.allBlocks; } + currentHeight--; } + this.buildIndex(); } - private async loadBisqDumpFile(): Promise { - this.allBlocks = []; - try { - await this.loadData(); - this.buildIndex(); - this.calculateStats(); - } catch (e) { - logger.info('Cannot load bisq dump file because: ' + (e instanceof Error ? e.message : e)); + private getRequiredBlocksFromCache(index: number, blockHeight: number, count: number) { + const returnBlocks: BisqBlock[] = []; + logger.info(`cache size:${this.blocks.length}, looking for starting height:${blockHeight} and count:${count}`); + while (count > 0) { + let block = this.blocks.find((b) => b.height === blockHeight); + if (block === undefined || block.height !== blockHeight) { + logger.debug(`returning incomplete results from cache lookup: ${returnBlocks.length} / ${count} remaining`); + return returnBlocks; // cache miss, force caller to index + } else { + returnBlocks.push(block); + ++index; + --blockHeight; + --count; + } } + logger.info(`found all ${returnBlocks.length} blocks in cache.`); + return returnBlocks; } private buildIndex() { - const start = new Date().getTime(); - this.transactions = []; - this.transactionIndex = {}; - this.addressIndex = {}; - + logger.info("buildIndex"); this.allBlocks.forEach((block) => { - /* Build block index */ if (!this.blockIndex[block.hash]) { this.blockIndex[block.hash] = block; + logger.info(`set block index for ${block.hash}`); } - - /* Build transactions index */ - block.txs.forEach((tx) => { - this.transactions.push(tx); - this.transactionIndex[tx.id] = tx; - }); }); - - /* Build address index */ - this.transactions.forEach((tx) => { - tx.inputs.forEach((input) => { - if (!this.addressIndex[input.address]) { - this.addressIndex[input.address] = []; - } - if (this.addressIndex[input.address].indexOf(tx) === -1) { - this.addressIndex[input.address].push(tx); - } - }); - tx.outputs.forEach((output) => { - if (!this.addressIndex[output.address]) { - this.addressIndex[output.address] = []; - } - if (this.addressIndex[output.address].indexOf(tx) === -1) { - this.addressIndex[output.address].push(tx); - } - }); - }); - - const time = new Date().getTime() - start; - logger.debug('Bisq data index rebuilt in ' + time + ' ms'); + logger.info(`blocks:${this.blocks.length} blockIndex:${Object.keys(this.blockIndex).length}`); } - private calculateStats() { - let minted = 0; - let burned = 0; - let unspent = 0; - let spent = 0; - - this.transactions.forEach((tx) => { - tx.outputs.forEach((output) => { - if (output.opReturn) { - return; - } - if (output.txOutputType === 'GENESIS_OUTPUT' || output.txOutputType === 'ISSUANCE_CANDIDATE_OUTPUT' && output.isVerified) { - minted += output.bsqAmount; - } - if (output.isUnspent) { - unspent++; - } else { - spent++; + private lookupStats() { + const customPromise = this.makeApiCall('dao/get-bsq-stats'); + customPromise.then((buffer) => { + try { + const stats: BisqStats = JSON.parse(buffer) + stats.minted /= 100.0; + stats.burnt /= 100.0; + stats._bsqPrice = bisqMarket.bsqPrice; + stats._usdPrice = bisqMarket.bsqPrice * pricesUpdater.getLatestPrices()['USD']; + stats._marketCap = stats._usdPrice * (stats.minted - stats.burnt); + this.stats = stats; + logger.debug(`stats: BSQ/BTC=${Bisq.FORMAT_BITCOIN(stats._bsqPrice)} BSQ/USD=${Bisq.FORMAT_USD(stats._usdPrice)} MktCap=${Bisq.FORMAT_USD(stats._marketCap)} height=${stats.height}`); + if (this.stats !== undefined && this.blocks.length < 30) { + // startup, pre-cache first page or so of blocks + this.$fillMissingBlocksFromCache(this.stats.height, this.blocks.length + 10); + } else if (this.stats !== undefined && this.blocks[0].height !== this.stats.height) { + // cache a newly issued block + this.$fillMissingBlocksFromCache(this.stats.height, 1); } - }); - burned += tx['burntFee']; - }); - - this.stats = { - addresses: Object.keys(this.addressIndex).length, - minted: minted / 100, - burnt: burned / 100, - spent_txos: spent, - unspent_txos: unspent, - }; + } catch (e) { Bisq.LOG_RESTAPI_DATA_ERR(e); } + }) + .catch(err => { Bisq.LOG_RESTAPI_ERR(err) }); } - private async loadData(): Promise { - if (!fs.existsSync(Bisq.BLOCKS_JSON_FILE_PATH)) { - throw new Error(Bisq.BLOCKS_JSON_FILE_PATH + ` doesn't exist`); - } + private async $lookupBsqTx(txId: string) : Promise { + const customPromise = this.makeApiCall('transactions/get-bsq-tx', [txId]); + var buffer = await customPromise; + try { + const tx: BisqTransaction = JSON.parse(buffer) + return tx; + } catch (e) { Bisq.LOG_RESTAPI_DATA_ERR(e); } + return undefined; + } - const readline = require('readline'); - const events = require('events'); + private async $lookupBsqTx2(start: number, limit: number, types: string) : Promise { + const customPromise = this.makeApiCall('transactions/query-txs-paginated', [String(start), String(limit), types]); + var buffer = await customPromise; + const txs: BisqTransaction[] = JSON.parse(buffer) + return txs; + } - const rl = readline.createInterface({ - input: fs.createReadStream(Bisq.BLOCKS_JSON_FILE_PATH), - crlfDelay: Infinity - }); + private async $lookupBsqTxForAddr(addr: string) : Promise { + const customPromise = this.makeApiCall('transactions/get-bsq-tx-for-addr', [addr]); + var buffer = await customPromise; + const txs: BisqTransaction[] = JSON.parse(buffer) + return txs; + } - let blockBuffer = ''; - let readingBlock = false; - let lineCount = 1; - const start = new Date().getTime(); + private async $lookupBsqBlockByHeight(height: number) : Promise { + const customPromise = this.makeApiCall('blocks/get-bsq-block-by-height', [String(height)]); + var buffer = await customPromise; + const block: BisqBlock = JSON.parse(buffer) + return block; + } - logger.debug('Processing Bisq data dump...'); + private async $lookupBsqBlockByHash(hash: string) : Promise { + const customPromise = this.makeApiCall('blocks/get-bsq-block-by-hash', [hash]); + try { + var buffer = await customPromise; + const block: BisqBlock = JSON.parse(buffer) + this.allBlocks.push(block); + this.allBlocks = this.allBlocks.sort((a,b) => { + return b['height'] >= a['height'] ? 1 : -1; + }); + this.blocks = this.allBlocks; + this.lookupStats(); + this.buildIndex(); + logger.debug(`blocks size is now ${this.blocks.length}`); + return block; + } catch (e) { Bisq.LOG_RESTAPI_DATA_ERR(e); } + } - rl.on('line', (line) => { - if (lineCount === 2) { - line = line.replace(' "chainHeight": ', ''); - this.latestBlockHeight = parseInt(line, 10); - } + private getCurrencies() { + const customPromise = this.makeApiCall('markets/get-currencies'); + customPromise.then((buffer) => { + try { + bisqMarket.setCurrencyData(JSON.parse(buffer)); + } catch (e) { Bisq.LOG_RESTAPI_DATA_ERR(e); } + }) + .catch(err => { Bisq.LOG_RESTAPI_ERR(err) }); + return customPromise; + } - if (line === ' {') { - readingBlock = true; - } else if (line === ' },') { - blockBuffer += '}'; - try { - const block: BisqBlock = JSON.parse(blockBuffer); - this.allBlocks.push(block); - readingBlock = false; - blockBuffer = ''; - } catch (e) { - logger.debug(blockBuffer); - throw Error(`Unable to parse Bisq data dump at line ${lineCount}` + (e instanceof Error ? e.message : e)); - } - } + private getOffers() { + const customPromise = this.makeApiCall('markets/get-offers'); + customPromise.then((buffer) => { + try { + bisqMarket.setOffersData(JSON.parse(buffer)); + } catch (e) { Bisq.LOG_RESTAPI_DATA_ERR(e); } + }) + .catch(err => { Bisq.LOG_RESTAPI_ERR(err) }); + return customPromise; + } - if (readingBlock === true) { - blockBuffer += line; - } + private getTrades() { + const customPromise = this.makeApiCall('markets/get-trades', [String(bisqMarket.getNewestTradeDate()), String(bisqMarket.getOldestTradeDate()-1)]); + customPromise.then((buffer) => { + try { + bisqMarket.setTradesData(JSON.parse(buffer)); + } catch (e) { Bisq.LOG_RESTAPI_DATA_ERR(e); } + }) + .catch(err => { Bisq.LOG_RESTAPI_ERR(err) }); + return customPromise; + } - ++lineCount; + // requesting information from Bisq REST API process + private makeApiCall(api_method: string, params?: string[]) { + var pathStr = '/api/v1/explorer/' + api_method + '/'; + if (params !== undefined) { + pathStr = pathStr + params.join("/"); + } + logger.debug(`${pathStr}`); + var requestOptions = { + host: config.BISQ.HOST, + port: config.BISQ.PORT, + method: 'GET', + path: pathStr, + headers: { + 'Host': config.BISQ.HOST, + 'Content-Length': 0 //optPostRequest.length + }, + agent: false, + rejectUnauthorized: false + } + var request = http.request(requestOptions); + //request.write(optPostRequest); + request.end(); + var customPromise = new Promise((resolve, reject) => { + request.on('error', function(e) { + reject(new Error(`unable to make http request. ${JSON.stringify(requestOptions)}`)); + }); + request.on('response', (response) => { + var buffer = '' + response.on('data', function (chunk) { + buffer = buffer + chunk + }) + response.on('end', () => { + resolve(buffer); + }); + }); }); + return customPromise; + } - await events.once(rl, 'close'); + private static LOG_RESTAPI_ERR(err) { + logger.err(`it appears the Bisq daemon is not responding:\n${err}`); + } - this.allBlocks.reverse(); - this.blocks = this.allBlocks.filter((block) => block.txs.length > 0); + private static LOG_RESTAPI_DATA_ERR(err) { + logger.err(`{err}`); + } + + private static FORMAT_BITCOIN(nbr) : string { + return nbr.toLocaleString('en-us', {maximumFractionDigits:8}); + } - const time = new Date().getTime() - start; - logger.debug('Bisq dump processed in ' + time + ' ms'); + private static FORMAT_USD(nbr) : string { + return nbr.toLocaleString('en-us', {maximumFractionDigits:2}); } } diff --git a/backend/src/api/bisq/interfaces.ts b/backend/src/api/bisq/interfaces.ts index eb10d2fa77..c9ace92907 100644 --- a/backend/src/api/bisq/interfaces.ts +++ b/backend/src/api/bisq/interfaces.ts @@ -33,6 +33,11 @@ export interface BisqStats { addresses: number; unspent_txos: number; spent_txos: number; + height: number; + genesisHeight: number; + _bsqPrice: number; + _usdPrice: number; + _marketCap: number; } interface BisqInput { diff --git a/backend/src/api/bisq/markets-api.ts b/backend/src/api/bisq/markets-api.ts index 1b5b930592..559e95425f 100644 --- a/backend/src/api/bisq/markets-api.ts +++ b/backend/src/api/bisq/markets-api.ts @@ -1,5 +1,7 @@ import { Currencies, OffersData, TradesData, Depth, Currency, Interval, HighLowOpenClose, Markets, Offers, Offer, BisqTrade, MarketVolume, Tickers, Ticker, SummarizedIntervals, SummarizedInterval } from './interfaces'; +import { Common } from '../common'; +import logger from '../../logger'; const strtotime = require('./strtotime'); @@ -14,35 +16,52 @@ class BisqMarketsApi { private allCurrenciesIndexed: { [code: string]: Currency } = {}; private tradeDataByMarket: { [market: string]: TradesData[] } = {}; private tickersCache: Ticker | Tickers | null = null; + private priceUpdateCallbackFunction: ((price: number) => void) | undefined; + public bsqPrice: number = 0; constructor() { } + setPriceCallbackFunction(fn: (price: number) => void) { + this.priceUpdateCallbackFunction = fn; + } + setOffersData(offers: OffersData[]) { + logger.debug(`setOffersData: Updating Bisq Market Offers Data with ${offers.length} records.`); this.offersData = offers; } setTradesData(trades: TradesData[]) { - this.tradesData = trades; - this.tradeDataByMarket = {}; - - this.tradesData.forEach((trade) => { - trade._market = trade.currencyPair.toLowerCase().replace('/', '_'); - if (!this.tradeDataByMarket[trade._market]) { - this.tradeDataByMarket[trade._market] = []; + var tradesPre = this.tradesData.length; + trades.forEach((trade) => { + // ignore trade stats older than two years (performance reasons) + var oneYear = 1000*60*60*24*365*2; + if (trade.tradeDate > new Date().getTime() - oneYear) { + trade._market = trade.currencyPair.toLowerCase().replace('/', '_'); + if (!this.tradeDataByMarket[trade._market]) { + this.tradeDataByMarket[trade._market] = []; + } + this.tradeDataByMarket[trade._market].push(trade); + this.tradesData.push(trade); } - this.tradeDataByMarket[trade._market].push(trade); }); + this.tradesData = this.tradesData.sort(function(b, a) { + return (a.tradeDate < b.tradeDate) ? -1 : (a.tradeDate > b.tradeDate) ? 1 : 0; + }); + logger.info(`Updated Bisq Market Trades Data, #${tradesPre} -> ${this.tradesData.length} records. Newest: ${this.getNewestTradeDate()}`); + this.updateBsqPrice(); // whenever trades change, recalc BSQ price average } - setCurrencyData(cryptoCurrency: Currency[], fiatCurrency: Currency[], activeCryptoCurrency: Currency[], activeFiatCurrency: Currency[]) { - this.cryptoCurrencyData = cryptoCurrency, - this.fiatCurrencyData = fiatCurrency, - this.activeCryptoCurrencyData = activeCryptoCurrency, - this.activeFiatCurrencyData = activeFiatCurrency; - + setCurrencyData(currencies: Currency[]) { + currencies.push( {'code': "BTC",'name': "Bitcoin", 'precision': 8, '_type': "crypto"} ); + this.cryptoCurrencyData = currencies.filter( (x) => x._type === "crypto" ); + this.fiatCurrencyData = currencies.filter( (x) => x._type === "fiat" ); + this.activeCryptoCurrencyData = currencies.filter( (x) => x._type === "crypto" ); + this.activeFiatCurrencyData = currencies.filter( (x) => x._type === "fiat" ); this.fiatCurrenciesIndexed = {}; this.allCurrenciesIndexed = {}; + logger.debug(`setCurrencyData: Updating Bisq Market Currency Data with ${this.fiatCurrencyData.length} fiat and ${this.cryptoCurrencyData.length} cryptos.`); + this.fiatCurrencyData.forEach((currency) => { currency._type = 'fiat'; this.fiatCurrenciesIndexed[currency.code] = true; @@ -55,13 +74,26 @@ class BisqMarketsApi { } updateCache() { + logger.debug("BisqMarketsApi updateCache"); this.tickersCache = null; this.tickersCache = this.getTicker(); } - getCurrencies( - type: 'crypto' | 'fiat' | 'active' | 'all' = 'all', - ): Currencies { + private updateBsqPrice() { + var trades: BisqTrade[] = this.getTrades("bsq_btc"); + const prices: number[] = []; + trades.forEach((trade) => { + prices.push(parseFloat(trade.price)); + }); + prices.sort((a, b) => a - b); + this.bsqPrice = Common.median(prices); + if (this.priceUpdateCallbackFunction) { + this.priceUpdateCallbackFunction(this.bsqPrice * 100000000); // for websockethandler storage + } + logger.debug(`Updated Bisq market price: ${this.bsqPrice}, ${prices.length} reference prices used.`); + } + + getCurrencies(type: 'crypto' | 'fiat' | 'active' | 'all' = 'all',): Currencies { let currencies: Currency[]; switch (type) { @@ -115,7 +147,7 @@ class BisqMarketsApi { direction?: 'buy' | 'sell', ): Offers { const currencyPair = market.replace('_', '/').toUpperCase(); - + logger.warn(`getOffers: ${currencyPair}`); let buys: Offer[] | null = null; let sells: Offer[] | null = null; @@ -142,7 +174,9 @@ class BisqMarketsApi { } getMarkets(): Markets { + var counter = 0; const allCurrencies = this.getCurrencies(); + const activeCurrencies = this.getCurrencies('active'); const markets = {}; @@ -150,7 +184,6 @@ class BisqMarketsApi { if (allCurrencies[currency].code === 'BTC') { continue; } - const isFiat = allCurrencies[currency]._type === 'fiat'; const pmarketname = allCurrencies['BTC']['name']; @@ -176,8 +209,9 @@ class BisqMarketsApi { 'rtype': rtype, 'name': lname + '/' + rname, }; + counter++; } - + logger.debug(`getMarkets returning ${Object.keys(markets).length} items`); return markets; } @@ -191,40 +225,40 @@ class BisqMarketsApi { limit: number = 100, sort: 'asc' | 'desc' = 'desc', ): BisqTrade[] { - limit = Math.min(limit, 2000); - const _market = market === 'all' ? undefined : market; + limit = Math.min(limit, 2000); + const _market = market === 'all' ? undefined : market; - if (!timestamp_from) { - timestamp_from = new Date('2016-01-01').getTime() / 1000; - } - if (!timestamp_to) { - timestamp_to = new Date().getTime() / 1000; - } + if (!timestamp_from) { + timestamp_from = new Date('2016-01-01').getTime() / 1000; + } + if (!timestamp_to) { + timestamp_to = new Date().getTime() / 1000; + } - const matches = this.getTradesByCriteria(_market, timestamp_to, timestamp_from, - trade_id_to, trade_id_from, direction, sort, limit, false); + const matches = this.getTradesByCriteria(_market, timestamp_to, timestamp_from, + trade_id_to, trade_id_from, direction, sort, limit, false); - if (sort === 'asc') { - matches.sort((a, b) => a.tradeDate - b.tradeDate); - } else { - matches.sort((a, b) => b.tradeDate - a.tradeDate); - } + if (sort === 'asc') { + matches.sort((a, b) => a.tradeDate - b.tradeDate); + } else { + matches.sort((a, b) => b.tradeDate - a.tradeDate); + } - return matches.map((trade) => { - const bsqTrade: BisqTrade = { - direction: trade.primaryMarketDirection, - price: trade._tradePriceStr, - amount: trade._tradeAmountStr, - volume: trade._tradeVolumeStr, - payment_method: trade.paymentMethod, - trade_id: trade.offerId, - trade_date: trade.tradeDate, - }; - if (market === 'all') { - bsqTrade.market = trade._market; - } - return bsqTrade; - }); + return matches.map((trade) => { + const bsqTrade: BisqTrade = { + direction: trade.primaryMarketDirection, + price: trade._tradePriceStr, + amount: trade._tradeAmountStr, + volume: trade._tradeVolumeStr, + payment_method: trade.paymentMethod, + trade_id: trade.offerId, + trade_date: trade.tradeDate, + }; + if (market === 'all') { + bsqTrade.market = trade._market; + } + return bsqTrade; + }); } getVolumes( @@ -235,6 +269,7 @@ class BisqMarketsApi { milliseconds?: boolean, timestamp: 'no' | 'yes' = 'yes', ): MarketVolume[] { + if (milliseconds) { timestamp_from = timestamp_from ? timestamp_from / 1000 : timestamp_from; timestamp_to = timestamp_to ? timestamp_to / 1000 : timestamp_to; @@ -285,17 +320,20 @@ class BisqMarketsApi { } } + logger.debug(`getVolumes returning ${marketVolumes.length} items.`); return marketVolumes; } getTicker( market?: string, ): Tickers | Ticker | null { + if (market) { return this.getTickerFromMarket(market); } if (this.tickersCache) { + logger.debug(`returning ${Object.keys(this.tickersCache).length} tickers from cache`); return this.tickersCache; } @@ -306,7 +344,7 @@ class BisqMarketsApi { tickers[allMarkets[m].pair] = this.getTickerFromMarket(allMarkets[m].pair); } } - + logger.debug(`returning ${Object.keys(tickers).length} tickers`); return tickers; } @@ -314,17 +352,19 @@ class BisqMarketsApi { let ticker: Ticker; const timestamp_from = strtotime('-24 hour'); const timestamp_to = new Date().getTime() / 1000; - const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from, - undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER); - - const periods: SummarizedInterval[] = Object.values(this.getTradesSummarized(trades, timestamp_from)); const allCurrencies = this.getCurrencies(); const currencyRight = allCurrencies[market.split('_')[1].toUpperCase()]; + const trades = this.getTradesByCriteria(market, timestamp_to, timestamp_from, + undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER); + const periods: SummarizedInterval[] = Object.values(this.getTradesSummarized(trades, timestamp_from)); + + const currencyLeft = allCurrencies[market.split('_')[0].toUpperCase()]; + const livePrice = NaN; //bisqPriceService.getPrice(currencyRight.code === 'BTC' ? currencyLeft.code : currencyRight.code); if (periods[0]) { ticker = { - 'last': this.intToBtc(periods[0].close), + 'last': (isNaN(livePrice) ? this.intToBtc(periods[0].close) : '' + livePrice), 'high': this.intToBtc(periods[0].high), 'low': this.intToBtc(periods[0].low), 'volume_left': this.intToBtc(periods[0].volume_left), @@ -333,15 +373,14 @@ class BisqMarketsApi { 'sell': null, }; } else { + var lastTradePrice = this.intToBtc(0); const lastTrade = this.tradeDataByMarket[market]; - if (!lastTrade) { - return null; + if (lastTrade) { + lastTradePrice = this.intToBtc( + lastTrade[0].primaryMarketTradePrice * Math.pow(10, 8 - currencyRight.precision)); } - const tradePrice = lastTrade[0].primaryMarketTradePrice * Math.pow(10, 8 - currencyRight.precision); - - const lastTradePrice = this.intToBtc(tradePrice); ticker = { - 'last': lastTradePrice, + 'last': (isNaN(livePrice) ? lastTradePrice : '' + livePrice), 'high': lastTradePrice, 'low': lastTradePrice, 'volume_left': '0', @@ -464,21 +503,22 @@ class BisqMarketsApi { const trades = this.getTradesByCriteria(undefined, timestamp_to, timestamp_from, undefined, undefined, undefined, 'asc', Number.MAX_SAFE_INTEGER); - const markets: any = {}; + const volumes: any = {}; for (const trade of trades) { - if (!markets[trade._market]) { - markets[trade._market] = { + if (!volumes[trade._market]) { + volumes[trade._market] = { 'volume': 0, 'num_trades': 0, }; } - markets[trade._market]['volume'] += this.fiatCurrenciesIndexed[trade.currency] ? trade._tradeAmount : trade._tradeVolume; - markets[trade._market]['num_trades']++; + volumes[trade._market]['volume'] += this.fiatCurrenciesIndexed[trade.currency] ? trade._tradeAmount : trade._tradeVolume; + volumes[trade._market]['num_trades']++; } - return markets; + logger.debug(`VolumesByTime returning ${volumes.length} items.`); + return volumes; } private getTradesSummarized(trades: TradesData[], timestamp_from: number, interval?: string): SummarizedIntervals { @@ -490,14 +530,14 @@ class BisqMarketsApi { const interval_start = !interval ? timestamp_from : this.intervalStart(traded_at, interval); if (!intervals[interval_start]) { - intervals[interval_start] = { - 'open': 0, - 'close': 0, - 'high': 0, - 'low': 0, - 'avg': 0, - 'volume_right': 0, - 'volume_left': 0, + intervals[interval_start] = { + 'open': 0, + 'close': 0, + 'high': 0, + 'low': 0, + 'avg': 0, + 'volume_right': 0, + 'volume_left': 0, }; intervals_prices[interval_start] = []; } @@ -515,21 +555,39 @@ class BisqMarketsApi { intervals_prices[interval_start]['rightvol'].push(trade._tradeVolume); if (price) { - const plow = period['low']; - period['period_start'] = interval_start; - period['open'] = period['open'] || price; - period['close'] = price; - period['high'] = price > period['high'] ? price : period['high']; - period['low'] = (plow && price > plow) ? period['low'] : price; - period['avg'] = intervals_prices[interval_start]['rightvol'].reduce((p: number, c: number) => c + p, 0) - / intervals_prices[interval_start]['leftvol'].reduce((c: number, p: number) => c + p, 0) * 100000000; - period['volume_left'] += trade._tradeAmount; - period['volume_right'] += trade._tradeVolume; + const plow = period['low']; + period['period_start'] = interval_start; + period['open'] = period['open'] || price; + period['close'] = price; + period['high'] = price > period['high'] ? price : period['high']; + period['low'] = (plow && price > plow) ? period['low'] : price; + period['avg'] = intervals_prices[interval_start]['rightvol'].reduce((p: number, c: number) => c + p, 0) + / intervals_prices[interval_start]['leftvol'].reduce((c: number, p: number) => c + p, 0) * 100000000; + period['volume_left'] += trade._tradeAmount; + period['volume_right'] += trade._tradeVolume; } } return intervals; } + public getOldestTradeDate(): number { + const tradesDataSorted = this.tradesData.slice(); + let ts = tradesDataSorted.at(-1); + if (ts) { + return ts.tradeDate; + } + return 0; + } + + public getNewestTradeDate(): number { + const tradesDataSorted = this.tradesData.slice(); + let ts = tradesDataSorted.at(0); + if (ts) { + return ts.tradeDate; + } + return 0; + } + private getTradesByCriteria( market: string | undefined, timestamp_to: number, @@ -627,28 +685,28 @@ class BisqMarketsApi { private intervalStart(ts: number, interval: string): number { switch (interval) { - case 'minute': - return (ts - (ts % 60)); - case '10_minute': - return (ts - (ts % 600)); - case 'half_hour': - return (ts - (ts % 1800)); - case 'hour': - return (ts - (ts % 3600)); - case 'half_day': - return (ts - (ts % (3600 * 12))); - case 'day': - return strtotime('midnight today', ts); - case 'week': - return strtotime('midnight sunday last week', ts); - case 'month': - return strtotime('midnight first day of this month', ts); - case 'year': - return strtotime('midnight first day of january', ts); - default: - throw new Error('Unsupported interval'); + case 'minute': + return (ts - (ts % 60)); + case '10_minute': + return (ts - (ts % 600)); + case 'half_hour': + return (ts - (ts % 1800)); + case 'hour': + return (ts - (ts % 3600)); + case 'half_day': + return (ts - (ts % (3600 * 12))); + case 'day': + return strtotime('midnight today', ts); + case 'week': + return strtotime('midnight sunday last week', ts); + case 'month': + return strtotime('midnight first day of this month', ts); + case 'year': + return strtotime('midnight first day of january', ts); + default: + throw new Error('Unsupported interval'); } -} + } private offerDataToOffer(offer: OffersData, market: string): Offer { const currencyPairs = market.split('_'); diff --git a/backend/src/api/bisq/markets.ts b/backend/src/api/bisq/markets.ts deleted file mode 100644 index 08f40d7724..0000000000 --- a/backend/src/api/bisq/markets.ts +++ /dev/null @@ -1,137 +0,0 @@ -import config from '../../config'; -import * as fs from 'fs'; -import { OffersData as OffersData, TradesData, Currency } from './interfaces'; -import bisqMarket from './markets-api'; -import logger from '../../logger'; - -class Bisq { - private static FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE = 4000; - private static MARKET_JSON_PATH = config.BISQ.DATA_PATH; - private static MARKET_JSON_FILE_PATHS = { - activeCryptoCurrency: '/active_crypto_currency_list.json', - activeFiatCurrency: '/active_fiat_currency_list.json', - cryptoCurrency: '/crypto_currency_list.json', - fiatCurrency: '/fiat_currency_list.json', - offers: '/offers_statistics.json', - trades: '/trade_statistics.json', - }; - - private cryptoCurrencyLastMtime = new Date('2016-01-01'); - private fiatCurrencyLastMtime = new Date('2016-01-01'); - private offersLastMtime = new Date('2016-01-01'); - private tradesLastMtime = new Date('2016-01-01'); - - private subdirectoryWatcher: fs.FSWatcher | undefined; - - constructor() {} - - startBisqService(): void { - try { - this.checkForBisqDataFolder(); - } catch (e) { - logger.info('Retrying to start bisq service (markets) in 3 minutes'); - setTimeout(this.startBisqService.bind(this), 180000); - return; - } - this.loadBisqDumpFile(); - this.startBisqDirectoryWatcher(); - } - - private checkForBisqDataFolder() { - if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) { - logger.err(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Make sure Bisq is running and the config is correct before starting the server.`); - throw new Error(`Cannot load BISQ ${Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency} file`); - } - } - - private startBisqDirectoryWatcher() { - if (this.subdirectoryWatcher) { - this.subdirectoryWatcher.close(); - } - if (!fs.existsSync(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency)) { - logger.warn(Bisq.MARKET_JSON_PATH + Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency + ` doesn't exist. Trying to restart sub directory watcher again in 3 minutes.`); - setTimeout(() => this.startBisqDirectoryWatcher(), 180000); - return; - } - let fsWait: NodeJS.Timeout | null = null; - this.subdirectoryWatcher = fs.watch(Bisq.MARKET_JSON_PATH, () => { - if (fsWait) { - clearTimeout(fsWait); - } - fsWait = setTimeout(() => { - logger.debug(`Change detected in the Bisq market data folder.`); - this.loadBisqDumpFile(); - }, Bisq.FOLDER_WATCH_CHANGE_DETECTION_DEBOUNCE); - }); - } - - private async loadBisqDumpFile(): Promise { - const start = new Date().getTime(); - try { - let marketsDataUpdated = false; - const cryptoMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency); - const fiatMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.fiatCurrency); - if (cryptoMtime > this.cryptoCurrencyLastMtime || fiatMtime > this.fiatCurrencyLastMtime) { - const cryptoCurrencyData = await this.loadData(Bisq.MARKET_JSON_FILE_PATHS.cryptoCurrency); - const fiatCurrencyData = await this.loadData(Bisq.MARKET_JSON_FILE_PATHS.fiatCurrency); - const activeCryptoCurrencyData = await this.loadData(Bisq.MARKET_JSON_FILE_PATHS.activeCryptoCurrency); - const activeFiatCurrencyData = await this.loadData(Bisq.MARKET_JSON_FILE_PATHS.activeFiatCurrency); - logger.debug('Updating Bisq Market Currency Data'); - bisqMarket.setCurrencyData(cryptoCurrencyData, fiatCurrencyData, activeCryptoCurrencyData, activeFiatCurrencyData); - if (cryptoMtime > this.cryptoCurrencyLastMtime) { - this.cryptoCurrencyLastMtime = cryptoMtime; - } - if (fiatMtime > this.fiatCurrencyLastMtime) { - this.fiatCurrencyLastMtime = fiatMtime; - } - marketsDataUpdated = true; - } - const offersMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.offers); - if (offersMtime > this.offersLastMtime) { - const offersData = await this.loadData(Bisq.MARKET_JSON_FILE_PATHS.offers); - logger.debug('Updating Bisq Market Offers Data'); - bisqMarket.setOffersData(offersData); - this.offersLastMtime = offersMtime; - marketsDataUpdated = true; - } - const tradesMtime = this.getFileMtime(Bisq.MARKET_JSON_FILE_PATHS.trades); - if (tradesMtime > this.tradesLastMtime) { - const tradesData = await this.loadData(Bisq.MARKET_JSON_FILE_PATHS.trades); - logger.debug('Updating Bisq Market Trades Data'); - bisqMarket.setTradesData(tradesData); - this.tradesLastMtime = tradesMtime; - marketsDataUpdated = true; - } - if (marketsDataUpdated) { - bisqMarket.updateCache(); - const time = new Date().getTime() - start; - logger.debug('Bisq market data updated in ' + time + ' ms'); - } - } catch (e) { - logger.err('loadBisqMarketDataDumpFile() error.' + (e instanceof Error ? e.message : e)); - } - } - - private getFileMtime(path: string): Date { - const stats = fs.statSync(Bisq.MARKET_JSON_PATH + path); - return stats.mtime; - } - - private loadData(path: string): Promise { - return new Promise((resolve, reject) => { - fs.readFile(Bisq.MARKET_JSON_PATH + path, 'utf8', (err, data) => { - if (err) { - reject(err); - } - try { - const parsedData = JSON.parse(data); - resolve(parsedData); - } catch (e) { - reject('JSON parse error (' + path + ')'); - } - }); - }); - } -} - -export default new Bisq(); diff --git a/backend/src/config.ts b/backend/src/config.ts index ee3645e581..36c6be8796 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -119,7 +119,8 @@ interface IConfig { }; BISQ: { ENABLED: boolean; - DATA_PATH: string; + HOST: string; + PORT: number; }; SOCKS5PROXY: { ENABLED: boolean; @@ -262,7 +263,8 @@ const defaults: IConfig = { }, 'BISQ': { 'ENABLED': false, - 'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db' + 'HOST': '127.0.0.1', + 'PORT': 8081 }, 'LIGHTNING': { 'ENABLED': false, diff --git a/backend/src/index.ts b/backend/src/index.ts index 1988c7c563..8bd106bbae 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -12,7 +12,6 @@ import diskCache from './api/disk-cache'; import statistics from './api/statistics/statistics'; import websocketHandler from './api/websocket-handler'; import bisq from './api/bisq/bisq'; -import bisqMarkets from './api/bisq/markets'; import logger from './logger'; import backendInfo from './api/backend-info'; import loadingIndicators from './api/loading-indicators'; @@ -184,8 +183,6 @@ class Server { if (config.BISQ.ENABLED) { bisq.startBisqService(); bisq.setPriceCallbackFunction((price) => websocketHandler.setExtraInitData('bsq-price', price)); - blocks.setNewBlockCallback(bisq.handleNewBitcoinBlock.bind(bisq)); - bisqMarkets.startBisqService(); } if (config.LIGHTNING.ENABLED) { diff --git a/docker/README.md b/docker/README.md index 444324af84..4c140ecd28 100644 --- a/docker/README.md +++ b/docker/README.md @@ -333,7 +333,8 @@ Corresponding `docker-compose.yml` overrides: ```json "BISQ": { "ENABLED": false, - "DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db" + "HOST": "127.0.0.1", + "PORT": 8081 } ``` @@ -342,7 +343,8 @@ Corresponding `docker-compose.yml` overrides: api: environment: BISQ_ENABLED: "" - BISQ_DATA_PATH: "" + HOST: "" + PORT: "" ... ``` diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index f8935706c5..b1d248ba2e 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -96,7 +96,8 @@ }, "BISQ": { "ENABLED": __BISQ_ENABLED__, - "DATA_PATH": "__BISQ_DATA_PATH__" + "HOST": "__BISQ_HOST__", + "PORT": __BISQ_PORT__ }, "LIGHTNING": { "ENABLED": __LIGHTNING_ENABLED__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 50ecc17ab1..99d5ad8c1e 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -97,7 +97,8 @@ __STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__=${STATISTICS_TX_PER_SECOND_SAMPLE_PER # BISQ __BISQ_ENABLED__=${BISQ_ENABLED:=false} -__BISQ_DATA_PATH__=${BISQ_DATA_PATH:=/bisq/statsnode-data/btc_mainnet/db} +__BISQ_HOST__=${BISQ_HOST:=127.0.0.1} +__BISQ_PORT__=${BISQ_PORT:=8081} # SOCKS5PROXY __SOCKS5PROXY_ENABLED__=${SOCKS5PROXY_ENABLED:=false} @@ -249,7 +250,8 @@ sed -i "s!__STATISTICS_ENABLED__!${__STATISTICS_ENABLED__}!g" mempool-config.jso sed -i "s!__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__!${__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__}!g" mempool-config.json sed -i "s!__BISQ_ENABLED__!${__BISQ_ENABLED__}!g" mempool-config.json -sed -i "s!__BISQ_DATA_PATH__!${__BISQ_DATA_PATH__}!g" mempool-config.json +sed -i "s!__BISQ_HOST__!${__BISQ_HOST__}!g" mempool-config.json +sed -i "s!__BISQ_PORT__!${__BISQ_PORT__}!g" mempool-config.json sed -i "s!__SOCKS5PROXY_ENABLED__!${__SOCKS5PROXY_ENABLED__}!g" mempool-config.json sed -i "s!__SOCKS5PROXY_USE_ONION__!${__SOCKS5PROXY_USE_ONION__}!g" mempool-config.json