Merge branch 'master' into nymkappa/bugfix/price

This commit is contained in:
wiz 2023-03-05 14:13:51 +09:00 committed by GitHub
commit fca813147d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 134 additions and 98 deletions

View file

@ -6,7 +6,7 @@ import websocketHandler from '../websocket-handler';
import mempool from '../mempool';
import feeApi from '../fee-api';
import mempoolBlocks from '../mempool-blocks';
import bitcoinApi from './bitcoin-api-factory';
import bitcoinApi, { bitcoinCoreApi } from './bitcoin-api-factory';
import { Common } from '../common';
import backendInfo from '../backend-info';
import transactionUtils from '../transaction-utils';
@ -469,7 +469,7 @@ class BitcoinRoutes {
returnBlocks.push(localBlock);
nextHash = localBlock.previousblockhash;
} else {
const block = await bitcoinApi.$getBlock(nextHash);
const block = await bitcoinCoreApi.$getBlock(nextHash);
returnBlocks.push(block);
nextHash = block.previousblockhash;
}

View file

@ -1,5 +1,5 @@
import config from '../config';
import bitcoinApi from './bitcoin/bitcoin-api-factory';
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
import logger from '../logger';
import memPool from './mempool';
import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionStripped, TransactionMinerInfo } from '../mempool.interfaces';
@ -484,7 +484,7 @@ class Blocks {
loadingIndicators.setProgress('block-indexing', progress, false);
}
const blockHash = await bitcoinApi.$getBlockHash(blockHeight);
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
const block: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(blockHash);
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true, true);
const blockExtended = await this.$getBlockExtended(block, transactions);
@ -532,13 +532,13 @@ class Blocks {
if (blockchainInfo.blocks === blockchainInfo.headers) {
const heightDiff = blockHeightTip % 2016;
const blockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff);
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
const block: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(blockHash);
this.lastDifficultyAdjustmentTime = block.timestamp;
this.currentDifficulty = block.difficulty;
if (blockHeightTip >= 2016) {
const previousPeriodBlockHash = await bitcoinApi.$getBlockHash(blockHeightTip - heightDiff - 2016);
const previousPeriodBlock: IEsploraApi.Block = await bitcoinApi.$getBlock(previousPeriodBlockHash);
const previousPeriodBlock: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(previousPeriodBlockHash);
this.previousDifficultyRetarget = (block.difficulty - previousPeriodBlock.difficulty) / previousPeriodBlock.difficulty * 100;
logger.debug(`Initial difficulty adjustment data set.`);
}
@ -662,7 +662,7 @@ class Blocks {
}
const blockHash = await bitcoinApi.$getBlockHash(height);
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(blockHash);
const block: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(blockHash);
const transactions = await this.$getTransactionsExtended(blockHash, block.height, true);
const blockExtended = await this.$getBlockExtended(block, transactions);
@ -685,11 +685,11 @@ class Blocks {
// Not Bitcoin network, return the block as it from the bitcoin backend
if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) === false) {
return await bitcoinApi.$getBlock(hash);
return await bitcoinCoreApi.$getBlock(hash);
}
// Bitcoin network, add our custom data on top
const block: IEsploraApi.Block = await bitcoinApi.$getBlock(hash);
const block: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(hash);
return await this.$indexBlock(block.height);
}

View file

