From 8822c0972f903bd23ff2d24e6f6796d43d5f09b6 Mon Sep 17 00:00:00 2001 From: natsoni Date: Mon, 25 Mar 2024 12:07:33 +0900 Subject: [PATCH 01/28] Fix unnecessary request of blocks on dashboard load --- frontend/src/app/components/start/start.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/components/start/start.component.ts b/frontend/src/app/components/start/start.component.ts index d8a667a28..0a4943bde 100644 --- a/frontend/src/app/components/start/start.component.ts +++ b/frontend/src/app/components/start/start.component.ts @@ -24,7 +24,7 @@ export class StartComponent implements OnInit, AfterViewChecked, OnDestroy { timeLtrSubscription: Subscription; timeLtr: boolean = this.stateService.timeLtr.value; chainTipSubscription: Subscription; - chainTip: number = 100; + chainTip: number = -1; tipIsSet: boolean = false; lastMark: MarkBlockState; markBlockSubscription: Subscription; From 82e115c00994674323e3c32eae0995277fadd51e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Wed, 27 Mar 2024 02:46:29 +0000 Subject: [PATCH 02/28] Standardize the block visualization grid --- .../block-overview-graph/block-overview-graph.component.html | 2 +- .../block-overview-graph/block-overview-graph.component.scss | 5 ++++- .../block-overview-graph/block-overview-graph.component.ts | 1 + .../src/app/components/block/block-preview.component.html | 3 ++- 4 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html index bc5b3744d..702718742 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.html @@ -1,5 +1,5 @@ -
+
diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss index deb0a6f55..c468cf792 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.scss @@ -22,9 +22,12 @@ } } -.grid-align { +.graph-alignment { position: relative; width: 100%; +} + +.grid-align { display: grid; grid-template-columns: repeat(auto-fit, 75px); justify-content: center; diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index ab6985a25..61c2108f2 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -32,6 +32,7 @@ const unmatchedAuditColors = { export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, OnChanges { @Input() isLoading: boolean; @Input() resolution: number; + @Input() autofit: boolean = false; @Input() blockLimit: number; @Input() orientation = 'left'; @Input() flip = true; diff --git a/frontend/src/app/components/block/block-preview.component.html b/frontend/src/app/components/block/block-preview.component.html index 0c33246a7..02fe62774 100644 --- a/frontend/src/app/components/block/block-preview.component.html +++ b/frontend/src/app/components/block/block-preview.component.html @@ -70,8 +70,9 @@
Date: Thu, 28 Mar 2024 07:42:18 +0000 Subject: [PATCH 03/28] Bump express from 4.18.1 to 4.19.2 in /unfurler Bumps [express](https://github.com/expressjs/express) from 4.18.1 to 4.19.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.18.1...4.19.2) --- updated-dependencies: - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- unfurler/package-lock.json | 379 ++++++++++++++++++++++++++----------- unfurler/package.json | 2 +- 2 files changed, 273 insertions(+), 108 deletions(-) diff --git a/unfurler/package-lock.json b/unfurler/package-lock.json index 16968f203..c2ab86486 100644 --- a/unfurler/package-lock.json +++ b/unfurler/package-lock.json @@ -9,7 +9,7 @@ "version": "3.0.0-dev", "dependencies": { "@types/node": "^16.11.41", - "express": "^4.18.0", + "express": "^4.19.2", "puppeteer": "^15.3.2", "puppeteer-cluster": "^0.23.0", "typescript": "~4.7.4" @@ -440,20 +440,20 @@ } }, "node_modules/body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", + "qs": "6.11.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -536,12 +536,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -612,17 +618,17 @@ } }, "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { "node": ">= 0.6" } }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -676,6 +682,22 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -743,6 +765,25 @@ "once": "^1.4.0" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -978,16 +1019,16 @@ } }, "node_modules/express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.0", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -1003,7 +1044,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.10.3", + "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.18.0", @@ -1225,9 +1266,12 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/functional-red-black-tree": { "version": "1.0.1", @@ -1236,13 +1280,18 @@ "dev": true }, "node_modules/get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1328,15 +1377,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "dependencies": { - "function-bind": "^1.1.1" + "get-intrinsic": "^1.1.3" }, - "engines": { - "node": ">= 0.4.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/has-flag": { @@ -1348,6 +1397,28 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -1359,6 +1430,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1702,9 +1784,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1967,9 +2049,9 @@ } }, "node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "dependencies": { "side-channel": "^1.0.4" }, @@ -2009,9 +2091,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -2197,6 +2279,22 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -2224,13 +2322,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2831,20 +2933,20 @@ } }, "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.10.3", - "raw-body": "2.5.1", + "qs": "6.11.0", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -2902,12 +3004,15 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -2960,14 +3065,14 @@ } }, "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -3007,6 +3112,16 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -3058,6 +3173,19 @@ "once": "^1.4.0" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -3233,16 +3361,16 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.1.tgz", - "integrity": "sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.0", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3258,7 +3386,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.10.3", + "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.18.0", @@ -3448,9 +3576,9 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "functional-red-black-tree": { "version": "1.0.1", @@ -3459,13 +3587,15 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-stream": { @@ -3521,12 +3651,12 @@ "slash": "^3.0.0" } }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", "requires": { - "function-bind": "^1.1.1" + "get-intrinsic": "^1.1.3" } }, "has-flag": { @@ -3535,11 +3665,32 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -3783,9 +3934,9 @@ } }, "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "on-finished": { "version": "2.4.1", @@ -3972,9 +4123,9 @@ } }, "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", "requires": { "side-channel": "^1.0.4" } @@ -3991,9 +4142,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -4118,6 +4269,19 @@ "send": "0.18.0" } }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -4139,13 +4303,14 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "slash": { diff --git a/unfurler/package.json b/unfurler/package.json index e9d2dca25..5c61f9233 100644 --- a/unfurler/package.json +++ b/unfurler/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "@types/node": "^16.11.41", - "express": "^4.18.0", + "express": "^4.19.2", "puppeteer": "^15.3.2", "puppeteer-cluster": "^0.23.0", "typescript": "~4.7.4" From 22d9a1cb8b6ea78cf7e439746ebde380ff1b888e Mon Sep 17 00:00:00 2001 From: natsoni Date: Wed, 27 Mar 2024 19:52:57 +0900 Subject: [PATCH 04/28] Keep block fees on infinite blockchain when leaving mining dashboard --- .../blockchain-blocks.component.ts | 13 +++++++++++-- .../mempool-blocks/mempool-blocks.component.ts | 13 ++++++------- frontend/src/app/services/state.service.ts | 1 + 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts index 760e9261d..e81e97acc 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -55,6 +55,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { blocksFilled = false; arrowTransition = '1s'; showMiningInfo = false; + showMiningInfoSubscription: Subscription; timeLtrSubscription: Subscription; timeLtr: boolean; @@ -80,8 +81,11 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { } enabledMiningInfoIfNeeded(url) { - this.showMiningInfo = url.includes('/mining') || url.includes('/acceleration'); - this.cd.markForCheck(); // Need to update the view asap + const urlParts = url.split('/'); + const onDashboard = ['','testnet','signet','mining','acceleration'].includes(urlParts[urlParts.length - 1]); + if (onDashboard) { // Only update showMiningInfo if we are on the main, mining or acceleration dashboards + this.stateService.showMiningInfo$.next(url.includes('/mining') || url.includes('/acceleration')); + } } ngOnInit() { @@ -90,6 +94,10 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { if (['', 'testnet', 'signet'].includes(this.stateService.network)) { this.enabledMiningInfoIfNeeded(this.location.path()); this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url)); + this.showMiningInfoSubscription = this.stateService.showMiningInfo$.subscribe((showMiningInfo) => { + this.showMiningInfo = showMiningInfo; + this.cd.markForCheck(); + }); } this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { @@ -202,6 +210,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { this.tabHiddenSubscription.unsubscribe(); this.markBlockSubscription.unsubscribe(); this.timeLtrSubscription.unsubscribe(); + this.showMiningInfoSubscription.unsubscribe(); clearInterval(this.interval); } diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts index 111491f94..016798777 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -60,6 +60,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { showMiningInfo = false; timeLtrSubscription: Subscription; timeLtr: boolean; + showMiningInfoSubscription: Subscription; animateEntry: boolean = false; blockOffset: number = 155; @@ -89,11 +90,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { private location: Location, ) { } - enabledMiningInfoIfNeeded(url) { - this.showMiningInfo = url.includes('/mining') || url.includes('/acceleration'); - this.cd.markForCheck(); // Need to update the view asap - } - ngOnInit() { this.chainTip = this.stateService.latestBlockHeight; @@ -102,8 +98,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { this.widthChange.emit(this.mempoolWidth); if (['', 'testnet', 'signet'].includes(this.stateService.network)) { - this.enabledMiningInfoIfNeeded(this.location.path()); - this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url)); + this.showMiningInfoSubscription = this.stateService.showMiningInfo$.subscribe((showMiningInfo) => { + this.showMiningInfo = showMiningInfo; + this.cd.markForCheck(); + }); } this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { @@ -269,6 +267,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { this.chainTipSubscription.unsubscribe(); this.keySubscription.unsubscribe(); this.isTabHiddenSubscription.unsubscribe(); + this.showMiningInfoSubscription.unsubscribe(); clearTimeout(this.resetTransitionTimeout); } diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 9a591452e..f9de61b69 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -151,6 +151,7 @@ export class StateService { hideAudit: BehaviorSubject; fiatCurrency$: BehaviorSubject; rateUnits$: BehaviorSubject; + showMiningInfo$: BehaviorSubject = new BehaviorSubject(false); searchFocus$: Subject = new Subject(); menuOpen$: BehaviorSubject = new BehaviorSubject(false); From 70bf31c3978d39677b18ab3afa4074f0fd426afe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 17:18:10 +0000 Subject: [PATCH 05/28] Bump express from 4.18.2 to 4.19.2 in /backend Bumps [express](https://github.com/expressjs/express) from 4.18.2 to 4.19.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.18.2...4.19.2) --- updated-dependencies: - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- backend/package-lock.json | 344 +++++++++++++++++++++++++++++--------- backend/package.json | 2 +- 2 files changed, 266 insertions(+), 80 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index b0d433059..41bc0577b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -16,7 +16,7 @@ "axios": "~1.6.1", "bitcoinjs-lib": "~6.1.3", "crypto-js": "~4.2.0", - "express": "~4.18.2", + "express": "~4.19.2", "maxmind": "~4.3.11", "mysql2": "~3.9.1", "redis": "^4.6.6", @@ -2529,12 +2529,12 @@ } }, "node_modules/body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "dependencies": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -2542,7 +2542,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -2671,12 +2671,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -2860,9 +2866,9 @@ "dev": true }, "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", "engines": { "node": ">= 0.6" } @@ -2934,6 +2940,22 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -3064,6 +3086,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -3459,16 +3500,16 @@ } }, "node_modules/express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -3734,9 +3775,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/generate-function": { "version": "2.3.1", @@ -3773,13 +3817,18 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3867,6 +3916,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3883,6 +3943,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "dependencies": { "function-bind": "^1.1.1" }, @@ -3899,6 +3960,28 @@ "node": ">=4" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", @@ -3910,6 +3993,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -6212,9 +6306,9 @@ } }, "node_modules/object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -6634,9 +6728,9 @@ } }, "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -6876,6 +6970,22 @@ "node": ">= 0.8.0" } }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -6903,13 +7013,17 @@ } }, "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9560,12 +9674,12 @@ } }, "body-parser": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", - "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", "requires": { "bytes": "3.1.2", - "content-type": "~1.0.4", + "content-type": "~1.0.5", "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", @@ -9573,7 +9687,7 @@ "iconv-lite": "0.4.24", "on-finished": "2.4.1", "qs": "6.11.0", - "raw-body": "2.5.1", + "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" }, @@ -9671,12 +9785,15 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" } }, "callsites": { @@ -9803,9 +9920,9 @@ "dev": true }, "cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" }, "cookie-signature": { "version": "1.0.6", @@ -9860,6 +9977,16 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "dev": true }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -9953,6 +10080,19 @@ "is-arrayish": "^0.2.1" } }, + "es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + } + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -10234,16 +10374,16 @@ } }, "express": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", - "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.1", + "body-parser": "1.20.2", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.5.0", + "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", @@ -10458,9 +10598,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "generate-function": { "version": "2.3.1", @@ -10488,13 +10628,15 @@ "dev": true }, "get-intrinsic": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", - "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-package-type": { @@ -10552,6 +10694,14 @@ "slash": "^3.0.0" } }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -10568,6 +10718,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, "requires": { "function-bind": "^1.1.1" } @@ -10578,11 +10729,32 @@ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + }, "has-symbols": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, "html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", @@ -12299,9 +12471,9 @@ } }, "object-inspect": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", - "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" }, "on-finished": { "version": "2.4.1", @@ -12581,9 +12753,9 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", "requires": { "bytes": "3.1.2", "http-errors": "2.0.0", @@ -12757,6 +12929,19 @@ "send": "0.18.0" } }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -12778,13 +12963,14 @@ "dev": true }, "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" } }, "signal-exit": { diff --git a/backend/package.json b/backend/package.json index 5710745f7..767571f42 100644 --- a/backend/package.json +++ b/backend/package.json @@ -45,7 +45,7 @@ "axios": "~1.6.1", "bitcoinjs-lib": "~6.1.3", "crypto-js": "~4.2.0", - "express": "~4.18.2", + "express": "~4.19.2", "maxmind": "~4.3.11", "mysql2": "~3.9.1", "rust-gbt": "file:./rust-gbt", From e2d3bb4cc571ccff4e20512620aa84477096b777 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 17 Feb 2023 17:54:29 -0600 Subject: [PATCH 06/28] Use minfee node to limit gbt input size --- backend/src/api/mempool-blocks.ts | 89 +++++++++++++++++----------- backend/src/api/mempool.ts | 70 +++++++++++++++++++--- backend/src/api/websocket-handler.ts | 60 +++++++++++++++---- backend/src/config.ts | 2 + backend/src/index.ts | 5 +- backend/src/mempool.interfaces.ts | 6 ++ 6 files changed, 176 insertions(+), 56 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index b9da7d4e8..09c5d8e79 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -1,6 +1,6 @@ import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt'; import logger from '../logger'; -import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, PoolTag, TransactionClassified, TransactionCompressed, MempoolDeltaChange } from '../mempool.interfaces'; +import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, TransactionClassified, TransactionCompressed, MempoolDeltaChange, GbtCandidates } from '../mempool.interfaces'; import { Common, OnlineFeeStatsCalculator } from './common'; import config from '../config'; import { Worker } from 'worker_threads'; @@ -40,12 +40,9 @@ class MempoolBlocks { return this.mempoolBlockDeltas; } - public updateMempoolBlocks(memPool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false): MempoolBlockWithTransactions[] { + public updateMempoolBlocks(transactions: string[], memPool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false): MempoolBlockWithTransactions[] { const latestMempool = memPool; - const memPoolArray: MempoolTransactionExtended[] = []; - for (const i in latestMempool) { - memPoolArray.push(latestMempool[i]); - } + const memPoolArray: MempoolTransactionExtended[] = transactions.map(txid => memPool[txid]); const start = new Date().getTime(); // Clear bestDescendants & ancestors @@ -207,7 +204,7 @@ class MempoolBlocks { return mempoolBlockDeltas; } - public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number): Promise { + public async $makeBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number): Promise { const start = Date.now(); // reset mempool short ids @@ -215,8 +212,9 @@ class MempoolBlocks { this.resetUids(); } // set missing short ids - for (const tx of Object.values(newMempool)) { - this.setUid(tx, !saveResults); + for (const txid of transactions) { + const tx = newMempool[txid]; + this.setUid(tx, false); } const accelerations = useAccelerations ? mempool.getAccelerations() : {}; @@ -224,7 +222,8 @@ class MempoolBlocks { // prepare a stripped down version of the mempool with only the minimum necessary data // to reduce the overhead of passing this data to the worker thread const strippedMempool: Map = new Map(); - Object.values(newMempool).forEach(entry => { + for (const txid of transactions) { + const entry = newMempool[txid]; if (entry.uid !== null && entry.uid !== undefined) { const stripped = { uid: entry.uid, @@ -237,7 +236,7 @@ class MempoolBlocks { }; strippedMempool.set(entry.uid, stripped); } - }); + } // (re)initialize tx selection worker thread if (!this.txSelectionWorker) { @@ -268,7 +267,7 @@ class MempoolBlocks { // clean up thread error listener this.txSelectionWorker?.removeListener('error', threadErrorListener); - const processed = this.processBlockTemplates(newMempool, blocks, null, Object.entries(rates), Object.values(clusters), accelerations, accelerationPool, saveResults); + const processed = this.processBlockTemplates(newMempool, blocks, null, Object.entries(rates), Object.values(clusters), candidates, accelerations, accelerationPool, saveResults); logger.debug(`makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); @@ -279,10 +278,10 @@ class MempoolBlocks { return this.mempoolBlocks; } - public async $updateBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], accelerationDelta: string[] = [], saveResults: boolean = false, useAccelerations: boolean = false): Promise { + public async $updateBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], candidates: GbtCandidates | undefined, accelerationDelta: string[] = [], saveResults: boolean = false, useAccelerations: boolean = false): Promise { if (!this.txSelectionWorker) { // need to reset the worker - await this.$makeBlockTemplates(newMempool, saveResults, useAccelerations); + await this.$makeBlockTemplates(transactions, newMempool, candidates, saveResults, useAccelerations); return; } @@ -292,7 +291,7 @@ class MempoolBlocks { const addedAndChanged: MempoolTransactionExtended[] = useAccelerations ? accelerationDelta.map(txid => newMempool[txid]).filter(tx => tx != null).concat(added) : added; for (const tx of addedAndChanged) { - this.setUid(tx, true); + this.setUid(tx, false); } const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[]; @@ -328,7 +327,7 @@ class MempoolBlocks { // clean up thread error listener this.txSelectionWorker?.removeListener('error', threadErrorListener); - this.processBlockTemplates(newMempool, blocks, null, Object.entries(rates), Object.values(clusters), accelerations, null, saveResults); + this.processBlockTemplates(newMempool, blocks, null, Object.entries(rates), Object.values(clusters), candidates, accelerations, null, saveResults); logger.debug(`updateBlockTemplates completed in ${(Date.now() - start) / 1000} seconds`); } catch (e) { logger.err('updateBlockTemplates failed. ' + (e instanceof Error ? e.message : e)); @@ -336,23 +335,26 @@ class MempoolBlocks { } private resetRustGbt(): void { + console.log('reset rust gbt'); this.rustInitialized = false; this.rustGbtGenerator = new GbtGenerator(); } - public async $rustMakeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number): Promise { + public async $rustMakeBlockTemplates(txids: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false, useAccelerations: boolean = false, accelerationPool?: number): Promise { const start = Date.now(); // reset mempool short ids if (saveResults) { this.resetUids(); } + + const transactions = txids.map(txid => newMempool[txid]).filter(tx => tx != null); // set missing short ids - for (const tx of Object.values(newMempool)) { - this.setUid(tx, !saveResults); + for (const tx of transactions) { + this.setUid(tx, false); } // set short ids for transaction inputs - for (const tx of Object.values(newMempool)) { + for (const tx of transactions) { tx.inputs = tx.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => (uid !== null && uid !== undefined)) as number[]; } @@ -369,15 +371,15 @@ class MempoolBlocks { const rustGbt = saveResults ? this.rustGbtGenerator : new GbtGenerator(); try { const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( - await rustGbt.make(Object.values(newMempool) as RustThreadTransaction[], convertedAccelerations as RustThreadAcceleration[], this.nextUid), + await rustGbt.make(transactions as RustThreadTransaction[], convertedAccelerations as RustThreadAcceleration[], this.nextUid), ); if (saveResults) { this.rustInitialized = true; } - const mempoolSize = Object.keys(newMempool).length; + const expectedSize = transactions.length; const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length; - logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`); - const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, saveResults); + logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${expectedSize} in the mempool, ${overflow.length} were unmineable`); + const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, candidates, accelerations, accelerationPool, saveResults); logger.debug(`RUST makeBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; } catch (e) { @@ -389,26 +391,26 @@ class MempoolBlocks { return this.mempoolBlocks; } - public async $oneOffRustBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, useAccelerations: boolean, accelerationPool?: number): Promise { - return this.$rustMakeBlockTemplates(newMempool, false, useAccelerations, accelerationPool); + public async $oneOffRustBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, useAccelerations: boolean, accelerationPool?: number): Promise { + return this.$rustMakeBlockTemplates(transactions, newMempool, candidates, false, useAccelerations, accelerationPool); } - public async $rustUpdateBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], useAccelerations: boolean, accelerationPool?: number): Promise { + public async $rustUpdateBlockTemplates(transactions: string[], newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], candidates: GbtCandidates | undefined, useAccelerations: boolean, accelerationPool?: number): Promise { // GBT optimization requires that uids never get too sparse // as a sanity check, we should also explicitly prevent uint32 uid overflow - if (this.nextUid + added.length >= Math.min(Math.max(262144, 2 * mempoolSize), MAX_UINT32)) { + if (this.nextUid + added.length >= Math.min(Math.max(262144, 2 * transactions.length), MAX_UINT32)) { this.resetRustGbt(); } if (!this.rustInitialized) { // need to reset the worker - return this.$rustMakeBlockTemplates(newMempool, true, useAccelerations, accelerationPool); + return this.$rustMakeBlockTemplates(transactions, newMempool, candidates, true, useAccelerations, accelerationPool); } const start = Date.now(); // set missing short ids for (const tx of added) { - this.setUid(tx, true); + this.setUid(tx, false); } // set short ids for transaction inputs for (const tx of added) { @@ -416,6 +418,8 @@ class MempoolBlocks { } const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => (uid !== null && uid !== undefined)) as number[]; + console.log(`removing ${removed.length} (${removedUids.length} with uids)`); + const accelerations = useAccelerations ? mempool.getAccelerations() : {}; const acceleratedList = accelerationPool ? Object.values(accelerations).filter(acc => newMempool[acc.txid] && acc.pools.includes(accelerationPool)) : Object.values(accelerations).filter(acc => newMempool[acc.txid]); const convertedAccelerations = acceleratedList.map(acc => { @@ -425,6 +429,8 @@ class MempoolBlocks { }; }); + console.log(`${acceleratedList.length} accelerations`); + // run the block construction algorithm in a separate thread, and wait for a result try { const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( @@ -436,11 +442,11 @@ class MempoolBlocks { ), ); const resultMempoolSize = blocks.reduce((total, block) => total + block.length, 0) + overflow.length; - logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${mempoolSize} in the mempool, ${overflow.length} were unmineable`); - if (mempoolSize !== resultMempoolSize) { - throw new Error('GBT returned wrong number of transactions , cache is probably out of sync'); + logger.debug(`RUST updateBlockTemplates returned ${resultMempoolSize} txs out of ${transactions.length} candidates, ${overflow.length} were unmineable`); + if (transactions.length !== resultMempoolSize) { + throw new Error(`GBT returned wrong number of transactions ${transactions.length} vs ${resultMempoolSize}, cache is probably out of sync`); } else { - const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, accelerations, accelerationPool, true); + const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, candidates, accelerations, accelerationPool, true); this.removeUids(removedUids); logger.debug(`RUST updateBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; @@ -452,7 +458,7 @@ class MempoolBlocks { } } - private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] { + private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], candidates: GbtCandidates | undefined, accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] { for (const [txid, rate] of rates) { if (txid in mempool) { mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize); @@ -486,6 +492,9 @@ class MempoolBlocks { if (txid === memberTxid) { matched = true; } else { + if (!mempool[txid]) { + console.log('txid missing from mempool! ', txid, candidates?.txs[txid]); + } const relative = { txid: txid, fee: mempool[txid].fee, @@ -518,6 +527,16 @@ class MempoolBlocks { let totalWeight = 0; let totalFees = 0; const transactions: MempoolTransactionExtended[] = []; + + // backfill purged transactions + if (candidates?.txs && blockIndex === blocks.length - 1) { + for (const txid of Object.keys(mempool)) { + if (!candidates.txs[txid]) { + block.push(txid); + } + } + } + for (const txid of block) { if (txid) { mempoolTx = mempool[txid]; diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 4c2f248ac..540e0828b 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -1,6 +1,6 @@ import config from '../config'; import bitcoinApi from './bitcoin/bitcoin-api-factory'; -import { MempoolTransactionExtended, TransactionExtended, VbytesPerSecond } from '../mempool.interfaces'; +import { MempoolTransactionExtended, TransactionExtended, VbytesPerSecond, GbtCandidates } from '../mempool.interfaces'; import logger from '../logger'; import { Common } from './common'; import transactionUtils from './transaction-utils'; @@ -11,18 +11,21 @@ import bitcoinSecondClient from './bitcoin/bitcoin-second-client'; import rbfCache from './rbf-cache'; import { Acceleration } from './services/acceleration'; import redisCache from './redis-cache'; +import blocks from './blocks'; class Mempool { private inSync: boolean = false; private mempoolCacheDelta: number = -1; private mempoolCache: { [txId: string]: MempoolTransactionExtended } = {}; + private mempoolCandidates: { [txid: string ]: boolean } = {}; + private minFeeMempool: { [txId: string]: boolean } = {}; private spendMap = new Map(); private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0, maxmempool: 300000000, mempoolminfee: Common.isLiquid() ? 0.00000100 : 0.00001000, minrelaytxfee: Common.isLiquid() ? 0.00000100 : 0.00001000 }; private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => void) | undefined; private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, mempoolSize: number, newTransactions: MempoolTransactionExtended[], - deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => Promise) | undefined; + deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[], candidates?: GbtCandidates) => Promise) | undefined; private accelerations: { [txId: string]: Acceleration } = {}; @@ -40,6 +43,8 @@ class Mempool { private missingTxCount = 0; private mainLoopTimeout: number = 120000; + public limitGBT = config.MEMPOOL.USE_SECOND_NODE_FOR_MINFEE && config.MEMPOOL.LIMIT_GBT; + constructor() { setInterval(this.updateTxPerSecond.bind(this), 1000); } @@ -74,7 +79,8 @@ class Mempool { } public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; }, mempoolSize: number, - newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]) => Promise): void { + newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[], + candidates?: GbtCandidates) => Promise): void { this.$asyncMempoolChangedCallback = fn; } @@ -117,7 +123,7 @@ class Mempool { this.mempoolChangedCallback(this.mempoolCache, [], [], []); } if (this.$asyncMempoolChangedCallback) { - await this.$asyncMempoolChangedCallback(this.mempoolCache, count, [], [], []); + await this.$asyncMempoolChangedCallback(this.mempoolCache, count, [], [], [], this.limitGBT ? { txs: {}, added: [], removed: [] } : undefined); } this.addToSpendMap(Object.values(this.mempoolCache)); } @@ -160,6 +166,10 @@ class Mempool { return newTransactions; } + public getMempoolCandidates(): { [txid: string]: boolean } { + return this.mempoolCandidates; + } + public async $updateMemPoolInfo() { this.mempoolInfo = await this.$getMempoolInfo(); } @@ -189,7 +199,7 @@ class Mempool { return txTimes; } - public async $updateMempool(transactions: string[], accelerations: Acceleration[] | null, pollRate: number): Promise { + public async $updateMempool(transactions: string[], accelerations: Acceleration[] | null, minFeeMempool: string[], minFeeTip: number, pollRate: number): Promise { logger.debug(`Updating mempool...`); // warn if this run stalls the main loop for more than 2 minutes @@ -311,6 +321,8 @@ class Mempool { }, 1000 * 60 * config.MEMPOOL.CLEAR_PROTECTION_MINUTES); } + const candidates = await this.getNextCandidates(minFeeMempool, minFeeTip); + const deletedTransactions: MempoolTransactionExtended[] = []; if (this.mempoolProtection !== 1) { @@ -341,12 +353,14 @@ class Mempool { this.mempoolCacheDelta = Math.abs(transactions.length - newMempoolSize); + const candidatesChanged = candidates?.added?.length || candidates?.removed?.length; + if (this.mempoolChangedCallback && (hasChange || deletedTransactions.length)) { this.mempoolChangedCallback(this.mempoolCache, newTransactions, deletedTransactions, accelerationDelta); } - if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length)) { + if (this.$asyncMempoolChangedCallback && (hasChange || deletedTransactions.length || candidatesChanged)) { this.updateTimerProgress(timer, 'running async mempool callback'); - await this.$asyncMempoolChangedCallback(this.mempoolCache, newMempoolSize, newTransactions, deletedTransactions, accelerationDelta); + await this.$asyncMempoolChangedCallback(this.mempoolCache, newMempoolSize, newTransactions, deletedTransactions, accelerationDelta, candidates); this.updateTimerProgress(timer, 'completed async mempool callback'); } @@ -432,6 +446,48 @@ class Mempool { } } + public async getNextCandidates(minFeeTransactions: string[], blockHeight: number): Promise { + if (this.limitGBT) { + const newCandidateTxMap = {}; + this.minFeeMempool = {}; + for (const txid of minFeeTransactions) { + if (this.mempoolCache[txid]) { + newCandidateTxMap[txid] = true; + } + this.minFeeMempool[txid] = true; + } + const removed: MempoolTransactionExtended[] = []; + const added: MempoolTransactionExtended[] = []; + // don't prematurely remove txs included in a new block + if (blockHeight > blocks.getCurrentBlockHeight()) { + for (const txid of Object.keys(this.mempoolCandidates)) { + newCandidateTxMap[txid] = true; + } + } else { + for (const txid of Object.keys(this.mempoolCandidates)) { + if (!newCandidateTxMap[txid]) { + const tx = this.mempoolCache[txid]; + removed.push(tx); + } + } + } + + for (const txid of Object.keys(newCandidateTxMap)) { + if (!this.mempoolCandidates[txid]) { + const tx = this.mempoolCache[txid]; + added.push(tx); + } + } + + this.mempoolCandidates = newCandidateTxMap; + return { + txs: this.mempoolCandidates, + added, + removed + }; + } + } + private startTimer() { const state: any = { start: Date.now(), diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 2884f72eb..ac79d828a 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -2,7 +2,7 @@ import logger from '../logger'; import * as WebSocket from 'ws'; import { BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse, - OptimizedStatistic, ILoadingIndicators + OptimizedStatistic, ILoadingIndicators, GbtCandidates, } from '../mempool.interfaces'; import blocks from './blocks'; import memPool from './mempool'; @@ -32,6 +32,7 @@ interface AddressTransactions { confirmed: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], } +import bitcoinSecondClient from './bitcoin/bitcoin-second-client'; // valid 'want' subscriptions const wantable = [ @@ -436,21 +437,33 @@ class WebsocketHandler { } async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended }, mempoolSize: number, - newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[]): Promise { + newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[], accelerationDelta: string[], + candidates?: GbtCandidates): Promise { if (!this.wss) { throw new Error('WebSocket.Server is not set'); } this.printLogs(); + const transactionIds = (memPool.limitGBT && candidates) ? Object.keys(candidates?.txs || {}) : Object.keys(newMempool); + let added = newTransactions; + let removed = deletedTransactions; + console.log(`handleMempoolChange: ${newTransactions.length} new txs, ${deletedTransactions.length} removed, ${candidates?.added.length} candidate added, ${candidates?.removed.length} candidate removed`); + console.log(`mempool size ${Object.keys(newMempool).length}, candidates: ${transactionIds.length}`); + if (memPool.limitGBT) { + console.log('GBT on limited mempool...'); + added = candidates?.added || []; + removed = candidates?.removed || []; + } + if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { if (config.MEMPOOL.RUST_GBT) { - await mempoolBlocks.$rustUpdateBlockTemplates(newMempool, mempoolSize, newTransactions, deletedTransactions, config.MEMPOOL_SERVICES.ACCELERATIONS); + await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, newMempool, added, removed, candidates, config.MEMPOOL_SERVICES.ACCELERATIONS); } else { - await mempoolBlocks.$updateBlockTemplates(newMempool, newTransactions, deletedTransactions, accelerationDelta, true, config.MEMPOOL_SERVICES.ACCELERATIONS); + await mempoolBlocks.$updateBlockTemplates(transactionIds, newMempool, added, removed, candidates, accelerationDelta, true, config.MEMPOOL_SERVICES.ACCELERATIONS); } } else { - mempoolBlocks.updateMempoolBlocks(newMempool, true); + mempoolBlocks.updateMempoolBlocks(transactionIds, newMempool, candidates, true); } const mBlocks = mempoolBlocks.getMempoolBlocks(); @@ -739,6 +752,9 @@ class WebsocketHandler { await statistics.runStatistics(); const _memPool = memPool.getMempool(); + const candidateTxs = await memPool.getMempoolCandidates(); + let candidates: GbtCandidates | undefined = (memPool.limitGBT && candidateTxs) ? { txs: candidateTxs, added: [], removed: [] } : undefined; + let transactionIds: string[] = (memPool.limitGBT && candidates) ? Object.keys(_memPool) : Object.keys(candidates?.txs || {}); const isAccelerated = config.MEMPOOL_SERVICES.ACCELERATIONS && accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations())); @@ -759,19 +775,23 @@ class WebsocketHandler { auditMempool = deepClone(_memPool); if (config.MEMPOOL.ADVANCED_GBT_AUDIT) { if (config.MEMPOOL.RUST_GBT) { - projectedBlocks = await mempoolBlocks.$oneOffRustBlockTemplates(auditMempool, isAccelerated, block.extras.pool.id); + projectedBlocks = await mempoolBlocks.$oneOffRustBlockTemplates(transactionIds, auditMempool, candidates, isAccelerated, block.extras.pool.id); } else { - projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false, isAccelerated, block.extras.pool.id); + projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); } } else { - projectedBlocks = mempoolBlocks.updateMempoolBlocks(auditMempool, false); + projectedBlocks = mempoolBlocks.updateMempoolBlocks(transactionIds, auditMempool, candidates, false); } } else { if ((config.MEMPOOL_SERVICES.ACCELERATIONS)) { if (config.MEMPOOL.RUST_GBT) { - projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates(auditMempool, Object.keys(auditMempool).length, [], [], isAccelerated, block.extras.pool.id); + console.log(`handleNewBlock: ${transactions.length} mined txs, ${candidates?.added.length} candidate added, ${candidates?.removed.length} candidate removed`); + console.log(`mempool size ${Object.keys(auditMempool).length}, candidates: ${transactionIds.length}`); + let added = memPool.limitGBT ? (candidates?.added || []) : []; + let removed = memPool.limitGBT ? (candidates?.removed || []) : []; + projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, auditMempool, added, removed, candidates, isAccelerated, block.extras.pool.id); } else { - projectedBlocks = await mempoolBlocks.$makeBlockTemplates(auditMempool, false, isAccelerated, block.extras.pool.id); + projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); } } else { projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions(); @@ -838,14 +858,28 @@ class WebsocketHandler { confirmedTxids[txId] = true; } + if (memPool.limitGBT) { + const minFeeMempool = memPool.limitGBT ? await bitcoinSecondClient.getRawMemPool() : null; + const minFeeTip = memPool.limitGBT ? await bitcoinSecondClient.getBlockCount() : -1; + candidates = await memPool.getNextCandidates(minFeeMempool, minFeeTip); + transactionIds = Object.keys(candidates?.txs || {}); + } else { + candidates = undefined; + transactionIds = Object.keys(memPool.getMempool()); + } + if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { if (config.MEMPOOL.RUST_GBT) { - await mempoolBlocks.$rustUpdateBlockTemplates(_memPool, Object.keys(_memPool).length, [], transactions, true); + console.log(`handleNewBlock (after): ${transactions.length} mined txs, ${candidates?.added.length} candidate added, ${candidates?.removed.length} candidate removed`); + console.log(`mempool size ${Object.keys(_memPool).length}, candidates: ${transactionIds.length}`); + let added = memPool.limitGBT ? (candidates?.added || []) : []; + let removed = memPool.limitGBT ? (candidates?.removed || []) : transactions; + await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, _memPool, added, removed, candidates, true); } else { - await mempoolBlocks.$makeBlockTemplates(_memPool, true, config.MEMPOOL_SERVICES.ACCELERATIONS); + await mempoolBlocks.$makeBlockTemplates(transactionIds, _memPool, candidates, true, config.MEMPOOL_SERVICES.ACCELERATIONS); } } else { - mempoolBlocks.updateMempoolBlocks(_memPool, true); + mempoolBlocks.updateMempoolBlocks(transactionIds, _memPool, candidates, true); } const mBlocks = mempoolBlocks.getMempoolBlocks(); const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); diff --git a/backend/src/config.ts b/backend/src/config.ts index ee3645e58..6a202a392 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -35,6 +35,7 @@ interface IConfig { ADVANCED_GBT_AUDIT: boolean; ADVANCED_GBT_MEMPOOL: boolean; RUST_GBT: boolean; + LIMIT_GBT: boolean; CPFP_INDEXING: boolean; MAX_BLOCKS_BULK_QUERY: number; DISK_CACHE_BLOCK_INTERVAL: number; @@ -197,6 +198,7 @@ const defaults: IConfig = { 'ADVANCED_GBT_AUDIT': false, 'ADVANCED_GBT_MEMPOOL': false, 'RUST_GBT': false, + 'LIMIT_GBT': false, 'CPFP_INDEXING': false, 'MAX_BLOCKS_BULK_QUERY': 0, 'DISK_CACHE_BLOCK_INTERVAL': 6, diff --git a/backend/src/index.ts b/backend/src/index.ts index 1988c7c56..ae5113029 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -45,6 +45,7 @@ import { formatBytes, getBytesUnit } from './utils/format'; import redisCache from './api/redis-cache'; import accelerationApi from './api/services/acceleration'; import bitcoinCoreRoutes from './api/bitcoin/bitcoin-core.routes'; +import bitcoinSecondClient from './api/bitcoin/bitcoin-second-client'; class Server { private wss: WebSocket.Server | undefined; @@ -215,11 +216,13 @@ class Server { } } const newMempool = await bitcoinApi.$getRawMempool(); + const minFeeMempool = memPool.limitGBT ? await bitcoinSecondClient.getRawMemPool() : null; + const minFeeTip = memPool.limitGBT ? await bitcoinSecondClient.getBlockCount() : -1; const newAccelerations = await accelerationApi.$fetchAccelerations(); const numHandledBlocks = await blocks.$updateBlocks(); const pollRate = config.MEMPOOL.POLL_RATE_MS * (indexer.indexerIsRunning() ? 10 : 1); if (numHandledBlocks === 0) { - await memPool.$updateMempool(newMempool, newAccelerations, pollRate); + await memPool.$updateMempool(newMempool, newAccelerations, minFeeMempool, minFeeTip, pollRate); } indexer.$run(); if (config.FIAT_PRICE.ENABLED) { diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index d04858607..64c10d322 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -143,6 +143,12 @@ export interface CompactThreadTransaction { dirty?: boolean; } +export interface GbtCandidates { + txs: { [txid: string ]: boolean }, + added: MempoolTransactionExtended[]; + removed: MempoolTransactionExtended[]; +} + export interface ThreadTransaction { txid: string; fee: number; From 62653086e92424ddc62ac0795ab1fc58fad3ebe0 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 5 Jan 2024 22:25:07 +0000 Subject: [PATCH 07/28] Limit GBT - calculate purged tx cpfp on demand --- backend/src/api/bitcoin/bitcoin.routes.ts | 3 +- backend/src/api/cpfp.ts | 282 ++++++++++++++++++++++ backend/src/api/mempool-blocks.ts | 7 +- backend/src/api/mempool.ts | 17 +- backend/src/api/websocket-handler.ts | 4 + backend/src/mempool.interfaces.ts | 4 + 6 files changed, 303 insertions(+), 14 deletions(-) create mode 100644 backend/src/api/cpfp.ts diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 93b614006..c65363315 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -19,6 +19,7 @@ import bitcoinClient from './bitcoin-client'; import difficultyAdjustment from '../difficulty-adjustment'; import transactionRepository from '../../repositories/TransactionRepository'; import rbfCache from '../rbf-cache'; +import { calculateCpfp } from '../cpfp'; class BitcoinRoutes { public initRoutes(app: Application) { @@ -217,7 +218,7 @@ class BitcoinRoutes { return; } - const cpfpInfo = Common.setRelativesAndGetCpfpInfo(tx, mempool.getMempool()); + const cpfpInfo = calculateCpfp(tx, mempool.getMempool()); res.json(cpfpInfo); return; diff --git a/backend/src/api/cpfp.ts b/backend/src/api/cpfp.ts new file mode 100644 index 000000000..cf54771bb --- /dev/null +++ b/backend/src/api/cpfp.ts @@ -0,0 +1,282 @@ +import { CpfpInfo, MempoolTransactionExtended } from '../mempool.interfaces'; + +const CPFP_UPDATE_INTERVAL = 60_000; // update CPFP info at most once per 60s per transaction + +interface GraphTx extends MempoolTransactionExtended { + depends: string[]; + spentby: string[]; + ancestorMap: Map; + fees: { + base: number; + ancestor: number; + }; + ancestorcount: number; + ancestorsize: number; + ancestorRate: number; + individualRate: number; + score: number; +} + +/** + * Takes a mempool transaction and a copy of the current mempool, and calculates the CPFP data for + * that transaction (and all others in the same cluster) + */ +export function calculateCpfp(tx: MempoolTransactionExtended, mempool: { [txid: string]: MempoolTransactionExtended }): CpfpInfo { + if (tx.cpfpUpdated && Date.now() < (tx.cpfpUpdated + CPFP_UPDATE_INTERVAL)) { + tx.cpfpDirty = false; + return { + ancestors: tx.ancestors || [], + bestDescendant: tx.bestDescendant || null, + descendants: tx.descendants || [], + effectiveFeePerVsize: tx.effectiveFeePerVsize || tx.adjustedFeePerVsize || tx.feePerVsize, + sigops: tx.sigops, + adjustedVsize: tx.adjustedVsize, + acceleration: tx.acceleration + }; + } + + const ancestorMap = new Map(); + const graphTx = mempoolToGraphTx(tx); + ancestorMap.set(tx.txid, graphTx); + + const allRelatives = expandRelativesGraph(mempool, ancestorMap); + const relativesMap = initializeRelatives(allRelatives); + const cluster = calculateCpfpCluster(tx.txid, relativesMap); + + let totalVsize = 0; + let totalFee = 0; + for (const tx of cluster.values()) { + totalVsize += tx.adjustedVsize; + totalFee += tx.fee; + } + const effectiveFeePerVsize = totalFee / totalVsize; + for (const tx of cluster.values()) { + mempool[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize; + mempool[tx.txid].ancestors = Array.from(tx.ancestorMap.values()).map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fee })); + mempool[tx.txid].descendants = Array.from(cluster.values()).filter(entry => entry.txid !== tx.txid && !tx.ancestorMap.has(entry.txid)).map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fee })); + mempool[tx.txid].bestDescendant = null; + mempool[tx.txid].cpfpChecked = true; + mempool[tx.txid].cpfpDirty = true; + mempool[tx.txid].cpfpUpdated = Date.now(); + } + + tx = mempool[tx.txid]; + + return { + ancestors: tx.ancestors || [], + bestDescendant: tx.bestDescendant || null, + descendants: tx.descendants || [], + effectiveFeePerVsize: tx.effectiveFeePerVsize || tx.adjustedFeePerVsize || tx.feePerVsize, + sigops: tx.sigops, + adjustedVsize: tx.adjustedVsize, + acceleration: tx.acceleration + }; +} + +function mempoolToGraphTx(tx: MempoolTransactionExtended): GraphTx { + return { + ...tx, + depends: [], + spentby: [], + ancestorMap: new Map(), + fees: { + base: tx.fee, + ancestor: tx.fee, + }, + ancestorcount: 1, + ancestorsize: tx.adjustedVsize, + ancestorRate: 0, + individualRate: 0, + score: 0, + }; +} + +/** + * Takes a map of transaction ancestors, and expands it into a full graph of up to 50 in-mempool relatives + */ +function expandRelativesGraph(mempool: { [txid: string]: MempoolTransactionExtended }, ancestors: Map): Map { + const relatives: Map = new Map(); + const stack: GraphTx[] = Array.from(ancestors.values()); + while (stack.length > 0) { + if (relatives.size > 50) { + return relatives; + } + + const nextTx = stack.pop(); + if (!nextTx) { + continue; + } + relatives.set(nextTx.txid, nextTx); + + for (const relativeTxid of [...nextTx.depends, ...nextTx.spentby]) { + if (relatives.has(relativeTxid)) { + // already processed this tx + continue; + } + let mempoolTx = ancestors.get(relativeTxid); + if (!mempoolTx && mempool[relativeTxid]) { + mempoolTx = mempoolToGraphTx(mempool[relativeTxid]); + } + if (mempoolTx) { + stack.push(mempoolTx); + } + } + } + + return relatives; +} + + /** + * Efficiently sets a Map of in-mempool ancestors for each member of an expanded relative graph + * by running setAncestors on each leaf, and caching intermediate results. + * then initializes ancestor data for each transaction + * + * @param all + */ + function initializeRelatives(mempoolTxs: Map): Map { + const visited: Map> = new Map(); + const leaves: GraphTx[] = Array.from(mempoolTxs.values()).filter(entry => entry.spentby.length === 0); + for (const leaf of leaves) { + setAncestors(leaf, mempoolTxs, visited); + } + mempoolTxs.forEach(entry => { + entry.ancestorMap?.forEach(ancestor => { + entry.ancestorcount++; + entry.ancestorsize += ancestor.adjustedVsize; + entry.fees.ancestor += ancestor.fees.base; + }); + setAncestorScores(entry); + }); + return mempoolTxs; +} + +/** + * Given a root transaction and a list of in-mempool ancestors, + * Calculate the CPFP cluster + * + * @param tx + * @param ancestors + */ +function calculateCpfpCluster(txid: string, graph: Map): Map { + const tx = graph.get(txid); + if (!tx) { + return new Map([]); + } + + // Initialize individual & ancestor fee rates + graph.forEach(entry => setAncestorScores(entry)); + + // Sort by descending ancestor score + let sortedRelatives = Array.from(graph.values()).sort(mempoolComparator); + + // Iterate until we reach a cluster that includes our target tx + let maxIterations = 50; + let best = sortedRelatives.shift(); + let bestCluster = new Map(best?.ancestorMap?.entries() || []); + while (sortedRelatives.length && best && (best.txid !== tx.txid && !best.ancestorMap.has(tx.txid)) && maxIterations > 0) { + maxIterations--; + if (bestCluster && bestCluster.has(tx.txid)) { + break; + } else { + // Remove this cluster (it doesn't include our target tx) + // and update scores, ancestor totals and dependencies for the survivors + removeAncestors(bestCluster, graph); + + // re-sort + sortedRelatives = Array.from(graph.values()).sort(mempoolComparator); + + // Grab the next highest scoring entry + best = sortedRelatives.shift(); + if (best) { + bestCluster = new Map(best?.ancestorMap?.entries() || []); + bestCluster.set(best?.txid, best); + } + } + } + + return bestCluster; +} + + /** + * Remove a cluster of transactions from an in-mempool dependency graph + * and update the survivors' scores and ancestors + * + * @param cluster + * @param ancestors + */ + function removeAncestors(cluster: Map, all: Map): void { + // remove + cluster.forEach(tx => { + all.delete(tx.txid); + }); + + // update survivors + all.forEach(tx => { + cluster.forEach(remove => { + if (tx.ancestorMap?.has(remove.txid)) { + // remove as dependency + tx.ancestorMap.delete(remove.txid); + tx.depends = tx.depends.filter(parent => parent !== remove.txid); + // update ancestor sizes and fees + tx.ancestorsize -= remove.adjustedVsize; + tx.fees.ancestor -= remove.fees.base; + } + }); + // recalculate fee rates + setAncestorScores(tx); + }); +} + +/** + * Recursively traverses an in-mempool dependency graph, and sets a Map of in-mempool ancestors + * for each transaction. + * + * @param tx + * @param all + */ +function setAncestors(tx: GraphTx, all: Map, visited: Map>, depth: number = 0): Map { + // sanity check for infinite recursion / too many ancestors (should never happen) + if (depth > 50) { + return tx.ancestorMap; + } + + // initialize the ancestor map for this tx + tx.ancestorMap = new Map(); + tx.depends.forEach(parentId => { + const parent = all.get(parentId); + if (parent) { + // add the parent + tx.ancestorMap?.set(parentId, parent); + // check for a cached copy of this parent's ancestors + let ancestors = visited.get(parent.txid); + if (!ancestors) { + // recursively fetch the parent's ancestors + ancestors = setAncestors(parent, all, visited, depth + 1); + } + // and add to this tx's map + ancestors.forEach((ancestor, ancestorId) => { + tx.ancestorMap?.set(ancestorId, ancestor); + }); + } + }); + visited.set(tx.txid, tx.ancestorMap); + + return tx.ancestorMap; +} + +/** + * Take a mempool transaction, and set the fee rates and ancestor score + * + * @param tx + */ +function setAncestorScores(tx: GraphTx): GraphTx { + tx.individualRate = (tx.fees.base * 100_000_000) / tx.adjustedVsize; + tx.ancestorRate = (tx.fees.ancestor * 100_000_000) / tx.ancestorsize; + tx.score = Math.min(tx.individualRate, tx.ancestorRate); + return tx; +} + +// Sort by descending score +function mempoolComparator(a: GraphTx, b: GraphTx): number { + return b.score - a.score; +} \ No newline at end of file diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 09c5d8e79..af071321e 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -335,7 +335,6 @@ class MempoolBlocks { } private resetRustGbt(): void { - console.log('reset rust gbt'); this.rustInitialized = false; this.rustGbtGenerator = new GbtGenerator(); } @@ -418,8 +417,6 @@ class MempoolBlocks { } const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => (uid !== null && uid !== undefined)) as number[]; - console.log(`removing ${removed.length} (${removedUids.length} with uids)`); - const accelerations = useAccelerations ? mempool.getAccelerations() : {}; const acceleratedList = accelerationPool ? Object.values(accelerations).filter(acc => newMempool[acc.txid] && acc.pools.includes(accelerationPool)) : Object.values(accelerations).filter(acc => newMempool[acc.txid]); const convertedAccelerations = acceleratedList.map(acc => { @@ -429,8 +426,6 @@ class MempoolBlocks { }; }); - console.log(`${acceleratedList.length} accelerations`); - // run the block construction algorithm in a separate thread, and wait for a result try { const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( @@ -458,7 +453,7 @@ class MempoolBlocks { } } - private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], candidates: GbtCandidates | undefined, accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] { + private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], candidates: GbtCandidates | undefined, accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] { for (const [txid, rate] of rates) { if (txid in mempool) { mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize); diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 540e0828b..b08848b26 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -18,7 +18,6 @@ class Mempool { private mempoolCacheDelta: number = -1; private mempoolCache: { [txId: string]: MempoolTransactionExtended } = {}; private mempoolCandidates: { [txid: string ]: boolean } = {}; - private minFeeMempool: { [txId: string]: boolean } = {}; private spendMap = new Map(); private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0, maxmempool: 300000000, mempoolminfee: Common.isLiquid() ? 0.00000100 : 0.00001000, minrelaytxfee: Common.isLiquid() ? 0.00000100 : 0.00001000 }; @@ -449,12 +448,10 @@ class Mempool { public async getNextCandidates(minFeeTransactions: string[], blockHeight: number): Promise { if (this.limitGBT) { const newCandidateTxMap = {}; - this.minFeeMempool = {}; for (const txid of minFeeTransactions) { if (this.mempoolCache[txid]) { newCandidateTxMap[txid] = true; } - this.minFeeMempool[txid] = true; } const removed: MempoolTransactionExtended[] = []; const added: MempoolTransactionExtended[] = []; @@ -466,16 +463,22 @@ class Mempool { } else { for (const txid of Object.keys(this.mempoolCandidates)) { if (!newCandidateTxMap[txid]) { - const tx = this.mempoolCache[txid]; - removed.push(tx); + removed.push(this.mempoolCache[txid]); + if (this.mempoolCache[txid]) { + this.mempoolCache[txid].effectiveFeePerVsize = this.mempoolCache[txid].adjustedFeePerVsize; + this.mempoolCache[txid].ancestors = []; + this.mempoolCache[txid].descendants = []; + this.mempoolCache[txid].bestDescendant = null; + this.mempoolCache[txid].cpfpChecked = false; + this.mempoolCache[txid].cpfpUpdated = undefined; + } } } } for (const txid of Object.keys(newCandidateTxMap)) { if (!this.mempoolCandidates[txid]) { - const tx = this.mempoolCache[txid]; - added.push(tx); + added.push(this.mempoolCache[txid]); } } diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index ac79d828a..f34f7e764 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -33,6 +33,7 @@ interface AddressTransactions { removed: MempoolTransactionExtended[], } import bitcoinSecondClient from './bitcoin/bitcoin-second-client'; +import { calculateCpfp } from './cpfp'; // valid 'want' subscriptions const wantable = [ @@ -702,6 +703,9 @@ class WebsocketHandler { accelerated: mempoolTx.acceleration || undefined, } }; + if (!mempoolTx.cpfpChecked) { + calculateCpfp(mempoolTx, newMempool); + } if (mempoolTx.cpfpDirty) { positionData['cpfp'] = { ancestors: mempoolTx.ancestors, diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 64c10d322..a12351fe3 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -107,6 +107,7 @@ export interface MempoolTransactionExtended extends TransactionExtended { inputs?: number[]; lastBoosted?: number; cpfpDirty?: boolean; + cpfpUpdated?: number; } export interface AuditTransaction { @@ -187,6 +188,9 @@ export interface CpfpInfo { bestDescendant?: BestDescendant | null; descendants?: Ancestor[]; effectiveFeePerVsize?: number; + sigops?: number; + adjustedVsize?: number, + acceleration?: boolean, } export interface TransactionStripped { From b3e07e0c22cbcdf642f20e153a017282a1a051e0 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 6 Jan 2024 18:26:03 +0000 Subject: [PATCH 08/28] Retire "getSimonTemplate" GBT option --- backend/mempool-config.sample.json | 2 - .../__fixtures__/mempool-config.template.json | 2 - backend/src/__tests__/config.test.ts | 2 - backend/src/api/common.ts | 63 --------- backend/src/api/mempool-blocks.ts | 123 ------------------ backend/src/api/websocket-handler.ts | 69 +++------- backend/src/config.ts | 4 - docker/README.md | 6 - docker/backend/mempool-config.json | 2 - docker/backend/start.sh | 4 - production/mempool-config.mainnet.json | 2 - production/mempool-config.signet.json | 2 - production/mempool-config.testnet.json | 2 - 13 files changed, 19 insertions(+), 264 deletions(-) diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index 781973055..c98a070f1 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -28,8 +28,6 @@ "POOLS_JSON_URL": "https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json", "POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master", "AUDIT": false, - "ADVANCED_GBT_AUDIT": false, - "ADVANCED_GBT_MEMPOOL": false, "RUST_GBT": false, "CPFP_INDEXING": false, "DISK_CACHE_BLOCK_INTERVAL": 6, diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 5cbff22a3..7d571c169 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -28,8 +28,6 @@ "POOLS_JSON_TREE_URL": "__MEMPOOL_POOLS_JSON_TREE_URL__", "POOLS_JSON_URL": "__MEMPOOL_POOLS_JSON_URL__", "AUDIT": true, - "ADVANCED_GBT_AUDIT": true, - "ADVANCED_GBT_MEMPOOL": true, "RUST_GBT": false, "CPFP_INDEXING": true, "MAX_BLOCKS_BULK_QUERY": 999, diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index e261e2adc..84c2e763b 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -41,8 +41,6 @@ describe('Mempool Backend Config', () => { POOLS_JSON_TREE_URL: 'https://api.github.com/repos/mempool/mining-pools/git/trees/master', POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json', AUDIT: false, - ADVANCED_GBT_AUDIT: false, - ADVANCED_GBT_MEMPOOL: false, RUST_GBT: false, CPFP_INDEXING: false, MAX_BLOCKS_BULK_QUERY: 0, diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 3021d947a..d8cf0d73f 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -574,69 +574,6 @@ export class Common { } } - static setRelativesAndGetCpfpInfo(tx: MempoolTransactionExtended, memPool: { [txid: string]: MempoolTransactionExtended }): CpfpInfo { - const parents = this.findAllParents(tx, memPool); - const lowerFeeParents = parents.filter((parent) => parent.adjustedFeePerVsize < tx.effectiveFeePerVsize); - - let totalWeight = (tx.adjustedVsize * 4) + lowerFeeParents.reduce((prev, val) => prev + (val.adjustedVsize * 4), 0); - let totalFees = tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0); - - tx.ancestors = parents - .map((t) => { - return { - txid: t.txid, - weight: (t.adjustedVsize * 4), - fee: t.fee, - }; - }); - - // Add high (high fee) decendant weight and fees - if (tx.bestDescendant) { - totalWeight += tx.bestDescendant.weight; - totalFees += tx.bestDescendant.fee; - } - - tx.effectiveFeePerVsize = Math.max(0, totalFees / (totalWeight / 4)); - tx.cpfpChecked = true; - - return { - ancestors: tx.ancestors, - bestDescendant: tx.bestDescendant || null, - }; - } - - - private static findAllParents(tx: MempoolTransactionExtended, memPool: { [txid: string]: MempoolTransactionExtended }): MempoolTransactionExtended[] { - let parents: MempoolTransactionExtended[] = []; - tx.vin.forEach((parent) => { - if (parents.find((p) => p.txid === parent.txid)) { - return; - } - - const parentTx = memPool[parent.txid]; - if (parentTx) { - if (tx.bestDescendant && tx.bestDescendant.fee / (tx.bestDescendant.weight / 4) > parentTx.adjustedFeePerVsize) { - if (parentTx.bestDescendant && parentTx.bestDescendant.fee < tx.fee + tx.bestDescendant.fee) { - parentTx.bestDescendant = { - weight: (tx.adjustedVsize * 4) + tx.bestDescendant.weight, - fee: tx.fee + tx.bestDescendant.fee, - txid: tx.txid, - }; - } - } else if (tx.adjustedFeePerVsize > parentTx.adjustedFeePerVsize) { - parentTx.bestDescendant = { - weight: (tx.adjustedVsize * 4), - fee: tx.fee, - txid: tx.txid - }; - } - parents.push(parentTx); - parents = parents.concat(this.findAllParents(parentTx, memPool)); - } - }); - return parents; - } - // calculates the ratio of matched transactions to projected transactions by weight static getSimilarity(projectedBlock: MempoolBlockWithTransactions, transactions: TransactionExtended[]): number { let matchedWeight = 0; diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index af071321e..fd9918f71 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -40,129 +40,6 @@ class MempoolBlocks { return this.mempoolBlockDeltas; } - public updateMempoolBlocks(transactions: string[], memPool: { [txid: string]: MempoolTransactionExtended }, candidates: GbtCandidates | undefined, saveResults: boolean = false): MempoolBlockWithTransactions[] { - const latestMempool = memPool; - const memPoolArray: MempoolTransactionExtended[] = transactions.map(txid => memPool[txid]); - const start = new Date().getTime(); - - // Clear bestDescendants & ancestors - memPoolArray.forEach((tx) => { - tx.bestDescendant = null; - tx.ancestors = []; - tx.cpfpChecked = false; - if (!tx.effectiveFeePerVsize) { - tx.effectiveFeePerVsize = tx.adjustedFeePerVsize; - } - }); - - // First sort - memPoolArray.sort((a, b) => { - if (a.adjustedFeePerVsize === b.adjustedFeePerVsize) { - // tie-break by lexicographic txid order for stability - return a.txid < b.txid ? -1 : 1; - } else { - return b.adjustedFeePerVsize - a.adjustedFeePerVsize; - } - }); - - // Loop through and traverse all ancestors and sum up all the sizes + fees - // Pass down size + fee to all unconfirmed children - let sizes = 0; - memPoolArray.forEach((tx) => { - sizes += tx.weight; - if (sizes > 4000000 * 8) { - return; - } - Common.setRelativesAndGetCpfpInfo(tx, memPool); - }); - - // Final sort, by effective fee - memPoolArray.sort((a, b) => { - if (a.effectiveFeePerVsize === b.effectiveFeePerVsize) { - // tie-break by lexicographic txid order for stability - return a.txid < b.txid ? -1 : 1; - } else { - return b.effectiveFeePerVsize - a.effectiveFeePerVsize; - } - }); - - const end = new Date().getTime(); - const time = end - start; - logger.debug('Mempool blocks calculated in ' + time / 1000 + ' seconds'); - - const blocks = this.calculateMempoolBlocks(memPoolArray); - - if (saveResults) { - const deltas = this.calculateMempoolDeltas(this.mempoolBlocks, blocks); - this.mempoolBlocks = blocks; - this.mempoolBlockDeltas = deltas; - } - - return blocks; - } - - private calculateMempoolBlocks(transactionsSorted: MempoolTransactionExtended[]): MempoolBlockWithTransactions[] { - const mempoolBlocks: MempoolBlockWithTransactions[] = []; - let feeStatsCalculator: OnlineFeeStatsCalculator = new OnlineFeeStatsCalculator(config.MEMPOOL.BLOCK_WEIGHT_UNITS); - let onlineStats = false; - let blockSize = 0; - let blockWeight = 0; - let blockVsize = 0; - let blockFees = 0; - const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2; - let transactionIds: string[] = []; - let transactions: MempoolTransactionExtended[] = []; - transactionsSorted.forEach((tx, index) => { - if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS - || mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) { - tx.position = { - block: mempoolBlocks.length, - vsize: blockVsize + (tx.vsize / 2), - }; - blockWeight += tx.weight; - blockVsize += tx.vsize; - blockSize += tx.size; - blockFees += tx.fee; - if (blockVsize <= sizeLimit) { - transactions.push(tx); - } - transactionIds.push(tx.txid); - if (onlineStats) { - feeStatsCalculator.processNext(tx); - } - } else { - mempoolBlocks.push(this.dataToMempoolBlocks(transactionIds, transactions, blockSize, blockWeight, blockFees)); - blockVsize = 0; - tx.position = { - block: mempoolBlocks.length, - vsize: blockVsize + (tx.vsize / 2), - }; - - if (mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) { - const stackWeight = transactionsSorted.slice(index).reduce((total, tx) => total + (tx.weight || 0), 0); - if (stackWeight > config.MEMPOOL.BLOCK_WEIGHT_UNITS) { - onlineStats = true; - feeStatsCalculator = new OnlineFeeStatsCalculator(stackWeight, 0.5, [10, 20, 30, 40, 50, 60, 70, 80, 90]); - feeStatsCalculator.processNext(tx); - } - } - - blockVsize += tx.vsize; - blockWeight = tx.weight; - blockSize = tx.size; - blockFees = tx.fee; - transactionIds = [tx.txid]; - transactions = [tx]; - } - }); - if (transactions.length) { - const feeStats = onlineStats ? feeStatsCalculator.getRawFeeStats() : undefined; - mempoolBlocks.push(this.dataToMempoolBlocks(transactionIds, transactions, blockSize, blockWeight, blockFees, feeStats)); - } - - return mempoolBlocks; - } - private calculateMempoolDeltas(prevBlocks: MempoolBlockWithTransactions[], mempoolBlocks: MempoolBlockWithTransactions[]): MempoolBlockDelta[] { const mempoolBlockDeltas: MempoolBlockDelta[] = []; for (let i = 0; i < Math.max(mempoolBlocks.length, prevBlocks.length); i++) { diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index f34f7e764..d42fc11f0 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -18,7 +18,6 @@ import feeApi from './fee-api'; import BlocksAuditsRepository from '../repositories/BlocksAuditsRepository'; import BlocksSummariesRepository from '../repositories/BlocksSummariesRepository'; import Audit from './audit'; -import { deepClone } from '../utils/clone'; import priceUpdater from '../tasks/price-updater'; import { ApiPrice } from '../repositories/PricesRepository'; import accelerationApi from './services/acceleration'; @@ -449,22 +448,15 @@ class WebsocketHandler { const transactionIds = (memPool.limitGBT && candidates) ? Object.keys(candidates?.txs || {}) : Object.keys(newMempool); let added = newTransactions; let removed = deletedTransactions; - console.log(`handleMempoolChange: ${newTransactions.length} new txs, ${deletedTransactions.length} removed, ${candidates?.added.length} candidate added, ${candidates?.removed.length} candidate removed`); - console.log(`mempool size ${Object.keys(newMempool).length}, candidates: ${transactionIds.length}`); if (memPool.limitGBT) { - console.log('GBT on limited mempool...'); added = candidates?.added || []; removed = candidates?.removed || []; } - if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { - if (config.MEMPOOL.RUST_GBT) { - await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, newMempool, added, removed, candidates, config.MEMPOOL_SERVICES.ACCELERATIONS); - } else { - await mempoolBlocks.$updateBlockTemplates(transactionIds, newMempool, added, removed, candidates, accelerationDelta, true, config.MEMPOOL_SERVICES.ACCELERATIONS); - } + if (config.MEMPOOL.RUST_GBT) { + await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, newMempool, added, removed, candidates, config.MEMPOOL_SERVICES.ACCELERATIONS); } else { - mempoolBlocks.updateMempoolBlocks(transactionIds, newMempool, candidates, true); + await mempoolBlocks.$updateBlockTemplates(transactionIds, newMempool, added, removed, candidates, accelerationDelta, true, config.MEMPOOL_SERVICES.ACCELERATIONS); } const mBlocks = mempoolBlocks.getMempoolBlocks(); @@ -760,8 +752,6 @@ class WebsocketHandler { let candidates: GbtCandidates | undefined = (memPool.limitGBT && candidateTxs) ? { txs: candidateTxs, added: [], removed: [] } : undefined; let transactionIds: string[] = (memPool.limitGBT && candidates) ? Object.keys(_memPool) : Object.keys(candidates?.txs || {}); - const isAccelerated = config.MEMPOOL_SERVICES.ACCELERATIONS && accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations())); - const accelerations = Object.values(mempool.getAccelerations()); await accelerationRepository.$indexAccelerationsForBlock(block, accelerations, transactions); @@ -771,35 +761,19 @@ class WebsocketHandler { if (config.MEMPOOL.AUDIT && memPool.isInSync()) { let projectedBlocks; - let auditMempool = _memPool; - // template calculation functions have mempool side effects, so calculate audits using - // a cloned copy of the mempool if we're running a different algorithm for mempool updates - const separateAudit = config.MEMPOOL.ADVANCED_GBT_AUDIT !== config.MEMPOOL.ADVANCED_GBT_MEMPOOL; - if (separateAudit) { - auditMempool = deepClone(_memPool); - if (config.MEMPOOL.ADVANCED_GBT_AUDIT) { - if (config.MEMPOOL.RUST_GBT) { - projectedBlocks = await mempoolBlocks.$oneOffRustBlockTemplates(transactionIds, auditMempool, candidates, isAccelerated, block.extras.pool.id); - } else { - projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); - } + const auditMempool = _memPool; + const isAccelerated = config.MEMPOOL_SERVICES.ACCELERATIONS && accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations())); + + if ((config.MEMPOOL_SERVICES.ACCELERATIONS)) { + if (config.MEMPOOL.RUST_GBT) { + const added = memPool.limitGBT ? (candidates?.added || []) : []; + const removed = memPool.limitGBT ? (candidates?.removed || []) : []; + projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, auditMempool, added, removed, candidates, isAccelerated, block.extras.pool.id); } else { - projectedBlocks = mempoolBlocks.updateMempoolBlocks(transactionIds, auditMempool, candidates, false); + projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); } } else { - if ((config.MEMPOOL_SERVICES.ACCELERATIONS)) { - if (config.MEMPOOL.RUST_GBT) { - console.log(`handleNewBlock: ${transactions.length} mined txs, ${candidates?.added.length} candidate added, ${candidates?.removed.length} candidate removed`); - console.log(`mempool size ${Object.keys(auditMempool).length}, candidates: ${transactionIds.length}`); - let added = memPool.limitGBT ? (candidates?.added || []) : []; - let removed = memPool.limitGBT ? (candidates?.removed || []) : []; - projectedBlocks = await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, auditMempool, added, removed, candidates, isAccelerated, block.extras.pool.id); - } else { - projectedBlocks = await mempoolBlocks.$makeBlockTemplates(transactionIds, auditMempool, candidates, false, isAccelerated, block.extras.pool.id); - } - } else { - projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions(); - } + projectedBlocks = mempoolBlocks.getMempoolBlocksWithTransactions(); } if (Common.indexingEnabled()) { @@ -872,18 +846,13 @@ class WebsocketHandler { transactionIds = Object.keys(memPool.getMempool()); } - if (config.MEMPOOL.ADVANCED_GBT_MEMPOOL) { - if (config.MEMPOOL.RUST_GBT) { - console.log(`handleNewBlock (after): ${transactions.length} mined txs, ${candidates?.added.length} candidate added, ${candidates?.removed.length} candidate removed`); - console.log(`mempool size ${Object.keys(_memPool).length}, candidates: ${transactionIds.length}`); - let added = memPool.limitGBT ? (candidates?.added || []) : []; - let removed = memPool.limitGBT ? (candidates?.removed || []) : transactions; - await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, _memPool, added, removed, candidates, true); - } else { - await mempoolBlocks.$makeBlockTemplates(transactionIds, _memPool, candidates, true, config.MEMPOOL_SERVICES.ACCELERATIONS); - } + + if (config.MEMPOOL.RUST_GBT) { + const added = memPool.limitGBT ? (candidates?.added || []) : []; + const removed = memPool.limitGBT ? (candidates?.removed || []) : transactions; + await mempoolBlocks.$rustUpdateBlockTemplates(transactionIds, _memPool, added, removed, candidates, true); } else { - mempoolBlocks.updateMempoolBlocks(transactionIds, _memPool, candidates, true); + await mempoolBlocks.$makeBlockTemplates(transactionIds, _memPool, candidates, true, config.MEMPOOL_SERVICES.ACCELERATIONS); } const mBlocks = mempoolBlocks.getMempoolBlocks(); const mBlockDeltas = mempoolBlocks.getMempoolBlockDeltas(); diff --git a/backend/src/config.ts b/backend/src/config.ts index 6a202a392..41dd0aadb 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -32,8 +32,6 @@ interface IConfig { POOLS_JSON_URL: string, POOLS_JSON_TREE_URL: string, AUDIT: boolean; - ADVANCED_GBT_AUDIT: boolean; - ADVANCED_GBT_MEMPOOL: boolean; RUST_GBT: boolean; LIMIT_GBT: boolean; CPFP_INDEXING: boolean; @@ -195,8 +193,6 @@ const defaults: IConfig = { 'POOLS_JSON_URL': 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json', 'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master', 'AUDIT': false, - 'ADVANCED_GBT_AUDIT': false, - 'ADVANCED_GBT_MEMPOOL': false, 'RUST_GBT': false, 'LIMIT_GBT': false, 'CPFP_INDEXING': false, diff --git a/docker/README.md b/docker/README.md index 444324af8..c8ac91d38 100644 --- a/docker/README.md +++ b/docker/README.md @@ -109,8 +109,6 @@ Below we list all settings from `mempool-config.json` and the corresponding over "AUTOMATIC_BLOCK_REINDEXING": false, "POOLS_JSON_URL": "https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json", "POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master", - "ADVANCED_GBT_AUDIT": false, - "ADVANCED_GBT_MEMPOOL": false, "CPFP_INDEXING": false, "MAX_BLOCKS_BULK_QUERY": 0, "DISK_CACHE_BLOCK_INTERVAL": 6, @@ -142,8 +140,6 @@ Corresponding `docker-compose.yml` overrides: MEMPOOL_AUTOMATIC_BLOCK_REINDEXING: "" MEMPOOL_POOLS_JSON_URL: "" MEMPOOL_POOLS_JSON_TREE_URL: "" - MEMPOOL_ADVANCED_GBT_AUDIT: "" - MEMPOOL_ADVANCED_GBT_MEMPOOL: "" MEMPOOL_CPFP_INDEXING: "" MEMPOOL_MAX_BLOCKS_BULK_QUERY: "" MEMPOOL_DISK_CACHE_BLOCK_INTERVAL: "" @@ -151,8 +147,6 @@ Corresponding `docker-compose.yml` overrides: ... ``` -`ADVANCED_GBT_AUDIT` AND `ADVANCED_GBT_MEMPOOL` enable a more accurate (but slower) block prediction algorithm for the block audit feature and the projected mempool-blocks respectively. - `CPFP_INDEXING` enables indexing CPFP (Child Pays For Parent) information for the last `INDEXING_BLOCKS_AMOUNT` blocks.
diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index f8935706c..0403ba42e 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -26,8 +26,6 @@ "GOGGLES_INDEXING": __MEMPOOL_GOGGLES_INDEXING__, "AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__, "AUDIT": __MEMPOOL_AUDIT__, - "ADVANCED_GBT_AUDIT": __MEMPOOL_ADVANCED_GBT_AUDIT__, - "ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__, "RUST_GBT": __MEMPOOL_RUST_GBT__, "CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__, "MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 50ecc17ab..39870ab0c 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -29,8 +29,6 @@ __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__=${MEMPOOL_AUTOMATIC_BLOCK_REINDEXING:=fal __MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json} __MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master} __MEMPOOL_AUDIT__=${MEMPOOL_AUDIT:=false} -__MEMPOOL_ADVANCED_GBT_AUDIT__=${MEMPOOL_ADVANCED_GBT_AUDIT:=false} -__MEMPOOL_ADVANCED_GBT_MEMPOOL__=${MEMPOOL_ADVANCED_GBT_MEMPOOL:=false} __MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=false} __MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false} __MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0} @@ -189,9 +187,7 @@ sed -i "s!__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__!${__MEMPOOL_AUTOMATIC_BLOCK_REI sed -i "s!__MEMPOOL_POOLS_JSON_URL__!${__MEMPOOL_POOLS_JSON_URL__}!g" mempool-config.json sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}!g" mempool-config.json sed -i "s!__MEMPOOL_AUDIT__!${__MEMPOOL_AUDIT__}!g" mempool-config.json -sed -i "s!__MEMPOOL_ADVANCED_GBT_MEMPOOL__!${__MEMPOOL_ADVANCED_GBT_MEMPOOL__}!g" mempool-config.json sed -i "s!__MEMPOOL_RUST_GBT__!${__MEMPOOL_RUST_GBT__}!g" mempool-config.json -sed -i "s!__MEMPOOL_ADVANCED_GBT_AUDIT__!${__MEMPOOL_ADVANCED_GBT_AUDIT__}!g" mempool-config.json sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-config.json sed -i "s!__MEMPOOL_MAX_BLOCKS_BULK_QUERY__!${__MEMPOOL_MAX_BLOCKS_BULK_QUERY__}!g" mempool-config.json sed -i "s!__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__!${__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__}!g" mempool-config.json diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index 5038d9bfb..385f8cbdc 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -15,8 +15,6 @@ "GOGGLES_INDEXING": true, "AUDIT": true, "CPFP_INDEXING": true, - "ADVANCED_GBT_AUDIT": true, - "ADVANCED_GBT_MEMPOOL": true, "RUST_GBT": true, "USE_SECOND_NODE_FOR_MINFEE": true, "DISK_CACHE_BLOCK_INTERVAL": 1, diff --git a/production/mempool-config.signet.json b/production/mempool-config.signet.json index 0a711d16f..6ebd9e8b3 100644 --- a/production/mempool-config.signet.json +++ b/production/mempool-config.signet.json @@ -9,8 +9,6 @@ "API_URL_PREFIX": "/api/v1/", "INDEXING_BLOCKS_AMOUNT": -1, "AUDIT": true, - "ADVANCED_GBT_AUDIT": true, - "ADVANCED_GBT_MEMPOOL": true, "RUST_GBT": true, "POLL_RATE_MS": 1000, "DISK_CACHE_BLOCK_INTERVAL": 1, diff --git a/production/mempool-config.testnet.json b/production/mempool-config.testnet.json index adc93c0e9..2394ac467 100644 --- a/production/mempool-config.testnet.json +++ b/production/mempool-config.testnet.json @@ -9,8 +9,6 @@ "API_URL_PREFIX": "/api/v1/", "INDEXING_BLOCKS_AMOUNT": -1, "AUDIT": true, - "ADVANCED_GBT_AUDIT": true, - "ADVANCED_GBT_MEMPOOL": true, "RUST_GBT": true, "POLL_RATE_MS": 1000, "DISK_CACHE_BLOCK_INTERVAL": 1, From 0533953f54326ae3a2e353f84a9f990070d44b8e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 6 Jan 2024 22:32:32 +0000 Subject: [PATCH 09/28] Add LIMIT_GBT config --- backend/mempool-config.sample.json | 1 + backend/src/__fixtures__/mempool-config.template.json | 1 + backend/src/__tests__/config.test.ts | 1 + docker/backend/mempool-config.json | 1 + docker/backend/start.sh | 2 ++ 5 files changed, 6 insertions(+) diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index c98a070f1..79afbae13 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -29,6 +29,7 @@ "POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master", "AUDIT": false, "RUST_GBT": false, + "LIMIT_GBT": false, "CPFP_INDEXING": false, "DISK_CACHE_BLOCK_INTERVAL": 6, "MAX_PUSH_TX_SIZE_WEIGHT": 4000000, diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 7d571c169..41e9cce4e 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -29,6 +29,7 @@ "POOLS_JSON_URL": "__MEMPOOL_POOLS_JSON_URL__", "AUDIT": true, "RUST_GBT": false, + "LIMIT_GBT": false, "CPFP_INDEXING": true, "MAX_BLOCKS_BULK_QUERY": 999, "DISK_CACHE_BLOCK_INTERVAL": 999, diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 84c2e763b..75661951e 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -42,6 +42,7 @@ describe('Mempool Backend Config', () => { POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json', AUDIT: false, RUST_GBT: false, + LIMIT_GBT: false, CPFP_INDEXING: false, MAX_BLOCKS_BULK_QUERY: 0, DISK_CACHE_BLOCK_INTERVAL: 6, diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index 0403ba42e..0d6017788 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -27,6 +27,7 @@ "AUTOMATIC_BLOCK_REINDEXING": __MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__, "AUDIT": __MEMPOOL_AUDIT__, "RUST_GBT": __MEMPOOL_RUST_GBT__, + "LIMIT_GBT": __MEMPOOL_LIMIT_GBT__, "CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__, "MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__, "DISK_CACHE_BLOCK_INTERVAL": __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 39870ab0c..aecdac8f1 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -30,6 +30,7 @@ __MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubuserconte __MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master} __MEMPOOL_AUDIT__=${MEMPOOL_AUDIT:=false} __MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=false} +__MEMPOOL_LIMIT_GBT__=${MEMPOOL_LIMIT_GBT:=false} __MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false} __MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0} __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__=${MEMPOOL_DISK_CACHE_BLOCK_INTERVAL:=6} @@ -188,6 +189,7 @@ sed -i "s!__MEMPOOL_POOLS_JSON_URL__!${__MEMPOOL_POOLS_JSON_URL__}!g" mempool-co sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}!g" mempool-config.json sed -i "s!__MEMPOOL_AUDIT__!${__MEMPOOL_AUDIT__}!g" mempool-config.json sed -i "s!__MEMPOOL_RUST_GBT__!${__MEMPOOL_RUST_GBT__}!g" mempool-config.json +sed -i "s!__MEMPOOL_LIMIT_GBT__!${__MEMPOOL_LIMIT_GBT__}!g" mempool-config.json sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-config.json sed -i "s!__MEMPOOL_MAX_BLOCKS_BULK_QUERY__!${__MEMPOOL_MAX_BLOCKS_BULK_QUERY__}!g" mempool-config.json sed -i "s!__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__!${__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__}!g" mempool-config.json From b10bd05207c82001621edb07f9d068a9f5d5c11a Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sat, 6 Jan 2024 22:41:03 +0000 Subject: [PATCH 10/28] Move max cpfp graph size to named const --- backend/src/api/cpfp.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/api/cpfp.ts b/backend/src/api/cpfp.ts index cf54771bb..aefdad5a0 100644 --- a/backend/src/api/cpfp.ts +++ b/backend/src/api/cpfp.ts @@ -1,6 +1,7 @@ import { CpfpInfo, MempoolTransactionExtended } from '../mempool.interfaces'; const CPFP_UPDATE_INTERVAL = 60_000; // update CPFP info at most once per 60s per transaction +const MAX_GRAPH_SIZE = 50; // the maximum number of in-mempool relatives to consider interface GraphTx extends MempoolTransactionExtended { depends: string[]; @@ -92,13 +93,13 @@ function mempoolToGraphTx(tx: MempoolTransactionExtended): GraphTx { } /** - * Takes a map of transaction ancestors, and expands it into a full graph of up to 50 in-mempool relatives + * Takes a map of transaction ancestors, and expands it into a full graph of up to MAX_GRAPH_SIZE in-mempool relatives */ function expandRelativesGraph(mempool: { [txid: string]: MempoolTransactionExtended }, ancestors: Map): Map { const relatives: Map = new Map(); const stack: GraphTx[] = Array.from(ancestors.values()); while (stack.length > 0) { - if (relatives.size > 50) { + if (relatives.size > MAX_GRAPH_SIZE) { return relatives; } @@ -170,7 +171,7 @@ function calculateCpfpCluster(txid: string, graph: Map): Map(best?.ancestorMap?.entries() || []); while (sortedRelatives.length && best && (best.txid !== tx.txid && !best.ancestorMap.has(tx.txid)) && maxIterations > 0) { @@ -236,7 +237,7 @@ function calculateCpfpCluster(txid: string, graph: Map): Map, visited: Map>, depth: number = 0): Map { // sanity check for infinite recursion / too many ancestors (should never happen) - if (depth > 50) { + if (depth > MAX_GRAPH_SIZE) { return tx.ancestorMap; } From 5411eb491f9326425aa7d6a72f5186b0d2d9691e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 7 Jan 2024 17:57:36 +0000 Subject: [PATCH 11/28] Limit GBT: fix candidate set inconsistency --- backend/src/api/mempool-blocks.ts | 27 ++++++++++++++++----------- backend/src/api/mempool.ts | 14 ++++++++++---- backend/src/api/websocket-handler.ts | 4 ++-- 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index fd9918f71..2308f6671 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -91,7 +91,7 @@ class MempoolBlocks { // set missing short ids for (const txid of transactions) { const tx = newMempool[txid]; - this.setUid(tx, false); + this.setUid(tx, !saveResults); } const accelerations = useAccelerations ? mempool.getAccelerations() : {}; @@ -170,7 +170,7 @@ class MempoolBlocks { for (const tx of addedAndChanged) { this.setUid(tx, false); } - const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => uid != null) as number[]; + const removedTxs = removed.filter(tx => tx.uid != null) as MempoolTransactionExtended[]; // prepare a stripped down version of the mempool with only the minimum necessary data // to reduce the overhead of passing this data to the worker thread @@ -196,10 +196,10 @@ class MempoolBlocks { }); this.txSelectionWorker?.once('error', reject); }); - this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed: removedUids }); + this.txSelectionWorker.postMessage({ type: 'update', added: addedStripped, removed: removedTxs.map(tx => tx.uid) as number[] }); const { blocks, rates, clusters } = this.convertResultTxids(await workerResultPromise); - this.removeUids(removedUids); + this.removeUids(removedTxs); // clean up thread error listener this.txSelectionWorker?.removeListener('error', threadErrorListener); @@ -227,7 +227,7 @@ class MempoolBlocks { const transactions = txids.map(txid => newMempool[txid]).filter(tx => tx != null); // set missing short ids for (const tx of transactions) { - this.setUid(tx, false); + this.setUid(tx, !saveResults); } // set short ids for transaction inputs for (const tx of transactions) { @@ -237,6 +237,7 @@ class MempoolBlocks { const accelerations = useAccelerations ? mempool.getAccelerations() : {}; const acceleratedList = accelerationPool ? Object.values(accelerations).filter(acc => newMempool[acc.txid] && acc.pools.includes(accelerationPool)) : Object.values(accelerations).filter(acc => newMempool[acc.txid]); const convertedAccelerations = acceleratedList.map(acc => { + this.setUid(newMempool[acc.txid], true); return { uid: this.getUid(newMempool[acc.txid]), delta: acc.feeDelta, @@ -292,11 +293,12 @@ class MempoolBlocks { for (const tx of added) { tx.inputs = tx.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => (uid !== null && uid !== undefined)) as number[]; } - const removedUids = removed.map(tx => this.getUid(tx)).filter(uid => (uid !== null && uid !== undefined)) as number[]; + const removedTxs = removed.filter(tx => tx.uid != null) as MempoolTransactionExtended[]; const accelerations = useAccelerations ? mempool.getAccelerations() : {}; const acceleratedList = accelerationPool ? Object.values(accelerations).filter(acc => newMempool[acc.txid] && acc.pools.includes(accelerationPool)) : Object.values(accelerations).filter(acc => newMempool[acc.txid]); const convertedAccelerations = acceleratedList.map(acc => { + this.setUid(newMempool[acc.txid], true); return { uid: this.getUid(newMempool[acc.txid]), delta: acc.feeDelta, @@ -308,7 +310,7 @@ class MempoolBlocks { const { blocks, blockWeights, rates, clusters, overflow } = this.convertNapiResultTxids( await this.rustGbtGenerator.update( added as RustThreadTransaction[], - removedUids, + removedTxs.map(tx => tx.uid) as number[], convertedAccelerations as RustThreadAcceleration[], this.nextUid, ), @@ -319,7 +321,7 @@ class MempoolBlocks { throw new Error(`GBT returned wrong number of transactions ${transactions.length} vs ${resultMempoolSize}, cache is probably out of sync`); } else { const processed = this.processBlockTemplates(newMempool, blocks, blockWeights, rates, clusters, candidates, accelerations, accelerationPool, true); - this.removeUids(removedUids); + this.removeUids(removedTxs); logger.debug(`RUST updateBlockTemplates completed in ${(Date.now() - start)/1000} seconds`); return processed; } @@ -522,9 +524,12 @@ class MempoolBlocks { } } - private removeUids(uids: number[]): void { - for (const uid of uids) { - this.uidMap.delete(uid); + private removeUids(txs: MempoolTransactionExtended[]): void { + for (const tx of txs) { + if (tx.uid != null) { + this.uidMap.delete(tx.uid); + tx.uid = undefined; + } } } diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index b08848b26..68f91b5b0 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -320,8 +320,6 @@ class Mempool { }, 1000 * 60 * config.MEMPOOL.CLEAR_PROTECTION_MINUTES); } - const candidates = await this.getNextCandidates(minFeeMempool, minFeeTip); - const deletedTransactions: MempoolTransactionExtended[] = []; if (this.mempoolProtection !== 1) { @@ -341,6 +339,8 @@ class Mempool { } } + const candidates = await this.getNextCandidates(minFeeMempool, minFeeTip, deletedTransactions); + const newMempoolSize = currentMempoolSize + newTransactions.length - deletedTransactions.length; const newTransactionsStripped = newTransactions.map((tx) => Common.stripTransaction(tx)); this.latestTransactions = newTransactionsStripped.concat(this.latestTransactions).slice(0, 6); @@ -445,8 +445,12 @@ class Mempool { } } - public async getNextCandidates(minFeeTransactions: string[], blockHeight: number): Promise { + public async getNextCandidates(minFeeTransactions: string[], blockHeight: number, deletedTransactions: MempoolTransactionExtended[]): Promise { if (this.limitGBT) { + const deletedTxsMap = {}; + for (const tx of deletedTransactions) { + deletedTxsMap[tx.txid] = tx; + } const newCandidateTxMap = {}; for (const txid of minFeeTransactions) { if (this.mempoolCache[txid]) { @@ -463,14 +467,16 @@ class Mempool { } else { for (const txid of Object.keys(this.mempoolCandidates)) { if (!newCandidateTxMap[txid]) { - removed.push(this.mempoolCache[txid]); if (this.mempoolCache[txid]) { + removed.push(this.mempoolCache[txid]); this.mempoolCache[txid].effectiveFeePerVsize = this.mempoolCache[txid].adjustedFeePerVsize; this.mempoolCache[txid].ancestors = []; this.mempoolCache[txid].descendants = []; this.mempoolCache[txid].bestDescendant = null; this.mempoolCache[txid].cpfpChecked = false; this.mempoolCache[txid].cpfpUpdated = undefined; + } else if (deletedTxsMap[txid]) { + removed.push(deletedTxsMap[txid]); } } } diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index d42fc11f0..63e7f383c 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -750,7 +750,7 @@ class WebsocketHandler { const _memPool = memPool.getMempool(); const candidateTxs = await memPool.getMempoolCandidates(); let candidates: GbtCandidates | undefined = (memPool.limitGBT && candidateTxs) ? { txs: candidateTxs, added: [], removed: [] } : undefined; - let transactionIds: string[] = (memPool.limitGBT && candidates) ? Object.keys(_memPool) : Object.keys(candidates?.txs || {}); + let transactionIds: string[] = (memPool.limitGBT) ? Object.keys(candidates?.txs || {}) : Object.keys(_memPool); const accelerations = Object.values(mempool.getAccelerations()); await accelerationRepository.$indexAccelerationsForBlock(block, accelerations, transactions); @@ -839,7 +839,7 @@ class WebsocketHandler { if (memPool.limitGBT) { const minFeeMempool = memPool.limitGBT ? await bitcoinSecondClient.getRawMemPool() : null; const minFeeTip = memPool.limitGBT ? await bitcoinSecondClient.getBlockCount() : -1; - candidates = await memPool.getNextCandidates(minFeeMempool, minFeeTip); + candidates = await memPool.getNextCandidates(minFeeMempool, minFeeTip, transactions); transactionIds = Object.keys(candidates?.txs || {}); } else { candidates = undefined; From 07c76d084e8f1b7fa12e79488bb1ac0e120ed28d Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 8 Jan 2024 00:03:05 +0000 Subject: [PATCH 12/28] Limit GBT: handle accelerations beneath the purge rate --- backend/src/api/mempool-blocks.ts | 18 ++++++++++++------ backend/src/api/mempool.ts | 6 ++++++ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 2308f6671..d88328161 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -18,6 +18,7 @@ class MempoolBlocks { private nextUid: number = 1; private uidMap: Map = new Map(); // map short numerical uids to full txids + private txidMap: Map = new Map(); // map full txids back to short numerical uids public getMempoolBlocks(): MempoolBlock[] { return this.mempoolBlocks.map((block) => { @@ -503,33 +504,38 @@ class MempoolBlocks { private resetUids(): void { this.uidMap.clear(); + this.txidMap.clear(); this.nextUid = 1; } private setUid(tx: MempoolTransactionExtended, skipSet = false): number { - if (tx.uid === null || tx.uid === undefined || !skipSet) { + if (!this.txidMap.has(tx.txid) || !skipSet) { const uid = this.nextUid; this.nextUid++; this.uidMap.set(uid, tx.txid); + this.txidMap.set(tx.txid, uid); tx.uid = uid; return uid; } else { + tx.uid = this.txidMap.get(tx.txid) as number; return tx.uid; } } private getUid(tx: MempoolTransactionExtended): number | void { - if (tx?.uid !== null && tx?.uid !== undefined && this.uidMap.has(tx.uid)) { - return tx.uid; + if (tx) { + return this.txidMap.get(tx.txid); } } private removeUids(txs: MempoolTransactionExtended[]): void { for (const tx of txs) { - if (tx.uid != null) { - this.uidMap.delete(tx.uid); - tx.uid = undefined; + const uid = this.txidMap.get(tx.txid); + if (uid != null) { + this.uidMap.delete(uid); + this.txidMap.delete(tx.txid); } + tx.uid = undefined; } } diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 68f91b5b0..9fab83080 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -457,6 +457,12 @@ class Mempool { newCandidateTxMap[txid] = true; } } + const accelerations = this.getAccelerations(); + for (const txid of Object.keys(accelerations)) { + if (this.mempoolCache[txid]) { + newCandidateTxMap[txid] = true; + } + } const removed: MempoolTransactionExtended[] = []; const added: MempoolTransactionExtended[] = []; // don't prematurely remove txs included in a new block From 8f2e1de578b25ec9e28bdbd0528e30a77401db52 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 8 Jan 2024 00:56:48 +0000 Subject: [PATCH 13/28] Limit GBT: fix on-demand CPFP calculation --- backend/src/api/cpfp.ts | 9 ++++++--- backend/src/api/mempool-blocks.ts | 10 ---------- backend/src/api/mempool.ts | 7 +++++++ 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/src/api/cpfp.ts b/backend/src/api/cpfp.ts index aefdad5a0..604c1b3c9 100644 --- a/backend/src/api/cpfp.ts +++ b/backend/src/api/cpfp.ts @@ -1,4 +1,5 @@ import { CpfpInfo, MempoolTransactionExtended } from '../mempool.interfaces'; +import memPool from './mempool'; const CPFP_UPDATE_INTERVAL = 60_000; // update CPFP info at most once per 60s per transaction const MAX_GRAPH_SIZE = 50; // the maximum number of in-mempool relatives to consider @@ -77,8 +78,8 @@ export function calculateCpfp(tx: MempoolTransactionExtended, mempool: { [txid: function mempoolToGraphTx(tx: MempoolTransactionExtended): GraphTx { return { ...tx, - depends: [], - spentby: [], + depends: tx.vin.map(v => v.txid), + spentby: tx.vout.map((v, i) => memPool.getFromSpendMap(tx.txid, i)).map(tx => tx?.txid).filter(txid => txid != null) as string[], ancestorMap: new Map(), fees: { base: tx.fee, @@ -176,7 +177,7 @@ function calculateCpfpCluster(txid: string, graph: Map): Map(best?.ancestorMap?.entries() || []); while (sortedRelatives.length && best && (best.txid !== tx.txid && !best.ancestorMap.has(tx.txid)) && maxIterations > 0) { maxIterations--; - if (bestCluster && bestCluster.has(tx.txid)) { + if ((best && best.txid === tx.txid) || (bestCluster && bestCluster.has(tx.txid))) { break; } else { // Remove this cluster (it doesn't include our target tx) @@ -195,6 +196,8 @@ function calculateCpfpCluster(txid: string, graph: Map): Map Date: Tue, 9 Jan 2024 00:27:04 +0000 Subject: [PATCH 14/28] Sanity checks for mempool visualization deltas --- .../block-overview-graph/block-overview-graph.component.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts index ab6985a25..a5f6403ad 100644 --- a/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts +++ b/frontend/src/app/components/block-overview-graph/block-overview-graph.component.ts @@ -206,6 +206,10 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On update(add: TransactionStripped[], remove: string[], change: { txid: string, rate: number | undefined, acc: boolean | undefined }[], direction: string = 'left', resetLayout: boolean = false): void { if (this.scene) { + add = add.filter(tx => !this.scene.txs[tx.txid]); + remove = remove.filter(txid => this.scene.txs[txid]); + change = change.filter(tx => this.scene.txs[tx.txid]); + this.scene.update(add, remove, change, direction, resetLayout); this.start(); this.updateSearchHighlight(); From cbcb61d3fa49ca28445fb62c651d8f5c34aeb72d Mon Sep 17 00:00:00 2001 From: Mononaut Date: Fri, 29 Mar 2024 06:51:56 +0000 Subject: [PATCH 15/28] hide empty audit row in tx page details panel --- .../src/app/components/transaction/transaction.component.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html index 0963014bd..f2a580d07 100644 --- a/frontend/src/app/components/transaction/transaction.component.html +++ b/frontend/src/app/components/transaction/transaction.component.html @@ -70,10 +70,10 @@ - + Audit - + Coinbase Expected in Block Seen in Mempool From 66309813d5aef7a413dbcb8fda4d2b4f18c0ed1c Mon Sep 17 00:00:00 2001 From: softsimon Date: Fri, 29 Mar 2024 16:30:55 +0900 Subject: [PATCH 16/28] Changing to async pipe --- .../blockchain-blocks.component.html | 6 ++++-- .../blockchain-blocks/blockchain-blocks.component.ts | 11 +++-------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html index 2224ec0bf..ac29524bb 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.html @@ -40,12 +40,14 @@
-
-
+
+
diff --git a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts index e81e97acc..02591f657 100644 --- a/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts +++ b/frontend/src/app/components/blockchain-blocks/blockchain-blocks.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, ChangeDetectorRef, Input, OnChanges, SimpleChanges } from '@angular/core'; -import { Observable, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { StateService } from '../../services/state.service'; import { specialBlocks } from '../../app.constants'; import { BlockExtended } from '../../interfaces/node-api.interface'; @@ -45,6 +45,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { markBlockSubscription: Subscription; txConfirmedSubscription: Subscription; loadingBlocks$: Observable; + showMiningInfo$: BehaviorSubject = new BehaviorSubject(false); blockStyles = []; emptyBlockStyles = []; interval: any; @@ -54,8 +55,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { arrowLeftPx = 30; blocksFilled = false; arrowTransition = '1s'; - showMiningInfo = false; - showMiningInfoSubscription: Subscription; timeLtrSubscription: Subscription; timeLtr: boolean; @@ -94,10 +93,7 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { if (['', 'testnet', 'signet'].includes(this.stateService.network)) { this.enabledMiningInfoIfNeeded(this.location.path()); this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url)); - this.showMiningInfoSubscription = this.stateService.showMiningInfo$.subscribe((showMiningInfo) => { - this.showMiningInfo = showMiningInfo; - this.cd.markForCheck(); - }); + this.showMiningInfo$ = this.stateService.showMiningInfo$; } this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { @@ -210,7 +206,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy { this.tabHiddenSubscription.unsubscribe(); this.markBlockSubscription.unsubscribe(); this.timeLtrSubscription.unsubscribe(); - this.showMiningInfoSubscription.unsubscribe(); clearInterval(this.interval); } From 6b8f08c6d72f907749d22c90c0442a56bc086b6e Mon Sep 17 00:00:00 2001 From: natsoni Date: Fri, 29 Mar 2024 21:56:13 +0900 Subject: [PATCH 17/28] Add node data to lightning world channels graph --- .../nodes-channels-map.component.ts | 54 +++++++++++++++---- 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts index aab62f2c4..18edc6ab1 100644 --- a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts +++ b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts @@ -1,13 +1,16 @@ import { ChangeDetectionStrategy, Component, Input, Output, EventEmitter, NgZone, OnInit } from '@angular/core'; import { SeoService } from '../../services/seo.service'; import { ApiService } from '../../services/api.service'; -import { delay, Observable, switchMap, tap, zip } from 'rxjs'; +import { delay, Observable, of, switchMap, tap, zip } from 'rxjs'; import { AssetsService } from '../../services/assets.service'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { StateService } from '../../services/state.service'; import { EChartsOption, echarts } from '../../graphs/echarts'; import { isMobile } from '../../shared/common.utils'; +import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe'; +import { getFlagEmoji } from '../../shared/common.utils'; +import { lerpColor } from '../../shared/graphs.utils'; @Component({ selector: 'app-nodes-channels-map', @@ -50,6 +53,7 @@ export class NodesChannelsMap implements OnInit { private router: Router, private zone: NgZone, private activatedRoute: ActivatedRoute, + private amountShortenerPipe: AmountShortenerPipe, ) { } @@ -86,10 +90,12 @@ export class NodesChannelsMap implements OnInit { return zip( this.assetsService.getWorldMapJson$, this.style !== 'channelpage' ? this.apiService.getChannelsGeo$(params.get('public_key') ?? undefined, this.style) : [''], - [params.get('public_key') ?? undefined] + [params.get('public_key') ?? undefined], + this.style === 'widget' ? of(undefined) : this.apiService.getWorldNodes$(), ).pipe(tap((data) => { echarts.registerMap('world', data[0]); + let maxLiquidity = data[3]?.maxLiquidity; const channelsLoc = []; const nodes = []; const nodesPubkeys = {}; @@ -197,13 +203,24 @@ export class NodesChannelsMap implements OnInit { this.zoom = -0.05 * distance + 8; } - this.prepareChartOptions(nodes, channelsLoc); + if (data[3]) { + for (const node of nodes) { + const foundNode = data[3].nodes.find((n) => n[2] === node[3]); + if (foundNode) { + node.push(foundNode[4], foundNode[5], foundNode[6]?.en, foundNode[7]); + maxLiquidity = Math.max(maxLiquidity ?? 0, foundNode[4]); + } + } + } + + maxLiquidity = Math.max(1, maxLiquidity); + this.prepareChartOptions(nodes, channelsLoc, maxLiquidity); })); }) ); } - prepareChartOptions(nodes, channels) { + prepareChartOptions(nodes, channels, maxLiquidity) { let title: object; if (channels.length === 0) { if (!this.placeholder) { @@ -267,7 +284,12 @@ export class NodesChannelsMap implements OnInit { data: nodes, coordinateSystem: 'geo', geoIndex: 0, - symbolSize: this.nodeSize, + symbolSize: (params) => { + if (maxLiquidity) { + return 10 * Math.pow(params[5] / maxLiquidity, 0.2) + 3; + } + return this.nodeSize; + }, tooltip: { show: true, backgroundColor: 'rgba(17, 19, 31, 1)', @@ -281,11 +303,25 @@ export class NodesChannelsMap implements OnInit { formatter: (value) => { const data = value.data; const alias = data[4].length > 0 ? data[4] : data[3].slice(0, 20); - return `${alias}`; - } + const liquidity = data[5] >= 100000000 ? + `${this.amountShortenerPipe.transform(data[5] / 100000000)} BTC` : + `${this.amountShortenerPipe.transform(data[5], 2)} sats`; + + return ` + ${alias}
+ ${liquidity}
` + + $localize`:@@205c1b86ac1cc419c4d0cca51fdde418c4ffdc20:${data[6]}:INTERPOLATION: channels` + `
+ ${getFlagEmoji(data[8])} ${data[7]} + `; + }, }, itemStyle: { - color: 'white', + color: (params) => { + if (!maxLiquidity) { + return 'white'; + } + return `${lerpColor('#1E88E5', '#D81B60', Math.pow(params.data[5] / maxLiquidity, 0.2))}`; + }, opacity: 1, borderColor: 'black', borderWidth: 0, @@ -361,8 +397,6 @@ export class NodesChannelsMap implements OnInit { } chartOptions.series[0].itemStyle.borderWidth = nodeBorder; - chartOptions.series[0].symbolSize += e.zoom > 1 ? speed * 15 : -speed * 15; - chartOptions.series[0].symbolSize = Math.max(4, Math.min(7, chartOptions.series[0].symbolSize)); chartOptions.series[1].lineStyle.opacity += e.zoom > 1 ? speed : -speed; chartOptions.series[1].lineStyle.width += e.zoom > 1 ? speed : -speed; From 6bebc76633f3605f2d5bd1446cd78cda74b5d7f6 Mon Sep 17 00:00:00 2001 From: softsimon Date: Sun, 31 Mar 2024 12:26:13 +0900 Subject: [PATCH 18/28] Fix websocket init test support --- .../cypress/fixtures/mainnet_mempoolInfo.json | 1305 ++++++++++------- frontend/cypress/support/websocket.ts | 10 +- 2 files changed, 768 insertions(+), 547 deletions(-) diff --git a/frontend/cypress/fixtures/mainnet_mempoolInfo.json b/frontend/cypress/fixtures/mainnet_mempoolInfo.json index 84a9eb304..5c41bd4dd 100644 --- a/frontend/cypress/fixtures/mainnet_mempoolInfo.json +++ b/frontend/cypress/fixtures/mainnet_mempoolInfo.json @@ -1,554 +1,771 @@ { "mempoolInfo": { - "loaded": true, - "size": 15168, - "bytes": 6070217, - "usage": 30922560, - "maxmempool": 300000000, - "mempoolminfee": 0.00001, - "minrelaytxfee": 0.00001, - "unbroadcastcount": 0 - }, - "vBytesPerSecond": 2249, - "blocks": [ - { - "extras": { - "reward": 636429476, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "031e190b04a4632f622f466f756e6472792055534120506f6f6c202364726f70676f6c642f12969a355b4c000000000000" - } - ], - "vout": [ - { - "scriptpubkey_address": "19dENFt4wVwos6xtgwStA6n8bbA57WCS58", - "value": 636429476 - } - ] - }, - "medianFee": 6, - "feeRange": [ - 1, - 3, - 5, - 6, - 10, - 16, - 272 - ], - "totalFees": 11429476, - "avgFee": 4177, - "avgFeeRate": 11, - "pool": { - "id": 108, - "name": "Foundry USA" - }, - "matchRate": 98 - }, - "id": "0000000000000000000918db3142fc4ccea865edb9a9f0c05f5a1b507fd7182a", - "height": 727326, - "version": 536870912, - "timestamp": 1647272873, - "bits": 386545523, - "nonce": 105379536, - "difficulty": 27550332084343.84, - "merkle_root": "d39e7d7b8e2374be518a313b9af734a0d0d87c7e8c001b5c19472258790ebfbc", - "tx_count": 2737, - "size": 1714410, - "weight": 3993069, - "previousblockhash": "00000000000000000007b6a24a3caa6b1f320c163b6ae15f763ca4d157a304ab" - }, - { - "extras": { - "reward": 634306311, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "031f190b1362696e616e63652f3832321c000b0193666a88fabe6d6de404a9ad37192b7a677b62eae09c0d60d2158b037e7250c395a0c4db62e699f20200000000000000bf370000a9600000" - } - ], - "vout": [ - { - "scriptpubkey_address": "1JvXhnHCi6XqcanvrZJ5s2Qiv4tsmm2UMy", - "value": 634306311 - } - ] - }, - "medianFee": 5, - "feeRange": [ - 1, - 2, - 3, - 5, - 9, - 15, - 316 - ], - "totalFees": 9306311, - "avgFee": 2861, - "avgFeeRate": 9, - "pool": { - "id": 102, - "name": "Binance Pool" - }, - "matchRate": 98 - }, - "id": "000000000000000000012de2fedb9f88f071ee149c03b4265cf77667b63c61fe", - "height": 727327, - "version": 536928256, - "timestamp": 1647273549, - "bits": 386545523, - "nonce": 104858074, - "difficulty": 27550332084343.84, - "merkle_root": "d2349186400acd9de2d1812bf2916834bf3299bf4a32d27da91aa2c04a434360", - "tx_count": 3253, - "size": 1610333, - "weight": 3993434, - "previousblockhash": "0000000000000000000918db3142fc4ccea865edb9a9f0c05f5a1b507fd7182a" - }, - { - "extras": { - "reward": 629478265, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "0320190b215c204d41524120506f6f6c205c000000003766ea3bdd65bc206ece9c55812c0000" - } - ], - "vout": [ - { - "scriptpubkey_address": "1A32KFEX7JNPmU1PVjrtiXRrTQcesT3Nf1", - "value": 629478265 - } - ] - }, - "medianFee": 2, - "feeRange": [ - 1, - 1, - 1, - 2, - 3, - 9, - 752 - ], - "totalFees": 4478265, - "avgFee": 2179, - "avgFeeRate": 4, - "pool": { - "id": 112, - "name": "MARA Pool" - }, - "matchRate": 99 - }, - "id": "0000000000000000000699b7d37e0614c1732f4a67314b039de04e025eb7f901", - "height": 727328, - "version": 543162368, - "timestamp": 1647273774, - "bits": 386545523, - "nonce": 2592684945, - "difficulty": 27550332084343.84, - "merkle_root": "94c744153aebf492935379cc1a1e9d1f28a3fb1f3c669e55e22b34ce09e7929c", - "tx_count": 2056, - "size": 1678108, - "weight": 3992617, - "previousblockhash": "000000000000000000012de2fedb9f88f071ee149c03b4265cf77667b63c61fe" - }, - { - "extras": { - "reward": 627516396, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "0321190b04be672f622f466f756e6472792055534120506f6f6c202364726f70676f6c642f063791a2b6c3000000000000" - } - ], - "vout": [ - { - "scriptpubkey_address": "19dENFt4wVwos6xtgwStA6n8bbA57WCS58", - "value": 627516396 - } - ] - }, - "medianFee": 2, - "feeRange": [ - 1, - 1, - 1, - 2, - 5, - 14, - 347 - ], - "totalFees": 2516396, - "avgFee": 3122, - "avgFeeRate": 7, - "pool": { - "id": 108, - "name": "Foundry USA" - }, - "matchRate": 100 - }, - "id": "00000000000000000008e79402333f8bb6f03147550d36fe731480cf2c9e2fb8", - "height": 727329, - "version": 536870912, - "timestamp": 1647273918, - "bits": 386545523, - "nonce": 585448784, - "difficulty": 27550332084343.84, - "merkle_root": "db0f5982d7797b74b5e92ff8495280e530c1d98f50e21f7cfe244b03c7fef6ab", - "tx_count": 807, - "size": 540195, - "weight": 1379553, - "previousblockhash": "0000000000000000000699b7d37e0614c1732f4a67314b039de04e025eb7f901" - }, - { - "extras": { - "reward": 646529546, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "0322190b04b7712f622f706f6f6c696e2e636f6d2ffabe6d6dec3c68ff646d6125f669e43f1053129a5664705728c0574e7db6449db652f4ed01000000000000009287821bd9525598170e94e5beaab395110045c9120000000000" - } - ], - "vout": [ - { - "scriptpubkey_address": "1GNgwA8JfG7Kc8akJ8opdNWJUihqUztfPe", - "value": 646529546 - } - ] - }, - "medianFee": 12, - "feeRange": [ - 1, - 5, - 10, - 12, - 19, - 38, - 1815 - ], - "totalFees": 21529546, - "avgFee": 6715, - "avgFeeRate": 21, - "pool": { - "id": 91, - "name": "Poolin" - }, - "matchRate": 95 - }, - "id": "00000000000000000003ccf134cbee355e976fbcb6e8573613410c61fa7a1b57", - "height": 727330, - "version": 536870916, - "timestamp": 1647276461, - "bits": 386545523, - "nonce": 1527558879, - "difficulty": 27550332084343.84, - "merkle_root": "af3870e45030bf7775b6eb996ab4c26cbe7964f572ca149e7e58efadaaf18dd4", - "tx_count": 3207, - "size": 1463281, - "weight": 3999829, - "previousblockhash": "00000000000000000008e79402333f8bb6f03147550d36fe731480cf2c9e2fb8" - }, - { - "extras": { - "reward": 644036960, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "0323190b082f5669614254432f2cfabe6d6df26d6c3e2d9050ab39b9332674dd451d68a03cb7e1db64056d4a5e7e235342fe100000000000000010fc7fd917f62bf41977ca2bb5f61d111100000000" - } - ], - "vout": [ - { - "scriptpubkey_address": "18cBEMRxXHqzWWCxZNtU91F5sbUNKhL5PX", - "value": 644036960 - } - ] - }, - "medianFee": 10, - "feeRange": [ - 1, - 5, - 8, - 10, - 17, - 30, - 724 - ], - "totalFees": 19036960, - "avgFee": 6429, - "avgFeeRate": 19, - "pool": { - "id": 70, - "name": "ViaBTC" - }, - "matchRate": 91 - }, - "id": "0000000000000000000270007691145df213b43d62e768052011b7b7d496ac15", - "height": 727331, - "version": 536870912, - "timestamp": 1647277836, - "bits": 386545523, - "nonce": 2078492739, - "difficulty": 27550332084343.84, - "merkle_root": "31df633f82c3ad60e692a9dc8f472167ba37ee00d61cf3e548792e64a6d0264f", - "tx_count": 2962, - "size": 1553257, - "weight": 3993235, - "previousblockhash": "00000000000000000003ccf134cbee355e976fbcb6e8573613410c61fa7a1b57" - }, - { - "extras": { - "reward": 636958014, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "0324190b0414a005ab627463706f6f6c2f6663637367fabe6d6dfa4a2a451de1d9dd584ea9d0fc8016c5600ca1133eac2ae3fefa82ce4e8d536e020000008e9b20aa0f5ae8f8ee41040000000000" - } - ], - "vout": [ - { - "scriptpubkey_address": "1Bf9sZvBHPFGVPX71WX2njhd1NXKv5y7v5", - "value": 636958014 - } - ] - }, - "medianFee": 8, - "feeRange": [ - 1, - 6, - 7, - 8, - 10, - 15, - 503 - ], - "totalFees": 11958014, - "avgFee": 4527, - "avgFeeRate": 11, - "pool": { - "id": 3, - "name": "BTC.com" - }, - "matchRate": 94 - }, - "id": "00000000000000000008b905d911099327f0f4feb992e58d603e96df3c445aa1", - "height": 727332, - "version": 541065220, - "timestamp": 1647278491, - "bits": 386545523, - "nonce": 2560770713, - "difficulty": 27550332084343.84, - "merkle_root": "fb35644b58e7f37bcb3062dda7e385e5a2c7b9f016039e84fff2043d201ea580", - "tx_count": 2642, - "size": 1365172, - "weight": 3993028, - "previousblockhash": "0000000000000000000270007691145df213b43d62e768052011b7b7d496ac15" - }, - { - "extras": { - "reward": 641367077, - "coinbaseTx": { - "vin": [ - { - "scriptsig": "0325190b2cfabe6d6dc9a64b2a34a929eab95ca6793bb05a59a4ada9440f2caae4a291527d5fc5876610000000f09f909f092f4632506f6f6c2f65000000000000000000000000000000000000000000000000000000000000000000000005004d270000" - } - ], - "vout": [ - { - "scriptpubkey_address": "1KFHE7w8BhaENAswwryaoccDb6qcT6DbYY", - "value": 641367077 - } - ] - }, - "medianFee": 10, - "feeRange": [ - 1, - 7, - 8, - 10, - 13, - 27, - 501 - ], - "totalFees": 16367077, - "avgFee": 5359, - "avgFeeRate": 16, - "pool": { - "id": 33, - "name": "F2Pool" - }, - "matchRate": 98 - }, - "id": "000000000000000000035194e2331953ecc1c2d4783c6fb692ebd0553795b185", - "height": 727333, - "version": 1073676292, - "timestamp": 1647279951, - "bits": 386545523, - "nonce": 2216944582, - "difficulty": 27550332084343.84, - "merkle_root": "f61bcda97b7b65336bd2b3f3dbea1ba0e00d881f3abb92d364795e971f4417fd", - "tx_count": 3055, - "size": 1511571, - "weight": 3998487, - "previousblockhash": "00000000000000000008b905d911099327f0f4feb992e58d603e96df3c445aa1" - } - ], - "conversions": { - "USD": 38723.918 + "loaded": true, + "size": 112686, + "bytes": 175691391, + "usage": 856780672, + "total_fee": 5.10536864, + "maxmempool": 300000000, + "mempoolminfee": 0.00001305, + "minrelaytxfee": 0.00001, + "incrementalrelayfee": 0.00001, + "unbroadcastcount": 0, + "fullrbf": true }, + "vBytesPerSecond": 2364, "mempool-blocks": [ - { - "blockSize": 1552154, - "blockVSize": 998819, - "nTx": 2700, - "totalFees": 13645525, - "medianFee": 9.348948740137697, - "feeRange": [ - 7.1906428439860015, - 7.636363636363637, - 8.03366058906031, - 8.537102473498233, - 10.102654867256637, - 12.356010230179027, - 17.915194346289752, - 350.87719298245617 - ] - }, - { - "blockSize": 1685282, - "blockVSize": 999945.5, - "nTx": 2711, - "totalFees": 6580658, - "medianFee": 6.031858407079646, - "feeRange": [ - 5.946188340807175, - 6.021505376344086, - 6.122448979591836, - 7.190512129380054 - ] - }, - { - "blockSize": 1598963, - "blockVSize": 991915.25, - "nTx": 2055, - "totalFees": 5267286, - "medianFee": 5.132743362831858, - "feeRange": [ - 5.007085498346717, - 5.095022624434389, - 5.322493224932249, - 5.946188340807175 - ] - }, - { - "blockSize": 1553568, - "blockVSize": 999920.25, - "nTx": 3688, - "totalFees": 4366448, - "medianFee": 4.106194690265487, - "feeRange": [ - 3.075103489059728, - 3.9575971731448765, - 4.559139784946237, - 5.0065912336592335 - ] - }, - { - "blockSize": 1638100, - "blockVSize": 999883, - "nTx": 2186, - "totalFees": 2322349, - "medianFee": 2.336283185840708, - "feeRange": [ - 2.0137103684661524, - 2.111358574610245, - 2.6007067137809186, - 3.075103489059728 - ] - }, - { - "blockSize": 1552617, - "blockVSize": 940875, - "nTx": 1746, - "totalFees": 1351076, - "medianFee": 1.3983842513934581, - "feeRange": [ - 1.0035005834305717, - 1.0972279685560613, - 1.7951509153884215, - 2.0136986301369864 - ] - }, - { - "blockSize": 224482, - "blockVSize": 122290, - "nTx": 65, - "totalFees": 122659, - "medianFee": 1.0020366598778003, - "feeRange": [ - 1, - 1.0010131712259371, - 1.0029268292682927, - 1.0034970564976715 - ] - } + { + "blockSize": 2162709, + "blockVSize": 997944, + "nTx": 4888, + "totalFees": 9079705, + "medianFee": 7.036906854130053, + "feeRange": [ + 7, + 7, + 7.010590015128593, + 7.022801302931596, + 7.12430426716141, + 10.01669449081803, + 236.07305936073058 + ] + }, + { + "blockSize": 2066789, + "blockVSize": 997996.25, + "nTx": 892, + "totalFees": 6623230, + "medianFee": 6.716362627217997, + "feeRange": [ + 6.189555125725338, + 6.42654028436019, + 6.636953672027368, + 6.994764397905759, + 7.005110732538331, + 7.005110732538331, + 7.00556586270872 + ] + }, + { + "blockSize": 1844287, + "blockVSize": 997956.25, + "nTx": 27, + "totalFees": 6370984, + "medianFee": 6.400148152967939, + "feeRange": [ + 6.152492668621701, + 6.156213928434013, + 6.165703275529865, + 6.179153864334673, + 6.400128130693307, + 6.400160164168272, + 6.400176181026004 + ] + }, + { + "blockSize": 1818283, + "blockVSize": 997984.75, + "nTx": 56, + "totalFees": 6365410, + "medianFee": 6.4000840859779125, + "feeRange": [ + 6.125, + 6.125, + 6.1303214596003475, + 6.149318801089918, + 6.161055058005517, + 6.400068069430819, + 6.400116119312593 + ] + }, + { + "blockSize": 1891412, + "blockVSize": 997993.25, + "nTx": 127, + "totalFees": 6355046, + "medianFee": 6.400068069430819, + "feeRange": [ + 6.024096385542169, + 6.033333333333333, + 6.049822064056939, + 6.080719839259195, + 6.123809523809523, + 6.141470180305132, + 6.400080081082096 + ] + }, + { + "blockSize": 1939469, + "blockVSize": 997987.25, + "nTx": 56, + "totalFees": 6350364, + "medianFee": 6.400036036577125, + "feeRange": [ + 6.01063829787234, + 6.014018691588785, + 6.01996303142329, + 6.023980815347722, + 6.031815335063076, + 6.4000200202705235, + 6.400052052963891 + ] + }, + { + "blockSize": 1906245, + "blockVSize": 997956, + "nTx": 28, + "totalFees": 6348648, + "medianFee": 6.4000200202705235, + "feeRange": [ + 6, + 6.010496568429552, + 6.011583011583012, + 6.019933554817276, + 6.400004004044084, + 6.400036036577125, + 6.400052052963891 + ] + }, + { + "blockSize": 333333741, + "blockVSize": 168604440, + "nTx": 106612, + "totalFees": 463043477, + "medianFee": 1.5796055679151149, + "feeRange": [ + 1.0005271543495804, + 1.1548204755273255, + 1.3540307201497894, + 1.408026342363497, + 1.519000275099878, + 1.5796055679151149, + 3.0039108631786284, + 3.927655905571819, + 5.004098751020287, + 5.041807153997041, + 6.4000294177934 + ] + } ], "transactions": [ - { - "txid": "d9e77f48b573addece8502bd081c39e3a7cd4df426e49ffddd7a45f9ffbc7d73", - "fee": 1136, - "vsize": 141.25, - "value": 50801 - }, - { - "txid": "6c08a2bacae4dc58eef85d3319256aaf5bd80d5364ffe9fff2e21a25a71d4e4b", - "fee": 1700, - "vsize": 140.25, - "value": 1538700 - }, - { - "txid": "163ba1ec8c9568821fa787f81e483f4f8216c4caeca30396b6322918b9763868", - "fee": 1850, - "vsize": 247, - "value": 369387 - }, - { - "txid": "e4d668153578c17d8f32404dd322e46d442c8eb00d58551b2ee51bdd1cf6d338", - "fee": 1369, - "vsize": 152.25, - "value": 723647 - }, - { - "txid": "7684807a3c6b1171b312be8cacc935883210d211045b6206516728484bc56e9e", - "fee": 845, - "vsize": 141.5, - "value": 464175 - }, - { - "txid": "249c69312f72cdba3ac5420eaf23a0e504570295daf7da8d0313883350c28778", - "fee": 330, - "vsize": 163.25, - "value": 116248571 - } + { + "txid": "6942073671954381264699dffce75b99d8afd1a3ccc8b58ff0201dcfd890be39", + "fee": 1094, + "vsize": 154, + "value": 365466, + "rate": 7.103896103896104 + }, + { + "txid": "e88521600e3c8348d0534777ca6490091cb3cdd1fac8a2abe95123f7cef319e5", + "fee": 2568, + "vsize": 208.25, + "value": 29143035, + "rate": 12.331332533013205 + }, + { + "txid": "e4c25dfb025084d0bad34e2350d3a49eff7cff7ee8dddeb069c56fa1511f5ce8", + "fee": 4548, + "vsize": 535.5, + "value": 215164, + "rate": 8.492997198879552 + }, + { + "txid": "dde3a5ac76a1aa79639a1b2f7509ab5c71996de00140ada84218bce12f2516f5", + "fee": 2110, + "vsize": 211.5, + "value": 196181, + "rate": 9.976359338061465 + }, + { + "txid": "28319981d28aa77dd3c75ad31cadc78c20c292d4713ecec1d4f7f6c495f3843b", + "fee": 3000, + "vsize": 202, + "value": 546, + "rate": 14.851485148514852 + }, + { + "txid": "29a2292326cdf97bb1189ea67e1ae5094db4be15ea05ff3b0624e4ee61ec1441", + "fee": 1970, + "vsize": 197, + "value": 3875456, + "rate": 10 + } ], - "backendInfo": { - "hostname": "node206.mempool.space", - "gitCommit": "8e61720e09faf5e0ecde1768583b1be9bb32292e", - "version": "2.4.0-dev" - }, "loadingIndicators": {}, + "fees": { + "fastestFee": 8, + "halfHourFee": 8, + "hourFee": 8, + "economyFee": 4, + "minimumFee": 2 + }, + "rbfSummary": [ + { + "txid": "feae4fb145c57d48c32b11c7f9ca15f2be10f102894dceed1b67f796b520c740", + "mined": false, + "fullRbf": false, + "oldFee": 8400, + "oldVsize": 345, + "newFee": 13301, + "newVsize": 346 + }, + { + "txid": "02b30c113a096d73871ea2d7c34189aadd04a60c3477043f19e3c806f6d69960", + "mined": false, + "fullRbf": false, + "oldFee": 2450, + "oldVsize": 263.25, + "newFee": 3151, + "newVsize": 263.25 + }, + { + "txid": "1d10e449f9b38126a707b6b88089d28ef81114acd2d8f9b0bd3f3266a7496e1f", + "mined": false, + "fullRbf": false, + "oldFee": 1967, + "oldVsize": 109.5, + "newFee": 3933, + "newVsize": 109.5 + }, + { + "txid": "281b1567a1de2a5c9109470b0a523702e438e935a4aa387727506ee852486a77", + "mined": false, + "fullRbf": false, + "oldFee": 1286, + "oldVsize": 143.25, + "newFee": 1550, + "newVsize": 143.25 + }, + { + "txid": "68ce3641b7e85fe60315bc1126cb93e8c61bdc3720f5d84bb0c8254e6cd58f82", + "mined": true, + "fullRbf": false, + "oldFee": 7579, + "oldVsize": 109.25, + "newFee": 14583, + "newVsize": 109.25 + }, + { + "txid": "08cf843ac10a7243d249d13912e27e4b635464ef5819c92488f6205d50d7c874", + "mined": true, + "fullRbf": false, + "oldFee": 1136, + "oldVsize": 141.5, + "newFee": 2272, + "newVsize": 141.5 + } + ], + "blocks": [ + { + "id": "0000000000000000000200e0829f5dd72b02401660837bf67379f6a7adf4d8c9", + "height": 837043, + "version": 803872768, + "timestamp": 1711850338, + "bits": 386097875, + "nonce": 2002092656, + "difficulty": 83126997340024.61, + "merkle_root": "e328e882992bd2de39ffc0a3ac331c31e64d7f4eb33547bb8fcbc09c2a4bffa3", + "tx_count": 3191, + "size": 2117591, + "weight": 3992738, + "previousblockhash": "000000000000000000011cefb2db6b82b6ae69b4ec06eedc81fc85d16f97865d", + "mediantime": 1711847828, + "stale": false, + "extras": { + "reward": 635081690, + "coinbaseRaw": "03b3c50c0463c308662f466f756e6472792055534120506f6f6c202364726f70676f6c642f3ed1ff622486000000000000", + "orphans": [], + "medianFee": 8.020001813606207, + "feeRange": [ + 7, + 7.011686143572621, + 7.012612612612613, + 7.12430426716141, + 10.005032712632108, + 15.515151515151516, + 102.36199095022624 + ], + "totalFees": 10081690, + "avgFee": 3160, + "avgFeeRate": 10, + "utxoSetChange": 3295, + "avgTxSize": 663.52, + "totalInputs": 5670, + "totalOutputs": 8965, + "totalOutputAmt": 163033638953, + "segwitTotalTxs": 3117, + "segwitTotalSize": 2095166, + "segwitTotalWeight": 3903146, + "feePercentiles": null, + "virtualSize": 998184.5, + "coinbaseAddress": "bc1qxhmdufsvnuaaaer4ynz88fspdsxq2h9e9cetdj", + "coinbaseSignature": "OP_0 OP_PUSHBYTES_20 35f6de260c9f3bdee47524c473a6016c0c055cb9", + "coinbaseSignatureAscii": "\u0000\u0000\u0000\u0000\u0000\u0000", + "header": "0020ea2f5d86976fd185fc81dcee06ecb469aeb6826bdbb2ef1c01000000000000000000a3ff4b2a9cc0cb8fbb4735b34e7f4de6311c33aca3c0ff39ded22b9982e828e362c30866d362031770825577", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 111, + "name": "Foundry USA", + "slug": "foundryusa" + }, + "matchRate": 100, + "expectedFees": 10293045, + "expectedWeight": 3991577, + "similarity": 0.9655852166344173 + } + }, + { + "id": "00000000000000000001911ff550eeb8493e589fdd6f1fc080f30969c26cf853", + "height": 837044, + "version": 1073676288, + "timestamp": 1711850704, + "bits": 386097875, + "nonce": 1562078084, + "difficulty": 83126997340024.61, + "merkle_root": "9756f896db91a4a7573cbd0e3baa4d8905c46a60ceed30cdcb440dc66d3fc102", + "tx_count": 3502, + "size": 1968923, + "weight": 3993734, + "previousblockhash": "0000000000000000000200e0829f5dd72b02401660837bf67379f6a7adf4d8c9", + "mediantime": 1711848750, + "stale": false, + "extras": { + "reward": 636442248, + "coinbaseRaw": "03b4c50c1b4d696e656420627920416e74506f6f6c38333035010001214a5697fabe6d6d89907adbb0addfe28be89c002fbe45908b9b32c68d1802b86ff36a5dfa22c6c810000000000000000000cbd2a647000000000000", + "orphans": [], + "medianFee": 9.015025041736227, + "feeRange": [ + 7, + 7.023255813953488, + 8.014973644733765, + 9.015025041736227, + 10.01669449081803, + 15.063829787234043, + 263.89487870619945 + ], + "totalFees": 11442248, + "avgFee": 3268, + "avgFeeRate": 11, + "utxoSetChange": 1740, + "avgTxSize": 562.09, + "totalInputs": 6657, + "totalOutputs": 8397, + "totalOutputAmt": 129424754973, + "segwitTotalTxs": 3428, + "segwitTotalSize": 1944942, + "segwitTotalWeight": 3897918, + "feePercentiles": null, + "virtualSize": 998433.5, + "coinbaseAddress": "37jKPSmbEGwgfacCr2nayn1wTaqMAbA94Z", + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 42402a28dd61f2718a4b27ae72a4791d5bbdade7 OP_EQUAL", + "coinbaseSignatureAscii": "Mined by AntPool8305\u0001\u0000\u0001!J\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000", + "header": "0000ff3fc9d8f4ada7f67973f67b83601640022bd75d9f82e0000200000000000000000002c13f6dc60d44cbcd30edce606ac405894daa3b0ebd3c57a7a491db96f85697d0c40866d3620317846b1b5d", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 44, + "name": "AntPool", + "slug": "antpool" + }, + "matchRate": 100, + "expectedFees": 11588891, + "expectedWeight": 3991949, + "similarity": 0.9797301408793522 + } + }, + { + "id": "00000000000000000001ee972656708fd787b570ec4b813ae8c5feee4f200c56", + "height": 837045, + "version": 637534208, + "timestamp": 1711853470, + "bits": 386097875, + "nonce": 235193117, + "difficulty": 83126997340024.61, + "merkle_root": "7e9ec459056b973b294dbe6dbcb821eb74b773c806e067db45ec36c781e4c18b", + "tx_count": 3135, + "size": 1521892, + "weight": 3997882, + "previousblockhash": "00000000000000000001911ff550eeb8493e589fdd6f1fc080f30969c26cf853", + "mediantime": 1711849305, + "stale": false, + "extras": { + "reward": 650142949, + "coinbaseRaw": "03b5c50c2cfabe6d6d96c7f8e8e15534e9902d846875d05bc0e5494b8519a860f51802daa2f48c215910000000f09f909f092f4632506f6f6c2f6700000000000000000000000000000000000000000000000000000000000000000000000500b4861602", + "orphans": [], + "medianFee": 19.30172311348782, + "feeRange": [ + 16, + 16.623739332816136, + 18, + 20.04728132387707, + 34.51977401129943, + 40.27210884353742, + 477.45358090185675 + ], + "totalFees": 25142949, + "avgFee": 8022, + "avgFeeRate": 25, + "utxoSetChange": 6554, + "avgTxSize": 485.28000000000003, + "totalInputs": 6655, + "totalOutputs": 13209, + "totalOutputAmt": 391783255749, + "segwitTotalTxs": 2914, + "segwitTotalSize": 1432235, + "segwitTotalWeight": 3639362, + "feePercentiles": null, + "virtualSize": 999470.5, + "coinbaseAddress": "1K6KoYC69NnafWJ7YgtrpwJxBLiijWqwa6", + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 c6740a12d0a7d556f89782bf5faf0e12cf25a639 OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "\/F2Pool/", + "header": "0000002653f86cc26909f380c01f6fdd9f583e49b8ee50f51f91010000000000000000008bc1e481c736ec45db67e006c873b774eb21b8bc6dbe4d293b976b0559c49e7e9ecf0866d36203171dc3040e", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 36, + "name": "F2Pool", + "slug": "f2pool" + }, + "matchRate": 99.9, + "expectedFees": 25286032, + "expectedWeight": 3991754, + "similarity": 0.9851630583666668 + } + }, + { + "id": "00000000000000000000ea58c5b60311988e9553e58ac5b4e7a2f1575d07a4f0", + "height": 837046, + "version": 732078080, + "timestamp": 1711853947, + "bits": 386097875, + "nonce": 2294678239, + "difficulty": 83126997340024.61, + "merkle_root": "0ea1bed22d6e98c256d92ee8c7e959949f88188872d290abaf89b3482ab5be55", + "tx_count": 3099, + "size": 1687564, + "weight": 3993067, + "previousblockhash": "00000000000000000001ee972656708fd787b570ec4b813ae8c5feee4f200c56", + "mediantime": 1711849492, + "stale": false, + "extras": { + "reward": 641586175, + "coinbaseRaw": "03b6c50c0484d108662f4d41524120506f6f6c202876303331393234292f38c0a19ff8fe2ab8e720838f0bd6411213114d8592000d010000ffffffff", + "orphans": [], + "medianFee": 15.003962563625333, + "feeRange": [ + 12, + 12.529550827423169, + 13.077227722772276, + 15.024711696869852, + 16.0427807486631, + 22.06646525679758, + 258.6666666666667 + ], + "totalFees": 16586175, + "avgFee": 5353, + "avgFeeRate": 16, + "utxoSetChange": 2417, + "avgTxSize": 544.45, + "totalInputs": 6941, + "totalOutputs": 9358, + "totalOutputAmt": 588984462870, + "segwitTotalTxs": 2969, + "segwitTotalSize": 1563622, + "segwitTotalWeight": 3497407, + "feePercentiles": null, + "virtualSize": 998266.75, + "coinbaseAddress": "15MdAHnkxt9TMC2Rj595hsg8Hnv693pPBB", + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 2fc701e2049ee4957b07134b6c1d771dd5a96b21 OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "/MARA Pool (v031924)/", + "header": "00a0a22b560c204feefec5e83a814bec70b587d78f70562697ee0100000000000000000055beb52a48b389afab90d2728818889f9459e9c7e82ed956c2986e2dd2bea10e7bd10866d3620317df02c688", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 115, + "name": "MARA Pool", + "slug": "marapool" + }, + "matchRate": 100, + "expectedFees": 16712235, + "expectedWeight": 3991968, + "similarity": 0.9876955157970453 + } + }, + { + "id": "000000000000000000021164d81f6059c18066bc0a93b928fdf455d2481c4708", + "height": 837047, + "version": 536936448, + "timestamp": 1711854238, + "bits": 386097875, + "nonce": 3048189996, + "difficulty": 83126997340024.61, + "merkle_root": "dbf52683cabe20a4bb4d2512cfed294a09e0825cac98bbe8fbafb3027ac9686a", + "tx_count": 2657, + "size": 1840568, + "weight": 3997883, + "previousblockhash": "00000000000000000000ea58c5b60311988e9553e58ac5b4e7a2f1575d07a4f0", + "mediantime": 1711849912, + "stale": false, + "extras": { + "reward": 637535865, + "coinbaseRaw": "03b7c50c2cfabe6d6d97e4a56bfc94081f5de141bab7dc7607f7511394a912e9f152f5e6d6e12f4d7210000000f09f909f092f4632506f6f6c2f7300000000000000000000000000000000000000000000000000000000000000000000000500b02a0c00", + "orphans": [], + "medianFee": 10.88611182786954, + "feeRange": [ + 9.610942249240122, + 10.003260515161395, + 10.42633371169126, + 11.058823529411764, + 12.969267139479905, + 18.044444444444444, + 422.5231646471846 + ], + "totalFees": 12535865, + "avgFee": 4719, + "avgFeeRate": 12, + "utxoSetChange": 3018, + "avgTxSize": 692.52, + "totalInputs": 6835, + "totalOutputs": 9853, + "totalOutputAmt": 272522814809, + "segwitTotalTxs": 2580, + "segwitTotalSize": 1813806, + "segwitTotalWeight": 3890943, + "feePercentiles": [ + 584, + 1492, + 1650, + 2002, + 3186, + 6487, + 942300 + ], + "medianFeeAmt": 2002, + "virtualSize": 999470.75, + "coinbaseAddress": "1K6KoYC69NnafWJ7YgtrpwJxBLiijWqwa6", + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 c6740a12d0a7d556f89782bf5faf0e12cf25a639 OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "/F2Pool/", + "header": "00000120f0a4075d57f1a2e7b4c58ae553958e981103b6c558ea000000000000000000006a68c97a02b3affbe8bb98ac5c82e0094a29edcf12254dbba420beca8326f5db9ed20866d36203172cb0afb5", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 36, + "name": "F2Pool", + "slug": "f2pool" + }, + "matchRate": 99.96, + "expectedFees": 12610218, + "expectedWeight": 3991619, + "similarity": 0.9361101713526737 + } + }, + { + "id": "00000000000000000001ab25f774a511e1cb09462dab28a36413f7495a65da2f", + "height": 837048, + "version": 536903680, + "timestamp": 1711854388, + "bits": 386097875, + "nonce": 665023114, + "difficulty": 83126997340024.61, + "merkle_root": "ffa1ea239fb6f9ed36f263d68c9739b691788f87f31ea826dbcf9eb64aaf66d7", + "tx_count": 2252, + "size": 1640520, + "weight": 3993366, + "previousblockhash": "000000000000000000021164d81f6059c18066bc0a93b928fdf455d2481c4708", + "mediantime": 1711850338, + "stale": false, + "extras": { + "reward": 636119622, + "coinbaseRaw": "03b8c50c172f5669614254432f4d696e65642062792032373931312f2cfabe6d6d8f86142521fd5c467f05b95e96adf049f4b3716c0cbf20aa9fa92a3a0cefe5761000000000000000108a5eab06f0c0bb91c6f4919eba6e030000000000", + "orphans": [], + "medianFee": 9.02733100888539, + "feeRange": [ + 9, + 9.015025041736227, + 9.015025041736227, + 9.015025041736227, + 11.499042512447339, + 15.024744308808973, + 146.4480408858603 + ], + "totalFees": 11119622, + "avgFee": 4939, + "avgFeeRate": 11, + "utxoSetChange": 908, + "avgTxSize": 728.3000000000001, + "totalInputs": 5995, + "totalOutputs": 6903, + "totalOutputAmt": 93595027274, + "segwitTotalTxs": 2194, + "segwitTotalSize": 1345859, + "segwitTotalWeight": 2814830, + "feePercentiles": null, + "virtualSize": 998341.5, + "coinbaseAddress": "18cBEMRxXHqzWWCxZNtU91F5sbUNKhL5PX", + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 536ffa992491508dca0354e52f32a3a7a679a53a OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "/ViaBTC/Mined by 27911/", + "header": "0080002008471c48d255f4fd28b9930abc6680c159601fd8641102000000000000000000d766af4ab69ecfdb26a81ef3878f7891b639978cd663f236edf9b69f23eaa1ff34d30866d36203178a72a327", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 73, + "name": "ViaBTC", + "slug": "viabtc" + }, + "matchRate": 100, + "expectedFees": 11558843, + "expectedWeight": 3991937, + "similarity": 0.9606822794906695 + } + }, + { + "id": "000000000000000000012a9794fcc2fd81bd0f54df00f7589ea96ff9f3f2ef49", + "height": 837049, + "version": 536944640, + "timestamp": 1711854621, + "bits": 386097875, + "nonce": 2568084255, + "difficulty": 83126997340024.61, + "merkle_root": "5d02722d5d17fa71ec0dd4a3ac72959b84c089e527d14f6f90a6651982b212e2", + "tx_count": 3260, + "size": 2027949, + "weight": 3992970, + "previousblockhash": "00000000000000000001ab25f774a511e1cb09462dab28a36413f7495a65da2f", + "mediantime": 1711850704, + "stale": false, + "extras": { + "reward": 635976151, + "coinbaseRaw": "03b9c50c041ed408662f466f756e6472792055534120506f6f6c202364726f70676f6c642f407c3137c5140b0000000000", + "orphans": [], + "medianFee": 8.965500401011468, + "feeRange": [ + 7.980922098569158, + 8.03076923076923, + 8.753180661577607, + 9.015025041736227, + 9.044925124792014, + 12.042105263157895, + 200.85543199315654 + ], + "totalFees": 10976151, + "avgFee": 3367, + "avgFeeRate": 10, + "utxoSetChange": -176, + "avgTxSize": 621.98, + "totalInputs": 7230, + "totalOutputs": 7054, + "totalOutputAmt": 99412242005, + "segwitTotalTxs": 3216, + "segwitTotalSize": 1992038, + "segwitTotalWeight": 3849434, + "feePercentiles": null, + "virtualSize": 998242.5, + "coinbaseAddress": "bc1qxhmdufsvnuaaaer4ynz88fspdsxq2h9e9cetdj", + "coinbaseSignature": "OP_0 OP_PUSHBYTES_20 35f6de260c9f3bdee47524c473a6016c0c055cb9", + "coinbaseSignatureAscii": "/Foundry USA Pool #dropgold/", + "header": "002001202fda655a49f71364a328ab2d4609cbe111a574f725ab01000000000000000000e212b2821965a6906f4fd127e589c0849b9572aca3d40dec71fa175d2d72025d1dd40866d36203171fdb1199", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 111, + "name": "Foundry USA", + "slug": "foundryusa" + }, + "matchRate": 100, + "expectedFees": 11047051, + "expectedWeight": 3991840, + "similarity": 0.9522324867980367 + } + }, + { + "id": "00000000000000000000e7d533924ecd9e2096bdf261ae3daf3f04861150c5a8", + "height": 837050, + "version": 548552704, + "timestamp": 1711854927, + "bits": 386097875, + "nonce": 1647633217, + "difficulty": 83126997340024.61, + "merkle_root": "4b7d3685fcdefae2a34359f1b3a4181969f7d84061fca9e5b185a6a81c4cd0d1", + "tx_count": 2659, + "size": 2137871, + "weight": 3992678, + "previousblockhash": "000000000000000000012a9794fcc2fd81bd0f54df00f7589ea96ff9f3f2ef49", + "mediantime": 1711853470, + "stale": false, + "extras": { + "reward": 635929414, + "coinbaseRaw": "03bac50c044fd508662f466f756e6472792055534120506f6f6c202364726f70676f6c642f4344be31b3818e6d00000000", + "orphans": [], + "medianFee": 8.971294962196815, + "feeRange": [ + 7.12430426716141, + 7.9810874704491725, + 8.013355592654424, + 8.01920768307323, + 10.047908843713582, + 17.270506108202444, + 261.78010471204186 + ], + "totalFees": 10929414, + "avgFee": 4111, + "avgFeeRate": 10, + "utxoSetChange": 591, + "avgTxSize": 803.9, + "totalInputs": 6276, + "totalOutputs": 6867, + "totalOutputAmt": 89075789136, + "segwitTotalTxs": 2607, + "segwitTotalSize": 2082733, + "segwitTotalWeight": 3772234, + "feePercentiles": null, + "virtualSize": 998169.5, + "coinbaseAddress": "bc1qxhmdufsvnuaaaer4ynz88fspdsxq2h9e9cetdj", + "coinbaseSignature": "OP_0 OP_PUSHBYTES_20 35f6de260c9f3bdee47524c473a6016c0c055cb9", + "coinbaseSignatureAscii": "/Foundry USA Pool #dropgold/", + "header": "0040b22049eff2f3f96fa99e58f700df540fbd81fdc2fc94972a01000000000000000000d1d04c1ca8a685b1e5a9fc6140d8f7691918a4b3f15943a3e2fadefc85367d4b4fd50866d362031741e33462", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 111, + "name": "Foundry USA", + "slug": "foundryusa" + }, + "matchRate": 100, + "expectedFees": 11074070, + "expectedWeight": 3991755, + "similarity": 0.9867917444269922 + } + } + ], + "conversions": { + "time": 1711854906, + "USD": 69880, + "EUR": 64823, + "GBP": 55198, + "CAD": 95154, + "CHF": 63041, + "AUD": 107374, + "JPY": 10601000 + }, + "backendInfo": { + "hostname": "node205.tk7.mempool.space", + "version": "3.0.0-dev", + "gitCommit": "abbc8a134", + "lightning": false + }, "da": { - "progressPercent": 78.02579365079364, - "difficultyChange": -1.3182475819161987, - "estimatedRetargetDate": 1649197503643.8958, - "remainingBlocks": 443, - "remainingTime": 1647550222725.0728, - "previousRetarget": -1.490392872877445, - "nextRetargetHeight": 727776, - "timeAvg": 607909.4854914972, - "timeOffset": 0 + "progressPercent": 20.33730158730159, + "difficultyChange": 5.914788959658535, + "estimatedRetargetDate": 1712766704114, + "remainingBlocks": 1606, + "remainingTime": 911596114, + "previousRetarget": -0.9778871328980188, + "previousTime": 1711619463, + "nextRetargetHeight": 838656, + "timeAvg": 574743, + "adjustedTimeAvg": 567619, + "timeOffset": 0, + "expectedBlocks": 392.7416666666667 } -} + } \ No newline at end of file diff --git a/frontend/cypress/support/websocket.ts b/frontend/cypress/support/websocket.ts index 4a411eeb2..1356ccc76 100644 --- a/frontend/cypress/support/websocket.ts +++ b/frontend/cypress/support/websocket.ts @@ -38,7 +38,13 @@ export const mockWebSocket = () => { win.mockServer = server; win.mockServer.on('connection', (socket) => { win.mockSocket = socket; - win.mockSocket.send('{"action":"init"}'); + win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}'); + cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'ascii').then((fixture) => { + win.mockSocket.send(JSON.stringify(fixture)); + }); + cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'ascii').then((fixture) => { + win.mockSocket.send(JSON.stringify(fixture)); + }); }); win.mockServer.on('message', (message) => { @@ -75,8 +81,6 @@ export const emitMempoolInfo = ({ switch (params.command) { case "init": { - win.mockSocket.send('{"action":"init"}'); - win.mockSocket.send('{"action":"want","data":["blocks","stats","mempool-blocks","live-2h-chart"]}'); win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}'); cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'ascii').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); From 7367991df12c77b4c692312c3db645405d926759 Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 05:40:51 +0000 Subject: [PATCH 19/28] Don't exclude accelerated txs from fee graph & fee statistics --- backend/src/api/mempool-blocks.ts | 2 +- .../fee-distribution-graph/fee-distribution-graph.component.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 1132663f7..020cbb1cc 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -478,7 +478,7 @@ class MempoolBlocks { private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, feeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions { if (!feeStats) { - feeStats = Common.calcEffectiveFeeStatistics(transactions.filter(tx => !tx.acceleration)); + feeStats = Common.calcEffectiveFeeStatistics(transactions); } return { blockSize: totalSize, diff --git a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts index df78cf34d..d7a63710f 100644 --- a/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts +++ b/frontend/src/app/components/fee-distribution-graph/fee-distribution-graph.component.ts @@ -66,7 +66,7 @@ export class FeeDistributionGraphComponent implements OnInit, OnChanges, OnDestr return; } const samples = []; - const txs = this.transactions.filter(tx => !tx.acc).map(tx => { return { vsize: tx.vsize, rate: tx.rate || (tx.fee / tx.vsize) }; }).sort((a, b) => { return b.rate - a.rate; }); + const txs = this.transactions.map(tx => { return { vsize: tx.vsize, rate: tx.rate || (tx.fee / tx.vsize) }; }).sort((a, b) => { return b.rate - a.rate; }); const maxBlockVSize = this.stateService.env.BLOCK_WEIGHT_UNITS / 4; const sampleInterval = maxBlockVSize / this.numSamples; let cumVSize = 0; From c5300c950b92b68540ab37106f91817285eb35cf Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 07:55:43 +0000 Subject: [PATCH 20/28] Add track-txs websocket subscription --- backend/src/api/websocket-handler.ts | 121 ++++++++++++++++++++++++++- backend/src/mempool.interfaces.ts | 16 ++++ 2 files changed, 135 insertions(+), 2 deletions(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 63e7f383c..41202e0fa 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -2,7 +2,7 @@ import logger from '../logger'; import * as WebSocket from 'ws'; import { BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse, - OptimizedStatistic, ILoadingIndicators, GbtCandidates, + OptimizedStatistic, ILoadingIndicators, GbtCandidates, TxTrackingInfo, } from '../mempool.interfaces'; import blocks from './blocks'; import memPool from './mempool'; @@ -209,6 +209,52 @@ class WebsocketHandler { } } + if (parsedMessage && parsedMessage['track-txs']) { + const txids: string[] = []; + if (Array.isArray(parsedMessage['track-txs'])) { + for (const txid of parsedMessage['track-txs']) { + if (/^[a-fA-F0-9]{64}$/.test(txid)) { + txids.push(txid); + } + } + } + + const txs: { [txid: string]: TxTrackingInfo } = {}; + for (const txid of txids) { + const txInfo: TxTrackingInfo = { + confirmed: true, + }; + const rbfCacheTxid = rbfCache.getReplacedBy(txid); + if (rbfCacheTxid) { + txInfo.replacedBy = rbfCacheTxid; + txInfo.confirmed = false; + } + const tx = memPool.getMempool()[txid]; + if (tx && tx.position) { + txInfo.position = { + ...tx.position + }; + if (tx.acceleration) { + txInfo.accelerated = tx.acceleration; + } + } + if (tx) { + txInfo.confirmed = false; + } + txs[txid] = txInfo; + } + + if (txids.length) { + client['track-txs'] = txids; + } else { + client['track-txs'] = null; + } + + if (Object.keys(txs).length) { + response['tracked-txs'] = JSON.stringify(txs); + } + } + if (parsedMessage && parsedMessage['track-address']) { const validAddress = this.testAddress(parsedMessage['track-address']); if (validAddress) { @@ -517,6 +563,11 @@ class WebsocketHandler { if (client['track-tx']) { trackedTxs.add(client['track-tx']); } + if (client['track-txs']) { + for (const txid of client['track-txs']) { + trackedTxs.add(client['track-tx']); + } + } }); if (trackedTxs.size > 0) { for (const tx of newTransactions) { @@ -713,6 +764,46 @@ class WebsocketHandler { } } + if (client['track-txs']) { + const txids = client['track-txs']; + const txs: { [txid: string]: TxTrackingInfo } = {}; + for (const txid of txids) { + const txInfo: TxTrackingInfo = {}; + const outspends = outspendCache[txid]; + if (outspends && Object.keys(outspends).length) { + txInfo.utxoSpent = outspends; + } + const replacedBy = rbfChanges.map[txid] ? rbfCache.getReplacedBy(txid) : false; + if (replacedBy) { + txInfo.replacedBy = replacedBy; + } + const mempoolTx = newMempool[txid]; + if (mempoolTx && mempoolTx.position) { + txInfo.position = { + ...mempoolTx.position, + accelerated: mempoolTx.acceleration || undefined, + }; + if (!mempoolTx.cpfpChecked) { + calculateCpfp(mempoolTx, newMempool); + } + if (mempoolTx.cpfpDirty) { + txInfo.cpfp = { + ancestors: mempoolTx.ancestors, + bestDescendant: mempoolTx.bestDescendant || null, + descendants: mempoolTx.descendants || null, + effectiveFeePerVsize: mempoolTx.effectiveFeePerVsize || null, + sigops: mempoolTx.sigops, + adjustedVsize: mempoolTx.adjustedVsize, + }; + } + } + txs[txid] = txInfo; + } + if (Object.keys(txs).length) { + response['tracked-txs'] = JSON.stringify(txs); + } + } + if (client['track-mempool-block'] >= 0 && memPool.isInSync()) { const index = client['track-mempool-block']; if (mBlockDeltas[index]) { @@ -931,6 +1022,28 @@ class WebsocketHandler { } } + if (client['track-txs']) { + const txs: { [txid: string]: TxTrackingInfo } = {}; + for (const txid of client['track-txs']) { + if (confirmedTxids[txid]) { + txs[txid] = { confirmed: true }; + } else { + const mempoolTx = _memPool[txid]; + if (mempoolTx && mempoolTx.position) { + txs[txid] = { + position: { + ...mempoolTx.position, + }, + accelerated: mempoolTx.acceleration || undefined, + }; + } + } + } + if (Object.keys(txs).length) { + response['tracked-txs'] = JSON.stringify(txs); + } + } + if (client['track-address']) { const foundTransactions: TransactionExtended[] = Array.from(addressCache[client['track-address']]?.values() || []); @@ -1126,6 +1239,7 @@ class WebsocketHandler { private printLogs(): void { if (this.wss) { let numTxSubs = 0; + let numTxsSubs = 0; let numProjectedSubs = 0; let numRbfSubs = 0; @@ -1133,6 +1247,9 @@ class WebsocketHandler { if (client['track-tx']) { numTxSubs++; } + if (client['track-txs']) { + numTxsSubs++; + } if (client['track-mempool-block'] != null && client['track-mempool-block'] >= 0) { numProjectedSubs++; } @@ -1145,7 +1262,7 @@ class WebsocketHandler { const diff = count - this.numClients; this.numClients = count; logger.debug(`${count} websocket clients | ${this.numConnected} connected | ${this.numDisconnected} disconnected | (${diff >= 0 ? '+' : ''}${diff})`); - logger.debug(`websocket subscriptions: track-tx: ${numTxSubs}, track-mempool-block: ${numProjectedSubs} track-rbf: ${numRbfSubs}`); + logger.debug(`websocket subscriptions: track-tx: ${numTxSubs}, track-txs: ${numTxsSubs}, track-mempool-block: ${numProjectedSubs} track-rbf: ${numRbfSubs}`); this.numConnected = 0; this.numDisconnected = 0; } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index a12351fe3..5b31f13a6 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -411,6 +411,22 @@ export interface OptimizedStatistic { vsizes: number[]; } +export interface TxTrackingInfo { + replacedBy?: string, + position?: { block: number, vsize: number, accelerated?: boolean }, + cpfp?: { + ancestors?: Ancestor[], + bestDescendant?: Ancestor | null, + descendants?: Ancestor[] | null, + effectiveFeePerVsize?: number | null, + sigops: number, + adjustedVsize: number, + }, + utxoSpent?: { [vout: number]: { vin: number, txid: string } }, + accelerated?: boolean, + confirmed?: boolean +} + export interface WebsocketResponse { action: string; data: string[]; From ac8fdd6405bf416f3e847d5136b883f5dd56f20e Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 08:06:01 +0000 Subject: [PATCH 21/28] Clear cpfp dirty status --- backend/src/api/mempool-blocks.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/src/api/mempool-blocks.ts b/backend/src/api/mempool-blocks.ts index 020cbb1cc..687fdbef4 100644 --- a/backend/src/api/mempool-blocks.ts +++ b/backend/src/api/mempool-blocks.ts @@ -334,6 +334,11 @@ class MempoolBlocks { } private processBlockTemplates(mempool: { [txid: string]: MempoolTransactionExtended }, blocks: string[][], blockWeights: number[] | null, rates: [string, number][], clusters: string[][], candidates: GbtCandidates | undefined, accelerations, accelerationPool, saveResults): MempoolBlockWithTransactions[] { + for (const txid of Object.keys(candidates?.txs ?? mempool)) { + if (txid in mempool) { + mempool[txid].cpfpDirty = false; + } + } for (const [txid, rate] of rates) { if (txid in mempool) { mempool[txid].cpfpDirty = (rate !== mempool[txid].effectiveFeePerVsize); From 9270298374364a307c195e6c8b4c7a3f33dbe7da Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 08:53:09 +0000 Subject: [PATCH 22/28] Fix next block subsidy calculation --- .../difficulty/difficulty.component.html | 2 +- .../components/difficulty/difficulty.component.ts | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/components/difficulty/difficulty.component.html b/frontend/src/app/components/difficulty/difficulty.component.html index d1de5f076..ff31d4f57 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.html +++ b/frontend/src/app/components/difficulty/difficulty.component.html @@ -97,7 +97,7 @@
- +
New subsidy diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts index 13f61dc5e..a58250653 100644 --- a/frontend/src/app/components/difficulty/difficulty.component.ts +++ b/frontend/src/app/components/difficulty/difficulty.component.ts @@ -62,6 +62,7 @@ export class DifficultyComponent implements OnInit { expectedIndex: number; difference: number; shapes: DiffShape[]; + nextSubsidy: number; tooltipPosition = { x: 0, y: 0 }; hoverSection: DiffShape | void; @@ -106,6 +107,7 @@ export class DifficultyComponent implements OnInit { const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH; const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks); this.now = new Date().getTime(); + this.nextSubsidy = getNextBlockSubsidy(maxHeight); if (blocksUntilHalving < da.remainingBlocks && !this.userSelectedMode) { this.mode = 'halving'; @@ -233,3 +235,16 @@ export class DifficultyComponent implements OnInit { this.hoverSection = null; } } + +function getNextBlockSubsidy(height: number): number { + const halvings = Math.floor(height / 210_000) + 1; + // Force block reward to zero when right shift is undefined. + if (halvings >= 64) { + return 0; + } + + let subsidy = BigInt(50 * 100_000_000); + // Subsidy is cut in half every 210,000 blocks which will occur approximately every 4 years. + subsidy >>= BigInt(halvings); + return Number(subsidy); +} \ No newline at end of file From 7deee5d5f2589ca738086425bfdb450dfef3b0bf Mon Sep 17 00:00:00 2001 From: Mononaut Date: Sun, 31 Mar 2024 09:45:26 +0000 Subject: [PATCH 23/28] Fix outspend tracking typo --- backend/src/api/websocket-handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 41202e0fa..a20088128 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -565,7 +565,7 @@ class WebsocketHandler { } if (client['track-txs']) { for (const txid of client['track-txs']) { - trackedTxs.add(client['track-tx']); + trackedTxs.add(txid); } } }); From df72829fd25238e101fa707d1c814c2ef19bc6ca Mon Sep 17 00:00:00 2001 From: Mononaut Date: Mon, 1 Apr 2024 04:00:29 +0000 Subject: [PATCH 24/28] Add backend mode to backend-info --- backend/src/api/backend-info.ts | 9 +++++---- backend/src/mempool.interfaces.ts | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/backend/src/api/backend-info.ts b/backend/src/api/backend-info.ts index fc3181524..d4500a837 100644 --- a/backend/src/api/backend-info.ts +++ b/backend/src/api/backend-info.ts @@ -9,8 +9,8 @@ class BackendInfo { constructor() { // This file is created by ./fetch-version.ts during building - const versionFile = path.join(__dirname, 'version.json') - var versionInfo; + const versionFile = path.join(__dirname, 'version.json'); + let versionInfo; if (fs.existsSync(versionFile)) { versionInfo = JSON.parse(fs.readFileSync(versionFile).toString()); } else { @@ -24,7 +24,8 @@ class BackendInfo { hostname: os.hostname(), version: versionInfo.version, gitCommit: versionInfo.gitCommit, - lightning: config.LIGHTNING.ENABLED + lightning: config.LIGHTNING.ENABLED, + backend: config.MEMPOOL.BACKEND, }; } @@ -32,7 +33,7 @@ class BackendInfo { return this.backendInfo; } - public getShortCommitHash() { + public getShortCommitHash(): string { return this.backendInfo.gitCommit.slice(0, 7); } } diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index 5b31f13a6..dd23db8ab 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -455,6 +455,7 @@ export interface IBackendInfo { gitCommit: string; version: string; lightning: boolean; + backend: 'esplora' | 'electrum' | 'none'; } export interface IDifficultyAdjustment { From cc63f207083f91874afcb6e88f9413009967bbec Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 1 Apr 2024 16:00:35 +0900 Subject: [PATCH 25/28] ops: Upgrade NodeJS to v20.12.0 in prod installer --- production/install | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/production/install b/production/install index c89bf004f..9ecfb7e2b 100755 --- a/production/install +++ b/production/install @@ -1047,9 +1047,9 @@ osSudo "${ROOT_USER}" crontab -u "${MEMPOOL_USER}" "${MEMPOOL_HOME}/${MEMPOOL_RE echo "[*] Installing nvm.sh from GitHub" osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh' -echo "[*] Building NodeJS v20.5.1 via nvm.sh" -osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.7.0 --shared-zlib' -osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm alias default 20.7.0' +echo "[*] Building NodeJS via nvm.sh" +osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.12.0 --shared-zlib' +osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm alias default 20.12.0' #################### # Tor installation # From e3f7f08fb49de9f42f4e38413eda63d7a6dec64c Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 1 Apr 2024 16:00:59 +0900 Subject: [PATCH 26/28] ops: Follow redirects to download AppleColorEmoji.ttf --- production/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/install b/production/install index 9ecfb7e2b..ee56fc64c 100755 --- a/production/install +++ b/production/install @@ -1449,7 +1449,7 @@ if [ "${UNFURL_INSTALL}" = ON ];then echo 'nvidia_xorg_enable="YES"' >> /etc/rc.conf echo "[*] Installing color emoji" - osSudo "${ROOT_USER}" curl "https://github.com/samuelngs/apple-emoji-linux/releases/download/ios-15.4/AppleColorEmoji.ttf" -o /usr/local/share/fonts/TTF/AppleColorEmoji.ttf + osSudo "${ROOT_USER}" curl -sSL "https://github.com/samuelngs/apple-emoji-linux/releases/download/ios-15.4/AppleColorEmoji.ttf" -o /usr/local/share/fonts/TTF/AppleColorEmoji.ttf cat > /usr/local/etc/fonts/conf.d/01-emoji.conf < From 56b9715fc510bac1166c040aacefd71c73124ca5 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 1 Apr 2024 16:05:31 +0900 Subject: [PATCH 27/28] ops: Upgrade NodeJS to v20.12 for Debian in prod installer --- production/install | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/production/install b/production/install index ee56fc64c..30754863c 100755 --- a/production/install +++ b/production/install @@ -1491,7 +1491,7 @@ EOF osSudo "${UNFURL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh' echo "[*] Building NodeJS via nvm.sh" - osSudo "${UNFURL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.7.0 --shared-zlib' + osSudo "${UNFURL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.12.0 --shared-zlib' ;; esac From 964be2a6df6ca1ae9e9fe53fe62ee7ffa913e0d7 Mon Sep 17 00:00:00 2001 From: wiz Date: Mon, 1 Apr 2024 16:53:31 +0900 Subject: [PATCH 28/28] Don't use PATH in sync-assets.js --- frontend/sync-assets.js | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index 2ff642e27..4164efff6 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -32,19 +32,19 @@ const githubSecret = process.env.GITHUB_TOKEN; const CONFIG_FILE_NAME = 'mempool-frontend-config.json'; let configContent = {}; -var PATH; +var ASSETS_PATH; if (process.argv[2]) { - PATH = process.argv[2]; - PATH += PATH.endsWith("/") ? "" : "/" - PATH = path.resolve(path.normalize(PATH)); - console.log(`[sync-assets] using PATH ${PATH}`); - if (!fs.existsSync(PATH)){ - console.log(`${LOG_TAG} ${PATH} does not exist, creating`); - fs.mkdirSync(PATH, { recursive: true }); + ASSETS_PATH = process.argv[2]; + ASSETS_PATH += ASSETS_PATH.endsWith("/") ? "" : "/" + ASSETS_PATH = path.resolve(path.normalize(ASSETS_PATH)); + console.log(`[sync-assets] using ASSETS_PATH ${ASSETS_PATH}`); + if (!fs.existsSync(ASSETS_PATH)){ + console.log(`${LOG_TAG} ${ASSETS_PATH} does not exist, creating`); + fs.mkdirSync(ASSETS_PATH, { recursive: true }); } } -if (!PATH) { +if (!ASSETS_PATH) { throw new Error('Resource path argument is not set'); } @@ -125,7 +125,8 @@ function downloadMiningPoolLogos$() { if (verbose) { console.log(`${LOG_TAG} Processing ${poolLogo.name}`); } - const filePath = `${PATH}/mining-pools/${poolLogo.name}`; + console.log(`${ASSETS_PATH}/mining-pools/${poolLogo.name}`); + const filePath = `${ASSETS_PATH}/mining-pools/${poolLogo.name}`; if (fs.existsSync(filePath)) { const localHash = getLocalHash(filePath); if (verbose) { @@ -152,7 +153,7 @@ function downloadMiningPoolLogos$() { } } else { console.log(`${LOG_TAG} \t\t${poolLogo.name} is missing, downloading...`); - const miningPoolsDir = `${PATH}/mining-pools/`; + const miningPoolsDir = `${ASSETS_PATH}/mining-pools/`; if (!fs.existsSync(miningPoolsDir)){ fs.mkdirSync(miningPoolsDir, { recursive: true }); } @@ -219,7 +220,7 @@ function downloadPromoVideoSubtiles$() { if (verbose) { console.log(`${LOG_TAG} Processing ${language.name}`); } - const filePath = `${PATH}/promo-video/${language.name}`; + const filePath = `${ASSETS_PATH}/promo-video/${language.name}`; if (fs.existsSync(filePath)) { if (verbose) { console.log(`${LOG_TAG} \t${language.name} remote promo video hash ${language.sha}`); @@ -245,7 +246,7 @@ function downloadPromoVideoSubtiles$() { } } else { console.log(`${LOG_TAG} \t\t${language.name} is missing, downloading`); - const promoVideosDir = `${PATH}/promo-video/`; + const promoVideosDir = `${ASSETS_PATH}/promo-video/`; if (!fs.existsSync(promoVideosDir)){ fs.mkdirSync(promoVideosDir, { recursive: true }); } @@ -313,7 +314,7 @@ function downloadPromoVideo$() { if (item.name !== 'promo.mp4') { continue; } - const filePath = `${PATH}/promo-video/mempool-promo.mp4`; + const filePath = `${ASSETS_PATH}/promo-video/mempool-promo.mp4`; if (fs.existsSync(filePath)) { const localHash = getLocalHash(filePath); @@ -373,16 +374,16 @@ if (configContent.BASE_MODULE && configContent.BASE_MODULE === 'liquid') { const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json'; console.log(`${LOG_TAG} Downloading assets`); - download(`${PATH}/assets.json`, assetsJsonUrl); + download(`${ASSETS_PATH}/assets.json`, assetsJsonUrl); console.log(`${LOG_TAG} Downloading assets minimal`); - download(`${PATH}/assets.minimal.json`, assetsMinimalJsonUrl); + download(`${ASSETS_PATH}/assets.minimal.json`, assetsMinimalJsonUrl); console.log(`${LOG_TAG} Downloading testnet assets`); - download(`${PATH}/assets-testnet.json`, testnetAssetsJsonUrl); + download(`${ASSETS_PATH}/assets-testnet.json`, testnetAssetsJsonUrl); console.log(`${LOG_TAG} Downloading testnet assets minimal`); - download(`${PATH}/assets-testnet.minimal.json`, testnetAssetsMinimalJsonUrl); + download(`${ASSETS_PATH}/assets-testnet.minimal.json`, testnetAssetsMinimalJsonUrl); } else { if (verbose) { console.log(`${LOG_TAG} BASE_MODULE is not set to Liquid (currently ${configContent.BASE_MODULE}), skipping downloading assets`);