diff --git a/backend/src/api/mining/mining.ts b/backend/src/api/mining/mining.ts index d3fadbcce..ebf8fd40d 100644 --- a/backend/src/api/mining/mining.ts +++ b/backend/src/api/mining/mining.ts @@ -114,31 +114,29 @@ class Mining { const poolsStatistics = {}; const poolsInfo: PoolInfo[] = await PoolsRepository.$getPoolsInfo(interval); - const emptyBlocks: any[] = await BlocksRepository.$countEmptyBlocks(null, interval); const poolsStats: PoolStats[] = []; let rank = 1; + let blockCount = 0; poolsInfo.forEach((poolInfo: PoolInfo) => { - const emptyBlocksCount = emptyBlocks.filter((emptyCount) => emptyCount.poolId === poolInfo.poolId); const poolStat: PoolStats = { poolId: poolInfo.poolId, // mysql row id name: poolInfo.name, link: poolInfo.link, blockCount: poolInfo.blockCount, rank: rank++, - emptyBlocks: emptyBlocksCount.length > 0 ? emptyBlocksCount[0]['count'] : 0, + emptyBlocks: poolInfo.emptyBlocks, slug: poolInfo.slug, avgMatchRate: poolInfo.avgMatchRate !== null ? Math.round(100 * poolInfo.avgMatchRate) / 100 : null, avgFeeDelta: poolInfo.avgFeeDelta, poolUniqueId: poolInfo.poolUniqueId }; poolsStats.push(poolStat); + blockCount += poolInfo.blockCount; }); poolsStatistics['pools'] = poolsStats; - - const blockCount: number = await BlocksRepository.$blockCount(null, interval); poolsStatistics['blockCount'] = blockCount; const totalBlock24h: number = await BlocksRepository.$blockCount(null, '24h'); @@ -151,6 +149,8 @@ class Mining { poolsStatistics['lastEstimatedHashrate1w'] = await bitcoinClient.getNetworkHashPs(totalBlock1w); } catch (e) { poolsStatistics['lastEstimatedHashrate'] = 0; + poolsStatistics['lastEstimatedHashrate3d'] = 0; + poolsStatistics['lastEstimatedHashrate1w'] = 0; logger.debug('Bitcoin Core is not available, using zeroed value for current hashrate', logger.tags.mining); } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 24e01d4dd..3f1403518 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -17,6 +17,7 @@ export interface PoolInfo { name: string; link: string; blockCount: number; + emptyBlocks: number; slug: string; avgMatchRate: number | null; avgFeeDelta: number | null; @@ -25,7 +26,6 @@ export interface PoolInfo { export interface PoolStats extends PoolInfo { rank: number; - emptyBlocks: number; } export enum TemplateAlgorithm { diff --git a/backend/src/repositories/BlocksRepository.ts b/backend/src/repositories/BlocksRepository.ts index faa8fce74..20d0a6e46 100644 --- a/backend/src/repositories/BlocksRepository.ts +++ b/backend/src/repositories/BlocksRepository.ts @@ -305,39 +305,6 @@ class BlocksRepository { } } - /** - * Get empty blocks for one or all pools - * @asyncSafe - */ - public async $countEmptyBlocks(poolId: number | null, interval: string | null = null): Promise { - interval = Common.getSqlInterval(interval); - - const params: any[] = []; - let query = `SELECT count(height) as count, pools.id as poolId - FROM blocks - JOIN pools on pools.id = blocks.pool_id - WHERE tx_count = 1 AND stale = 0`; - - if (poolId) { - query += ` AND pool_id = ?`; - params.push(poolId); - } - - if (interval) { - query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; - } - - query += ` GROUP by pools.id`; - - try { - const [rows] = await DB.query(query, params); - return rows; - } catch (e) { - logger.err('Cannot count empty blocks. Reason: ' + (e instanceof Error ? e.message : e)); - throw e; - } - } - /** * Return most recent block height * @asyncSafe @@ -360,17 +327,26 @@ class BlocksRepository { interval = Common.getSqlInterval(interval); const params: any[] = []; - let query = `SELECT count(height) as blockCount - FROM blocks - WHERE stale = 0`; - - if (poolId) { - query += ` AND pool_id = ?`; + let query; + if (!poolId && !interval) { + // optimized query to get full indexed block count + query = `SELECT CAST(COALESCE(MAX(height) - MIN(height) + 1, 0) AS SIGNED) as blockCount FROM blocks`; + } else if (!poolId) { + // optimized query for indexed count within an interval + query = `SELECT GREATEST(0, COALESCE( + CAST((SELECT height FROM blocks WHERE stale = 0 AND blockTimestamp <= NOW() ORDER BY blockTimestamp DESC LIMIT 1) AS SIGNED) - + CAST((SELECT height FROM blocks WHERE stale = 0 AND blockTimestamp >= DATE_SUB(NOW(), INTERVAL ${interval}) ORDER BY blockTimestamp ASC LIMIT 1) AS SIGNED) + 1 + , 0)) as blockCount`; + } else { + // for specific pools, we do still have to actually count the blocks + query = `SELECT count(*) as blockCount + FROM blocks + WHERE stale = 0 AND pool_id = ?`; params.push(poolId); - } - if (interval) { - query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + if (interval) { + query += ` AND blockTimestamp BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW()`; + } } try { diff --git a/backend/src/repositories/PoolsRepository.ts b/backend/src/repositories/PoolsRepository.ts index fcc218f7b..4fc3742fd 100644 --- a/backend/src/repositories/PoolsRepository.ts +++ b/backend/src/repositories/PoolsRepository.ts @@ -38,6 +38,7 @@ class PoolsRepository { let query = ` SELECT COUNT(blocks.height) As blockCount, + COUNT(CASE WHEN blocks.tx_count = 1 THEN 1 END) AS emptyBlocks, pool_id AS poolId, pools.name AS name, pools.link AS link, @@ -47,7 +48,7 @@ class PoolsRepository { unique_id as poolUniqueId FROM blocks JOIN pools on pools.id = pool_id - LEFT JOIN blocks_audits ON blocks_audits.height = blocks.height + LEFT JOIN blocks_audits ON blocks_audits.hash = blocks.hash WHERE blocks.stale = 0 `; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 57406f287..380138485 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -32,7 +32,7 @@ "bootstrap": "~5.3.8", "clipboard": "^2.0.11", "domino": "^2.1.6", - "echarts": "~5.4.0", + "echarts": "~6.1.0", "ngx-echarts": "~20.0.2", "ngx-infinite-scroll": "^20.0.0", "qrcode": "1.5.1", @@ -354,6 +354,22 @@ } } }, + "node_modules/@angular-devkit/architect/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/architect/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -372,6 +388,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/architect/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/architect/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -989,6 +1019,22 @@ } } }, + "node_modules/@angular-devkit/build-angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -1107,6 +1153,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/build-angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/build-angular/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -1243,6 +1303,22 @@ } } }, + "node_modules/@angular-devkit/schematics/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/schematics/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -1261,6 +1337,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular-devkit/schematics/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular-devkit/schematics/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -2145,6 +2235,22 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/@angular/cli/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/cli/node_modules/cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -2220,6 +2326,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@angular/cli/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@angular/cli/node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -7455,6 +7575,22 @@ } } }, + "node_modules/@schematics/angular/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@schematics/angular/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -7473,6 +7609,20 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@schematics/angular/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@schematics/angular/node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -10671,13 +10821,12 @@ } }, "node_modules/echarts": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", - "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", - "license": "Apache-2.0", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "dependencies": { "tslib": "2.3.0", - "zrender": "5.4.4" + "zrender": "6.1.0" } }, "node_modules/echarts/node_modules/tslib": { @@ -18629,10 +18778,9 @@ "license": "MIT" }, "node_modules/zrender": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", - "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", - "license": "BSD-3-Clause", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", "dependencies": { "tslib": "2.3.0" } @@ -18640,8 +18788,7 @@ "node_modules/zrender/node_modules/tslib": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", - "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", - "license": "0BSD" + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" } }, "dependencies": { @@ -18845,6 +18992,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -18855,6 +19012,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19112,6 +19276,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -19192,6 +19366,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -19268,6 +19449,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -19278,6 +19469,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -19687,6 +19885,16 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==" }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "cli-truncate": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", @@ -19734,6 +19942,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", @@ -22612,6 +22827,16 @@ "ajv": "^8.0.0" } }, + "chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "optional": true, + "peer": true, + "requires": { + "readdirp": "^4.0.1" + } + }, "json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -22622,6 +22847,13 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==" }, + "readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "optional": true, + "peer": true + }, "source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -24919,12 +25151,12 @@ } }, "echarts": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz", - "integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", "requires": { "tslib": "2.3.0", - "zrender": "5.4.4" + "zrender": "6.1.0" }, "dependencies": { "tslib": { @@ -30309,9 +30541,9 @@ "integrity": "sha512-XE96n56IQpJM7NAoXswY3XRLcWFW83xe0BiAOeMD7K5k5xecOeul3Qcpx6GqEeeHNkW5DWL5zOyTbEfB4eti8w==" }, "zrender": { - "version": "5.4.4", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz", - "integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", "requires": { "tslib": "2.3.0" }, diff --git a/frontend/package.json b/frontend/package.json index 829694835..9fe8f89ef 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -82,7 +82,7 @@ "bootstrap": "~5.3.8", "clipboard": "^2.0.11", "domino": "^2.1.6", - "echarts": "~5.4.0", + "echarts": "~6.1.0", "ngx-echarts": "~20.0.2", "ngx-infinite-scroll": "^20.0.0", "qrcode": "1.5.1", diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index c93e831c9..acc85d65b 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -45,15 +45,289 @@

Enterprise Sponsors 🚀

- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - Spiral diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts index d27d7db1b..915c79c93 100644 --- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts +++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts @@ -220,6 +220,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnChanges, OnDest }, }, legend: { + top: 'top', data: [ { name: this.totalBidBoostLabel, diff --git a/frontend/src/app/components/address-graph/address-graph.component.ts b/frontend/src/app/components/address-graph/address-graph.component.ts index 2d4d77f13..dd4022741 100644 --- a/frontend/src/app/components/address-graph/address-graph.component.ts +++ b/frontend/src/app/components/address-graph/address-graph.component.ts @@ -211,6 +211,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { }, graphic: graphicElements.length > 0 ? graphicElements : undefined, legend: (this.showLegend && !this.stateService.isAnyTestnet()) ? { + top: 'top', data: [ { name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`, @@ -451,6 +452,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { }, graphic: graphicElements.length > 0 ? graphicElements : undefined, legend: { + top: 'top', selected: this.selected, }, dataZoom: this.allowZoom ? [{ @@ -490,15 +492,15 @@ export class AddressGraphComponent implements OnChanges, OnDestroy { // Add a point at today's date to make the graph end at the current time extendedSummary.unshift({ time: Date.now() / 1000, value: 0 }); - let maxTime = Date.now() / 1000; - + const timeStep = 0.001; const oneHour = 60 * 60; // Fill gaps longer than interval for (let i = 0; i < extendedSummary.length - 1; i++) { - if (extendedSummary[i].time > maxTime) { - extendedSummary[i].time = maxTime - 30; + if (extendedSummary[i].height !== undefined && extendedSummary[i + 1].height === extendedSummary[i].height) { + extendedSummary[i + 1].time = extendedSummary[i].time; + } else if (extendedSummary[i + 1].time >= extendedSummary[i].time) { + extendedSummary[i + 1].time = extendedSummary[i].time - timeStep; } - maxTime = extendedSummary[i].time; const hours = Math.floor((extendedSummary[i].time - extendedSummary[i + 1].time) / oneHour); if (hours > 1) { for (let j = 1; j < hours; j++) { diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts index 86c7e4619..5293c7ba3 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -281,6 +281,7 @@ export class BlockFeeRatesGraphComponent implements OnInit { }, }, legend: (this.widget || data.series.length === 0) ? undefined : { + top: 'top', padding: [10, 75], data: data.legends, selected: JSON.parse(this.storageService.getValue('fee_rates_legend') || 'null') ?? { diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index 82caae836..4c3d7f3a0 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -187,6 +187,7 @@ export class BlockFeesGraphComponent implements OnInit { } }, legend: data.blockFees.length === 0 ? undefined : { + top: 'top', data: [ { name: feesBtcLabel, diff --git a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts index 9ac3fb01f..212f1c41d 100644 --- a/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts +++ b/frontend/src/app/components/block-fees-subsidy-graph/block-fees-subsidy-graph.component.ts @@ -240,6 +240,7 @@ export class BlockFeesSubsidyGraphComponent implements OnInit { } ], legend: this.data.blockFees.length === 0 ? undefined : { + top: 'top', data: [ { name: this.subsidyLabel, diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index 137cabe74..8dabd236c 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -183,6 +183,7 @@ export class BlockRewardsGraphComponent implements OnInit { } }, legend: data.blockRewards.length === 0 ? undefined : { + top: 'top', data: [ { name: 'Rewards BTC', diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index 1223be9c8..f1ec24e9a 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -182,7 +182,7 @@ export class BlockSizesWeightsGraphComponent implements OnInit { } }, legend: data.sizes.length === 0 ? undefined : { - padding: 10, + top: 'top', data: [ { name: $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`, diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index 5c429fd6c..2ecba6e56 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -287,6 +287,7 @@ export class HashrateChartComponent implements OnInit { } }, legend: (this.widget || data.hashrates.length === 0) ? undefined : { + top: 'top', data: [ { name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`, diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index cc45cbc7f..a51bcf548 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -250,6 +250,7 @@ export class HashrateChartPoolsComponent implements OnInit { } }, legend: (this.isMobile() || data.series.length === 0) ? undefined : { + top: 'top', data: data.legends }, yAxis: data.series.length === 0 ? undefined : { diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 427035693..61821b540 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -240,6 +240,7 @@ export class PoolComponent implements OnInit, OnDestroy { } }, legend: { + top: 'top', data: [ { name: $localize`:@@79a9dc5b1caca3cbeb1733a19515edacc5fc7920:Hashrate`, diff --git a/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts b/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts index d1c15ec2b..8991c251a 100644 --- a/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts +++ b/frontend/src/app/components/treasuries/treasuries-graph/treasuries-graph.component.ts @@ -402,6 +402,7 @@ export class TreasuriesGraphComponent implements OnInit, OnChanges, OnDestroy { this.chartOptions = { legend: { + top: 'top', selected: this.selectedWallets, } }; diff --git a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts index d335e8b30..2eaf67183 100644 --- a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts +++ b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts @@ -200,7 +200,7 @@ export class LightningStatisticsChartComponent implements OnInit, OnChanges { } }, legend: this.widget || data.channel_count.length === 0 ? undefined : { - padding: 10, + top: 'top', data: [ { name: $localize`:@@807cf11e6ac1cde912496f764c176bdfdd6b7e19:Channels`, diff --git a/frontend/src/app/shared/transaction.utils.ts b/frontend/src/app/shared/transaction.utils.ts index 8bb4fcbc4..7598d3862 100644 --- a/frontend/src/app/shared/transaction.utils.ts +++ b/frontend/src/app/shared/transaction.utils.ts @@ -713,7 +713,7 @@ function isNonStandardLegacySigops(tx: Transaction, height?: number, network?: s // A witness program is any valid scriptpubkey that consists of a 1-byte push opcode // followed by a data push between 2 and 40 bytes. // https://github.com/bitcoin/bitcoin/blob/2c79abc7ad4850e9e3ba32a04c530155cda7f980/src/script/script.cpp#L224-L240 -function isWitnessProgram(scriptpubkey: string): false | { version: number, program: string } { +export function isWitnessProgram(scriptpubkey: string): false | { version: number, program: string } { if (scriptpubkey.length < 8 || scriptpubkey.length > 84) { return false; } @@ -1456,6 +1456,11 @@ function fromBuffer(buffer: Uint8Array, network: string, inputs?: PsbtKeyValueMa export type PsbtKeyValue = { keyData: Uint8Array; value: Uint8Array; }; export type PsbtKeyValueMap = Map; +export const PSBT_GLOBAL = { + UNSIGNED_TX: 0x00, + GENERIC_SIGNED_MESSAGE: 0x09, +}; + export const PSBT_IN = { NON_WITNESS_UTXO: 0x00, WITNESS_UTXO: 0x01, @@ -1601,9 +1606,10 @@ function decodePsbt(psbtBuffer: Uint8Array): { rawTx: Uint8Array; inputs: PsbtKe * @param rawTx - The unsigned transaction as Uint8Array * @param inputs - Array of input maps containing key-value pairs for each input * @param outputs - Array of output maps containing key-value pairs for each output + * @param globals - Global key-value pairs for the PSBT * @returns PSBT buffer as Uint8Array */ -export function encodePsbt(rawTx: Uint8Array, inputs: PsbtKeyValueMap[], outputs: PsbtKeyValueMap[]): Uint8Array { +export function encodePsbt(rawTx: Uint8Array, inputs: PsbtKeyValueMap[], outputs: PsbtKeyValueMap[], globals: PsbtKeyValueMap = new Map()): Uint8Array { const result: number[] = []; // Magic bytes: "psbt" in ASCII @@ -1633,7 +1639,15 @@ export function encodePsbt(rawTx: Uint8Array, inputs: PsbtKeyValueMap[], outputs // GLOBAL MAP // Add unsigned transaction (key type 0x00) - writeKeyValue(0x00, new Uint8Array(), rawTx); + writeKeyValue(PSBT_GLOBAL.UNSIGNED_TX, new Uint8Array(), rawTx); + for (const [keyType, items] of globals) { + if (keyType === PSBT_GLOBAL.UNSIGNED_TX) { + throw new Error('PSBT global map must not include UNSIGNED_TX (0x00); provide it via rawTx'); + } + for (const record of items) { + writeKeyValue(keyType, record.keyData, record.value); + } + } // End global map result.push(0x00); diff --git a/production/elements.crontab b/production/elements.crontab index c93a982d6..132e3dd8f 100644 --- a/production/elements.crontab +++ b/production/elements.crontab @@ -6,9 +6,6 @@ @reboot sleep 35 ; screen -dmS liquidv1 /elements/electrs/start liquid @reboot sleep 45 ; screen -dmS liquidtestnet /elements/electrs/start liquidtestnet -# hourly asset update and electrs restart -6 * * * * cd $HOME/asset_registry_db && git pull --quiet origin master && cd $HOME/asset_registry_testnet_db && git pull --quiet origin master && killall electrs - # daily update of popular-scripts 32 03 * * * $HOME/electrs/start liquid popular-scripts >/dev/null 2>&1 33 03 * * * $HOME/electrs/start liquidtestnet popular-scripts >/dev/null 2>&1 diff --git a/production/nginx-cache-warmer b/production/nginx-cache-warmer index 171f95430..a24898a30 100755 --- a/production/nginx-cache-warmer +++ b/production/nginx-cache-warmer @@ -1,5 +1,5 @@ #!/usr/bin/env zsh -delay=0.15 +delay=0.25 hostname=$(hostname) slugs=(`curl -sSL https://${hostname}/api/v1/mining/pools/3y|jq -r -S '(.pools[].slug)'`) @@ -137,7 +137,7 @@ warmURLs=( '/api/v1/lightning/nodes/isp/34197' `# SHRD SARL` '/api/v1/lightning/nodes/isp/42275' `# Three Fourteen SASU` '/api/v1/lightning/nodes/isp/16276' `# OVH SAS` - '/api/v1/lightning/nodes/isp/10796,11351,11426,11427,12271,20001,2$delay,33363' `# Spectrum` + '/api/v1/lightning/nodes/isp/10796,11351,11426,11427,12271,20001,20115,33363' `# Spectrum` \ '/api/v1/lightning/nodes/isp/701' `# Verizon` '/api/v1/lightning/nodes/isp/12876' `# Scaleway` '/api/v1/lightning/nodes/isp/33915' `# Ziggo` @@ -175,6 +175,9 @@ do for url in $warmURLs do warm "https://${hostname}${url}" + warm "https://${hostname}/testnet${url}" + warm "https://${hostname}/testnet4${url}" + warm "https://${hostname}/signet${url}" sleep $delay # delay between queries to not DoS mariadb done diff --git a/production/nginx/location-liquid-api.conf b/production/nginx/location-liquid-api.conf index a6538c6b8..c20e9a87c 100644 --- a/production/nginx/location-liquid-api.conf +++ b/production/nginx/location-liquid-api.conf @@ -90,8 +90,8 @@ location @mempool-liquid-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; proxy_cache_valid 200 10s; proxy_redirect off; diff --git a/production/nginx/location-liquidtestnet-api.conf b/production/nginx/location-liquidtestnet-api.conf index c7a547e03..ede04b320 100644 --- a/production/nginx/location-liquidtestnet-api.conf +++ b/production/nginx/location-liquidtestnet-api.conf @@ -94,10 +94,10 @@ location @mempool-liquidtestnet-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; - proxy_cache_valid 200 10s; + proxy_cache_valid 200 30s; proxy_redirect off; } diff --git a/production/nginx/location-signet-api.conf b/production/nginx/location-signet-api.conf index c184c4272..5b01394ef 100644 --- a/production/nginx/location-signet-api.conf +++ b/production/nginx/location-signet-api.conf @@ -100,8 +100,8 @@ location @mempool-signet-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; proxy_cache_valid 200 10s; proxy_redirect off; diff --git a/production/nginx/location-testnet-api.conf b/production/nginx/location-testnet-api.conf index 4b9433bb8..80d61198c 100644 --- a/production/nginx/location-testnet-api.conf +++ b/production/nginx/location-testnet-api.conf @@ -100,11 +100,13 @@ location @mempool-testnet-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; - proxy_cache_valid 200 10s; + proxy_cache_valid 200 30s; proxy_redirect off; + + expires 120s; } location @mempool-testnet-api-v1-cache-normal { diff --git a/production/nginx/location-testnet4-api.conf b/production/nginx/location-testnet4-api.conf index 11600d9e2..a24126d70 100644 --- a/production/nginx/location-testnet4-api.conf +++ b/production/nginx/location-testnet4-api.conf @@ -100,10 +100,10 @@ location @mempool-testnet4-api-v1-cache-warm { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; - proxy_cache_background_update off; - proxy_cache_use_stale error; + proxy_cache_background_update on; + proxy_cache_use_stale updating; proxy_cache apiwarm; - proxy_cache_valid 200 10s; + proxy_cache_valid 200 30s; proxy_redirect off; }