diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index 6be8b1674..19758c0ed 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -15,9 +15,9 @@ "INDEXING_BLOCKS_AMOUNT": 11000, "PRICE_FEED_UPDATE_INTERVAL": 600, "USE_SECOND_NODE_FOR_MINFEE": false, - "EXTERNAL_ASSETS": [ - "https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json" - ], + "EXTERNAL_ASSETS": [], + "EXTERNAL_MAX_RETRY": 10, + "EXTERNAL_RETRY_INTERVAL": 60, "STDOUT_LOG_MIN_PRIORITY": "debug" }, "CORE_RPC": { @@ -66,6 +66,7 @@ }, "SOCKS5PROXY": { "ENABLED": false, + "USE_ONION": true, "HOST": "127.0.0.1", "PORT": 9050, "USERNAME": "", @@ -73,6 +74,14 @@ }, "PRICE_DATA_SERVER": { "TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices", - "CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices" + "CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices", + "BISQ_URL": "https://bisq.markets/api", + "BISQ_ONION": "http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api" + }, + "EXTERNAL_DATA_SERVER": { + "MEMPOOL_API": "https://mempool.space/api/v1", + "MEMPOOL_ONION": "http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/api/v1", + "LIQUID_API": "https://liquid.network/api/v1", + "LIQUID_ONION": "http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1" } } diff --git a/backend/src/api/bisq/bisq.ts b/backend/src/api/bisq/bisq.ts index f16356d1c..332dd5e81 100644 --- a/backend/src/api/bisq/bisq.ts +++ b/backend/src/api/bisq/bisq.ts @@ -1,6 +1,9 @@ import config from '../../config'; import * as fs from 'fs'; -import axios from 'axios'; +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'; @@ -143,12 +146,47 @@ class Bisq { }, 2000); }); } + private async updatePrice() { + type axiosOptions = { + 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.PRICE_DATA_SERVER.BISQ_ONION : config.PRICE_DATA_SERVER.BISQ_URL; + const isHTTP = (new URL(BISQ_URL).protocol.split(':')[0] === 'http') ? true : false; + const axiosOptions: axiosOptions = {}; + let retry = 0; - private updatePrice() { - axios.get('https://bisq.markets/api/trades/?market=bsq_btc', { timeout: 10000 }) - .then((response) => { + 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; + } + + // Handle proxy agent for onion addresses + if (isHTTP) { + axiosOptions.httpAgent = new SocksProxyAgent(socksOptions); + } else { + axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions); + } + } + + while(retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) { + try { + 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[] = []; - response.data.forEach((trade) => { + data.data.forEach((trade) => { prices.push(parseFloat(trade.price) * 100000000); }); prices.sort((a, b) => a - b); @@ -156,9 +194,14 @@ class Bisq { if (this.priceUpdateCallbackFunction) { this.priceUpdateCallbackFunction(this.price); } - }).catch((err) => { - logger.err('Error updating Bisq market price: ' + err); - }); + 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++; + } + } } private async loadBisqDumpFile(): Promise { diff --git a/backend/src/api/fiat-conversion.ts b/backend/src/api/fiat-conversion.ts index 3562760f1..5bc664099 100644 --- a/backend/src/api/fiat-conversion.ts +++ b/backend/src/api/fiat-conversion.ts @@ -1,4 +1,6 @@ import logger from '../logger'; +import * as http from 'http'; +import * as https from 'https'; import axios, { AxiosResponse } from 'axios'; import { IConversionRates } from '../mempool.interfaces'; import config from '../config'; @@ -40,49 +42,76 @@ class FiatConversion { } private async updateCurrency(): Promise { - const headers = { 'User-Agent': `mempool/v${backendInfo.getBackendInfo().version}` }; - let fiatConversionUrl: string; - let response: AxiosResponse; + 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 fiatConversionUrl = (config.SOCKS5PROXY.ENABLED === true) && (config.SOCKS5PROXY.USE_ONION === true) ? config.PRICE_DATA_SERVER.TOR_URL : config.PRICE_DATA_SERVER.CLEARNET_URL; + const isHTTP = (new URL(fiatConversionUrl).protocol.split(':')[0] === 'http') ? true : false; + const axiosOptions: axiosOptions = { + headers: { + 'User-Agent': `mempool/v${backendInfo.getBackendInfo().version}` + }, + timeout: config.SOCKS5PROXY.ENABLED ? 30000 : 10000 + }; - try { - if (config.SOCKS5PROXY.ENABLED) { - let socksOptions: any = { - agentOptions: { - keepAlive: true, - }, - hostname: config.SOCKS5PROXY.HOST, - port: config.SOCKS5PROXY.PORT - }; + let retry = 0; - if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) { - socksOptions.username = config.SOCKS5PROXY.USERNAME; - socksOptions.password = config.SOCKS5PROXY.PASSWORD; - } + if (config.SOCKS5PROXY.ENABLED) { + let socksOptions: any = { + agentOptions: { + keepAlive: true, + }, + hostname: config.SOCKS5PROXY.HOST, + port: config.SOCKS5PROXY.PORT + }; - const agent = new SocksProxyAgent(socksOptions); - fiatConversionUrl = config.PRICE_DATA_SERVER.TOR_URL; - logger.debug('Querying currency rates service...'); - response = await axios.get(fiatConversionUrl, { httpAgent: agent, headers: headers, timeout: 30000 }); + if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) { + socksOptions.username = config.SOCKS5PROXY.USERNAME; + socksOptions.password = config.SOCKS5PROXY.PASSWORD; + } + + // Handle proxy agent for onion addresses + if (isHTTP) { + axiosOptions.httpAgent = new SocksProxyAgent(socksOptions); } else { - fiatConversionUrl = config.PRICE_DATA_SERVER.CLEARNET_URL; + axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions); + } + } + + while(retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) { + try { logger.debug('Querying currency rates service...'); - response = await axios.get(fiatConversionUrl, { headers: headers, timeout: 10000 }); - } - for (const rate of response.data.data) { - if (this.debasingFiatCurrencies.includes(rate.currencyCode) && rate.provider === 'Bisq-Aggregate') { - this.conversionRates[rate.currencyCode] = Math.round(100 * rate.price) / 100; + const response: AxiosResponse = await axios.get(`${fiatConversionUrl}`, axiosOptions); + + if (response.statusText === 'error' || !response.data) { + throw new Error(`Could not fetch data from ${fiatConversionUrl}, Error: ${response.status}`); } - } - this.ratesInitialized = true; - logger.debug(`USD Conversion Rate: ${this.conversionRates.USD}`); + for (const rate of response.data.data) { + if (this.debasingFiatCurrencies.includes(rate.currencyCode) && rate.provider === 'Bisq-Aggregate') { + this.conversionRates[rate.currencyCode] = Math.round(100 * rate.price) / 100; + } + } - if (this.ratesChangedCallback) { - this.ratesChangedCallback(this.conversionRates); + this.ratesInitialized = true; + logger.debug(`USD Conversion Rate: ${this.conversionRates.USD}`); + + if (this.ratesChangedCallback) { + this.ratesChangedCallback(this.conversionRates); + } + break; + } catch (e) { + logger.err('Error updating fiat conversion rates: ' + (e instanceof Error ? e.message : e)); + await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL); + retry++; } - } catch (e) { - logger.err('Error updating fiat conversion rates: ' + (e instanceof Error ? e.message : e)); } } } diff --git a/backend/src/config.ts b/backend/src/config.ts index 1aa37b8be..8295faeaa 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -18,6 +18,8 @@ interface IConfig { PRICE_FEED_UPDATE_INTERVAL: number; USE_SECOND_NODE_FOR_MINFEE: boolean; EXTERNAL_ASSETS: string[]; + EXTERNAL_MAX_RETRY: number; + EXTERNAL_RETRY_INTERVAL: number; STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug'; }; ESPLORA: { @@ -66,6 +68,7 @@ interface IConfig { }; SOCKS5PROXY: { ENABLED: boolean; + USE_ONION: boolean; HOST: string; PORT: number; USERNAME: string; @@ -74,6 +77,14 @@ interface IConfig { PRICE_DATA_SERVER: { TOR_URL: string; CLEARNET_URL: string; + BISQ_URL: string; + BISQ_ONION: string; + }; + EXTERNAL_DATA_SERVER: { + MEMPOOL_API: string; + MEMPOOL_ONION: string; + LIQUID_API: string; + LIQUID_ONION: string; }; } @@ -94,9 +105,9 @@ const defaults: IConfig = { 'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks 'PRICE_FEED_UPDATE_INTERVAL': 600, 'USE_SECOND_NODE_FOR_MINFEE': false, - 'EXTERNAL_ASSETS': [ - 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json' - ], + 'EXTERNAL_ASSETS': [], + 'EXTERNAL_MAX_RETRY': 10, + 'EXTERNAL_RETRY_INTERVAL': 60, 'STDOUT_LOG_MIN_PRIORITY': 'debug', }, 'ESPLORA': { @@ -145,6 +156,7 @@ const defaults: IConfig = { }, 'SOCKS5PROXY': { 'ENABLED': false, + 'USE_ONION': true, 'HOST': '127.0.0.1', 'PORT': 9050, 'USERNAME': '', @@ -152,7 +164,15 @@ const defaults: IConfig = { }, "PRICE_DATA_SERVER": { 'TOR_URL': 'http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices', - 'CLEARNET_URL': 'https://price.bisq.wiz.biz/getAllMarketPrices' + 'CLEARNET_URL': 'https://price.bisq.wiz.biz/getAllMarketPrices', + 'BISQ_URL': 'https://bisq.markets/api', + 'BISQ_ONION': 'http://bisqmktse2cabavbr2xjq7xw3h6g5ottemo5rolfcwt6aly6tp5fdryd.onion/api' + }, + "EXTERNAL_DATA_SERVER": { + 'MEMPOOL_API': 'https://mempool.space/api/v1', + 'MEMPOOL_ONION': 'http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/api/v1', + 'LIQUID_API': 'https://liquid.network/api/v1', + 'LIQUID_ONION': 'http://liquidmom47f6s3m53ebfxn47p76a6tlnxib3wp6deux7wuzotdr6cyd.onion/api/v1' } }; @@ -168,6 +188,7 @@ class Config implements IConfig { BISQ: IConfig['BISQ']; SOCKS5PROXY: IConfig['SOCKS5PROXY']; PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER']; + EXTERNAL_DATA_SERVER: IConfig['EXTERNAL_DATA_SERVER']; constructor() { const configs = this.merge(configFile, defaults); @@ -182,6 +203,7 @@ class Config implements IConfig { this.BISQ = configs.BISQ; this.SOCKS5PROXY = configs.SOCKS5PROXY; this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER; + this.EXTERNAL_DATA_SERVER = configs.EXTERNAL_DATA_SERVER; } merge = (...objects: object[]): IConfig => { diff --git a/backend/src/index.ts b/backend/src/index.ts index 42cfe9aea..d421a6fba 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -205,7 +205,7 @@ class Server { .post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', routes.$postTransactionForm) .get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => { try { - const response = await axios.get('https://mempool.space/api/v1/donations', { responseType: 'stream', timeout: 10000 }); + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); } catch (e) { res.status(500).end(); @@ -213,7 +213,7 @@ class Server { }) .get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => { try { - const response = await axios.get('https://mempool.space/api/v1/donations/images/' + req.params.id, { + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/donations/images/${req.params.id}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -223,7 +223,7 @@ class Server { }) .get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => { try { - const response = await axios.get('https://mempool.space/api/v1/contributors', { responseType: 'stream', timeout: 10000 }); + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); } catch (e) { res.status(500).end(); @@ -231,7 +231,7 @@ class Server { }) .get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => { try { - const response = await axios.get('https://mempool.space/api/v1/contributors/images/' + req.params.id, { + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/contributors/images/${req.params.id}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); @@ -241,7 +241,7 @@ class Server { }) .get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => { try { - const response = await axios.get('https://mempool.space/api/v1/translators', { responseType: 'stream', timeout: 10000 }); + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); } catch (e) { res.status(500).end(); @@ -249,7 +249,7 @@ class Server { }) .get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => { try { - const response = await axios.get('https://mempool.space/api/v1/translators/images/' + req.params.id, { + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.MEMPOOL_API}/translators/images/${req.params.id}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); diff --git a/backend/src/routes.ts b/backend/src/routes.ts index 91c41faa6..12368b1ee 100644 --- a/backend/src/routes.ts +++ b/backend/src/routes.ts @@ -990,7 +990,7 @@ class Routes { public async $getAllFeaturedLiquidAssets(req: Request, res: Response) { try { - const response = await axios.get('https://liquid.network/api/v1/assets/featured', { responseType: 'stream', timeout: 10000 }); + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.LIQUID_API}/assets/featured`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); } catch (e) { res.status(500).end(); @@ -999,7 +999,7 @@ class Routes { public async $getAssetGroup(req: Request, res: Response) { try { - const response = await axios.get('https://liquid.network/api/v1/assets/group/' + parseInt(req.params.id, 10), + const response = await axios.get(`${config.EXTERNAL_DATA_SERVER.LIQUID_API}/assets/group/${parseInt(req.params.id, 10)}`, { responseType: 'stream', timeout: 10000 }); response.data.pipe(res); } catch (e) { diff --git a/backend/src/tasks/pools-updater.ts b/backend/src/tasks/pools-updater.ts index 05a1da5dc..6ff56548d 100644 --- a/backend/src/tasks/pools-updater.ts +++ b/backend/src/tasks/pools-updater.ts @@ -1,4 +1,4 @@ -import axios from 'axios'; +import axios, { AxiosResponse } from 'axios'; import poolsParser from '../api/pools-parser'; import config from '../config'; import DB from '../database'; @@ -136,10 +136,10 @@ class PoolsUpdater { axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions); } - while(retry < 5) { + while(retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) { try { - const data = await axios.get(path, axiosOptions); - if (data.statusText !== 'OK' || !data.data) { + const data: AxiosResponse = await axios.get(path, axiosOptions); + if (data.statusText === 'error' || !data.data) { throw new Error(`Could not fetch data from Github, Error: ${data.status}`); } return data.data; @@ -147,7 +147,7 @@ class PoolsUpdater { logger.err('Could not connect to Github. Reason: ' + (e instanceof Error ? e.message : e)); retry++; } - await setDelay(); + await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL); } return undefined; }