Merge branch 'master' into nymkappa/fix-one-bug-add-two-more

This commit is contained in:
nymkappa 2023-12-04 10:47:49 +09:00
commit e2eeaebfc7
No known key found for this signature in database
GPG Key ID: E155910B16E8BD04
25 changed files with 1035 additions and 583 deletions

View File

@ -15,11 +15,12 @@ class FeeApi {
constructor() { }
defaultFee = Common.isLiquid() ? 0.1 : 1;
minimumIncrement = Common.isLiquid() ? 0.1 : 1;
public getRecommendedFee(): RecommendedFees {
const pBlocks = projectedBlocks.getMempoolBlocks();
const mPool = mempool.getMempoolInfo();
const minimumFee = Math.ceil(mPool.mempoolminfee * 100000);
const minimumFee = this.roundUpToNearest(mPool.mempoolminfee * 100000, this.minimumIncrement);
const defaultMinFee = Math.max(minimumFee, this.defaultFee);
if (!pBlocks.length) {
@ -58,7 +59,11 @@ class FeeApi {
const multiplier = (pBlock.blockVSize - 500000) / 500000;
return Math.max(Math.round(useFee * multiplier), this.defaultFee);
}
return Math.ceil(useFee);
return this.roundUpToNearest(useFee, this.minimumIncrement);
}
private roundUpToNearest(value: number, nearest: number): number {
return Math.ceil(value / nearest) * nearest;
}
}

View File

@ -18,7 +18,7 @@ class Mempool {
private mempoolCache: { [txId: string]: MempoolTransactionExtended } = {};
private spendMap = new Map<string, MempoolTransactionExtended>();
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: Common.isLiquid() ? 0.00000100 : 0.00001000, minrelaytxfee: Common.isLiquid() ? 0.00000100 : 0.00001000 };
private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => void) | undefined;
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, mempoolSize: number, newTransactions: MempoolTransactionExtended[],

View File

@ -15,6 +15,13 @@ import bitcoinApi from '../bitcoin/bitcoin-api-factory';
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
import database from '../../database';
interface DifficultyBlock {
timestamp: number,
height: number,
bits: number,
difficulty: number,
}
class Mining {
private blocksPriceIndexingRunning = false;
public lastHashrateIndexingDate: number | null = null;
@ -421,6 +428,7 @@ class Mining {
indexedHeights[height] = true;
}
// gets {time, height, difficulty, bits} of blocks in ascending order of height
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
const genesisBlock: IEsploraApi.Block = await bitcoinApi.$getBlock(await bitcoinApi.$getBlockHash(0));
let currentDifficulty = genesisBlock.difficulty;
@ -436,41 +444,45 @@ class Mining {
});
}
const oldestConsecutiveBlock = await BlocksRepository.$getOldestConsecutiveBlock();
if (config.MEMPOOL.INDEXING_BLOCKS_AMOUNT !== -1) {
currentBits = oldestConsecutiveBlock.bits;
currentDifficulty = oldestConsecutiveBlock.difficulty;
if (!blocks?.length) {
// no blocks in database yet
return;
}
const oldestConsecutiveBlock = this.getOldestConsecutiveBlock(blocks);
currentBits = oldestConsecutiveBlock.bits;
currentDifficulty = oldestConsecutiveBlock.difficulty;
let totalBlockChecked = 0;
let timer = new Date().getTime() / 1000;
for (const block of blocks) {
// skip until the first block after the oldest consecutive block
if (block.height <= oldestConsecutiveBlock.height) {
continue;
}
// difficulty has changed between two consecutive blocks!
if (block.bits !== currentBits) {
if (indexedHeights[block.height] === true) { // Already indexed
if (block.height >= oldestConsecutiveBlock.height) {
currentDifficulty = block.difficulty;
currentBits = block.bits;
}
continue;
// skip if already indexed
if (indexedHeights[block.height] !== true) {
let adjustment = block.difficulty / currentDifficulty;
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
await DifficultyAdjustmentsRepository.$saveAdjustments({
time: block.time,
height: block.height,
difficulty: block.difficulty,
adjustment: adjustment,
});
totalIndexed++;
}
let adjustment = block.difficulty / currentDifficulty;
adjustment = Math.round(adjustment * 1000000) / 1000000; // Remove float point noise
await DifficultyAdjustmentsRepository.$saveAdjustments({
time: block.time,
height: block.height,
difficulty: block.difficulty,
adjustment: adjustment,
});
totalIndexed++;
if (block.height >= oldestConsecutiveBlock.height) {
currentDifficulty = block.difficulty;
currentBits = block.bits;
}
}
// update the current difficulty
currentDifficulty = block.difficulty;
currentBits = block.bits;
}
totalBlockChecked++;
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
@ -633,6 +645,17 @@ class Mining {
default: return 86400 * scale;
}
}
// Finds the oldest block in a consecutive chain back from the tip
// assumes `blocks` is sorted in ascending height order
private getOldestConsecutiveBlock(blocks: DifficultyBlock[]): DifficultyBlock {
for (let i = blocks.length - 1; i > 0; i--) {
if ((blocks[i].height - blocks[i - 1].height) > 1) {
return blocks[i];
}
}
return blocks[0];
}
}
export default new Mining();

