Handle Error with basic retry while syncing external assets ( Price Data )

+ Removed unused External Assets value

+ Make static URL dynamic

+ Added config options for syncing pool data

+ Added retry interval & max retry
This commit is contained in:
Ayanami 2022-05-20 12:55:28 +09:00
parent e99a684354
commit 9d5bbf1f44
No known key found for this signature in database
GPG Key ID: 0CABDF03077D92E4
7 changed files with 165 additions and 62 deletions

View File

@ -15,9 +15,9 @@
"INDEXING_BLOCKS_AMOUNT": 11000, "INDEXING_BLOCKS_AMOUNT": 11000,
"PRICE_FEED_UPDATE_INTERVAL": 600, "PRICE_FEED_UPDATE_INTERVAL": 600,
"USE_SECOND_NODE_FOR_MINFEE": false, "USE_SECOND_NODE_FOR_MINFEE": false,
"EXTERNAL_ASSETS": [ "EXTERNAL_ASSETS": [],
"https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json" "EXTERNAL_MAX_RETRY": 10,
], "EXTERNAL_RETRY_INTERVAL": 60,
"STDOUT_LOG_MIN_PRIORITY": "debug" "STDOUT_LOG_MIN_PRIORITY": "debug"
}, },
"CORE_RPC": { "CORE_RPC": {
@ -66,6 +66,7 @@
}, },
"SOCKS5PROXY": { "SOCKS5PROXY": {
"ENABLED": false, "ENABLED": false,
"USE_ONION": true,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 9050, "PORT": 9050,
"USERNAME": "", "USERNAME": "",
@ -73,6 +74,14 @@
}, },
"PRICE_DATA_SERVER": { "PRICE_DATA_SERVER": {
"TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices", "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"
} }
} }

View File

