[esplora] fallback to tcp socket if unix socket fails
This commit is contained in:
		
							parent
							
								
									6c81dcdc76
								
							
						
					
					
						commit
						44a0913b81
					
				| @ -3,68 +3,96 @@ 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 { | ||||||
|   axiosConfig: AxiosRequestConfig = config.ESPLORA.UNIX_SOCKET_PATH ? { |   private axiosConfigWithUnixSocket: 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, | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   constructor() { } |   unixSocketRetryTimeout; | ||||||
|  |   activeAxiosConfig; | ||||||
|  | 
 | ||||||
|  |   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} seconds`); | ||||||
|  |       // Retry the unix socket after a few seconds
 | ||||||
|  |       this.unixSocketRetryTimeout = setTimeout(() => { | ||||||
|  |         this.activeAxiosConfig = this.axiosConfigWithUnixSocket; | ||||||
|  |       }, 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' || e?.code === 'ETIMEDOUT') { | ||||||
|  |           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 axiosConnection.get<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids', this.axiosConfig) |     return this.$queryWrapper<IEsploraApi.Transaction['txid'][]>(config.ESPLORA.REST_API_URL + '/mempool/txids'); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> { |   $getRawTransaction(txId: string): Promise<IEsploraApi.Transaction> { | ||||||
|     return axiosConnection.get<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId, this.axiosConfig) |     return this.$queryWrapper<IEsploraApi.Transaction>(config.ESPLORA.REST_API_URL + '/tx/' + txId); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getTransactionHex(txId: string): Promise<string> { |   $getTransactionHex(txId: string): Promise<string> { | ||||||
|     return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex', this.axiosConfig) |     return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/hex'); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getBlockHeightTip(): Promise<number> { |   $getBlockHeightTip(): Promise<number> { | ||||||
|     return axiosConnection.get<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height', this.axiosConfig) |     return this.$queryWrapper<number>(config.ESPLORA.REST_API_URL + '/blocks/tip/height'); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getBlockHashTip(): Promise<string> { |   $getBlockHashTip(): Promise<string> { | ||||||
|     return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash', this.axiosConfig) |     return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/blocks/tip/hash'); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getTxIdsForBlock(hash: string): Promise<string[]> { |   $getTxIdsForBlock(hash: string): Promise<string[]> { | ||||||
|     return axiosConnection.get<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids', this.axiosConfig) |     return this.$queryWrapper<string[]>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/txids'); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getBlockHash(height: number): Promise<string> { |   $getBlockHash(height: number): Promise<string> { | ||||||
|     return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height, this.axiosConfig) |     return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block-height/' + height); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getBlockHeader(hash: string): Promise<string> { |   $getBlockHeader(hash: string): Promise<string> { | ||||||
|     return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header', this.axiosConfig) |     return this.$queryWrapper<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + '/header'); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getBlock(hash: string): Promise<IEsploraApi.Block> { |   $getBlock(hash: string): Promise<IEsploraApi.Block> { | ||||||
|     return axiosConnection.get<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash, this.axiosConfig) |     return this.$queryWrapper<IEsploraApi.Block>(config.ESPLORA.REST_API_URL + '/block/' + hash); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getRawBlock(hash: string): Promise<Buffer> { |   $getRawBlock(hash: string): Promise<Buffer> { | ||||||
|     return axiosConnection.get<string>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", { ...this.axiosConfig, responseType: 'arraybuffer' }) |     return this.$queryWrapper<any>(config.ESPLORA.REST_API_URL + '/block/' + hash + "/raw", 'arraybuffer') | ||||||
|       .then((response) => { return Buffer.from(response.data); }); |       .then((response) => { return Buffer.from(response.data); }); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
| @ -85,13 +113,11 @@ class ElectrsApi implements AbstractBitcoinApi { | |||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> { |   $getOutspend(txId: string, vout: number): Promise<IEsploraApi.Outspend> { | ||||||
|     return axiosConnection.get<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout, this.axiosConfig) |     return this.$queryWrapper<IEsploraApi.Outspend>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspend/' + vout); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   $getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> { |   $getOutspends(txId: string): Promise<IEsploraApi.Outspend[]> { | ||||||
|     return axiosConnection.get<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends', this.axiosConfig) |     return this.$queryWrapper<IEsploraApi.Outspend[]>(config.ESPLORA.REST_API_URL + '/tx/' + txId + '/outspends'); | ||||||
|       .then((response) => response.data); |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> { |   async $getBatchedOutspends(txId: string[]): Promise<IEsploraApi.Outspend[][]> { | ||||||
|  | |||||||
| @ -38,6 +38,7 @@ interface IConfig { | |||||||
|   ESPLORA: { |   ESPLORA: { | ||||||
|     REST_API_URL: string; |     REST_API_URL: string; | ||||||
|     UNIX_SOCKET_PATH: string | void | null; |     UNIX_SOCKET_PATH: string | void | null; | ||||||
|  |     RETRY_UNIX_SOCKET_AFTER: number; | ||||||
|   }; |   }; | ||||||
|   LIGHTNING: { |   LIGHTNING: { | ||||||
|     ENABLED: boolean; |     ENABLED: boolean; | ||||||
| @ -165,6 +166,7 @@ const defaults: IConfig = { | |||||||
|   'ESPLORA': { |   'ESPLORA': { | ||||||
|     'REST_API_URL': 'http://127.0.0.1:3000', |     'REST_API_URL': 'http://127.0.0.1:3000', | ||||||
|     'UNIX_SOCKET_PATH': null, |     'UNIX_SOCKET_PATH': null, | ||||||
|  |     'RETRY_UNIX_SOCKET_AFTER': 30000, | ||||||
|   }, |   }, | ||||||
|   'ELECTRUM': { |   'ELECTRUM': { | ||||||
|     'HOST': '127.0.0.1', |     'HOST': '127.0.0.1', | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user