Merge branch 'master' into fetch_conversion_rates_over_tor

This commit is contained in:
wiz 2022-02-06 07:53:59 +00:00 committed by GitHub
commit aae780de6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 311 additions and 127 deletions

View file

@ -96,14 +96,20 @@ class Blocks {
*/ */
private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended { private getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): BlockExtended {
const blockExtended: BlockExtended = Object.assign({}, block); 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]; const transactionsTmp = [...transactions];
transactionsTmp.shift(); transactionsTmp.shift();
transactionsTmp.sort((a, b) => b.effectiveFeePerVsize - a.effectiveFeePerVsize); 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; return blockExtended;
} }
@ -197,7 +203,14 @@ class Blocks {
const block = await bitcoinApi.$getBlock(blockHash); const block = await bitcoinApi.$getBlock(blockHash);
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true); const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
const blockExtended = this.getBlockExtended(block, transactions); 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); const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner); await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
} catch (e) { } catch (e) {
@ -262,7 +275,12 @@ class Blocks {
const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true); const coinbase: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(transactions[0].txid, true);
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === 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); await blocksRepository.$saveBlockInDatabase(blockExtended, blockHash, coinbase.hex, miner);
} }

View file

@ -380,7 +380,9 @@ class WebsocketHandler {
mBlocks = mempoolBlocks.getMempoolBlocks(); mBlocks = mempoolBlocks.getMempoolBlocks();
} }
block.matchRate = matchRate; if (block.extras) {
block.extras.matchRate = matchRate;
}
this.wss.clients.forEach((client) => { this.wss.clients.forEach((client) => {
if (client.readyState !== WebSocket.OPEN) { if (client.readyState !== WebSocket.OPEN) {

View file

@ -76,7 +76,8 @@ export interface TransactionStripped {
vsize: number; vsize: number;
value: number; value: number;
} }
export interface BlockExtended extends IEsploraApi.Block {
export interface BlockExtension {
medianFee?: number; medianFee?: number;
feeRange?: number[]; feeRange?: number[];
reward?: number; reward?: number;
@ -84,6 +85,10 @@ export interface BlockExtended extends IEsploraApi.Block {
matchRate?: number; matchRate?: number;
} }
export interface BlockExtended extends IEsploraApi.Block {
extras?: BlockExtension;
}
export interface TransactionMinerInfo { export interface TransactionMinerInfo {
vin: VinStrippedToScriptsig[]; vin: VinStrippedToScriptsig[];
vout: VoutStrippedToScriptPubkey[]; vout: VoutStrippedToScriptPubkey[];

View file

@ -31,9 +31,18 @@ class BlocksRepository {
)`; )`;
const params: any[] = [ const params: any[] = [
block.height, blockHash, block.timestamp, block.size, block.height,
block.weight, block.tx_count, coinbaseHex ? coinbaseHex : '', block.difficulty, blockHash,
poolTag.id, 0, '[]', block.medianFee, 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); await connection.query(query, params);

View file

@ -218,6 +218,10 @@
"proxyConfig": "proxy.conf.local.js", "proxyConfig": "proxy.conf.local.js",
"verbose": true "verbose": true
}, },
"mixed": {
"proxyConfig": "proxy.conf.mixed.js",
"verbose": true
},
"staging": { "staging": {
"proxyConfig": "proxy.conf.js", "proxyConfig": "proxy.conf.js",
"disableHostCheck": true, "disableHostCheck": true,

View file

@ -30,6 +30,7 @@
"start": "npm run generate-config && npm run sync-assets-dev && ng serve -c local", "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: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: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", "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": "node sync-assets.js && rsync -av ./dist/mempool/browser/en-US/resources ./dist/mempool/browser/resources",
"sync-assets-dev": "node sync-assets.js dev", "sync-assets-dev": "node sync-assets.js dev",

View file

@ -1,17 +1,13 @@
const fs = require('fs'); 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'; const FRONTEND_CONFIG_FILE_NAME = 'mempool-frontend-config.json';
let backendConfigContent; let configContent;
let frontendConfigContent;
// Read frontend config // Read frontend config
try { try {
const rawConfig = fs.readFileSync(FRONTEND_CONFIG_FILE_NAME); 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`); console.log(`${FRONTEND_CONFIG_FILE_NAME} file found, using provided config`);
} catch (e) { } catch (e) {
console.log(e); console.log(e);
@ -22,51 +18,88 @@ try {
} }
} }
// Read backend config let PROXY_CONFIG = [];
try {
const rawConfig = fs.readFileSync(BACKEND_CONFIG_FILE_NAME); if (configContent && configContent.BASE_MODULE === 'liquid') {
backendConfigContent = JSON.parse(rawConfig); PROXY_CONFIG.push(...[
console.log(`${BACKEND_CONFIG_FILE_NAME} file found, using provided config`); {
} catch (e) { context: ['/liquid/api/v1/**'],
console.log(e); target: `http://localhost:8999`,
if (e.code !== 'ENOENT') { secure: false,
throw new Error(e); ws: true,
} else { changeOrigin: true,
console.log(`${BACKEND_CONFIG_FILE_NAME} file not found, using default config`); 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); console.log(PROXY_CONFIG);

View 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;

View file

@ -74,9 +74,9 @@
<div class="col-sm"> <div class="col-sm">
<table class="table table-borderless table-striped"> <table class="table table-borderless table-striped">
<tbody> <tbody>
<tr *ngIf="block.medianFee !== undefined"> <tr *ngIf="block?.extras?.medianFee != undefined">
<td class="td-width" i18n="block.median-fee">Median fee</td> <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> </tr>
<ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees"> <ng-template [ngIf]="fees !== undefined" [ngIfElse]="loadingFees">
<tr> <tr>

View file

@ -3,12 +3,13 @@ import { Location } from '@angular/common';
import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { ActivatedRoute, ParamMap, Router } from '@angular/router';
import { ElectrsApiService } from '../../services/electrs-api.service'; import { ElectrsApiService } from '../../services/electrs-api.service';
import { switchMap, tap, debounceTime, catchError, map } from 'rxjs/operators'; 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 { Observable, of, Subscription } from 'rxjs';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
import { SeoService } from 'src/app/services/seo.service'; import { SeoService } from 'src/app/services/seo.service';
import { WebsocketService } from 'src/app/services/websocket.service'; import { WebsocketService } from 'src/app/services/websocket.service';
import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe'; import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.pipe';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({ @Component({
selector: 'app-block', selector: 'app-block',
@ -17,13 +18,13 @@ import { RelativeUrlPipe } from 'src/app/shared/pipes/relative-url/relative-url.
}) })
export class BlockComponent implements OnInit, OnDestroy { export class BlockComponent implements OnInit, OnDestroy {
network = ''; network = '';
block: Block; block: BlockExtended;
blockHeight: number; blockHeight: number;
nextBlockHeight: number; nextBlockHeight: number;
blockHash: string; blockHash: string;
isLoadingBlock = true; isLoadingBlock = true;
latestBlock: Block; latestBlock: BlockExtended;
latestBlocks: Block[] = []; latestBlocks: BlockExtended[] = [];
transactions: Transaction[]; transactions: Transaction[];
isLoadingTransactions = true; isLoadingTransactions = true;
error: any; error: any;
@ -76,7 +77,9 @@ export class BlockComponent implements OnInit, OnDestroy {
if (block.id === this.blockHash) { if (block.id === this.blockHash) {
this.block = block; 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 { } else {
this.isLoadingBlock = true; this.isLoadingBlock = true;
let blockInCache: Block; let blockInCache: BlockExtended;
if (isBlockHeight) { if (isBlockHeight) {
blockInCache = this.latestBlocks.find((block) => block.height === parseInt(blockHash, 10)); blockInCache = this.latestBlocks.find((block) => block.height === parseInt(blockHash, 10));
if (blockInCache) { if (blockInCache) {
@ -134,7 +137,7 @@ export class BlockComponent implements OnInit, OnDestroy {
return this.electrsApiService.getBlock$(blockHash); return this.electrsApiService.getBlock$(blockHash);
} }
}), }),
tap((block: Block) => { tap((block: BlockExtended) => {
this.block = block; this.block = block;
this.blockHeight = block.height; this.blockHeight = block.height;
this.nextBlockHeight = block.height + 1; 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.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`);
this.isLoadingBlock = false; this.isLoadingBlock = false;
if (block.coinbaseTx) { this.coinbaseTx = block?.extras?.coinbaseTx;
this.coinbaseTx = block.coinbaseTx;
}
this.setBlockSubsidy(); this.setBlockSubsidy();
if (block.reward !== undefined) { if (block?.extras?.reward !== undefined) {
this.fees = block.reward / 100000000 - this.blockSubsidy; this.fees = block.extras.reward / 100000000 - this.blockSubsidy;
} }
this.stateService.markBlock$.next({ blockHeight: this.blockHeight }); this.stateService.markBlock$.next({ blockHeight: this.blockHeight });
this.isLoadingTransactions = true; this.isLoadingTransactions = true;

View file

@ -8,10 +8,10 @@
</div> </div>
<div class="block-body"> <div class="block-body">
<div class="fees"> <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>
<div class="fee-span"> <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>
<div class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div> <div class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div>
<div class="transaction-count"> <div class="transaction-count">

View file

@ -1,9 +1,9 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { Block } from 'src/app/interfaces/electrs.interface';
import { StateService } from 'src/app/services/state.service'; import { StateService } from 'src/app/services/state.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { specialBlocks } from 'src/app/app.constants'; import { specialBlocks } from 'src/app/app.constants';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({ @Component({
selector: 'app-blockchain-blocks', selector: 'app-blockchain-blocks',
@ -14,8 +14,8 @@ import { specialBlocks } from 'src/app/app.constants';
export class BlockchainBlocksComponent implements OnInit, OnDestroy { export class BlockchainBlocksComponent implements OnInit, OnDestroy {
specialBlocks = specialBlocks; specialBlocks = specialBlocks;
network = ''; network = '';
blocks: Block[] = []; blocks: BlockExtended[] = [];
emptyBlocks: Block[] = this.mountEmptyBlocks(); emptyBlocks: BlockExtended[] = this.mountEmptyBlocks();
markHeight: number; markHeight: number;
blocksSubscription: Subscription; blocksSubscription: Subscription;
networkSubscription: Subscription; networkSubscription: Subscription;
@ -69,8 +69,8 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
this.blocks.unshift(block); this.blocks.unshift(block);
this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT); this.blocks = this.blocks.slice(0, this.stateService.env.KEEP_BLOCKS_AMOUNT);
if (this.blocksFilled && !this.tabHidden) { if (this.blocksFilled && !this.tabHidden && block.extras) {
block.stage = block.matchRate >= 66 ? 1 : 2; block.extras.stage = block.extras.matchRate >= 66 ? 1 : 2;
} }
if (txConfirmed) { 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; return item.height;
} }
getStyleForBlock(block: Block) { getStyleForBlock(block: BlockExtended) {
const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100; const greenBackgroundHeight = 100 - (block.weight / this.stateService.env.BLOCK_WEIGHT_UNITS) * 100;
let addLeft = 0; let addLeft = 0;
if (block.stage === 1) { if (block?.extras?.stage === 1) {
block.stage = 2; block.extras.stage = 2;
addLeft = -205; addLeft = -205;
} }
@ -167,11 +167,11 @@ export class BlockchainBlocksComponent implements OnInit, OnDestroy {
}; };
} }
getStyleForEmptyBlock(block: Block) { getStyleForEmptyBlock(block: BlockExtended) {
let addLeft = 0; let addLeft = 0;
if (block.stage === 1) { if (block?.extras?.stage === 1) {
block.stage = 2; block.extras.stage = 2;
addLeft = -205; addLeft = -205;
} }

View file

@ -153,7 +153,7 @@ export class MempoolBlocksComponent implements OnInit, OnDestroy {
this.blockSubscription = this.stateService.blocks$ this.blockSubscription = this.stateService.blocks$
.subscribe(([block]) => { .subscribe(([block]) => {
if (block.matchRate >= 66 && !this.tabHidden) { if (block?.extras?.matchRate >= 66 && !this.tabHidden) {
this.blockIndex++; this.blockIndex++;
} }
}); });

View file

@ -9,14 +9,14 @@ import {
delay, delay,
map map
} from 'rxjs/operators'; } 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 { of, merge, Subscription, Observable, Subject, timer, combineLatest, from } from 'rxjs';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
import { WebsocketService } from '../../services/websocket.service'; import { WebsocketService } from '../../services/websocket.service';
import { AudioService } from 'src/app/services/audio.service'; import { AudioService } from 'src/app/services/audio.service';
import { ApiService } from 'src/app/services/api.service'; import { ApiService } from 'src/app/services/api.service';
import { SeoService } from 'src/app/services/seo.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'; import { LiquidUnblinding } from './liquid-ublinding';
@Component({ @Component({
@ -33,7 +33,7 @@ export class TransactionComponent implements OnInit, OnDestroy {
error: any = undefined; error: any = undefined;
errorUnblinded: any = undefined; errorUnblinded: any = undefined;
waitingForTransaction = false; waitingForTransaction = false;
latestBlock: Block; latestBlock: BlockExtended;
transactionTime = -1; transactionTime = -1;
subscription: Subscription; subscription: Subscription;
fetchCpfpSubscription: Subscription; fetchCpfpSubscription: Subscription;

View file

@ -1,11 +1,12 @@
import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, Output, EventEmitter } from '@angular/core'; import { Component, OnInit, Input, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, Output, EventEmitter } from '@angular/core';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
import { Observable, forkJoin } from 'rxjs'; 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 { ElectrsApiService } from '../../services/electrs-api.service';
import { environment } from 'src/environments/environment'; import { environment } from 'src/environments/environment';
import { AssetsService } from 'src/app/services/assets.service'; import { AssetsService } from 'src/app/services/assets.service';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({ @Component({
selector: 'app-transactions-list', selector: 'app-transactions-list',
@ -26,7 +27,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
@Output() loadMore = new EventEmitter(); @Output() loadMore = new EventEmitter();
latestBlock$: Observable<Block>; latestBlock$: Observable<BlockExtended>;
outspends: Outspend[] = []; outspends: Outspend[] = [];
assetsMinimal: any; assetsMinimal: any;

View file

@ -1,7 +1,8 @@
import { Component, ChangeDetectionStrategy, OnChanges, Input, OnInit, ChangeDetectorRef, OnDestroy } from '@angular/core'; 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 { StateService } from 'src/app/services/state.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { BlockExtended } from 'src/app/interfaces/node-api.interface';
@Component({ @Component({
selector: 'app-tx-fee-rating', selector: 'app-tx-fee-rating',
@ -18,7 +19,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
overpaidTimes: number; overpaidTimes: number;
feeRating: number; feeRating: number;
blocks: Block[] = []; blocks: BlockExtended[] = [];
constructor( constructor(
private stateService: StateService, private stateService: StateService,
@ -28,7 +29,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
ngOnInit() { ngOnInit() {
this.blocksSubscription = this.stateService.blocks$.subscribe(([block]) => { this.blocksSubscription = this.stateService.blocks$.subscribe(([block]) => {
this.blocks.push(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.calculateRatings(block);
this.cd.markForCheck(); 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); 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); this.calculateRatings(foundBlock);
} }
} }
@ -51,9 +52,9 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
this.blocksSubscription.unsubscribe(); this.blocksSubscription.unsubscribe();
} }
calculateRatings(block: Block) { calculateRatings(block: BlockExtended) {
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4); const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
this.medianFeeNeeded = block.medianFee; this.medianFeeNeeded = block?.extras?.medianFee;
// Block not filled // Block not filled
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) { if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {

View file

@ -1,8 +1,7 @@
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
import { combineLatest, merge, Observable, of, timer } from 'rxjs'; import { combineLatest, merge, Observable, of, timer } from 'rxjs';
import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators'; import { filter, map, scan, share, switchMap, tap } from 'rxjs/operators';
import { Block } from '../interfaces/electrs.interface'; import { BlockExtended, OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { OptimizedMempoolStats } from '../interfaces/node-api.interface';
import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface'; import { MempoolInfo, TransactionStripped } from '../interfaces/websocket.interface';
import { ApiService } from '../services/api.service'; import { ApiService } from '../services/api.service';
import { StateService } from '../services/state.service'; import { StateService } from '../services/state.service';
@ -40,7 +39,7 @@ export class DashboardComponent implements OnInit {
mempoolInfoData$: Observable<MempoolInfoData>; mempoolInfoData$: Observable<MempoolInfoData>;
mempoolLoadingStatus$: Observable<number>; mempoolLoadingStatus$: Observable<number>;
vBytesPerSecondLimit = 1667; vBytesPerSecondLimit = 1667;
blocks$: Observable<Block[]>; blocks$: Observable<BlockExtended[]>;
transactions$: Observable<TransactionStripped[]>; transactions$: Observable<TransactionStripped[]>;
latestBlockHeight: number; latestBlockHeight: number;
mempoolTransactionsWeightPerSecondData: any; mempoolTransactionsWeightPerSecondData: any;
@ -199,7 +198,7 @@ export class DashboardComponent implements OnInit {
}; };
} }
trackByBlock(index: number, block: Block) { trackByBlock(index: number, block: BlockExtended) {
return block.height; return block.height;
} }

View file

@ -107,14 +107,6 @@ export interface Block {
size: number; size: number;
weight: number; weight: number;
previousblockhash: string; previousblockhash: string;
// Custom properties
medianFee?: number;
feeRange?: number[];
reward?: number;
coinbaseTx?: Transaction;
matchRate: number;
stage: number;
} }
export interface Address { export interface Address {

View file

@ -1,3 +1,5 @@
import { Block, Transaction } from "./electrs.interface";
export interface OptimizedMempoolStats { export interface OptimizedMempoolStats {
added: number; added: number;
vbytes_per_second: number; vbytes_per_second: number;
@ -80,3 +82,17 @@ export interface MiningStats {
pools: SinglePoolStats[], 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;
}

View file

@ -1,9 +1,10 @@
import { ILoadingIndicators } from '../services/state.service'; 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 { export interface WebsocketResponse {
block?: Block; block?: BlockExtended;
blocks?: Block[]; blocks?: BlockExtended[];
conversions?: any; conversions?: any;
txConfirmed?: boolean; txConfirmed?: boolean;
historicalDate?: string; historicalDate?: string;

View file

@ -1,8 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; 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 { StateService } from './state.service';
import { BlockExtended } from '../interfaces/node-api.interface';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -28,12 +29,12 @@ export class ElectrsApiService {
}); });
} }
getBlock$(hash: string): Observable<Block> { getBlock$(hash: string): Observable<BlockExtended> {
return this.httpClient.get<Block>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash); return this.httpClient.get<BlockExtended>(this.apiBaseUrl + this.apiBasePath + '/api/block/' + hash);
} }
listBlocks$(height?: number): Observable<Block[]> { listBlocks$(height?: number): Observable<BlockExtended[]> {
return this.httpClient.get<Block[]>(this.apiBaseUrl + this.apiBasePath + '/api/blocks/' + (height || '')); return this.httpClient.get<BlockExtended[]>(this.apiBaseUrl + this.apiBasePath + '/api/blocks/' + (height || ''));
} }
getTransaction$(txId: string): Observable<Transaction> { getTransaction$(txId: string): Observable<Transaction> {

View file

@ -1,8 +1,8 @@
import { Inject, Injectable, PLATFORM_ID } from '@angular/core'; import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable } from 'rxjs'; 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 { 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 { Router, NavigationStart } from '@angular/router';
import { isPlatformBrowser } from '@angular/common'; import { isPlatformBrowser } from '@angular/common';
import { map, shareReplay } from 'rxjs/operators'; import { map, shareReplay } from 'rxjs/operators';
@ -72,7 +72,7 @@ export class StateService {
latestBlockHeight = 0; latestBlockHeight = 0;
networkChanged$ = new ReplaySubject<string>(1); networkChanged$ = new ReplaySubject<string>(1);
blocks$: ReplaySubject<[Block, boolean]>; blocks$: ReplaySubject<[BlockExtended, boolean]>;
transactions$ = new ReplaySubject<TransactionStripped>(6); transactions$ = new ReplaySubject<TransactionStripped>(6);
conversions$ = new ReplaySubject<any>(1); conversions$ = new ReplaySubject<any>(1);
bsqPrice$ = new ReplaySubject<number>(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') { if (this.env.BASE_MODULE === 'bisq') {
this.network = this.env.BASE_MODULE; this.network = this.env.BASE_MODULE;

View file

@ -2,11 +2,12 @@ import { Injectable } from '@angular/core';
import { webSocket, WebSocketSubject } from 'rxjs/webSocket'; import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
import { WebsocketResponse, IBackendInfo } from '../interfaces/websocket.interface'; import { WebsocketResponse, IBackendInfo } from '../interfaces/websocket.interface';
import { StateService } from './state.service'; import { StateService } from './state.service';
import { Block, Transaction } from '../interfaces/electrs.interface'; import { Transaction } from '../interfaces/electrs.interface';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ApiService } from './api.service'; import { ApiService } from './api.service';
import { take } from 'rxjs/operators'; import { take } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/platform-browser'; import { TransferState, makeStateKey } from '@angular/platform-browser';
import { BlockExtended } from '../interfaces/node-api.interface';
const OFFLINE_RETRY_AFTER_MS = 10000; const OFFLINE_RETRY_AFTER_MS = 10000;
const OFFLINE_PING_CHECK_AFTER_MS = 30000; const OFFLINE_PING_CHECK_AFTER_MS = 30000;
@ -207,7 +208,7 @@ export class WebsocketService {
handleResponse(response: WebsocketResponse) { handleResponse(response: WebsocketResponse) {
if (response.blocks && response.blocks.length) { if (response.blocks && response.blocks.length) {
const blocks = response.blocks; const blocks = response.blocks;
blocks.forEach((block: Block) => { blocks.forEach((block: BlockExtended) => {
if (block.height > this.stateService.latestBlockHeight) { if (block.height > this.stateService.latestBlockHeight) {
this.stateService.latestBlockHeight = block.height; this.stateService.latestBlockHeight = block.height;
this.stateService.blocks$.next([block, false]); this.stateService.blocks$.next([block, false]);