Merge branch 'master' into nymkappa/bugfix/channel-geo

This commit is contained in:
Felipe Knorr Kuhn
2023-02-19 13:56:11 -08:00
committed by GitHub
23 changed files with 169 additions and 85 deletions

View File

@@ -64,7 +64,7 @@ describe('Mainnet', () => {
it('loads the status screen', () => {
cy.visit('/status');
cy.get('#mempool-block-0').should('be.visible');
cy.get('[id^="bitcoin-block-"]').should('have.length', 8);
cy.get('[id^="bitcoin-block-"]').should('have.length', 22);
cy.get('.footer').should('be.visible');
cy.get('.row > :nth-child(1)').invoke('text').then((text) => {
expect(text).to.match(/Incoming transactions.* vB\/s/);
@@ -219,11 +219,11 @@ describe('Mainnet', () => {
describe('blocks navigation', () => {
describe('keyboard events', () => {
it('loads first blockchain blocks visible and keypress arrow right', () => {
it('loads first blockchain block visible and keypress arrow right', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.get('[data-cy="bitcoin-block-offset-0-index-0"]').click().then(() => {
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
cy.waitForPageIdle();
@@ -233,11 +233,11 @@ describe('Mainnet', () => {
});
});
it('loads first blockchain blocks visible and keypress arrow left', () => {
it('loads first blockchain block visible and keypress arrow left', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.get('[data-cy="bitcoin-block-offset-0-index-0"]').click().then(() => {
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');
@@ -246,11 +246,11 @@ describe('Mainnet', () => {
});
});
it('loads last blockchain blocks and keypress arrow right', () => {
it.skip('loads last blockchain block and keypress arrow right', () => { //Skip for now as "last" doesn't really work with infinite scrolling
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-4 > a').click().then(() => {
cy.get('bitcoin-block-offset-0-index-7').click().then(() => {
cy.waitForPageIdle();
// block 6
@@ -309,7 +309,7 @@ describe('Mainnet', () => {
cy.viewport('macbook-16');
cy.visit('/');
cy.waitForSkeletonGone();
cy.get('.blockchain-blocks-0 > a').click().then(() => {
cy.get('[data-cy="bitcoin-block-offset-0-index-0"]').click().then(() => {
cy.waitForPageIdle();
cy.get('[ngbtooltip="Next Block"] > .ng-fa-icon > .svg-inline--fa').should('not.exist');
cy.get('[ngbtooltip="Previous Block"] > .ng-fa-icon > .svg-inline--fa').should('be.visible');

View File

@@ -72,22 +72,10 @@ export const chartColors = [
];
export const poolsColor = {
'foundryusa': '#D81B60',
'antpool': '#8E24AA',
'f2pool': '#5E35B1',
'poolin': '#3949AB',
'binancepool': '#1E88E5',
'viabtc': '#039BE5',
'btccom': '#00897B',
'braiinspool': '#00ACC1',
'sbicrypto': '#43A047',
'marapool': '#7CB342',
'luxor': '#C0CA33',
'unknown': '#FDD835',
'okkong': '#FFB300',
}
'unknown': '#9C9C9C',
};
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
export const feeLevels = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
export interface Language {

View File

@@ -1,4 +1,4 @@
<div class="container-xl">
<div class="container-xl" [class.liquid-address]="network === 'liquid' || network === 'liquidtestnet'">
<div class="title-address">
<h1 i18n="shared.address">Address</h1>
<div class="tx-link">
@@ -15,7 +15,7 @@
<div class="row">
<div class="col-md">
<table class="table table-borderless table-striped">
<table class="table table-borderless table-striped address-table">
<tbody>
<tr *ngIf="addressInfo && addressInfo.unconfidential">
<td i18n="address.unconfidential">Unconfidential</td>

View File

@@ -91,3 +91,20 @@ h1 {
margin-bottom: 10px;
}
}
.liquid-address {
.address-table {
table-layout: fixed;
tr td:first-child {
width: 170px;
}
tr td:last-child {
width: 80%;
}
}
.qrcode-col {
flex-grow: 0.5;
}
}

View File

@@ -90,6 +90,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
this.prepareChartOptions({
sizes: data.sizes.map(val => [val.timestamp * 1000, val.avgSize / 1000000, val.avgHeight]),
weights: data.weights.map(val => [val.timestamp * 1000, val.avgWeight / 1000000, val.avgHeight]),
sizePerWeight: data.weights.map((val, i) => [val.timestamp * 1000, data.sizes[i].avgSize / (val.avgWeight / 4), val.avgHeight]),
});
this.isLoading = false;
}),
@@ -124,6 +125,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
color: [
'#FDD835',
'#D81B60',
'#039BE5',
],
grid: {
top: 30,
@@ -153,6 +155,8 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MB`;
} else if (tick.seriesIndex === 1) { // Weight
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} MWU`;
} else if (tick.seriesIndex === 2) { // Size per weight
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.2-2')} B/vB`;
}
tooltip += `<br>`;
}
@@ -192,10 +196,19 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
},
icon: 'roundRect',
},
{
name: $localize`Size per weight`,
inactiveColor: 'rgb(110, 112, 121)',
textStyle: {
color: 'white',
},
icon: 'roundRect',
},
],
selected: JSON.parse(this.storageService.getValue('sizes_weights_legend')) ?? {
'Size': true,
'Weight': true,
'Size per weight': true,
}
},
yAxis: data.sizes.length === 0 ? undefined : [
@@ -262,6 +275,18 @@ export class BlockSizesWeightsGraphComponent implements OnInit {
lineStyle: {
width: 2,
}
},
{
zlevel: 1,
yAxisIndex: 0,
name: $localize`Size per weight`,
showSymbol: false,
symbol: 'none',
data: data.sizePerWeight,
type: 'line',
lineStyle: {
width: 2,
}
}
],
dataZoom: [{

View File

@@ -1,64 +1,86 @@
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr" [style.left]="static ? (offset || 0) + 'px' : null" *ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div class="blocks-container blockchain-blocks-container" [class.time-ltr]="timeLtr"
[style.left]="static ? (offset || 0) + 'px' : null"
*ngIf="(loadingBlocks$ | async) === false; else loadingBlocksTemplate">
<div *ngFor="let block of blocks; let i = index; trackBy: trackByBlocksFn">
<ng-container *ngIf="block && !block.loading && !block.placeholder; else placeholderBlock">
<div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]" [class.blink-bg]="(specialBlocks[block.height] !== undefined)">
<div [attr.data-cy]="'bitcoin-block-offset-' + offset + '-index-' + i"
class="text-center bitcoin-block mined-block blockchain-blocks-offset-{{ offset }}-index-{{ i }}"
id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"
[class.blink-bg]="(specialBlocks[block.height] !== undefined)">
<a draggable="false" [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }"
class="blockLink" [ngClass]="{'disabled': (this.stateService.blockScrolling$ | async)}">&nbsp;</a>
<div [attr.data-cy]="'bitcoin-block-' + i + '-height'" class="block-height">
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height }}</a>
<a [routerLink]="['/block/' | relativeUrl, block.id]" [state]="{ data: { block: block } }">{{ block.height
}}</a>
</div>
<div class="block-body">
<div [attr.data-cy]="'bitcoin-block-' + i + '-fees'" class="fees">
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
~{{ block?.extras?.medianFee | number:feeRounding }} <ng-container
i18n="shared.sat-vbyte|sat/vB">sat/vB</ng-container>
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="block?.extras?.feeRange">
{{ 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 [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
*ngIf="block?.extras?.feeRange">
{{ 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 [attr.data-cy]="'bitcoin-block-' + i + '-fee-span'" class="fee-span" *ngIf="!block?.extras?.feeRange">
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-fee-span'" class="fee-span"
*ngIf="!block?.extras?.feeRange">
&nbsp;
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-total-fees'" *ngIf="showMiningInfo" class="block-size">
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-total-fees'" *ngIf="showMiningInfo"
class="block-size">
<app-amount [satoshis]="block.extras?.totalFees ?? 0" digitsInfo="1.2-3" [noFiat]="true"></app-amount>
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + 'block-size'" *ngIf="!showMiningInfo" class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div>
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + 'block-size'" *ngIf="!showMiningInfo"
class="block-size" [innerHTML]="'&lrm;' + (block.size | bytes: 2)"></div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-transactions'" class="transaction-count">
<ng-container *ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }} transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }} transactions</ng-template>
<ng-container
*ngTemplateOutlet="block.tx_count === 1 ? transactionsSingular : transactionsPlural; context: {$implicit: block.tx_count | number}"></ng-container>
<ng-template #transactionsSingular let-i i18n="shared.transaction-count.singular">{{ i }}
transaction</ng-template>
<ng-template #transactionsPlural let-i i18n="shared.transaction-count.plural">{{ i }}
transactions</ng-template>
</div>
<div [attr.data-cy]="'bitcoin-block-' + i + '-time'" class="time-difference"><app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-time'" class="time-difference">
<app-time-since [time]="block.timestamp" [fastRender]="true"></app-time-since></div>
</div>
<div class="animated" [class]="showMiningInfo ? 'show' : 'hide'" *ngIf="block.extras?.pool != undefined">
<a [attr.data-cy]="'bitcoin-block-' + i + '-pool'" class="badge badge-primary" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge badge-primary"
[routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
{{ block.extras.pool.name}}</a>
</div>
</div>
</ng-container>
<ng-template #placeholderBlock>
<ng-container *ngIf="block && block.placeholder; else loadingBlock">
<div [attr.data-cy]="'bitcoin-block-' + i" class="text-center bitcoin-block mined-block placeholder-block blockchain-blocks-{{ i }}" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
<div [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i"
class="text-center bitcoin-block mined-block placeholder-block blockchain-blocks-{{ i }}"
id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]">
</div>
</ng-container>
</ng-template>
<ng-template #loadingBlock>
<ng-container *ngIf="block && block.loading">
<div class="flashing">
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="blockStyles[i]"></div>
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}"
[ngStyle]="blockStyles[i]"></div>
</div>
</ng-container>
</ng-template>
</div>
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="arrowTransition" [ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
<div [hidden]="!arrowVisible" id="arrow-up" [style.transition]="arrowTransition"
[ngStyle]="{'left': arrowLeftPx + 'px' }"></div>
</div>
<ng-template #loadingBlocksTemplate>
<div class="blocks-container" [class.time-ltr]="timeLtr">
<div class="flashing">
<div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn" >
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}" [ngStyle]="emptyBlockStyles[i]"></div>
<div *ngFor="let block of emptyBlocks; let i = index; trackBy: trackByBlocksFn">
<div class="text-center bitcoin-block mined-block" id="bitcoin-block-{{ block.height }}"
[ngStyle]="emptyBlockStyles[i]"></div>
</div>
</div>
</div>
</ng-template>

View File

@@ -123,8 +123,10 @@
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
</ngb-pagination>
<div class="clearfix"></div>
<br>
<ng-template [ngIf]="!widget">
<div class="clearfix"></div>
<br>
</ng-template>
</div>
</div>

View File

@@ -176,7 +176,7 @@ export class PoolRankingComponent implements OnInit {
// 'Other'
data.push({
itemStyle: {
color: 'grey',
color: '#6b6b6b',
},
value: totalShareOther,
name: 'Other' + (isMobile() ? `` : ` (${totalShareOther.toFixed(2)}%)`),

View File

@@ -216,8 +216,8 @@
<ng-template type="why-dont-fee-ranges-match">
<p>Mempool aims to show you the <i>effective feerate</i> range for blocks—how much would you actually need to pay to get a transaction included in a block.</p>
<p>A transaction's effective feerate is not always the same as the feerate explicitly set for it. For example, if you see a 1 s/vb transaction in a block with a displayed feerate range of 5 s/vb to 72 s/vb, chances are that 1 s/vb transaction had a high-feerate child transaction that boosted its effective feerate to 5 s/vb or higher (this is how CPFP fee-bumping works). In such a case, it would be misleading to use 1 s/vb as the lower bound of the block's feerate range because it actually required more than 1 s/vb to confirm that transaction in that block.</p>
<p>For unconfirmed CPFP transactions, Mempool will show the effective feerate (along with descendent & ancestor transaction information) on the transaction page. For confirmed transactions, CPFP relationships are not stored, so this additional information is not shown.</p>
<p>A transaction's effective feerate is not always the same as the feerate explicitly set for it. For example, if you see a 1 s/vb transaction in a block with a displayed feerate range of 5 s/vb to 72 s/vb, chances are that 1 s/vb transaction had a high-feerate child transaction that boosted its effective feerate to 5 s/vb or higher (this is how CPFP fee-bumping works). In such a case, it would be misleading to use 1 s/vb as the lower bound of the block's feerate range since it actually required more than 1 s/vb to confirm that transaction in that block.</p>
<p>You can find a transaction's feerate on its transaction details page. If the transaction has any CPFP relationships, the page will also show the transaction's effective feerate along with links to descendent and/or ancestor transactions.</p>
</ng-template>
<ng-template type="how-do-block-audits-work">

View File

@@ -151,6 +151,19 @@ export interface RewardStats {
totalTx: number;
}
export interface BlockSizesAndWeights {
sizes: {
timestamp: number;
avgHeight: number;
avgSize: number;
}[];
weights: {
timestamp: number;
avgHeight: number;
avgWeight: number;
}[];
}
export interface AuditScore {
hash: string;
matchRate?: number;

View File

@@ -103,6 +103,5 @@
</div>
<br>
</div>
<br>
</div>

View File

@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { HttpClient, HttpParams, HttpResponse } from '@angular/common/http';
import { CpfpInfo, OptimizedMempoolStats, AddressInformation, LiquidPegs, ITranslators,
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore } from '../interfaces/node-api.interface';
PoolStat, BlockExtended, TransactionStripped, RewardStats, AuditScore, BlockSizesAndWeights } from '../interfaces/node-api.interface';
import { Observable } from 'rxjs';
import { StateService } from './state.service';
import { WebsocketResponse } from '../interfaces/websocket.interface';
@@ -222,8 +222,8 @@ export class ApiService {
);
}
getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable<any> {
return this.httpClient.get<any[]>(
getHistoricalBlockSizesAndWeights$(interval: string | undefined) : Observable<HttpResponse<BlockSizesAndWeights>> {
return this.httpClient.get<BlockSizesAndWeights>(
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/sizes-weights` +
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
);