Merge branch 'master' into mononaut/wallet-balance
This commit is contained in:
commit
57a878403e
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -9,7 +9,7 @@ jobs:
|
|||||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ["18", "20"]
|
node: ["20", "21"]
|
||||||
flavor: ["dev", "prod"]
|
flavor: ["dev", "prod"]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
@ -160,7 +160,7 @@ jobs:
|
|||||||
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')"
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
node: ["18", "20"]
|
node: ["20", "21"]
|
||||||
flavor: ["dev", "prod"]
|
flavor: ["dev", "prod"]
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
|
|||||||
19
.github/workflows/get_backend_block_height.yml
vendored
Normal file
19
.github/workflows/get_backend_block_height.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: 'Check if servers are in sync'
|
||||||
|
|
||||||
|
on: [workflow_dispatch]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
print-backend-sha:
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
name: Get block height
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: repo
|
||||||
|
|
||||||
|
- name: Run script
|
||||||
|
working-directory: repo
|
||||||
|
run: |
|
||||||
|
chmod +x ./scripts/get_block_tip_height.sh
|
||||||
|
sh ./scripts/get_block_tip_height.sh
|
||||||
85
Cargo.lock
generated
85
Cargo.lock
generated
@ -57,9 +57,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "1586fa608b1dab41f667475b4a41faec5ba680aee428bfa5de4ea520fdc6e901"
|
checksum = "1586fa608b1dab41f667475b4a41faec5ba680aee428bfa5de4ea520fdc6e901"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.20",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "equivalent"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gbt"
|
name = "gbt"
|
||||||
version = "1.0.0"
|
version = "1.0.0"
|
||||||
@ -71,15 +77,15 @@ dependencies = [
|
|||||||
"napi-derive",
|
"napi-derive",
|
||||||
"priority-queue",
|
"priority-queue",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-log",
|
"tracing-log 0.2.0",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.12.3"
|
version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
@ -92,11 +98,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "2.2.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -114,12 +120,12 @@ checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.7.4"
|
version = "0.8.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
|
checksum = "2caa5afb8bf9f3a2652760ce7d4f62d21c4d5a423e68466fca30df82f2330164"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"winapi",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -145,9 +151,9 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi"
|
name = "napi"
|
||||||
version = "2.13.2"
|
version = "2.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ede2d12cd6fce44da537a4be1f5510c73be2506c2e32dfaaafd1f36968f3a0e"
|
checksum = "54a63d0570e4c3e0daf7a8d380563610e159f538e20448d6c911337246f40e84"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
"ctor",
|
"ctor",
|
||||||
@ -159,29 +165,29 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi-build"
|
name = "napi-build"
|
||||||
version = "2.0.1"
|
version = "2.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "882a73d9ef23e8dc2ebbffb6a6ae2ef467c0f18ac10711e4cc59c5485d41df0e"
|
checksum = "2f9130fccc5f763cf2069b34a089a18f0d0883c66aceb81f2fad541a3d823c43"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi-derive"
|
name = "napi-derive"
|
||||||
version = "2.13.0"
|
version = "2.16.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "da1c6a8fa84d549aa8708fcd062372bf8ec6e849de39016ab921067d21bde367"
|
checksum = "05bb7c37e3c1dda9312fdbe4a9fc7507fca72288ba154ec093e2d49114e727ce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"napi-derive-backend",
|
"napi-derive-backend",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi-derive-backend"
|
name = "napi-derive-backend"
|
||||||
version = "1.0.52"
|
version = "1.0.62"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "20bbc7c69168d06a848f925ec5f0e0997f98e8c8d4f2cc30157f0da51c009e17"
|
checksum = "f785a8b8d7b83e925f5aa6d2ae3c159d17fe137ac368dc185bef410e7acdaeb4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"convert_case",
|
"convert_case",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@ -189,14 +195,14 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"regex",
|
"regex",
|
||||||
"semver",
|
"semver",
|
||||||
"syn 1.0.109",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "napi-sys"
|
name = "napi-sys"
|
||||||
version = "2.2.3"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "166b5ef52a3ab5575047a9fe8d4a030cdd0f63c96f071cd6907674453b07bae3"
|
checksum = "2503fa6af34dc83fb74888df8b22afe933b58d37daf7d80424b1c60c68196b8b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libloading",
|
"libloading",
|
||||||
]
|
]
|
||||||
@ -223,9 +229,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.18.0"
|
version = "1.19.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "overload"
|
name = "overload"
|
||||||
@ -241,11 +247,12 @@ checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "priority-queue"
|
name = "priority-queue"
|
||||||
version = "1.3.2"
|
version = "2.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fff39edfcaec0d64e8d0da38564fad195d2d51b680940295fcc307366e101e61"
|
checksum = "509354d8a769e8d0b567d6821b84495c60213162761a732d68ce87c964bd347f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
|
"equivalent",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -320,17 +327,6 @@ version = "1.10.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "syn"
|
|
||||||
version = "1.0.109"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.20"
|
version = "2.0.20"
|
||||||
@ -384,7 +380,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.20",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -408,6 +404,17 @@ dependencies = [
|
|||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
@ -423,7 +430,7 @@ dependencies = [
|
|||||||
"thread_local",
|
"thread_local",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
"tracing-log",
|
"tracing-log 0.1.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
"./backend/rust-gbt",
|
"./backend/rust-gbt",
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": false,
|
||||||
"NETWORK": "mainnet",
|
"NETWORK": "mainnet",
|
||||||
"BACKEND": "electrum",
|
"BACKEND": "electrum",
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
|||||||
352
backend/package-lock.json
generated
352
backend/package-lock.json
generated
@ -9,7 +9,7 @@
|
|||||||
"version": "3.0.0-dev",
|
"version": "3.0.0-dev",
|
||||||
"license": "GNU Affero General Public License v3.0",
|
"license": "GNU Affero General Public License v3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.23.2",
|
"@babel/core": "^7.24.0",
|
||||||
"@mempool/electrum-client": "1.1.9",
|
"@mempool/electrum-client": "1.1.9",
|
||||||
"@types/node": "^18.15.3",
|
"@types/node": "^18.15.3",
|
||||||
"axios": "~1.6.1",
|
"axios": "~1.6.1",
|
||||||
@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/core": "^7.23.2",
|
"@babel/core": "^7.24.0",
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
@ -65,12 +65,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.22.13",
|
"version": "7.23.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
|
||||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/highlight": "^7.22.13",
|
"@babel/highlight": "^7.23.4",
|
||||||
"chalk": "^2.4.2"
|
"chalk": "^2.4.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -78,30 +78,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/compat-data": {
|
"node_modules/@babel/compat-data": {
|
||||||
"version": "7.23.2",
|
"version": "7.23.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
|
||||||
"integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==",
|
"integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/core": {
|
"node_modules/@babel/core": {
|
||||||
"version": "7.23.2",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
|
||||||
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
|
"integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.22.13",
|
"@babel/code-frame": "^7.23.5",
|
||||||
"@babel/generator": "^7.23.0",
|
"@babel/generator": "^7.23.6",
|
||||||
"@babel/helper-compilation-targets": "^7.22.15",
|
"@babel/helper-compilation-targets": "^7.23.6",
|
||||||
"@babel/helper-module-transforms": "^7.23.0",
|
"@babel/helper-module-transforms": "^7.23.3",
|
||||||
"@babel/helpers": "^7.23.2",
|
"@babel/helpers": "^7.24.0",
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.24.0",
|
||||||
"@babel/template": "^7.22.15",
|
"@babel/template": "^7.24.0",
|
||||||
"@babel/traverse": "^7.23.2",
|
"@babel/traverse": "^7.24.0",
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.24.0",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
@ -123,12 +123,12 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
"version": "7.23.0",
|
"version": "7.23.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
|
||||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
"integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.23.6",
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
"@jridgewell/gen-mapping": "^0.3.2",
|
||||||
"@jridgewell/trace-mapping": "^0.3.17",
|
"@jridgewell/trace-mapping": "^0.3.17",
|
||||||
"jsesc": "^2.5.1"
|
"jsesc": "^2.5.1"
|
||||||
@ -152,14 +152,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-compilation-targets": {
|
"node_modules/@babel/helper-compilation-targets": {
|
||||||
"version": "7.22.15",
|
"version": "7.23.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
|
||||||
"integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
|
"integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/compat-data": "^7.22.9",
|
"@babel/compat-data": "^7.23.5",
|
||||||
"@babel/helper-validator-option": "^7.22.15",
|
"@babel/helper-validator-option": "^7.23.5",
|
||||||
"browserslist": "^4.21.9",
|
"browserslist": "^4.22.2",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
"semver": "^6.3.1"
|
"semver": "^6.3.1"
|
||||||
},
|
},
|
||||||
@ -214,9 +214,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-module-transforms": {
|
"node_modules/@babel/helper-module-transforms": {
|
||||||
"version": "7.23.0",
|
"version": "7.23.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
|
||||||
"integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
|
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-environment-visitor": "^7.22.20",
|
"@babel/helper-environment-visitor": "^7.22.20",
|
||||||
@ -266,9 +266,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-string-parser": {
|
"node_modules/@babel/helper-string-parser": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -284,32 +284,32 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-option": {
|
"node_modules/@babel/helper-validator-option": {
|
||||||
"version": "7.22.15",
|
"version": "7.23.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
|
||||||
"integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
|
"integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helpers": {
|
"node_modules/@babel/helpers": {
|
||||||
"version": "7.23.2",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz",
|
||||||
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
|
"integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.22.15",
|
"@babel/template": "^7.24.0",
|
||||||
"@babel/traverse": "^7.23.2",
|
"@babel/traverse": "^7.24.0",
|
||||||
"@babel/types": "^7.23.0"
|
"@babel/types": "^7.24.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/highlight": {
|
"node_modules/@babel/highlight": {
|
||||||
"version": "7.22.20",
|
"version": "7.23.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
|
||||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.22.20",
|
"@babel/helper-validator-identifier": "^7.22.20",
|
||||||
@ -321,9 +321,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.23.0",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
|
||||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
@ -510,34 +510,34 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.22.15",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
|
||||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
"integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.22.13",
|
"@babel/code-frame": "^7.23.5",
|
||||||
"@babel/parser": "^7.22.15",
|
"@babel/parser": "^7.24.0",
|
||||||
"@babel/types": "^7.22.15"
|
"@babel/types": "^7.24.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/traverse": {
|
"node_modules/@babel/traverse": {
|
||||||
"version": "7.23.2",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz",
|
||||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
"integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.22.13",
|
"@babel/code-frame": "^7.23.5",
|
||||||
"@babel/generator": "^7.23.0",
|
"@babel/generator": "^7.23.6",
|
||||||
"@babel/helper-environment-visitor": "^7.22.20",
|
"@babel/helper-environment-visitor": "^7.22.20",
|
||||||
"@babel/helper-function-name": "^7.23.0",
|
"@babel/helper-function-name": "^7.23.0",
|
||||||
"@babel/helper-hoist-variables": "^7.22.5",
|
"@babel/helper-hoist-variables": "^7.22.5",
|
||||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.24.0",
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.24.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.3.1",
|
||||||
"globals": "^11.1.0"
|
"globals": "^11.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -545,12 +545,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.23.0",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
|
||||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.22.5",
|
"@babel/helper-string-parser": "^7.23.4",
|
||||||
"@babel/helper-validator-identifier": "^7.22.20",
|
"@babel/helper-validator-identifier": "^7.22.20",
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
},
|
},
|
||||||
@ -1500,9 +1500,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/cli": {
|
"node_modules/@napi-rs/cli": {
|
||||||
"version": "2.16.1",
|
"version": "2.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz",
|
||||||
"integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==",
|
"integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"napi": "scripts/index.js"
|
"napi": "scripts/index.js"
|
||||||
},
|
},
|
||||||
@ -2594,9 +2594,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/browserslist": {
|
"node_modules/browserslist": {
|
||||||
"version": "4.22.1",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
||||||
"integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
|
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -2613,9 +2613,9 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"caniuse-lite": "^1.0.30001541",
|
"caniuse-lite": "^1.0.30001587",
|
||||||
"electron-to-chromium": "^1.4.535",
|
"electron-to-chromium": "^1.4.668",
|
||||||
"node-releases": "^2.0.13",
|
"node-releases": "^2.0.14",
|
||||||
"update-browserslist-db": "^1.0.13"
|
"update-browserslist-db": "^1.0.13"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -2708,9 +2708,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001547",
|
"version": "1.0.30001591",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz",
|
||||||
"integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==",
|
"integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@ -3031,9 +3031,9 @@
|
|||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
"version": "1.4.551",
|
"version": "1.4.686",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.686.tgz",
|
||||||
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==",
|
"integrity": "sha512-3avY1B+vUzNxEgkBDpKOP8WarvUAEwpRaiCL0He5OKWEFxzaOFiq4WoZEZe7qh0ReS7DiWoHMnYoQCKxNZNzSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/emittery": {
|
"node_modules/emittery": {
|
||||||
@ -6192,9 +6192,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.13",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/normalize-path": {
|
"node_modules/normalize-path": {
|
||||||
@ -7670,7 +7670,7 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@napi-rs/cli": "2.16.1"
|
"@napi-rs/cli": "2.18.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
@ -7695,37 +7695,37 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.22.13",
|
"version": "7.23.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
|
||||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
"integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/highlight": "^7.22.13",
|
"@babel/highlight": "^7.23.4",
|
||||||
"chalk": "^2.4.2"
|
"chalk": "^2.4.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/compat-data": {
|
"@babel/compat-data": {
|
||||||
"version": "7.23.2",
|
"version": "7.23.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz",
|
||||||
"integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==",
|
"integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/core": {
|
"@babel/core": {
|
||||||
"version": "7.23.2",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.0.tgz",
|
||||||
"integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==",
|
"integrity": "sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@ampproject/remapping": "^2.2.0",
|
"@ampproject/remapping": "^2.2.0",
|
||||||
"@babel/code-frame": "^7.22.13",
|
"@babel/code-frame": "^7.23.5",
|
||||||
"@babel/generator": "^7.23.0",
|
"@babel/generator": "^7.23.6",
|
||||||
"@babel/helper-compilation-targets": "^7.22.15",
|
"@babel/helper-compilation-targets": "^7.23.6",
|
||||||
"@babel/helper-module-transforms": "^7.23.0",
|
"@babel/helper-module-transforms": "^7.23.3",
|
||||||
"@babel/helpers": "^7.23.2",
|
"@babel/helpers": "^7.24.0",
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.24.0",
|
||||||
"@babel/template": "^7.22.15",
|
"@babel/template": "^7.24.0",
|
||||||
"@babel/traverse": "^7.23.2",
|
"@babel/traverse": "^7.24.0",
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.24.0",
|
||||||
"convert-source-map": "^2.0.0",
|
"convert-source-map": "^2.0.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"gensync": "^1.0.0-beta.2",
|
"gensync": "^1.0.0-beta.2",
|
||||||
@ -7742,12 +7742,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/generator": {
|
"@babel/generator": {
|
||||||
"version": "7.23.0",
|
"version": "7.23.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz",
|
||||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
"integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.23.6",
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
"@jridgewell/gen-mapping": "^0.3.2",
|
||||||
"@jridgewell/trace-mapping": "^0.3.17",
|
"@jridgewell/trace-mapping": "^0.3.17",
|
||||||
"jsesc": "^2.5.1"
|
"jsesc": "^2.5.1"
|
||||||
@ -7767,14 +7767,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-compilation-targets": {
|
"@babel/helper-compilation-targets": {
|
||||||
"version": "7.22.15",
|
"version": "7.23.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz",
|
||||||
"integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
|
"integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/compat-data": "^7.22.9",
|
"@babel/compat-data": "^7.23.5",
|
||||||
"@babel/helper-validator-option": "^7.22.15",
|
"@babel/helper-validator-option": "^7.23.5",
|
||||||
"browserslist": "^4.21.9",
|
"browserslist": "^4.22.2",
|
||||||
"lru-cache": "^5.1.1",
|
"lru-cache": "^5.1.1",
|
||||||
"semver": "^6.3.1"
|
"semver": "^6.3.1"
|
||||||
}
|
}
|
||||||
@ -7814,9 +7814,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-module-transforms": {
|
"@babel/helper-module-transforms": {
|
||||||
"version": "7.23.0",
|
"version": "7.23.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz",
|
||||||
"integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==",
|
"integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-environment-visitor": "^7.22.20",
|
"@babel/helper-environment-visitor": "^7.22.20",
|
||||||
@ -7851,9 +7851,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-string-parser": {
|
"@babel/helper-string-parser": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz",
|
||||||
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==",
|
"integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-identifier": {
|
"@babel/helper-validator-identifier": {
|
||||||
@ -7863,26 +7863,26 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-option": {
|
"@babel/helper-validator-option": {
|
||||||
"version": "7.22.15",
|
"version": "7.23.5",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz",
|
||||||
"integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
|
"integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/helpers": {
|
"@babel/helpers": {
|
||||||
"version": "7.23.2",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.0.tgz",
|
||||||
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==",
|
"integrity": "sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/template": "^7.22.15",
|
"@babel/template": "^7.24.0",
|
||||||
"@babel/traverse": "^7.23.2",
|
"@babel/traverse": "^7.24.0",
|
||||||
"@babel/types": "^7.23.0"
|
"@babel/types": "^7.24.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/highlight": {
|
"@babel/highlight": {
|
||||||
"version": "7.22.20",
|
"version": "7.23.4",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
|
||||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
"integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.22.20",
|
"@babel/helper-validator-identifier": "^7.22.20",
|
||||||
@ -7891,9 +7891,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.23.0",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
|
||||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
"integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"@babel/plugin-syntax-async-generators": {
|
"@babel/plugin-syntax-async-generators": {
|
||||||
@ -8023,41 +8023,41 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
"version": "7.22.15",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz",
|
||||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
"integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.22.13",
|
"@babel/code-frame": "^7.23.5",
|
||||||
"@babel/parser": "^7.22.15",
|
"@babel/parser": "^7.24.0",
|
||||||
"@babel/types": "^7.22.15"
|
"@babel/types": "^7.24.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/traverse": {
|
"@babel/traverse": {
|
||||||
"version": "7.23.2",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.0.tgz",
|
||||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
"integrity": "sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.22.13",
|
"@babel/code-frame": "^7.23.5",
|
||||||
"@babel/generator": "^7.23.0",
|
"@babel/generator": "^7.23.6",
|
||||||
"@babel/helper-environment-visitor": "^7.22.20",
|
"@babel/helper-environment-visitor": "^7.22.20",
|
||||||
"@babel/helper-function-name": "^7.23.0",
|
"@babel/helper-function-name": "^7.23.0",
|
||||||
"@babel/helper-hoist-variables": "^7.22.5",
|
"@babel/helper-hoist-variables": "^7.22.5",
|
||||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||||
"@babel/parser": "^7.23.0",
|
"@babel/parser": "^7.24.0",
|
||||||
"@babel/types": "^7.23.0",
|
"@babel/types": "^7.24.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.3.1",
|
||||||
"globals": "^11.1.0"
|
"globals": "^11.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.23.0",
|
"version": "7.24.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
|
||||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
"integrity": "sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-string-parser": "^7.22.5",
|
"@babel/helper-string-parser": "^7.23.4",
|
||||||
"@babel/helper-validator-identifier": "^7.22.20",
|
"@babel/helper-validator-identifier": "^7.22.20",
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
@ -8775,9 +8775,9 @@
|
|||||||
"integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ=="
|
"integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ=="
|
||||||
},
|
},
|
||||||
"@napi-rs/cli": {
|
"@napi-rs/cli": {
|
||||||
"version": "2.16.1",
|
"version": "2.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz",
|
||||||
"integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA=="
|
"integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA=="
|
||||||
},
|
},
|
||||||
"@noble/hashes": {
|
"@noble/hashes": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
@ -9633,14 +9633,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"version": "4.22.1",
|
"version": "4.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz",
|
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
|
||||||
"integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==",
|
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"caniuse-lite": "^1.0.30001541",
|
"caniuse-lite": "^1.0.30001587",
|
||||||
"electron-to-chromium": "^1.4.535",
|
"electron-to-chromium": "^1.4.668",
|
||||||
"node-releases": "^2.0.13",
|
"node-releases": "^2.0.14",
|
||||||
"update-browserslist-db": "^1.0.13"
|
"update-browserslist-db": "^1.0.13"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -9712,9 +9712,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"caniuse-lite": {
|
"caniuse-lite": {
|
||||||
"version": "1.0.30001547",
|
"version": "1.0.30001591",
|
||||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz",
|
||||||
"integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==",
|
"integrity": "sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
@ -9942,9 +9942,9 @@
|
|||||||
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
|
||||||
},
|
},
|
||||||
"electron-to-chromium": {
|
"electron-to-chromium": {
|
||||||
"version": "1.4.551",
|
"version": "1.4.686",
|
||||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz",
|
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.686.tgz",
|
||||||
"integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==",
|
"integrity": "sha512-3avY1B+vUzNxEgkBDpKOP8WarvUAEwpRaiCL0He5OKWEFxzaOFiq4WoZEZe7qh0ReS7DiWoHMnYoQCKxNZNzSg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"emittery": {
|
"emittery": {
|
||||||
@ -12298,9 +12298,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node-releases": {
|
"node-releases": {
|
||||||
"version": "2.0.13",
|
"version": "2.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||||
"integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==",
|
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"normalize-path": {
|
"normalize-path": {
|
||||||
@ -12703,7 +12703,7 @@
|
|||||||
"rust-gbt": {
|
"rust-gbt": {
|
||||||
"version": "file:rust-gbt",
|
"version": "file:rust-gbt",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@napi-rs/cli": "2.16.1"
|
"@napi-rs/cli": "2.18.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
"rust-build": "npm run rust-clean && cd rust-gbt && npm run build-release"
|
"rust-build": "npm run rust-clean && cd rust-gbt && npm run build-release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/core": "^7.23.2",
|
"@babel/core": "^7.24.0",
|
||||||
"@mempool/electrum-client": "1.1.9",
|
"@mempool/electrum-client": "1.1.9",
|
||||||
"@types/node": "^18.15.3",
|
"@types/node": "^18.15.3",
|
||||||
"axios": "~1.6.1",
|
"axios": "~1.6.1",
|
||||||
@ -56,7 +56,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/code-frame": "^7.18.6",
|
"@babel/code-frame": "^7.18.6",
|
||||||
"@babel/core": "^7.23.2",
|
"@babel/core": "^7.24.0",
|
||||||
"@types/compression": "^1.7.2",
|
"@types/compression": "^1.7.2",
|
||||||
"@types/crypto-js": "^4.1.1",
|
"@types/crypto-js": "^4.1.1",
|
||||||
"@types/express": "^4.17.17",
|
"@types/express": "^4.17.17",
|
||||||
|
|||||||
@ -12,14 +12,14 @@ crate-type = ["cdylib"]
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
priority-queue = "1.3.2"
|
priority-queue = "2.0.2"
|
||||||
bytes = "1.4.0"
|
bytes = "1.4.0"
|
||||||
napi = { version = "2.13.2", features = ["napi8", "tokio_rt"] }
|
napi = { version = "2.16.0", features = ["napi8", "tokio_rt"] }
|
||||||
napi-derive = "2.13.0"
|
napi-derive = "2.16.0"
|
||||||
bytemuck = "1.13.1"
|
bytemuck = "1.13.1"
|
||||||
tracing = "0.1.36"
|
tracing = "0.1.36"
|
||||||
tracing-log = "0.1.3"
|
tracing-log = "0.2.0"
|
||||||
tracing-subscriber = { version = "0.3.15", features = ["env-filter"]}
|
tracing-subscriber = { version = "0.3.15", features = ["env-filter"]}
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
napi-build = "2.0.1"
|
napi-build = "2.1.2"
|
||||||
|
|||||||
@ -237,6 +237,49 @@ switch (platform) {
|
|||||||
loadError = e
|
loadError = e
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case 'riscv64':
|
||||||
|
if (isMusl()) {
|
||||||
|
localFileExisted = existsSync(
|
||||||
|
join(__dirname, 'gbt.linux-riscv64-musl.node')
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (localFileExisted) {
|
||||||
|
nativeBinding = require('./gbt.linux-riscv64-musl.node')
|
||||||
|
} else {
|
||||||
|
nativeBinding = require('gbt-linux-riscv64-musl')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
loadError = e
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
localFileExisted = existsSync(
|
||||||
|
join(__dirname, 'gbt.linux-riscv64-gnu.node')
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (localFileExisted) {
|
||||||
|
nativeBinding = require('./gbt.linux-riscv64-gnu.node')
|
||||||
|
} else {
|
||||||
|
nativeBinding = require('gbt-linux-riscv64-gnu')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
loadError = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 's390x':
|
||||||
|
localFileExisted = existsSync(
|
||||||
|
join(__dirname, 'gbt.linux-s390x-gnu.node')
|
||||||
|
)
|
||||||
|
try {
|
||||||
|
if (localFileExisted) {
|
||||||
|
nativeBinding = require('./gbt.linux-s390x-gnu.node')
|
||||||
|
} else {
|
||||||
|
nativeBinding = require('gbt-linux-s390x-gnu')
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
loadError = e
|
||||||
|
}
|
||||||
|
break
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
throw new Error(`Unsupported architecture on Linux: ${arch}`)
|
||||||
}
|
}
|
||||||
|
|||||||
8
backend/rust-gbt/package-lock.json
generated
8
backend/rust-gbt/package-lock.json
generated
@ -9,16 +9,16 @@
|
|||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@napi-rs/cli": "2.16.1"
|
"@napi-rs/cli": "2.18.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/cli": {
|
"node_modules/@napi-rs/cli": {
|
||||||
"version": "2.16.1",
|
"version": "2.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@napi-rs/cli/-/cli-2.18.0.tgz",
|
||||||
"integrity": "sha512-L0Gr5iEQIDEbvWdDr1HUaBOxBSHL1VZhWSk1oryawoT8qJIY+KGfLFelU+Qma64ivCPbxYpkfPoKYVG3rcoGIA==",
|
"integrity": "sha512-lfSRT7cs3iC4L+kv9suGYQEezn5Nii7Kpu+THsYVI0tA1Vh59LH45p4QADaD7hvIkmOz79eEGtoKQ9nAkAPkzA==",
|
||||||
"bin": {
|
"bin": {
|
||||||
"napi": "scripts/index.js"
|
"napi": "scripts/index.js"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@napi-rs/cli": "2.16.1"
|
"@napi-rs/cli": "2.18.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 12"
|
"node": ">= 12"
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
"ENABLED": true,
|
"ENABLED": true,
|
||||||
|
"OFFICIAL": false,
|
||||||
"NETWORK": "__MEMPOOL_NETWORK__",
|
"NETWORK": "__MEMPOOL_NETWORK__",
|
||||||
"BACKEND": "__MEMPOOL_BACKEND__",
|
"BACKEND": "__MEMPOOL_BACKEND__",
|
||||||
"BLOCKS_SUMMARIES_INDEXING": true,
|
"BLOCKS_SUMMARIES_INDEXING": true,
|
||||||
@ -79,7 +80,8 @@
|
|||||||
"USERNAME": "__DATABASE_USERNAME__",
|
"USERNAME": "__DATABASE_USERNAME__",
|
||||||
"PASSWORD": "__DATABASE_PASSWORD__",
|
"PASSWORD": "__DATABASE_PASSWORD__",
|
||||||
"PID_DIR": "__DATABASE_PID_FILE__",
|
"PID_DIR": "__DATABASE_PID_FILE__",
|
||||||
"TIMEOUT": 3000
|
"TIMEOUT": 3000,
|
||||||
|
"POOL_SIZE": 100
|
||||||
},
|
},
|
||||||
"SYSLOG": {
|
"SYSLOG": {
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
|
|||||||
@ -14,6 +14,7 @@ describe('Mempool Backend Config', () => {
|
|||||||
|
|
||||||
expect(config.MEMPOOL).toStrictEqual({
|
expect(config.MEMPOOL).toStrictEqual({
|
||||||
ENABLED: true,
|
ENABLED: true,
|
||||||
|
OFFICIAL: false,
|
||||||
NETWORK: 'mainnet',
|
NETWORK: 'mainnet',
|
||||||
BACKEND: 'none',
|
BACKEND: 'none',
|
||||||
BLOCKS_SUMMARIES_INDEXING: false,
|
BLOCKS_SUMMARIES_INDEXING: false,
|
||||||
@ -93,7 +94,8 @@ describe('Mempool Backend Config', () => {
|
|||||||
USERNAME: 'mempool',
|
USERNAME: 'mempool',
|
||||||
PASSWORD: 'mempool',
|
PASSWORD: 'mempool',
|
||||||
TIMEOUT: 180000,
|
TIMEOUT: 180000,
|
||||||
PID_DIR: ''
|
PID_DIR: '',
|
||||||
|
POOL_SIZE: 100,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(config.SYSLOG).toStrictEqual({
|
expect(config.SYSLOG).toStrictEqual({
|
||||||
|
|||||||
738
backend/src/api/acceleration.ts
Normal file
738
backend/src/api/acceleration.ts
Normal file
@ -0,0 +1,738 @@
|
|||||||
|
import logger from '../logger';
|
||||||
|
import { MempoolTransactionExtended } from '../mempool.interfaces';
|
||||||
|
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
||||||
|
|
||||||
|
const BLOCK_WEIGHT_UNITS = 4_000_000;
|
||||||
|
const BLOCK_SIGOPS = 80_000;
|
||||||
|
const MAX_RELATIVE_GRAPH_SIZE = 200;
|
||||||
|
const BID_BOOST_WINDOW = 40_000;
|
||||||
|
const BID_BOOST_MIN_OFFSET = 10_000;
|
||||||
|
const BID_BOOST_MAX_OFFSET = 400_000;
|
||||||
|
|
||||||
|
type Acceleration = {
|
||||||
|
txid: string;
|
||||||
|
max_bid: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TxSummary {
|
||||||
|
txid: string; // txid of the current transaction
|
||||||
|
effectiveVsize: number; // Total vsize of the dependency tree
|
||||||
|
effectiveFee: number; // Total fee of the dependency tree in sats
|
||||||
|
ancestorCount: number; // Number of ancestors
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AccelerationInfo {
|
||||||
|
txSummary: TxSummary;
|
||||||
|
targetFeeRate: number; // target fee rate (recommended next block fee, or median fee for mined block)
|
||||||
|
nextBlockFee: number; // fee in sats required to be in the next block (using recommended next block fee, or median fee for mined block)
|
||||||
|
cost: number; // additional cost to accelerate ((cost + txSummary.effectiveFee) / txSummary.effectiveVsize) >= targetFeeRate
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GraphTx {
|
||||||
|
txid: string;
|
||||||
|
vsize: number;
|
||||||
|
weight: number;
|
||||||
|
fees: {
|
||||||
|
base: number;
|
||||||
|
};
|
||||||
|
depends: string[];
|
||||||
|
spentby: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MempoolTx extends GraphTx {
|
||||||
|
ancestorcount: number;
|
||||||
|
ancestorsize: number;
|
||||||
|
fees: {
|
||||||
|
base: number;
|
||||||
|
ancestor: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
ancestors: Map<string, MempoolTx>,
|
||||||
|
ancestorRate: number;
|
||||||
|
individualRate: number;
|
||||||
|
score: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccelerationCosts {
|
||||||
|
/**
|
||||||
|
* Takes a list of accelerations and verbose block data
|
||||||
|
* Returns the "fair" boost rate to charge accelerations
|
||||||
|
*
|
||||||
|
* @param accelerationsx
|
||||||
|
* @param verboseBlock
|
||||||
|
*/
|
||||||
|
public calculateBoostRate(accelerations: Acceleration[], blockTxs: IEsploraApi.Transaction[]): number {
|
||||||
|
// Run GBT ourselves to calculate accurate effective fee rates
|
||||||
|
// the list of transactions comes from a mined block, so we already know everything fits within consensus limits
|
||||||
|
const template = makeBlockTemplate(blockTxs, accelerations, 1, Infinity, Infinity);
|
||||||
|
|
||||||
|
// initialize working maps for fast tx lookups
|
||||||
|
const accMap = {};
|
||||||
|
const txMap = {};
|
||||||
|
for (const acceleration of accelerations) {
|
||||||
|
accMap[acceleration.txid] = acceleration;
|
||||||
|
}
|
||||||
|
for (const tx of template) {
|
||||||
|
txMap[tx.txid] = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Identify and exclude accelerated and otherwise prioritized transactions
|
||||||
|
const excludeMap = {};
|
||||||
|
let totalWeight = 0;
|
||||||
|
let minAcceleratedPackage = Infinity;
|
||||||
|
let lastEffectiveRate = 0;
|
||||||
|
// Iterate over the mined template from bottom to top.
|
||||||
|
// Transactions should appear in ascending order of mining priority.
|
||||||
|
for (const blockTx of [...blockTxs].reverse()) {
|
||||||
|
const txid = blockTx.txid;
|
||||||
|
const tx = txMap[txid];
|
||||||
|
totalWeight += tx.weight;
|
||||||
|
const isAccelerated = accMap[txid] != null;
|
||||||
|
// If a cluster has a in-band effective fee rate than the previous cluster,
|
||||||
|
// it must have been prioritized out-of-band (in order to have a higher mining priority)
|
||||||
|
// so exclude from the analysis.
|
||||||
|
const isPrioritized = tx.effectiveFeePerVsize < lastEffectiveRate;
|
||||||
|
if (isPrioritized || isAccelerated) {
|
||||||
|
let packageWeight = 0;
|
||||||
|
// exclude this whole CPFP cluster
|
||||||
|
for (const clusterTxid of tx.cluster) {
|
||||||
|
packageWeight += txMap[clusterTxid].weight;
|
||||||
|
if (!excludeMap[clusterTxid]) {
|
||||||
|
excludeMap[clusterTxid] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// keep track of the smallest accelerated CPFP cluster for later
|
||||||
|
if (isAccelerated) {
|
||||||
|
minAcceleratedPackage = Math.min(minAcceleratedPackage, packageWeight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isPrioritized) {
|
||||||
|
if (!isAccelerated || !lastEffectiveRate) {
|
||||||
|
lastEffectiveRate = tx.effectiveFeePerVsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Bid Boost Rate is calculated by disregarding the bottom X weight units of the block,
|
||||||
|
// where X is the larger of BID_BOOST_MIN_OFFSET or the smallest accelerated package weight (the "offset"),
|
||||||
|
// then taking the average fee rate of the following BID_BOOST_WINDOW weight units
|
||||||
|
// (ignoring accelerated transactions and their ancestors).
|
||||||
|
//
|
||||||
|
// Transactions within the offset might pay less than the fair rate due to bin-packing effects
|
||||||
|
// But the average rate paid by the next chunk of non-accelerated transactions provides a good
|
||||||
|
// upper bound on the "next best rate" of alternatives to including the accelerated transactions
|
||||||
|
// (since, if there were any better options, they would have been included instead)
|
||||||
|
const spareWeight = BLOCK_WEIGHT_UNITS - totalWeight;
|
||||||
|
const windowOffset = Math.min(Math.max(minAcceleratedPackage, BID_BOOST_MIN_OFFSET, spareWeight), BID_BOOST_MAX_OFFSET);
|
||||||
|
const leftBound = windowOffset;
|
||||||
|
const rightBound = windowOffset + BID_BOOST_WINDOW;
|
||||||
|
let totalFeeInWindow = 0;
|
||||||
|
let totalWeightInWindow = Math.max(0, spareWeight - leftBound);
|
||||||
|
let txIndex = blockTxs.length - 1;
|
||||||
|
for (let offset = spareWeight; offset < BLOCK_WEIGHT_UNITS && txIndex >= 0; txIndex--) {
|
||||||
|
const txid = blockTxs[txIndex].txid;
|
||||||
|
const tx = txMap[txid];
|
||||||
|
if (excludeMap[txid]) {
|
||||||
|
// skip prioritized transactions and their ancestors
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const left = offset;
|
||||||
|
const right = offset + tx.weight;
|
||||||
|
offset += tx.weight;
|
||||||
|
if (right < leftBound) {
|
||||||
|
// not within window yet
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (left > rightBound) {
|
||||||
|
// past window
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// count fees for weight units within the window
|
||||||
|
const overlapLeft = Math.max(leftBound, left);
|
||||||
|
const overlapRight = Math.min(rightBound, right);
|
||||||
|
const overlapUnits = overlapRight - overlapLeft;
|
||||||
|
totalFeeInWindow += (tx.effectiveFeePerVsize * (overlapUnits / 4));
|
||||||
|
totalWeightInWindow += overlapUnits;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalWeightInWindow < BID_BOOST_WINDOW) {
|
||||||
|
// not enough un-prioritized transactions to calculate a fair rate
|
||||||
|
// just charge everyone their max bids
|
||||||
|
return Infinity;
|
||||||
|
}
|
||||||
|
// Divide the total fee by the size of the BID_BOOST_WINDOW in vbytes
|
||||||
|
const averageRate = totalFeeInWindow / (BID_BOOST_WINDOW / 4);
|
||||||
|
return averageRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes an accelerated mined txid and a target rate
|
||||||
|
* Returns the total vsize, fees and acceleration cost (in sats) of the tx and all same-block ancestors
|
||||||
|
*
|
||||||
|
* @param txid
|
||||||
|
* @param medianFeeRate
|
||||||
|
*/
|
||||||
|
public getAccelerationInfo(tx: MempoolTransactionExtended, targetFeeRate: number, transactions: MempoolTransactionExtended[]): AccelerationInfo {
|
||||||
|
// Get same-block transaction ancestors
|
||||||
|
const allRelatives = this.getSameBlockRelatives(tx, transactions);
|
||||||
|
const relativesMap = this.initializeRelatives(allRelatives);
|
||||||
|
const rootTx = relativesMap.get(tx.txid) as MempoolTx;
|
||||||
|
|
||||||
|
// Calculate cost to boost
|
||||||
|
return this.calculateAccelerationAncestors(rootTx, relativesMap, targetFeeRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a raw transaction, and builds a graph of same-block relatives,
|
||||||
|
* and returns as a MempoolTx
|
||||||
|
*
|
||||||
|
* @param tx
|
||||||
|
*/
|
||||||
|
private getSameBlockRelatives(tx: MempoolTransactionExtended, transactions: MempoolTransactionExtended[]): Map<string, GraphTx> {
|
||||||
|
const blockTxs = new Map<string, MempoolTransactionExtended>(); // map of txs in this block
|
||||||
|
const spendMap = new Map<string, string>(); // map of outpoints to spending txids
|
||||||
|
for (const tx of transactions) {
|
||||||
|
blockTxs.set(tx.txid, tx);
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
spendMap.set(`${vin.txid}:${vin.vout}`, tx.txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const relatives: Map<string, GraphTx> = new Map();
|
||||||
|
const stack: string[] = [tx.txid];
|
||||||
|
|
||||||
|
// build set of same-block ancestors
|
||||||
|
while (stack.length > 0) {
|
||||||
|
const nextTxid = stack.pop();
|
||||||
|
const nextTx = nextTxid ? blockTxs.get(nextTxid) : null;
|
||||||
|
if (!nextTx || relatives.has(nextTx.txid)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mempoolTx = this.convertToGraphTx(nextTx);
|
||||||
|
|
||||||
|
mempoolTx.fees.base = nextTx.fee || 0;
|
||||||
|
mempoolTx.depends = nextTx.vin.map(vin => vin.txid).filter(inTxid => inTxid && blockTxs.has(inTxid)) as string[];
|
||||||
|
mempoolTx.spentby = nextTx.vout.map((vout, index) => spendMap.get(`${nextTx.txid}:${index}`)).filter(outTxid => outTxid && blockTxs.has(outTxid)) as string[];
|
||||||
|
|
||||||
|
for (const txid of [...mempoolTx.depends, ...mempoolTx.spentby]) {
|
||||||
|
if (txid) {
|
||||||
|
stack.push(txid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
relatives.set(mempoolTx.txid, mempoolTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
return relatives;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a raw transaction and converts it to MempoolTx format
|
||||||
|
* fee and ancestor data is initialized with dummy/null values
|
||||||
|
*
|
||||||
|
* @param tx
|
||||||
|
*/
|
||||||
|
private convertToGraphTx(tx: MempoolTransactionExtended): GraphTx {
|
||||||
|
return {
|
||||||
|
txid: tx.txid,
|
||||||
|
vsize: tx.vsize,
|
||||||
|
weight: tx.weight,
|
||||||
|
fees: {
|
||||||
|
base: 0, // dummy
|
||||||
|
},
|
||||||
|
depends: [], // dummy
|
||||||
|
spentby: [], //dummy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertGraphToMempoolTx(tx: GraphTx): MempoolTx {
|
||||||
|
return {
|
||||||
|
...tx,
|
||||||
|
fees: {
|
||||||
|
base: tx.fees.base,
|
||||||
|
ancestor: tx.fees.base,
|
||||||
|
},
|
||||||
|
ancestorcount: 1,
|
||||||
|
ancestorsize: tx.vsize,
|
||||||
|
ancestors: new Map<string, MempoolTx>(),
|
||||||
|
ancestorRate: 0,
|
||||||
|
individualRate: 0,
|
||||||
|
score: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a root transaction, a list of in-mempool ancestors, and a target fee rate,
|
||||||
|
* Calculate the minimum set of transactions to fee-bump, their total vsize + fees
|
||||||
|
*
|
||||||
|
* @param tx
|
||||||
|
* @param ancestors
|
||||||
|
*/
|
||||||
|
private calculateAccelerationAncestors(tx: MempoolTx, relatives: Map<string, MempoolTx>, targetFeeRate: number): AccelerationInfo {
|
||||||
|
// add root tx to the ancestor map
|
||||||
|
relatives.set(tx.txid, tx);
|
||||||
|
|
||||||
|
// Check for high-sigop transactions (not supported)
|
||||||
|
relatives.forEach(entry => {
|
||||||
|
if (entry.vsize > Math.ceil(entry.weight / 4)) {
|
||||||
|
throw new Error(`high_sigop_tx`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize individual & ancestor fee rates
|
||||||
|
relatives.forEach(entry => this.setAncestorScores(entry));
|
||||||
|
|
||||||
|
// Sort by descending ancestor score
|
||||||
|
let sortedRelatives = Array.from(relatives.values()).sort(this.mempoolComparator);
|
||||||
|
|
||||||
|
let includedInCluster: Map<string, MempoolTx> | null = null;
|
||||||
|
|
||||||
|
// While highest score >= targetFeeRate
|
||||||
|
let maxIterations = MAX_RELATIVE_GRAPH_SIZE;
|
||||||
|
while (sortedRelatives.length && sortedRelatives[0].score && sortedRelatives[0].score >= targetFeeRate && maxIterations > 0) {
|
||||||
|
maxIterations--;
|
||||||
|
// Grab the highest scoring entry
|
||||||
|
const best = sortedRelatives.shift();
|
||||||
|
if (best) {
|
||||||
|
const cluster = new Map<string, MempoolTx>(best.ancestors?.entries() || []);
|
||||||
|
if (best.ancestors.has(tx.txid)) {
|
||||||
|
includedInCluster = cluster;
|
||||||
|
}
|
||||||
|
cluster.set(best.txid, best);
|
||||||
|
// Remove this cluster (it already pays over the target rate, so doesn't need to be boosted)
|
||||||
|
// and update scores, ancestor totals and dependencies for the survivors
|
||||||
|
this.removeAncestors(cluster, relatives);
|
||||||
|
|
||||||
|
// re-sort
|
||||||
|
sortedRelatives = Array.from(relatives.values()).sort(this.mempoolComparator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sanity check for infinite loops / too many ancestors (should never happen)
|
||||||
|
if (maxIterations <= 0) {
|
||||||
|
logger.warn(`acceleration dependency calculation failed: calculateAccelerationAncestors loop exceeded ${MAX_RELATIVE_GRAPH_SIZE} iterations, unable to proceed`);
|
||||||
|
throw new Error('invalid_tx_dependencies');
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalFee = Math.round(tx.fees.ancestor * 100_000_000);
|
||||||
|
|
||||||
|
// transaction is already CPFP-d above the target rate by some descendant
|
||||||
|
if (includedInCluster) {
|
||||||
|
let clusterSize = 0;
|
||||||
|
let clusterFee = 0;
|
||||||
|
includedInCluster.forEach(entry => {
|
||||||
|
clusterSize += entry.vsize;
|
||||||
|
clusterFee += (entry.fees.base * 100_000_000);
|
||||||
|
});
|
||||||
|
const clusterRate = clusterFee / clusterSize;
|
||||||
|
totalFee = Math.ceil(tx.ancestorsize * clusterRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Whatever remains in the accelerated tx's dependencies needs to be boosted to the targetFeeRate
|
||||||
|
// Cost = (totalVsize * targetFeeRate) - totalFee
|
||||||
|
return {
|
||||||
|
txSummary: {
|
||||||
|
txid: tx.txid,
|
||||||
|
effectiveVsize: tx.ancestorsize,
|
||||||
|
effectiveFee: totalFee,
|
||||||
|
ancestorCount: tx.ancestorcount,
|
||||||
|
},
|
||||||
|
cost: Math.max(0, Math.ceil(tx.ancestorsize * targetFeeRate) - totalFee),
|
||||||
|
targetFeeRate,
|
||||||
|
nextBlockFee: Math.ceil(tx.ancestorsize * targetFeeRate),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively traverses an in-mempool dependency graph, and sets a Map of in-mempool ancestors
|
||||||
|
* for each transaction.
|
||||||
|
*
|
||||||
|
* @param tx
|
||||||
|
* @param all
|
||||||
|
*/
|
||||||
|
private setAncestors(tx: MempoolTx, all: Map<string, MempoolTx>, visited: Map<string, Map<string, MempoolTx>>, depth: number = 0): Map<string, MempoolTx> {
|
||||||
|
// sanity check for infinite recursion / too many ancestors (should never happen)
|
||||||
|
if (depth >= 100) {
|
||||||
|
logger.warn('acceleration dependency calculation failed: setAncestors reached depth of 100, unable to proceed', `Accelerator`);
|
||||||
|
throw new Error('invalid_tx_dependencies');
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the ancestor map for this tx
|
||||||
|
tx.ancestors = new Map<string, MempoolTx>();
|
||||||
|
tx.depends.forEach(parentId => {
|
||||||
|
const parent = all.get(parentId);
|
||||||
|
if (parent) {
|
||||||
|
// add the parent
|
||||||
|
tx.ancestors?.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 = this.setAncestors(parent, all, visited, depth + 1);
|
||||||
|
}
|
||||||
|
// and add to this tx's map
|
||||||
|
ancestors.forEach((ancestor, ancestorId) => {
|
||||||
|
tx.ancestors?.set(ancestorId, ancestor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
visited.set(tx.txid, tx.ancestors);
|
||||||
|
|
||||||
|
return tx.ancestors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
private initializeRelatives(all: Map<string, GraphTx>): Map<string, MempoolTx> {
|
||||||
|
const mempoolTxs = new Map<string, MempoolTx>();
|
||||||
|
all.forEach(entry => {
|
||||||
|
mempoolTxs.set(entry.txid, this.convertGraphToMempoolTx(entry));
|
||||||
|
});
|
||||||
|
const visited: Map<string, Map<string, MempoolTx>> = new Map();
|
||||||
|
const leaves: MempoolTx[] = Array.from(mempoolTxs.values()).filter(entry => entry.spentby.length === 0);
|
||||||
|
for (const leaf of leaves) {
|
||||||
|
this.setAncestors(leaf, mempoolTxs, visited);
|
||||||
|
}
|
||||||
|
mempoolTxs.forEach(entry => {
|
||||||
|
entry.ancestors?.forEach(ancestor => {
|
||||||
|
entry.ancestorcount++;
|
||||||
|
entry.ancestorsize += ancestor.vsize;
|
||||||
|
entry.fees.ancestor += ancestor.fees.base;
|
||||||
|
});
|
||||||
|
this.setAncestorScores(entry);
|
||||||
|
});
|
||||||
|
return mempoolTxs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a cluster of transactions from an in-mempool dependency graph
|
||||||
|
* and update the survivors' scores and ancestors
|
||||||
|
*
|
||||||
|
* @param cluster
|
||||||
|
* @param ancestors
|
||||||
|
*/
|
||||||
|
private removeAncestors(cluster: Map<string, MempoolTx>, all: Map<string, MempoolTx>): void {
|
||||||
|
// remove
|
||||||
|
cluster.forEach(tx => {
|
||||||
|
all.delete(tx.txid);
|
||||||
|
});
|
||||||
|
|
||||||
|
// update survivors
|
||||||
|
all.forEach(tx => {
|
||||||
|
cluster.forEach(remove => {
|
||||||
|
if (tx.ancestors?.has(remove.txid)) {
|
||||||
|
// remove as dependency
|
||||||
|
tx.ancestors.delete(remove.txid);
|
||||||
|
tx.depends = tx.depends.filter(parent => parent !== remove.txid);
|
||||||
|
// update ancestor sizes and fees
|
||||||
|
tx.ancestorsize -= remove.vsize;
|
||||||
|
tx.fees.ancestor -= remove.fees.base;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// recalculate fee rates
|
||||||
|
this.setAncestorScores(tx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take a mempool transaction, and set the fee rates and ancestor score
|
||||||
|
*
|
||||||
|
* @param tx
|
||||||
|
*/
|
||||||
|
private setAncestorScores(tx: MempoolTx): void {
|
||||||
|
tx.individualRate = (tx.fees.base * 100_000_000) / tx.vsize;
|
||||||
|
tx.ancestorRate = (tx.fees.ancestor * 100_000_000) / tx.ancestorsize;
|
||||||
|
tx.score = Math.min(tx.individualRate, tx.ancestorRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by descending score
|
||||||
|
private mempoolComparator(a, b): number {
|
||||||
|
return b.score - a.score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AccelerationCosts;
|
||||||
|
|
||||||
|
interface TemplateTransaction {
|
||||||
|
txid: string;
|
||||||
|
order: number;
|
||||||
|
weight: number;
|
||||||
|
adjustedVsize: number; // sigop-adjusted vsize, rounded up to the nearest integer
|
||||||
|
sigops: number;
|
||||||
|
fee: number;
|
||||||
|
feeDelta: number;
|
||||||
|
ancestors: string[];
|
||||||
|
cluster: string[];
|
||||||
|
effectiveFeePerVsize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MinerTransaction extends TemplateTransaction {
|
||||||
|
inputs: string[];
|
||||||
|
feePerVsize: number;
|
||||||
|
relativesSet: boolean;
|
||||||
|
ancestorMap: Map<string, MinerTransaction>;
|
||||||
|
children: Set<MinerTransaction>;
|
||||||
|
ancestorFee: number;
|
||||||
|
ancestorVsize: number;
|
||||||
|
ancestorSigops: number;
|
||||||
|
score: number;
|
||||||
|
used: boolean;
|
||||||
|
modified: boolean;
|
||||||
|
dependencyRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build a block using an approximation of the transaction selection algorithm from Bitcoin Core
|
||||||
|
* (see BlockAssembler in https://github.com/bitcoin/bitcoin/blob/master/src/node/miner.cpp)
|
||||||
|
*/
|
||||||
|
function makeBlockTemplate(candidates: IEsploraApi.Transaction[], accelerations: Acceleration[], maxBlocks: number = 8, weightLimit: number = BLOCK_WEIGHT_UNITS, sigopLimit: number = BLOCK_SIGOPS): TemplateTransaction[] {
|
||||||
|
const auditPool: Map<string, MinerTransaction> = new Map();
|
||||||
|
const mempoolArray: MinerTransaction[] = [];
|
||||||
|
|
||||||
|
candidates.forEach(tx => {
|
||||||
|
// initializing everything up front helps V8 optimize property access later
|
||||||
|
const adjustedVsize = Math.ceil(Math.max(tx.weight / 4, 5 * (tx.sigops || 0)));
|
||||||
|
const feePerVsize = (tx.fee / adjustedVsize);
|
||||||
|
auditPool.set(tx.txid, {
|
||||||
|
txid: tx.txid,
|
||||||
|
order: txidToOrdering(tx.txid),
|
||||||
|
fee: tx.fee,
|
||||||
|
feeDelta: 0,
|
||||||
|
weight: tx.weight,
|
||||||
|
adjustedVsize,
|
||||||
|
feePerVsize: feePerVsize,
|
||||||
|
effectiveFeePerVsize: feePerVsize,
|
||||||
|
dependencyRate: feePerVsize,
|
||||||
|
sigops: tx.sigops || 0,
|
||||||
|
inputs: (tx.vin?.map(vin => vin.txid) || []) as string[],
|
||||||
|
relativesSet: false,
|
||||||
|
ancestors: [],
|
||||||
|
cluster: [],
|
||||||
|
ancestorMap: new Map<string, MinerTransaction>(),
|
||||||
|
children: new Set<MinerTransaction>(),
|
||||||
|
ancestorFee: 0,
|
||||||
|
ancestorVsize: 0,
|
||||||
|
ancestorSigops: 0,
|
||||||
|
score: 0,
|
||||||
|
used: false,
|
||||||
|
modified: false,
|
||||||
|
});
|
||||||
|
mempoolArray.push(auditPool.get(tx.txid) as MinerTransaction);
|
||||||
|
});
|
||||||
|
|
||||||
|
// set accelerated effective fee
|
||||||
|
for (const acceleration of accelerations) {
|
||||||
|
const tx = auditPool.get(acceleration.txid);
|
||||||
|
if (tx) {
|
||||||
|
tx.feeDelta = acceleration.max_bid;
|
||||||
|
tx.feePerVsize = ((tx.fee + tx.feeDelta) / tx.adjustedVsize);
|
||||||
|
tx.effectiveFeePerVsize = tx.feePerVsize;
|
||||||
|
tx.dependencyRate = tx.feePerVsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build relatives graph & calculate ancestor scores
|
||||||
|
for (const tx of mempoolArray) {
|
||||||
|
if (!tx.relativesSet) {
|
||||||
|
setRelatives(tx, auditPool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by descending ancestor score
|
||||||
|
mempoolArray.sort(priorityComparator);
|
||||||
|
|
||||||
|
// Build blocks by greedily choosing the highest feerate package
|
||||||
|
// (i.e. the package rooted in the transaction with the best ancestor score)
|
||||||
|
const blocks: number[][] = [];
|
||||||
|
let blockWeight = 0;
|
||||||
|
let blockSigops = 0;
|
||||||
|
const transactions: MinerTransaction[] = [];
|
||||||
|
let modified: MinerTransaction[] = [];
|
||||||
|
const overflow: MinerTransaction[] = [];
|
||||||
|
let failures = 0;
|
||||||
|
while (mempoolArray.length || modified.length) {
|
||||||
|
// skip invalid transactions
|
||||||
|
while (mempoolArray[0].used || mempoolArray[0].modified) {
|
||||||
|
mempoolArray.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select best next package
|
||||||
|
let nextTx;
|
||||||
|
const nextPoolTx = mempoolArray[0];
|
||||||
|
const nextModifiedTx = modified[0];
|
||||||
|
if (nextPoolTx && (!nextModifiedTx || (nextPoolTx.score || 0) > (nextModifiedTx.score || 0))) {
|
||||||
|
nextTx = nextPoolTx;
|
||||||
|
mempoolArray.shift();
|
||||||
|
} else {
|
||||||
|
modified.shift();
|
||||||
|
if (nextModifiedTx) {
|
||||||
|
nextTx = nextModifiedTx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextTx && !nextTx?.used) {
|
||||||
|
// Check if the package fits into this block
|
||||||
|
if (blocks.length >= (maxBlocks - 1) || ((blockWeight + (4 * nextTx.ancestorVsize) < weightLimit) && (blockSigops + nextTx.ancestorSigops <= sigopLimit))) {
|
||||||
|
const ancestors: MinerTransaction[] = Array.from(nextTx.ancestorMap.values());
|
||||||
|
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
|
||||||
|
const sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
|
||||||
|
const clusterTxids = sortedTxSet.map(tx => tx.txid);
|
||||||
|
const effectiveFeeRate = Math.min(nextTx.dependencyRate || Infinity, nextTx.ancestorFee / nextTx.ancestorVsize);
|
||||||
|
const used: MinerTransaction[] = [];
|
||||||
|
while (sortedTxSet.length) {
|
||||||
|
const ancestor = sortedTxSet.pop();
|
||||||
|
if (!ancestor) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ancestor.used = true;
|
||||||
|
ancestor.usedBy = nextTx.txid;
|
||||||
|
// update this tx with effective fee rate & relatives data
|
||||||
|
if (ancestor.effectiveFeePerVsize !== effectiveFeeRate) {
|
||||||
|
ancestor.effectiveFeePerVsize = effectiveFeeRate;
|
||||||
|
}
|
||||||
|
ancestor.cluster = clusterTxids;
|
||||||
|
transactions.push(ancestor);
|
||||||
|
blockWeight += ancestor.weight;
|
||||||
|
blockSigops += ancestor.sigops;
|
||||||
|
used.push(ancestor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove these as valid package ancestors for any descendants remaining in the mempool
|
||||||
|
if (used.length) {
|
||||||
|
used.forEach(tx => {
|
||||||
|
modified = updateDescendants(tx, auditPool, modified, effectiveFeeRate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
failures = 0;
|
||||||
|
} else {
|
||||||
|
// hold this package in an overflow list while we check for smaller options
|
||||||
|
overflow.push(nextTx);
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this block is full
|
||||||
|
const exceededPackageTries = failures > 1000 && blockWeight > (weightLimit - 4000);
|
||||||
|
const queueEmpty = !mempoolArray.length && !modified.length;
|
||||||
|
|
||||||
|
if (exceededPackageTries || queueEmpty) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tx of transactions) {
|
||||||
|
tx.ancestors = Object.values(tx.ancestorMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
return transactions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// traverse in-mempool ancestors
|
||||||
|
// recursion unavoidable, but should be limited to depth < 25 by mempool policy
|
||||||
|
function setRelatives(
|
||||||
|
tx: MinerTransaction,
|
||||||
|
mempool: Map<string, MinerTransaction>,
|
||||||
|
): void {
|
||||||
|
for (const parent of tx.inputs) {
|
||||||
|
const parentTx = mempool.get(parent);
|
||||||
|
if (parentTx && !tx.ancestorMap?.has(parent)) {
|
||||||
|
tx.ancestorMap.set(parent, parentTx);
|
||||||
|
parentTx.children.add(tx);
|
||||||
|
// visit each node only once
|
||||||
|
if (!parentTx.relativesSet) {
|
||||||
|
setRelatives(parentTx, mempool);
|
||||||
|
}
|
||||||
|
parentTx.ancestorMap.forEach((ancestor) => {
|
||||||
|
tx.ancestorMap.set(ancestor.txid, ancestor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
tx.ancestorFee = (tx.fee + tx.feeDelta);
|
||||||
|
tx.ancestorVsize = tx.adjustedVsize || 0;
|
||||||
|
tx.ancestorSigops = tx.sigops || 0;
|
||||||
|
tx.ancestorMap.forEach((ancestor) => {
|
||||||
|
tx.ancestorFee += (ancestor.fee + ancestor.feeDelta);
|
||||||
|
tx.ancestorVsize += ancestor.adjustedVsize;
|
||||||
|
tx.ancestorSigops += ancestor.sigops;
|
||||||
|
});
|
||||||
|
tx.score = tx.ancestorFee / tx.ancestorVsize;
|
||||||
|
tx.relativesSet = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over remaining descendants, removing the root as a valid ancestor & updating the ancestor score
|
||||||
|
// avoids recursion to limit call stack depth
|
||||||
|
function updateDescendants(
|
||||||
|
rootTx: MinerTransaction,
|
||||||
|
mempool: Map<string, MinerTransaction>,
|
||||||
|
modified: MinerTransaction[],
|
||||||
|
clusterRate: number,
|
||||||
|
): MinerTransaction[] {
|
||||||
|
const descendantSet: Set<MinerTransaction> = new Set();
|
||||||
|
// stack of nodes left to visit
|
||||||
|
const descendants: MinerTransaction[] = [];
|
||||||
|
let descendantTx: MinerTransaction | undefined;
|
||||||
|
rootTx.children.forEach(childTx => {
|
||||||
|
if (!descendantSet.has(childTx)) {
|
||||||
|
descendants.push(childTx);
|
||||||
|
descendantSet.add(childTx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
while (descendants.length) {
|
||||||
|
descendantTx = descendants.pop();
|
||||||
|
if (descendantTx && descendantTx.ancestorMap && descendantTx.ancestorMap.has(rootTx.txid)) {
|
||||||
|
// remove tx as ancestor
|
||||||
|
descendantTx.ancestorMap.delete(rootTx.txid);
|
||||||
|
descendantTx.ancestorFee -= (rootTx.fee + rootTx.feeDelta);
|
||||||
|
descendantTx.ancestorVsize -= rootTx.adjustedVsize;
|
||||||
|
descendantTx.ancestorSigops -= rootTx.sigops;
|
||||||
|
descendantTx.score = descendantTx.ancestorFee / descendantTx.ancestorVsize;
|
||||||
|
descendantTx.dependencyRate = descendantTx.dependencyRate ? Math.min(descendantTx.dependencyRate, clusterRate) : clusterRate;
|
||||||
|
|
||||||
|
if (!descendantTx.modified) {
|
||||||
|
descendantTx.modified = true;
|
||||||
|
modified.push(descendantTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// add this node's children to the stack
|
||||||
|
descendantTx.children.forEach(childTx => {
|
||||||
|
// visit each node only once
|
||||||
|
if (!descendantSet.has(childTx)) {
|
||||||
|
descendants.push(childTx);
|
||||||
|
descendantSet.add(childTx);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// return new, resorted modified list
|
||||||
|
return modified.sort(priorityComparator);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to sort an array of MinerTransactions by descending ancestor score
|
||||||
|
function priorityComparator(a: MinerTransaction, b: MinerTransaction): number {
|
||||||
|
if (b.score === a.score) {
|
||||||
|
// tie-break by txid for stability
|
||||||
|
return a.order - b.order;
|
||||||
|
} else {
|
||||||
|
return b.score - a.score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the most significant 4 bytes of the txid as an integer
|
||||||
|
function txidToOrdering(txid: string): number {
|
||||||
|
return parseInt(
|
||||||
|
txid.substring(62, 64) +
|
||||||
|
txid.substring(60, 62) +
|
||||||
|
txid.substring(58, 60) +
|
||||||
|
txid.substring(56, 58),
|
||||||
|
16
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -29,6 +29,7 @@ export interface AbstractBitcoinApi {
|
|||||||
$getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise<IEsploraApi.Outspend[]>;
|
$getOutSpendsByOutpoint(outpoints: { txid: string, vout: number }[]): Promise<IEsploraApi.Outspend[]>;
|
||||||
|
|
||||||
startHealthChecks(): void;
|
startHealthChecks(): void;
|
||||||
|
getHealthStatus(): HealthCheckHost[];
|
||||||
}
|
}
|
||||||
export interface BitcoinRpcCredentials {
|
export interface BitcoinRpcCredentials {
|
||||||
host: string;
|
host: string;
|
||||||
@ -38,3 +39,14 @@ export interface BitcoinRpcCredentials {
|
|||||||
timeout: number;
|
timeout: number;
|
||||||
cookie?: string;
|
cookie?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HealthCheckHost {
|
||||||
|
host: string;
|
||||||
|
active: boolean;
|
||||||
|
rtt: number;
|
||||||
|
latestHeight: number;
|
||||||
|
socket: boolean;
|
||||||
|
outOfSync: boolean;
|
||||||
|
unreachable: boolean;
|
||||||
|
checked: boolean;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import * as bitcoinjs from 'bitcoinjs-lib';
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||||
import { IBitcoinApi } from './bitcoin-api.interface';
|
import { IBitcoinApi } from './bitcoin-api.interface';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import blocks from '../blocks';
|
import blocks from '../blocks';
|
||||||
@ -382,6 +382,10 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public startHealthChecks(): void {};
|
public startHealthChecks(): void {};
|
||||||
|
|
||||||
|
public getHealthStatus() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BitcoinApi;
|
export default BitcoinApi;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import axios, { AxiosResponse } from 'axios';
|
import axios from 'axios';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||||
import { IEsploraApi } from './esplora-api.interface';
|
import { IEsploraApi } from './esplora-api.interface';
|
||||||
import logger from '../../logger';
|
import logger from '../../logger';
|
||||||
import { Common } from '../common';
|
import { Common } from '../common';
|
||||||
@ -157,7 +157,7 @@ class FailoverRouter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// sort hosts by connection quality, and update default fallback
|
// sort hosts by connection quality, and update default fallback
|
||||||
private sortHosts(): FailoverHost[] {
|
public sortHosts(): FailoverHost[] {
|
||||||
// sort by connection quality
|
// sort by connection quality
|
||||||
return this.hosts.slice().sort((a, b) => {
|
return this.hosts.slice().sort((a, b) => {
|
||||||
if ((a.unreachable || a.outOfSync) === (b.unreachable || b.outOfSync)) {
|
if ((a.unreachable || a.outOfSync) === (b.unreachable || b.outOfSync)) {
|
||||||
@ -342,6 +342,23 @@ class ElectrsApi implements AbstractBitcoinApi {
|
|||||||
public startHealthChecks(): void {
|
public startHealthChecks(): void {
|
||||||
this.failoverRouter.startHealthChecks();
|
this.failoverRouter.startHealthChecks();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getHealthStatus(): HealthCheckHost[] {
|
||||||
|
if (config.MEMPOOL.OFFICIAL) {
|
||||||
|
return this.failoverRouter.sortHosts().map(host => ({
|
||||||
|
host: host.host,
|
||||||
|
active: host === this.failoverRouter.activeHost,
|
||||||
|
rtt: host.rtt,
|
||||||
|
latestHeight: host.latestHeight || 0,
|
||||||
|
socket: !!host.socket,
|
||||||
|
outOfSync: !!host.outOfSync,
|
||||||
|
unreachable: !!host.unreachable,
|
||||||
|
checked: !!host.checked,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ElectrsApi;
|
export default ElectrsApi;
|
||||||
|
|||||||
@ -40,6 +40,7 @@ class Blocks {
|
|||||||
private quarterEpochBlockTime: number | null = null;
|
private quarterEpochBlockTime: number | null = null;
|
||||||
private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
|
private newBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: TransactionExtended[]) => void)[] = [];
|
||||||
private newAsyncBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise<void>)[] = [];
|
private newAsyncBlockCallbacks: ((block: BlockExtended, txIds: string[], transactions: MempoolTransactionExtended[]) => Promise<void>)[] = [];
|
||||||
|
private classifyingBlocks: boolean = false;
|
||||||
|
|
||||||
private mainLoopTimeout: number = 120000;
|
private mainLoopTimeout: number = 120000;
|
||||||
|
|
||||||
@ -568,6 +569,11 @@ class Blocks {
|
|||||||
* [INDEXING] Index transaction classification flags for Goggles
|
* [INDEXING] Index transaction classification flags for Goggles
|
||||||
*/
|
*/
|
||||||
public async $classifyBlocks(): Promise<void> {
|
public async $classifyBlocks(): Promise<void> {
|
||||||
|
if (this.classifyingBlocks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.classifyingBlocks = true;
|
||||||
|
|
||||||
// classification requires an esplora backend
|
// classification requires an esplora backend
|
||||||
if (!Common.gogglesIndexingEnabled() || config.MEMPOOL.BACKEND !== 'esplora') {
|
if (!Common.gogglesIndexingEnabled() || config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
return;
|
return;
|
||||||
@ -679,6 +685,8 @@ class Blocks {
|
|||||||
indexedThisRun = 0;
|
indexedThisRun = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.classifyingBlocks = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -7,6 +7,24 @@ import { isIP } from 'net';
|
|||||||
import transactionUtils from './transaction-utils';
|
import transactionUtils from './transaction-utils';
|
||||||
import { isPoint } from '../utils/secp256k1';
|
import { isPoint } from '../utils/secp256k1';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
import { getVarIntLength, opcodes, parseMultisigScript } from '../utils/bitcoin-script';
|
||||||
|
|
||||||
|
// Bitcoin Core default policy settings
|
||||||
|
const TX_MAX_STANDARD_VERSION = 2;
|
||||||
|
const MAX_STANDARD_TX_WEIGHT = 400_000;
|
||||||
|
const MAX_BLOCK_SIGOPS_COST = 80_000;
|
||||||
|
const MAX_STANDARD_TX_SIGOPS_COST = (MAX_BLOCK_SIGOPS_COST / 5);
|
||||||
|
const MIN_STANDARD_TX_NONWITNESS_SIZE = 65;
|
||||||
|
const MAX_P2SH_SIGOPS = 15;
|
||||||
|
const MAX_STANDARD_P2WSH_STACK_ITEMS = 100;
|
||||||
|
const MAX_STANDARD_P2WSH_STACK_ITEM_SIZE = 80;
|
||||||
|
const MAX_STANDARD_TAPSCRIPT_STACK_ITEM_SIZE = 80;
|
||||||
|
const MAX_STANDARD_P2WSH_SCRIPT_SIZE = 3600;
|
||||||
|
const MAX_STANDARD_SCRIPTSIG_SIZE = 1650;
|
||||||
|
const DUST_RELAY_TX_FEE = 3;
|
||||||
|
const MAX_OP_RETURN_RELAY = 83;
|
||||||
|
const DEFAULT_PERMIT_BAREMULTISIG = true;
|
||||||
|
|
||||||
export class Common {
|
export class Common {
|
||||||
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
|
static nativeAssetId = config.MEMPOOL.NETWORK === 'liquidtestnet' ?
|
||||||
'144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'
|
'144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49'
|
||||||
@ -177,6 +195,141 @@ export class Common {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates most standardness rules
|
||||||
|
*
|
||||||
|
* returns true early if any standardness rule is violated, otherwise false
|
||||||
|
* (except for non-mandatory-script-verify-flag and p2sh script evaluation rules which are *not* enforced)
|
||||||
|
*/
|
||||||
|
static isNonStandard(tx: TransactionExtended): boolean {
|
||||||
|
// version
|
||||||
|
if (tx.version > TX_MAX_STANDARD_VERSION) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx-size
|
||||||
|
if (tx.weight > MAX_STANDARD_TX_WEIGHT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// tx-size-small
|
||||||
|
if (this.getNonWitnessSize(tx) < MIN_STANDARD_TX_NONWITNESS_SIZE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// bad-txns-too-many-sigops
|
||||||
|
if (tx.sigops && tx.sigops > MAX_STANDARD_TX_SIGOPS_COST) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// input validation
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
if (vin.is_coinbase) {
|
||||||
|
// standardness rules don't apply to coinbase transactions
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// scriptsig-size
|
||||||
|
if ((vin.scriptsig.length / 2) > MAX_STANDARD_SCRIPTSIG_SIZE) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// scriptsig-not-pushonly
|
||||||
|
if (vin.scriptsig_asm) {
|
||||||
|
for (const op of vin.scriptsig_asm.split(' ')) {
|
||||||
|
if (opcodes[op] && opcodes[op] > opcodes['OP_16']) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// bad-txns-nonstandard-inputs
|
||||||
|
if (vin.prevout?.scriptpubkey_type === 'p2sh') {
|
||||||
|
// TODO: evaluate script (https://github.com/bitcoin/bitcoin/blob/1ac627c485a43e50a9a49baddce186ee3ad4daad/src/policy/policy.cpp#L177)
|
||||||
|
// countScriptSigops returns the witness-scaled sigops, so divide by 4 before comparison with MAX_P2SH_SIGOPS
|
||||||
|
const sigops = (transactionUtils.countScriptSigops(vin.inner_redeemscript_asm) / 4);
|
||||||
|
if (sigops > MAX_P2SH_SIGOPS) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (['unknown', 'provably_unspendable', 'empty'].includes(vin.prevout?.scriptpubkey_type || '')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// TODO: bad-witness-nonstandard
|
||||||
|
}
|
||||||
|
|
||||||
|
// output validation
|
||||||
|
let opreturnCount = 0;
|
||||||
|
for (const vout of tx.vout) {
|
||||||
|
// scriptpubkey
|
||||||
|
if (['unknown', 'provably_unspendable', 'empty'].includes(vout.scriptpubkey_type)) {
|
||||||
|
// (non-standard output type)
|
||||||
|
return true;
|
||||||
|
} else if (vout.scriptpubkey_type === 'multisig') {
|
||||||
|
if (!DEFAULT_PERMIT_BAREMULTISIG) {
|
||||||
|
// bare-multisig
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const mOfN = parseMultisigScript(vout.scriptpubkey_asm);
|
||||||
|
if (!mOfN || mOfN.n < 1 || mOfN.n > 3 || mOfN.m < 1 || mOfN.m > mOfN.n) {
|
||||||
|
// (non-standard bare multisig threshold)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (vout.scriptpubkey_type === 'op_return') {
|
||||||
|
opreturnCount++;
|
||||||
|
if ((vout.scriptpubkey.length / 2) > MAX_OP_RETURN_RELAY) {
|
||||||
|
// over default datacarrier limit
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dust
|
||||||
|
// (we could probably hardcode this for the different output types...)
|
||||||
|
if (vout.scriptpubkey_type !== 'op_return') {
|
||||||
|
let dustSize = (vout.scriptpubkey.length / 2);
|
||||||
|
// add varint length overhead
|
||||||
|
dustSize += getVarIntLength(dustSize);
|
||||||
|
// add value size
|
||||||
|
dustSize += 8;
|
||||||
|
if (['v0_p2wpkh', 'v0_p2wsh', 'v1_p2tr'].includes(vout.scriptpubkey_type)) {
|
||||||
|
dustSize += 67;
|
||||||
|
} else {
|
||||||
|
dustSize += 148;
|
||||||
|
}
|
||||||
|
if (vout.value < (dustSize * DUST_RELAY_TX_FEE)) {
|
||||||
|
// under minimum output size
|
||||||
|
console.log(`NON-STANDARD | dust | ${vout.value} | ${dustSize} ${dustSize * DUST_RELAY_TX_FEE} `, tx.txid);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// multi-op-return
|
||||||
|
if (opreturnCount > 1) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: non-mandatory-script-verify-flag
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static getNonWitnessSize(tx: TransactionExtended): number {
|
||||||
|
let weight = tx.weight;
|
||||||
|
let hasWitness = false;
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
if (vin.witness?.length) {
|
||||||
|
hasWitness = true;
|
||||||
|
// witness count
|
||||||
|
weight -= getVarIntLength(vin.witness.length);
|
||||||
|
for (const witness of vin.witness) {
|
||||||
|
// witness item size + content
|
||||||
|
weight -= getVarIntLength(witness.length / 2) + (witness.length / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasWitness) {
|
||||||
|
// marker & segwit flag
|
||||||
|
weight -= 2;
|
||||||
|
}
|
||||||
|
return Math.ceil(weight / 4);
|
||||||
|
}
|
||||||
|
|
||||||
static setSegwitSighashFlags(flags: bigint, witness: string[]): bigint {
|
static setSegwitSighashFlags(flags: bigint, witness: string[]): bigint {
|
||||||
for (const w of witness) {
|
for (const w of witness) {
|
||||||
if (this.isDERSig(w)) {
|
if (this.isDERSig(w)) {
|
||||||
@ -245,6 +398,8 @@ export class Common {
|
|||||||
flags |= TransactionFlags.v1;
|
flags |= TransactionFlags.v1;
|
||||||
} else if (tx.version === 2) {
|
} else if (tx.version === 2) {
|
||||||
flags |= TransactionFlags.v2;
|
flags |= TransactionFlags.v2;
|
||||||
|
} else if (tx.version === 3) {
|
||||||
|
flags |= TransactionFlags.v3;
|
||||||
}
|
}
|
||||||
const reusedInputAddresses: { [address: string ]: number } = {};
|
const reusedInputAddresses: { [address: string ]: number } = {};
|
||||||
const reusedOutputAddresses: { [address: string ]: number } = {};
|
const reusedOutputAddresses: { [address: string ]: number } = {};
|
||||||
@ -349,6 +504,10 @@ export class Common {
|
|||||||
flags |= TransactionFlags.batch_payout;
|
flags |= TransactionFlags.batch_payout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.isNonStandard(tx)) {
|
||||||
|
flags |= TransactionFlags.nonstandard;
|
||||||
|
}
|
||||||
|
|
||||||
return Number(flags);
|
return Number(flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 68;
|
private static currentVersion = 70;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -580,6 +580,16 @@ class DatabaseMigration {
|
|||||||
await this.$executeQuery(`INSERT INTO state VALUES('last_bitcoin_block_audit', 0, NULL);`);
|
await this.$executeQuery(`INSERT INTO state VALUES('last_bitcoin_block_audit', 0, NULL);`);
|
||||||
await this.updateToSchemaVersion(68);
|
await this.updateToSchemaVersion(68);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 69 && config.MEMPOOL.NETWORK === 'mainnet') {
|
||||||
|
await this.$executeQuery(this.getCreateAccelerationsTableQuery(), await this.$checkIfTableExists('accelerations'));
|
||||||
|
await this.updateToSchemaVersion(69);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 70 && config.MEMPOOL.NETWORK === 'mainnet') {
|
||||||
|
await this.$executeQuery('ALTER TABLE accelerations MODIFY COLUMN added DATETIME;');
|
||||||
|
await this.updateToSchemaVersion(70);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1123,6 +1133,23 @@ class DatabaseMigration {
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getCreateAccelerationsTableQuery(): string {
|
||||||
|
return `CREATE TABLE IF NOT EXISTS accelerations (
|
||||||
|
txid varchar(65) NOT NULL,
|
||||||
|
added datetime NOT NULL,
|
||||||
|
height int(10) NOT NULL,
|
||||||
|
pool smallint unsigned NULL,
|
||||||
|
effective_vsize int(10) NOT NULL,
|
||||||
|
effective_fee bigint(20) unsigned NOT NULL,
|
||||||
|
boost_rate float unsigned,
|
||||||
|
boost_cost bigint(20) unsigned NOT NULL,
|
||||||
|
PRIMARY KEY (txid),
|
||||||
|
INDEX (added),
|
||||||
|
INDEX (height),
|
||||||
|
INDEX (pool)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8;`;
|
||||||
|
}
|
||||||
|
|
||||||
public async $blocksReindexingTruncate(): Promise<void> {
|
public async $blocksReindexingTruncate(): Promise<void> {
|
||||||
logger.warn(`Truncating pools, blocks, hashrates and difficulty_adjustments tables for re-indexing (using '--reindex-blocks'). You can cancel this command within 5 seconds`);
|
logger.warn(`Truncating pools, blocks, hashrates and difficulty_adjustments tables for re-indexing (using '--reindex-blocks'). You can cancel this command within 5 seconds`);
|
||||||
await Common.sleep$(5000);
|
await Common.sleep$(5000);
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import HashratesRepository from '../../repositories/HashratesRepository';
|
|||||||
import bitcoinClient from '../bitcoin/bitcoin-client';
|
import bitcoinClient from '../bitcoin/bitcoin-client';
|
||||||
import mining from "./mining";
|
import mining from "./mining";
|
||||||
import PricesRepository from '../../repositories/PricesRepository';
|
import PricesRepository from '../../repositories/PricesRepository';
|
||||||
|
import AccelerationRepository from '../../repositories/AccelerationRepository';
|
||||||
|
|
||||||
class MiningRoutes {
|
class MiningRoutes {
|
||||||
public initRoutes(app: Application) {
|
public initRoutes(app: Application) {
|
||||||
@ -34,6 +35,10 @@ class MiningRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/:hash', this.$getBlockAudit)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/timestamp/:timestamp', this.$getHeightFromTimestamp)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'historical-price', this.$getHistoricalPrice)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'historical-price', this.$getHistoricalPrice)
|
||||||
|
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/pool/:slug', this.$getAccelerationsByPool)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/block/:height', this.$getAccelerationsByHeight)
|
||||||
|
.get(config.MEMPOOL.API_URL_PREFIX + 'accelerations/recent/:interval', this.$getRecentAccelerations)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -352,6 +357,52 @@ class MiningRoutes {
|
|||||||
res.status(500).send(e instanceof Error ? e.message : e);
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async $getAccelerationsByPool(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||||
|
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
|
||||||
|
res.status(400).send('Acceleration data is not available.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(200).send(await AccelerationRepository.$getAccelerationInfo(req.params.slug));
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async $getAccelerationsByHeight(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString());
|
||||||
|
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
|
||||||
|
res.status(400).send('Acceleration data is not available.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
|
||||||
|
res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, height));
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async $getRecentAccelerations(req: Request, res: Response): Promise<void> {
|
||||||
|
try {
|
||||||
|
res.header('Pragma', 'public');
|
||||||
|
res.header('Cache-control', 'public');
|
||||||
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||||
|
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || ['testnet', 'signet', 'liquidtestnet', 'liquid'].includes(config.MEMPOOL.NETWORK)) {
|
||||||
|
res.status(400).send('Acceleration data is not available.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, null, req.params.interval));
|
||||||
|
} catch (e) {
|
||||||
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new MiningRoutes();
|
export default new MiningRoutes();
|
||||||
|
|||||||
@ -19,45 +19,90 @@ class RedisCache {
|
|||||||
private client;
|
private client;
|
||||||
private connected = false;
|
private connected = false;
|
||||||
private schemaVersion = 1;
|
private schemaVersion = 1;
|
||||||
|
private redisConfig: any;
|
||||||
|
|
||||||
|
private pauseFlush: boolean = false;
|
||||||
private cacheQueue: MempoolTransactionExtended[] = [];
|
private cacheQueue: MempoolTransactionExtended[] = [];
|
||||||
|
private removeQueue: string[] = [];
|
||||||
|
private rbfCacheQueue: { type: string, txid: string, value: any }[] = [];
|
||||||
|
private rbfRemoveQueue: { type: string, txid: string }[] = [];
|
||||||
private txFlushLimit: number = 10000;
|
private txFlushLimit: number = 10000;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
if (config.REDIS.ENABLED) {
|
if (config.REDIS.ENABLED) {
|
||||||
const redisConfig = {
|
this.redisConfig = {
|
||||||
socket: {
|
socket: {
|
||||||
path: config.REDIS.UNIX_SOCKET_PATH
|
path: config.REDIS.UNIX_SOCKET_PATH
|
||||||
},
|
},
|
||||||
database: NetworkDB[config.MEMPOOL.NETWORK],
|
database: NetworkDB[config.MEMPOOL.NETWORK],
|
||||||
};
|
};
|
||||||
this.client = createClient(redisConfig);
|
|
||||||
this.client.on('error', (e) => {
|
|
||||||
logger.err(`Error in Redis client: ${e instanceof Error ? e.message : e}`);
|
|
||||||
});
|
|
||||||
this.$ensureConnected();
|
this.$ensureConnected();
|
||||||
|
setInterval(() => { this.$ensureConnected(); }, 10000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async $ensureConnected(): Promise<void> {
|
private async $ensureConnected(): Promise<boolean> {
|
||||||
if (!this.connected && config.REDIS.ENABLED) {
|
if (!this.connected && config.REDIS.ENABLED) {
|
||||||
return this.client.connect().then(async () => {
|
try {
|
||||||
this.connected = true;
|
this.client = createClient(this.redisConfig);
|
||||||
logger.info(`Redis client connected`);
|
this.client.on('error', async (e) => {
|
||||||
const version = await this.client.get('schema_version');
|
logger.err(`Error in Redis client: ${e instanceof Error ? e.message : e}`);
|
||||||
if (version !== this.schemaVersion) {
|
this.connected = false;
|
||||||
// schema changed
|
await this.client.disconnect();
|
||||||
// perform migrations or flush DB if necessary
|
});
|
||||||
logger.info(`Redis schema version changed from ${version} to ${this.schemaVersion}`);
|
await this.client.connect().then(async () => {
|
||||||
await this.client.set('schema_version', this.schemaVersion);
|
try {
|
||||||
}
|
const version = await this.client.get('schema_version');
|
||||||
});
|
this.connected = true;
|
||||||
|
if (version !== this.schemaVersion) {
|
||||||
|
// schema changed
|
||||||
|
// perform migrations or flush DB if necessary
|
||||||
|
logger.info(`Redis schema version changed from ${version} to ${this.schemaVersion}`);
|
||||||
|
await this.client.set('schema_version', this.schemaVersion);
|
||||||
|
}
|
||||||
|
logger.info(`Redis client connected`);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
this.connected = false;
|
||||||
|
logger.warn('Failed to connect to Redis');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await this.$onConnected();
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Error connecting to Redis: ' + (e instanceof Error ? e.message : e));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
// test connection
|
||||||
|
await this.client.get('schema_version');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn('Lost connection to Redis: ' + (e instanceof Error ? e.message : e));
|
||||||
|
logger.warn('Attempting to reconnect in 10 seconds');
|
||||||
|
this.connected = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $updateBlocks(blocks: BlockExtended[]) {
|
private async $onConnected(): Promise<void> {
|
||||||
|
await this.$flushTransactions();
|
||||||
|
await this.$removeTransactions([]);
|
||||||
|
await this.$flushRbfQueues();
|
||||||
|
}
|
||||||
|
|
||||||
|
async $updateBlocks(blocks: BlockExtended[]): Promise<void> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
logger.warn(`Failed to update blocks in Redis cache: Redis is not connected`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
await this.client.set('blocks', JSON.stringify(blocks));
|
await this.client.set('blocks', JSON.stringify(blocks));
|
||||||
logger.debug(`Saved latest blocks to Redis cache`);
|
logger.debug(`Saved latest blocks to Redis cache`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -65,9 +110,15 @@ class RedisCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $updateBlockSummaries(summaries: BlockSummary[]) {
|
async $updateBlockSummaries(summaries: BlockSummary[]): Promise<void> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
logger.warn(`Failed to update block summaries in Redis cache: Redis is not connected`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
await this.client.set('block-summaries', JSON.stringify(summaries));
|
await this.client.set('block-summaries', JSON.stringify(summaries));
|
||||||
logger.debug(`Saved latest block summaries to Redis cache`);
|
logger.debug(`Saved latest block summaries to Redis cache`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -75,30 +126,35 @@ class RedisCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $addTransaction(tx: MempoolTransactionExtended) {
|
async $addTransaction(tx: MempoolTransactionExtended): Promise<void> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.cacheQueue.push(tx);
|
this.cacheQueue.push(tx);
|
||||||
if (this.cacheQueue.length >= this.txFlushLimit) {
|
if (this.cacheQueue.length >= this.txFlushLimit) {
|
||||||
await this.$flushTransactions();
|
if (!this.pauseFlush) {
|
||||||
|
await this.$flushTransactions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $flushTransactions() {
|
async $flushTransactions(): Promise<void> {
|
||||||
const success = await this.$addTransactions(this.cacheQueue);
|
if (!config.REDIS.ENABLED) {
|
||||||
if (success) {
|
return;
|
||||||
logger.debug(`Saved ${this.cacheQueue.length} transactions to Redis cache`);
|
}
|
||||||
this.cacheQueue = [];
|
if (!this.cacheQueue.length) {
|
||||||
} else {
|
return;
|
||||||
logger.err(`Failed to save ${this.cacheQueue.length} transactions to Redis cache`);
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
logger.warn(`Failed to add ${this.cacheQueue.length} transactions to Redis cache: Redis not connected`);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private async $addTransactions(newTransactions: MempoolTransactionExtended[]): Promise<boolean> {
|
this.pauseFlush = false;
|
||||||
if (!newTransactions.length) {
|
|
||||||
return true;
|
const toAdd = this.cacheQueue.slice(0, this.txFlushLimit);
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
const msetData = toAdd.map(tx => {
|
||||||
const msetData = newTransactions.map(tx => {
|
|
||||||
const minified: any = { ...tx };
|
const minified: any = { ...tx };
|
||||||
delete minified.hex;
|
delete minified.hex;
|
||||||
for (const vin of minified.vin) {
|
for (const vin of minified.vin) {
|
||||||
@ -112,30 +168,53 @@ class RedisCache {
|
|||||||
return [`mempool:tx:${tx.txid}`, JSON.stringify(minified)];
|
return [`mempool:tx:${tx.txid}`, JSON.stringify(minified)];
|
||||||
});
|
});
|
||||||
await this.client.MSET(msetData);
|
await this.client.MSET(msetData);
|
||||||
return true;
|
// successful, remove transactions from cache queue
|
||||||
|
this.cacheQueue = this.cacheQueue.slice(toAdd.length);
|
||||||
|
logger.debug(`Saved ${toAdd.length} transactions to Redis cache, ${this.cacheQueue.length} left in queue`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(`Failed to add ${newTransactions.length} transactions to Redis cache: ${e instanceof Error ? e.message : e}`);
|
logger.warn(`Failed to add ${toAdd.length} transactions to Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
return false;
|
this.pauseFlush = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $removeTransactions(transactions: string[]) {
|
async $removeTransactions(transactions: string[]): Promise<void> {
|
||||||
try {
|
if (!config.REDIS.ENABLED) {
|
||||||
await this.$ensureConnected();
|
return;
|
||||||
|
}
|
||||||
|
const toRemove = this.removeQueue.concat(transactions);
|
||||||
|
this.removeQueue = [];
|
||||||
|
let failed: string[] = [];
|
||||||
|
let numRemoved = 0;
|
||||||
|
if (this.connected) {
|
||||||
const sliceLength = config.REDIS.BATCH_QUERY_BASE_SIZE;
|
const sliceLength = config.REDIS.BATCH_QUERY_BASE_SIZE;
|
||||||
for (let i = 0; i < Math.ceil(transactions.length / sliceLength); i++) {
|
for (let i = 0; i < Math.ceil(toRemove.length / sliceLength); i++) {
|
||||||
const slice = transactions.slice(i * sliceLength, (i + 1) * sliceLength);
|
const slice = toRemove.slice(i * sliceLength, (i + 1) * sliceLength);
|
||||||
await this.client.unlink(slice.map(txid => `mempool:tx:${txid}`));
|
try {
|
||||||
logger.debug(`Deleted ${slice.length} transactions from the Redis cache`);
|
await this.client.unlink(slice.map(txid => `mempool:tx:${txid}`));
|
||||||
|
numRemoved+= sliceLength;
|
||||||
|
logger.debug(`Deleted ${slice.length} transactions from the Redis cache`);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to remove ${slice.length} transactions from Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
|
failed = failed.concat(slice);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
// concat instead of replace, in case more txs have been added in the meantime
|
||||||
logger.warn(`Failed to remove ${transactions.length} transactions from Redis cache: ${e instanceof Error ? e.message : e}`);
|
this.removeQueue = this.removeQueue.concat(failed);
|
||||||
|
} else {
|
||||||
|
this.removeQueue = this.removeQueue.concat(toRemove);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $setRbfEntry(type: string, txid: string, value: any): Promise<void> {
|
async $setRbfEntry(type: string, txid: string, value: any): Promise<void> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
this.rbfCacheQueue.push({ type, txid, value });
|
||||||
|
logger.warn(`Failed to set RBF ${type} in Redis cache: Redis is not connected`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
await this.client.set(`rbf:${type}:${txid}`, JSON.stringify(value));
|
await this.client.set(`rbf:${type}:${txid}`, JSON.stringify(value));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(`Failed to set RBF ${type} in Redis cache: ${e instanceof Error ? e.message : e}`);
|
logger.warn(`Failed to set RBF ${type} in Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
@ -143,17 +222,55 @@ class RedisCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $removeRbfEntry(type: string, txid: string): Promise<void> {
|
async $removeRbfEntry(type: string, txid: string): Promise<void> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
this.rbfRemoveQueue.push({ type, txid });
|
||||||
|
logger.warn(`Failed to remove RBF ${type} from Redis cache: Redis is not connected`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
await this.client.unlink(`rbf:${type}:${txid}`);
|
await this.client.unlink(`rbf:${type}:${txid}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn(`Failed to remove RBF ${type} from Redis cache: ${e instanceof Error ? e.message : e}`);
|
logger.warn(`Failed to remove RBF ${type} from Redis cache: ${e instanceof Error ? e.message : e}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $getBlocks(): Promise<BlockExtended[]> {
|
private async $flushRbfQueues(): Promise<void> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const toAdd = this.rbfCacheQueue;
|
||||||
|
this.rbfCacheQueue = [];
|
||||||
|
for (const { type, txid, value } of toAdd) {
|
||||||
|
await this.$setRbfEntry(type, txid, value);
|
||||||
|
}
|
||||||
|
logger.debug(`Saved ${toAdd.length} queued RBF entries to the Redis cache`);
|
||||||
|
const toRemove = this.rbfRemoveQueue;
|
||||||
|
this.rbfRemoveQueue = [];
|
||||||
|
for (const { type, txid } of toRemove) {
|
||||||
|
await this.$removeRbfEntry(type, txid);
|
||||||
|
}
|
||||||
|
logger.debug(`Removed ${toRemove.length} queued RBF entries from the Redis cache`);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(`Failed to flush RBF cache event queues after reconnecting to Redis: ${e instanceof Error ? e.message : e}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $getBlocks(): Promise<BlockExtended[]> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
logger.warn(`Failed to retrieve blocks from Redis cache: Redis is not connected`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
const json = await this.client.get('blocks');
|
const json = await this.client.get('blocks');
|
||||||
return JSON.parse(json);
|
return JSON.parse(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -163,8 +280,14 @@ class RedisCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $getBlockSummaries(): Promise<BlockSummary[]> {
|
async $getBlockSummaries(): Promise<BlockSummary[]> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
logger.warn(`Failed to retrieve blocks from Redis cache: Redis is not connected`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
const json = await this.client.get('block-summaries');
|
const json = await this.client.get('block-summaries');
|
||||||
return JSON.parse(json);
|
return JSON.parse(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -174,10 +297,16 @@ class RedisCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $getMempool(): Promise<{ [txid: string]: MempoolTransactionExtended }> {
|
async $getMempool(): Promise<{ [txid: string]: MempoolTransactionExtended }> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
logger.warn(`Failed to retrieve mempool from Redis cache: Redis is not connected`);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
const mempool = {};
|
const mempool = {};
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
const mempoolList = await this.scanKeys<MempoolTransactionExtended>('mempool:tx:*');
|
const mempoolList = await this.scanKeys<MempoolTransactionExtended>('mempool:tx:*');
|
||||||
for (const tx of mempoolList) {
|
for (const tx of mempoolList) {
|
||||||
mempool[tx.key] = tx.value;
|
mempool[tx.key] = tx.value;
|
||||||
@ -191,8 +320,14 @@ class RedisCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async $getRbfEntries(type: string): Promise<any[]> {
|
async $getRbfEntries(type: string): Promise<any[]> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
if (!this.connected) {
|
||||||
|
logger.warn(`Failed to retrieve Rbf ${type}s from Redis cache: Redis is not connected`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
await this.$ensureConnected();
|
|
||||||
const rbfEntries = await this.scanKeys<MempoolTransactionExtended[]>(`rbf:${type}:*`);
|
const rbfEntries = await this.scanKeys<MempoolTransactionExtended[]>(`rbf:${type}:*`);
|
||||||
return rbfEntries;
|
return rbfEntries;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -201,7 +336,10 @@ class RedisCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async $loadCache() {
|
async $loadCache(): Promise<void> {
|
||||||
|
if (!config.REDIS.ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
logger.info('Restoring mempool and blocks data from Redis cache');
|
logger.info('Restoring mempool and blocks data from Redis cache');
|
||||||
// Load block data
|
// Load block data
|
||||||
const loadedBlocks = await this.$getBlocks();
|
const loadedBlocks = await this.$getBlocks();
|
||||||
@ -226,7 +364,7 @@ class RedisCache {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private inflateLoadedTxs(mempool: { [txid: string]: MempoolTransactionExtended }) {
|
private inflateLoadedTxs(mempool: { [txid: string]: MempoolTransactionExtended }): void {
|
||||||
for (const tx of Object.values(mempool)) {
|
for (const tx of Object.values(mempool)) {
|
||||||
for (const vin of tx.vin) {
|
for (const vin of tx.vin) {
|
||||||
if (vin.scriptsig) {
|
if (vin.scriptsig) {
|
||||||
|
|||||||
@ -145,6 +145,10 @@ class TransactionUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public countScriptSigops(script: string, isRawScript: boolean = false, witness: boolean = false): number {
|
public countScriptSigops(script: string, isRawScript: boolean = false, witness: boolean = false): number {
|
||||||
|
if (!script?.length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
let sigops = 0;
|
let sigops = 0;
|
||||||
// count OP_CHECKSIG and OP_CHECKSIGVERIFY
|
// count OP_CHECKSIG and OP_CHECKSIGVERIFY
|
||||||
sigops += (script.match(/OP_CHECKSIG/g)?.length || 0);
|
sigops += (script.match(/OP_CHECKSIG/g)?.length || 0);
|
||||||
|
|||||||
@ -24,6 +24,9 @@ import { ApiPrice } from '../repositories/PricesRepository';
|
|||||||
import accelerationApi from './services/acceleration';
|
import accelerationApi from './services/acceleration';
|
||||||
import mempool from './mempool';
|
import mempool from './mempool';
|
||||||
import statistics from './statistics/statistics';
|
import statistics from './statistics/statistics';
|
||||||
|
import accelerationCosts from './acceleration';
|
||||||
|
import accelerationRepository from '../repositories/AccelerationRepository';
|
||||||
|
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||||
|
|
||||||
interface AddressTransactions {
|
interface AddressTransactions {
|
||||||
mempool: MempoolTransactionExtended[],
|
mempool: MempoolTransactionExtended[],
|
||||||
@ -37,6 +40,7 @@ const wantable = [
|
|||||||
'mempool-blocks',
|
'mempool-blocks',
|
||||||
'live-2h-chart',
|
'live-2h-chart',
|
||||||
'stats',
|
'stats',
|
||||||
|
'tomahawk',
|
||||||
];
|
];
|
||||||
|
|
||||||
class WebsocketHandler {
|
class WebsocketHandler {
|
||||||
@ -121,7 +125,7 @@ class WebsocketHandler {
|
|||||||
for (const sub of wantable) {
|
for (const sub of wantable) {
|
||||||
const key = `want-${sub}`;
|
const key = `want-${sub}`;
|
||||||
const wants = parsedMessage.data.includes(sub);
|
const wants = parsedMessage.data.includes(sub);
|
||||||
if (wants && client['wants'] && !client[key]) {
|
if (wants && !client[key]) {
|
||||||
wantNow[key] = true;
|
wantNow[key] = true;
|
||||||
}
|
}
|
||||||
client[key] = wants;
|
client[key] = wants;
|
||||||
@ -145,6 +149,10 @@ class WebsocketHandler {
|
|||||||
response['da'] = this.socketData['da'];
|
response['da'] = this.socketData['da'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wantNow['want-tomahawk']) {
|
||||||
|
response['tomahawk'] = JSON.stringify(bitcoinApi.getHealthStatus());
|
||||||
|
}
|
||||||
|
|
||||||
if (parsedMessage && parsedMessage['track-tx']) {
|
if (parsedMessage && parsedMessage['track-tx']) {
|
||||||
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-tx'])) {
|
if (/^[a-fA-F0-9]{64}$/.test(parsedMessage['track-tx'])) {
|
||||||
client['track-tx'] = parsedMessage['track-tx'];
|
client['track-tx'] = parsedMessage['track-tx'];
|
||||||
@ -544,6 +552,10 @@ class WebsocketHandler {
|
|||||||
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client['want-tomahawk']) {
|
||||||
|
response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus());
|
||||||
|
}
|
||||||
|
|
||||||
if (client['track-mempool-tx']) {
|
if (client['track-mempool-tx']) {
|
||||||
const tx = newTransactions.find((t) => t.txid === client['track-mempool-tx']);
|
const tx = newTransactions.find((t) => t.txid === client['track-mempool-tx']);
|
||||||
if (tx) {
|
if (tx) {
|
||||||
@ -728,6 +740,28 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
const _memPool = memPool.getMempool();
|
const _memPool = memPool.getMempool();
|
||||||
|
|
||||||
|
const isAccelerated = config.MEMPOOL_SERVICES.ACCELERATIONS && accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations()));
|
||||||
|
|
||||||
|
|
||||||
|
if (isAccelerated) {
|
||||||
|
const blockTxs: { [txid: string]: MempoolTransactionExtended } = {};
|
||||||
|
for (const tx of transactions) {
|
||||||
|
blockTxs[tx.txid] = tx;
|
||||||
|
}
|
||||||
|
const accelerations = Object.values(mempool.getAccelerations());
|
||||||
|
const boostRate = accelerationCosts.calculateBoostRate(
|
||||||
|
accelerations.map(acc => ({ txid: acc.txid, max_bid: acc.feeDelta })),
|
||||||
|
transactions
|
||||||
|
);
|
||||||
|
for (const acc of accelerations) {
|
||||||
|
if (blockTxs[acc.txid]) {
|
||||||
|
const tx = blockTxs[acc.txid];
|
||||||
|
const accelerationInfo = accelerationCosts.getAccelerationInfo(tx, boostRate, transactions);
|
||||||
|
accelerationRepository.$saveAcceleration(accelerationInfo, block, block.extras.pool.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
|
const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
|
||||||
memPool.handleMinedRbfTransactions(rbfTransactions);
|
memPool.handleMinedRbfTransactions(rbfTransactions);
|
||||||
memPool.removeFromSpendMap(transactions);
|
memPool.removeFromSpendMap(transactions);
|
||||||
@ -735,7 +769,6 @@ class WebsocketHandler {
|
|||||||
if (config.MEMPOOL.AUDIT && memPool.isInSync()) {
|
if (config.MEMPOOL.AUDIT && memPool.isInSync()) {
|
||||||
let projectedBlocks;
|
let projectedBlocks;
|
||||||
let auditMempool = _memPool;
|
let auditMempool = _memPool;
|
||||||
const isAccelerated = config.MEMPOOL_SERVICES.ACCELERATIONS && accelerationApi.isAcceleratedBlock(block, Object.values(mempool.getAccelerations()));
|
|
||||||
// template calculation functions have mempool side effects, so calculate audits using
|
// 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
|
// 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;
|
const separateAudit = config.MEMPOOL.ADVANCED_GBT_AUDIT !== config.MEMPOOL.ADVANCED_GBT_MEMPOOL;
|
||||||
@ -886,6 +919,10 @@ class WebsocketHandler {
|
|||||||
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
response['mempool-blocks'] = getCachedResponse('mempool-blocks', mBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (client['want-tomahawk']) {
|
||||||
|
response['tomahawk'] = getCachedResponse('tomahawk', bitcoinApi.getHealthStatus());
|
||||||
|
}
|
||||||
|
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
const trackTxid = client['track-tx'];
|
const trackTxid = client['track-tx'];
|
||||||
if (trackTxid && confirmedTxids[trackTxid]) {
|
if (trackTxid && confirmedTxids[trackTxid]) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ const configFromFile = require(
|
|||||||
interface IConfig {
|
interface IConfig {
|
||||||
MEMPOOL: {
|
MEMPOOL: {
|
||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
|
OFFICIAL: boolean;
|
||||||
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
|
NETWORK: 'mainnet' | 'testnet' | 'signet' | 'liquid' | 'liquidtestnet';
|
||||||
BACKEND: 'esplora' | 'electrum' | 'none';
|
BACKEND: 'esplora' | 'electrum' | 'none';
|
||||||
HTTP_PORT: number;
|
HTTP_PORT: number;
|
||||||
@ -103,6 +104,7 @@ interface IConfig {
|
|||||||
PASSWORD: string;
|
PASSWORD: string;
|
||||||
TIMEOUT: number;
|
TIMEOUT: number;
|
||||||
PID_DIR: string;
|
PID_DIR: string;
|
||||||
|
POOL_SIZE: number;
|
||||||
};
|
};
|
||||||
SYSLOG: {
|
SYSLOG: {
|
||||||
ENABLED: boolean;
|
ENABLED: boolean;
|
||||||
@ -161,6 +163,7 @@ interface IConfig {
|
|||||||
const defaults: IConfig = {
|
const defaults: IConfig = {
|
||||||
'MEMPOOL': {
|
'MEMPOOL': {
|
||||||
'ENABLED': true,
|
'ENABLED': true,
|
||||||
|
'OFFICIAL': false,
|
||||||
'NETWORK': 'mainnet',
|
'NETWORK': 'mainnet',
|
||||||
'BACKEND': 'none',
|
'BACKEND': 'none',
|
||||||
'HTTP_PORT': 8999,
|
'HTTP_PORT': 8999,
|
||||||
@ -240,6 +243,7 @@ const defaults: IConfig = {
|
|||||||
'PASSWORD': 'mempool',
|
'PASSWORD': 'mempool',
|
||||||
'TIMEOUT': 180000,
|
'TIMEOUT': 180000,
|
||||||
'PID_DIR': '',
|
'PID_DIR': '',
|
||||||
|
'POOL_SIZE': 100,
|
||||||
},
|
},
|
||||||
'SYSLOG': {
|
'SYSLOG': {
|
||||||
'ENABLED': true,
|
'ENABLED': true,
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import { execSync } from 'child_process';
|
|||||||
database: config.DATABASE.DATABASE,
|
database: config.DATABASE.DATABASE,
|
||||||
user: config.DATABASE.USERNAME,
|
user: config.DATABASE.USERNAME,
|
||||||
password: config.DATABASE.PASSWORD,
|
password: config.DATABASE.PASSWORD,
|
||||||
connectionLimit: 10,
|
connectionLimit: config.DATABASE.POOL_SIZE,
|
||||||
supportBigNumbers: true,
|
supportBigNumbers: true,
|
||||||
timezone: '+00:00',
|
timezone: '+00:00',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -185,7 +185,8 @@ class Indexer {
|
|||||||
await blocks.$generateCPFPDatabase();
|
await blocks.$generateCPFPDatabase();
|
||||||
await blocks.$generateAuditStats();
|
await blocks.$generateAuditStats();
|
||||||
await auditReplicator.$sync();
|
await auditReplicator.$sync();
|
||||||
await blocks.$classifyBlocks();
|
// do not wait for classify blocks to finish
|
||||||
|
blocks.$classifyBlocks();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.indexerRunning = false;
|
this.indexerRunning = false;
|
||||||
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Indexer failed, trying again in 10 seconds. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
|||||||
@ -208,6 +208,8 @@ export const TransactionFlags = {
|
|||||||
no_rbf: 0b00000010n,
|
no_rbf: 0b00000010n,
|
||||||
v1: 0b00000100n,
|
v1: 0b00000100n,
|
||||||
v2: 0b00001000n,
|
v2: 0b00001000n,
|
||||||
|
v3: 0b00010000n,
|
||||||
|
nonstandard: 0b00100000n,
|
||||||
// address types
|
// address types
|
||||||
p2pk: 0b00000001_00000000n,
|
p2pk: 0b00000001_00000000n,
|
||||||
p2ms: 0b00000010_00000000n,
|
p2ms: 0b00000010_00000000n,
|
||||||
|
|||||||
109
backend/src/repositories/AccelerationRepository.ts
Normal file
109
backend/src/repositories/AccelerationRepository.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { AccelerationInfo } from '../api/acceleration';
|
||||||
|
import { ResultSetHeader, RowDataPacket } from 'mysql2';
|
||||||
|
import DB from '../database';
|
||||||
|
import logger from '../logger';
|
||||||
|
import { IEsploraApi } from '../api/bitcoin/esplora-api.interface';
|
||||||
|
import { Common } from '../api/common';
|
||||||
|
import config from '../config';
|
||||||
|
|
||||||
|
export interface PublicAcceleration {
|
||||||
|
txid: string,
|
||||||
|
height: number,
|
||||||
|
pool: {
|
||||||
|
id: number,
|
||||||
|
slug: string,
|
||||||
|
name: string,
|
||||||
|
},
|
||||||
|
effective_vsize: number,
|
||||||
|
effective_fee: number,
|
||||||
|
boost_rate: number,
|
||||||
|
boost_cost: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccelerationRepository {
|
||||||
|
public async $saveAcceleration(acceleration: AccelerationInfo, block: IEsploraApi.Block, pool_id: number): Promise<void> {
|
||||||
|
try {
|
||||||
|
await DB.query(`
|
||||||
|
INSERT INTO accelerations(txid, added, height, pool, effective_vsize, effective_fee, boost_rate, boost_cost)
|
||||||
|
VALUE (?, FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
added = FROM_UNIXTIME(?),
|
||||||
|
height = ?,
|
||||||
|
pool = ?,
|
||||||
|
effective_vsize = ?,
|
||||||
|
effective_fee = ?,
|
||||||
|
boost_rate = ?,
|
||||||
|
boost_cost = ?
|
||||||
|
`, [
|
||||||
|
acceleration.txSummary.txid,
|
||||||
|
block.timestamp,
|
||||||
|
block.height,
|
||||||
|
pool_id,
|
||||||
|
acceleration.txSummary.effectiveVsize,
|
||||||
|
acceleration.txSummary.effectiveFee,
|
||||||
|
acceleration.targetFeeRate, acceleration.cost,
|
||||||
|
block.timestamp,
|
||||||
|
block.height,
|
||||||
|
pool_id,
|
||||||
|
acceleration.txSummary.effectiveVsize,
|
||||||
|
acceleration.txSummary.effectiveFee,
|
||||||
|
acceleration.targetFeeRate, acceleration.cost,
|
||||||
|
]);
|
||||||
|
} catch (e: any) {
|
||||||
|
logger.err(`Cannot save acceleration (${acceleration.txSummary.txid}) into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
// We don't throw, not a critical issue if we miss some accelerations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $getAccelerationInfo(poolSlug: string | null = null, height: number | null = null, interval: string | null = null): Promise<PublicAcceleration[]> {
|
||||||
|
interval = Common.getSqlInterval(interval);
|
||||||
|
|
||||||
|
if (!config.MEMPOOL_SERVICES.ACCELERATIONS || (interval == null && poolSlug == null && height == null)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let query = `
|
||||||
|
SELECT * FROM accelerations
|
||||||
|
JOIN pools on pools.unique_id = accelerations.pool
|
||||||
|
`;
|
||||||
|
let params: any[] = [];
|
||||||
|
|
||||||
|
if (interval) {
|
||||||
|
query += ` WHERE accelerations.added BETWEEN DATE_SUB(NOW(), INTERVAL ${interval}) AND NOW() `;
|
||||||
|
} else if (height != null) {
|
||||||
|
query += ` WHERE accelerations.height = ? `;
|
||||||
|
params.push(height);
|
||||||
|
} else if (poolSlug != null) {
|
||||||
|
query += ` WHERE pools.slug = ? `;
|
||||||
|
params.push(poolSlug);
|
||||||
|
}
|
||||||
|
|
||||||
|
query += ` ORDER BY accelerations.added DESC `;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [rows] = await DB.query(query, params) as RowDataPacket[][];
|
||||||
|
if (rows?.length) {
|
||||||
|
return rows.map(row => ({
|
||||||
|
txid: row.txid,
|
||||||
|
height: row.height,
|
||||||
|
pool: {
|
||||||
|
id: row.id,
|
||||||
|
slug: row.slug,
|
||||||
|
name: row.name,
|
||||||
|
},
|
||||||
|
effective_vsize: row.effective_vsize,
|
||||||
|
effective_fee: row.effective_fee,
|
||||||
|
boost_rate: row.boost_rate,
|
||||||
|
boost_cost: row.boost_cost,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
logger.err(`Cannot query acceleration info. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AccelerationRepository();
|
||||||
203
backend/src/utils/bitcoin-script.ts
Normal file
203
backend/src/utils/bitcoin-script.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
const opcodes = {
|
||||||
|
OP_FALSE: 0,
|
||||||
|
OP_0: 0,
|
||||||
|
OP_PUSHDATA1: 76,
|
||||||
|
OP_PUSHDATA2: 77,
|
||||||
|
OP_PUSHDATA4: 78,
|
||||||
|
OP_1NEGATE: 79,
|
||||||
|
OP_PUSHNUM_NEG1: 79,
|
||||||
|
OP_RESERVED: 80,
|
||||||
|
OP_TRUE: 81,
|
||||||
|
OP_1: 81,
|
||||||
|
OP_2: 82,
|
||||||
|
OP_3: 83,
|
||||||
|
OP_4: 84,
|
||||||
|
OP_5: 85,
|
||||||
|
OP_6: 86,
|
||||||
|
OP_7: 87,
|
||||||
|
OP_8: 88,
|
||||||
|
OP_9: 89,
|
||||||
|
OP_10: 90,
|
||||||
|
OP_11: 91,
|
||||||
|
OP_12: 92,
|
||||||
|
OP_13: 93,
|
||||||
|
OP_14: 94,
|
||||||
|
OP_15: 95,
|
||||||
|
OP_16: 96,
|
||||||
|
OP_PUSHNUM_1: 81,
|
||||||
|
OP_PUSHNUM_2: 82,
|
||||||
|
OP_PUSHNUM_3: 83,
|
||||||
|
OP_PUSHNUM_4: 84,
|
||||||
|
OP_PUSHNUM_5: 85,
|
||||||
|
OP_PUSHNUM_6: 86,
|
||||||
|
OP_PUSHNUM_7: 87,
|
||||||
|
OP_PUSHNUM_8: 88,
|
||||||
|
OP_PUSHNUM_9: 89,
|
||||||
|
OP_PUSHNUM_10: 90,
|
||||||
|
OP_PUSHNUM_11: 91,
|
||||||
|
OP_PUSHNUM_12: 92,
|
||||||
|
OP_PUSHNUM_13: 93,
|
||||||
|
OP_PUSHNUM_14: 94,
|
||||||
|
OP_PUSHNUM_15: 95,
|
||||||
|
OP_PUSHNUM_16: 96,
|
||||||
|
OP_NOP: 97,
|
||||||
|
OP_VER: 98,
|
||||||
|
OP_IF: 99,
|
||||||
|
OP_NOTIF: 100,
|
||||||
|
OP_VERIF: 101,
|
||||||
|
OP_VERNOTIF: 102,
|
||||||
|
OP_ELSE: 103,
|
||||||
|
OP_ENDIF: 104,
|
||||||
|
OP_VERIFY: 105,
|
||||||
|
OP_RETURN: 106,
|
||||||
|
OP_TOALTSTACK: 107,
|
||||||
|
OP_FROMALTSTACK: 108,
|
||||||
|
OP_2DROP: 109,
|
||||||
|
OP_2DUP: 110,
|
||||||
|
OP_3DUP: 111,
|
||||||
|
OP_2OVER: 112,
|
||||||
|
OP_2ROT: 113,
|
||||||
|
OP_2SWAP: 114,
|
||||||
|
OP_IFDUP: 115,
|
||||||
|
OP_DEPTH: 116,
|
||||||
|
OP_DROP: 117,
|
||||||
|
OP_DUP: 118,
|
||||||
|
OP_NIP: 119,
|
||||||
|
OP_OVER: 120,
|
||||||
|
OP_PICK: 121,
|
||||||
|
OP_ROLL: 122,
|
||||||
|
OP_ROT: 123,
|
||||||
|
OP_SWAP: 124,
|
||||||
|
OP_TUCK: 125,
|
||||||
|
OP_CAT: 126,
|
||||||
|
OP_SUBSTR: 127,
|
||||||
|
OP_LEFT: 128,
|
||||||
|
OP_RIGHT: 129,
|
||||||
|
OP_SIZE: 130,
|
||||||
|
OP_INVERT: 131,
|
||||||
|
OP_AND: 132,
|
||||||
|
OP_OR: 133,
|
||||||
|
OP_XOR: 134,
|
||||||
|
OP_EQUAL: 135,
|
||||||
|
OP_EQUALVERIFY: 136,
|
||||||
|
OP_RESERVED1: 137,
|
||||||
|
OP_RESERVED2: 138,
|
||||||
|
OP_1ADD: 139,
|
||||||
|
OP_1SUB: 140,
|
||||||
|
OP_2MUL: 141,
|
||||||
|
OP_2DIV: 142,
|
||||||
|
OP_NEGATE: 143,
|
||||||
|
OP_ABS: 144,
|
||||||
|
OP_NOT: 145,
|
||||||
|
OP_0NOTEQUAL: 146,
|
||||||
|
OP_ADD: 147,
|
||||||
|
OP_SUB: 148,
|
||||||
|
OP_MUL: 149,
|
||||||
|
OP_DIV: 150,
|
||||||
|
OP_MOD: 151,
|
||||||
|
OP_LSHIFT: 152,
|
||||||
|
OP_RSHIFT: 153,
|
||||||
|
OP_BOOLAND: 154,
|
||||||
|
OP_BOOLOR: 155,
|
||||||
|
OP_NUMEQUAL: 156,
|
||||||
|
OP_NUMEQUALVERIFY: 157,
|
||||||
|
OP_NUMNOTEQUAL: 158,
|
||||||
|
OP_LESSTHAN: 159,
|
||||||
|
OP_GREATERTHAN: 160,
|
||||||
|
OP_LESSTHANOREQUAL: 161,
|
||||||
|
OP_GREATERTHANOREQUAL: 162,
|
||||||
|
OP_MIN: 163,
|
||||||
|
OP_MAX: 164,
|
||||||
|
OP_WITHIN: 165,
|
||||||
|
OP_RIPEMD160: 166,
|
||||||
|
OP_SHA1: 167,
|
||||||
|
OP_SHA256: 168,
|
||||||
|
OP_HASH160: 169,
|
||||||
|
OP_HASH256: 170,
|
||||||
|
OP_CODESEPARATOR: 171,
|
||||||
|
OP_CHECKSIG: 172,
|
||||||
|
OP_CHECKSIGVERIFY: 173,
|
||||||
|
OP_CHECKMULTISIG: 174,
|
||||||
|
OP_CHECKMULTISIGVERIFY: 175,
|
||||||
|
OP_NOP1: 176,
|
||||||
|
OP_NOP2: 177,
|
||||||
|
OP_CHECKLOCKTIMEVERIFY: 177,
|
||||||
|
OP_CLTV: 177,
|
||||||
|
OP_NOP3: 178,
|
||||||
|
OP_CHECKSEQUENCEVERIFY: 178,
|
||||||
|
OP_CSV: 178,
|
||||||
|
OP_NOP4: 179,
|
||||||
|
OP_NOP5: 180,
|
||||||
|
OP_NOP6: 181,
|
||||||
|
OP_NOP7: 182,
|
||||||
|
OP_NOP8: 183,
|
||||||
|
OP_NOP9: 184,
|
||||||
|
OP_NOP10: 185,
|
||||||
|
OP_CHECKSIGADD: 186,
|
||||||
|
OP_PUBKEYHASH: 253,
|
||||||
|
OP_PUBKEY: 254,
|
||||||
|
OP_INVALIDOPCODE: 255,
|
||||||
|
};
|
||||||
|
// add unused opcodes
|
||||||
|
for (let i = 187; i <= 255; i++) {
|
||||||
|
opcodes[`OP_RETURN_${i}`] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { opcodes };
|
||||||
|
|
||||||
|
/** extracts m and n from a multisig script (asm), returns nothing if it is not a multisig script */
|
||||||
|
export function parseMultisigScript(script: string): void | { m: number, n: number } {
|
||||||
|
if (!script) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const ops = script.split(' ');
|
||||||
|
if (ops.length < 3 || ops.pop() !== 'OP_CHECKMULTISIG') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const opN = ops.pop();
|
||||||
|
if (!opN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!opN.startsWith('OP_PUSHNUM_')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const n = parseInt(opN.match(/[0-9]+/)?.[0] || '', 10);
|
||||||
|
if (ops.length < n * 2 + 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// pop n public keys
|
||||||
|
for (let i = 0; i < n; i++) {
|
||||||
|
if (!/^0((2|3)\w{64}|4\w{128})$/.test(ops.pop() || '')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!/^OP_PUSHBYTES_(33|65)$/.test(ops.pop() || '')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const opM = ops.pop();
|
||||||
|
if (!opM) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!opM.startsWith('OP_PUSHNUM_')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const m = parseInt(opM.match(/[0-9]+/)?.[0] || '', 10);
|
||||||
|
|
||||||
|
if (ops.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { m, n };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getVarIntLength(n: number): number {
|
||||||
|
if (n < 0xfd) {
|
||||||
|
return 1;
|
||||||
|
} else if (n <= 0xffff) {
|
||||||
|
return 3;
|
||||||
|
} else if (n <= 0xffffffff) {
|
||||||
|
return 5;
|
||||||
|
} else {
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
"NETWORK": "__MEMPOOL_NETWORK__",
|
"NETWORK": "__MEMPOOL_NETWORK__",
|
||||||
"BACKEND": "__MEMPOOL_BACKEND__",
|
"BACKEND": "__MEMPOOL_BACKEND__",
|
||||||
"ENABLED": __MEMPOOL_ENABLED__,
|
"ENABLED": __MEMPOOL_ENABLED__,
|
||||||
|
"OFFICIAL": __MEMPOOL_OFFICIAL__,
|
||||||
"HTTP_PORT": __MEMPOOL_HTTP_PORT__,
|
"HTTP_PORT": __MEMPOOL_HTTP_PORT__,
|
||||||
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
|
"SPAWN_CLUSTER_PROCS": __MEMPOOL_SPAWN_CLUSTER_PROCS__,
|
||||||
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
"API_URL_PREFIX": "__MEMPOOL_API_URL_PREFIX__",
|
||||||
@ -79,7 +80,8 @@
|
|||||||
"USERNAME": "__DATABASE_USERNAME__",
|
"USERNAME": "__DATABASE_USERNAME__",
|
||||||
"PASSWORD": "__DATABASE_PASSWORD__",
|
"PASSWORD": "__DATABASE_PASSWORD__",
|
||||||
"TIMEOUT": __DATABASE_TIMEOUT__,
|
"TIMEOUT": __DATABASE_TIMEOUT__,
|
||||||
"PID_DIR": "__DATABASE_PID_DIR__"
|
"PID_DIR": "__DATABASE_PID_DIR__",
|
||||||
|
"POOL_SIZE": __DATABASE_POOL_SIZE__
|
||||||
},
|
},
|
||||||
"SYSLOG": {
|
"SYSLOG": {
|
||||||
"ENABLED": __SYSLOG_ENABLED__,
|
"ENABLED": __SYSLOG_ENABLED__,
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
__MEMPOOL_NETWORK__=${MEMPOOL_NETWORK:=mainnet}
|
__MEMPOOL_NETWORK__=${MEMPOOL_NETWORK:=mainnet}
|
||||||
__MEMPOOL_BACKEND__=${MEMPOOL_BACKEND:=electrum}
|
__MEMPOOL_BACKEND__=${MEMPOOL_BACKEND:=electrum}
|
||||||
__MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=true}
|
__MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=true}
|
||||||
|
__MEMPOOL_OFFICIAL__=${MEMPOOL_OFFICIAL:=false}
|
||||||
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
|
__MEMPOOL_HTTP_PORT__=${BACKEND_HTTP_PORT:=8999}
|
||||||
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
|
__MEMPOOL_SPAWN_CLUSTER_PROCS__=${MEMPOOL_SPAWN_CLUSTER_PROCS:=0}
|
||||||
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
|
__MEMPOOL_API_URL_PREFIX__=${MEMPOOL_API_URL_PREFIX:=/api/v1/}
|
||||||
@ -81,6 +82,7 @@ __DATABASE_USERNAME__=${DATABASE_USERNAME:=mempool}
|
|||||||
__DATABASE_PASSWORD__=${DATABASE_PASSWORD:=mempool}
|
__DATABASE_PASSWORD__=${DATABASE_PASSWORD:=mempool}
|
||||||
__DATABASE_TIMEOUT__=${DATABASE_TIMEOUT:=180000}
|
__DATABASE_TIMEOUT__=${DATABASE_TIMEOUT:=180000}
|
||||||
__DATABASE_PID_DIR__=${DATABASE_PID_DIR:=""}
|
__DATABASE_PID_DIR__=${DATABASE_PID_DIR:=""}
|
||||||
|
__DATABASE_POOL_SIZE__=${DATABASE_POOL_SIZE:=100}
|
||||||
|
|
||||||
# SYSLOG
|
# SYSLOG
|
||||||
__SYSLOG_ENABLED__=${SYSLOG_ENABLED:=false}
|
__SYSLOG_ENABLED__=${SYSLOG_ENABLED:=false}
|
||||||
@ -158,6 +160,7 @@ mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
|||||||
sed -i "s!__MEMPOOL_NETWORK__!${__MEMPOOL_NETWORK__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_NETWORK__!${__MEMPOOL_NETWORK__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_BACKEND__!${__MEMPOOL_BACKEND__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_BACKEND__!${__MEMPOOL_BACKEND__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_ENABLED__!${__MEMPOOL_ENABLED__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_ENABLED__!${__MEMPOOL_ENABLED__}!g" mempool-config.json
|
||||||
|
sed -i "s!__MEMPOOL_OFFICIAL__!${__MEMPOOL_OFFICIAL__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_HTTP_PORT__!${__MEMPOOL_HTTP_PORT__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_HTTP_PORT__!${__MEMPOOL_HTTP_PORT__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_SPAWN_CLUSTER_PROCS__!${__MEMPOOL_SPAWN_CLUSTER_PROCS__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_SPAWN_CLUSTER_PROCS__!${__MEMPOOL_SPAWN_CLUSTER_PROCS__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_API_URL_PREFIX__!${__MEMPOOL_API_URL_PREFIX__}!g" mempool-config.json
|
||||||
@ -230,6 +233,7 @@ sed -i "s!__DATABASE_USERNAME__!${__DATABASE_USERNAME__}!g" mempool-config.json
|
|||||||
sed -i "s!__DATABASE_PASSWORD__!${__DATABASE_PASSWORD__}!g" mempool-config.json
|
sed -i "s!__DATABASE_PASSWORD__!${__DATABASE_PASSWORD__}!g" mempool-config.json
|
||||||
sed -i "s!__DATABASE_TIMEOUT__!${__DATABASE_TIMEOUT__}!g" mempool-config.json
|
sed -i "s!__DATABASE_TIMEOUT__!${__DATABASE_TIMEOUT__}!g" mempool-config.json
|
||||||
sed -i "s!__DATABASE_PID_DIR__!${__DATABASE_PID_DIR__}!g" mempool-config.json
|
sed -i "s!__DATABASE_PID_DIR__!${__DATABASE_PID_DIR__}!g" mempool-config.json
|
||||||
|
sed -i "s!__DATABASE_POOL_SIZE__!${__DATABASE_POOL_SIZE__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s!__SYSLOG_ENABLED__!${__SYSLOG_ENABLED__}!g" mempool-config.json
|
sed -i "s!__SYSLOG_ENABLED__!${__SYSLOG_ENABLED__}!g" mempool-config.json
|
||||||
sed -i "s!__SYSLOG_HOST__!${__SYSLOG_HOST__}!g" mempool-config.json
|
sed -i "s!__SYSLOG_HOST__!${__SYSLOG_HOST__}!g" mempool-config.json
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
#backend
|
#backend
|
||||||
cp ./docker/backend/* ./backend/
|
cp -r ./docker/backend/* ./backend/
|
||||||
|
|
||||||
#geoip-data
|
#geoip-data
|
||||||
mkdir -p ./backend/GeoIP/
|
mkdir -p ./backend/GeoIP/
|
||||||
@ -13,8 +13,8 @@ localhostIP="127.0.0.1"
|
|||||||
cp ./docker/frontend/* ./frontend
|
cp ./docker/frontend/* ./frontend
|
||||||
cp ./nginx.conf ./frontend/
|
cp ./nginx.conf ./frontend/
|
||||||
cp ./nginx-mempool.conf ./frontend/
|
cp ./nginx-mempool.conf ./frontend/
|
||||||
sed -i "s/${localhostIP}:80/0.0.0.0:__MEMPOOL_FRONTEND_HTTP_PORT__/g" ./frontend/nginx.conf
|
sed -i"" -e "s/${localhostIP}:80/0.0.0.0:__MEMPOOL_FRONTEND_HTTP_PORT__/g" ./frontend/nginx.conf
|
||||||
sed -i "s/${localhostIP}/0.0.0.0/g" ./frontend/nginx.conf
|
sed -i"" -e "s/${localhostIP}/0.0.0.0/g" ./frontend/nginx.conf
|
||||||
sed -i "s/user nobody;//g" ./frontend/nginx.conf
|
sed -i"" -e "s/user nobody;//g" ./frontend/nginx.conf
|
||||||
sed -i "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" ./frontend/nginx.conf
|
sed -i"" -e "s!/etc/nginx/nginx-mempool.conf!/etc/nginx/conf.d/nginx-mempool.conf!g" ./frontend/nginx.conf
|
||||||
sed -i "s/${localhostIP}:8999/__MEMPOOL_BACKEND_MAINNET_HTTP_HOST__:__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__/g" ./frontend/nginx-mempool.conf
|
sed -i"" -e "s/${localhostIP}:8999/__MEMPOOL_BACKEND_MAINNET_HTTP_HOST__:__MEMPOOL_BACKEND_MAINNET_HTTP_PORT__/g" ./frontend/nginx-mempool.conf
|
||||||
|
|||||||
30
frontend/package-lock.json
generated
30
frontend/package-lock.json
generated
@ -32,7 +32,7 @@
|
|||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.4.3",
|
"echarts": "~5.5.0",
|
||||||
"lightweight-charts": "~3.8.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-echarts": "~16.2.0",
|
"ngx-echarts": "~16.2.0",
|
||||||
"ngx-infinite-scroll": "^16.0.0",
|
"ngx-infinite-scroll": "^16.0.0",
|
||||||
@ -7783,12 +7783,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/echarts": {
|
"node_modules/echarts": {
|
||||||
"version": "5.4.3",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz",
|
||||||
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
|
"integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.3.0",
|
"tslib": "2.3.0",
|
||||||
"zrender": "5.4.4"
|
"zrender": "5.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/echarts/node_modules/tslib": {
|
"node_modules/echarts/node_modules/tslib": {
|
||||||
@ -17319,9 +17319,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/zrender": {
|
"node_modules/zrender": {
|
||||||
"version": "5.4.4",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz",
|
||||||
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
|
"integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "2.3.0"
|
"tslib": "2.3.0"
|
||||||
}
|
}
|
||||||
@ -22822,12 +22822,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"echarts": {
|
"echarts": {
|
||||||
"version": "5.4.3",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz",
|
||||||
"integrity": "sha512-mYKxLxhzy6zyTi/FaEbJMOZU1ULGEQHaeIeuMR5L+JnJTpz+YR03mnnpBhbR4+UYJAgiXgpyTVLffPAjOTLkZA==",
|
"integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "2.3.0",
|
"tslib": "2.3.0",
|
||||||
"zrender": "5.4.4"
|
"zrender": "5.5.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": {
|
"tslib": {
|
||||||
@ -29869,9 +29869,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"zrender": {
|
"zrender": {
|
||||||
"version": "5.4.4",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz",
|
||||||
"integrity": "sha512-0VxCNJ7AGOMCWeHVyTrGzUgrK4asT4ml9PEkeGirAkKNYXYzoPJCLvmyfdoOXcjTHPs10OZVMfD1Rwg16AZyYw==",
|
"integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"tslib": "2.3.0"
|
"tslib": "2.3.0"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -84,7 +84,7 @@
|
|||||||
"browserify": "^17.0.0",
|
"browserify": "^17.0.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"domino": "^2.1.6",
|
"domino": "^2.1.6",
|
||||||
"echarts": "~5.4.3",
|
"echarts": "~5.5.0",
|
||||||
"lightweight-charts": "~3.8.0",
|
"lightweight-charts": "~3.8.0",
|
||||||
"ngx-echarts": "~16.2.0",
|
"ngx-echarts": "~16.2.0",
|
||||||
"ngx-infinite-scroll": "^16.0.0",
|
"ngx-infinite-scroll": "^16.0.0",
|
||||||
|
|||||||
@ -416,7 +416,7 @@
|
|||||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the full license terms for more details.<br>
|
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the full license terms for more details.<br>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
This program incorporates software and other components licensed from third parties. See the full list of <a href="https://mempool.space/3rdpartylicenses.txt">Third-Party Licenses</a> for legal notices from those projects.
|
This program incorporates software and other components licensed from third parties. See the full list of <a href="/3rdpartylicenses.txt">Third-Party Licenses</a> for legal notices from those projects.
|
||||||
</p>
|
</p>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
Trademark Notice<br>
|
Trademark Notice<br>
|
||||||
@ -429,10 +429,6 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="footer-links">
|
|
||||||
<a href="/3rdpartylicenses.txt">Third-party Licenses</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@ -129,7 +129,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="info">
|
<tr class="info">
|
||||||
<td class="info">
|
<td class="info">
|
||||||
<i><small>mempool.space fee</small></i>
|
<i><small>Accelerator Service Fee</small></i>
|
||||||
</td>
|
</td>
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
+{{ estimate.mempoolBaseFee | number }}
|
+{{ estimate.mempoolBaseFee | number }}
|
||||||
@ -141,7 +141,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="info group-last">
|
<tr class="info group-last">
|
||||||
<td class="info">
|
<td class="info">
|
||||||
<i><small>Transaction vsize fee</small></i>
|
<i><small>Transaction Size Surcharge</small></i>
|
||||||
</td>
|
</td>
|
||||||
<td class="amt">
|
<td class="amt">
|
||||||
+{{ estimate.vsizeFee | number }}
|
+{{ estimate.vsizeFee | number }}
|
||||||
|
|||||||
@ -23,6 +23,9 @@
|
|||||||
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
||||||
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 1M
|
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||||
</label>
|
</label>
|
||||||
|
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
||||||
|
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 3M
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -25,7 +25,8 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0px 15px;
|
padding: 0px 15px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: calc(100vh - 250px);
|
height: calc(100vh - 225px);
|
||||||
|
min-height: 400px;
|
||||||
@media (min-width: 992px) {
|
@media (min-width: 992px) {
|
||||||
height: calc(100vh - 150px);
|
height: calc(100vh - 150px);
|
||||||
}
|
}
|
||||||
@ -35,6 +36,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
@media (max-width: 992px) {
|
@media (max-width: 992px) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
|
||||||
import { EChartsOption, graphic } from 'echarts';
|
import { EChartsOption } from 'echarts';
|
||||||
import { Observable, Subscription, combineLatest, fromEvent } from 'rxjs';
|
import { Observable, Subscription, combineLatest, fromEvent } from 'rxjs';
|
||||||
import { map, max, startWith, switchMap, tap } from 'rxjs/operators';
|
import { startWith, switchMap, tap } from 'rxjs/operators';
|
||||||
import { SeoService } from '../../../services/seo.service';
|
import { SeoService } from '../../../services/seo.service';
|
||||||
import { formatNumber } from '@angular/common';
|
import { formatNumber } from '@angular/common';
|
||||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||||
@ -11,7 +11,6 @@ import { MiningService } from '../../../services/mining.service';
|
|||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||||
import { ApiService } from '../../../services/api.service';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-acceleration-fees-graph',
|
selector: 'app-acceleration-fees-graph',
|
||||||
@ -29,7 +28,7 @@ import { ApiService } from '../../../services/api.service';
|
|||||||
})
|
})
|
||||||
export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||||
@Input() widget: boolean = false;
|
@Input() widget: boolean = false;
|
||||||
@Input() height: number | string = '200';
|
@Input() height: number = 300;
|
||||||
@Input() right: number | string = 45;
|
@Input() right: number | string = 45;
|
||||||
@Input() left: number | string = 75;
|
@Input() left: number | string = 75;
|
||||||
@Input() accelerations$: Observable<Acceleration[]>;
|
@Input() accelerations$: Observable<Acceleration[]>;
|
||||||
@ -55,7 +54,6 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
@Inject(LOCALE_ID) public locale: string,
|
@Inject(LOCALE_ID) public locale: string,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private apiService: ApiService,
|
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
private formBuilder: UntypedFormBuilder,
|
private formBuilder: UntypedFormBuilder,
|
||||||
private storageService: StorageService,
|
private storageService: StorageService,
|
||||||
@ -69,104 +67,56 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isLoading = true;
|
|
||||||
if (this.widget) {
|
if (this.widget) {
|
||||||
this.miningWindowPreference = '1m';
|
this.miningWindowPreference = '3m';
|
||||||
this.timespan = this.miningWindowPreference;
|
|
||||||
|
|
||||||
this.statsObservable$ = combineLatest([
|
|
||||||
(this.accelerations$ || this.servicesApiService.getAccelerationHistory$({ timeframe: this.miningWindowPreference })),
|
|
||||||
this.apiService.getHistoricalBlockFees$(this.miningWindowPreference),
|
|
||||||
fromEvent(window, 'resize').pipe(startWith(null)),
|
|
||||||
]).pipe(
|
|
||||||
tap(([accelerations, blockFeesResponse]) => {
|
|
||||||
this.prepareChartOptions(accelerations, blockFeesResponse.body);
|
|
||||||
}),
|
|
||||||
map(([accelerations, blockFeesResponse]) => {
|
|
||||||
return {
|
|
||||||
avgFeesPaid: accelerations.filter(acc => acc.status === 'completed').reduce((total, acc) => total + (acc.feePaid - acc.baseFee - acc.vsizeFee), 0) / accelerations.length
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`);
|
this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`);
|
||||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('1w');
|
this.miningWindowPreference = this.miningService.getDefaultTimespan('3m');
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
|
||||||
this.route.fragment.subscribe((fragment) => {
|
|
||||||
if (['24h', '3d', '1w', '1m'].indexOf(fragment) > -1) {
|
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.statsObservable$ = combineLatest([
|
|
||||||
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
|
||||||
startWith(this.radioGroupForm.controls.dateSpan.value),
|
|
||||||
switchMap((timespan) => {
|
|
||||||
this.isLoading = true;
|
|
||||||
this.storageService.setValue('miningWindowPreference', timespan);
|
|
||||||
this.timespan = timespan;
|
|
||||||
return this.servicesApiService.getAccelerationHistory$({});
|
|
||||||
})
|
|
||||||
),
|
|
||||||
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
|
||||||
startWith(this.radioGroupForm.controls.dateSpan.value),
|
|
||||||
switchMap((timespan) => {
|
|
||||||
return this.apiService.getHistoricalBlockFees$(timespan);
|
|
||||||
})
|
|
||||||
)
|
|
||||||
]).pipe(
|
|
||||||
tap(([accelerations, blockFeesResponse]) => {
|
|
||||||
this.prepareChartOptions(accelerations, blockFeesResponse.body);
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
this.statsSubscription = this.statsObservable$.subscribe(() => {
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||||
this.isLoading = false;
|
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||||
this.cd.markForCheck();
|
|
||||||
|
this.route.fragment.subscribe((fragment) => {
|
||||||
|
if (['24h', '3d', '1w', '1m', '3m'].indexOf(fragment) > -1) {
|
||||||
|
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
this.statsObservable$ = combineLatest([
|
||||||
|
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
||||||
|
startWith(this.radioGroupForm.controls.dateSpan.value),
|
||||||
|
switchMap((timespan) => {
|
||||||
|
if (!this.widget) {
|
||||||
|
this.storageService.setValue('miningWindowPreference', timespan);
|
||||||
|
}
|
||||||
|
this.isLoading = true;
|
||||||
|
this.timespan = timespan;
|
||||||
|
return this.servicesApiService.getAggregatedAccelerationHistory$({timeframe: this.timespan});
|
||||||
|
})
|
||||||
|
),
|
||||||
|
fromEvent(window, 'resize').pipe(startWith(null)),
|
||||||
|
]).pipe(
|
||||||
|
tap(([history]) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.prepareChartOptions(history);
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
this.statsObservable$.subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(accelerations, blockFees) {
|
prepareChartOptions(data) {
|
||||||
let title: object;
|
let title: object;
|
||||||
|
if (data.length === 0) {
|
||||||
const blockAccelerations = {};
|
title = {
|
||||||
|
textStyle: {
|
||||||
for (const acceleration of accelerations) {
|
color: 'grey',
|
||||||
if (acceleration.status === 'completed') {
|
fontSize: 15
|
||||||
if (!blockAccelerations[acceleration.blockHeight]) {
|
},
|
||||||
blockAccelerations[acceleration.blockHeight] = [];
|
text: $localize`No accelerated transaction for this timeframe`,
|
||||||
}
|
left: 'center',
|
||||||
blockAccelerations[acceleration.blockHeight].push(acceleration);
|
top: 'center'
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
let last = null;
|
|
||||||
let minValue = Infinity;
|
|
||||||
let maxValue = 0;
|
|
||||||
const data = [];
|
|
||||||
for (const val of blockFees) {
|
|
||||||
if (last == null) {
|
|
||||||
last = val.avgHeight;
|
|
||||||
}
|
|
||||||
let totalFeeDelta = 0;
|
|
||||||
let totalFeePaid = 0;
|
|
||||||
let totalCount = 0;
|
|
||||||
let blockCount = 0;
|
|
||||||
while (last <= val.avgHeight) {
|
|
||||||
blockCount++;
|
|
||||||
totalFeeDelta += (blockAccelerations[last] || []).reduce((total, acc) => total + acc.feeDelta, 0);
|
|
||||||
totalFeePaid += (blockAccelerations[last] || []).reduce((total, acc) => total + (acc.feePaid - acc.baseFee - acc.vsizeFee), 0);
|
|
||||||
totalCount += (blockAccelerations[last] || []).length;
|
|
||||||
last++;
|
|
||||||
}
|
|
||||||
minValue = Math.min(minValue, val.avgFees);
|
|
||||||
maxValue = Math.max(maxValue, val.avgFees);
|
|
||||||
data.push({
|
|
||||||
...val,
|
|
||||||
feeDelta: totalFeeDelta,
|
|
||||||
avgFeePaid: (totalFeePaid / blockCount),
|
|
||||||
accelerations: totalCount / blockCount,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.chartOptions = {
|
this.chartOptions = {
|
||||||
@ -177,11 +127,11 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
],
|
],
|
||||||
animation: false,
|
animation: false,
|
||||||
grid: {
|
grid: {
|
||||||
height: this.height,
|
height: (this.widget && this.height) ? this.height - 30 : undefined,
|
||||||
|
top: this.widget ? 20 : 40,
|
||||||
|
bottom: this.widget ? 30 : 80,
|
||||||
right: this.right,
|
right: this.right,
|
||||||
left: this.left,
|
left: this.left,
|
||||||
bottom: this.widget ? 30 : 80,
|
|
||||||
top: this.widget ? 20 : (this.isMobile() ? 10 : 50),
|
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
show: !this.isMobile(),
|
show: !this.isMobile(),
|
||||||
@ -197,29 +147,23 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
align: 'left',
|
align: 'left',
|
||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: function (data) {
|
formatter: (ticks) => {
|
||||||
if (data.length <= 0) {
|
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
|
||||||
return '';
|
|
||||||
}
|
|
||||||
let tooltip = `<b style="color: white; margin-left: 2px">
|
|
||||||
${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}</b><br>`;
|
|
||||||
|
|
||||||
for (const tick of data.reverse()) {
|
if (ticks[0].data[1] > 10_000_000) {
|
||||||
if (tick.data[1] >= 1_000_000) {
|
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-0')} BTC<br>`;
|
||||||
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1] / 100_000_000, this.locale, '1.0-3')} BTC<br>`;
|
} else {
|
||||||
} else {
|
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats<br>`;
|
||||||
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')} sats<br>`;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (['24h', '3d'].includes(this.timespan)) {
|
if (['24h', '3d'].includes(this.timespan)) {
|
||||||
tooltip += `<small>` + $localize`At block: ${data[0].data[2]}` + `</small>`;
|
tooltip += `<small>` + $localize`At block: ${ticks[0].data[2]}` + `</small>`;
|
||||||
} else {
|
} else {
|
||||||
tooltip += `<small>` + $localize`Around block: ${data[0].data[2]}` + `</small>`;
|
tooltip += `<small>` + $localize`Around block: ${ticks[0].data[2]}` + `</small>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return tooltip;
|
return tooltip;
|
||||||
}.bind(this)
|
}
|
||||||
},
|
},
|
||||||
xAxis: data.length === 0 ? undefined :
|
xAxis: data.length === 0 ? undefined :
|
||||||
{
|
{
|
||||||
@ -228,7 +172,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
nameTextStyle: {
|
nameTextStyle: {
|
||||||
padding: [10, 0, 0, 0],
|
padding: [10, 0, 0, 0],
|
||||||
},
|
},
|
||||||
type: 'category',
|
type: 'time',
|
||||||
boundaryGap: false,
|
boundaryGap: false,
|
||||||
axisLine: { onZero: true },
|
axisLine: { onZero: true },
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
@ -243,15 +187,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
legend: {
|
legend: {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
name: 'In-band fees per block',
|
name: 'Total bid boost',
|
||||||
inactiveColor: 'rgb(110, 112, 121)',
|
|
||||||
textStyle: {
|
|
||||||
color: 'white',
|
|
||||||
},
|
|
||||||
icon: 'roundRect',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Total bid boost per block',
|
|
||||||
inactiveColor: 'rgb(110, 112, 121)',
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'white',
|
color: 'white',
|
||||||
@ -260,8 +196,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
selected: {
|
selected: {
|
||||||
'In-band fees per block': false,
|
'Total bid boost': true,
|
||||||
'Total bid boost per block': true,
|
|
||||||
},
|
},
|
||||||
show: !this.widget,
|
show: !this.widget,
|
||||||
},
|
},
|
||||||
@ -304,21 +239,13 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
{
|
{
|
||||||
legendHoverLink: false,
|
legendHoverLink: false,
|
||||||
zlevel: 1,
|
zlevel: 1,
|
||||||
name: 'Total bid boost per block',
|
name: 'Total bid boost',
|
||||||
data: data.map(block => [block.timestamp * 1000, block.avgFeePaid, block.avgHeight]),
|
data: data.map(h => {
|
||||||
|
return [h.timestamp * 1000, h.sumBidBoost, h.avgHeight]
|
||||||
|
}),
|
||||||
stack: 'Total',
|
stack: 'Total',
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
barWidth: '100%',
|
barWidth: '90%',
|
||||||
large: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
legendHoverLink: false,
|
|
||||||
zlevel: 0,
|
|
||||||
name: 'In-band fees per block',
|
|
||||||
data: data.map(block => [block.timestamp * 1000, block.avgFees, block.avgHeight]),
|
|
||||||
stack: 'Total',
|
|
||||||
type: 'bar',
|
|
||||||
barWidth: '100%',
|
|
||||||
large: true,
|
large: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -347,17 +274,6 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
visualMap: {
|
|
||||||
type: 'continuous',
|
|
||||||
min: minValue,
|
|
||||||
max: maxValue,
|
|
||||||
dimension: 1,
|
|
||||||
seriesIndex: 1,
|
|
||||||
show: false,
|
|
||||||
inRange: {
|
|
||||||
color: ['#F4511E7f', '#FB8C007f', '#FFB3007f', '#FDD8357f', '#7CB3427f'].reverse() // Gradient color range
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,16 +3,16 @@
|
|||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="accelerator.requests">Requests</h5>
|
<h5 class="card-title" i18n="accelerator.requests">Requests</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div>{{ stats.count }}</div>
|
<div>{{ stats.totalRequested }}</div>
|
||||||
<div class="symbol" i18n="accelerator.total-accelerated">accelerated</div>
|
<div class="symbol" i18n="accelerator.total-accelerated">accelerated</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="item">
|
<div class="item">
|
||||||
<h5 class="card-title" i18n="accelerator.total-boost">Total Bid Boost</h5>
|
<h5 class="card-title" i18n="accelerator.total-boost">Total Bid Boost</h5>
|
||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<div>{{ stats.totalFeesPaid / 100_000_000 | amountShortener: 4 }} <span class="symbol" i18n="shared.btc|BTC">BTC</span></div>
|
<div>{{ stats.totalBidBoost / 100_000_000 | amountShortener: 4 }} <span class="symbol" i18n="shared.btc|BTC">BTC</span></div>
|
||||||
<span class="fiat">
|
<span class="fiat">
|
||||||
<app-fiat [value]="stats.totalFeesPaid"></app-fiat>
|
<app-fiat [value]="stats.totalBidBoost"></app-fiat>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable } from 'rxjs';
|
||||||
import { switchMap } from 'rxjs/operators';
|
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||||
import { ApiService } from '../../../services/api.service';
|
|
||||||
import { StateService } from '../../../services/state.service';
|
export type AccelerationStats = {
|
||||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
totalRequested: number;
|
||||||
|
totalBidBoost: number;
|
||||||
|
successRate: number;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-acceleration-stats',
|
selector: 'app-acceleration-stats',
|
||||||
@ -12,35 +15,13 @@ import { Acceleration } from '../../../interfaces/node-api.interface';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AccelerationStatsComponent implements OnInit {
|
export class AccelerationStatsComponent implements OnInit {
|
||||||
@Input() timespan: '24h' | '1w' | '1m' = '24h';
|
accelerationStats$: Observable<AccelerationStats>;
|
||||||
@Input() accelerations$: Observable<Acceleration[]>;
|
|
||||||
public accelerationStats$: Observable<any>;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private apiService: ApiService,
|
private servicesApiService: ServicesApiServices
|
||||||
private stateService: StateService,
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.accelerationStats$ = this.accelerations$.pipe(
|
this.accelerationStats$ = this.servicesApiService.getAccelerationStats$();
|
||||||
switchMap(accelerations => {
|
|
||||||
let totalFeesPaid = 0;
|
|
||||||
let totalSucceeded = 0;
|
|
||||||
let totalCanceled = 0;
|
|
||||||
for (const acc of accelerations) {
|
|
||||||
if (acc.status === 'completed') {
|
|
||||||
totalSucceeded++;
|
|
||||||
totalFeesPaid += (acc.feePaid - acc.baseFee - acc.vsizeFee) || 0;
|
|
||||||
} else if (acc.status === 'failed') {
|
|
||||||
totalCanceled++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return of({
|
|
||||||
count: totalSucceeded,
|
|
||||||
totalFeesPaid,
|
|
||||||
successRate: (totalSucceeded + totalCanceled > 0) ? ((totalSucceeded / (totalSucceeded + totalCanceled)) * 100) : 0.0,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<div class="container-xl widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
<div class="container-lg widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
||||||
<h1 *ngIf="!widget" class="float-left" i18n="master-page.blocks">Accelerations</h1>
|
<h1 *ngIf="!widget" class="float-left" i18n="master-page.blocks">Accelerations</h1>
|
||||||
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
|
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
|
||||||
|
|
||||||
@ -17,6 +17,7 @@
|
|||||||
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
||||||
<th class="block text-right" i18n="accelerator.block">Block</th>
|
<th class="block text-right" i18n="accelerator.block">Block</th>
|
||||||
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
||||||
|
<th class="date text-right" i18n="" *ngIf="!this.widget">Requested</th>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="accelerations; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
<tbody *ngIf="accelerations; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||||
@ -49,9 +50,13 @@
|
|||||||
</td>
|
</td>
|
||||||
<td class="status text-right">
|
<td class="status text-right">
|
||||||
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
|
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
|
||||||
<span *ngIf="acceleration.status === 'mined' || acceleration.status === 'completed'" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
|
<span *ngIf="acceleration.status === 'mined'" class="badge badge-info" i18n="transaction.rbf.mined">Mined</span>
|
||||||
|
<span *ngIf="acceleration.status === 'completed'" class="badge badge-success" i18n="">Completed</span>
|
||||||
<span *ngIf="acceleration.status === 'failed'" class="badge badge-danger" i18n="accelerator.canceled">Canceled</span>
|
<span *ngIf="acceleration.status === 'failed'" class="badge badge-danger" i18n="accelerator.canceled">Canceled</span>
|
||||||
</td>
|
</td>
|
||||||
|
<td class="date text-right" *ngIf="!this.widget">
|
||||||
|
<app-time kind="since" [time]="acceleration.added" [fastRender]="true"></app-time>
|
||||||
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -75,6 +80,11 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||||
|
[collectionSize]="this.accelerationCount" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page"
|
||||||
|
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||||
|
</ngb-pagination>
|
||||||
|
|
||||||
<ng-template [ngIf]="!widget">
|
<ng-template [ngIf]="!widget">
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
@ -63,16 +63,28 @@ tr, td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.txid {
|
.txid {
|
||||||
@media (max-width: 500px) {
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fee {
|
||||||
|
width: 15%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.block {
|
||||||
|
width: 15%;
|
||||||
|
@media (max-width: 700px) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fee, .block, .status {
|
.status {
|
||||||
width: 15%;
|
width: 13%;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
.date {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,23 +95,12 @@ tr, td, th {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: 30%;
|
max-width: 30%;
|
||||||
@media (max-width: 1060px) and (min-width: 768px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.fee-rate {
|
.fee-rate {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
@media (max-width: 1060px) and (min-width: 768px) {
|
text-align: end !important;
|
||||||
text-align: start !important;
|
@media (max-width: 975px) and (min-width: 768px) {
|
||||||
}
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
text-align: start !important;
|
|
||||||
}
|
|
||||||
@media (max-width: 840px) and (min-width: 768px) {
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
@media (max-width: 410px) {
|
@media (max-width: 410px) {
|
||||||
@ -108,32 +109,31 @@ tr, td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.bid {
|
.bid {
|
||||||
|
text-align: end !important;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
@media (max-width: 840px) and (min-width: 768px) {
|
|
||||||
text-align: start !important;
|
|
||||||
}
|
|
||||||
@media (max-width: 410px) {
|
|
||||||
text-align: start !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
width: 25%;
|
width: 25%;
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
@media (max-width: 1200px) and (min-width: 768px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.fee {
|
.fee {
|
||||||
width: 30%;
|
width: 30%;
|
||||||
@media (max-width: 1060px) and (min-width: 768px) {
|
text-align: end !important;
|
||||||
text-align: start !important;
|
|
||||||
}
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
text-align: start !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.block {
|
.block {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
@media (max-width: 1200px) and (min-width: 768px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.status {
|
.status {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
||||||
import { Observable, catchError, of, switchMap, tap } from 'rxjs';
|
import { combineLatest, BehaviorSubject, Observable, catchError, of, switchMap, tap } from 'rxjs';
|
||||||
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
||||||
import { StateService } from '../../../services/state.service';
|
import { StateService } from '../../../services/state.service';
|
||||||
import { WebsocketService } from '../../../services/websocket.service';
|
import { WebsocketService } from '../../../services/websocket.service';
|
||||||
@ -21,9 +21,10 @@ export class AccelerationsListComponent implements OnInit {
|
|||||||
isLoading = true;
|
isLoading = true;
|
||||||
paginationMaxSize: number;
|
paginationMaxSize: number;
|
||||||
page = 1;
|
page = 1;
|
||||||
lastPage = 1;
|
accelerationCount: number;
|
||||||
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||||
skeletonLines: number[] = [];
|
skeletonLines: number[] = [];
|
||||||
|
pageSubject: BehaviorSubject<number> = new BehaviorSubject(this.page);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
@ -41,33 +42,46 @@ export class AccelerationsListComponent implements OnInit {
|
|||||||
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
|
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
|
||||||
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||||
|
|
||||||
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistory$({ timeframe: '1m' }));
|
this.accelerationList$ = this.pageSubject.pipe(
|
||||||
this.accelerationList$ = accelerationObservable$.pipe(
|
switchMap((page) => {
|
||||||
switchMap(accelerations => {
|
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistoryObserveResponse$({ timeframe: '1y', page: page }));
|
||||||
if (this.pending) {
|
return accelerationObservable$.pipe(
|
||||||
for (const acceleration of accelerations) {
|
switchMap(response => {
|
||||||
acceleration.status = acceleration.status || 'accelerating';
|
let accelerations = response;
|
||||||
}
|
if (response.body) {
|
||||||
}
|
accelerations = response.body;
|
||||||
for (const acc of accelerations) {
|
this.accelerationCount = parseInt(response.headers.get('x-total-count'), 10);
|
||||||
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
|
}
|
||||||
}
|
if (this.pending) {
|
||||||
if (this.widget) {
|
for (const acceleration of accelerations) {
|
||||||
return of(accelerations.slice(-6).reverse());
|
acceleration.status = acceleration.status || 'accelerating';
|
||||||
} else {
|
}
|
||||||
return of(accelerations.reverse());
|
}
|
||||||
}
|
for (const acc of accelerations) {
|
||||||
}),
|
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
|
||||||
catchError((err) => {
|
}
|
||||||
this.isLoading = false;
|
if (this.widget) {
|
||||||
return of([]);
|
return of(accelerations.slice(0, 6));
|
||||||
}),
|
} else {
|
||||||
tap(() => {
|
return of(accelerations);
|
||||||
this.isLoading = false;
|
}
|
||||||
|
}),
|
||||||
|
catchError((err) => {
|
||||||
|
this.isLoading = false;
|
||||||
|
return of([]);
|
||||||
|
}),
|
||||||
|
tap(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageChange(page: number): void {
|
||||||
|
this.pageSubject.next(page);
|
||||||
|
}
|
||||||
|
|
||||||
trackByBlock(index: number, block: BlockExtended): number {
|
trackByBlock(index: number, block: BlockExtended): number {
|
||||||
return block.height;
|
return block.height;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,12 +22,12 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="main-title">
|
<div class="main-title">
|
||||||
<span [attr.data-cy]="'acceleration-stats'" i18n="accelerator.acceleration-stats">Acceleration stats</span>
|
<span [attr.data-cy]="'acceleration-stats'" i18n="accelerator.acceleration-stats">Acceleration stats</span>
|
||||||
<span style="font-size: xx-small" i18n="mining.144-blocks">(1 month)</span>
|
<span style="font-size: xx-small" i18n="mining.3-months">(3 months)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-wrapper">
|
<div class="card-wrapper">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body more-padding">
|
<div class="card-body more-padding">
|
||||||
<app-acceleration-stats timespan="1m" [accelerations$]="minedAccelerations$"></app-acceleration-stats>
|
<app-acceleration-stats></app-acceleration-stats>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -59,7 +59,6 @@
|
|||||||
[height]="graphHeight"
|
[height]="graphHeight"
|
||||||
[attr.data-cy]="'acceleration-fees'"
|
[attr.data-cy]="'acceleration-fees'"
|
||||||
[widget]=true
|
[widget]=true
|
||||||
[accelerations$]="accelerations$"
|
|
||||||
></app-acceleration-fees-graph>
|
></app-acceleration-fees-graph>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-1"><a [attr.data-cy]="'acceleration-fees-view-more'" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div>
|
<div class="mt-1"><a [attr.data-cy]="'acceleration-fees-view-more'" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div>
|
||||||
@ -84,7 +83,7 @@
|
|||||||
<div class="title-link">
|
<div class="title-link">
|
||||||
<h5 class="card-title d-inline" i18n="accelerator.pending-accelerations">Active Accelerations</h5>
|
<h5 class="card-title d-inline" i18n="accelerator.pending-accelerations">Active Accelerations</h5>
|
||||||
</div>
|
</div>
|
||||||
<app-accelerations-list [attr.data-cy]="'pending-accelerations'" [widget]=true [pending]="true" [accelerations$]="pendingAccelerations$"></app-accelerations-list>
|
<app-accelerations-list [attr.data-cy]="'pending-accelerations'" [widget]=true [pending]=true [accelerations$]="pendingAccelerations$"></app-accelerations-list>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -60,7 +60,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
this.accelerations$ = this.stateService.chainTip$.pipe(
|
this.accelerations$ = this.stateService.chainTip$.pipe(
|
||||||
distinctUntilChanged(),
|
distinctUntilChanged(),
|
||||||
switchMap(() => {
|
switchMap(() => {
|
||||||
return this.serviceApiServices.getAccelerationHistory$({ timeframe: '1m' }).pipe(
|
return this.serviceApiServices.getAccelerationHistory$({ timeframe: '3m', page: 1, pageLength: 100}).pipe(
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
return of([]);
|
return of([]);
|
||||||
}),
|
}),
|
||||||
@ -71,7 +71,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
|
|
||||||
this.minedAccelerations$ = this.accelerations$.pipe(
|
this.minedAccelerations$ = this.accelerations$.pipe(
|
||||||
map(accelerations => {
|
map(accelerations => {
|
||||||
return accelerations.filter(acc => ['mined', 'completed', 'failed'].includes(acc.status));
|
return accelerations.filter(acc => ['mined', 'completed'].includes(acc.status));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -128,11 +128,11 @@ export class AcceleratorDashboardComponent implements OnInit {
|
|||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(): void {
|
onResize(): void {
|
||||||
if (window.innerWidth >= 992) {
|
if (window.innerWidth >= 992) {
|
||||||
this.graphHeight = 330;
|
this.graphHeight = 380;
|
||||||
} else if (window.innerWidth >= 768) {
|
} else if (window.innerWidth >= 768) {
|
||||||
this.graphHeight = 245;
|
this.graphHeight = 300;
|
||||||
} else {
|
} else {
|
||||||
this.graphHeight = 210;
|
this.graphHeight = 270;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { SpriteUpdateParams, Square, Color, ViewUpdateParams } from './sprite-ty
|
|||||||
import { hexToColor } from './utils';
|
import { hexToColor } from './utils';
|
||||||
import BlockScene from './block-scene';
|
import BlockScene from './block-scene';
|
||||||
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
import { TransactionStripped } from '../../interfaces/node-api.interface';
|
||||||
|
import { TransactionFlags } from '../../shared/filters.utils';
|
||||||
|
|
||||||
const hoverTransitionTime = 300;
|
const hoverTransitionTime = 300;
|
||||||
const defaultHoverColor = hexToColor('1bd8f4');
|
const defaultHoverColor = hexToColor('1bd8f4');
|
||||||
@ -58,7 +59,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
this.acc = tx.acc;
|
this.acc = tx.acc;
|
||||||
this.rate = tx.rate;
|
this.rate = tx.rate;
|
||||||
this.status = tx.status;
|
this.status = tx.status;
|
||||||
this.bigintFlags = tx.flags ? BigInt(tx.flags) : 0n;
|
this.bigintFlags = tx.flags ? (BigInt(tx.flags) | (this.acc ? TransactionFlags.acceleration : 0n)): 0n;
|
||||||
this.initialised = false;
|
this.initialised = false;
|
||||||
this.vertexArray = scene.vertexArray;
|
this.vertexArray = scene.vertexArray;
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@
|
|||||||
<app-fee-rate [fee]="feeRate"></app-fee-rate>
|
<app-fee-rate [fee]="feeRate"></app-fee-rate>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="effectiveRate && effectiveRate !== feeRate">
|
<tr *ngIf="hasEffectiveRate && effectiveRate != null">
|
||||||
<td *ngIf="!this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
<td *ngIf="!this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||||
<td *ngIf="this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Accelerated fee rate</td>
|
<td *ngIf="this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Accelerated fee rate</td>
|
||||||
<td>
|
<td>
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
text-align: left;
|
text-align: left;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
z-index: 11;
|
||||||
|
|
||||||
&.clickable {
|
&.clickable {
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStra
|
|||||||
import { Position } from '../../components/block-overview-graph/sprite-types.js';
|
import { Position } from '../../components/block-overview-graph/sprite-types.js';
|
||||||
import { Price } from '../../services/price.service';
|
import { Price } from '../../services/price.service';
|
||||||
import { TransactionStripped } from '../../interfaces/node-api.interface.js';
|
import { TransactionStripped } from '../../interfaces/node-api.interface.js';
|
||||||
|
import { TransactionFlags } from '../../shared/filters.utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-overview-tooltip',
|
selector: 'app-block-overview-tooltip',
|
||||||
@ -22,6 +23,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
feeRate = 0;
|
feeRate = 0;
|
||||||
effectiveRate;
|
effectiveRate;
|
||||||
acceleration;
|
acceleration;
|
||||||
|
hasEffectiveRate: boolean = false;
|
||||||
|
|
||||||
tooltipPosition: Position = { x: 0, y: 0 };
|
tooltipPosition: Position = { x: 0, y: 0 };
|
||||||
|
|
||||||
@ -55,6 +57,8 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
this.feeRate = this.fee / this.vsize;
|
this.feeRate = this.fee / this.vsize;
|
||||||
this.effectiveRate = tx.rate;
|
this.effectiveRate = tx.rate;
|
||||||
this.acceleration = tx.acc;
|
this.acceleration = tx.acc;
|
||||||
|
this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05
|
||||||
|
|| (tx.bigintFlags && (tx.bigintFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,7 @@
|
|||||||
<app-time kind="since" [time]="block.timestamp" [fastRender]="true" [precision]="1" minUnit="minute"></app-time></div>
|
<app-time kind="since" [time]="block.timestamp" [fastRender]="true" [precision]="1" minUnit="minute"></app-time></div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
<div class="animated" [class]="showMiningInfo ? 'show' : 'hide'" *ngIf="block.extras?.pool != undefined">
|
<div class="animated" [class]="markHeight === block.height ? 'hide' : 'show'" *ngIf="block.extras?.pool != undefined">
|
||||||
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge badge-primary"
|
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge badge-primary"
|
||||||
[routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
|
[routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
|
||||||
{{ block.extras.pool.name}}</a>
|
{{ block.extras.pool.name}}</a>
|
||||||
|
|||||||
@ -166,7 +166,7 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
.hide {
|
.hide {
|
||||||
opacity: 0;
|
opacity: 0.4;
|
||||||
pointer-events : none;
|
pointer-events : none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,15 +46,13 @@
|
|||||||
<div class="item" *ngIf="showHalving">
|
<div class="item" *ngIf="showHalving">
|
||||||
<h5 class="card-title" i18n="difficulty-box.next-halving">Next Halving</h5>
|
<h5 class="card-title" i18n="difficulty-box.next-halving">Next Halving</h5>
|
||||||
<div class="card-text" i18n-ngbTooltip="mining.average-fee" [ngbTooltip]="halvingBlocksLeft" [tooltipContext]="{ epochData: epochData }" placement="bottom">
|
<div class="card-text" i18n-ngbTooltip="mining.average-fee" [ngbTooltip]="halvingBlocksLeft" [tooltipContext]="{ epochData: epochData }" placement="bottom">
|
||||||
<ng-container *ngTemplateOutlet="epochData.blocksUntilHalving === 1 ? blocksSingular : blocksPlural; context: {$implicit: epochData.blocksUntilHalving }"></ng-container>
|
<span>{{ timeUntilHalving | date }}</span>
|
||||||
<ng-template #blocksPlural let-i i18n="shared.blocks">{{ i }} <span class="shared-block">blocks</span></ng-template>
|
|
||||||
<ng-template #blocksSingular let-i i18n="shared.block">{{ i }} <span class="shared-block">block</span></ng-template>
|
|
||||||
<div class="symbol" *ngIf="blocksUntilHalving === 1; else approxTime">
|
<div class="symbol" *ngIf="blocksUntilHalving === 1; else approxTime">
|
||||||
<app-time kind="until" [time]="epochData.adjustedTimeAvg + now" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time>
|
<app-time kind="until" [time]="epochData.adjustedTimeAvg + now" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time>
|
||||||
</div>
|
</div>
|
||||||
<ng-template #approxTime>
|
<ng-template #approxTime>
|
||||||
<div class="symbol">
|
<div class="symbol">
|
||||||
<span>{{ timeUntilHalving | date }}</span>
|
<app-time kind="until" [time]="timeUntilHalving" [fastRender]="false" [fixedRender]="true" [precision]="0" [numUnits]="2" [units]="['year', 'day', 'hour', 'minute']"></app-time>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<div [ngClass]="{'widget': widget}">
|
<div [ngClass]="{'widget': widget, 'extra-margin-right': widget}">
|
||||||
|
|
||||||
<div class="clearfix"></div>
|
<div class="clearfix"></div>
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,12 @@
|
|||||||
margin-top: 13px;
|
margin-top: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.extra-margin-right {
|
||||||
|
@media (max-width: 380px) {
|
||||||
|
margin-left: -10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
tr, td, th {
|
tr, td, th {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
padding-top: 0.65rem;
|
padding-top: 0.65rem;
|
||||||
|
|||||||
@ -31,7 +31,7 @@ tr, td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.transaction {
|
.transaction {
|
||||||
width: 20%;
|
width: 65%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -57,7 +57,7 @@ tr, td, th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.output {
|
.output {
|
||||||
width: 20%;
|
width: 50%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|||||||
@ -52,7 +52,7 @@
|
|||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home">
|
||||||
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
|
<a class="nav-link" [routerLink]="['/' | relativeUrl]" (click)="collapse()"><fa-icon [icon]="['fas', 'tachometer-alt']" [fixedWidth]="true" i18n-title="master-page.dashboard" title="Dashboard"></fa-icon></a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home" *ngIf="stateService.env.ACCELERATOR">
|
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home" *ngIf="network.val === '' && stateService.env.ACCELERATOR">
|
||||||
<a class="nav-link" [routerLink]="['/acceleration' | relativeUrl]" (click)="collapse()">
|
<a class="nav-link" [routerLink]="['/acceleration' | relativeUrl]" (click)="collapse()">
|
||||||
<fa-icon [icon]="['fas', 'rocket']" [fixedWidth]="true" i18n-title="master-page.accelerator-dashboard" title="Accelerator Dashboard"></fa-icon>
|
<fa-icon [icon]="['fas', 'rocket']" [fixedWidth]="true" i18n-title="master-page.accelerator-dashboard" title="Accelerator Dashboard"></fa-icon>
|
||||||
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
|
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
|
||||||
|
|||||||
@ -8,6 +8,7 @@
|
|||||||
[showFilters]="showFilters"
|
[showFilters]="showFilters"
|
||||||
[filterFlags]="filterFlags"
|
[filterFlags]="filterFlags"
|
||||||
[filterMode]="filterMode"
|
[filterMode]="filterMode"
|
||||||
|
[excludeFilters]="['nonstandard']"
|
||||||
[overrideColors]="overrideColors"
|
[overrideColors]="overrideColors"
|
||||||
(txClickEvent)="onTxClick($event)"
|
(txClickEvent)="onTxClick($event)"
|
||||||
></app-block-overview-graph>
|
></app-block-overview-graph>
|
||||||
|
|||||||
@ -163,7 +163,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
const i = pool.blockCount.toString();
|
const i = pool.blockCount.toString();
|
||||||
if (this.miningWindowPreference === '24h') {
|
if (this.miningWindowPreference === '24h') {
|
||||||
return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
|
return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
|
||||||
pool.lastEstimatedHashrate.toString() + ' PH/s' +
|
pool.lastEstimatedHashrate.toString() + ' ' + miningStats.miningUnits.hashrateUnit +
|
||||||
`<br>` + $localize`${ i }:INTERPOLATION: blocks`;
|
`<br>` + $localize`${ i }:INTERPOLATION: blocks`;
|
||||||
} else {
|
} else {
|
||||||
return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
|
return `<b style="color: white">${pool.name} (${pool.share}%)</b><br>` +
|
||||||
@ -201,7 +201,7 @@ export class PoolRankingComponent implements OnInit {
|
|||||||
const i = totalBlockOther.toString();
|
const i = totalBlockOther.toString();
|
||||||
if (this.miningWindowPreference === '24h') {
|
if (this.miningWindowPreference === '24h') {
|
||||||
return `<b style="color: white">` + $localize`Other (${percentage})` + `</b><br>` +
|
return `<b style="color: white">` + $localize`Other (${percentage})` + `</b><br>` +
|
||||||
totalEstimatedHashrateOther.toString() + ' PH/s' +
|
totalEstimatedHashrateOther.toString() + ' ' + miningStats.miningUnits.hashrateUnit +
|
||||||
`<br>` + $localize`${ i }:INTERPOLATION: blocks`;
|
`<br>` + $localize`${ i }:INTERPOLATION: blocks`;
|
||||||
} else {
|
} else {
|
||||||
return `<b style="color: white">` + $localize`Other (${percentage})` + `</b><br>` +
|
return `<b style="color: white">` + $localize`Other (${percentage})` + `</b><br>` +
|
||||||
|
|||||||
@ -0,0 +1,32 @@
|
|||||||
|
<div class="tomahawk container-xl dashboard-container">
|
||||||
|
<div class="links">
|
||||||
|
<span>Status</span>
|
||||||
|
<a [routerLink]='"/network"'>Live</a>
|
||||||
|
</div>
|
||||||
|
<h1 class="dashboard-title">Node Status</h1>
|
||||||
|
|
||||||
|
<ng-container *ngIf="(hosts$ | async) as hosts">
|
||||||
|
<div class="status-panel">
|
||||||
|
<table class="status-table table table-fixed table-borderless table-striped" *ngIf="(tip$ | async) as tip">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th class="rank"></th>
|
||||||
|
<th class="flag"></th>
|
||||||
|
<th class="host">Host</th>
|
||||||
|
<th class="rtt only-small">RTT</th>
|
||||||
|
<th class="rtt only-large">RTT</th>
|
||||||
|
<th class="height">Height</th>
|
||||||
|
</tr>
|
||||||
|
<tr *ngFor="let host of hosts; let i = index; trackBy: trackByFn">
|
||||||
|
<td class="rank">{{ i + 1 }}</td>
|
||||||
|
<td class="flag">{{ host.active ? '⭐️' : host.flag }}</td>
|
||||||
|
<td class="host">{{ host.link }}</td>
|
||||||
|
<td class="rtt only-small">{{ (host.rtt / 1000) | number : '1.1-1' }} {{ host.rtt == null ? '' : 's'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
|
||||||
|
<td class="rtt only-large">{{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
|
||||||
|
<td class="height">{{ host.latestHeight }} {{ !host.checked ? '⏳' : (host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < tip ? '🟧' : '✅')) }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,72 @@
|
|||||||
|
.tomahawk {
|
||||||
|
.links {
|
||||||
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
a, span {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-panel {
|
||||||
|
max-width: 720px;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 2em;
|
||||||
|
padding: 1em;
|
||||||
|
background: #24273e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-table {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
td, th {
|
||||||
|
padding: 0.25em;
|
||||||
|
|
||||||
|
&.rank, &.flag {
|
||||||
|
width: 28px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
&.rtt, &.height {
|
||||||
|
width: 92px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
&.only-small {
|
||||||
|
display: table-cell;
|
||||||
|
&.rtt {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.only-large {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.height {
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
&.host {
|
||||||
|
width: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
&.rank, &.flag {
|
||||||
|
width: 32px;
|
||||||
|
}
|
||||||
|
&.rtt, &.height {
|
||||||
|
width: 96px;
|
||||||
|
}
|
||||||
|
&.only-small {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
&.only-large {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
import { Component, OnInit, ChangeDetectionStrategy, SecurityContext } from '@angular/core';
|
||||||
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
|
import { Observable, Subject, map } from 'rxjs';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { HealthCheckHost } from '../../interfaces/websocket.interface';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-server-health',
|
||||||
|
templateUrl: './server-health.component.html',
|
||||||
|
styleUrls: ['./server-health.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ServerHealthComponent implements OnInit {
|
||||||
|
hosts$: Observable<HealthCheckHost[]>;
|
||||||
|
tip$: Subject<number>;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private stateService: StateService,
|
||||||
|
public sanitizer: DomSanitizer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.hosts$ = this.stateService.serverHealth$.pipe(
|
||||||
|
map((hosts) => {
|
||||||
|
const subpath = window.location.pathname.slice(0, -6);
|
||||||
|
for (const host of hosts) {
|
||||||
|
let statusUrl = '';
|
||||||
|
let linkHost = '';
|
||||||
|
if (host.socket) {
|
||||||
|
statusUrl = 'https://' + window.location.hostname + subpath + '/status';
|
||||||
|
linkHost = window.location.hostname + subpath;
|
||||||
|
} else {
|
||||||
|
const hostUrl = new URL(host.host);
|
||||||
|
statusUrl = 'https://' + hostUrl.hostname + subpath + '/status';
|
||||||
|
linkHost = hostUrl.hostname + subpath;
|
||||||
|
}
|
||||||
|
host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl));
|
||||||
|
host.link = linkHost;
|
||||||
|
host.flag = this.parseFlag(host.host);
|
||||||
|
}
|
||||||
|
return hosts;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
this.tip$ = this.stateService.chainTip$;
|
||||||
|
this.websocketService.want(['blocks', 'tomahawk']);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackByFn(index: number, host: HealthCheckHost): string {
|
||||||
|
return host.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseFlag(host: string): string {
|
||||||
|
if (host.includes('.fra.')) {
|
||||||
|
return '🇩🇪';
|
||||||
|
} else if (host.includes('.tk7.')) {
|
||||||
|
return '🇯🇵';
|
||||||
|
} else if (host.includes('.fmt.')) {
|
||||||
|
return '🇺🇸';
|
||||||
|
} else if (host.includes('.va1.')) {
|
||||||
|
return '🇺🇸';
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,16 @@
|
|||||||
|
<div class="tomahawk">
|
||||||
|
<div class="container-xl dashboard-container">
|
||||||
|
<div class="links">
|
||||||
|
<a [routerLink]='"/nodes"'>Status</a>
|
||||||
|
<span>Live</span>
|
||||||
|
</div>
|
||||||
|
<h1 class="dashboard-title">Live Network</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ng-container *ngFor="let host of hosts; trackBy: trackByFn">
|
||||||
|
<h5 [id]="host.host" class="hostLink">
|
||||||
|
<a [href]="'https://' + host.link">{{ host.link }}</a>
|
||||||
|
</h5>
|
||||||
|
<iframe class="mempoolStatus" [src]="host.statusPage"></iframe>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
.tomahawk {
|
||||||
|
.links {
|
||||||
|
float: right;
|
||||||
|
text-align: right;
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
a, span {
|
||||||
|
margin-left: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard-title {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mempoolStatus {
|
||||||
|
width: 100%;
|
||||||
|
height: 270px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hostLink {
|
||||||
|
text-align: center;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,80 @@
|
|||||||
|
import { Component, OnInit, ChangeDetectionStrategy, SecurityContext, OnDestroy, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
|
import { Observable, Subject, Subscription, map, tap } from 'rxjs';
|
||||||
|
import { StateService } from '../../services/state.service';
|
||||||
|
import { HealthCheckHost } from '../../interfaces/websocket.interface';
|
||||||
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-server-status',
|
||||||
|
templateUrl: './server-status.component.html',
|
||||||
|
styleUrls: ['./server-status.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ServerStatusComponent implements OnInit, OnDestroy {
|
||||||
|
tip$: Subject<number>;
|
||||||
|
hosts: HealthCheckHost[] = [];
|
||||||
|
hostSubscription: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private websocketService: WebsocketService,
|
||||||
|
private stateService: StateService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
public sanitizer: DomSanitizer,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.hostSubscription = this.stateService.serverHealth$.pipe(
|
||||||
|
map((hosts) => {
|
||||||
|
const subpath = window.location.pathname.slice(0, -8);
|
||||||
|
for (const host of hosts) {
|
||||||
|
let statusUrl = '';
|
||||||
|
let linkHost = '';
|
||||||
|
if (host.socket) {
|
||||||
|
statusUrl = 'https://' + window.location.hostname + subpath + '/status';
|
||||||
|
linkHost = window.location.hostname + subpath;
|
||||||
|
} else {
|
||||||
|
const hostUrl = new URL(host.host);
|
||||||
|
statusUrl = 'https://' + hostUrl.hostname + subpath + '/status';
|
||||||
|
linkHost = hostUrl.hostname + subpath;
|
||||||
|
}
|
||||||
|
host.statusPage = this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, statusUrl));
|
||||||
|
host.link = linkHost;
|
||||||
|
}
|
||||||
|
return hosts;
|
||||||
|
}),
|
||||||
|
tap((hosts) => {
|
||||||
|
if (this.hosts.length !== hosts.length) {
|
||||||
|
this.hosts = hosts.sort((a,b) => {
|
||||||
|
const aParts = (a.host?.split('.') || []).reverse();
|
||||||
|
const bParts = (b.host?.split('.') || []).reverse();
|
||||||
|
let i = 0;
|
||||||
|
while (i < Math.max(aParts.length, bParts.length)) {
|
||||||
|
if (aParts[i] && !bParts[i]) {
|
||||||
|
return 1;
|
||||||
|
} else if (bParts[i] && !aParts[i]) {
|
||||||
|
return -1;
|
||||||
|
} else if (aParts[i] !== bParts[i]) {
|
||||||
|
return aParts[i].localeCompare(bParts[i]);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
).subscribe();
|
||||||
|
this.tip$ = this.stateService.chainTip$;
|
||||||
|
this.websocketService.want(['blocks', 'tomahawk']);
|
||||||
|
}
|
||||||
|
|
||||||
|
trackByFn(index: number, host: HealthCheckHost): string {
|
||||||
|
return host.host;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy(): void {
|
||||||
|
this.hosts = [];
|
||||||
|
this.hostSubscription.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -26,6 +26,7 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
|
|||||||
import { Price, PriceService } from '../../services/price.service';
|
import { Price, PriceService } from '../../services/price.service';
|
||||||
import { isFeatureActive } from '../../bitcoin.utils';
|
import { isFeatureActive } from '../../bitcoin.utils';
|
||||||
import { ServicesApiServices } from '../../services/services-api.service';
|
import { ServicesApiServices } from '../../services/services-api.service';
|
||||||
|
import { EnterpriseService } from '../../services/enterprise.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-transaction',
|
selector: 'app-transaction',
|
||||||
@ -116,12 +117,15 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
private servicesApiService: ServicesApiServices,
|
private servicesApiService: ServicesApiServices,
|
||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private priceService: PriceService,
|
private priceService: PriceService,
|
||||||
private storageService: StorageService
|
private storageService: StorageService,
|
||||||
|
private enterpriseService: EnterpriseService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
this.acceleratorAvailable = this.stateService.env.OFFICIAL_MEMPOOL_SPACE && this.stateService.env.ACCELERATOR && this.stateService.network === '';
|
||||||
|
|
||||||
|
this.enterpriseService.page();
|
||||||
|
|
||||||
this.websocketService.want(['blocks', 'mempool-blocks']);
|
this.websocketService.want(['blocks', 'mempool-blocks']);
|
||||||
this.stateService.networkChanged$.subscribe(
|
this.stateService.networkChanged$.subscribe(
|
||||||
(network) => {
|
(network) => {
|
||||||
@ -527,6 +531,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (!this.txId) {
|
if (!this.txId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.enterpriseService.goal(8);
|
||||||
this.showAccelerationSummary = true && this.acceleratorAvailable;
|
this.showAccelerationSummary = true && this.acceleratorAvailable;
|
||||||
this.scrollIntoAccelPreview = !this.scrollIntoAccelPreview;
|
this.scrollIntoAccelPreview = !this.scrollIntoAccelPreview;
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -427,6 +427,7 @@
|
|||||||
|
|
||||||
.card-title-liquid {
|
.card-title-liquid {
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.in-progress-message {
|
.in-progress-message {
|
||||||
|
|||||||
@ -1017,7 +1017,7 @@ export const restApiDocsData = [
|
|||||||
fragment: "get-address-transactions",
|
fragment: "get-address-transactions",
|
||||||
title: "GET Address Transactions",
|
title: "GET Address Transactions",
|
||||||
description: {
|
description: {
|
||||||
default: "Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using <code>:last_seen_txid</code> (see below)."
|
default: "Get transaction history for the specified address/scripthash, sorted with newest first. Returns up to 50 mempool transactions plus the first 25 confirmed transactions. You can request more confirmed transactions using an <code>after_txid</code> query parameter."
|
||||||
},
|
},
|
||||||
urlString: "/address/:address/txs",
|
urlString: "/address/:address/txs",
|
||||||
showConditions: bitcoinNetworks.concat(liquidNetworks),
|
showConditions: bitcoinNetworks.concat(liquidNetworks),
|
||||||
@ -10070,8 +10070,7 @@ export const restApiDocsData = [
|
|||||||
"id": 89,
|
"id": 89,
|
||||||
"user_id": 1,
|
"user_id": 1,
|
||||||
"txid": "ae2639469ec000ed1d14e2550cbb01794e1cd288a00cdc7cce18398ba3cc2ffe",
|
"txid": "ae2639469ec000ed1d14e2550cbb01794e1cd288a00cdc7cce18398ba3cc2ffe",
|
||||||
"status": "failed",
|
"status": "failed"
|
||||||
"estimated_fee": 247,
|
|
||||||
"fee_paid": 0,
|
"fee_paid": 0,
|
||||||
"added": 1706378712,
|
"added": 1706378712,
|
||||||
"last_updated": 1706378712,
|
"last_updated": 1706378712,
|
||||||
@ -10100,8 +10099,7 @@ export const restApiDocsData = [
|
|||||||
"id": 88,
|
"id": 88,
|
||||||
"user_id": 1,
|
"user_id": 1,
|
||||||
"txid": "c5840e89173331760e959a190b24e2a289121277ed7f8a095fe289b37cee9fde",
|
"txid": "c5840e89173331760e959a190b24e2a289121277ed7f8a095fe289b37cee9fde",
|
||||||
"status": "completed",
|
"status": "completed"
|
||||||
"estimated_fee": 223,
|
|
||||||
"fee_paid": 140019,
|
"fee_paid": 140019,
|
||||||
"added": 1706378704,
|
"added": 1706378704,
|
||||||
"last_updated": 1706380231,
|
"last_updated": 1706380231,
|
||||||
@ -10130,8 +10128,7 @@ export const restApiDocsData = [
|
|||||||
"id": 87,
|
"id": 87,
|
||||||
"user_id": 1,
|
"user_id": 1,
|
||||||
"txid": "178b5b9b310f0d667d7ea563a2cdcc17bc8cd15261b58b1653860a724ca83458",
|
"txid": "178b5b9b310f0d667d7ea563a2cdcc17bc8cd15261b58b1653860a724ca83458",
|
||||||
"status": "completed",
|
"status": "completed"
|
||||||
"estimated_fee": 115,
|
|
||||||
"fee_paid": 90062,
|
"fee_paid": 90062,
|
||||||
"added": 1706378684,
|
"added": 1706378684,
|
||||||
"last_updated": 1706380231,
|
"last_updated": 1706380231,
|
||||||
|
|||||||
@ -393,8 +393,11 @@ export interface Acceleration {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface AccelerationHistoryParams {
|
export interface AccelerationHistoryParams {
|
||||||
timeframe?: string,
|
status?: string;
|
||||||
status?: string,
|
timeframe?: string;
|
||||||
pool?: string,
|
poolUniqueId?: number;
|
||||||
blockHash?: string,
|
blockHash?: string;
|
||||||
|
blockHeight?: number;
|
||||||
|
page?: number;
|
||||||
|
pageLength?: number;
|
||||||
}
|
}
|
||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { SafeResourceUrl } from '@angular/platform-browser';
|
||||||
import { ILoadingIndicators } from '../services/state.service';
|
import { ILoadingIndicators } from '../services/state.service';
|
||||||
import { Transaction } from './electrs.interface';
|
import { Transaction } from './electrs.interface';
|
||||||
import { BlockExtended, DifficultyAdjustment, RbfTree } from './node-api.interface';
|
import { BlockExtended, DifficultyAdjustment, RbfTree } from './node-api.interface';
|
||||||
@ -121,3 +122,17 @@ export interface Recommendedfees {
|
|||||||
minimumFee: number;
|
minimumFee: number;
|
||||||
economyFee: number;
|
economyFee: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface HealthCheckHost {
|
||||||
|
host: string;
|
||||||
|
active: boolean;
|
||||||
|
rtt: number;
|
||||||
|
latestHeight: number;
|
||||||
|
socket: boolean;
|
||||||
|
outOfSync: boolean;
|
||||||
|
unreachable: boolean;
|
||||||
|
checked: boolean;
|
||||||
|
link?: string;
|
||||||
|
statusPage?: SafeResourceUrl;
|
||||||
|
flag?: string;
|
||||||
|
}
|
||||||
@ -88,7 +88,7 @@ export class NodesMap implements OnInit, OnChanges {
|
|||||||
node.public_key,
|
node.public_key,
|
||||||
node.alias,
|
node.alias,
|
||||||
node.capacity,
|
node.capacity,
|
||||||
node.active_channel_count,
|
node.channels,
|
||||||
node.country,
|
node.country,
|
||||||
node.iso_code,
|
node.iso_code,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -64,8 +64,8 @@
|
|||||||
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
||||||
<th class="city text-right" i18n="lightning.location">Location</th>
|
<th class="city text-right" i18n="lightning.location">Location</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="nodes$ | async as countryNodes; else skeleton">
|
<tbody *ngIf="nodesPagination$ | async as countryNodes; else skeleton">
|
||||||
<tr *ngFor="let node of countryNodes.nodes; let i= index; trackBy: trackByPublicKey">
|
<tr *ngFor="let node of countryNodes; let i= index; trackBy: trackByPublicKey">
|
||||||
<td class="alias text-left text-truncate">
|
<td class="alias text-left text-truncate">
|
||||||
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
||||||
</td>
|
</td>
|
||||||
@ -116,5 +116,10 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<ngb-pagination *ngIf="nodes$ | async as countryNodes" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||||
|
[collectionSize]="countryNodes.nodes.length" [rotate]="true" [maxSize]="maxSize" [pageSize]="pageSize" [(page)]="page"
|
||||||
|
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||||
|
</ngb-pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -22,14 +22,14 @@
|
|||||||
|
|
||||||
.timestamp-first {
|
.timestamp-first {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 1060px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.timestamp-update {
|
.timestamp-update {
|
||||||
width: 16%;
|
width: 16%;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 1060px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@
|
|||||||
|
|
||||||
.city {
|
.city {
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 675px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { map, Observable, share } from 'rxjs';
|
import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { getFlagEmoji } from '../../shared/common.utils';
|
import { getFlagEmoji } from '../../shared/common.utils';
|
||||||
@ -15,6 +15,12 @@ import { GeolocationData } from '../../shared/components/geolocation/geolocation
|
|||||||
export class NodesPerCountry implements OnInit {
|
export class NodesPerCountry implements OnInit {
|
||||||
nodes$: Observable<any>;
|
nodes$: Observable<any>;
|
||||||
country: {name: string, flag: string};
|
country: {name: string, flag: string};
|
||||||
|
nodesPagination$: Observable<any>;
|
||||||
|
startingIndexSubject: BehaviorSubject<number> = new BehaviorSubject(0);
|
||||||
|
page = 1;
|
||||||
|
pageSize = 15;
|
||||||
|
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
skeletonLines: number[] = [];
|
skeletonLines: number[] = [];
|
||||||
|
|
||||||
@ -23,7 +29,7 @@ export class NodesPerCountry implements OnInit {
|
|||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < 20; ++i) {
|
for (let i = 0; i < this.pageSize; ++i) {
|
||||||
this.skeletonLines.push(i);
|
this.skeletonLines.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +37,7 @@ export class NodesPerCountry implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
|
this.nodes$ = this.apiService.getNodeForCountry$(this.route.snapshot.params.country)
|
||||||
.pipe(
|
.pipe(
|
||||||
|
tap(() => this.isLoading = true),
|
||||||
map(response => {
|
map(response => {
|
||||||
this.seoService.setTitle($localize`Lightning nodes in ${response.country.en}`);
|
this.seoService.setTitle($localize`Lightning nodes in ${response.country.en}`);
|
||||||
this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-country:Explore all the Lightning nodes hosted in ${response.country.en} and see an overview of each node's capacity, number of open channels, and more.`);
|
this.seoService.setDescription($localize`:@@meta.description.lightning.nodes-country:Explore all the Lightning nodes hosted in ${response.country.en} and see an overview of each node's capacity, number of open channels, and more.`);
|
||||||
@ -87,11 +94,21 @@ export class NodesPerCountry implements OnInit {
|
|||||||
ispCount: Object.keys(isps).length
|
ispCount: Object.keys(isps).length
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
tap(() => this.isLoading = false),
|
||||||
share()
|
share()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.nodesPagination$ = combineLatest([this.nodes$, this.startingIndexSubject]).pipe(
|
||||||
|
map(([response, startingIndex]) => response.nodes.slice(startingIndex, startingIndex + this.pageSize))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByPublicKey(index: number, node: any): string {
|
trackByPublicKey(index: number, node: any): string {
|
||||||
return node.public_key;
|
return node.public_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageChange(page: number): void {
|
||||||
|
this.startingIndexSubject.next((page - 1) * this.pageSize);
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,8 +61,8 @@
|
|||||||
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
<th class="channels text-right" i18n="lightning.channels">Channels</th>
|
||||||
<th class="city text-right" i18n="lightning.location">Location</th>
|
<th class="city text-right" i18n="lightning.location">Location</th>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody *ngIf="nodes$ | async as ispNodes; else skeleton">
|
<tbody *ngIf="nodesPagination$ | async as ispNodes; else skeleton">
|
||||||
<tr *ngFor="let node of ispNodes.nodes; let i= index; trackBy: trackByPublicKey">
|
<tr *ngFor="let node of ispNodes; let i= index; trackBy: trackByPublicKey">
|
||||||
<td class="alias text-left text-truncate">
|
<td class="alias text-left text-truncate">
|
||||||
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
<a [routerLink]="['/lightning/node/' | relativeUrl, node.public_key]">{{ node.alias }}</a>
|
||||||
</td>
|
</td>
|
||||||
@ -113,5 +113,10 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<ngb-pagination *ngIf="nodes$ | async as ispNodes" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||||
|
[collectionSize]="ispNodes.nodes.length" [rotate]="true" [maxSize]="maxSize" [pageSize]="pageSize" [(page)]="page"
|
||||||
|
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||||
|
</ngb-pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
.timestamp-first {
|
.timestamp-first {
|
||||||
width: 20%;
|
width: 20%;
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 1060px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -32,7 +32,7 @@
|
|||||||
.timestamp-update {
|
.timestamp-update {
|
||||||
width: 16%;
|
width: 16%;
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 1060px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,7 +56,7 @@
|
|||||||
.city {
|
.city {
|
||||||
max-width: 150px;
|
max-width: 150px;
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
@media (max-width: 675px) {
|
||||||
display: none
|
display: none
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { map, Observable, share } from 'rxjs';
|
import { BehaviorSubject, combineLatest, map, Observable, share, tap } from 'rxjs';
|
||||||
import { ApiService } from '../../services/api.service';
|
import { ApiService } from '../../services/api.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { getFlagEmoji } from '../../shared/common.utils';
|
import { getFlagEmoji } from '../../shared/common.utils';
|
||||||
@ -15,6 +15,12 @@ import { GeolocationData } from '../../shared/components/geolocation/geolocation
|
|||||||
export class NodesPerISP implements OnInit {
|
export class NodesPerISP implements OnInit {
|
||||||
nodes$: Observable<any>;
|
nodes$: Observable<any>;
|
||||||
isp: {name: string, id: number};
|
isp: {name: string, id: number};
|
||||||
|
nodesPagination$: Observable<any>;
|
||||||
|
startingIndexSubject: BehaviorSubject<number> = new BehaviorSubject(0);
|
||||||
|
page = 1;
|
||||||
|
pageSize = 15;
|
||||||
|
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||||
|
isLoading = true;
|
||||||
|
|
||||||
skeletonLines: number[] = [];
|
skeletonLines: number[] = [];
|
||||||
|
|
||||||
@ -23,7 +29,7 @@ export class NodesPerISP implements OnInit {
|
|||||||
private seoService: SeoService,
|
private seoService: SeoService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < 20; ++i) {
|
for (let i = 0; i < this.pageSize; ++i) {
|
||||||
this.skeletonLines.push(i);
|
this.skeletonLines.push(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -31,6 +37,7 @@ export class NodesPerISP implements OnInit {
|
|||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp)
|
this.nodes$ = this.apiService.getNodeForISP$(this.route.snapshot.params.isp)
|
||||||
.pipe(
|
.pipe(
|
||||||
|
tap(() => this.isLoading = true),
|
||||||
map(response => {
|
map(response => {
|
||||||
this.isp = {
|
this.isp = {
|
||||||
name: response.isp,
|
name: response.isp,
|
||||||
@ -77,11 +84,21 @@ export class NodesPerISP implements OnInit {
|
|||||||
topCountry: topCountry,
|
topCountry: topCountry,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
|
tap(() => this.isLoading = false),
|
||||||
share()
|
share()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
this.nodesPagination$ = combineLatest([this.nodes$, this.startingIndexSubject]).pipe(
|
||||||
|
map(([response, startingIndex]) => response.nodes.slice(startingIndex, startingIndex + this.pageSize))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByPublicKey(index: number, node: any): string {
|
trackByPublicKey(index: number, node: any): string {
|
||||||
return node.public_key;
|
return node.public_key;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageChange(page: number): void {
|
||||||
|
this.startingIndexSubject.next((page - 1) * this.pageSize);
|
||||||
|
this.page = page;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,8 @@ import { RecentPegsListComponent } from '../components/liquid-reserves-audit/rec
|
|||||||
import { FederationWalletComponent } from '../components/liquid-reserves-audit/federation-wallet/federation-wallet.component';
|
import { FederationWalletComponent } from '../components/liquid-reserves-audit/federation-wallet/federation-wallet.component';
|
||||||
import { FederationUtxosListComponent } from '../components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component';
|
import { FederationUtxosListComponent } from '../components/liquid-reserves-audit/federation-utxos-list/federation-utxos-list.component';
|
||||||
import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component';
|
import { FederationAddressesListComponent } from '../components/liquid-reserves-audit/federation-addresses-list/federation-addresses-list.component';
|
||||||
|
import { ServerHealthComponent } from '../components/server-health/server-health.component';
|
||||||
|
import { ServerStatusComponent } from '../components/server-health/server-status.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
@ -140,6 +142,19 @@ const routes: Routes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
||||||
|
routes[0].children.push({
|
||||||
|
path: 'nodes',
|
||||||
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
component: ServerHealthComponent
|
||||||
|
});
|
||||||
|
routes[0].children.push({
|
||||||
|
path: 'network',
|
||||||
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
component: ServerStatusComponent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild(routes)
|
RouterModule.forChild(routes)
|
||||||
|
|||||||
@ -11,6 +11,8 @@ import { PushTransactionComponent } from './components/push-transaction/push-tra
|
|||||||
import { CalculatorComponent } from './components/calculator/calculator.component';
|
import { CalculatorComponent } from './components/calculator/calculator.component';
|
||||||
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
import { BlocksList } from './components/blocks-list/blocks-list.component';
|
||||||
import { RbfList } from './components/rbf-list/rbf-list.component';
|
import { RbfList } from './components/rbf-list/rbf-list.component';
|
||||||
|
import { ServerHealthComponent } from './components/server-health/server-health.component';
|
||||||
|
import { ServerStatusComponent } from './components/server-health/server-status.component';
|
||||||
|
|
||||||
const browserWindow = window || {};
|
const browserWindow = window || {};
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
@ -97,6 +99,19 @@ const routes: Routes = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
||||||
|
routes[0].children.push({
|
||||||
|
path: 'nodes',
|
||||||
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
component: ServerHealthComponent
|
||||||
|
});
|
||||||
|
routes[0].children.push({
|
||||||
|
path: 'network',
|
||||||
|
data: { networks: ['bitcoin', 'liquid'] },
|
||||||
|
component: ServerStatusComponent
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild(routes)
|
RouterModule.forChild(routes)
|
||||||
|
|||||||
@ -139,6 +139,14 @@ export class EnterpriseService {
|
|||||||
this.getMatomo()?.trackGoal(id);
|
this.getMatomo()?.trackGoal(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page() {
|
||||||
|
const matomo = this.getMatomo();
|
||||||
|
if (matomo) {
|
||||||
|
matomo.setCustomUrl(this.getCustomUrl());
|
||||||
|
matomo.trackPageView();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private getCustomUrl(): string {
|
private getCustomUrl(): string {
|
||||||
let url = window.location.origin + '/';
|
let url = window.location.origin + '/';
|
||||||
let route = this.activatedRoute;
|
let route = this.activatedRoute;
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { MenuGroup } from '../interfaces/services.interface';
|
|||||||
import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap } from 'rxjs';
|
import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap } from 'rxjs';
|
||||||
import { IBackendInfo } from '../interfaces/websocket.interface';
|
import { IBackendInfo } from '../interfaces/websocket.interface';
|
||||||
import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
|
import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
|
||||||
|
import { AccelerationStats } from '../components/acceleration/acceleration-stats/acceleration-stats.component';
|
||||||
|
|
||||||
export type ProductType = 'enterprise' | 'community' | 'mining_pool' | 'custom';
|
export type ProductType = 'enterprise' | 'community' | 'mining_pool' | 'custom';
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
@ -144,7 +145,19 @@ export class ServicesApiServices {
|
|||||||
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`);
|
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAggregatedAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
|
||||||
|
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history/aggregated`, { params: { ...params } });
|
||||||
|
}
|
||||||
|
|
||||||
getAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
|
getAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
|
||||||
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params } });
|
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAccelerationHistoryObserveResponse$(params: AccelerationHistoryParams): Observable<any> {
|
||||||
|
return this.httpClient.get<any>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params }, observe: 'response'});
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccelerationStats$(): Observable<AccelerationStats> {
|
||||||
|
return this.httpClient.get<AccelerationStats>(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,13 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID, LOCALE_ID } from '@angular/core';
|
||||||
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
import { ReplaySubject, BehaviorSubject, Subject, fromEvent, Observable, merge } from 'rxjs';
|
||||||
import { Transaction } from '../interfaces/electrs.interface';
|
import { Transaction } from '../interfaces/electrs.interface';
|
||||||
import { IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionCompressed, TransactionStripped } from '../interfaces/websocket.interface';
|
import { HealthCheckHost, IBackendInfo, MempoolBlock, MempoolBlockDelta, MempoolInfo, Recommendedfees, ReplacedTransaction, ReplacementInfo, TransactionStripped } from '../interfaces/websocket.interface';
|
||||||
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
import { BlockExtended, CpfpInfo, DifficultyAdjustment, MempoolPosition, OptimizedMempoolStats, RbfTree } from '../interfaces/node-api.interface';
|
||||||
import { Router, NavigationStart } from '@angular/router';
|
import { Router, NavigationStart } from '@angular/router';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
import { filter, map, scan, shareReplay } from 'rxjs/operators';
|
||||||
import { StorageService } from './storage.service';
|
import { StorageService } from './storage.service';
|
||||||
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
import { hasTouchScreen } from '../shared/pipes/bytes-pipe/utils';
|
||||||
import { ApiService } from './api.service';
|
|
||||||
import { ActiveFilter } from '../shared/filters.utils';
|
import { ActiveFilter } from '../shared/filters.utils';
|
||||||
|
|
||||||
export interface MarkBlockState {
|
export interface MarkBlockState {
|
||||||
@ -130,6 +129,7 @@ export class StateService {
|
|||||||
loadingIndicators$ = new ReplaySubject<ILoadingIndicators>(1);
|
loadingIndicators$ = new ReplaySubject<ILoadingIndicators>(1);
|
||||||
recommendedFees$ = new ReplaySubject<Recommendedfees>(1);
|
recommendedFees$ = new ReplaySubject<Recommendedfees>(1);
|
||||||
chainTip$ = new ReplaySubject<number>(-1);
|
chainTip$ = new ReplaySubject<number>(-1);
|
||||||
|
serverHealth$ = new Subject<HealthCheckHost[]>();
|
||||||
|
|
||||||
live2Chart$ = new Subject<OptimizedMempoolStats>();
|
live2Chart$ = new Subject<OptimizedMempoolStats>();
|
||||||
|
|
||||||
|
|||||||
@ -433,6 +433,10 @@ export class WebsocketService {
|
|||||||
this.stateService.previousRetarget$.next(response.previousRetarget);
|
this.stateService.previousRetarget$.next(response.previousRetarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (response['tomahawk']) {
|
||||||
|
this.stateService.serverHealth$.next(response['tomahawk']);
|
||||||
|
}
|
||||||
|
|
||||||
if (response['git-commit']) {
|
if (response['git-commit']) {
|
||||||
this.stateService.backendInfo$.next(response['git-commit']);
|
this.stateService.backendInfo$.next(response['git-commit']);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,7 @@
|
|||||||
<p><a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a></p>
|
<p><a [routerLink]="['/terms-of-service']" i18n="shared.terms-of-service|Terms of Service">Terms of Service</a></p>
|
||||||
<p><a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a></p>
|
<p><a [routerLink]="['/privacy-policy']" i18n="shared.privacy-policy|Privacy Policy">Privacy Policy</a></p>
|
||||||
<p><a [routerLink]="['/trademark-policy']" i18n="shared.trademark-policy|Trademark Policy">Trademark Policy</a></p>
|
<p><a [routerLink]="['/trademark-policy']" i18n="shared.trademark-policy|Trademark Policy">Trademark Policy</a></p>
|
||||||
|
<p><a [routerLink]="['/3rdpartylicenses.txt']" i18n="shared.trademark-policy|Third-party Licenses">Third-party Licenses</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row social-links">
|
<div class="row social-links">
|
||||||
|
|||||||
@ -21,7 +21,8 @@ export const TransactionFlags = {
|
|||||||
no_rbf: 0b00000010n,
|
no_rbf: 0b00000010n,
|
||||||
v1: 0b00000100n,
|
v1: 0b00000100n,
|
||||||
v2: 0b00001000n,
|
v2: 0b00001000n,
|
||||||
multisig: 0b00010000n,
|
v3: 0b00010000n,
|
||||||
|
nonstandard: 0b00100000n,
|
||||||
// address types
|
// address types
|
||||||
p2pk: 0b00000001_00000000n,
|
p2pk: 0b00000001_00000000n,
|
||||||
p2ms: 0b00000010_00000000n,
|
p2ms: 0b00000010_00000000n,
|
||||||
@ -34,6 +35,7 @@ export const TransactionFlags = {
|
|||||||
cpfp_parent: 0b00000001_00000000_00000000n,
|
cpfp_parent: 0b00000001_00000000_00000000n,
|
||||||
cpfp_child: 0b00000010_00000000_00000000n,
|
cpfp_child: 0b00000010_00000000_00000000n,
|
||||||
replacement: 0b00000100_00000000_00000000n,
|
replacement: 0b00000100_00000000_00000000n,
|
||||||
|
acceleration: 0b00001000_00000000_00000000n,
|
||||||
// data
|
// data
|
||||||
op_return: 0b00000001_00000000_00000000_00000000n,
|
op_return: 0b00000001_00000000_00000000_00000000n,
|
||||||
fake_pubkey: 0b00000010_00000000_00000000_00000000n,
|
fake_pubkey: 0b00000010_00000000_00000000_00000000n,
|
||||||
@ -64,7 +66,8 @@ export const TransactionFilters: { [key: string]: Filter } = {
|
|||||||
no_rbf: { key: 'no_rbf', label: 'RBF disabled', flag: TransactionFlags.no_rbf, toggle: 'rbf', important: true },
|
no_rbf: { key: 'no_rbf', label: 'RBF disabled', flag: TransactionFlags.no_rbf, toggle: 'rbf', important: true },
|
||||||
v1: { key: 'v1', label: 'Version 1', flag: TransactionFlags.v1, toggle: 'version' },
|
v1: { key: 'v1', label: 'Version 1', flag: TransactionFlags.v1, toggle: 'version' },
|
||||||
v2: { key: 'v2', label: 'Version 2', flag: TransactionFlags.v2, toggle: 'version' },
|
v2: { key: 'v2', label: 'Version 2', flag: TransactionFlags.v2, toggle: 'version' },
|
||||||
// multisig: { key: 'multisig', label: 'Multisig', flag: TransactionFlags.multisig },
|
v3: { key: 'v3', label: 'Version 3', flag: TransactionFlags.v3, toggle: 'version' },
|
||||||
|
nonstandard: { key: 'nonstandard', label: 'Non-Standard', flag: TransactionFlags.nonstandard, important: true },
|
||||||
/* address types */
|
/* address types */
|
||||||
p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk, important: true },
|
p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk, important: true },
|
||||||
p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms, important: true },
|
p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms, important: true },
|
||||||
@ -77,6 +80,7 @@ export const TransactionFilters: { [key: string]: Filter } = {
|
|||||||
cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent, important: true },
|
cpfp_parent: { key: 'cpfp_parent', label: 'Paid for by child', flag: TransactionFlags.cpfp_parent, important: true },
|
||||||
cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child, important: true },
|
cpfp_child: { key: 'cpfp_child', label: 'Pays for parent', flag: TransactionFlags.cpfp_child, important: true },
|
||||||
replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement, important: true },
|
replacement: { key: 'replacement', label: 'Replacement', flag: TransactionFlags.replacement, important: true },
|
||||||
|
acceleration: window?.['__env']?.ACCELERATOR ? { key: 'acceleration', label: 'Accelerated', flag: TransactionFlags.acceleration, important: false } : undefined,
|
||||||
/* data */
|
/* data */
|
||||||
op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true },
|
op_return: { key: 'op_return', label: 'OP_RETURN', flag: TransactionFlags.op_return, important: true },
|
||||||
fake_pubkey: { key: 'fake_pubkey', label: 'Fake pubkey', flag: TransactionFlags.fake_pubkey },
|
fake_pubkey: { key: 'fake_pubkey', label: 'Fake pubkey', flag: TransactionFlags.fake_pubkey },
|
||||||
@ -94,9 +98,9 @@ export const TransactionFilters: { [key: string]: Filter } = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const FilterGroups: { label: string, filters: Filter[]}[] = [
|
export const FilterGroups: { label: string, filters: Filter[]}[] = [
|
||||||
{ label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'multisig'] },
|
{ label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'v3', 'nonstandard'] },
|
||||||
{ label: 'Address Types', filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] },
|
{ label: 'Address Types', filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] },
|
||||||
{ label: 'Behavior', filters: ['cpfp_parent', 'cpfp_child', 'replacement'] },
|
{ label: 'Behavior', filters: ['cpfp_parent', 'cpfp_child', 'replacement', 'acceleration'] },
|
||||||
{ label: 'Data', filters: ['op_return', 'fake_pubkey', 'inscription'] },
|
{ label: 'Data', filters: ['op_return', 'fake_pubkey', 'inscription'] },
|
||||||
{ label: 'Heuristics', filters: ['coinjoin', 'consolidation', 'batch_payout'] },
|
{ label: 'Heuristics', filters: ['coinjoin', 'consolidation', 'batch_payout'] },
|
||||||
{ label: 'Sighash Flags', filters: ['sighash_all', 'sighash_none', 'sighash_single', 'sighash_default', 'sighash_acp'] },
|
{ label: 'Sighash Flags', filters: ['sighash_all', 'sighash_none', 'sighash_single', 'sighash_default', 'sighash_acp'] },
|
||||||
|
|||||||
@ -54,6 +54,8 @@ import { AssetComponent } from '../components/asset/asset.component';
|
|||||||
import { AssetsComponent } from '../components/assets/assets.component';
|
import { AssetsComponent } from '../components/assets/assets.component';
|
||||||
import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component';
|
import { AssetsNavComponent } from '../components/assets/assets-nav/assets-nav.component';
|
||||||
import { StatusViewComponent } from '../components/status-view/status-view.component';
|
import { StatusViewComponent } from '../components/status-view/status-view.component';
|
||||||
|
import { ServerHealthComponent } from '../components/server-health/server-health.component';
|
||||||
|
import { ServerStatusComponent } from '../components/server-health/server-status.component';
|
||||||
import { FeesBoxComponent } from '../components/fees-box/fees-box.component';
|
import { FeesBoxComponent } from '../components/fees-box/fees-box.component';
|
||||||
import { DifficultyComponent } from '../components/difficulty/difficulty.component';
|
import { DifficultyComponent } from '../components/difficulty/difficulty.component';
|
||||||
import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component';
|
import { DifficultyTooltipComponent } from '../components/difficulty/difficulty-tooltip.component';
|
||||||
@ -153,6 +155,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
AssetComponent,
|
AssetComponent,
|
||||||
AssetsComponent,
|
AssetsComponent,
|
||||||
StatusViewComponent,
|
StatusViewComponent,
|
||||||
|
ServerHealthComponent,
|
||||||
|
ServerStatusComponent,
|
||||||
FeesBoxComponent,
|
FeesBoxComponent,
|
||||||
DifficultyComponent,
|
DifficultyComponent,
|
||||||
DifficultyMiningComponent,
|
DifficultyMiningComponent,
|
||||||
@ -280,6 +284,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
AssetComponent,
|
AssetComponent,
|
||||||
AssetsComponent,
|
AssetsComponent,
|
||||||
StatusViewComponent,
|
StatusViewComponent,
|
||||||
|
ServerHealthComponent,
|
||||||
|
ServerStatusComponent,
|
||||||
FeesBoxComponent,
|
FeesBoxComponent,
|
||||||
DifficultyComponent,
|
DifficultyComponent,
|
||||||
DifficultyMiningComponent,
|
DifficultyMiningComponent,
|
||||||
|
|||||||
@ -7,7 +7,6 @@ discover=1
|
|||||||
par=16
|
par=16
|
||||||
dbcache=8192
|
dbcache=8192
|
||||||
maxmempool=4096
|
maxmempool=4096
|
||||||
mempoolexpiry=999999
|
|
||||||
mempoolfullrbf=1
|
mempoolfullrbf=1
|
||||||
maxconnections=100
|
maxconnections=100
|
||||||
onion=127.0.0.1:9050
|
onion=127.0.0.1:9050
|
||||||
@ -20,6 +19,7 @@ whitelist=2401:b140::/32
|
|||||||
#uacomment=@wiz
|
#uacomment=@wiz
|
||||||
|
|
||||||
[main]
|
[main]
|
||||||
|
mempoolexpiry=999999
|
||||||
rpcbind=127.0.0.1:8332
|
rpcbind=127.0.0.1:8332
|
||||||
rpcbind=[::1]:8332
|
rpcbind=[::1]:8332
|
||||||
bind=0.0.0.0:8333
|
bind=0.0.0.0:8333
|
||||||
|
|||||||
23
production/check
Executable file
23
production/check
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env zsh
|
||||||
|
|
||||||
|
check_frontend_git_commit_hash() {
|
||||||
|
echo -n $(curl -s https://node$1.$2.mempool.space/resources/config.js|grep GIT_COMMIT_HASH|cut -d "'" -f2|cut -c1-8)
|
||||||
|
}
|
||||||
|
|
||||||
|
check_html_md5_hash() {
|
||||||
|
echo -n $(curl -s https://node$1.$2.mempool.space|md5|cut -c1-8)
|
||||||
|
}
|
||||||
|
|
||||||
|
for site in fmt va1 fra tk7;do
|
||||||
|
echo "${site}"
|
||||||
|
for node in 201 202 203 204 205 206 207 208 209 210 211 212 213 214;do
|
||||||
|
[ "${site}" = "fmt" ] && [ "${node}" -gt 206 ] && continue
|
||||||
|
[ "${site}" = "tk7" ] && [ "${node}" -gt 206 ] && continue
|
||||||
|
echo -n "node${node}.${site}: "
|
||||||
|
check_frontend_git_commit_hash $node $site
|
||||||
|
echo -n " "
|
||||||
|
check_html_md5_hash $node $site
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"NETWORK": "bisq",
|
"NETWORK": "bisq",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8996,
|
"HTTP_PORT": 8996,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"NETWORK": "liquid",
|
"NETWORK": "liquid",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8998,
|
"HTTP_PORT": 8998,
|
||||||
@ -64,15 +65,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3001",
|
"http://node203.tk7.mempool.space:3001",
|
||||||
"http://node204.tk7.mempool.space:3001",
|
"http://node204.tk7.mempool.space:3001",
|
||||||
"http://node205.tk7.mempool.space:3001",
|
"http://node205.tk7.mempool.space:3001",
|
||||||
"http://node206.tk7.mempool.space:3001",
|
"http://node206.tk7.mempool.space:3001"
|
||||||
"http://node207.tk7.mempool.space:3001",
|
|
||||||
"http://node208.tk7.mempool.space:3001",
|
|
||||||
"http://node209.tk7.mempool.space:3001",
|
|
||||||
"http://node210.tk7.mempool.space:3001",
|
|
||||||
"http://node211.tk7.mempool.space:3001",
|
|
||||||
"http://node212.tk7.mempool.space:3001",
|
|
||||||
"http://node213.tk7.mempool.space:3001",
|
|
||||||
"http://node214.tk7.mempool.space:3001"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"NETWORK": "liquid",
|
"NETWORK": "liquid",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8994,
|
"HTTP_PORT": 8994,
|
||||||
@ -64,15 +65,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3004",
|
"http://node203.tk7.mempool.space:3004",
|
||||||
"http://node204.tk7.mempool.space:3004",
|
"http://node204.tk7.mempool.space:3004",
|
||||||
"http://node205.tk7.mempool.space:3004",
|
"http://node205.tk7.mempool.space:3004",
|
||||||
"http://node206.tk7.mempool.space:3004",
|
"http://node206.tk7.mempool.space:3004"
|
||||||
"http://node207.tk7.mempool.space:3004",
|
|
||||||
"http://node208.tk7.mempool.space:3004",
|
|
||||||
"http://node209.tk7.mempool.space:3004",
|
|
||||||
"http://node210.tk7.mempool.space:3004",
|
|
||||||
"http://node211.tk7.mempool.space:3004",
|
|
||||||
"http://node212.tk7.mempool.space:3004",
|
|
||||||
"http://node213.tk7.mempool.space:3004",
|
|
||||||
"http://node214.tk7.mempool.space:3004"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"NETWORK": "mainnet",
|
"NETWORK": "mainnet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
@ -57,15 +58,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3000",
|
"http://node203.tk7.mempool.space:3000",
|
||||||
"http://node204.tk7.mempool.space:3000",
|
"http://node204.tk7.mempool.space:3000",
|
||||||
"http://node205.tk7.mempool.space:3000",
|
"http://node205.tk7.mempool.space:3000",
|
||||||
"http://node206.tk7.mempool.space:3000",
|
"http://node206.tk7.mempool.space:3000"
|
||||||
"http://node207.tk7.mempool.space:3000",
|
|
||||||
"http://node208.tk7.mempool.space:3000",
|
|
||||||
"http://node209.tk7.mempool.space:3000",
|
|
||||||
"http://node210.tk7.mempool.space:3000",
|
|
||||||
"http://node211.tk7.mempool.space:3000",
|
|
||||||
"http://node212.tk7.mempool.space:3000",
|
|
||||||
"http://node213.tk7.mempool.space:3000",
|
|
||||||
"http://node214.tk7.mempool.space:3000"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"LIGHTNING": {
|
"LIGHTNING": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"NETWORK": "mainnet",
|
"NETWORK": "mainnet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8999,
|
"HTTP_PORT": 8999,
|
||||||
@ -21,7 +22,8 @@
|
|||||||
"DISK_CACHE_BLOCK_INTERVAL": 1,
|
"DISK_CACHE_BLOCK_INTERVAL": 1,
|
||||||
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
||||||
"ALLOW_UNREACHABLE": true,
|
"ALLOW_UNREACHABLE": true,
|
||||||
"PRICE_UPDATES_PER_HOUR": 12
|
"PRICE_UPDATES_PER_HOUR": 12,
|
||||||
|
"MAX_TRACKED_ADDRESSES": 10
|
||||||
},
|
},
|
||||||
"SYSLOG" : {
|
"SYSLOG" : {
|
||||||
"MIN_PRIORITY": "debug"
|
"MIN_PRIORITY": "debug"
|
||||||
@ -78,15 +80,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3000",
|
"http://node203.tk7.mempool.space:3000",
|
||||||
"http://node204.tk7.mempool.space:3000",
|
"http://node204.tk7.mempool.space:3000",
|
||||||
"http://node205.tk7.mempool.space:3000",
|
"http://node205.tk7.mempool.space:3000",
|
||||||
"http://node206.tk7.mempool.space:3000",
|
"http://node206.tk7.mempool.space:3000"
|
||||||
"http://node207.tk7.mempool.space:3000",
|
|
||||||
"http://node208.tk7.mempool.space:3000",
|
|
||||||
"http://node209.tk7.mempool.space:3000",
|
|
||||||
"http://node210.tk7.mempool.space:3000",
|
|
||||||
"http://node211.tk7.mempool.space:3000",
|
|
||||||
"http://node212.tk7.mempool.space:3000",
|
|
||||||
"http://node213.tk7.mempool.space:3000",
|
|
||||||
"http://node214.tk7.mempool.space:3000"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
@ -145,15 +139,7 @@
|
|||||||
"node203.tk7.mempool.space",
|
"node203.tk7.mempool.space",
|
||||||
"node204.tk7.mempool.space",
|
"node204.tk7.mempool.space",
|
||||||
"node205.tk7.mempool.space",
|
"node205.tk7.mempool.space",
|
||||||
"node206.tk7.mempool.space",
|
"node206.tk7.mempool.space"
|
||||||
"node207.tk7.mempool.space",
|
|
||||||
"node208.tk7.mempool.space",
|
|
||||||
"node209.tk7.mempool.space",
|
|
||||||
"node210.tk7.mempool.space",
|
|
||||||
"node211.tk7.mempool.space",
|
|
||||||
"node212.tk7.mempool.space",
|
|
||||||
"node213.tk7.mempool.space",
|
|
||||||
"node214.tk7.mempool.space"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"REDIS": {
|
"REDIS": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"NETWORK": "signet",
|
"NETWORK": "signet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
@ -57,15 +58,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3003",
|
"http://node203.tk7.mempool.space:3003",
|
||||||
"http://node204.tk7.mempool.space:3003",
|
"http://node204.tk7.mempool.space:3003",
|
||||||
"http://node205.tk7.mempool.space:3003",
|
"http://node205.tk7.mempool.space:3003",
|
||||||
"http://node206.tk7.mempool.space:3003",
|
"http://node206.tk7.mempool.space:3003"
|
||||||
"http://node207.tk7.mempool.space:3003",
|
|
||||||
"http://node208.tk7.mempool.space:3003",
|
|
||||||
"http://node209.tk7.mempool.space:3003",
|
|
||||||
"http://node210.tk7.mempool.space:3003",
|
|
||||||
"http://node211.tk7.mempool.space:3003",
|
|
||||||
"http://node212.tk7.mempool.space:3003",
|
|
||||||
"http://node213.tk7.mempool.space:3003",
|
|
||||||
"http://node214.tk7.mempool.space:3003"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"LIGHTNING": {
|
"LIGHTNING": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"NETWORK": "signet",
|
"NETWORK": "signet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8995,
|
"HTTP_PORT": 8995,
|
||||||
@ -14,7 +15,8 @@
|
|||||||
"POLL_RATE_MS": 1000,
|
"POLL_RATE_MS": 1000,
|
||||||
"DISK_CACHE_BLOCK_INTERVAL": 1,
|
"DISK_CACHE_BLOCK_INTERVAL": 1,
|
||||||
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
||||||
"ALLOW_UNREACHABLE": true
|
"ALLOW_UNREACHABLE": true,
|
||||||
|
"MAX_TRACKED_ADDRESSES": 10
|
||||||
},
|
},
|
||||||
"SYSLOG" : {
|
"SYSLOG" : {
|
||||||
"MIN_PRIORITY": "debug"
|
"MIN_PRIORITY": "debug"
|
||||||
@ -66,15 +68,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3003",
|
"http://node203.tk7.mempool.space:3003",
|
||||||
"http://node204.tk7.mempool.space:3003",
|
"http://node204.tk7.mempool.space:3003",
|
||||||
"http://node205.tk7.mempool.space:3003",
|
"http://node205.tk7.mempool.space:3003",
|
||||||
"http://node206.tk7.mempool.space:3003",
|
"http://node206.tk7.mempool.space:3003"
|
||||||
"http://node207.tk7.mempool.space:3003",
|
|
||||||
"http://node208.tk7.mempool.space:3003",
|
|
||||||
"http://node209.tk7.mempool.space:3003",
|
|
||||||
"http://node210.tk7.mempool.space:3003",
|
|
||||||
"http://node211.tk7.mempool.space:3003",
|
|
||||||
"http://node212.tk7.mempool.space:3003",
|
|
||||||
"http://node213.tk7.mempool.space:3003",
|
|
||||||
"http://node214.tk7.mempool.space:3003"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
"NETWORK": "testnet",
|
"NETWORK": "testnet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
@ -57,15 +58,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3002",
|
"http://node203.tk7.mempool.space:3002",
|
||||||
"http://node204.tk7.mempool.space:3002",
|
"http://node204.tk7.mempool.space:3002",
|
||||||
"http://node205.tk7.mempool.space:3002",
|
"http://node205.tk7.mempool.space:3002",
|
||||||
"http://node206.tk7.mempool.space:3002",
|
"http://node206.tk7.mempool.space:3002"
|
||||||
"http://node207.tk7.mempool.space:3002",
|
|
||||||
"http://node208.tk7.mempool.space:3002",
|
|
||||||
"http://node209.tk7.mempool.space:3002",
|
|
||||||
"http://node210.tk7.mempool.space:3002",
|
|
||||||
"http://node211.tk7.mempool.space:3002",
|
|
||||||
"http://node212.tk7.mempool.space:3002",
|
|
||||||
"http://node213.tk7.mempool.space:3002",
|
|
||||||
"http://node214.tk7.mempool.space:3002"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"LIGHTNING": {
|
"LIGHTNING": {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"MEMPOOL": {
|
"MEMPOOL": {
|
||||||
|
"OFFICIAL": true,
|
||||||
"NETWORK": "testnet",
|
"NETWORK": "testnet",
|
||||||
"BACKEND": "esplora",
|
"BACKEND": "esplora",
|
||||||
"HTTP_PORT": 8997,
|
"HTTP_PORT": 8997,
|
||||||
@ -14,7 +15,8 @@
|
|||||||
"POLL_RATE_MS": 1000,
|
"POLL_RATE_MS": 1000,
|
||||||
"DISK_CACHE_BLOCK_INTERVAL": 1,
|
"DISK_CACHE_BLOCK_INTERVAL": 1,
|
||||||
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
"MAX_PUSH_TX_SIZE_WEIGHT": 4000000,
|
||||||
"ALLOW_UNREACHABLE": true
|
"ALLOW_UNREACHABLE": true,
|
||||||
|
"MAX_TRACKED_ADDRESSES": 10
|
||||||
},
|
},
|
||||||
"SYSLOG" : {
|
"SYSLOG" : {
|
||||||
"MIN_PRIORITY": "debug"
|
"MIN_PRIORITY": "debug"
|
||||||
@ -66,15 +68,7 @@
|
|||||||
"http://node203.tk7.mempool.space:3002",
|
"http://node203.tk7.mempool.space:3002",
|
||||||
"http://node204.tk7.mempool.space:3002",
|
"http://node204.tk7.mempool.space:3002",
|
||||||
"http://node205.tk7.mempool.space:3002",
|
"http://node205.tk7.mempool.space:3002",
|
||||||
"http://node206.tk7.mempool.space:3002",
|
"http://node206.tk7.mempool.space:3002"
|
||||||
"http://node207.tk7.mempool.space:3002",
|
|
||||||
"http://node208.tk7.mempool.space:3002",
|
|
||||||
"http://node209.tk7.mempool.space:3002",
|
|
||||||
"http://node210.tk7.mempool.space:3002",
|
|
||||||
"http://node211.tk7.mempool.space:3002",
|
|
||||||
"http://node212.tk7.mempool.space:3002",
|
|
||||||
"http://node213.tk7.mempool.space:3002",
|
|
||||||
"http://node214.tk7.mempool.space:3002"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
|
|||||||
@ -20,5 +20,10 @@ for pid in `ps uaxww|grep warmer|grep zsh|awk '{print $2}'`;do
|
|||||||
kill $pid
|
kill $pid
|
||||||
done
|
done
|
||||||
|
|
||||||
|
# kill nginx cache heater scripts
|
||||||
|
for pid in `ps uaxww|grep heater|grep zsh|awk '{print $2}'`;do
|
||||||
|
kill $pid
|
||||||
|
done
|
||||||
|
|
||||||
# always exit successfully despite above errors
|
# always exit successfully despite above errors
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
6
production/nginx/http-acl.conf
Normal file
6
production/nginx/http-acl.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# used for "internal" API restriction
|
||||||
|
geo $remote_addr $mempool_external {
|
||||||
|
127.0.0.1 '';
|
||||||
|
::1 '';
|
||||||
|
default 1;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user