mirror of
https://github.com/mempool/mempool.git
synced 2025-01-18 21:32:55 +01:00
Merge branch 'master' into log-priority
This commit is contained in:
commit
5c4f6d6ada
35
README.md
35
README.md
@ -88,7 +88,7 @@ JSON:
|
||||
"BLOCK_WEIGHT_UNITS": 4000000,
|
||||
"INITIAL_BLOCKS_AMOUNT": 8,
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||
"PRICE_FEED_UPDATE_INTERVAL": 3600,
|
||||
"PRICE_FEED_UPDATE_INTERVAL": 600,
|
||||
"USE_SECOND_NODE_FOR_MINFEE": false,
|
||||
"EXTERNAL_ASSETS": [],
|
||||
"STDOUT_LOG_MIN_PRIORITY": "debug"
|
||||
@ -247,6 +247,39 @@ docker-compose overrides:
|
||||
BISQ_DATA_PATH: ""
|
||||
```
|
||||
|
||||
JSON:
|
||||
```
|
||||
"SOCKS5PROXY": {
|
||||
"ENABLED": false,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": "9050",
|
||||
"USERNAME": "",
|
||||
"PASSWORD": ""
|
||||
}
|
||||
```
|
||||
|
||||
docker-compose overrides:
|
||||
```
|
||||
SOCKS5PROXY_ENABLED: ""
|
||||
SOCKS5PROXY_HOST: ""
|
||||
SOCKS5PROXY_PORT: ""
|
||||
SOCKS5PROXY_USERNAME: ""
|
||||
SOCKS5PROXY_PASSWORD: ""
|
||||
```
|
||||
|
||||
JSON:
|
||||
```
|
||||
"PRICE_DATA_SERVER": {
|
||||
"TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices",
|
||||
"CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices"
|
||||
}
|
||||
```
|
||||
|
||||
docker-compose overrides:
|
||||
```
|
||||
PRICE_DATA_SERVER_TOR_URL: ""
|
||||
PRICE_DATA_SERVER_CLEARNET_URL: ""
|
||||
```
|
||||
|
||||
# Manual Installation
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
"INITIAL_BLOCKS_AMOUNT": 8,
|
||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||
"INDEXING_BLOCKS_AMOUNT": 1100,
|
||||
"PRICE_FEED_UPDATE_INTERVAL": 3600,
|
||||
"PRICE_FEED_UPDATE_INTERVAL": 600,
|
||||
"USE_SECOND_NODE_FOR_MINFEE": false,
|
||||
"EXTERNAL_ASSETS": [
|
||||
"https://mempool.space/resources/pools.json"
|
||||
@ -62,5 +62,16 @@
|
||||
"BISQ": {
|
||||
"ENABLED": false,
|
||||
"DATA_PATH": "/bisq/statsnode-data/btc_mainnet/db"
|
||||
},
|
||||
"SOCKS5PROXY": {
|
||||
"ENABLED": false,
|
||||
"HOST": "127.0.0.1",
|
||||
"PORT": 9050,
|
||||
"USERNAME": "",
|
||||
"PASSWORD": ""
|
||||
},
|
||||
"PRICE_DATA_SERVER": {
|
||||
"TOR_URL": "http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices",
|
||||
"CLEARNET_URL": "https://price.bisq.wiz.biz/getAllMarketPrices"
|
||||
}
|
||||
}
|
||||
|
161
backend/package-lock.json
generated
161
backend/package-lock.json
generated
@ -19,6 +19,7 @@
|
||||
"locutus": "^2.0.12",
|
||||
"mysql2": "2.3.3",
|
||||
"node-worker-threads-pool": "^1.4.3",
|
||||
"socks-proxy-agent": "^6.1.1",
|
||||
"typescript": "4.4.4",
|
||||
"ws": "8.3.0"
|
||||
},
|
||||
@ -181,6 +182,38 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"dependencies": {
|
||||
"debug": "4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base/node_modules/debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/agent-base/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
@ -731,6 +764,11 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"node_modules/ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
@ -1189,6 +1227,62 @@
|
||||
"sha.js": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
|
||||
"engines": {
|
||||
"node": ">= 6.0.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socks": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz",
|
||||
"integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==",
|
||||
"dependencies": {
|
||||
"ip": "^1.1.5",
|
||||
"smart-buffer": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0",
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/socks-proxy-agent": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz",
|
||||
"integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==",
|
||||
"dependencies": {
|
||||
"agent-base": "^6.0.2",
|
||||
"debug": "^4.3.1",
|
||||
"socks": "^2.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/socks-proxy-agent/node_modules/debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/socks-proxy-agent/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
@ -1538,6 +1632,29 @@
|
||||
"negotiator": "0.6.2"
|
||||
}
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
|
||||
"requires": {
|
||||
"debug": "4"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
@ -1992,6 +2109,11 @@
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
|
||||
},
|
||||
"ipaddr.js": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
|
||||
@ -2352,6 +2474,45 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"smart-buffer": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
|
||||
"integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="
|
||||
},
|
||||
"socks": {
|
||||
"version": "2.6.1",
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz",
|
||||
"integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==",
|
||||
"requires": {
|
||||
"ip": "^1.1.5",
|
||||
"smart-buffer": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"socks-proxy-agent": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz",
|
||||
"integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==",
|
||||
"requires": {
|
||||
"agent-base": "^6.0.2",
|
||||
"debug": "^4.3.1",
|
||||
"socks": "^2.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
|
@ -38,6 +38,7 @@
|
||||
"locutus": "^2.0.12",
|
||||
"mysql2": "2.3.3",
|
||||
"node-worker-threads-pool": "^1.4.3",
|
||||
"socks-proxy-agent": "^6.1.1",
|
||||
"typescript": "4.4.4",
|
||||
"ws": "8.3.0"
|
||||
},
|
||||
|
@ -96,14 +96,20 @@ class Blocks {
|
||||
*/
|
||||
private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended {
|
||||
const blockExtended: BlockExtended = Object.assign({}, block);
|
||||
blockExtended.reward = transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0);
|
||||
blockExtended.coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
|
||||
|
||||
blockExtended.extras = {
|
||||
reward: transactions[0].vout.reduce((acc, curr) => acc + curr.value, 0),
|
||||
coinbaseTx: transactionUtils.stripCoinbaseTransaction(transactions[0]),
|
||||
};
|
||||
|
||||
const transactionsTmp = [...transactions];
|
||||
transactionsTmp.shift();
|
||||
transactionsTmp.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize);
|
||||
blockExtended.medianFee = transactionsTmp.length > 0 ? Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0;
|
||||
blockExtended.feeRange = transactionsTmp.length > 0 ? Common.getFeesInRange(transactionsTmp, 8) : [0, 0];
|
||||
|
||||
blockExtended.extras.medianFee = transactionsTmp.length > 0 ?
|
||||
Common.median(transactionsTmp.map((tx) => tx.effectiveFeePerVsize)) : 0;
|
||||
blockExtended.extras.feeRange = transactionsTmp.length > 0 ?
|
||||
Common.getFeesInRange(transactionsTmp, 8) : [0, 0];
|
||||
|
||||
return blockExtended;
|
||||
}
|
||||
@ -197,7 +203,14 @@ class Blocks {
|
||||
const block = await bitcoinApi.$getBlock(blockHash);
|
||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
|
||||
const blockExtended = this.getBlockExtended(block, transactions);
|
||||
const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
|
||||
|
||||
let miner: PoolTag;
|
||||
if (blockExtended?.extras?.coinbaseTx) {
|
||||
miner = await this.$findBlockMiner(blockExtended.extras.coinbaseTx);
|
||||
} else {
|
||||
miner = await poolsRepository.$getUnknownPool();
|
||||
}
|
||||
|
||||
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
||||
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
|
||||
} catch (e) {
|
||||
@ -262,7 +275,12 @@ class Blocks {
|
||||
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
|
||||
|
||||
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === true) {
|
||||
const miner = await this.$findBlockMiner(blockExtended.coinbaseTx);
|
||||
let miner: PoolTag;
|
||||
if (blockExtended?.extras?.coinbaseTx) {
|
||||
miner = await this.$findBlockMiner(blockExtended.extras.coinbaseTx);
|
||||
} else {
|
||||
miner = await poolsRepository.$getUnknownPool();
|
||||
}
|
||||
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
import logger from '../logger';
|
||||
import axios from 'axios';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import { IConversionRates } from '../mempool.interfaces';
|
||||
import config from '../config';
|
||||
import backendInfo from './backend-info';
|
||||
import { SocksProxyAgent } from 'socks-proxy-agent';
|
||||
|
||||
class FiatConversion {
|
||||
private conversionRates: IConversionRates = {
|
||||
@ -17,6 +19,11 @@ class FiatConversion {
|
||||
|
||||
public startService() {
|
||||
logger.info('Starting currency rates service');
|
||||
if (config.SOCKS5PROXY.ENABLED) {
|
||||
logger.info(`Currency rates service will be queried over the Tor network using ${config.PRICE_DATA_SERVER.TOR_URL}`);
|
||||
} else {
|
||||
logger.info(`Currency rates service will be queried over clearnet using ${config.PRICE_DATA_SERVER.CLEARNET_URL}`);
|
||||
}
|
||||
setInterval(this.updateCurrency.bind(this), 1000 * config.MEMPOOL.PRICE_FEED_UPDATE_INTERVAL);
|
||||
this.updateCurrency();
|
||||
}
|
||||
@ -26,12 +33,43 @@ class FiatConversion {
|
||||
}
|
||||
|
||||
private async updateCurrency(): Promise<void> {
|
||||
const headers = { 'User-Agent': `mempool/v${backendInfo.getBackendInfo().version}` };
|
||||
let fiatConversionUrl: string;
|
||||
let response: AxiosResponse;
|
||||
|
||||
try {
|
||||
const response = await axios.get('https://price.bisq.wiz.biz/getAllMarketPrices', { timeout: 10000 });
|
||||
if (config.SOCKS5PROXY.ENABLED) {
|
||||
let socksOptions: any = {
|
||||
agentOptions: {
|
||||
keepAlive: true,
|
||||
},
|
||||
host: config.SOCKS5PROXY.HOST,
|
||||
port: config.SOCKS5PROXY.PORT
|
||||
};
|
||||
|
||||
if (config.SOCKS5PROXY.USERNAME && config.SOCKS5PROXY.PASSWORD) {
|
||||
socksOptions.username = config.SOCKS5PROXY.USERNAME;
|
||||
socksOptions.password = config.SOCKS5PROXY.PASSWORD;
|
||||
}
|
||||
|
||||
const agent = new SocksProxyAgent(socksOptions);
|
||||
fiatConversionUrl = config.PRICE_DATA_SERVER.TOR_URL;
|
||||
logger.debug('Querying currency rates service...');
|
||||
response = await axios.get(fiatConversionUrl, { httpAgent: agent, headers: headers, timeout: 30000 });
|
||||
} else {
|
||||
fiatConversionUrl = config.PRICE_DATA_SERVER.CLEARNET_URL;
|
||||
logger.debug('Querying currency rates service...');
|
||||
response = await axios.get(fiatConversionUrl, { headers: headers, timeout: 10000 });
|
||||
}
|
||||
|
||||
const usd = response.data.data.find((item: any) => item.currencyCode === 'USD');
|
||||
|
||||
this.conversionRates = {
|
||||
'USD': usd.price,
|
||||
};
|
||||
|
||||
logger.debug(`USD Conversion Rate: ${usd.price}`);
|
||||
|
||||
if (this.ratesChangedCallback) {
|
||||
this.ratesChangedCallback(this.conversionRates);
|
||||
}
|
||||
|
@ -380,7 +380,9 @@ class WebsocketHandler {
|
||||
mBlocks = mempoolBlocks.getMempoolBlocks();
|
||||
}
|
||||
|
||||
block.matchRate = matchRate;
|
||||
if (block.extras) {
|
||||
block.extras.matchRate = matchRate;
|
||||
}
|
||||
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
|
@ -63,6 +63,17 @@ interface IConfig {
|
||||
ENABLED: boolean;
|
||||
DATA_PATH: string;
|
||||
};
|
||||
SOCKS5PROXY: {
|
||||
ENABLED: boolean;
|
||||
HOST: string;
|
||||
PORT: number;
|
||||
USERNAME: string;
|
||||
PASSWORD: string;
|
||||
};
|
||||
PRICE_DATA_SERVER: {
|
||||
TOR_URL: string;
|
||||
CLEARNET_URL: string;
|
||||
};
|
||||
}
|
||||
|
||||
const defaults: IConfig = {
|
||||
@ -80,7 +91,7 @@ const defaults: IConfig = {
|
||||
'INITIAL_BLOCKS_AMOUNT': 8,
|
||||
'MEMPOOL_BLOCKS_AMOUNT': 8,
|
||||
'INDEXING_BLOCKS_AMOUNT': 1100, // 0 = disable indexing, -1 = index all blocks
|
||||
'PRICE_FEED_UPDATE_INTERVAL': 3600,
|
||||
'PRICE_FEED_UPDATE_INTERVAL': 600,
|
||||
'USE_SECOND_NODE_FOR_MINFEE': false,
|
||||
'EXTERNAL_ASSETS': [
|
||||
'https://mempool.space/resources/pools.json'
|
||||
@ -130,6 +141,17 @@ const defaults: IConfig = {
|
||||
'ENABLED': false,
|
||||
'DATA_PATH': '/bisq/statsnode-data/btc_mainnet/db'
|
||||
},
|
||||
'SOCKS5PROXY': {
|
||||
'ENABLED': false,
|
||||
'HOST': '127.0.0.1',
|
||||
'PORT': 9050,
|
||||
'USERNAME': '',
|
||||
'PASSWORD': ''
|
||||
},
|
||||
"PRICE_DATA_SERVER": {
|
||||
'TOR_URL': 'http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices',
|
||||
'CLEARNET_URL': 'https://price.bisq.wiz.biz/getAllMarketPrices'
|
||||
}
|
||||
};
|
||||
|
||||
class Config implements IConfig {
|
||||
@ -142,6 +164,8 @@ class Config implements IConfig {
|
||||
SYSLOG: IConfig['SYSLOG'];
|
||||
STATISTICS: IConfig['STATISTICS'];
|
||||
BISQ: IConfig['BISQ'];
|
||||
SOCKS5PROXY: IConfig['SOCKS5PROXY'];
|
||||
PRICE_DATA_SERVER: IConfig['PRICE_DATA_SERVER'];
|
||||
|
||||
constructor() {
|
||||
const configs = this.merge(configFile, defaults);
|
||||
@ -154,6 +178,8 @@ class Config implements IConfig {
|
||||
this.SYSLOG = configs.SYSLOG;
|
||||
this.STATISTICS = configs.STATISTICS;
|
||||
this.BISQ = configs.BISQ;
|
||||
this.SOCKS5PROXY = configs.SOCKS5PROXY;
|
||||
this.PRICE_DATA_SERVER = configs.PRICE_DATA_SERVER;
|
||||
}
|
||||
|
||||
merge = (...objects: object[]): IConfig => {
|
||||
|
@ -76,7 +76,8 @@ export interface TransactionStripped {
|
||||
vsize: number;
|
||||
value: number;
|
||||
}
|
||||
export interface BlockExtended extends IEsploraApi.Block {
|
||||
|
||||
export interface BlockExtension {
|
||||
medianFee?: number;
|
||||
feeRange?: number[];
|
||||
reward?: number;
|
||||
@ -84,6 +85,10 @@ export interface BlockExtended extends IEsploraApi.Block {
|
||||
matchRate?: number;
|
||||
}
|
||||
|
||||
export interface BlockExtended extends IEsploraApi.Block {
|
||||
extras?: BlockExtension;
|
||||
}
|
||||
|
||||
export interface TransactionMinerInfo {
|
||||
vin: VinStrippedToScriptsig[];
|
||||
vout: VoutStrippedToScriptPubkey[];
|
||||
|
@ -31,9 +31,18 @@ class BlocksRepository {
|
||||
)`;
|
||||
|
||||
const params: any[] = [
|
||||
block.height, blockHash, block.timestamp, block.size,
|
||||
block.weight, block.tx_count, coinbaseHex ? coinbaseHex : '', block.difficulty,
|
||||
poolTag.id, 0, '[]', block.medianFee,
|
||||
block.height,
|
||||
blockHash,
|
||||
block.timestamp,
|
||||
block.size,
|
||||
block.weight,
|
||||
block.tx_count,
|
||||
coinbaseHex ? coinbaseHex : '',
|
||||
block.difficulty,
|
||||
poolTag.id,
|
||||
0,
|
||||
'[]',
|
||||
block.extras ? block.extras.medianFee : 0,
|
||||
];
|
||||
|
||||
await connection.query(query, params);
|
||||
|
@ -10,7 +10,8 @@
|
||||
"moduleResolution": "node",
|
||||
"typeRoots": [
|
||||
"node_modules/@types"
|
||||
]
|
||||
],
|
||||
"allowSyntheticDefaultImports": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts"
|
||||
|
@ -59,5 +59,16 @@
|
||||
"BISQ": {
|
||||
"ENABLED": __BISQ_ENABLED__,
|
||||
"DATA_PATH": "__BISQ_DATA_PATH__"
|
||||
},
|
||||
"SOCKS5PROXY": {
|
||||
"ENABLED": __SOCKS5PROXY_ENABLED__,
|
||||
"HOST": "__SOCKS5PROXY_HOST__",
|
||||
"PORT": "__SOCKS5PROXY_PORT__",
|
||||
"USERNAME": "__SOCKS5PROXY_USERNAME__",
|
||||
"PASSWORD": "__SOCKS5PROXY_PASSWORD__"
|
||||
},
|
||||
"PRICE_DATA_SERVER": {
|
||||
"TOR_URL": "__PRICE_DATA_SERVER_TOR_URL__",
|
||||
"CLEARNET_URL": "__PRICE_DATA_SERVER_CLEARNET_URL__"
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ __MEMPOOL_BLOCK_WEIGHT_UNITS__=${MEMPOOL_BLOCK_WEIGHT_UNITS:=4000000}
|
||||
__MEMPOOL_INITIAL_BLOCKS_AMOUNT__=${MEMPOOL_INITIAL_BLOCKS_AMOUNT:=8}
|
||||
__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__=${MEMPOOL_MEMPOOL_BLOCKS_AMOUNT:=8}
|
||||
__MEMPOOL_INDEXING_BLOCKS_AMOUNT__=${MEMPOOL_INDEXING_BLOCKS_AMOUNT:=1100}
|
||||
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=3600}
|
||||
__MEMPOOL_PRICE_FEED_UPDATE_INTERVAL__=${MEMPOOL_PRICE_FEED_UPDATE_INTERVAL:=600}
|
||||
__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__=${MEMPOOL_USE_SECOND_NODE_FOR_MINFEE:=false}
|
||||
__MEMPOOL_EXTERNAL_ASSETS__=${MEMPOOL_EXTERNAL_ASSETS:=[]}
|
||||
__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=debug}
|
||||
@ -62,6 +62,17 @@ __STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__=${STATISTICS_TX_PER_SECOND_SAMPLE_PER
|
||||
__BISQ_ENABLED__=${BISQ_ENABLED:=false}
|
||||
__BISQ_DATA_PATH__=${BISQ_DATA_PATH:=/bisq/statsnode-data/btc_mainnet/db}
|
||||
|
||||
# SOCKS5PROXY
|
||||
__SOCKS5PROXY_ENABLED__=${SOCKS5PROXY_ENABLED:=false}
|
||||
__SOCKS5PROXY_HOST__=${SOCKS5PROXY_HOST:=localhost}
|
||||
__SOCKS5PROXY_PORT__=${SOCKS5PROXY_PORT:=9050}
|
||||
__SOCKS5PROXY_USERNAME__=${SOCKS5PROXY_USERNAME:=""}
|
||||
__SOCKS5PROXY_PASSWORD__=${SOCKS5PROXY_PASSWORD:=""}
|
||||
|
||||
# PRICE_DATA_SERVER
|
||||
__PRICE_DATA_SERVER_TOR_URL__=${PRICE_DATA_SERVER_TOR_URL:=http://wizpriceje6q5tdrxkyiazsgu7irquiqjy2dptezqhrtu7l2qelqktid.onion/getAllMarketPrices}
|
||||
__PRICE_DATA_SERVER_CLEARNET_URL__=${PRICE_DATA_SERVER_CLEARNET_URL:=https://price.bisq.wiz.biz/getAllMarketPrices}
|
||||
|
||||
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
||||
|
||||
sed -i "s/__MEMPOOL_NETWORK__/${__MEMPOOL_NETWORK__}/g" mempool-config.json
|
||||
@ -117,4 +128,13 @@ sed -i "s/__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__/${__STATISTICS_TX_PER_SECON
|
||||
sed -i "s/__BISQ_ENABLED__/${__BISQ_ENABLED__}/g" mempool-config.json
|
||||
sed -i "s!__BISQ_DATA_PATH__!${__BISQ_DATA_PATH__}!g" mempool-config.json
|
||||
|
||||
sed -i "s/__SOCKS5PROXY_ENABLED__/${__SOCKS5PROXY_ENABLED__}/g" mempool-config.json
|
||||
sed -i "s/__SOCKS5PROXY_HOST__/${__SOCKS5PROXY_HOST__}/g" mempool-config.json
|
||||
sed -i "s/__SOCKS5PROXY_PORT__/${__SOCKS5PROXY_PORT__}/g" mempool-config.json
|
||||
sed -i "s/__SOCKS5PROXY_USERNAME__/${__SOCKS5PROXY_USERNAME__}/g" mempool-config.json
|
||||
sed -i "s/__SOCKS5PROXY_PASSWORD__/${__SOCKS5PROXY_PASSWORD__}/g" mempool-config.json
|
||||
|
||||
sed -i "s!__PRICE_DATA_SERVER_TOR_URL__!${__PRICE_DATA_SERVER_TOR_URL__}!g" mempool-config.json
|
||||
sed -i "s!__PRICE_DATA_SERVER_CLEARNET_URL__!${__PRICE_DATA_SERVER_CLEARNET_URL__}!g" mempool-config.json
|
||||
|
||||
node /backend/dist/index.js
|
||||
|
@ -218,6 +218,10 @@
|
||||
"proxyConfig": "proxy.conf.local.js",
|
||||
"verbose": true
|
||||
},
|
||||
"mixed": {
|
||||
"proxyConfig": "proxy.conf.mixed.js",
|
||||
"verbose": true
|
||||
},
|
||||
"staging": {
|
||||
"proxyConfig": "proxy.conf.js",
|
||||
"disableHostCheck": true,
|
||||
|
@ -30,6 +30,7 @@
|
||||
"start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local",
|
||||
"start:stg": "npm run generate-config && npm run sync-assets-dev && ng serve -c staging",
|
||||
"start:local-prod": "npm run generate-config && npm run sync-assets-dev && ng serve -c local-prod",
|
||||
"start:mixed": "npm run generate-config && npm run sync-assets-dev && ng serve -c mixed",
|
||||
"build": "npm run generate-config && ng build --configuration production --localize && npm run sync-assets && npm run build-mempool.js",
|
||||
"sync-assets": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
|
||||
"sync-assets-dev": "node sync-assets.js dev",
|
||||
|
@ -1,17 +1,13 @@
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
let PROXY_CONFIG = require('./proxy.conf.js');
|
||||
const BACKEND_CONFIG_FILE_NAME = '../backend/mempool-config.json';
|
||||
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||
|
||||
let backendConfigContent;
|
||||
let frontendConfigContent;
|
||||
let configContent;
|
||||
|
||||
// Read frontend config
|
||||
try {
|
||||
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
|
||||
frontendConfigContent = JSON.parse(rawConfig);
|
||||
configContent = JSON.parse(rawConfig);
|
||||
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
@ -22,51 +18,88 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
// Read backend config
|
||||
try {
|
||||
const rawConfig = fs.readFileSync(BACKEND_CONFIG_FILE_NAME);
|
||||
backendConfigContent = JSON.parse(rawConfig);
|
||||
console.log(`${BACKEND_CONFIG_FILE_NAME} file found, using provided config`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.code !== 'ENOENT') {
|
||||
throw new Error(e);
|
||||
} else {
|
||||
console.log(`${BACKEND_CONFIG_FILE_NAME} file not found, using default config`);
|
||||
let PROXY_CONFIG = [];
|
||||
|
||||
if (configContent && configContent.BASE_MODULE === 'liquid') {
|
||||
PROXY_CONFIG.push(...[
|
||||
{
|
||||
context: ['/liquid/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/liquid": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
context: ['/liquid/api/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/liquid/api/": "/api/v1/"
|
||||
},
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
if (configContent && configContent.BASE_MODULE === 'bisq') {
|
||||
PROXY_CONFIG.push(...[
|
||||
{
|
||||
context: ['/bisq/api/v1/ws'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/bisq": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
context: ['/bisq/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
{
|
||||
context: ['/bisq/api/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/bisq/api/": "/api/v1/bisq/"
|
||||
},
|
||||
}
|
||||
]);
|
||||
}
|
||||
|
||||
PROXY_CONFIG.push(...[
|
||||
{
|
||||
context: ['/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
{
|
||||
context: ['/api/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/api/": "/api/v1/"
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the "/api/**" entry from the default proxy config
|
||||
let localDevContext = PROXY_CONFIG[0].context
|
||||
|
||||
localDevContext.splice(PROXY_CONFIG[0].context.indexOf('/api/**'), 1);
|
||||
|
||||
PROXY_CONFIG[0].context = localDevContext;
|
||||
|
||||
// Change all targets to localhost
|
||||
PROXY_CONFIG.map(conf => conf.target = "http://localhost:8999");
|
||||
|
||||
// Add rules for local backend
|
||||
if (backendConfigContent) {
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/api/address/**', '/api/tx/**', '/api/block/**', '/api/blocks/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/api/": "/api/v1/"
|
||||
},
|
||||
});
|
||||
PROXY_CONFIG.push({
|
||||
context: ['/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000
|
||||
});
|
||||
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(PROXY_CONFIG);
|
||||
|
||||
|
99
frontend/proxy.conf.mixed.js
Normal file
99
frontend/proxy.conf.mixed.js
Normal file
@ -0,0 +1,99 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
|
||||
|
||||
let configContent;
|
||||
|
||||
// Read frontend config
|
||||
try {
|
||||
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME);
|
||||
configContent = JSON.parse(rawConfig);
|
||||
console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
if (e.code !== 'ENOENT') {
|
||||
throw new Error(e);
|
||||
} else {
|
||||
console.log(`${FRONTEND_CONFIG_FILE_NAME} file not found, using default config`);
|
||||
}
|
||||
}
|
||||
|
||||
let PROXY_CONFIG = [];
|
||||
|
||||
if (configContent && configContent.BASE_MODULE === 'liquid') {
|
||||
PROXY_CONFIG.push(...[
|
||||
{
|
||||
context: ['/liquid/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/liquid": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
context: ['/liquid/api/**'],
|
||||
target: `https://liquid.network`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
if (configContent && configContent.BASE_MODULE === 'bisq') {
|
||||
PROXY_CONFIG.push(...[
|
||||
{
|
||||
context: ['/bisq/api/v1/ws'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/bisq": ""
|
||||
},
|
||||
},
|
||||
{
|
||||
context: ['/bisq/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
{
|
||||
context: ['/bisq/api/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
pathRewrite: {
|
||||
"^/bisq/api/": "/api/v1/bisq/"
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
PROXY_CONFIG.push(...[
|
||||
{
|
||||
context: ['/api/v1/**'],
|
||||
target: `http://localhost:8999`,
|
||||
secure: false,
|
||||
ws: true,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
},
|
||||
{
|
||||
context: ['/api/**'],
|
||||
target: `https://mempool.space`,
|
||||
secure: false,
|
||||
changeOrigin: true,
|
||||
proxyTimeout: 30000,
|
||||
}
|
||||
]);
|
||||
|
||||
console.log(PROXY_CONFIG);
|
||||
|
||||
module.exports = PROXY_CONFIG;
|
@ -74,9 +74,9 @@
|
||||
<div class="col-sm">
|
||||
<table class="table table-borderless table-striped">
|
||||
<tbody>
|
||||
<tr *ngIf="block.medianFee !== undefined">
|
||||
<tr *ngIf="block?.extras?.medianFee != undefined">
|
||||
<td class="td-width" i18n="block.median-fee">Median fee</td>
|
||||
<td>~{{ block.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
<td>~{{ block?.extras?.medianFee | number:'1.0-0' }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span> <span class="fiat"><app-fiat [value]="block?.extras?.medianFee * 140" digitsInfo="1.2-2" i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes" placement="bottom"></app-fiat></span></td>
|
||||
</tr>
|
||||
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
|
||||
<tr>
|
||||
|
@ -3,12 +3,13 @@ import { Location } from '@angular/common';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { switchMap, tap, debounceTime, catchError, map } from 'rxjs/operators';
|
||||
import { Block, Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||
import { Transaction, Vout } from '../../interfaces/electrs.interface';
|
||||
import { Observable, of, Subscription } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { WebsocketService } from 'src/app/services/websocket.service';
|
||||
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
|
||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block',
|
||||
@ -17,13 +18,13 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
|
||||
})
|
||||
export class BlockComponent implements OnInit, OnDestroy {
|
||||
network = '';
|
||||
block: Block;
|
||||
block: BlockExtended;
|
||||
blockHeight: number;
|
||||
nextBlockHeight: number;
|
||||
blockHash: string;
|
||||
isLoadingBlock = true;
|
||||
latestBlock: Block;
|
||||
latestBlocks: Block[] = [];
|
||||
latestBlock: BlockExtended;
|
||||
latestBlocks: BlockExtended[] = [];
|
||||
transactions: Transaction[];
|
||||
isLoadingTransactions = true;
|
||||
error: any;
|
||||
@ -76,7 +77,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
|
||||
if (block.id === this.blockHash) {
|
||||
this.block = block;
|
||||
this.fees = block.reward / 100000000 - this.blockSubsidy;
|
||||
if (block?.extras?.reward != undefined) {
|
||||
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@ -108,7 +111,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
} else {
|
||||
this.isLoadingBlock = true;
|
||||
|
||||
let blockInCache: Block;
|
||||
let blockInCache: BlockExtended;
|
||||
if (isBlockHeight) {
|
||||
blockInCache = this.latestBlocks.find((block) => block.height === parseInt(blockHash, 10));
|
||||
if (blockInCache) {
|
||||
@ -134,7 +137,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
return this.electrsApiService.getBlock$(blockHash);
|
||||
}
|
||||
}),
|
||||
tap((block: Block) => {
|
||||
tap((block: BlockExtended) => {
|
||||
this.block = block;
|
||||
this.blockHeight = block.height;
|
||||
this.nextBlockHeight = block.height + 1;
|
||||
@ -142,12 +145,10 @@ export class BlockComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
|
||||
this.isLoadingBlock = false;
|
||||
if (block.coinbaseTx) {
|
||||
this.coinbaseTx = block.coinbaseTx;
|
||||
}
|
||||
this.coinbaseTx = block?.extras?.coinbaseTx;
|
||||
this.setBlockSubsidy();
|
||||
if (block.reward !== undefined) {
|
||||
this.fees = block.reward / 100000000 - this.blockSubsidy;
|
||||
if (block?.extras?.reward !== undefined) {
|
||||
this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
|
||||
}
|
||||
this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
|
||||
this.isLoadingTransactions = true;
|
||||
|
@ -8,10 +8,10 @@
|
||||
</div>
|
||||
<div class="block-body">
|
||||
<div class="fees">
|
||||
~{{ block.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
</div>
|
||||
<div class="fee-span">
|
||||
{{ block.feeRange[1] | number:feeRounding }} - {{ block.feeRange[block.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
{{ block?.extras?.feeRange[1] | number:feeRounding }} - {{ block?.extras?.feeRange[block?.extras?.feeRange.length - 1] | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
|
||||
</div>
|
||||
<div class="block-size" [innerHTML]="'‎' + (block.size | bytes: 2)"></div>
|
||||
<div class="transaction-count">
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Block } from 'src/app/interfaces/electrs.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { specialBlocks } from 'src/app/app.constants';
|
||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-blockchain-blocks',
|
||||
@ -14,8 +14,8 @@ import { specialBlocks } from 'src/app/app.constants';
|
||||
export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
specialBlocks = specialBlocks;
|
||||
network = '';
|
||||
blocks: Block[] = [];
|
||||
emptyBlocks: Block[] = this.mountEmptyBlocks();
|
||||
blocks: BlockExtended[] = [];
|
||||
emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
|
||||
markHeight: number;
|
||||
blocksSubscription: Subscription;
|
||||
networkSubscription: Subscription;
|
||||
@ -69,8 +69,8 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
this.blocks.unshift(block);
|
||||
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
|
||||
|
||||
if (this.blocksFilled && !this.tabHidden) {
|
||||
block.stage = block.matchRate >= 66 ? 1 : 2;
|
||||
if (this.blocksFilled && !this.tabHidden && block.extras) {
|
||||
block.extras.stage = block.extras.matchRate >= 66 ? 1 : 2;
|
||||
}
|
||||
|
||||
if (txConfirmed) {
|
||||
@ -143,16 +143,16 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
trackByBlocksFn(index: number, item: Block) {
|
||||
trackByBlocksFn(index: number, item: BlockExtended) {
|
||||
return item.height;
|
||||
}
|
||||
|
||||
getStyleForBlock(block: Block) {
|
||||
getStyleForBlock(block: BlockExtended) {
|
||||
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
|
||||
let addLeft = 0;
|
||||
|
||||
if (block.stage === 1) {
|
||||
block.stage = 2;
|
||||
if (block?.extras?.stage === 1) {
|
||||
block.extras.stage = 2;
|
||||
addLeft = -205;
|
||||
}
|
||||
|
||||
@ -167,11 +167,11 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
|
||||
};
|
||||
}
|
||||
|
||||
getStyleForEmptyBlock(block: Block) {
|
||||
getStyleForEmptyBlock(block: BlockExtended) {
|
||||
let addLeft = 0;
|
||||
|
||||
if (block.stage === 1) {
|
||||
block.stage = 2;
|
||||
if (block?.extras?.stage === 1) {
|
||||
block.extras.stage = 2;
|
||||
addLeft = -205;
|
||||
}
|
||||
|
||||
|
@ -153,7 +153,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.blockSubscription = this.stateService.blocks$
|
||||
.subscribe(([block]) => {
|
||||
if (block.matchRate >= 66 && !this.tabHidden) {
|
||||
if (block?.extras?.matchRate >= 66 && !this.tabHidden) {
|
||||
this.blockIndex++;
|
||||
}
|
||||
});
|
||||
|
@ -9,14 +9,14 @@ import {
|
||||
delay,
|
||||
map
|
||||
} from 'rxjs/operators';
|
||||
import { Transaction, Block } from '../../interfaces/electrs.interface';
|
||||
import { Transaction } from '../../interfaces/electrs.interface';
|
||||
import { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
import { AudioService } from 'src/app/services/audio.service';
|
||||
import { ApiService } from 'src/app/services/api.service';
|
||||
import { SeoService } from 'src/app/services/seo.service';
|
||||
import { CpfpInfo } from 'src/app/interfaces/node-api.interface';
|
||||
import { BlockExtended, CpfpInfo } from 'src/app/interfaces/node-api.interface';
|
||||
import { LiquidUnblinding } from './liquid-ublinding';
|
||||
|
||||
@Component({
|
||||
@ -33,7 +33,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
|
||||
error: any = undefined;
|
||||
errorUnblinded: any = undefined;
|
||||
waitingForTransaction = false;
|
||||
latestBlock: Block;
|
||||
latestBlock: BlockExtended;
|
||||
transactionTime = -1;
|
||||
subscription: Subscription;
|
||||
fetchCpfpSubscription: Subscription;
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, Output, EventEmitter } from '@angular/core';
|
||||
import { StateService } from '../../services/state.service';
|
||||
import { Observable, forkJoin } from 'rxjs';
|
||||
import { Block, Outspend, Transaction } from '../../interfaces/electrs.interface';
|
||||
import { Outspend, Transaction } from '../../interfaces/electrs.interface';
|
||||
import { ElectrsApiService } from '../../services/electrs-api.service';
|
||||
import { environment } from 'src/environments/environment';
|
||||
import { AssetsService } from 'src/app/services/assets.service';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transactions-list',
|
||||
@ -26,7 +27,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
|
||||
@Output() loadMore = new EventEmitter();
|
||||
|
||||
latestBlock$: Observable<Block>;
|
||||
latestBlock$: Observable<BlockExtended>;
|
||||
outspends: Outspend[] = [];
|
||||
assetsMinimal: any;
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Component, ChangeDetectionStrategy, OnChanges, Input, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core';
|
||||
import { Transaction, Block } from 'src/app/interfaces/electrs.interface';
|
||||
import { Transaction } from 'src/app/interfaces/electrs.interface';
|
||||
import { StateService } from 'src/app/services/state.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tx-fee-rating',
|
||||
@ -18,7 +19,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
overpaidTimes: number;
|
||||
feeRating: number;
|
||||
|
||||
blocks: Block[] = [];
|
||||
blocks: BlockExtended[] = [];
|
||||
|
||||
constructor(
|
||||
private stateService: StateService,
|
||||
@ -28,7 +29,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
ngOnInit() {
|
||||
this.blocksSubscription = this.stateService.blocks$.subscribe(([block]) => {
|
||||
this.blocks.push(block);
|
||||
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block.medianFee > 0) {
|
||||
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block?.extras?.medianFee > 0) {
|
||||
this.calculateRatings(block);
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
@ -42,7 +43,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
const foundBlock = this.blocks.find((b) => b.height === this.tx.status.block_height);
|
||||
if (foundBlock && foundBlock.medianFee > 0) {
|
||||
if (foundBlock && foundBlock?.extras?.medianFee > 0) {
|
||||
this.calculateRatings(foundBlock);
|
||||
}
|
||||
}
|
||||
@ -51,9 +52,9 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
|
||||
this.blocksSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
calculateRatings(block: Block) {
|
||||
calculateRatings(block: BlockExtended) {
|
||||
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
|
||||
this.medianFeeNeeded = block.medianFee;
|
||||
this.medianFeeNeeded = block?.extras?.medianFee;
|
||||
|
||||
// Block not filled
|
||||
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
||||
import { combineLatest, merge, Observable, of, timer } from 'rxjs';
|
||||
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
|
||||
import { Block } from '../interfaces/electrs.interface';
|
||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { ApiService } from '../services/api.service';
|
||||
import { StateService } from '../services/state.service';
|
||||
@ -40,7 +39,7 @@ export class DashboardComponent implements OnInit {
|
||||
mempoolInfoData$: Observable<MempoolInfoData>;
|
||||
mempoolLoadingStatus$: Observable<number>;
|
||||
vBytesPerSecondLimit = 1667;
|
||||
blocks$: Observable<Block[]>;
|
||||
blocks$: Observable<BlockExtended[]>;
|
||||
transactions$: Observable<TransactionStripped[]>;
|
||||
latestBlockHeight: number;
|
||||
mempoolTransactionsWeightPerSecondData: any;
|
||||
@ -199,7 +198,7 @@ export class DashboardComponent implements OnInit {
|
||||
};
|
||||
}
|
||||
|
||||
trackByBlock(index: number, block: Block) {
|
||||
trackByBlock(index: number, block: BlockExtended) {
|
||||
return block.height;
|
||||
}
|
||||
|
||||
|
@ -107,14 +107,6 @@ export interface Block {
|
||||
size: number;
|
||||
weight: number;
|
||||
previousblockhash: string;
|
||||
|
||||
// Custom properties
|
||||
medianFee?: number;
|
||||
feeRange?: number[];
|
||||
reward?: number;
|
||||
coinbaseTx?: Transaction;
|
||||
matchRate: number;
|
||||
stage: number;
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { Block, Transaction } from "./electrs.interface";
|
||||
|
||||
export interface OptimizedMempoolStats {
|
||||
added: number;
|
||||
vbytes_per_second: number;
|
||||
@ -80,3 +82,17 @@ export interface MiningStats {
|
||||
pools: SinglePoolStats[],
|
||||
}
|
||||
|
||||
export interface BlockExtension {
|
||||
medianFee?: number;
|
||||
feeRange?: number[];
|
||||
reward?: number;
|
||||
coinbaseTx?: Transaction;
|
||||
matchRate?: number;
|
||||
|
||||
stage?: number; // Frontend only
|
||||
}
|
||||
|
||||
export interface BlockExtended extends Block {
|
||||
extras?: BlockExtension;
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { ILoadingIndicators } from '../services/state.service';
|
||||
import { Block, Transaction } from './electrs.interface';
|
||||
import { Transaction } from './electrs.interface';
|
||||
import { BlockExtended } from './node-api.interface';
|
||||
|
||||
export interface WebsocketResponse {
|
||||
block?: Block;
|
||||
blocks?: Block[];
|
||||
block?: BlockExtended;
|
||||
blocks?: BlockExtended[];
|
||||
conversions?: any;
|
||||
txConfirmed?: boolean;
|
||||
historicalDate?: string;
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Block, Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
|
||||
import { Transaction, Address, Outspend, Recent, Asset } from '../interfaces/electrs.interface';
|
||||
import { StateService } from './state.service';
|
||||
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -28,12 +29,12 @@ export class ElectrsApiService {
|
||||
});
|
||||
}
|
||||
|
||||
getBlock$(hash: string): Observable<Block> {
|
||||
return this.httpClient.get<Block>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash);
|
||||
getBlock$(hash: string): Observable<BlockExtended> {
|
||||
return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash);
|
||||
}
|
||||
|
||||
listBlocks$(height?: number): Observable<Block[]> {
|
||||
return this.httpClient.get<Block[]>(this.apiBaseUrl + this.apiBasePath + '/api/blocks/' + (height || ''));
|
||||
listBlocks$(height?: number): Observable<BlockExtended[]> {
|
||||
return this.httpClient.get<BlockExtended[]>(this.apiBaseUrl + this.apiBasePath + '/api/blocks/' + (height || ''));
|
||||
}
|
||||
|
||||
getTransaction$(txId: string): Observable<Transaction> {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs';
|
||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { IBackendInfo, MempoolBlock, MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
|
||||
import { Router, NavigationStart } from '@angular/router';
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { map, shareReplay } from 'rxjs/operators';
|
||||
@ -72,7 +72,7 @@ export class StateService {
|
||||
latestBlockHeight = 0;
|
||||
|
||||
networkChanged$ = new ReplaySubject<string>(1);
|
||||
blocks$: ReplaySubject<[Block, boolean]>;
|
||||
blocks$: ReplaySubject<[BlockExtended, boolean]>;
|
||||
transactions$ = new ReplaySubject<TransactionStripped>(6);
|
||||
conversions$ = new ReplaySubject<any>(1);
|
||||
bsqPrice$ = new ReplaySubject<number>(1);
|
||||
@ -122,7 +122,7 @@ export class StateService {
|
||||
}
|
||||
});
|
||||
|
||||
this.blocks$ = new ReplaySubject<[Block, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
||||
this.blocks$ = new ReplaySubject<[BlockExtended, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
||||
|
||||
if (this.env.BASE_MODULE === 'bisq') {
|
||||
this.network = this.env.BASE_MODULE;
|
||||
|
@ -2,11 +2,12 @@ import { Injectable } from '@angular/core';
|
||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||
import { WebsocketResponse, IBackendInfo } from '../interfaces/websocket.interface';
|
||||
import { StateService } from './state.service';
|
||||
import { Block, Transaction } from '../interfaces/electrs.interface';
|
||||
import { Transaction } from '../interfaces/electrs.interface';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ApiService } from './api.service';
|
||||
import { take } from 'rxjs/operators';
|
||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
||||
import { BlockExtended } from '../interfaces/node-api.interface';
|
||||
|
||||
const OFFLINE_RETRY_AFTER_MS = 10000;
|
||||
const OFFLINE_PING_CHECK_AFTER_MS = 30000;
|
||||
@ -207,7 +208,7 @@ export class WebsocketService {
|
||||
handleResponse(response: WebsocketResponse) {
|
||||
if (response.blocks && response.blocks.length) {
|
||||
const blocks = response.blocks;
|
||||
blocks.forEach((block: Block) => {
|
||||
blocks.forEach((block: BlockExtended) => {
|
||||
if (block.height > this.stateService.latestBlockHeight) {
|
||||
this.stateService.latestBlockHeight = block.height;
|
||||
this.stateService.blocks$.next([block, false]);
|
||||
|
Loading…
Reference in New Issue
Block a user