View File

@ -94,9 +94,13 @@ class WebsocketHandler {
throw new Error('WebSocket.Server is not set');
}
this.wss.on('connection', (client: WebSocket) => {
this.wss.on('connection', (client: WebSocket, req) => {
this.numConnected++;
client.on('error', logger.info);
client['remoteAddress'] = req.headers['x-forwarded-for'] || req.socket?.remoteAddress || 'unknown';
client.on('error', (e) => {
logger.info(`websocket client error from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e));
client.close();
});
client.on('close', () => {
this.numDisconnected++;
});
@ -282,7 +286,8 @@ class WebsocketHandler {
client.send(serializedResponse);
}
} catch (e) {
logger.debug('Error parsing websocket message: ' + (e instanceof Error ? e.message : e));
logger.debug(`Error parsing websocket message from ${client['remoteAddress']}: ` + (e instanceof Error ? e.message : e));
client.close();
}
});
});

View File

@ -541,7 +541,7 @@ class BlocksRepository {
*/
public async $getBlocksDifficulty(): Promise<object[]> {
try {
const [rows]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty, bits FROM blocks`);
const [rows]: any[] = await DB.query(`SELECT UNIX_TIMESTAMP(blockTimestamp) as time, height, difficulty, bits FROM blocks ORDER BY height ASC`);
return rows;
} catch (e) {
logger.err('Cannot get blocks difficulty list from the db. Reason: ' + (e instanceof Error ? e.message : e));

File diff suppressed because it is too large Load Diff

View File

@ -74,9 +74,9 @@
"@angular/platform-server": "^16.2.2",
"@angular/router": "^16.2.2",
"@fortawesome/angular-fontawesome": "~0.13.0",
"@fortawesome/fontawesome-common-types": "~6.4.0",
"@fortawesome/fontawesome-svg-core": "~6.4.0",
"@fortawesome/free-solid-svg-icons": "~6.4.0",
"@fortawesome/fontawesome-common-types": "~6.5.1",
"@fortawesome/fontawesome-svg-core": "~6.5.1",
"@fortawesome/free-solid-svg-icons": "~6.5.1",
"@mempool/mempool.js": "2.3.0",
"@ng-bootstrap/ng-bootstrap": "^15.1.0",
"@types/qrcode": "~1.5.0",
@ -90,7 +90,7 @@
"ngx-infinite-scroll": "^16.0.0",
"qrcode": "1.5.1",
"rxjs": "~7.8.1",
"tinyify": "^4.0.0",
"tinyify": "^3.1.0",
"tlite": "^0.1.9",
"tslib": "~2.6.0",
"zone.js": "~0.13.1"

View File

@ -1,14 +1,14 @@
<div id="become-sponsor-container">
<div class="become-sponsor community">
<p style="font-weight: 700; font-size: 18px;">If you're an individual...</p>
<a href="https://mempool.space/sponsor" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.community-sponsor-button">Become a Community Sponsor</a>
<a href="https://mempool.space/sponsor" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.community-sponsor-button" (click)="onSponsorClick($event)">Become a Community Sponsor</a>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Exclusive swag</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Your avatar on the About page</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> And more coming soon :)</p>
</div>
<div class="become-sponsor enterprise">
<p style="font-weight: 700; font-size: 18px;">If you're a business...</p>
<a href="https://mempool.space/enterprise" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.enterprise-sponsor-button">Become an Enterprise Sponsor</a>
<a href="https://mempool.space/enterprise" class="btn" style="background-color: rgba(152, 88, 255, 0.75); box-shadow: 0px 0px 50px 5px rgba(152, 88, 255, 0.75)" i18n="about.enterprise-sponsor-button" (click)="onEnterpriseClick($event)">Become an Enterprise Sponsor</a>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Increased API limits</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> Co-branded instance</p>
<p class="sponsor-feature"><fa-icon [icon]="['fas', 'check']"></fa-icon> 99% service-level agreement</p>

View File

@ -9,6 +9,7 @@ import { Router, ActivatedRoute } from '@angular/router';
import { map, share, tap } from 'rxjs/operators';
import { ITranslators } from '../../interfaces/node-api.interface';
import { DOCUMENT } from '@angular/common';
import { EnterpriseService } from '../../services/enterprise.service';
@Component({
selector: 'app-about',
@ -33,6 +34,7 @@ export class AboutComponent implements OnInit {
private websocketService: WebsocketService,
private seoService: SeoService,
public stateService: StateService,
private enterpriseService: EnterpriseService,
private apiService: ApiService,
private router: Router,
private route: ActivatedRoute,
@ -121,4 +123,14 @@ export class AboutComponent implements OnInit {
unmutePromoVideo(): void {
this.promoVideo.nativeElement.muted = false;
}
onSponsorClick(e): boolean {
this.enterpriseService.goal(5);
return true;
}
onEnterpriseClick(e): boolean {
this.enterpriseService.goal(6);
return true;
}
}

View File

@ -27,6 +27,11 @@
<ng-container *ngIf="estimate">
<div [class]="{estimateDisabled: error}">
<div *ngIf="!estimate.hasAccess">
<div class="alert alert-mempool">You are currently on the wait list</div>
</div>
<h5>Your transaction</h5>
<div class="row">
<div class="col">
@ -230,7 +235,7 @@
<div class="row mb-3" *ngIf="isLoggedIn()">
<div class="col">
<div class="d-flex justify-content-end">
<div class="d-flex justify-content-end" *ngIf="estimate.hasAccess">
<button class="btn btn-sm btn-primary btn-success" style="width: 150px" (click)="accelerate()">Accelerate</button>
</div>
</div>

View File

@ -93,7 +93,7 @@ export class AcceleratePreviewComponent implements OnInit, OnDestroy, OnChanges
this.estimateSubscription.unsubscribe();
}
if (this.estimate.userBalance <= 0) {
if (this.estimate.hasAccess === true && this.estimate.userBalance <= 0) {
if (this.isLoggedIn()) {
this.error = `not_enough_balance`;
this.scrollToPreviewWithTimeout('mempoolError', 'center');

View File

@ -69,7 +69,7 @@ export class AddressPreviewComponent implements OnInit, OnDestroy {
this.addressString = this.addressString.toLowerCase();
}
this.seoService.setTitle($localize`:@@address.component.browser-title:Address: ${this.addressString}:INTERPOLATION:`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.address:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'} ${seoDescriptionNetwork(this.stateService.network)} address ${this.addressString}:INTERPOLATION:.`);
return (this.addressString.match(/04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}/)
? this.electrsApiService.getPubKeyAddress$(this.addressString)

View File

@ -219,13 +219,13 @@
<div class="box" *ngIf="!error && webGlEnabled && showAudit">
<div class="nav nav-tabs" *ngIf="isMobile && showAudit">
<a class="nav-link" [class.active]="mode === 'projected'"
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.expected">Expected</ng-container>&nbsp;&nbsp;<span class="badge badge-pill badge-warning" i18n="beta">beta</span></a>
fragment="projected" (click)="changeMode('projected')"><ng-container i18n="block.expected">Expected</ng-container></a>
<a class="nav-link" [class.active]="mode === 'actual'" i18n="block.actual"
fragment="actual" (click)="changeMode('actual')">Actual</a>
</div>
<div class="row">
<div class="col-sm">
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container> <span class="badge badge-pill badge-warning beta" i18n="beta">beta</span></h3>
<h3 class="block-subtitle" *ngIf="!isMobile"><ng-container i18n="block.expected-block">Expected Block</ng-container></h3>
<div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="86"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [auditHighlighting]="showAudit"

View File

@ -249,10 +249,8 @@ export class HashrateChartComponent implements OnInit {
for (const tick of ticks) {
if (tick.seriesIndex === 0) { // Hashrate
let hashrate = tick.data[1];
if (this.isMobile()) {
hashratePowerOfTen = selectPowerOfTen(tick.data[1]);
hashrate = Math.round(tick.data[1] / hashratePowerOfTen.divider);
}
hashratePowerOfTen = selectPowerOfTen(tick.data[1]);
hashrate = Math.round(tick.data[1] / hashratePowerOfTen.divider);
hashrateString = `${tick.marker} ${tick.seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s<br>`;
} else if (tick.seriesIndex === 1) { // Difficulty
let difficultyPowerOfTen = hashratePowerOfTen;
@ -260,18 +258,14 @@ export class HashrateChartComponent implements OnInit {
if (difficulty === null) {
difficultyString = `${tick.marker} ${tick.seriesName}: No data<br>`;
} else {
if (this.isMobile()) {
difficultyPowerOfTen = selectPowerOfTen(tick.data[1]);
difficulty = Math.round(tick.data[1] / difficultyPowerOfTen.divider);
}
difficultyPowerOfTen = selectPowerOfTen(tick.data[1]);
difficulty = tick.data[1] / difficultyPowerOfTen.divider;
difficultyString = `${tick.marker} ${tick.seriesName}: ${formatNumber(difficulty, this.locale, '1.2-2')} ${difficultyPowerOfTen.unit}<br>`;
}
} else if (tick.seriesIndex === 2) { // Hashrate MA
let hashrate = tick.data[1];
if (this.isMobile()) {
hashratePowerOfTen = selectPowerOfTen(tick.data[1]);
hashrate = Math.round(tick.data[1] / hashratePowerOfTen.divider);
}
hashratePowerOfTen = selectPowerOfTen(tick.data[1]);
hashrate = Math.round(tick.data[1] / hashratePowerOfTen.divider);
hashrateStringMA = `${tick.marker} ${tick.seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s`;
}
}

View File

@ -88,7 +88,7 @@ export class TransactionPreviewComponent implements OnInit, OnDestroy {
this.seoService.setTitle(
$localize`:@@bisq.transaction.browser-title:Transaction: ${this.txId}:INTERPOLATION:`
);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transaction with txid {txid}.`);
this.seoService.setDescription($localize`:@@meta.description.bitcoin.transaction:Get real-time status, addresses, fees, script info, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transaction with txid ${this.txId}.`);
this.resetTransaction();
return merge(
of(true),

View File

@ -83,9 +83,13 @@
<!-- Accelerator -->
<ng-container *ngIf="!tx?.status?.confirmed && showAccelerationSummary">
<div class="title mt-3">
<br>
<div class="title float-left">
<h2>Accelerate</h2>
</div>
<button type="button" class="btn btn-outline-info accelerator-toggle btn-sm float-right" (click)="showAccelerationSummary = false" i18n="hide-accelerator">Hide accelerator</button>
<div class="clearfix"></div>
<div class="box">
<app-accelerate-preview [tx]="tx" [scrollEvent]="scrollIntoAccelPreview"></app-accelerate-preview>
</div>
@ -519,7 +523,7 @@
<div class="effective-fee-container">
<app-fee-rate [fee]="tx.effectiveFeePerVsize"></app-fee-rate>
<ng-template [ngIf]="tx?.status?.confirmed">
<app-tx-fee-rating class="ml-2 mr-2" *ngIf="tx.fee || tx.effectiveFeePerVsize" [tx]="tx"></app-tx-fee-rating>
<app-tx-fee-rating class="ml-2 mr-2 effective-fee-rating" *ngIf="tx.fee || tx.effectiveFeePerVsize" [tx]="tx"></app-tx-fee-rating>
</ng-template>
</div>
<button *ngIf="cpfpInfo.bestDescendant || cpfpInfo.descendants?.length || cpfpInfo.ancestors?.length" type="button" class="btn btn-outline-info btn-sm btn-small-height float-right" (click)="showCpfpDetails = !showCpfpDetails">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>

View File

@ -152,6 +152,16 @@
@media (min-width: 768px){
display: inline-block;
}
@media (max-width: 425px){
display: flex;
flex-direction: column;
}
}
.effective-fee-rating {
@media (max-width: 767px){
margin-right: 0px !important;
}
}
.title {
@ -169,7 +179,7 @@
}
}
.details-button, .flow-toggle {
.details-button, .flow-toggle, .accelerator-toggle {
margin-top: -5px;
margin-left: 10px;
@media (min-width: 768px){

View File

@ -2922,7 +2922,7 @@ export const restApiDocsData = [
fragment: "get-blocks-bulk",
title: "GET Blocks (Bulk)",
description: {
default: "<p>Returns details on the range of blocks between <code>:minHeight</code> and <code>:maxHeight</code>, inclusive, up to 10 blocks. If <code>:maxHeight</code> is not specified, it defaults to the current tip.</p><p>To return data for more than 10 blocks, consider becoming an <a href='/enterprise'>enterprise sponsor</a>.</p>"
default: "<p>Returns details on the range of blocks between <code>:minHeight</code> and <code>:maxHeight</code>, inclusive, up to 10 blocks. If <code>:maxHeight</code> is not specified, it defaults to the current tip.</p><p>To return data for more than 10 blocks, consider becoming an <a href='https://mempool.space/enterprise'>enterprise sponsor</a>.</p>"
},
urlString: "/v1/blocks-bulk/:minHeight[/:maxHeight]",
showConditions: bitcoinNetworks,

View File

@ -40,7 +40,7 @@
<div class="doc-content">
<p class="doc-welcome-note">Below is a reference for the {{ network.val === '' ? 'Bitcoin' : network.val.charAt(0).toUpperCase() + network.val.slice(1) }} <ng-container i18n="api-docs.title">REST API service</ng-container>.</p>
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an <a href="/enterprise">enterprise sponsorship</a> if you need higher API limits.</p>
<p class="doc-welcome-note api-note" *ngIf="officialMempoolInstance">Note that we enforce rate limits. If you exceed these limits, you will get an HTTP 429 error. If you repeatedly exceed the limits, you may be banned from accessing the service altogether. Consider an <a href="https://mempool.space/enterprise">enterprise sponsorship</a> if you need higher API limits.</p>
<div class="doc-item-container" *ngFor="let item of restDocs">
<h3 *ngIf="( item.type === 'category' ) && ( item.showConditions.indexOf(network.val) > -1 )">{{ item.title }}</h3>
@ -123,7 +123,7 @@
<p>{{electrsPort}}</p>
<p class="subtitle">SSL</p>
<p>Enabled</p>
<p class="note" *ngIf="network.val !== 'signet'">Electrum RPC interface for Bitcoin Signet is <a href="/signet/docs/api/electrs">publicly available</a>. Electrum RPC interface for all other networks is available to <a href='/enterprise'>sponsors</a> only—whitelisting is required.</p>
<p class="note" *ngIf="network.val !== 'signet'">Electrum RPC interface for Bitcoin Signet is <a href="/signet/docs/api/electrs">publicly available</a>. Electrum RPC interface for all other networks is available to <a href='https://mempool.space/enterprise'>sponsors</a> only—whitelisting is required.</p>
</div>
</div>
</div>
@ -288,7 +288,7 @@
</ng-template>
<ng-template type="host-my-own-instance-server">
<p>You can manually install Mempool on your own server, but this requires advanced sysadmin skills since you will be manually configuring everything. You could also use our <a href="https://github.com/mempool/mempool/tree/master/docker" target="_blank">Docker images</a>.</p><p>In any case, we only provide support for manual deployments to <a href="/enterprise">enterprise sponsors</a>.</p>
<p>You can manually install Mempool on your own server, but this requires advanced sysadmin skills since you will be manually configuring everything. You could also use our <a href="https://github.com/mempool/mempool/tree/master/docker" target="_blank">Docker images</a>.</p><p>In any case, we only provide support for manual deployments to <a href="https://mempool.space/enterprise">enterprise sponsors</a>.</p>
<p>For casual users, we strongly suggest installing Mempool using one of the <a href="https://github.com/mempool/mempool#one-click-installation" target="_blank">1-click install methods</a>.</p>
</ng-template>

View File

@ -25,7 +25,7 @@ export class LightningDashboardComponent implements OnInit, AfterViewInit {
ngOnInit(): void {
this.seoService.setTitle($localize`:@@142e923d3b04186ac6ba23387265d22a2fa404e0:Lightning Explorer`);
this.seoService.setDescription($localize`:@@meta.description.lightning.dashboard:Get stats on the Lightning network (aggregate capacity, connectivity, etc) and Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).`);
this.seoService.setDescription($localize`:@@meta.description.lightning.dashboard:Get stats on the Lightning network (aggregate capacity, connectivity, etc), Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).`);
this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
this.statistics$ = this.lightningApiService.getLatestStatistics$().pipe(share());

View File

@ -20,7 +20,7 @@ export class NodesRankingsDashboard implements OnInit {
ngOnInit(): void {
this.seoService.setTitle($localize`Top lightning nodes`);
this.seoService.setDescription($localize`:@@meta.description.lightning.rankings-dashboard:See top the Lightning network nodes ranked by liquidity, connectivity, and age.`);
this.seoService.setDescription($localize`:@@meta.description.lightning.rankings-dashboard:See the top Lightning network nodes ranked by liquidity, connectivity, and age.`);
this.nodesRanking$ = this.lightningApiService.getNodesRanking$().pipe(share());
}
}

View File

@ -3,6 +3,7 @@ import { Inject, Injectable } from '@angular/core';
import { ApiService } from './api.service';
import { SeoService } from './seo.service';
import { StateService } from './state.service';
import { ActivatedRoute } from '@angular/router';
@Injectable({
providedIn: 'root'
@ -11,12 +12,15 @@ export class EnterpriseService {
exclusiveHostName = '.mempool.space';
subdomain: string | null = null;
info: object = {};
statsUrl: string;
siteId: number;
constructor(
@Inject(DOCUMENT) private document: Document,
private apiService: ApiService,
private seoService: SeoService,
private stateService: StateService,
private activatedRoute: ActivatedRoute,
) {
const subdomain = this.document.location.hostname.indexOf(this.exclusiveHostName) > -1
&& this.document.location.hostname.split(this.exclusiveHostName)[0] || false;
@ -88,16 +92,63 @@ export class EnterpriseService {
}
}
this.statsUrl = statsUrl;
this.siteId = siteId;
// @ts-ignore
const _paq = window._paq = window._paq || [];
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
_paq.push(['setTrackerUrl', statsUrl+'m.php']);
_paq.push(['setSiteId', siteId.toString()]);
const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.src=statsUrl+'m.js'; s.parentNode.insertBefore(g,s);
})();
if (window._paq && window['Matomo']) {
window['Matomo'].addTracker(statsUrl+'m.php', siteId.toString());
const matomo = this.getMatomo();
matomo.setDocumentTitle(this.seoService.getTitle());
matomo.setCustomUrl(this.getCustomUrl());
matomo.disableCookies();
matomo.trackPageView();
matomo.enableLinkTracking();
} else {
// @ts-ignore
const alreadyInitialized = !!window._paq;
// @ts-ignore
const _paq = window._paq = window._paq || [];
_paq.push(['setDocumentTitle', this.seoService.getTitle()]);
_paq.push(['setCustomUrl', this.getCustomUrl()]);
_paq.push(['disableCookies']);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
if (alreadyInitialized) {
_paq.push(['addTracker', statsUrl+'m.php', siteId.toString()]);
} else {
(function() {
_paq.push(['setTrackerUrl', statsUrl+'m.php']);
_paq.push(['setSiteId', siteId.toString()]);
const d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
// @ts-ignore
g.type='text/javascript'; g.async=true; g.src=statsUrl+'m.js'; s.parentNode.insertBefore(g,s);
})();
}
}
}
private getMatomo() {
if (this.siteId != null) {
return window['Matomo']?.getTracker(this.statsUrl+'m.php', this.siteId);
}
}
goal(id: number) {
// @ts-ignore
this.getMatomo()?.trackGoal(id);
}
private getCustomUrl(): string {
let url = window.location.origin + '/';
let route = this.activatedRoute;
while (route) {
const segment = route?.routeConfig?.path;
if (segment && segment.length) {
url += segment + '/';
}
route = route.firstChild;
}
return url;
}
}

View File

@ -76,17 +76,22 @@ export class WebsocketService {
this.stateService.resetChainTip();
this.websocketSubject.complete();
this.subscription.unsubscribe();
this.websocketSubject = webSocket<WebsocketResponse>(
this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : '')
);
this.startSubscription();
this.reconnectWebsocket();
});
}
}
reconnectWebsocket(retrying = false, hasInitData = false) {
console.log('reconnecting websocket');
this.websocketSubject.complete();
this.subscription.unsubscribe();
this.websocketSubject = webSocket<WebsocketResponse>(
this.webSocketUrl.replace('{network}', this.network ? '/' + this.network : '')
);
this.startSubscription(retrying, hasInitData);
}
startSubscription(retrying = false, hasInitData = false) {
if (!hasInitData) {
this.stateService.isLoadingWebSocket$.next(true);
@ -237,7 +242,7 @@ export class WebsocketService {
this.goneOffline = true;
this.stateService.connectionState$.next(0);
window.setTimeout(() => {
this.startSubscription(true);
this.reconnectWebsocket(true);
}, retryDelay);
}

View File

@ -2,12 +2,12 @@ import { Component, Input, OnInit } from "@angular/core";
import { DomSanitizer, SafeHtml } from "@angular/platform-browser";
const MempoolErrors = {
'internal_server_error': `Something went wrong, please try again later`,
'acceleration_duplicated': `This transaction has already been accelerated.`,
'acceleration_outbid': `Your fee delta is too low.`,
'cannot_accelerate_tx': `Cannot accelerate this transaction.`,
'cannot_decode_raw_tx': `Cannot decode this raw transaction.`,
'cannot_fetch_raw_tx': `Cannot find this transaction.`,
'database_error': `Something went wrong. Please try again later.`,
'high_sigop_tx': `This transaction cannot be accelerated.`,
'invalid_acceleration_request': `This acceleration request is not valid.`,
'invalid_tx_dependencies': `This transaction dependencies are not valid.`,

View File

@ -1698,7 +1698,7 @@
</context-group>
</trans-unit>
<trans-unit id="meta.description.bitcoin.address" datatype="html">
<source>See mempool transactions, confirmed transactions, balance, and more for <x id="PH" equiv-text="this.stateService.network===&apos;liquid&apos;||this.stateService.network===&apos;liquidtestnet&apos;?&apos;Liquid&apos;:&apos;Bitcoin&apos;"/><x id="PH_1" equiv-text="seoDescriptionNetwork(this.stateService.network)"/> address <x id="INTERPOLATION" equiv-text="this.addressString"/>.</source>
<source>See mempool transactions, confirmed transactions, balance, and more for <x id="PH" equiv-text="this.stateService.network===&apos;liquid&apos;||this.stateService.network===&apos;liquidtestnet&apos;?&apos;Liquid&apos;:&apos;Bitcoin&apos;"/> <x id="PH_1" equiv-text="seoDescriptionNetwork(this.stateService.network)"/> address <x id="INTERPOLATION" equiv-text="this.addressString"/>.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/address/address-preview.component.ts</context>
<context context-type="linenumber">72</context>
@ -2256,7 +2256,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts</context>
<context context-type="linenumber">215</context>
<context context-type="linenumber">216</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-map/nodes-map.component.ts</context>
@ -3774,7 +3774,7 @@
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.html</context>
<context context-type="linenumber">6</context>
<context context-type="linenumber">19</context>
</context-group>
<note priority="1" from="description">lightning.nodes-channels-world-map</note>
</trans-unit>
@ -5024,7 +5024,7 @@
<note priority="1" from="description">transactions-list.coinbase</note>
</trans-unit>
<trans-unit id="meta.description.bitcoin.transaction" datatype="html">
<source>Get real-time status, addresses, fees, script info, and more for <x id="PH" equiv-text="this.stateService.network===&apos;liquid&apos;||this.stateService.network===&apos;liquidtestnet&apos;?&apos;Liquid&apos;:&apos;Bitcoin&apos;"/><x id="PH_1" equiv-text="seoDescriptionNetwork(this.stateService.network)"/> transaction with txid {txid}.</source>
<source>Get real-time status, addresses, fees, script info, and more for <x id="PH" equiv-text="this.stateService.network===&apos;liquid&apos;||this.stateService.network===&apos;liquidtestnet&apos;?&apos;Liquid&apos;:&apos;Bitcoin&apos;"/><x id="PH_1" equiv-text="seoDescriptionNetwork(this.stateService.network)"/> transaction with txid <x id="PH_2" equiv-text="this.txId"/>.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/components/transaction/transaction-preview.component.ts</context>
<context context-type="linenumber">91</context>
@ -6629,7 +6629,7 @@
<note priority="1" from="description">lightning.connectivity-ranking</note>
</trans-unit>
<trans-unit id="meta.description.lightning.dashboard" datatype="html">
<source>Get stats on the Lightning network (aggregate capacity, connectivity, etc) and Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).</source>
<source>Get stats on the Lightning network (aggregate capacity, connectivity, etc), Lightning nodes (channels, liquidity, etc) and Lightning channels (status, fees, etc).</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/lightning-dashboard/lightning-dashboard.component.ts</context>
<context context-type="linenumber">28</context>
@ -6886,7 +6886,7 @@
<source>(Tor nodes excluded)</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.html</context>
<context context-type="linenumber">8</context>
<context context-type="linenumber">21</context>
</context-group>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-map/nodes-map.component.html</context>
@ -6906,21 +6906,21 @@
<source>Lightning Nodes Channels World Map</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts</context>
<context context-type="linenumber">68</context>
<context context-type="linenumber">69</context>
</context-group>
</trans-unit>
<trans-unit id="meta.description.lightning.node-map" datatype="html">
<source>See the channels of non-Tor Lightning network nodes visualized on a world map. Hover/tap on points on the map for node names and details.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts</context>
<context context-type="linenumber">69</context>
<context context-type="linenumber">70</context>
</context-group>
</trans-unit>
<trans-unit id="4390631969351833104" datatype="html">
<source>No geolocation data available</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts</context>
<context context-type="linenumber">227</context>
<context context-type="linenumber">228</context>
</context-group>
</trans-unit>
<trans-unit id="a4d393ee035f4225083c22cc3909b26a05a87528" datatype="html">
@ -7289,7 +7289,7 @@
</context-group>
</trans-unit>
<trans-unit id="meta.description.lightning.rankings-dashboard" datatype="html">
<source>See top the Lightning network nodes ranked by liquidity, connectivity, and age.</source>
<source>See the top Lightning network nodes ranked by liquidity, connectivity, and age.</source>
<context-group purpose="location">
<context context-type="sourcefile">src/app/lightning/nodes-rankings-dashboard/nodes-rankings-dashboard.component.ts</context>
<context context-type="linenumber">23</context>