Compare commits

..

9 Commits

15 changed files with 87 additions and 236 deletions

View File

@@ -16,7 +16,7 @@ class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
super(bitcoinClient); super(bitcoinClient);
const electrumConfig = { client: 'mempool-v2', version: '1.4' }; const electrumConfig = { client: 'mempool-v2', version: '1.4' };
const electrumPersistencePolicy = { retryPeriod: 10000, maxRetry: 1000, callback: null }; const electrumPersistencePolicy = { retryPeriod: 1000, maxRetry: Number.MAX_SAFE_INTEGER, callback: null };
const electrumCallbacks = { const electrumCallbacks = {
onConnect: (client, versionInfo) => { logger.info(`Connected to Electrum Server at ${config.ELECTRUM.HOST}:${config.ELECTRUM.PORT} (${JSON.stringify(versionInfo)})`); }, onConnect: (client, versionInfo) => { logger.info(`Connected to Electrum Server at ${config.ELECTRUM.HOST}:${config.ELECTRUM.PORT} (${JSON.stringify(versionInfo)})`); },

View File

@@ -1,5 +1,5 @@
import logger from '../logger'; import logger from '../logger';
import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, GbtCandidates } from '../mempool.interfaces'; import { MempoolBlock, TransactionExtended, ThreadTransaction, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor } from '../mempool.interfaces';
import { Common } from './common'; import { Common } from './common';
import config from '../config'; import config from '../config';
import { Worker } from 'worker_threads'; import { Worker } from 'worker_threads';
@@ -147,23 +147,19 @@ class MempoolBlocks {
return mempoolBlockDeltas; return mempoolBlockDeltas;
} }
public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false, candidates?: GbtCandidates): Promise<MempoolBlockWithTransactions[]> { public async makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
// prepare a stripped down version of the mempool with only the minimum necessary data // prepare a stripped down version of the mempool with only the minimum necessary data
// to reduce the overhead of passing this data to the worker thread // to reduce the overhead of passing this data to the worker thread
const txids = candidates ? Object.keys(candidates.txs) : Object.keys(newMempool);
const strippedMempool: { [txid: string]: ThreadTransaction } = {}; const strippedMempool: { [txid: string]: ThreadTransaction } = {};
txids.forEach(txid => { Object.values(newMempool).forEach(entry => {
const entry = newMempool[txid]; strippedMempool[entry.txid] = {
if (entry) { txid: entry.txid,
strippedMempool[entry.txid] = { fee: entry.fee,
txid: entry.txid, weight: entry.weight,
fee: entry.fee, feePerVsize: entry.fee / (entry.weight / 4),
weight: entry.weight, effectiveFeePerVsize: entry.fee / (entry.weight / 4),
feePerVsize: entry.fee / (entry.weight / 4), vin: entry.vin.map(v => v.txid),
effectiveFeePerVsize: entry.fee / (entry.weight / 4), };
vin: entry.vin.map(v => v.txid),
};
}
}); });
// (re)initialize tx selection worker thread // (re)initialize tx selection worker thread
@@ -195,49 +191,31 @@ class MempoolBlocks {
// clean up thread error listener // clean up thread error listener
this.txSelectionWorker?.removeListener('error', threadErrorListener); this.txSelectionWorker?.removeListener('error', threadErrorListener);
return this.processBlockTemplates(newMempool, blocks, clusters, saveResults, candidates); return this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
} catch (e) { } catch (e) {
logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e)); logger.err('makeBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
} }
return this.mempoolBlocks; return this.mempoolBlocks;
} }
public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[], saveResults: boolean = false, candidates?: GbtCandidates): Promise<void> { public async updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: string[], saveResults: boolean = false): Promise<void> {
if (!this.txSelectionWorker) { if (!this.txSelectionWorker) {
// need to reset the worker // need to reset the worker
this.makeBlockTemplates(newMempool, saveResults, candidates); this.makeBlockTemplates(newMempool, saveResults);
return; return;
} }
// prepare a stripped down version of the mempool with only the minimum necessary data // prepare a stripped down version of the mempool with only the minimum necessary data
// to reduce the overhead of passing this data to the worker thread // to reduce the overhead of passing this data to the worker thread
let addedStripped: ThreadTransaction[] = []; const addedStripped: ThreadTransaction[] = added.map(entry => {
let removedList; return {
if (candidates) { txid: entry.txid,
addedStripped = candidates.added.filter(txid => newMempool[txid]).map(txid => { fee: entry.fee,
const entry = newMempool[txid]; weight: entry.weight,
return { feePerVsize: entry.fee / (entry.weight / 4),
txid: entry.txid, effectiveFeePerVsize: entry.fee / (entry.weight / 4),
fee: entry.fee, vin: entry.vin.map(v => v.txid),
weight: entry.weight, };
feePerVsize: entry.fee / (entry.weight / 4), });
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
vin: entry.vin.map(v => v.txid),
};
});
removedList = candidates.removed;
} else {
addedStripped = added.map(entry => {
return {
txid: entry.txid,
fee: entry.fee,
weight: entry.weight,
feePerVsize: entry.fee / (entry.weight / 4),
effectiveFeePerVsize: entry.fee / (entry.weight / 4),
vin: entry.vin.map(v => v.txid),
};
});
removedList = removed;
}
// run the block construction algorithm in a separate thread, and wait for a result // run the block construction algorithm in a separate thread, and wait for a result
let threadErrorListener; let threadErrorListener;
@@ -249,19 +227,19 @@ class MempoolBlocks {
}); });
this.txSelectionWorker?.once('error', reject); this.txSelectionWorker?.once('error', reject);
}); });
this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed: removedList }); this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed });
const { blocks, clusters } = await workerResultPromise; const { blocks, clusters } = await workerResultPromise;
// clean up thread error listener // clean up thread error listener
this.txSelectionWorker?.removeListener('error', threadErrorListener); this.txSelectionWorker?.removeListener('error', threadErrorListener);
this.processBlockTemplates(newMempool, blocks, clusters, saveResults, candidates); this.processBlockTemplates(newMempool, blocks, clusters, saveResults);
} catch (e) { } catch (e) {
logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e)); logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e));
} }
} }
private processBlockTemplates(mempool: { [txid: string ]: TransactionExtended }, blocks, clusters, saveResults, candidates?: GbtCandidates): MempoolBlockWithTransactions[] { private processBlockTemplates(mempool, blocks, clusters, saveResults): MempoolBlockWithTransactions[] {
// update this thread's mempool with the results // update this thread's mempool with the results
blocks.forEach(block => { blocks.forEach(block => {
block.forEach(tx => { block.forEach(tx => {
@@ -299,32 +277,6 @@ class MempoolBlocks {
}); });
}); });
// Add purged transactions at the end, if required
if (candidates) {
const purged: string[] = [];
Object.values(mempool).forEach(tx => {
if (!candidates.txs[tx.txid]) {
purged.push(tx.txid);
}
});
if (!blocks.length) {
blocks = [[]];
}
let blockIndex = blocks.length - 1;
let weight = blocks[blockIndex].reduce((acc, tx) => acc + tx.weight, 0);
purged.sort((a,b) => { return mempool[b].effectiveFeePerVsize - mempool[a].effectiveFeePerVsize});
purged.forEach(txid => {
const tx = mempool[txid];
if ((weight + tx.weight) >= (config.MEMPOOL.BLOCK_WEIGHT_UNITS - 4000) && blockIndex < 7) {
blocks.push([]);
blockIndex++;
weight = 0;
}
blocks[blockIndex].push(tx);
weight += tx.weight;
});
}
// unpack the condensed blocks into proper mempool blocks // unpack the condensed blocks into proper mempool blocks
const mempoolBlocks = blocks.map((transactions, blockIndex) => { const mempoolBlocks = blocks.map((transactions, blockIndex) => {
return this.dataToMempoolBlocks(transactions.map(tx => { return this.dataToMempoolBlocks(transactions.map(tx => {

View File

@@ -1,6 +1,6 @@
import config from '../config'; import config from '../config';
import bitcoinApi from './bitcoin/bitcoin-api-factory'; import bitcoinApi from './bitcoin/bitcoin-api-factory';
import { GbtCandidates, TransactionExtended, VbytesPerSecond } from '../mempool.interfaces'; import { TransactionExtended, VbytesPerSecond } from '../mempool.interfaces';
import logger from '../logger'; import logger from '../logger';
import { Common } from './common'; import { Common } from './common';
import transactionUtils from './transaction-utils'; import transactionUtils from './transaction-utils';
@@ -9,7 +9,6 @@ import loadingIndicators from './loading-indicators';
import bitcoinClient from './bitcoin/bitcoin-client'; import bitcoinClient from './bitcoin/bitcoin-client';
import bitcoinSecondClient from './bitcoin/bitcoin-second-client'; import bitcoinSecondClient from './bitcoin/bitcoin-second-client';
import rbfCache from './rbf-cache'; import rbfCache from './rbf-cache';
import blocks from './blocks';
class Mempool { class Mempool {
private static WEBSOCKET_REFRESH_RATE_MS = 10000; private static WEBSOCKET_REFRESH_RATE_MS = 10000;
@@ -17,16 +16,12 @@ class Mempool {
private inSync: boolean = false; private inSync: boolean = false;
private mempoolCacheDelta: number = -1; private mempoolCacheDelta: number = -1;
private mempoolCache: { [txId: string]: TransactionExtended } = {}; private mempoolCache: { [txId: string]: TransactionExtended } = {};
private mempoolCandidates: { [txid: string ]: boolean } = {};
private minFeeMempool: { [txId: string]: boolean } = {};
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0, private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 }; maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
private secondMempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[], private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
deletedTransactions: TransactionExtended[]) => void) | undefined; deletedTransactions: TransactionExtended[]) => void) | undefined;
private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[], private asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
deletedTransactions: TransactionExtended[], candidates?: GbtCandidates) => Promise<void>) | undefined; deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
private txPerSecondArray: number[] = []; private txPerSecondArray: number[] = [];
private txPerSecond: number = 0; private txPerSecond: number = 0;
@@ -76,7 +71,7 @@ class Mempool {
} }
public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: TransactionExtended; }, public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: TransactionExtended; },
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[], candidates?: GbtCandidates) => Promise<void>) { newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]) => Promise<void>) {
this.asyncMempoolChangedCallback = fn; this.asyncMempoolChangedCallback = fn;
} }
@@ -90,18 +85,10 @@ class Mempool {
this.mempoolChangedCallback(this.mempoolCache, [], []); this.mempoolChangedCallback(this.mempoolCache, [], []);
} }
if (this.asyncMempoolChangedCallback) { if (this.asyncMempoolChangedCallback) {
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) { this.asyncMempoolChangedCallback(this.mempoolCache, [], []);
this.asyncMempoolChangedCallback(this.mempoolCache, [], [], { txs: {}, added: [], removed: [] });
} else {
this.asyncMempoolChangedCallback(this.mempoolCache, [], [], );
}
} }
} }
public getMempoolCandidates(): { [txid: string]: boolean } {
return this.mempoolCandidates;
}
public async $updateMemPoolInfo() { public async $updateMemPoolInfo() {
this.mempoolInfo = await this.$getMempoolInfo(); this.mempoolInfo = await this.$getMempoolInfo();
} }
@@ -137,7 +124,6 @@ class Mempool {
let hasChange: boolean = false; let hasChange: boolean = false;
const currentMempoolSize = Object.keys(this.mempoolCache).length; const currentMempoolSize = Object.keys(this.mempoolCache).length;
const transactions = await bitcoinApi.$getRawMempool(); const transactions = await bitcoinApi.$getRawMempool();
const candidates = await this.getNextCandidates();
const diff = transactions.length - currentMempoolSize; const diff = transactions.length - currentMempoolSize;
const newTransactions: TransactionExtended[] = []; const newTransactions: TransactionExtended[] = [];
@@ -237,8 +223,8 @@ class Mempool {
if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) { if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) {
this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions); this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
} }
if (this.asyncMempoolChangedCallback && (hasChange || deletedTransactions.length || candidates?.added.length || candidates?.removed.length)) { if (this.asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) {
await this.asyncMempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions, candidates); await this.asyncMempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions);
} }
const end = new Date().getTime(); const end = new Date().getTime();
@@ -246,55 +232,6 @@ class Mempool {
logger.debug(`Mempool updated in ${time / 1000} seconds. New size: ${Object.keys(this.mempoolCache).length} (${diff > 0 ? '+' + diff : diff})`); logger.debug(`Mempool updated in ${time / 1000} seconds. New size: ${Object.keys(this.mempoolCache).length} (${diff > 0 ? '+' + diff : diff})`);
} }
public isTxPurged(txid: string): boolean {
return !this.minFeeMempool[txid];
}
public async getNextCandidates(): Promise<GbtCandidates | undefined> {
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) {
const minFeeTransactions = await bitcoinSecondClient.getRawMemPool();
const blockHeight = await bitcoinSecondClient.getChainTips()
.then((result: IBitcoinApi.ChainTips[]) => {
return result.find(tip => tip.status === 'active')!.height;
});
const newCandidateTxMap = {};
this.minFeeMempool = {};
for (const txid of minFeeTransactions) {
if (this.mempoolCache[txid]) {
newCandidateTxMap[txid] = true;
}
this.minFeeMempool[txid] = true;
}
const removed: string[] = [];
const added: string[] = [];
// don't prematurely remove txs included in a new block
if (blockHeight > blocks.getCurrentBlockHeight()) {
for (const txid of Object.keys(this.mempoolCandidates)) {
newCandidateTxMap[txid] = true;
}
} else {
for (const txid of Object.keys(this.mempoolCandidates)) {
if (!newCandidateTxMap[txid]) {
removed.push(txid);
}
}
}
for (const txid of Object.keys(newCandidateTxMap)) {
if (!this.mempoolCandidates[txid]) {
added.push(txid);
}
}
this.mempoolCandidates = newCandidateTxMap;
return {
txs: this.mempoolCandidates,
added,
removed
};
}
}
public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended; }) { public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended; }) {
for (const rbfTransaction in rbfTransactions) { for (const rbfTransaction in rbfTransactions) {
if (this.mempoolCache[rbfTransaction]) { if (this.mempoolCache[rbfTransaction]) {

View File

@@ -2,7 +2,7 @@ import logger from '../logger';
import * as WebSocket from 'ws'; import * as WebSocket from 'ws';
import { import {
BlockExtended, TransactionExtended, WebsocketResponse, BlockExtended, TransactionExtended, WebsocketResponse,
OptimizedStatistic, ILoadingIndicators, GbtCandidates OptimizedStatistic, ILoadingIndicators
} from '../mempool.interfaces'; } from '../mempool.interfaces';
import blocks from './blocks'; import blocks from './blocks';
import memPool from './mempool'; import memPool from './mempool';
@@ -92,9 +92,6 @@ class WebsocketHandler {
} }
} }
} }
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE && memPool.getMempool()[client['track-tx']]) {
response['txPurged'] = memPool.isTxPurged(client['track-tx']);
}
} else { } else {
client['track-tx'] = null; client['track-tx'] = null;
} }
@@ -250,14 +247,13 @@ class WebsocketHandler {
} }
async handleMempoolChange(newMempool: { [txid: string]: TransactionExtended }, async handleMempoolChange(newMempool: { [txid: string]: TransactionExtended },
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[], newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]): Promise<void> {
candidates?: GbtCandidates): Promise<void> {
if (!this.wss) { if (!this.wss) {
throw new Error('WebSocket.Server is not set'); throw new Error('WebSocket.Server is not set');
} }
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid), true, candidates); await mempoolBlocks.updateBlockTemplates(newMempool, newTransactions, deletedTransactions.map(tx => tx.txid), true);
} else { } else {
mempoolBlocks.updateMempoolBlocks(newMempool, true); mempoolBlocks.updateMempoolBlocks(newMempool, true);
} }
@@ -399,11 +395,6 @@ class WebsocketHandler {
} }
} }
} }
// update purge status of unconfirmed tracked txs
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE && newMempool[client['track-tx']]) {
response['txPurged'] = memPool.isTxPurged(client['track-tx']);
}
} }
if (client['track-mempool-block'] >= 0) { if (client['track-mempool-block'] >= 0) {
@@ -428,7 +419,6 @@ class WebsocketHandler {
} }
const _memPool = memPool.getMempool(); const _memPool = memPool.getMempool();
const candidateTxs = await memPool.getMempoolCandidates();
if (config.MEMPOOL.AUDIT) { if (config.MEMPOOL.AUDIT) {
let projectedBlocks; let projectedBlocks;
@@ -436,15 +426,7 @@ class WebsocketHandler {
// a cloned copy of the mempool if we're running a different algorithm for mempool updates // a cloned copy of the mempool if we're running a different algorithm for mempool updates
const auditMempool = (config.MEMPOOL.ADVANCED_GBT_AUDIT === config.MEMPOOL.ADVANCED_GBT_MEMPOOL) ? _memPool : deepClone(_memPool); const auditMempool = (config.MEMPOOL.ADVANCED_GBT_AUDIT === config.MEMPOOL.ADVANCED_GBT_MEMPOOL) ? _memPool : deepClone(_memPool);
if (config.MEMPOOL.ADVANCED_GBT_AUDIT) { if (config.MEMPOOL.ADVANCED_GBT_AUDIT) {
let candidates; projectedBlocks = await mempoolBlocks.makeBlockTemplates(auditMempool, false);
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) {
candidates = {
txs: candidateTxs,
added: [],
removed: [],
};
}
projectedBlocks = await mempoolBlocks.makeBlockTemplates(auditMempool, false, candidates);
} else { } else {
projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false); projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false);
} }
@@ -496,21 +478,12 @@ class WebsocketHandler {
// Update mempool to remove transactions included in the new block // Update mempool to remove transactions included in the new block
for (const txId of txIds) { for (const txId of txIds) {
delete _memPool[txId]; delete _memPool[txId];
delete candidateTxs[txId];
removed.push(txId); removed.push(txId);
rbfCache.evict(txId); rbfCache.evict(txId);
} }
if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) {
let candidates; await mempoolBlocks.updateBlockTemplates(_memPool, [], removed, true);
if (config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE) {
candidates = {
txs: candidateTxs,
added: [],
removed: removed,
};
}
await mempoolBlocks.updateBlockTemplates(_memPool, [], removed, true, candidates);
} else { } else {
mempoolBlocks.updateMempoolBlocks(_memPool, true); mempoolBlocks.updateMempoolBlocks(_memPool, true);
} }

