mirror of
https://github.com/mempool/mempool.git
synced 2025-01-18 05:12:35 +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_PORT": 8332,
|
||||||
"BITCOIN_NODE_USER": "",
|
"BITCOIN_NODE_USER": "",
|
||||||
"BITCOIN_NODE_PASS": "",
|
"BITCOIN_NODE_PASS": "",
|
||||||
|
"BACKEND_API": "bitcoind",
|
||||||
|
"ESPLORA_API_URL": "https://www.blockstream.info/api",
|
||||||
"TX_PER_SECOND_SPAN_SECONDS": 150
|
"TX_PER_SECOND_SPAN_SECONDS": 150
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^0.19.0",
|
||||||
"bitcoin": "^3.0.1",
|
"bitcoin": "^3.0.1",
|
||||||
"compression": "^1.7.3",
|
"compression": "^1.7.3",
|
||||||
"express": "^4.16.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 * 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;
|
client: any;
|
||||||
|
|
||||||
constructor() {
|
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');
|
const config = require('../../mempool-config.json');
|
||||||
import bitcoinApi from './bitcoin-api-wrapper';
|
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||||
import { DB } from '../database';
|
import { DB } from '../database';
|
||||||
import { IBlock, ITransaction } from '../interfaces';
|
import { IBlock, ITransaction } from '../interfaces';
|
||||||
import memPool from './mempool';
|
import memPool from './mempool';
|
||||||
@ -56,7 +56,7 @@ class Blocks {
|
|||||||
block = storedBlock;
|
block = storedBlock;
|
||||||
} else {
|
} else {
|
||||||
const blockHash = await bitcoinApi.getBlockHash(this.currentBlockHeight);
|
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);
|
const coinbase = await memPool.getRawTransaction(block.tx[0], true);
|
||||||
if (coinbase && coinbase.totalOut) {
|
if (coinbase && coinbase.totalOut) {
|
||||||
@ -74,6 +74,7 @@ class Blocks {
|
|||||||
transactions.push(mempool[block.tx[i]]);
|
transactions.push(mempool[block.tx[i]]);
|
||||||
found++;
|
found++;
|
||||||
} else {
|
} else {
|
||||||
|
console.log(`Fetching block tx ${i} of ${block.tx.length}`);
|
||||||
const tx = await memPool.getRawTransaction(block.tx[i]);
|
const tx = await memPool.getRawTransaction(block.tx[i]);
|
||||||
if (tx) {
|
if (tx) {
|
||||||
transactions.push(tx);
|
transactions.push(tx);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const config = require('../../mempool-config.json');
|
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';
|
import { ITransaction, IMempoolInfo, IMempool } from '../interfaces';
|
||||||
|
|
||||||
class Mempool {
|
class Mempool {
|
||||||
@ -52,6 +52,15 @@ class Mempool {
|
|||||||
public async getRawTransaction(txId: string, isCoinbase = false): Promise<ITransaction | false> {
|
public async getRawTransaction(txId: string, isCoinbase = false): Promise<ITransaction | false> {
|
||||||
try {
|
try {
|
||||||
const transaction = await bitcoinApi.getRawTransaction(txId);
|
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;
|
let totalIn = 0;
|
||||||
if (!isCoinbase) {
|
if (!isCoinbase) {
|
||||||
for (let i = 0; i < transaction.vin.length; i++) {
|
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) {
|
if (totalIn > totalOut) {
|
||||||
transaction.fee = parseFloat((totalIn - totalOut).toFixed(8));
|
transaction.fee = parseFloat((totalIn - totalOut).toFixed(8));
|
||||||
@ -78,6 +85,7 @@ class Mempool {
|
|||||||
console.log('Minus fee error!');
|
console.log('Minus fee error!');
|
||||||
}
|
}
|
||||||
transaction.totalOut = totalOut;
|
transaction.totalOut = totalOut;
|
||||||
|
}
|
||||||
return transaction;
|
return transaction;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(txId + ' not found');
|
console.log(txId + ' not found');
|
||||||
|
@ -6,7 +6,7 @@ import * as http from 'http';
|
|||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
import * as WebSocket from 'ws';
|
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 diskCache from './api/disk-cache';
|
||||||
import memPool from './api/mempool';
|
import memPool from './api/mempool';
|
||||||
import blocks from './api/blocks';
|
import blocks from './api/blocks';
|
||||||
@ -55,7 +55,7 @@ class MempoolSpace {
|
|||||||
port: 8999
|
port: 8999
|
||||||
};
|
};
|
||||||
this.server.listen(opts, () => {
|
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 {
|
export interface IMempoolInfo {
|
||||||
size: number;
|
size: number;
|
||||||
bytes: number;
|
bytes: number;
|
||||||
usage: number;
|
usage?: number;
|
||||||
maxmempool: number;
|
maxmempool?: number;
|
||||||
mempoolminfee: number;
|
mempoolminfee?: number;
|
||||||
minrelaytxfee: number;
|
minrelaytxfee?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ITransaction {
|
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() {
|
ngOnInit() {
|
||||||
this.blocksSubscription = this.memPoolService.blocks$
|
this.blocksSubscription = this.memPoolService.blocks$
|
||||||
.subscribe((block) => {
|
.subscribe((block) => {
|
||||||
|
if (this.blocks.some((b) => b.height === block.height)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.blocks.unshift(block);
|
this.blocks.unshift(block);
|
||||||
this.blocks = this.blocks.slice(0, 8);
|
this.blocks = this.blocks.slice(0, 8);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user