@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
import { RowDataPacket } from 'mysql2';
class DatabaseMigration {
private static currentVersion = 57;
private static currentVersion = 58;
private queryTimeout = 3600_000;
private statisticsAddedIndexed = false;
private uniqueLogs: string[] = [];
@ -505,6 +505,11 @@ class DatabaseMigration {
await this.$executeQuery(`ALTER TABLE nodes MODIFY updated_at datetime NULL`);
await this.updateToSchemaVersion(57);
}
if (databaseSchemaVersion < 58) {
// We only run some migration queries for this version
await this.updateToSchemaVersion(58);
}
}
/**
@ -632,6 +637,11 @@ class DatabaseMigration {
queries.push(`INSERT INTO state(name, number, string) VALUES ('last_weekly_hashrates_indexing', 0, NULL)`);
}
if (version < 58) {
queries.push(`DELETE FROM state WHERE name = 'last_hashrates_indexing'`);
queries.push(`DELETE FROM state WHERE name = 'last_weekly_hashrates_indexing'`);
}
return queries;
}
@ -1023,6 +1033,7 @@ class DatabaseMigration {
await this.$executeQuery(`TRUNCATE blocks`);
await this.$executeQuery(`TRUNCATE hashrates`);
await this.$executeQuery(`TRUNCATE difficulty_adjustments`);
await this.$executeQuery('DELETE FROM `pools`');
await this.$executeQuery('ALTER TABLE pools AUTO_INCREMENT = 1');
await this.$executeQuery(`UPDATE state SET string = NULL WHERE name = 'pools_json_sha'`);

View file

@ -97,14 +97,14 @@ class MempoolBlocks {
blockSize += tx.size;
transactions.push(tx);
} else {
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, mempoolBlocks.length));
blockWeight = tx.weight;
blockSize = tx.size;
transactions = [tx];
}
});
if (transactions.length) {
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, mempoolBlocks.length));
}
return mempoolBlocks;
@ -281,7 +281,7 @@ class MempoolBlocks {
const mempoolBlocks = blocks.map((transactions, blockIndex) => {
return this.dataToMempoolBlocks(transactions.map(tx => {
return mempool[tx.txid] || null;
}).filter(tx => !!tx), undefined, undefined, blockIndex);
}).filter(tx => !!tx), blockIndex);
});
if (saveResults) {
@ -293,18 +293,17 @@ class MempoolBlocks {
return mempoolBlocks;
}
private dataToMempoolBlocks(transactions: TransactionExtended[],
blockSize: number | undefined, blockWeight: number | undefined, blocksIndex: number): MempoolBlockWithTransactions {
let totalSize = blockSize || 0;
let totalWeight = blockWeight || 0;
if (blockSize === undefined && blockWeight === undefined) {
totalSize = 0;
totalWeight = 0;
private dataToMempoolBlocks(transactions: TransactionExtended[], blocksIndex: number): MempoolBlockWithTransactions {
let totalSize = 0;
let totalWeight = 0;
const fitTransactions: TransactionExtended[] = [];
transactions.forEach(tx => {
totalSize += tx.size;
totalWeight += tx.weight;
});
if ((totalWeight + tx.weight) <= config.MEMPOOL.BLOCK_WEIGHT_UNITS * 1.2) {
fitTransactions.push(tx);
}
});
let rangeLength = 4;
if (blocksIndex === 0) {
rangeLength = 8;
@ -322,7 +321,7 @@ class MempoolBlocks {
medianFee: Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
feeRange: Common.getFeesInRange(transactions, rangeLength),
transactionIds: transactions.map((tx) => tx.txid),
transactions: transactions.map((tx) => Common.stripTransaction(tx)),
transactions: fitTransactions.map((tx) => Common.stripTransaction(tx)),
};
}
}

View file

@ -11,14 +11,13 @@ import DifficultyAdjustmentsRepository from '../../repositories/DifficultyAdjust
import config from '../../config';
import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
import PricesRepository from '../../repositories/PricesRepository';
import bitcoinApiFactory from '../bitcoin/bitcoin-api-factory';
import { bitcoinCoreApi } from '../bitcoin/bitcoin-api-factory';
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
class Mining {
blocksPriceIndexingRunning = false;
constructor() {
}
private blocksPriceIndexingRunning = false;
public lastHashrateIndexingDate: number | null = null;
public lastWeeklyHashrateIndexingDate: number | null = null;
/**
* Get historical block predictions match rate
@ -178,20 +177,21 @@ class Mining {
*/
public async $generatePoolHashrateHistory(): Promise<void> {
const now = new Date();
const lastestRunDate = await HashratesRepository.$getLatestRun('last_weekly_hashrates_indexing');
// Run only if:
// * lastestRunDate is set to 0 (node backend restart, reorg)
// * this.lastWeeklyHashrateIndexingDate is set to null (node backend restart, reorg)
// * we started a new week (around Monday midnight)
const runIndexing = lastestRunDate === 0 || now.getUTCDay() === 1 && lastestRunDate !== now.getUTCDate();
const runIndexing = this.lastWeeklyHashrateIndexingDate === null ||
now.getUTCDay() === 1 && this.lastWeeklyHashrateIndexingDate !== now.getUTCDate();
if (!runIndexing) {
logger.debug(`Pool hashrate history indexing is up to date, nothing to do`, logger.tags.mining);
return;
}
try {
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
const genesisBlock: IEsploraApi.Block = await bitcoinApiFactory.$getBlock(await bitcoinClient.getBlockHash(0));
const genesisBlock: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(await bitcoinClient.getBlockHash(0));
const genesisTimestamp = genesisBlock.timestamp * 1000;
const indexedTimestamp = await HashratesRepository.$getWeeklyHashrateTimestamps();
@ -266,7 +266,7 @@ class Mining {
++indexedThisRun;
++totalIndexed;
}
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', new Date().getUTCDate());
this.lastWeeklyHashrateIndexingDate = new Date().getUTCDate();
if (newlyIndexed > 0) {
logger.notice(`Weekly mining pools hashrates indexing completed: indexed ${newlyIndexed}`, logger.tags.mining);
} else {
@ -285,16 +285,16 @@ class Mining {
*/
public async $generateNetworkHashrateHistory(): Promise<void> {
// We only run this once a day around midnight
const latestRunDate = await HashratesRepository.$getLatestRun('last_hashrates_indexing');
const now = new Date().getUTCDate();
if (now === latestRunDate) {
const today = new Date().getUTCDate();
if (today === this.lastHashrateIndexingDate) {
logger.debug(`Network hashrate history indexing is up to date, nothing to do`, logger.tags.mining);
return;
}
const oldestConsecutiveBlockTimestamp = 1000 * (await BlocksRepository.$getOldestConsecutiveBlock()).timestamp;
try {
const genesisBlock: IEsploraApi.Block = await bitcoinApiFactory.$getBlock(await bitcoinClient.getBlockHash(0));
const genesisBlock: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(await bitcoinClient.getBlockHash(0));
const genesisTimestamp = genesisBlock.timestamp * 1000;
const indexedTimestamp = (await HashratesRepository.$getRawNetworkDailyHashrate(null)).map(hashrate => hashrate.timestamp);
const lastMidnight = this.getDateMidnight(new Date());
@ -371,7 +371,7 @@ class Mining {
newlyIndexed += hashrates.length;
await HashratesRepository.$saveHashrates(hashrates);
await HashratesRepository.$setLatestRun('last_hashrates_indexing', new Date().getUTCDate());
this.lastHashrateIndexingDate = new Date().getUTCDate();
if (newlyIndexed > 0) {
logger.notice(`Daily network hashrate indexing completed: indexed ${newlyIndexed} days`, logger.tags.mining);
} else {
@ -396,7 +396,7 @@ class Mining {
}
const blocks: any = await BlocksRepository.$getBlocksDifficulty();
const genesisBlock: IEsploraApi.Block = await bitcoinApiFactory.$getBlock(await bitcoinClient.getBlockHash(0));
const genesisBlock: IEsploraApi.Block = await bitcoinCoreApi.$getBlock(await bitcoinClient.getBlockHash(0));
let currentDifficulty = genesisBlock.difficulty;
let totalIndexed = 0;

View file

@ -87,9 +87,6 @@ class Server {
await databaseMigration.$blocksReindexingTruncate();
}
await databaseMigration.$initializeOrMigrateDatabase();
if (Common.indexingEnabled()) {
await indexer.$resetHashratesIndexingState();
}
} catch (e) {
throw new Error(e instanceof Error ? e.message : 'Error');
}

View file

@ -3,7 +3,6 @@ import blocks from './api/blocks';
import mempool from './api/mempool';
import mining from './api/mining/mining';
import logger from './logger';
import HashratesRepository from './repositories/HashratesRepository';
import bitcoinClient from './api/bitcoin/bitcoin-client';
import priceUpdater from './tasks/price-updater';
import PricesRepository from './repositories/PricesRepository';
@ -131,7 +130,6 @@ class Indexer {
this.runSingleTask('blocksPrices');
await mining.$indexDifficultyAdjustments();
await this.$resetHashratesIndexingState(); // TODO - Remove this as it's not efficient
await mining.$generateNetworkHashrateHistory();
await mining.$generatePoolHashrateHistory();
await blocks.$generateBlocksSummariesDatabase();
@ -150,16 +148,6 @@ class Indexer {
logger.debug(`Indexing completed. Next run planned at ${new Date(new Date().getTime() + runEvery).toUTCString()}`);
setTimeout(() => this.reindex(), runEvery);
}
async $resetHashratesIndexingState(): Promise<void> {
try {
await HashratesRepository.$setLatestRun('last_hashrates_indexing', 0);
await HashratesRepository.$setLatestRun('last_weekly_hashrates_indexing', 0);
} catch (e) {
logger.err(`Cannot reset hashrate indexing timestamps. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
}
export default new Indexer();

View file

@ -1,5 +1,4 @@
import { Common } from '../api/common';
import config from '../config';
import DB from '../database';
import logger from '../logger';
import { IndexedDifficultyAdjustment } from '../mempool.interfaces';

View file

@ -1,5 +1,6 @@
import { escape } from 'mysql2';
import { Common } from '../api/common';
import mining from '../api/mining/mining';
import DB from '../database';
import logger from '../logger';
import PoolsRepository from './PoolsRepository';
@ -177,20 +178,6 @@ class HashratesRepository {
}
}
/**
* Set latest run timestamp
*/
public async $setLatestRun(key: string, val: number) {
const query = `UPDATE state SET number = ? WHERE name = ?`;
try {
await DB.query(query, [val, key]);
} catch (e) {
logger.err(`Cannot set last indexing run for ${key}. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
/**
* Get latest run timestamp
*/
@ -222,8 +209,8 @@ class HashratesRepository {
await DB.query(`DELETE FROM hashrates WHERE hashrate_timestamp = ?`, [row.timestamp]);
}
// Re-run the hashrate indexing to fill up missing data
await this.$setLatestRun('last_hashrates_indexing', 0);
await this.$setLatestRun('last_weekly_hashrates_indexing', 0);
mining.lastHashrateIndexingDate = null;
mining.lastWeeklyHashrateIndexingDate = null;
} catch (e) {
logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e));
}
@ -238,8 +225,8 @@ class HashratesRepository {
try {
await DB.query(`DELETE FROM hashrates WHERE hashrate_timestamp >= FROM_UNIXTIME(?)`, [timestamp]);
// Re-run the hashrate indexing to fill up missing data
await this.$setLatestRun('last_hashrates_indexing', 0);
await this.$setLatestRun('last_weekly_hashrates_indexing', 0);
mining.lastHashrateIndexingDate = null;
mining.lastWeeklyHashrateIndexingDate = null;
} catch (e) {
logger.err('Cannot delete latest hashrates data points. Reason: ' + (e instanceof Error ? e.message : e));
}

View file

@ -16,7 +16,7 @@
</tr>
<tr>
<td class="td-width" i18n="dashboard.latest-transactions.amount">Amount</td>
<td><app-amount [blockConversion]="blockConversion" [satoshis]="value"></app-amount></td>
<td><app-amount [blockConversion]="blockConversion" [satoshis]="value" [noFiat]="true"></app-amount></td>
</tr>
<tr>
<td class="td-width" i18n="transaction.fee|Transaction fee">Fee</td>

View file

@ -107,7 +107,13 @@ export class SearchFormComponent implements OnInit {
}))),
);
}),
tap((result: any[]) => {
map((result: any[]) => {
if (this.network === 'bisq') {
result[0] = result[0].map((address: string) => 'B' + address);
}
return result;
}),
tap(() => {
this.isTypeaheading$.next(false);
})
);
@ -126,7 +132,7 @@ export class SearchFormComponent implements OnInit {
]
).pipe(
map((latestData) => {
const searchText = latestData[0];
let searchText = latestData[0];
if (!searchText.length) {
return {
searchText: '',
@ -144,15 +150,15 @@ export class SearchFormComponent implements OnInit {
const addressPrefixSearchResults = result[0];
const lightningResults = result[1];
if (this.network === 'bisq') {
return searchText.map((address: string) => 'B' + address);
}
const matchesBlockHeight = this.regexBlockheight.test(searchText);
const matchesTxId = this.regexTransaction.test(searchText) && !this.regexBlockhash.test(searchText);
const matchesBlockHash = this.regexBlockhash.test(searchText);
const matchesAddress = this.regexAddress.test(searchText);
if (matchesAddress && this.network === 'bisq') {
searchText = 'B' + searchText;
}
return {
searchText: searchText,
hashQuickMatch: +(matchesBlockHeight || matchesBlockHash || matchesTxId || matchesAddress),

View file

@ -38,6 +38,9 @@ export class StartComponent implements OnInit, OnDestroy {
pageIndex: number = 0;
pages: any[] = [];
pendingMark: number | void = null;
lastUpdate: number = 0;
lastMouseX: number;
velocity: number = 0;
constructor(
private stateService: StateService,
@ -136,6 +139,7 @@ export class StartComponent implements OnInit, OnDestroy {
onMouseDown(event: MouseEvent) {
this.mouseDragStartX = event.clientX;
this.resetMomentum(event.clientX);
this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft;
}
onPointerDown(event: PointerEvent) {
@ -159,6 +163,7 @@ export class StartComponent implements OnInit, OnDestroy {
@HostListener('document:mousemove', ['$event'])
onMouseMove(event: MouseEvent): void {
if (this.mouseDragStartX != null) {
this.updateVelocity(event.clientX);
this.stateService.setBlockScrollingInProgress(true);
this.blockchainContainer.nativeElement.scrollLeft =
this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX;
@ -167,7 +172,7 @@ export class StartComponent implements OnInit, OnDestroy {
@HostListener('document:mouseup', [])
onMouseUp() {
this.mouseDragStartX = null;
this.stateService.setBlockScrollingInProgress(false);
this.animateMomentum();
}
@HostListener('document:pointermove', ['$event'])
onPointerMove(event: PointerEvent): void {
@ -183,6 +188,45 @@ export class StartComponent implements OnInit, OnDestroy {
}
}
resetMomentum(x: number) {
this.lastUpdate = performance.now();
this.lastMouseX = x;
this.velocity = 0;
}
updateVelocity(x: number) {
const now = performance.now();
let dt = now - this.lastUpdate;
if (dt > 0) {
this.lastUpdate = now;
const velocity = (x - this.lastMouseX) / dt;
this.velocity = (0.8 * this.velocity) + (0.2 * velocity);
this.lastMouseX = x;
}
}
animateMomentum() {
this.lastUpdate = performance.now();
requestAnimationFrame(() => {
const now = performance.now();
const dt = now - this.lastUpdate;
this.lastUpdate = now;
if (Math.abs(this.velocity) < 0.005) {
this.stateService.setBlockScrollingInProgress(false);
} else {
const deceleration = Math.max(0.0025, 0.001 * this.velocity * this.velocity) * (this.velocity > 0 ? -1 : 1);
const displacement = (this.velocity * dt) - (0.5 * (deceleration * dt * dt));
const dv = (deceleration * dt);
if ((this.velocity < 0 && dv + this.velocity > 0) || (this.velocity > 0 && dv + this.velocity < 0)) {
this.velocity = 0;
} else {
this.velocity += dv;
}
this.blockchainContainer.nativeElement.scrollLeft -= displacement;
this.animateMomentum();
}
});
}
onScroll(e) {
const middlePage = this.pageIndex === 0 ? this.pages[0] : this.pages[1];

View file

@ -204,6 +204,12 @@
.txids {
width: 60%;
}
@media (max-width: 500px) {
.txids {
width: 40%;
}
}
}
.tx-list {

View file

@ -250,7 +250,7 @@
</span>
<ng-template #inSync>
<div class="progress inc-tx-progress-bar">
<div class="progress-bar" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth, 'background-color': mempoolInfoData.value.progressColor}">&nbsp;</div>
<div class="progress-bar {{ mempoolInfoData.value.progressColor }}" role="progressbar" [ngStyle]="{'width': mempoolInfoData.value.progressWidth}">&nbsp;</div>
<div class="progress-text">&lrm;{{ mempoolInfoData.value.vBytesPerSecond | ceil | number }} <ng-container i18n="shared.vbytes-per-second|vB/s">vB/s</ng-container></div>
</div>
</ng-template>

View file

@ -78,21 +78,12 @@ export class DashboardComponent implements OnInit, OnDestroy {
map(([mempoolInfo, vbytesPerSecond]) => {
const percent = Math.round((Math.min(vbytesPerSecond, this.vBytesPerSecondLimit) / this.vBytesPerSecondLimit) * 100);
let progressColor = '#7CB342';
let progressColor = 'bg-success';
if (vbytesPerSecond > 1667) {
progressColor = '#FDD835';
}
if (vbytesPerSecond > 2000) {
progressColor = '#FFB300';
}
if (vbytesPerSecond > 2500) {
progressColor = '#FB8C00';
progressColor = 'bg-warning';
}
if (vbytesPerSecond > 3000) {
progressColor = '#F4511E';
}
if (vbytesPerSecond > 3500) {
progressColor = '#D81B60';
progressColor = 'bg-danger';
}
const mempoolSizePercentage = (mempoolInfo.usage / mempoolInfo.maxmempool * 100);

View file

@ -23,6 +23,10 @@ export class FiatCurrencyPipe implements PipeTransform {
const digits = args[0] || 1;
const currency = args[1] || this.currency || 'USD';
if (num >= 1000) {
return new Intl.NumberFormat(this.locale, { style: 'currency', currency, maximumFractionDigits: 0 }).format(num);
} else {
return new Intl.NumberFormat(this.locale, { style: 'currency', currency }).format(num);
}
}
}

View file

@ -9,6 +9,10 @@ location /api/tx/ {
rewrite ^/api/(.*) /$1 break;
try_files /dev/null @esplora-api-cache-disabled;
}
location /api/address-prefix/ {
rewrite ^/api/(.*) /$1 break;
try_files /dev/null @esplora-api-cache-disabled;
}
# rewrite APIs to match what backend expects
location /api/currencies {