mirror of
https://github.com/mempool/mempool.git
synced 2025-01-17 18:52:34 +01:00
Merge branch 'esplora'
* esplora: Adding optional Blockstream esplora backend support.
This commit is contained in:
commit
f5e74c844b
@ -19,5 +19,7 @@
|
||||
"BITCOIN_NODE_PORT": 8332,
|
||||
"BITCOIN_NODE_USER": "",
|
||||
"BITCOIN_NODE_PASS": "",
|
||||
"BACKEND_API": "bitcoind",
|
||||
"ESPLORA_API_URL": "https://www.blockstream.info/api",
|
||||
"TX_PER_SECOND_SPAN_SECONDS": 150
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"bitcoin": "^3.0.1",
|
||||
"compression": "^1.7.3",
|
||||
"express": "^4.16.3",
|
||||
|
10
backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
Normal file
10
backend/src/api/bitcoin/bitcoin-api-abstract-factory.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { IMempoolInfo, ITransaction, IBlock } from '../../interfaces';
|
||||
|
||||
export interface AbstractBitcoinApi {
|
||||
getMempoolInfo(): Promise<IMempoolInfo>;
|
||||
getRawMempool(): Promise<ITransaction['txid'][]>;
|
||||
getRawTransaction(txId: string): Promise<ITransaction>;
|
||||
getBlockCount(): Promise<number>;
|
||||
getBlock(hash: string): Promise<IBlock>;
|
||||
getBlockHash(height: number): Promise<string>;
|
||||
}
|
16
backend/src/api/bitcoin/bitcoin-api-factory.ts
Normal file
16
backend/src/api/bitcoin/bitcoin-api-factory.ts
Normal file
@ -0,0 +1,16 @@
|
||||
const config = require('../../../mempool-config.json');
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import BitcoindApi from './bitcoind-api';
|
||||
import EsploraApi from './esplora-api';
|
||||
|
||||
function factory(): AbstractBitcoinApi {
|
||||
switch (config.BACKEND_API) {
|
||||
case 'esplora':
|
||||
return new EsploraApi();
|
||||
case 'bitcoind':
|
||||
default:
|
||||
return new BitcoindApi();
|
||||
}
|
||||
}
|
||||
|
||||
export default factory();
|
@ -1,8 +1,9 @@
|
||||
const config = require('../../mempool-config.json');
|
||||
const config = require('../../../mempool-config.json');
|
||||
import * as bitcoin from 'bitcoin';
|
||||
import { ITransaction, IMempoolInfo, IBlock } from '../interfaces';
|
||||
import { ITransaction, IMempoolInfo, IBlock } from '../../interfaces';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
|
||||
class BitcoinApi {
|
||||
class BitcoindApi implements AbstractBitcoinApi {
|
||||
client: any;
|
||||
|
||||
constructor() {
|
||||
@ -81,4 +82,4 @@ class BitcoinApi {
|
||||
}
|
||||
}
|
||||
|
||||
export default new BitcoinApi();
|
||||
export default BitcoindApi;
|
99
backend/src/api/bitcoin/esplora-api.ts
Normal file
99
backend/src/api/bitcoin/esplora-api.ts
Normal file
@ -0,0 +1,99 @@
|
||||
const config = require('../../../mempool-config.json');
|
||||
import { ITransaction, IMempoolInfo, IBlock } from '../../interfaces';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
|
||||
class EsploraApi implements AbstractBitcoinApi {
|
||||
client: any;
|
||||
|
||||
constructor() {
|
||||
this.client = axios.create({
|
||||
baseURL: config.ESPLORA_API_URL,
|
||||
timeout: 15000,
|
||||
});
|
||||
}
|
||||
|
||||
getMempoolInfo(): Promise<IMempoolInfo> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const response: AxiosResponse = await this.client.get('/mempool');
|
||||
resolve({
|
||||
size: response.data.count,
|
||||
bytes: response.data.vsize,
|
||||
});
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getRawMempool(): Promise<ITransaction['txid'][]> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const response: AxiosResponse = await this.client.get('/mempool/txids');
|
||||
resolve(response.data);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getRawTransaction(txId: string): Promise<ITransaction> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const response: AxiosResponse = await this.client.get('/tx/' + txId);
|
||||
|
||||
response.data.vsize = response.data.size;
|
||||
response.data.size = response.data.weight;
|
||||
response.data.fee = response.data.fee / 100000000;
|
||||
|
||||
resolve(response.data);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getBlockCount(): Promise<number> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const response: AxiosResponse = await this.client.get('/blocks/tip/height');
|
||||
resolve(response.data);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getBlock(hash: string): Promise<IBlock> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const blockInfo: AxiosResponse = await this.client.get('/block/' + hash);
|
||||
const blockTxs: AxiosResponse = await this.client.get('/block/' + hash + '/txids');
|
||||
|
||||
const block = blockInfo.data;
|
||||
block.hash = hash;
|
||||
block.nTx = block.tx_count;
|
||||
block.time = block.timestamp;
|
||||
block.tx = blockTxs.data;
|
||||
|
||||
resolve(block);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getBlockHash(height: number): Promise<string> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const response: AxiosResponse = await this.client.get('/block-height/' + height);
|
||||
resolve(response.data);
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default EsploraApi;
|
@ -1,5 +1,5 @@
|
||||
const config = require('../../mempool-config.json');
|
||||
import bitcoinApi from './bitcoin-api-wrapper';
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { DB } from '../database';
|
||||
import { IBlock, ITransaction } from '../interfaces';
|
||||
import memPool from './mempool';
|
||||
@ -56,7 +56,7 @@ class Blocks {
|
||||
block = storedBlock;
|
||||
} else {
|
||||
const blockHash = await bitcoinApi.getBlockHash(this.currentBlockHeight);
|
||||
block = await bitcoinApi.getBlock(blockHash, 1);
|
||||
block = await bitcoinApi.getBlock(blockHash);
|
||||
|
||||
const coinbase = await memPool.getRawTransaction(block.tx[0], true);
|
||||
if (coinbase && coinbase.totalOut) {
|
||||
@ -74,6 +74,7 @@ class Blocks {
|
||||
transactions.push(mempool[block.tx[i]]);
|
||||
found++;
|
||||
} else {
|
||||
console.log(`Fetching block tx ${i} of ${block.tx.length}`);
|
||||
const tx = await memPool.getRawTransaction(block.tx[i]);
|
||||
if (tx) {
|
||||
transactions.push(tx);
|
||||
|
@ -1,5 +1,5 @@
|
||||
const config = require('../../mempool-config.json');
|
||||
import bitcoinApi from './bitcoin-api-wrapper';
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { ITransaction, IMempoolInfo, IMempool } from '../interfaces';
|
||||
|
||||
class Mempool {
|
||||
@ -52,6 +52,15 @@ class Mempool {
|
||||
public async getRawTransaction(txId: string, isCoinbase = false): Promise<ITransaction | false> {
|
||||
try {
|
||||
const transaction = await bitcoinApi.getRawTransaction(txId);
|
||||
|
||||
let totalOut = 0;
|
||||
transaction.vout.forEach((output) => totalOut += output.value);
|
||||
|
||||
if (config.BACKEND_API === 'esplora') {
|
||||
transaction.feePerWeightUnit = (transaction.fee * 100000000) / (transaction.vsize * 4) || 0;
|
||||
transaction.feePerVsize = (transaction.fee * 100000000) / (transaction.vsize) || 0;
|
||||
transaction.totalOut = totalOut / 100000000;
|
||||
} else {
|
||||
let totalIn = 0;
|
||||
if (!isCoinbase) {
|
||||
for (let i = 0; i < transaction.vin.length; i++) {
|
||||
@ -64,8 +73,6 @@ class Mempool {
|
||||
}
|
||||
}
|
||||
}
|
||||
let totalOut = 0;
|
||||
transaction.vout.forEach((output) => totalOut += output.value);
|
||||
|
||||
if (totalIn > totalOut) {
|
||||
transaction.fee = parseFloat((totalIn - totalOut).toFixed(8));
|
||||
@ -78,6 +85,7 @@ class Mempool {
|
||||
console.log('Minus fee error!');
|
||||
}
|
||||
transaction.totalOut = totalOut;
|
||||
}
|
||||
return transaction;
|
||||
} catch (e) {
|
||||
console.log(txId + ' not found');
|
||||
|
@ -6,7 +6,7 @@ import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as WebSocket from 'ws';
|
||||
|
||||
import bitcoinApi from './api/bitcoin-api-wrapper';
|
||||
import bitcoinApi from './api/bitcoin/bitcoin-api-factory';
|
||||
import diskCache from './api/disk-cache';
|
||||
import memPool from './api/mempool';
|
||||
import blocks from './api/blocks';
|
||||
@ -55,7 +55,7 @@ class MempoolSpace {
|
||||
port: 8999
|
||||
};
|
||||
this.server.listen(opts, () => {
|
||||
console.log(`Server started on ${opts.host}:${opts.port})`);
|
||||
console.log(`Server started on ${opts.host}:${opts.port}`);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
export interface IMempoolInfo {
|
||||
size: number;
|
||||
bytes: number;
|
||||
usage: number;
|
||||
maxmempool: number;
|
||||
mempoolminfee: number;
|
||||
minrelaytxfee: number;
|
||||
usage?: number;
|
||||
maxmempool?: number;
|
||||
mempoolminfee?: number;
|
||||
minrelaytxfee?: number;
|
||||
}
|
||||
|
||||
export interface ITransaction {
|
||||
|
1187
backend/yarn.lock
Normal file
1187
backend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,9 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
ngOnInit() {
|
||||
this.blocksSubscription = this.memPoolService.blocks$
|
||||
.subscribe((block) => {
|
||||
if (this.blocks.some((b) => b.height === block.height)) {
|
||||
return;
|
||||
}
|
||||
this.blocks.unshift(block);
|
||||
this.blocks = this.blocks.slice(0, 8);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user