Merge branch 'master' into mononaut/fix-unfurl-cpfp-badge

This commit is contained in:
wiz 2023-04-03 11:40:47 +09:00 committed by GitHub
commit 675ecc608c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 4571 additions and 2206 deletions

6613
backend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -34,35 +34,35 @@
"prettier": "./node_modules/.bin/prettier --write \"src/**/*.{js,ts}\""
},
"dependencies": {
"@babel/core": "^7.20.12",
"@babel/core": "^7.21.3",
"@mempool/electrum-client": "1.1.9",
"@types/node": "^16.18.11",
"@types/node": "^18.15.3",
"axios": "~0.27.2",
"bitcoinjs-lib": "~6.1.0",
"crypto-js": "~4.1.1",
"express": "~4.18.2",
"maxmind": "~4.3.8",
"mysql2": "~2.3.3",
"mysql2": "~3.2.0",
"node-worker-threads-pool": "~1.5.1",
"socks-proxy-agent": "~7.0.0",
"typescript": "~4.7.4",
"ws": "~8.11.0"
"ws": "~8.13.0"
},
"devDependencies": {
"@babel/core": "^7.20.7",
"@babel/core": "^7.21.3",
"@babel/code-frame": "^7.18.6",
"@types/compression": "^1.7.2",
"@types/crypto-js": "^4.1.1",
"@types/express": "^4.17.15",
"@types/jest": "^29.2.5",
"@types/jest": "^29.5.0",
"@types/ws": "~8.5.4",
"@typescript-eslint/eslint-plugin": "^5.48.1",
"@typescript-eslint/parser": "^5.48.1",
"eslint": "^8.31.0",
"eslint-config-prettier": "^8.5.0",
"jest": "^29.3.1",
"prettier": "^2.8.2",
"ts-jest": "^29.0.3",
"@typescript-eslint/eslint-plugin": "^5.55.0",
"@typescript-eslint/parser": "^5.55.0",
"eslint": "^8.36.0",
"eslint-config-prettier": "^8.7.0",
"jest": "^29.5.0",
"prettier": "^2.8.4",
"ts-jest": "^29.0.5",
"ts-node": "^10.9.1"
}
}

View File

