Refactoring Bitcoin RPC client implementation

This commit is contained in:
softsimon 2021-09-15 01:47:24 +04:00
parent d602b20f56
commit 641d2ad028
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
13 changed files with 71 additions and 85 deletions

View File

@ -28,7 +28,7 @@
"ESPLORA": { "ESPLORA": {
"REST_API_URL": "http://127.0.0.1:3000" "REST_API_URL": "http://127.0.0.1:3000"
}, },
"CORE_RPC_MINFEE": { "SECOND_CORE_RPC": {
"ENABLED": false, "ENABLED": false,
"HOST": "127.0.0.1", "HOST": "127.0.0.1",
"PORT": 8332, "PORT": 8332,

View File

@ -12,3 +12,10 @@ export interface AbstractBitcoinApi {
$getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>; $getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
$getAddressPrefix(prefix: string): string[]; $getAddressPrefix(prefix: string): string[];
} }
export interface BitcoinRpcCredentials {
host: string;
port: number;
user: string;
pass: string;
timeout: number;
}

View File

@ -3,16 +3,17 @@ import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
import EsploraApi from './esplora-api'; import EsploraApi from './esplora-api';
import BitcoinApi from './bitcoin-api'; import BitcoinApi from './bitcoin-api';
import ElectrumApi from './electrum-api'; import ElectrumApi from './electrum-api';
import bitcoinClient from './bitcoin-client';
function bitcoinApiFactory(): AbstractBitcoinApi { function bitcoinApiFactory(): AbstractBitcoinApi {
switch (config.MEMPOOL.BACKEND) { switch (config.MEMPOOL.BACKEND) {
case 'esplora': case 'esplora':
return new EsploraApi(); return new EsploraApi();
case 'electrum': case 'electrum':
return new ElectrumApi(); return new ElectrumApi(bitcoinClient);
case 'none': case 'none':
default: default:
return new BitcoinApi(); return new BitcoinApi(bitcoinClient);
} }
} }

View File

@ -1,5 +1,3 @@
import config from '../../config';
import * as bitcoin from '@mempool/bitcoin';
import * as bitcoinjs from 'bitcoinjs-lib'; import * as bitcoinjs from 'bitcoinjs-lib';
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
import { IBitcoinApi } from './bitcoin-api.interface'; import { IBitcoinApi } from './bitcoin-api.interface';
@ -10,16 +8,10 @@ import { TransactionExtended } from '../../mempool.interfaces';
class BitcoinApi implements AbstractBitcoinApi { class BitcoinApi implements AbstractBitcoinApi {
private rawMempoolCache: IBitcoinApi.RawMempool | null = null; private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
private bitcoindClient: any; protected bitcoindClient: any;
constructor() { constructor(bitcoinClient: any) {
this.bitcoindClient = new bitcoin.Client({ this.bitcoindClient = bitcoinClient;
host: config.CORE_RPC.HOST,
port: config.CORE_RPC.PORT,
user: config.CORE_RPC.USERNAME,
pass: config.CORE_RPC.PASSWORD,
timeout: 60000,
});
} }
$getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> { $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {

View File

@ -1,53 +0,0 @@
import config from '../../config';
import * as bitcoin from '@mempool/bitcoin';
import { IBitcoinApi } from './bitcoin-api.interface';
class BitcoinBaseApi {
bitcoindClient: any;
bitcoindClientMempoolInfo: any;
constructor() {
this.bitcoindClient = new bitcoin.Client({
host: config.CORE_RPC.HOST,
port: config.CORE_RPC.PORT,
user: config.CORE_RPC.USERNAME,
pass: config.CORE_RPC.PASSWORD,
timeout: 60000,
});
if (config.CORE_RPC_MINFEE.ENABLED) {
this.bitcoindClientMempoolInfo = new bitcoin.Client({
host: config.CORE_RPC_MINFEE.HOST,
port: config.CORE_RPC_MINFEE.PORT,
user: config.CORE_RPC_MINFEE.USERNAME,
pass: config.CORE_RPC_MINFEE.PASSWORD,
timeout: 60000,
});
}
}
$getMempoolInfo(): Promise<IBitcoinApi.MempoolInfo> {
if (config.CORE_RPC_MINFEE.ENABLED) {
return Promise.all([
this.bitcoindClient.getMempoolInfo(),
this.bitcoindClientMempoolInfo.getMempoolInfo()
]).then(([mempoolInfo, secondMempoolInfo]) => {
mempoolInfo.maxmempool = secondMempoolInfo.maxmempool;
mempoolInfo.mempoolminfee = secondMempoolInfo.mempoolminfee;
mempoolInfo.minrelaytxfee = secondMempoolInfo.minrelaytxfee;
return mempoolInfo;
});
}
return this.bitcoindClient.getMempoolInfo();
}
$getBlockchainInfo(): Promise<IBitcoinApi.BlockchainInfo> {
return this.bitcoindClient.getBlockchainInfo();
}
$validateAddress(address: string): Promise<IBitcoinApi.AddressInformation> {
return this.bitcoindClient.validateAddress(address);
}
}
export default new BitcoinBaseApi();

View File

@ -0,0 +1,13 @@
import config from '../../config';
import * as bitcoin from '@mempool/bitcoin';
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.CORE_RPC.HOST,
port: config.CORE_RPC.PORT,
user: config.CORE_RPC.USERNAME,
pass: config.CORE_RPC.PASSWORD,
timeout: 60000,
};
export default new bitcoin.Client(nodeRpcCredentials);

View File

@ -0,0 +1,13 @@
import config from '../../config';
import * as bitcoin from '@mempool/bitcoin';
import { BitcoinRpcCredentials } from './bitcoin-api-abstract-factory';
const nodeRpcCredentials: BitcoinRpcCredentials = {
host: config.SECOND_CORE_RPC.HOST,
port: config.SECOND_CORE_RPC.PORT,
user: config.SECOND_CORE_RPC.USERNAME,
pass: config.SECOND_CORE_RPC.PASSWORD,
timeout: 60000,
};
export default new bitcoin.Client(nodeRpcCredentials);

View File

@ -1,23 +1,20 @@
import config from '../../config'; import config from '../../config';
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory'; import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
import { IBitcoinApi } from './bitcoin-api.interface';
import { IEsploraApi } from './esplora-api.interface'; import { IEsploraApi } from './esplora-api.interface';
import { IElectrumApi } from './electrum-api.interface'; import { IElectrumApi } from './electrum-api.interface';
import BitcoinApi from './bitcoin-api'; import BitcoinApi from './bitcoin-api';
import mempool from '../mempool';
import logger from '../../logger'; import logger from '../../logger';
import * as ElectrumClient from '@mempool/electrum-client'; import * as ElectrumClient from '@mempool/electrum-client';
import * as sha256 from 'crypto-js/sha256'; import * as sha256 from 'crypto-js/sha256';
import * as hexEnc from 'crypto-js/enc-hex'; import * as hexEnc from 'crypto-js/enc-hex';
import loadingIndicators from '../loading-indicators'; import loadingIndicators from '../loading-indicators';
import memoryCache from '../memory-cache'; import memoryCache from '../memory-cache';
import bitcoinBaseApi from './bitcoin-base.api';
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi { class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
private electrumClient: any; private electrumClient: any;
constructor() { constructor(bitcoinClient: any) {
super(); super(bitcoinClient);
const electrumConfig = { client: 'mempool-v2', version: '1.4' }; const electrumConfig = { client: 'mempool-v2', version: '1.4' };
const electrumPersistencePolicy = { retryPeriod: 10000, maxRetry: 1000, callback: null }; const electrumPersistencePolicy = { retryPeriod: 10000, maxRetry: 1000, callback: null };
@ -45,7 +42,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
} }
async $getAddress(address: string): Promise<IEsploraApi.Address> { async $getAddress(address: string): Promise<IEsploraApi.Address> {
const addressInfo = await bitcoinBaseApi.$validateAddress(address); const addressInfo = await this.bitcoindClient.validateAddress(address);
if (!addressInfo || !addressInfo.isvalid) { if (!addressInfo || !addressInfo.isvalid) {
return ({ return ({
'address': address, 'address': address,
@ -99,7 +96,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
} }
async $getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]> { async $getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]> {
const addressInfo = await bitcoinBaseApi.$validateAddress(address); const addressInfo = await this.bitcoindClient.validateAddress(address);
if (!addressInfo || !addressInfo.isvalid) { if (!addressInfo || !addressInfo.isvalid) {
return []; return [];
} }

View File

@ -6,7 +6,7 @@ import { BlockExtended, TransactionExtended } from '../mempool.interfaces';
import { Common } from './common'; import { Common } from './common';
import diskCache from './disk-cache'; import diskCache from './disk-cache';
import transactionUtils from './transaction-utils'; import transactionUtils from './transaction-utils';
import bitcoinBaseApi from './bitcoin/bitcoin-base.api'; import bitcoinClient from './bitcoin/bitcoin-client';
class Blocks { class Blocks {
private blocks: BlockExtended[] = []; private blocks: BlockExtended[] = [];
@ -45,7 +45,7 @@ class Blocks {
} }
if (!this.lastDifficultyAdjustmentTime) { if (!this.lastDifficultyAdjustmentTime) {
const blockchainInfo = await bitcoinBaseApi.$getBlockchainInfo(); const blockchainInfo = await bitcoinClient.getBlockchainInfo();
if (blockchainInfo.blocks === blockchainInfo.headers) { if (blockchainInfo.blocks === blockchainInfo.headers) {
const heightDiff = blockHeightTip % 2016; const heightDiff = blockHeightTip % 2016;
const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff); const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);

View File

@ -5,8 +5,9 @@ import logger from '../logger';
import { Common } from './common'; import { Common } from './common';
import transactionUtils from './transaction-utils'; import transactionUtils from './transaction-utils';
import { IBitcoinApi } from './bitcoin/bitcoin-api.interface'; import { IBitcoinApi } from './bitcoin/bitcoin-api.interface';
import bitcoinBaseApi from './bitcoin/bitcoin-base.api';
import loadingIndicators from './loading-indicators'; import loadingIndicators from './loading-indicators';
import bitcoinClient from './bitcoin/bitcoin-client';
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
class Mempool { class Mempool {
private static WEBSOCKET_REFRESH_RATE_MS = 10000; private static WEBSOCKET_REFRESH_RATE_MS = 10000;
@ -61,7 +62,7 @@ class Mempool {
} }
public async $updateMemPoolInfo() { public async $updateMemPoolInfo() {
this.mempoolInfo = await bitcoinBaseApi.$getMempoolInfo(); this.mempoolInfo = await this.$getMempoolInfo();
} }
public getMempoolInfo(): IBitcoinApi.MempoolInfo { public getMempoolInfo(): IBitcoinApi.MempoolInfo {
@ -205,6 +206,21 @@ class Mempool {
} }
} }
} }
private $getMempoolInfo() {
if (config.SECOND_CORE_RPC.ENABLED) {
return Promise.all([
bitcoinClient.getMempoolInfo(),
bitcoinSecondClient.getMempoolInfo()
]).then(([mempoolInfo, secondMempoolInfo]) => {
mempoolInfo.maxmempool = secondMempoolInfo.maxmempool;
mempoolInfo.mempoolminfee = secondMempoolInfo.mempoolminfee;
mempoolInfo.minrelaytxfee = secondMempoolInfo.minrelaytxfee;
return mempoolInfo;
});
}
return bitcoinClient.getMempoolInfo();
}
} }
export default new Mempool(); export default new Mempool();

View File

@ -30,7 +30,7 @@ interface IConfig {
USERNAME: string; USERNAME: string;
PASSWORD: string; PASSWORD: string;
}; };
CORE_RPC_MINFEE: { SECOND_CORE_RPC: {
ENABLED: boolean; ENABLED: boolean;
HOST: string; HOST: string;
PORT: number; PORT: number;
@ -92,7 +92,7 @@ const defaults: IConfig = {
'USERNAME': 'mempool', 'USERNAME': 'mempool',
'PASSWORD': 'mempool' 'PASSWORD': 'mempool'
}, },
'CORE_RPC_MINFEE': { 'SECOND_CORE_RPC': {
'ENABLED': false, 'ENABLED': false,
'HOST': '127.0.0.1', 'HOST': '127.0.0.1',
'PORT': 8332, 'PORT': 8332,
@ -129,7 +129,7 @@ class Config implements IConfig {
ESPLORA: IConfig['ESPLORA']; ESPLORA: IConfig['ESPLORA'];
ELECTRUM: IConfig['ELECTRUM']; ELECTRUM: IConfig['ELECTRUM'];
CORE_RPC: IConfig['CORE_RPC']; CORE_RPC: IConfig['CORE_RPC'];
CORE_RPC_MINFEE: IConfig['CORE_RPC_MINFEE']; SECOND_CORE_RPC: IConfig['SECOND_CORE_RPC'];
DATABASE: IConfig['DATABASE']; DATABASE: IConfig['DATABASE'];
SYSLOG: IConfig['SYSLOG']; SYSLOG: IConfig['SYSLOG'];
STATISTICS: IConfig['STATISTICS']; STATISTICS: IConfig['STATISTICS'];
@ -141,7 +141,7 @@ class Config implements IConfig {
this.ESPLORA = configs.ESPLORA; this.ESPLORA = configs.ESPLORA;
this.ELECTRUM = configs.ELECTRUM; this.ELECTRUM = configs.ELECTRUM;
this.CORE_RPC = configs.CORE_RPC; this.CORE_RPC = configs.CORE_RPC;
this.CORE_RPC_MINFEE = configs.CORE_RPC_MINFEE; this.SECOND_CORE_RPC = configs.SECOND_CORE_RPC;
this.DATABASE = configs.DATABASE; this.DATABASE = configs.DATABASE;
this.SYSLOG = configs.SYSLOG; this.SYSLOG = configs.SYSLOG;
this.STATISTICS = configs.STATISTICS; this.STATISTICS = configs.STATISTICS;

View File

@ -112,7 +112,7 @@ class Server {
await memPool.$updateMemPoolInfo(); await memPool.$updateMemPoolInfo();
} catch (e) { } catch (e) {
const msg = `updateMempoolInfo: ${(e instanceof Error ? e.message : e)}`; const msg = `updateMempoolInfo: ${(e instanceof Error ? e.message : e)}`;
if (config.CORE_RPC_MINFEE.ENABLED) { if (config.SECOND_CORE_RPC.ENABLED) {
logger.warn(msg); logger.warn(msg);
} else { } else {
logger.debug(msg); logger.debug(msg);

View File

@ -17,7 +17,7 @@ import transactionUtils from './api/transaction-utils';
import blocks from './api/blocks'; import blocks from './api/blocks';
import loadingIndicators from './api/loading-indicators'; import loadingIndicators from './api/loading-indicators';
import { Common } from './api/common'; import { Common } from './api/common';
import bitcoinBaseApi from './api/bitcoin/bitcoin-base.api'; import bitcoinClient from './api/bitcoin/bitcoin-client';
class Routes { class Routes {
constructor() {} constructor() {}
@ -690,7 +690,7 @@ class Routes {
public async validateAddress(req: Request, res: Response) { public async validateAddress(req: Request, res: Response) {
try { try {
const result = await bitcoinBaseApi.$validateAddress(req.params.address); const result = await bitcoinClient.validateAddress(req.params.address);
res.json(result); res.json(result);
} catch (e) { } catch (e) {
res.status(500).send(e instanceof Error ? e.message : e); res.status(500).send(e instanceof Error ? e.message : e);