@ -1,6 +1,9 @@
import config from '../../config'; import config from '../../config';
import * as fs from 'fs'; 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 { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces';
import { Common } from '../common'; import { Common } from '../common';
import { BlockExtended } from '../../mempool.interfaces'; import { BlockExtended } from '../../mempool.interfaces';
@ -143,12 +146,47 @@ class Bisq {
}, 2000); }, 2000);
}); });
} }
private async updatePrice() {
type axiosOptions = {
httpAgent?: http.Agent;
httpsAgent?: https.Agent;
}
const setDelay = (secs: number = 1): Promise<void> => 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() { if (config.SOCKS5PROXY.ENABLED) {
axios.get<BisqTrade[]>('https://bisq.markets/api/trades/?market=bsq_btc', { timeout: 10000 }) const socksOptions: any = {
.then((response) => { 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[] = []; const prices: number[] = [];
response.data.forEach((trade) => { data.data.forEach((trade) => {
prices.push(parseFloat(trade.price) * 100000000); prices.push(parseFloat(trade.price) * 100000000);
}); });
prices.sort((a, b) => a - b); prices.sort((a, b) => a - b);
@ -156,9 +194,14 @@ class Bisq {
if (this.priceUpdateCallbackFunction) { if (this.priceUpdateCallbackFunction) {
this.priceUpdateCallbackFunction(this.price); this.priceUpdateCallbackFunction(this.price);
} }
}).catch((err) => { logger.debug('Successfully updated Bisq market price');
logger.err('Error updating Bisq market price: ' + err); 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<void> { private async loadBisqDumpFile(): Promise<void> {

View File

@ -1,4 +1,6 @@
import logger from '../logger'; import logger from '../logger';
import * as http from 'http';
import * as https from 'https';
import axios, { AxiosResponse } from 'axios'; import axios, { AxiosResponse } from 'axios';
import { IConversionRates } from '../mempool.interfaces'; import { IConversionRates } from '../mempool.interfaces';
import config from '../config'; import config from '../config';
@ -40,49 +42,76 @@ class FiatConversion {
} }
private async updateCurrency(): Promise<void> { private async updateCurrency(): Promise<void> {
const headers = { 'User-Agent': `mempool/v${backendInfo.getBackendInfo().version}` }; type axiosOptions = {
let fiatConversionUrl: string; headers: {
let response: AxiosResponse; 'User-Agent': string
};
timeout: number;
httpAgent?: http.Agent;
httpsAgent?: https.Agent;
}
const setDelay = (secs: number = 1): Promise<void> => 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 { let retry = 0;
if (config.SOCKS5PROXY.ENABLED) {
let socksOptions: any = {
agentOptions: {
keepAlive: true,
},
hostname: config.SOCKS5PROXY.HOST,
port: config.SOCKS5PROXY.PORT
};
if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) { if (config.SOCKS5PROXY.ENABLED) {
socksOptions.username = config.SOCKS5PROXY.USERNAME; let socksOptions: any = {
socksOptions.password = config.SOCKS5PROXY.PASSWORD; agentOptions: {
} keepAlive: true,
},
hostname: config.SOCKS5PROXY.HOST,
port: config.SOCKS5PROXY.PORT
};
const agent = new SocksProxyAgent(socksOptions); if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) {
fiatConversionUrl = config.PRICE_DATA_SERVER.TOR_URL; socksOptions.username = config.SOCKS5PROXY.USERNAME;
logger.debug('Querying currency rates service...'); socksOptions.password = config.SOCKS5PROXY.PASSWORD;
response = await axios.get(fiatConversionUrl, { httpAgent: agent, headers: headers, timeout: 30000 }); }
// Handle proxy agent for onion addresses
if (isHTTP) {
axiosOptions.httpAgent = new SocksProxyAgent(socksOptions);
} else { } 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...'); logger.debug('Querying currency rates service...');
response = await axios.get(fiatConversionUrl, { headers: headers, timeout: 10000 });
}
for (const rate of response.data.data) { const response: AxiosResponse = await axios.get(`${fiatConversionUrl}`, axiosOptions);
if (this.debasingFiatCurrencies.includes(rate.currencyCode) && rate.provider === 'Bisq-Aggregate') {
this.conversionRates[rate.currencyCode] = Math.round(100 * rate.price) / 100; if (response.statusText === 'error' || !response.data) {
throw new Error(`Could not fetch data from ${fiatConversionUrl}, Error: ${response.status}`);
} }
}
this.ratesInitialized = true; for (const rate of response.data.data) {
logger.debug(`USD Conversion Rate: ${this.conversionRates.USD}`); if (this.debasingFiatCurrencies.includes(rate.currencyCode) && rate.provider === 'Bisq-Aggregate') {
this.conversionRates[rate.currencyCode] = Math.round(100 * rate.price) / 100;
}
}
if (this.ratesChangedCallback) { this.ratesInitialized = true;
this.ratesChangedCallback(this.conversionRates); 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));
} }
} }
} }

View File

@ -18,6 +18,8 @@ interface IConfig {
PRICE_FEED_UPDATE_INTERVAL: number; PRICE_FEED_UPDATE_INTERVAL: number;
USE_SECOND_NODE_FOR_MINFEE: boolean; USE_SECOND_NODE_FOR_MINFEE: boolean;
EXTERNAL_ASSETS: string[]; EXTERNAL_ASSETS: string[];
EXTERNAL_MAX_RETRY: number;
EXTERNAL_RETRY_INTERVAL: number;
STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug'; STDOUT_LOG_MIN_PRIORITY: 'emerg' | 'alert' | 'crit' | 'err' | 'warn' | 'notice' | 'info' | 'debug';
}; };
ESPLORA: { ESPLORA: {
@ -66,6 +68,7 @@ interface IConfig {
}; };
SOCKS5PROXY: { SOCKS5PROXY: {
ENABLED: boolean; ENABLED: boolean;
USE_ONION: boolean;
HOST: string; HOST: string;
PORT: number; PORT: number;
USERNAME: string; USERNAME: string;
@ -74,6 +77,14 @@ interface IConfig {
PRICE_DATA_SERVER: { PRICE_DATA_SERVER: {
TOR_URL: string; TOR_URL: string;
CLEARNET_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 'INDEXING_BLOCKS_AMOUNT': 11000, // 0 = disable indexing, -1 = index all blocks
'PRICE_FEED_UPDATE_INTERVAL': 600, 'PRICE_FEED_UPDATE_INTERVAL': 600,
'USE_SECOND_NODE_FOR_MINFEE': false, 'USE_SECOND_NODE_FOR_MINFEE': false,
'EXTERNAL_ASSETS': [ 'EXTERNAL_ASSETS': [],
'https://raw.githubusercontent.com/mempool/mining-pools/master/pools.json' 'EXTERNAL_MAX_RETRY': 10,
], 'EXTERNAL_RETRY_INTERVAL': 60,
'STDOUT_LOG_MIN_PRIORITY': 'debug', 'STDOUT_LOG_MIN_PRIORITY': 'debug',
}, },
'ESPLORA': { 'ESPLORA': {
@ -145,6 +156,7 @@ const defaults: IConfig = {
}, },
'SOCKS5PROXY': { 'SOCKS5PROXY': {
'ENABLED': false, 'ENABLED': false,
'USE_ONION': true,
'HOST': '127.0.0.1', 'HOST': '127.0.0.1',
'PORT': 9050, 'PORT': 9050,
'USERNAME': '', 'USERNAME': '',
@ -152,7 +164,15 @@ const defaults: IConfig = {
}, },
"PRICE_DATA_SERVER": { "PRICE_DATA_SERVER": {
'TOR_URL': 'http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices', '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']; BISQ: IConfig['BISQ'];
SOCKS5PROXY: IConfig['SOCKS5PROXY']; SOCKS5PROXY: IConfig['SOCKS5PROXY'];
PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER']; PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER'];
EXTERNAL_DATA_SERVER: IConfig['EXTERNAL_DATA_SERVER'];
constructor() { constructor() {
const configs = this.merge(configFile, defaults); const configs = this.merge(configFile, defaults);
@ -182,6 +203,7 @@ class Config implements IConfig {
this.BISQ = configs.BISQ; this.BISQ = configs.BISQ;
this.SOCKS5PROXY = configs.SOCKS5PROXY; this.SOCKS5PROXY = configs.SOCKS5PROXY;
this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER; this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER;
this.EXTERNAL_DATA_SERVER = configs.EXTERNAL_DATA_SERVER;
} }
merge = (...objects: object[]): IConfig => { merge = (...objects: object[]): IConfig => {

View File

@ -205,7 +205,7 @@ class Server {
.post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', routes.$postTransactionForm) .post(config.MEMPOOL.API_URL_PREFIX + 'tx/push', routes.$postTransactionForm)
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => { .get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
try { 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); response.data.pipe(res);
} catch (e) { } catch (e) {
res.status(500).end(); res.status(500).end();
@ -213,7 +213,7 @@ class Server {
}) })
.get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => { .get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
try { 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 responseType: 'stream', timeout: 10000
}); });
response.data.pipe(res); response.data.pipe(res);
@ -223,7 +223,7 @@ class Server {
}) })
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => { .get(config.MEMPOOL.API_URL_PREFIX + 'contributors', async (req, res) => {
try { 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); response.data.pipe(res);
} catch (e) { } catch (e) {
res.status(500).end(); res.status(500).end();
@ -231,7 +231,7 @@ class Server {
}) })
.get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => { .get(config.MEMPOOL.API_URL_PREFIX + 'contributors/images/:id', async (req, res) => {
try { 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 responseType: 'stream', timeout: 10000
}); });
response.data.pipe(res); response.data.pipe(res);
@ -241,7 +241,7 @@ class Server {
}) })
.get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => { .get(config.MEMPOOL.API_URL_PREFIX + 'translators', async (req, res) => {
try { 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); response.data.pipe(res);
} catch (e) { } catch (e) {
res.status(500).end(); res.status(500).end();
@ -249,7 +249,7 @@ class Server {
}) })
.get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => { .get(config.MEMPOOL.API_URL_PREFIX + 'translators/images/:id', async (req, res) => {
try { 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 responseType: 'stream', timeout: 10000
}); });
response.data.pipe(res); response.data.pipe(res);

View File

@ -990,7 +990,7 @@ class Routes {
public async $getAllFeaturedLiquidAssets(req: Request, res: Response) { public async $getAllFeaturedLiquidAssets(req: Request, res: Response) {
try { 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); response.data.pipe(res);
} catch (e) { } catch (e) {
res.status(500).end(); res.status(500).end();
@ -999,7 +999,7 @@ class Routes {
public async $getAssetGroup(req: Request, res: Response) { public async $getAssetGroup(req: Request, res: Response) {
try { 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 }); { responseType: 'stream', timeout: 10000 });
response.data.pipe(res); response.data.pipe(res);
} catch (e) { } catch (e) {

View File

@ -1,4 +1,4 @@
import axios from 'axios'; import axios, { AxiosResponse } from 'axios';
import poolsParser from '../api/pools-parser'; import poolsParser from '../api/pools-parser';
import config from '../config'; import config from '../config';
import DB from '../database'; import DB from '../database';
@ -136,10 +136,10 @@ class PoolsUpdater {
axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions); axiosOptions.httpsAgent = new SocksProxyAgent(socksOptions);
} }
while(retry < 5) { while(retry < config.MEMPOOL.EXTERNAL_MAX_RETRY) {
try { try {
const data = await axios.get(path, axiosOptions); const data: AxiosResponse = await axios.get(path, axiosOptions);
if (data.statusText !== 'OK' || !data.data) { if (data.statusText === 'error' || !data.data) {
throw new Error(`Could not fetch data from Github, Error: ${data.status}`); throw new Error(`Could not fetch data from Github, Error: ${data.status}`);
} }
return data.data; return data.data;
@ -147,7 +147,7 @@ class PoolsUpdater {
logger.err('Could not connect to Github. Reason: ' + (e instanceof Error ? e.message : e)); logger.err('Could not connect to Github. Reason: ' + (e instanceof Error ? e.message : e));
retry++; retry++;
} }
await setDelay(); await setDelay(config.MEMPOOL.EXTERNAL_RETRY_INTERVAL);
} }
return undefined; return undefined;
} }