@ -410,12 +410,13 @@ class Blocks {
try {
// Get all indexed block hash
const unindexedBlockHeights = await blocksRepository.$getCPFPUnindexedBlocks();
logger.info(`Indexing cpfp data for ${unindexedBlockHeights.length} blocks`);
if (!unindexedBlockHeights?.length) {
return;
}
logger.info(`Indexing cpfp data for ${unindexedBlockHeights.length} blocks`);
// Logging
let count = 0;
let countThisRun = 0;
@ -616,7 +617,7 @@ class Blocks {
priceId: lastestPriceId,
}]);
} else {
logger.info(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining);
logger.debug(`Cannot save block price for ${blockExtended.height} because the price updater hasnt completed yet. Trying again in 10 seconds.`, logger.tags.mining);
setTimeout(() => {
indexer.runSingleTask('blocksPrices');
}, 10000);

View File

@ -164,7 +164,7 @@ class DiskCache {
}
}
} catch (e) {
logger.info('Error parsing ' + fileName + '. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
logger.err('Error parsing ' + fileName + '. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
}
}

View File

@ -452,7 +452,7 @@ class Mining {
const elapsedSeconds = Math.max(1, Math.round((new Date().getTime() / 1000) - timer));
if (elapsedSeconds > 5) {
const progress = Math.round(totalBlockChecked / blocks.length * 100);
logger.info(`Indexing difficulty adjustment at block #${block.height} | Progress: ${progress}%`, logger.tags.mining);
logger.debug(`Indexing difficulty adjustment at block #${block.height} | Progress: ${progress}%`, logger.tags.mining);
timer = new Date().getTime() / 1000;
}
}
@ -558,8 +558,10 @@ class Mining {
currentBlockHeight -= 10000;
}
if (totalIndexed) {
logger.info(`Indexing missing coinstatsindex data completed`, logger.tags.mining);
if (totalIndexed > 0) {
logger.info(`Indexing missing coinstatsindex data completed. Indexed ${totalIndexed}`, logger.tags.mining);
} else {
logger.debug(`Indexing missing coinstatsindex data completed. Indexed 0.`, logger.tags.mining);
}
}

View File

@ -276,7 +276,7 @@ class Server {
if (!this.warnedHeapCritical && this.maxHeapSize > warnThreshold) {
this.warnedHeapCritical = true;
logger.warn(`Used ${(this.maxHeapSize / stats.heap_size_limit).toFixed(2)}% of heap limit (${formatBytes(this.maxHeapSize, byteUnits, true)} / ${formatBytes(stats.heap_size_limit, byteUnits)})!`);
logger.warn(`Used ${(this.maxHeapSize / stats.heap_size_limit * 100).toFixed(2)}% of heap limit (${formatBytes(this.maxHeapSize, byteUnits, true)} / ${formatBytes(stats.heap_size_limit, byteUnits)})!`);
}
if (this.lastHeapLogTime === null || (now - this.lastHeapLogTime) > (this.heapLogInterval * 1000)) {
logger.debug(`Memory usage: ${formatBytes(this.maxHeapSize, byteUnits)} / ${formatBytes(stats.heap_size_limit, byteUnits)}`);

View File

@ -160,7 +160,7 @@ class PricesRepository {
// Compute fiat exchange rates
let latestPrice = rates[0] as ApiPrice;
if (latestPrice.USD === -1) {
if (!latestPrice || latestPrice.USD === -1) {
latestPrice = priceUpdater.getEmptyPricesObj();
}

View File

@ -27,7 +27,7 @@ class ForensicsService {
private async $runTasks(): Promise<void> {
try {
logger.info(`Running forensics scans`);
logger.debug(`Running forensics scans`);
if (config.MEMPOOL.BACKEND === 'esplora') {
await this.$runClosedChannelsForensics(false);
@ -73,7 +73,7 @@ class ForensicsService {
let progress = 0;
try {
logger.info(`Started running closed channel forensics...`);
logger.debug(`Started running closed channel forensics...`);
let channels;
if (onlyNewChannels) {
channels = await channelsApi.$getClosedChannelsWithoutReason();
@ -156,7 +156,7 @@ class ForensicsService {
this.loggerTimer = new Date().getTime() / 1000;
}
}
logger.info(`Closed channels forensics scan complete.`);
logger.debug(`Closed channels forensics scan complete.`);
} catch (e) {
logger.err('$runClosedChannelsForensics() error: ' + (e instanceof Error ? e.message : e));
}
@ -217,7 +217,7 @@ class ForensicsService {
let progress = 0;
try {
logger.info(`Started running open channel forensics...`);
logger.debug(`Started running open channel forensics...`);
const channels = await channelsApi.$getChannelsWithoutSourceChecked();
for (const openChannel of channels) {
@ -266,7 +266,7 @@ class ForensicsService {
}
}
logger.info(`Open channels forensics scan complete.`);
logger.debug(`Open channels forensics scan complete.`);
} catch (e) {
logger.err('$runOpenedChannelsForensics() error: ' + (e instanceof Error ? e.message : e));
} finally {

View File

@ -283,7 +283,7 @@ class NetworkSyncService {
} else {
log += ` for the first time`;
}
logger.info(`${log}`, logger.tags.ln);
logger.debug(`${log}`, logger.tags.ln);
const channels = await channelsApi.$getChannelsByStatus([0, 1]);
for (const channel of channels) {

View File

@ -15,16 +15,20 @@ class LightningStatsImporter {
topologiesFolder = config.LIGHTNING.TOPOLOGY_FOLDER;
async $run(): Promise<void> {
const [channels]: any[] = await DB.query('SELECT short_id from channels;');
logger.info(`Caching funding txs for currently existing channels`, logger.tags.ln);
await fundingTxFetcher.$fetchChannelsFundingTxs(channels.map(channel => channel.short_id));
try {
const [channels]: any[] = await DB.query('SELECT short_id from channels;');
logger.info(`Caching funding txs for currently existing channels`, logger.tags.ln);
await fundingTxFetcher.$fetchChannelsFundingTxs(channels.map(channel => channel.short_id));
if (config.MEMPOOL.NETWORK !== 'mainnet' || config.DATABASE.ENABLED === false) {
return;
if (config.MEMPOOL.NETWORK !== 'mainnet' || config.DATABASE.ENABLED === false) {
return;
}
await this.$importHistoricalLightningStats();
await this.$cleanupIncorrectSnapshot();
} catch (e) {
logger.err(`Exception in LightningStatsImporter::$run(). ${e}`);
}
await this.$importHistoricalLightningStats();
await this.$cleanupIncorrectSnapshot();
}
/**

View File

@ -62,7 +62,7 @@ class PoolsUpdater {
if (this.currentSha === null) {
logger.info(`Downloading pools-v2.json for the first time from ${this.poolsUrl} over ${network}`, logger.tags.mining);
} else {
logger.warn(`pools-v2.json is outdated, fetch latest from ${this.poolsUrl} over ${network}`, logger.tags.mining);
logger.warn(`pools-v2.json is outdated, fetching latest from ${this.poolsUrl} over ${network}`, logger.tags.mining);
}
const poolsJson = await this.query(this.poolsUrl);
if (poolsJson === undefined) {

View File

@ -222,7 +222,7 @@ class PriceUpdater {
private async $insertMissingRecentPrices(type: 'hour' | 'day'): Promise<void> {
const existingPriceTimes = await PricesRepository.$getPricesTimes();
logger.info(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database`, logger.tags.mining);
logger.debug(`Fetching ${type === 'day' ? 'dai' : 'hour'}ly price history from exchanges and saving missing ones into the database`, logger.tags.mining);
const historicalPrices: PriceHistory[] = [];

View File

@ -5,6 +5,7 @@
"types": ["node", "jest"],
"lib": ["es2019", "dom"],
"strict": true,
"skipLibCheck": true,
"noImplicitAny": false,
"sourceMap": false,
"outDir": "dist",

View File

@ -127,7 +127,7 @@ describe('Mainnet', () => {
cy.get('.search-box-container > .form-control').type('S').then(() => {
cy.wait('@search-1wizS');
cy.get('app-search-results button.dropdown-item').should('have.length', 5);
cy.get('app-search-results button.dropdown-item').should('have.length', 6);
});
cy.get('.search-box-container > .form-control').type('A').then(() => {

View File

@ -62,6 +62,21 @@
</nav>
</header>
<div *ngIf="network.val === 'testnet' || network.val === 'signet' || network.val === 'liquidtestnet'">
<div class="container p-lg-0 pb-0" style="max-width: 100%; margin-top: 7px" *ngIf="storageService.getValue('hideWarning') !== 'hidden'">
<div class="alert alert-danger mb-0 text-center">
<div class="d-flex justify-content-center align-items-center">
<fa-icon class="between-arrow mr-1" [icon]="['fas', 'exclamation-triangle']" [fixedWidth]="true"></fa-icon>
<span i18n="warning-testnet">This is a test network. Coins have no value</span>
<fa-icon class="between-arrow ml-1" [icon]="['fas', 'exclamation-triangle']" [fixedWidth]="true"></fa-icon>
</div>
<button type="button" class="close" (click)="dismissWarning()">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
</div>
<br />
<router-outlet></router-outlet>

View File

@ -192,4 +192,19 @@ nav {
margin: 33px 0px 0px -19px;
font-size: 7px;
}
}
.close {
position: absolute;
color: black;
right: 10px;
top: 17px;
@media (max-width: 620px) {
right: 10px;
top: 0px;
};
@media (min-width: 992px) {
right: 10px;
top: 13px;
};
}

View File

@ -4,6 +4,7 @@ import { Observable, merge, of } from 'rxjs';
import { LanguageService } from '../../services/language.service';
import { EnterpriseService } from '../../services/enterprise.service';
import { NavigationService } from '../../services/navigation.service';
import { StorageService } from '../../services/storage.service';
@Component({
selector: 'app-master-page',
@ -26,6 +27,7 @@ export class MasterPageComponent implements OnInit {
private languageService: LanguageService,
private enterpriseService: EnterpriseService,
private navigationService: NavigationService,
public storageService: StorageService
) { }
ngOnInit() {
@ -46,4 +48,8 @@ export class MasterPageComponent implements OnInit {
onResize(event: any) {
this.isMobile = window.innerWidth <= 767.98;
}
dismissWarning() {
this.storageService.setValue('hideWarning', 'hidden');
}
}

View File

@ -1,3 +1,5 @@
<div infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()">
<ng-container *ngFor="let tx of transactions; let i = index; trackBy: trackByFn">
<div *ngIf="!transactionPage" class="header-bg box tx-page-container">
<a class="tx-link" [routerLink]="['/tx/' | relativeUrl, tx.txid]">
@ -11,7 +13,7 @@
</div>
</div>
<div class="header-bg box" infiniteScroll [alwaysCallback]="true" [infiniteScrollDistance]="2" [infiniteScrollUpDistance]="1.5" [infiniteScrollThrottle]="50" (scrolled)="onScroll()" [attr.data-cy]="'tx-' + i">
<div class="header-bg box">
<div *ngIf="errorUnblinded" class="error-unblinded">{{ errorUnblinded }}</div>
<div class="row">
@ -321,6 +323,8 @@
</ng-container>
</div>
<ng-template #assetBox let-item>
{{ item.value / pow(10, assetsMinimal[item.asset][3]) | number: '1.' + assetsMinimal[item.asset][3] + '-' + assetsMinimal[item.asset][3] }} {{ assetsMinimal[item.asset][1] }}
<br />

View File

@ -182,14 +182,7 @@ export class TransactionsListComponent implements OnInit, OnChanges {
}
onScroll(): void {
const scrollHeight = document.body.scrollHeight;
const scrollTop = document.documentElement.scrollTop;
if (scrollHeight > 0) {
const percentageScrolled = scrollTop * 100 / scrollHeight;
if (percentageScrolled > 50) {
this.loadMore.emit();
}
}
this.loadMore.emit();
}
haveBlindedOutputValues(tx: Transaction): boolean {

View File

@ -8863,7 +8863,7 @@ export const faqData = [
type: "endpoint",
category: "advanced",
showConditions: bitcoinNetworks,
fragment: "how-big-is-mempool-used-by-mempool.space",
fragment: "how-big-is-mempool-used-by-mempool-space",
title: "How big is the mempool used by mempool.space?",
options: { officialOnly: true },
},

View File

@ -207,7 +207,7 @@
<p>When a Bitcoin transaction is made, it is stored in a Bitcoin node's mempool before it is confirmed into a block. When the rate of incoming transactions exceeds the rate transactions are confirmed, the mempool grows in size.</p><p>By default, Bitcoin Core allocates 300MB of memory for its mempool, so when a node's mempool grows big enough to use all 300MB of allocated memory, we say it's "full".</p><p>Once a node's mempool is using all of its allocated memory, it will start rejecting new transactions below a certain feerate threshold—so when this is the case, be extra sure to set a feerate that (at a minimum) exceeds that threshold. The current threshold feerate (and memory usage) are displayed right on Mempool's front page.</p>
</ng-template>
<ng-template type="how-big-is-mempool-used-by-mempool.space">
<ng-template type="how-big-is-mempool-used-by-mempool-space">
<p>mempool.space uses multiple Bitcoin nodes to obtain data: some with the default 300MB mempool memory limit (call these Small Nodes) and others with a much larger mempool memory limit (call these Big Nodes).</p>
<p>Many nodes on the Bitcoin network are configured to run with the default 300MB mempool memory setting. When all 300MB of memory are used up, such nodes will reject transactions below a certain threshold feerate. Running Small Nodes allows mempool.space to tell you what this threshold feerate is—this is the "Purging" feerate that shows on the front page when mempools are full, which you can use to be reasonably sure that your transaction will be widely propagated.</p>
<p>Big Node mempools are so big that they don't need to reject (or purge) transactions. Such nodes allow for mempool.space to provide you with information on any pending transaction it has received—no matter how congested the mempool is, and no matter how low-feerate or low-priority the transaction is.</p>

View File

@ -12,7 +12,7 @@
</div>
</div>
<div class="row">
<div class="col-md">
<div class="col-md table-col">
<a class="subtitle" [routerLink]="['/lightning/node' | relativeUrl, node.public_key]">{{ node.public_key }}</a>
<table class="table table-borderless table-striped">
<tbody>

View File

@ -18,6 +18,10 @@
}
}
.table-col {
max-width: calc(100% - 470px);
}
.map-col {
flex-grow: 0;
flex-shrink: 0;

View File

@ -12,20 +12,22 @@ export class StorageService {
setDefaultValueIfNeeded(key: string, defaultValue: string) {
const graphWindowPreference: string = this.getValue(key);
const fragment = window.location.hash.replace('#', '');
if (graphWindowPreference === null) { // First visit to mempool.space
if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'miningWindowPreference'
if (window.location.pathname.includes('graphs') && key === 'graphWindowPreference' ||
window.location.pathname.includes('pools') && key === 'miningWindowPreference'
) {
this.setValue(key, this.route.snapshot.fragment ? this.route.snapshot.fragment : defaultValue);
this.setValue(key, fragment ? fragment : defaultValue);
} else {
this.setValue(key, defaultValue);
}
} else if (this.router.url.includes('graphs') && key === 'graphWindowPreference' ||
this.router.url.includes('pools') && key === 'miningWindowPreference'
} else if (window.location.pathname.includes('graphs') && key === 'graphWindowPreference' ||
window.location.pathname.includes('pools') && key === 'miningWindowPreference'
) {
// Visit a different graphs#fragment from last visit
if (this.route.snapshot.fragment !== null && graphWindowPreference !== this.route.snapshot.fragment) {
this.setValue(key, this.route.snapshot.fragment);
if (fragment !== null && graphWindowPreference !== fragment) {
this.setValue(key, fragment);
}
}
}

View File

@ -4,7 +4,7 @@ import { NgbCollapseModule, NgbTypeaheadModule } from '@ng-bootstrap/ng-bootstra
import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontawesome';
import { faFilter, faAngleDown, faAngleUp, faAngleRight, faAngleLeft, faBolt, faChartArea, faCogs, faCubes, faHammer, faDatabase, faExchangeAlt, faInfoCircle,
faLink, faList, faSearch, faCaretUp, faCaretDown, faTachometerAlt, faThList, faTint, faTv, faAngleDoubleDown, faSortUp, faAngleDoubleUp, faChevronDown,
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft } from '@fortawesome/free-solid-svg-icons';
faFileAlt, faRedoAlt, faArrowAltCircleRight, faExternalLinkAlt, faBook, faListUl, faDownload, faQrcode, faArrowRightArrowLeft, faArrowsRotate, faCircleLeft, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { InfiniteScrollModule } from 'ngx-infinite-scroll';
import { MasterPageComponent } from '../components/master-page/master-page.component';
import { PreviewTitleComponent } from '../components/master-page-preview/preview-title.component';
@ -309,5 +309,6 @@ export class SharedModule {
library.addIcons(faQrcode);
library.addIcons(faArrowRightArrowLeft);
library.addIcons(faExchangeAlt);
library.addIcons(faExclamationTriangle);
}
}