Revert TCP socket fallback

This commit is contained in:
softsimon 2023-04-28 12:06:49 +04:00
parent 2fbe2b2fa6
commit 000c46bf57
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
2 changed files with 32 additions and 63 deletions

View File

@ -3,102 +3,68 @@ import axios, { AxiosRequestConfig } from 'axios';
import http from 'http'; import http from 'http';
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
import { IEsploraApi } from './esplora-api.interface'; import { IEsploraApi } from './esplora-api.interface';
import logger from '../../logger';
const axiosConnection = axios.create({ const axiosConnection = axios.create({
httpAgent: new http.Agent({ keepAlive: true, }) httpAgent: new http.Agent({ keepAlive: true, })
}); });
class ElectrsApi implements AbstractBitcoinApi { class ElectrsApi implements AbstractBitcoinApi {
private axiosConfigWithUnixSocket: AxiosRequestConfig = config.ESPLORA.UNIX_SOCKET_PATH ? { axiosConfig: AxiosRequestConfig = config.ESPLORA.UNIX_SOCKET_PATH ? {
socketPath: config.ESPLORA.UNIX_SOCKET_PATH, socketPath: config.ESPLORA.UNIX_SOCKET_PATH,
timeout: 10000, timeout: 10000,
} : { } : {
timeout: 10000, timeout: 10000,
}; };
private axiosConfigTcpSocketOnly: AxiosRequestConfig = {
timeout: 10000,
};
unixSocketRetryTimeout; constructor() { }
activeAxiosConfig;
constructor() {
this.activeAxiosConfig = this.axiosConfigWithUnixSocket;
}
fallbackToTcpSocket() {
if (!this.unixSocketRetryTimeout) {
logger.err(`Unable to connect to esplora unix socket. Falling back to tcp socket. Retrying unix socket in ${config.ESPLORA.RETRY_UNIX_SOCKET_AFTER / 1000} seconds`);
// Retry the unix socket after a few seconds
this.unixSocketRetryTimeout = setTimeout(() => {
logger.info(`Retrying to use unix socket for esplora now (applied for the next query)`);
this.activeAxiosConfig = this.axiosConfigWithUnixSocket;
this.unixSocketRetryTimeout = undefined;
}, config.ESPLORA.RETRY_UNIX_SOCKET_AFTER);
}
// Use the TCP socket (reach a different esplora instance through nginx)
this.activeAxiosConfig = this.axiosConfigTcpSocketOnly;
}
$queryWrapper<T>(url, responseType = 'json'): Promise<T> {
return axiosConnection.get<T>(url, { ...this.activeAxiosConfig, responseType: responseType })
.then((response) => response.data)
.catch((e) => {
if (e?.code === 'ECONNREFUSED') {
this.fallbackToTcpSocket();
// Retry immediately
return axiosConnection.get<T>(url, this.activeAxiosConfig)
.then((response) => response.data)
.catch((e) => {
logger.warn(`Cannot query esplora through the unix socket nor the tcp socket. Exception ${e}`);
throw e;
});
} else {
throw e;
}
});
}
$getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]> { $getRawMempool(): Promise<IEsploraApi.Transaction['txid'][]> {
return this.$queryWrapper<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids'); return axiosConnection.get<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig)
.then((response) => response.data);
} }
$getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> { $getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> {
return this.$queryWrapper<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId); return axiosConnection.get<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig)
.then((response) => response.data);
} }
$getTransactionHex(txId: string): Promise<string> { $getTransactionHex(txId: string): Promise<string> {
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex'); return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig)
.then((response) => response.data);
} }
$getBlockHeightTip(): Promise<number> { $getBlockHeightTip(): Promise<number> {
return this.$queryWrapper<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height'); return axiosConnection.get<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig)
.then((response) => response.data);
} }
$getBlockHashTip(): Promise<string> { $getBlockHashTip(): Promise<string> {
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash'); return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig)
.then((response) => response.data);
} }
$getTxIdsForBlock(hash: string): Promise<string[]> { $getTxIdsForBlock(hash: string): Promise<string[]> {
return this.$queryWrapper<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids'); return axiosConnection.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig)
.then((response) => response.data);
} }
$getBlockHash(height: number): Promise<string> { $getBlockHash(height: number): Promise<string> {
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height); return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig)
.then((response) => response.data);
} }
$getBlockHeader(hash: string): Promise<string> { $getBlockHeader(hash: string): Promise<string> {
return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header'); return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig)
.then((response) => response.data);
} }
$getBlock(hash: string): Promise<IEsploraApi.Block> { $getBlock(hash: string): Promise<IEsploraApi.Block> {
return this.$queryWrapper<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash); return axiosConnection.get<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig)
.then((response) => response.data);
} }
$getRawBlock(hash: string): Promise<Buffer> { $getRawBlock(hash: string): Promise<Buffer> {
return this.$queryWrapper<any>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", 'arraybuffer') return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", { ...this.axiosConfig, responseType: 'arraybuffer' })
.then((response) => { return Buffer.from(response.data); }); .then((response) => { return Buffer.from(response.data); });
} }
@ -119,11 +85,13 @@ class ElectrsApi implements AbstractBitcoinApi {
} }
$getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> { $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> {
return this.$queryWrapper<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout); return axiosConnection.get<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig)
.then((response) => response.data);
} }
$getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> { $getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> {
return this.$queryWrapper<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends'); return axiosConnection.get<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig)
.then((response) => response.data);
} }
async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> { async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> {

View File

@ -45,8 +45,7 @@ class Server {
private wss: WebSocket.Server | undefined; private wss: WebSocket.Server | undefined;
private server: http.Server | undefined; private server: http.Server | undefined;
private app: Application; private app: Application;
private currentBackendRetryInterval = 1; private currentBackendRetryInterval = 5;
private backendRetryCount = 0;
private maxHeapSize: number = 0; private maxHeapSize: number = 0;
private heapLogInterval: number = 60; private heapLogInterval: number = 60;
@ -185,17 +184,17 @@ class Server {
indexer.$run(); indexer.$run();
setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS); setTimeout(this.runMainUpdateLoop.bind(this), config.MEMPOOL.POLL_RATE_MS);
this.backendRetryCount = 0; this.currentBackendRetryInterval = 5;
} catch (e: any) { } catch (e: any) {
this.backendRetryCount++; let loggerMsg = `Exception in runMainUpdateLoop(). Retrying in ${this.currentBackendRetryInterval} sec.`;
let loggerMsg = `Exception in runMainUpdateLoop() (count: ${this.backendRetryCount}). Retrying in ${this.currentBackendRetryInterval} sec.`;
loggerMsg += ` Reason: ${(e instanceof Error ? e.message : e)}.`; loggerMsg += ` Reason: ${(e instanceof Error ? e.message : e)}.`;
if (e?.stack) { if (e?.stack) {
loggerMsg += ` Stack trace: ${e.stack}`; loggerMsg += ` Stack trace: ${e.stack}`;
} }
// When we get a first Exception, only `logger.debug` it and retry after 5 seconds // When we get a first Exception, only `logger.debug` it and retry after 5 seconds
// From the second Exception, `logger.warn` the Exception and increase the retry delay // From the second Exception, `logger.warn` the Exception and increase the retry delay
if (this.backendRetryCount >= 5) { // Maximum retry delay is 60 seconds
if (this.currentBackendRetryInterval > 5) {
logger.warn(loggerMsg); logger.warn(loggerMsg);
mempool.setOutOfSync(); mempool.setOutOfSync();
} else { } else {
@ -205,6 +204,8 @@ class Server {
logger.debug(`AxiosError: ${e?.message}`); logger.debug(`AxiosError: ${e?.message}`);
} }
setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval); setTimeout(this.runMainUpdateLoop.bind(this), 1000 * this.currentBackendRetryInterval);
this.currentBackendRetryInterval *= 2;
this.currentBackendRetryInterval = Math.min(this.currentBackendRetryInterval, 60);
} }
} }