Refactoring Bitcoin RPC client implementation
This commit is contained in:
		
							parent
							
								
									d602b20f56
								
							
						
					
					
						commit
						641d2ad028
					
				@ -28,7 +28,7 @@
 | 
			
		||||
  "ESPLORA": {
 | 
			
		||||
    "REST_API_URL": "http://127.0.0.1:3000"
 | 
			
		||||
  },
 | 
			
		||||
  "CORE_RPC_MINFEE": {
 | 
			
		||||
  "SECOND_CORE_RPC": {
 | 
			
		||||
    "ENABLED": false,
 | 
			
		||||
    "HOST": "127.0.0.1",
 | 
			
		||||
    "PORT": 8332,
 | 
			
		||||
 | 
			
		||||
@ -12,3 +12,10 @@ export interface AbstractBitcoinApi {
 | 
			
		||||
  $getAddressTransactions(address: string, lastSeenTxId: string): Promise<IEsploraApi.Transaction[]>;
 | 
			
		||||
  $getAddressPrefix(prefix: string): string[];
 | 
			
		||||
}
 | 
			
		||||
export interface BitcoinRpcCredentials {
 | 
			
		||||
  host: string;
 | 
			
		||||
  port: number;
 | 
			
		||||
  user: string;
 | 
			
		||||
  pass: string;
 | 
			
		||||
  timeout: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -3,16 +3,17 @@ import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
			
		||||
import EsploraApi from './esplora-api';
 | 
			
		||||
import BitcoinApi from './bitcoin-api';
 | 
			
		||||
import ElectrumApi from './electrum-api';
 | 
			
		||||
import bitcoinClient from './bitcoin-client';
 | 
			
		||||
 | 
			
		||||
function bitcoinApiFactory(): AbstractBitcoinApi {
 | 
			
		||||
  switch (config.MEMPOOL.BACKEND) {
 | 
			
		||||
    case 'esplora':
 | 
			
		||||
      return new EsploraApi();
 | 
			
		||||
    case 'electrum':
 | 
			
		||||
      return new ElectrumApi();
 | 
			
		||||
      return new ElectrumApi(bitcoinClient);
 | 
			
		||||
    case 'none':
 | 
			
		||||
    default:
 | 
			
		||||
      return new BitcoinApi();
 | 
			
		||||
      return new BitcoinApi(bitcoinClient);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,3 @@
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import * as bitcoin from '@mempool/bitcoin';
 | 
			
		||||
import * as bitcoinjs from 'bitcoinjs-lib';
 | 
			
		||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
			
		||||
import { IBitcoinApi } from './bitcoin-api.interface';
 | 
			
		||||
@ -10,16 +8,10 @@ import { TransactionExtended } from '../../mempool.interfaces';
 | 
			
		||||
 | 
			
		||||
class BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
  private rawMempoolCache: IBitcoinApi.RawMempool | null = null;
 | 
			
		||||
  private bitcoindClient: any;
 | 
			
		||||
  protected bitcoindClient: 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,
 | 
			
		||||
    });
 | 
			
		||||
  constructor(bitcoinClient: any) {
 | 
			
		||||
    this.bitcoindClient = bitcoinClient;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getRawTransaction(txId: string, skipConversion = false, addPrevout = false): Promise<IEsploraApi.Transaction> {
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
							
								
								
									
										13
									
								
								backend/src/api/bitcoin/bitcoin-client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/src/api/bitcoin/bitcoin-client.ts
									
									
									
									
									
										Normal 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);
 | 
			
		||||
							
								
								
									
										13
									
								
								backend/src/api/bitcoin/bitcoin-second-client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								backend/src/api/bitcoin/bitcoin-second-client.ts
									
									
									
									
									
										Normal 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);
 | 
			
		||||
@ -1,23 +1,20 @@
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
			
		||||
import { IBitcoinApi } from './bitcoin-api.interface';
 | 
			
		||||
import { IEsploraApi } from './esplora-api.interface';
 | 
			
		||||
import { IElectrumApi } from './electrum-api.interface';
 | 
			
		||||
import BitcoinApi from './bitcoin-api';
 | 
			
		||||
import mempool from '../mempool';
 | 
			
		||||
import logger from '../../logger';
 | 
			
		||||
import * as ElectrumClient from '@mempool/electrum-client';
 | 
			
		||||
import * as sha256 from 'crypto-js/sha256';
 | 
			
		||||
import * as hexEnc from 'crypto-js/enc-hex';
 | 
			
		||||
import loadingIndicators from '../loading-indicators';
 | 
			
		||||
import memoryCache from '../memory-cache';
 | 
			
		||||
import bitcoinBaseApi from './bitcoin-base.api';
 | 
			
		||||
 | 
			
		||||
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
  private electrumClient: any;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
  constructor(bitcoinClient: any) {
 | 
			
		||||
    super(bitcoinClient);
 | 
			
		||||
 | 
			
		||||
    const electrumConfig = { client: 'mempool-v2', version: '1.4' };
 | 
			
		||||
    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> {
 | 
			
		||||
    const addressInfo = await bitcoinBaseApi.$validateAddress(address);
 | 
			
		||||
    const addressInfo = await this.bitcoindClient.validateAddress(address);
 | 
			
		||||
    if (!addressInfo || !addressInfo.isvalid) {
 | 
			
		||||
      return ({
 | 
			
		||||
        'address': address,
 | 
			
		||||
@ -99,7 +96,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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) {
 | 
			
		||||
     return [];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,7 @@ import { BlockExtended, TransactionExtended } from '../mempool.interfaces';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
import diskCache from './disk-cache';
 | 
			
		||||
import transactionUtils from './transaction-utils';
 | 
			
		||||
import bitcoinBaseApi from './bitcoin/bitcoin-base.api';
 | 
			
		||||
import bitcoinClient from './bitcoin/bitcoin-client';
 | 
			
		||||
 | 
			
		||||
class Blocks {
 | 
			
		||||
  private blocks: BlockExtended[] = [];
 | 
			
		||||
@ -45,7 +45,7 @@ class Blocks {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!this.lastDifficultyAdjustmentTime) {
 | 
			
		||||
      const blockchainInfo = await bitcoinBaseApi.$getBlockchainInfo();
 | 
			
		||||
      const blockchainInfo = await bitcoinClient.getBlockchainInfo();
 | 
			
		||||
      if (blockchainInfo.blocks === blockchainInfo.headers) {
 | 
			
		||||
        const heightDiff = blockHeightTip % 2016;
 | 
			
		||||
        const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);
 | 
			
		||||
 | 
			
		||||
@ -5,8 +5,9 @@ import logger from '../logger';
 | 
			
		||||
import { Common } from './common';
 | 
			
		||||
import transactionUtils from './transaction-utils';
 | 
			
		||||
import { IBitcoinApi } from './bitcoin/bitcoin-api.interface';
 | 
			
		||||
import bitcoinBaseApi from './bitcoin/bitcoin-base.api';
 | 
			
		||||
import loadingIndicators from './loading-indicators';
 | 
			
		||||
import bitcoinClient from './bitcoin/bitcoin-client';
 | 
			
		||||
import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
 | 
			
		||||
 | 
			
		||||
class Mempool {
 | 
			
		||||
  private static WEBSOCKET_REFRESH_RATE_MS = 10000;
 | 
			
		||||
@ -61,7 +62,7 @@ class Mempool {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async $updateMemPoolInfo() {
 | 
			
		||||
    this.mempoolInfo = await bitcoinBaseApi.$getMempoolInfo();
 | 
			
		||||
    this.mempoolInfo = await this.$getMempoolInfo();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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();
 | 
			
		||||
 | 
			
		||||
@ -30,7 +30,7 @@ interface IConfig {
 | 
			
		||||
    USERNAME: string;
 | 
			
		||||
    PASSWORD: string;
 | 
			
		||||
  };
 | 
			
		||||
  CORE_RPC_MINFEE: {
 | 
			
		||||
  SECOND_CORE_RPC: {
 | 
			
		||||
    ENABLED: boolean;
 | 
			
		||||
    HOST: string;
 | 
			
		||||
    PORT: number;
 | 
			
		||||
@ -92,7 +92,7 @@ const defaults: IConfig = {
 | 
			
		||||
    'USERNAME': 'mempool',
 | 
			
		||||
    'PASSWORD': 'mempool'
 | 
			
		||||
  },
 | 
			
		||||
  'CORE_RPC_MINFEE': {
 | 
			
		||||
  'SECOND_CORE_RPC': {
 | 
			
		||||
    'ENABLED': false,
 | 
			
		||||
    'HOST': '127.0.0.1',
 | 
			
		||||
    'PORT': 8332,
 | 
			
		||||
@ -129,7 +129,7 @@ class Config implements IConfig {
 | 
			
		||||
  ESPLORA: IConfig['ESPLORA'];
 | 
			
		||||
  ELECTRUM: IConfig['ELECTRUM'];
 | 
			
		||||
  CORE_RPC: IConfig['CORE_RPC'];
 | 
			
		||||
  CORE_RPC_MINFEE: IConfig['CORE_RPC_MINFEE'];
 | 
			
		||||
  SECOND_CORE_RPC: IConfig['SECOND_CORE_RPC'];
 | 
			
		||||
  DATABASE: IConfig['DATABASE'];
 | 
			
		||||
  SYSLOG: IConfig['SYSLOG'];
 | 
			
		||||
  STATISTICS: IConfig['STATISTICS'];
 | 
			
		||||
@ -141,7 +141,7 @@ class Config implements IConfig {
 | 
			
		||||
    this.ESPLORA = configs.ESPLORA;
 | 
			
		||||
    this.ELECTRUM = configs.ELECTRUM;
 | 
			
		||||
    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.SYSLOG = configs.SYSLOG;
 | 
			
		||||
    this.STATISTICS = configs.STATISTICS;
 | 
			
		||||
 | 
			
		||||
@ -112,7 +112,7 @@ class Server {
 | 
			
		||||
        await memPool.$updateMemPoolInfo();
 | 
			
		||||
      } catch (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);
 | 
			
		||||
        } else {
 | 
			
		||||
          logger.debug(msg);
 | 
			
		||||
 | 
			
		||||
@ -17,7 +17,7 @@ import transactionUtils from './api/transaction-utils';
 | 
			
		||||
import blocks from './api/blocks';
 | 
			
		||||
import loadingIndicators from './api/loading-indicators';
 | 
			
		||||
import { Common } from './api/common';
 | 
			
		||||
import bitcoinBaseApi from './api/bitcoin/bitcoin-base.api';
 | 
			
		||||
import bitcoinClient from './api/bitcoin/bitcoin-client';
 | 
			
		||||
 | 
			
		||||
class Routes {
 | 
			
		||||
  constructor() {}
 | 
			
		||||
@ -690,7 +690,7 @@ class Routes {
 | 
			
		||||
 | 
			
		||||
  public async validateAddress(req: Request, res: Response) {
 | 
			
		||||
    try {
 | 
			
		||||
      const result = await bitcoinBaseApi.$validateAddress(req.params.address);
 | 
			
		||||
      const result = await bitcoinClient.validateAddress(req.params.address);
 | 
			
		||||
      res.json(result);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e instanceof Error ? e.message : e);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user