View File

@@ -100,12 +100,6 @@ export interface AuditTransaction {
modifiedNode: HeapNode<AuditTransaction>; modifiedNode: HeapNode<AuditTransaction>;
} }
export interface GbtCandidates {
txs: { [txid: string ]: boolean },
added: string[];
removed: string[];
}
export interface ThreadTransaction { export interface ThreadTransaction {
txid: string; txid: string;
fee: number; fee: number;

View File

@@ -106,6 +106,7 @@ https://www.transifex.com/mempool/mempool/dashboard/
* Arabic @baro0k * Arabic @baro0k
* Czech @pixelmade2 * Czech @pixelmade2
* Danish @pierrevendelboe
* German @Emzy * German @Emzy
* English (default) * English (default)
* Spanish @maxhodler @bisqes * Spanish @maxhodler @bisqes
@@ -113,6 +114,7 @@ https://www.transifex.com/mempool/mempool/dashboard/
* French @Bayernatoor * French @Bayernatoor
* Korean @kcalvinalvinn @sogoagain * Korean @kcalvinalvinn @sogoagain
* Italian @HodlBits * Italian @HodlBits
* Lithuanian @eimze21
* Hebrew @rapidlab309 * Hebrew @rapidlab309
* Georgian @wyd_idk * Georgian @wyd_idk
* Hungarian @btcdragonlord * Hungarian @btcdragonlord

View File

@@ -1,10 +1,6 @@
<div class="container-xl"> <div class="container-xl">
<div class="title-block"> <div class="title-block">
<div *ngIf="isPurged" class="alert alert-mempool" role="alert">
<span i18n="transaction.purged|Purged from mempool">This transaction has been purged from our default sized 300MB mempool</span>
</div>
<div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert"> <div *ngIf="rbfTransaction" class="alert alert-mempool" role="alert">
<span i18n="transaction.rbf.replacement|RBF replacement">This transaction has been replaced by:</span> <span i18n="transaction.rbf.replacement|RBF replacement">This transaction has been replaced by:</span>
<app-truncate [text]="rbfTransaction.txid" [lastChars]="12" [link]="['/tx/' | relativeUrl, rbfTransaction.txid]"></app-truncate> <app-truncate [text]="rbfTransaction.txid" [lastChars]="12" [link]="['/tx/' | relativeUrl, rbfTransaction.txid]"></app-truncate>

View File

@@ -46,7 +46,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
fetchRbfSubscription: Subscription; fetchRbfSubscription: Subscription;
fetchCachedTxSubscription: Subscription; fetchCachedTxSubscription: Subscription;
txReplacedSubscription: Subscription; txReplacedSubscription: Subscription;
txPurgedSubscription: Subscription;
blocksSubscription: Subscription; blocksSubscription: Subscription;
queryParamsSubscription: Subscription; queryParamsSubscription: Subscription;
urlFragmentSubscription: Subscription; urlFragmentSubscription: Subscription;
@@ -60,7 +59,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
fetchRbfHistory$ = new Subject<string>(); fetchRbfHistory$ = new Subject<string>();
fetchCachedTx$ = new Subject<string>(); fetchCachedTx$ = new Subject<string>();
isCached: boolean = false; isCached: boolean = false;
isPurged: boolean = false;
now = new Date().getTime(); now = new Date().getTime();
timeAvg$: Observable<number>; timeAvg$: Observable<number>;
liquidUnblinding = new LiquidUnblinding(); liquidUnblinding = new LiquidUnblinding();
@@ -382,10 +380,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
} }
}); });
this.txPurgedSubscription = this.stateService.txPurged$.subscribe((isPurged) => {
this.isPurged = isPurged;
});
this.queryParamsSubscription = this.route.queryParams.subscribe((params) => { this.queryParamsSubscription = this.route.queryParams.subscribe((params) => {
if (params.showFlow === 'false') { if (params.showFlow === 'false') {
this.overrideFlowPreference = false; this.overrideFlowPreference = false;
@@ -538,7 +532,6 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
this.fetchRbfSubscription.unsubscribe(); this.fetchRbfSubscription.unsubscribe();
this.fetchCachedTxSubscription.unsubscribe(); this.fetchCachedTxSubscription.unsubscribe();
this.txReplacedSubscription.unsubscribe(); this.txReplacedSubscription.unsubscribe();
this.txPurgedSubscription.unsubscribe();
this.blocksSubscription.unsubscribe(); this.blocksSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe(); this.queryParamsSubscription.unsubscribe();
this.flowPrefSubscription.unsubscribe(); this.flowPrefSubscription.unsubscribe();

View File

@@ -16,7 +16,6 @@ export interface WebsocketResponse {
tx?: Transaction; tx?: Transaction;
rbfTransaction?: ReplacedTransaction; rbfTransaction?: ReplacedTransaction;
txReplaced?: ReplacedTransaction; txReplaced?: ReplacedTransaction;
txPurged?: boolean;
utxoSpent?: object; utxoSpent?: object;
transactions?: TransactionStripped[]; transactions?: TransactionStripped[];
loadingIndicators?: ILoadingIndicators; loadingIndicators?: ILoadingIndicators;

View File

@@ -101,8 +101,15 @@ export class NodeFeeChartComponent implements OnInit {
} }
prepareChartOptions(outgoingData, incomingData): void { prepareChartOptions(outgoingData, incomingData): void {
let sum = outgoingData.reduce((accumulator, object) => {
return accumulator + object.count;
}, 0);
sum += incomingData.reduce((accumulator, object) => {
return accumulator + object.count;
}, 0);
let title: object; let title: object;
if (outgoingData.length === 0) { if (sum === 0) {
title = { title = {
textStyle: { textStyle: {
color: 'grey', color: 'grey',
@@ -115,7 +122,7 @@ export class NodeFeeChartComponent implements OnInit {
} }
this.chartOptions = { this.chartOptions = {
title: outgoingData.length === 0 ? title : undefined, title: sum === 0 ? title : undefined,
animation: false, animation: false,
grid: { grid: {
top: 30, top: 30,
@@ -151,7 +158,7 @@ export class NodeFeeChartComponent implements OnInit {
`; `;
} }
}, },
xAxis: outgoingData.length === 0 ? undefined : { xAxis: sum === 0 ? undefined : {
type: 'category', type: 'category',
axisLine: { onZero: true }, axisLine: { onZero: true },
axisLabel: { axisLabel: {
@@ -163,7 +170,7 @@ export class NodeFeeChartComponent implements OnInit {
}, },
data: outgoingData.map(bucket => bucket.label) data: outgoingData.map(bucket => bucket.label)
}, },
legend: outgoingData.length === 0 ? undefined : { legend: sum === 0 ? undefined : {
padding: 10, padding: 10,
data: [ data: [
{ {
@@ -184,7 +191,7 @@ export class NodeFeeChartComponent implements OnInit {
}, },
], ],
}, },
yAxis: outgoingData.length === 0 ? undefined : [ yAxis: sum === 0 ? undefined : [
{ {
type: 'value', type: 'value',
axisLabel: { axisLabel: {
@@ -202,7 +209,7 @@ export class NodeFeeChartComponent implements OnInit {
}, },
}, },
], ],
series: outgoingData.length === 0 ? undefined : [ series: sum === 0 ? undefined : [
{ {
zlevel: 0, zlevel: 0,
name: $localize`Outgoing Fees`, name: $localize`Outgoing Fees`,

View File

@@ -75,13 +75,13 @@ export class NodeStatisticsChartComponent implements OnInit {
prepareChartOptions(data) { prepareChartOptions(data) {
let title: object; let title: object;
if (data.channels.length === 0) { if (data.channels.length < 2) {
title = { title = {
textStyle: { textStyle: {
color: 'grey', color: 'grey',
fontSize: 15 fontSize: 15
}, },
text: `Loading`, text: $localize`No data to display yet. Try again later.`,
left: 'center', left: 'center',
top: 'center' top: 'center'
}; };
@@ -135,14 +135,14 @@ export class NodeStatisticsChartComponent implements OnInit {
return tooltip; return tooltip;
} }
}, },
xAxis: data.channels.length === 0 ? undefined : { xAxis: data.channels.length < 2 ? undefined : {
type: 'time', type: 'time',
splitNumber: this.isMobile() ? 5 : 10, splitNumber: this.isMobile() ? 5 : 10,
axisLabel: { axisLabel: {
hideOverlap: true, hideOverlap: true,
} }
}, },
legend: data.channels.length === 0 ? undefined : { legend: data.channels.length < 2 ? undefined : {
padding: 10, padding: 10,
data: [ data: [
{ {
@@ -167,7 +167,7 @@ export class NodeStatisticsChartComponent implements OnInit {
'Capacity': true, 'Capacity': true,
} }
}, },
yAxis: data.channels.length === 0 ? undefined : [ yAxis: data.channels.length < 2 ? undefined : [
{ {
type: 'value', type: 'value',
axisLabel: { axisLabel: {
@@ -198,7 +198,7 @@ export class NodeStatisticsChartComponent implements OnInit {
} }
} }
], ],
series: data.channels.length === 0 ? [] : [ series: data.channels.length < 2 ? [] : [
{ {
zlevel: 1, zlevel: 1,
name: $localize`:@@807cf11e6ac1cde912496f764c176bdfdd6b7e19:Channels`, name: $localize`:@@807cf11e6ac1cde912496f764c176bdfdd6b7e19:Channels`,

View File

@@ -18,8 +18,5 @@
<div class="text-center loading-spinner" [class]="style" *ngIf="isLoading && !disableSpinner"> <div class="text-center loading-spinner" [class]="style" *ngIf="isLoading && !disableSpinner">
<div class="spinner-border text-light"></div> <div class="spinner-border text-light"></div>
</div> </div>
<div *ngIf="showIndexingInProgress" class="indexing-message">
<span class="badge badge-pill badge-warning" i18n="lightning.indexing-in-progress">Indexing in progress</span>
</div>
</ng-container> </ng-container>
</div> </div>

View File

@@ -36,7 +36,6 @@ export class NodesChannelsMap implements OnInit {
channelCurve = 0; channelCurve = 0;
nodeSize = 4; nodeSize = 4;
isLoading = false; isLoading = false;
showIndexingInProgress = false;
chartInstance = undefined; chartInstance = undefined;
chartOptions: EChartsOption = {}; chartOptions: EChartsOption = {};
@@ -205,26 +204,33 @@ export class NodesChannelsMap implements OnInit {
prepareChartOptions(nodes, channels) { prepareChartOptions(nodes, channels) {
let title: object; let title: object;
if (channels.length === 0 && !this.placeholder) { if (channels.length === 0) {
this.chartOptions = null; if (!this.placeholder) {
this.showIndexingInProgress = true; this.isLoading = false;
this.isLoading = false; title = {
return; textStyle: {
} color: 'white',
fontSize: 18
// empty map fallback },
if (channels.length === 0 && this.placeholder) { text: $localize`No data to display yet. Try again later.`,
title = { left: 'center',
textStyle: { top: 'center'
color: 'white', };
fontSize: 18 this.zoom = 1.5;
}, this.center = [0, 20];
text: $localize`No geolocation data available`, } else { // used for Node and Channel preview components
left: 'center', title = {
top: 'center' textStyle: {
}; color: 'white',
this.zoom = 1.5; fontSize: 18
this.center = [0, 20]; },
text: $localize`No geolocation data available`,
left: 'center',
top: 'center'
};
this.zoom = 1.5;
this.center = [0, 20];
}
} }
this.chartOptions = { this.chartOptions = {

View File

@@ -98,7 +98,6 @@ export class StateService {
mempoolBlockTransactions$ = new Subject<TransactionStripped[]>(); mempoolBlockTransactions$ = new Subject<TransactionStripped[]>();
mempoolBlockDelta$ = new Subject<MempoolBlockDelta>(); mempoolBlockDelta$ = new Subject<MempoolBlockDelta>();
txReplaced$ = new Subject<ReplacedTransaction>(); txReplaced$ = new Subject<ReplacedTransaction>();
txPurged$ = new Subject<boolean>();
utxoSpent$ = new Subject<object>(); utxoSpent$ = new Subject<object>();
difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1); difficultyAdjustment$ = new ReplaySubject<DifficultyAdjustment>(1);
mempoolTransactions$ = new Subject<Transaction>(); mempoolTransactions$ = new Subject<Transaction>();

View File

@@ -261,10 +261,6 @@ export class WebsocketService {
this.stateService.txReplaced$.next(response.txReplaced); this.stateService.txReplaced$.next(response.txReplaced);
} }
if (response.txPurged != null) {
this.stateService.txPurged$.next(response.txPurged);
}
if (response['mempool-blocks']) { if (response['mempool-blocks']) {
this.stateService.mempoolBlocks$.next(response['mempool-blocks']); this.stateService.mempoolBlocks$.next(response['mempool-blocks']);
} }