mirror of
https://github.com/mempool/mempool.git
synced 2024-11-20 02:11:49 +01:00
Merge branch 'master' into simon/ignore-existing-mining-pools
This commit is contained in:
commit
a36303e1fb
289
backend/package-lock.json
generated
289
backend/package-lock.json
generated
@ -1485,6 +1485,17 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@noble/hashes": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://paulmillr.com/funding/"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"node_modules/@nodelib/fs.scandir": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -2403,12 +2414,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/base-x": {
|
"node_modules/base-x": {
|
||||||
"version": "3.0.9",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
|
||||||
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
|
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/bech32": {
|
"node_modules/bech32": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@ -2424,18 +2432,16 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bitcoinjs-lib": {
|
"node_modules/bitcoinjs-lib": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.1.tgz",
|
||||||
"integrity": "sha512-eupi1FBTJmPuAZdChnzTXLv2HBqFW2AICpzXZQLniP0V9FWWeeUQSMKES6sP8isy/xO0ijDexbgkdEyFVrsuJw==",
|
"integrity": "sha512-FYihfgTk29lt1eK2y48OtuarEDUnTprNBW3ctT8yHiOhvmeS3DzAVG6gI0VCvMkydz6UdlXlYNWIPqGD0SUYRQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@noble/hashes": "^1.2.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
"bip174": "^2.1.0",
|
"bip174": "^2.1.0",
|
||||||
"bs58check": "^2.1.2",
|
"bs58check": "^3.0.1",
|
||||||
"create-hash": "^1.1.0",
|
|
||||||
"ripemd160": "^2.0.2",
|
|
||||||
"typeforce": "^1.11.3",
|
"typeforce": "^1.11.3",
|
||||||
"varuint-bitcoin": "^1.1.2",
|
"varuint-bitcoin": "^1.1.2"
|
||||||
"wif": "^2.0.1"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=8.0.0"
|
||||||
@ -2540,21 +2546,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bs58": {
|
"node_modules/bs58": {
|
||||||
"version": "4.0.1",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||||
"integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
|
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"base-x": "^3.0.2"
|
"base-x": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bs58check": {
|
"node_modules/bs58check": {
|
||||||
"version": "2.1.2",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz",
|
||||||
"integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
|
"integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bs58": "^4.0.0",
|
"@noble/hashes": "^1.2.0",
|
||||||
"create-hash": "^1.1.0",
|
"bs58": "^5.0.0"
|
||||||
"safe-buffer": "^5.1.2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/bser": {
|
"node_modules/bser": {
|
||||||
@ -2668,15 +2673,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cipher-base": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/cjs-module-lexer": {
|
"node_modules/cjs-module-lexer": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
||||||
@ -2783,18 +2779,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||||
},
|
},
|
||||||
"node_modules/create-hash": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
|
||||||
"dependencies": {
|
|
||||||
"cipher-base": "^1.0.1",
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"md5.js": "^1.3.4",
|
|
||||||
"ripemd160": "^2.0.1",
|
|
||||||
"sha.js": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/create-require": {
|
"node_modules/create-require": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
@ -3825,19 +3809,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hash-base": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.4",
|
|
||||||
"readable-stream": "^3.6.0",
|
|
||||||
"safe-buffer": "^5.2.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/html-escaper": {
|
"node_modules/html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
@ -5916,16 +5887,6 @@
|
|||||||
"npm": ">=6"
|
"npm": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/md5.js": {
|
|
||||||
"version": "1.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
|
||||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
|
||||||
"dependencies": {
|
|
||||||
"hash-base": "^3.0.0",
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"safe-buffer": "^5.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/media-typer": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@ -6591,19 +6552,6 @@
|
|||||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"node_modules/readable-stream": {
|
|
||||||
"version": "3.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
|
||||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"string_decoder": "^1.1.1",
|
|
||||||
"util-deprecate": "^1.0.1"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 6"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/require-directory": {
|
"node_modules/require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@ -6694,15 +6642,6 @@
|
|||||||
"url": "https://github.com/sponsors/isaacs"
|
"url": "https://github.com/sponsors/isaacs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ripemd160": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
|
|
||||||
"dependencies": {
|
|
||||||
"hash-base": "^3.0.0",
|
|
||||||
"inherits": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/run-parallel": {
|
"node_modules/run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@ -6824,18 +6763,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||||
},
|
},
|
||||||
"node_modules/sha.js": {
|
|
||||||
"version": "2.4.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
|
||||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"sha.js": "bin.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/shebang-command": {
|
"node_modules/shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@ -6988,14 +6915,6 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/string_decoder": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "~5.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/string-length": {
|
"node_modules/string-length": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||||
@ -7397,11 +7316,6 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/util-deprecate": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
|
||||||
},
|
|
||||||
"node_modules/utils-merge": {
|
"node_modules/utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
@ -7470,14 +7384,6 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wif": {
|
|
||||||
"version": "2.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz",
|
|
||||||
"integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==",
|
|
||||||
"dependencies": {
|
|
||||||
"bs58check": "<3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/word-wrap": {
|
"node_modules/word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||||
@ -8725,6 +8631,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@mempool/electrum-client/-/electrum-client-1.1.9.tgz",
|
||||||
"integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ=="
|
"integrity": "sha512-mlvPiCzUlaETpYW3i6V87A24jjMYgsebaXtUo3WQyyLnYUuxs0KiXQ2mnKh3h15j8Xg/hfxeGIi+5OC9u0nftQ=="
|
||||||
},
|
},
|
||||||
|
"@noble/hashes": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg=="
|
||||||
|
},
|
||||||
"@nodelib/fs.scandir": {
|
"@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -9445,12 +9356,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"base-x": {
|
"base-x": {
|
||||||
"version": "3.0.9",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
|
"resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz",
|
||||||
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
|
"integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw=="
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"bech32": {
|
"bech32": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
@ -9463,18 +9371,16 @@
|
|||||||
"integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA=="
|
"integrity": "sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA=="
|
||||||
},
|
},
|
||||||
"bitcoinjs-lib": {
|
"bitcoinjs-lib": {
|
||||||
"version": "6.1.0",
|
"version": "6.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.1.tgz",
|
||||||
"integrity": "sha512-eupi1FBTJmPuAZdChnzTXLv2HBqFW2AICpzXZQLniP0V9FWWeeUQSMKES6sP8isy/xO0ijDexbgkdEyFVrsuJw==",
|
"integrity": "sha512-FYihfgTk29lt1eK2y48OtuarEDUnTprNBW3ctT8yHiOhvmeS3DzAVG6gI0VCvMkydz6UdlXlYNWIPqGD0SUYRQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
|
"@noble/hashes": "^1.2.0",
|
||||||
"bech32": "^2.0.0",
|
"bech32": "^2.0.0",
|
||||||
"bip174": "^2.1.0",
|
"bip174": "^2.1.0",
|
||||||
"bs58check": "^2.1.2",
|
"bs58check": "^3.0.1",
|
||||||
"create-hash": "^1.1.0",
|
|
||||||
"ripemd160": "^2.0.2",
|
|
||||||
"typeforce": "^1.11.3",
|
"typeforce": "^1.11.3",
|
||||||
"varuint-bitcoin": "^1.1.2",
|
"varuint-bitcoin": "^1.1.2"
|
||||||
"wif": "^2.0.1"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"body-parser": {
|
"body-parser": {
|
||||||
@ -9552,21 +9458,20 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bs58": {
|
"bs58": {
|
||||||
"version": "4.0.1",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz",
|
||||||
"integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==",
|
"integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"base-x": "^3.0.2"
|
"base-x": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bs58check": {
|
"bs58check": {
|
||||||
"version": "2.1.2",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz",
|
||||||
"integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==",
|
"integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"bs58": "^4.0.0",
|
"@noble/hashes": "^1.2.0",
|
||||||
"create-hash": "^1.1.0",
|
"bs58": "^5.0.0"
|
||||||
"safe-buffer": "^5.1.2"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bser": {
|
"bser": {
|
||||||
@ -9639,15 +9544,6 @@
|
|||||||
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
|
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"cipher-base": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz",
|
|
||||||
"integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==",
|
|
||||||
"requires": {
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cjs-module-lexer": {
|
"cjs-module-lexer": {
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz",
|
||||||
@ -9735,18 +9631,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||||
},
|
},
|
||||||
"create-hash": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz",
|
|
||||||
"integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==",
|
|
||||||
"requires": {
|
|
||||||
"cipher-base": "^1.0.1",
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"md5.js": "^1.3.4",
|
|
||||||
"ripemd160": "^2.0.1",
|
|
||||||
"sha.js": "^2.4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"create-require": {
|
"create-require": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||||
@ -10513,16 +10397,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
|
||||||
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
|
||||||
},
|
},
|
||||||
"hash-base": {
|
|
||||||
"version": "3.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
|
|
||||||
"integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
|
|
||||||
"requires": {
|
|
||||||
"inherits": "^2.0.4",
|
|
||||||
"readable-stream": "^3.6.0",
|
|
||||||
"safe-buffer": "^5.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"html-escaper": {
|
"html-escaper": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
|
||||||
@ -12069,16 +11943,6 @@
|
|||||||
"tiny-lru": "10.3.0"
|
"tiny-lru": "10.3.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"md5.js": {
|
|
||||||
"version": "1.3.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
|
||||||
"integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==",
|
|
||||||
"requires": {
|
|
||||||
"hash-base": "^3.0.0",
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"safe-buffer": "^5.1.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"media-typer": {
|
"media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
@ -12547,16 +12411,6 @@
|
|||||||
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
|
||||||
"version": "3.6.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
|
|
||||||
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
|
|
||||||
"requires": {
|
|
||||||
"inherits": "^2.0.3",
|
|
||||||
"string_decoder": "^1.1.1",
|
|
||||||
"util-deprecate": "^1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"require-directory": {
|
"require-directory": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||||
@ -12618,15 +12472,6 @@
|
|||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ripemd160": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==",
|
|
||||||
"requires": {
|
|
||||||
"hash-base": "^3.0.0",
|
|
||||||
"inherits": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"run-parallel": {
|
"run-parallel": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
@ -12715,15 +12560,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
|
||||||
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
|
||||||
},
|
},
|
||||||
"sha.js": {
|
|
||||||
"version": "2.4.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
|
|
||||||
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
|
|
||||||
"requires": {
|
|
||||||
"inherits": "^2.0.1",
|
|
||||||
"safe-buffer": "^5.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@ -12840,14 +12676,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
|
||||||
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
"integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="
|
||||||
},
|
},
|
||||||
"string_decoder": {
|
|
||||||
"version": "1.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
|
|
||||||
"requires": {
|
|
||||||
"safe-buffer": "~5.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"string-length": {
|
"string-length": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz",
|
||||||
@ -13101,11 +12929,6 @@
|
|||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"util-deprecate": {
|
|
||||||
"version": "1.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
|
||||||
},
|
|
||||||
"utils-merge": {
|
"utils-merge": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||||
@ -13159,14 +12982,6 @@
|
|||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"wif": {
|
|
||||||
"version": "2.0.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz",
|
|
||||||
"integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==",
|
|
||||||
"requires": {
|
|
||||||
"bs58check": "<3.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"word-wrap": {
|
"word-wrap": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||||
|
@ -28,6 +28,8 @@
|
|||||||
"package-rm-build-deps": "(cd package/node_modules; rm -r typescript @typescript-eslint)",
|
"package-rm-build-deps": "(cd package/node_modules; rm -r typescript @typescript-eslint)",
|
||||||
"start": "node --max-old-space-size=2048 dist/index.js",
|
"start": "node --max-old-space-size=2048 dist/index.js",
|
||||||
"start-production": "node --max-old-space-size=16384 dist/index.js",
|
"start-production": "node --max-old-space-size=16384 dist/index.js",
|
||||||
|
"reindex-updated-pools": "npm run start-production --update-pools",
|
||||||
|
"reindex-all-blocks": "npm run start-production --update-pools --reindex-blocks",
|
||||||
"test": "./node_modules/.bin/jest --coverage",
|
"test": "./node_modules/.bin/jest --coverage",
|
||||||
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
"lint": "./node_modules/.bin/eslint . --ext .ts",
|
||||||
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
|
"lint:fix": "./node_modules/.bin/eslint . --ext .ts --fix",
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
"INITIAL_BLOCKS_AMOUNT": 7,
|
"INITIAL_BLOCKS_AMOUNT": 7,
|
||||||
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
"MEMPOOL_BLOCKS_AMOUNT": 8,
|
||||||
"USE_SECOND_NODE_FOR_MINFEE": 10,
|
"USE_SECOND_NODE_FOR_MINFEE": 10,
|
||||||
"EXTERNAL_ASSETS": 11,
|
"EXTERNAL_ASSETS": [],
|
||||||
"EXTERNAL_MAX_RETRY": 12,
|
"EXTERNAL_MAX_RETRY": 12,
|
||||||
"EXTERNAL_RETRY_INTERVAL": 13,
|
"EXTERNAL_RETRY_INTERVAL": 13,
|
||||||
"USER_AGENT": "__MEMPOOL_USER_AGENT__",
|
"USER_AGENT": "__MEMPOOL_USER_AGENT__",
|
||||||
@ -24,19 +24,19 @@
|
|||||||
"INDEXING_BLOCKS_AMOUNT": 14,
|
"INDEXING_BLOCKS_AMOUNT": 14,
|
||||||
"POOLS_JSON_TREE_URL": "__POOLS_JSON_TREE_URL__",
|
"POOLS_JSON_TREE_URL": "__POOLS_JSON_TREE_URL__",
|
||||||
"POOLS_JSON_URL": "__POOLS_JSON_URL__",
|
"POOLS_JSON_URL": "__POOLS_JSON_URL__",
|
||||||
"AUDIT": "__MEMPOOL_AUDIT__",
|
"AUDIT": true,
|
||||||
"ADVANCED_GBT_AUDIT": "__MEMPOOL_ADVANCED_GBT_AUDIT__",
|
"ADVANCED_GBT_AUDIT": true,
|
||||||
"ADVANCED_GBT_MEMPOOL": "__MEMPOOL_ADVANCED_GBT_MEMPOOL__",
|
"ADVANCED_GBT_MEMPOOL": true,
|
||||||
"CPFP_INDEXING": "__MEMPOOL_CPFP_INDEXING__",
|
"CPFP_INDEXING": true,
|
||||||
"MAX_BLOCKS_BULK_QUERY": "__MEMPOOL_MAX_BLOCKS_BULK_QUERY__",
|
"MAX_BLOCKS_BULK_QUERY": 999,
|
||||||
"DISK_CACHE_BLOCK_INTERVAL": "__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__"
|
"DISK_CACHE_BLOCK_INTERVAL": 999
|
||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"HOST": "__CORE_RPC_HOST__",
|
"HOST": "__CORE_RPC_HOST__",
|
||||||
"PORT": 15,
|
"PORT": 15,
|
||||||
"USERNAME": "__CORE_RPC_USERNAME__",
|
"USERNAME": "__CORE_RPC_USERNAME__",
|
||||||
"PASSWORD": "__CORE_RPC_PASSWORD__",
|
"PASSWORD": "__CORE_RPC_PASSWORD__",
|
||||||
"TIMEOUT": "__CORE_RPC_TIMEOUT__"
|
"TIMEOUT": 1000
|
||||||
},
|
},
|
||||||
"ELECTRUM": {
|
"ELECTRUM": {
|
||||||
"HOST": "__ELECTRUM_HOST__",
|
"HOST": "__ELECTRUM_HOST__",
|
||||||
@ -46,14 +46,14 @@
|
|||||||
"ESPLORA": {
|
"ESPLORA": {
|
||||||
"REST_API_URL": "__ESPLORA_REST_API_URL__",
|
"REST_API_URL": "__ESPLORA_REST_API_URL__",
|
||||||
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
|
"UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__",
|
||||||
"RETRY_UNIX_SOCKET_AFTER": "__ESPLORA_RETRY_UNIX_SOCKET_AFTER__"
|
"RETRY_UNIX_SOCKET_AFTER": 888
|
||||||
},
|
},
|
||||||
"SECOND_CORE_RPC": {
|
"SECOND_CORE_RPC": {
|
||||||
"HOST": "__SECOND_CORE_RPC_HOST__",
|
"HOST": "__SECOND_CORE_RPC_HOST__",
|
||||||
"PORT": 17,
|
"PORT": 17,
|
||||||
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
|
"USERNAME": "__SECOND_CORE_RPC_USERNAME__",
|
||||||
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
|
"PASSWORD": "__SECOND_CORE_RPC_PASSWORD__",
|
||||||
"TIMEOUT": "__SECOND_CORE_RPC_TIMEOUT__"
|
"TIMEOUT": 2000
|
||||||
},
|
},
|
||||||
"DATABASE": {
|
"DATABASE": {
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
@ -63,7 +63,7 @@
|
|||||||
"DATABASE": "__DATABASE_DATABASE__",
|
"DATABASE": "__DATABASE_DATABASE__",
|
||||||
"USERNAME": "__DATABASE_USERNAME__",
|
"USERNAME": "__DATABASE_USERNAME__",
|
||||||
"PASSWORD": "__DATABASE_PASSWORD__",
|
"PASSWORD": "__DATABASE_PASSWORD__",
|
||||||
"TIMEOUT": "__DATABASE_TIMEOUT__"
|
"TIMEOUT": 3000
|
||||||
},
|
},
|
||||||
"SYSLOG": {
|
"SYSLOG": {
|
||||||
"ENABLED": false,
|
"ENABLED": false,
|
||||||
@ -101,14 +101,14 @@
|
|||||||
"BISQ_ONION": "__EXTERNAL_DATA_SERVER_BISQ_ONION__"
|
"BISQ_ONION": "__EXTERNAL_DATA_SERVER_BISQ_ONION__"
|
||||||
},
|
},
|
||||||
"LIGHTNING": {
|
"LIGHTNING": {
|
||||||
"ENABLED": "__LIGHTNING_ENABLED__",
|
"ENABLED": true,
|
||||||
"BACKEND": "__LIGHTNING_BACKEND__",
|
"BACKEND": "__LIGHTNING_BACKEND__",
|
||||||
"TOPOLOGY_FOLDER": "__LIGHTNING_TOPOLOGY_FOLDER__",
|
"TOPOLOGY_FOLDER": "__LIGHTNING_TOPOLOGY_FOLDER__",
|
||||||
"STATS_REFRESH_INTERVAL": 600,
|
"STATS_REFRESH_INTERVAL": 600,
|
||||||
"GRAPH_REFRESH_INTERVAL": 600,
|
"GRAPH_REFRESH_INTERVAL": 600,
|
||||||
"LOGGER_UPDATE_INTERVAL": 30,
|
"LOGGER_UPDATE_INTERVAL": 30,
|
||||||
"FORENSICS_INTERVAL": 43200,
|
"FORENSICS_INTERVAL": 43200,
|
||||||
"FORENSICS_RATE_LIMIT": "__FORENSICS_RATE_LIMIT__"
|
"FORENSICS_RATE_LIMIT": 1234
|
||||||
},
|
},
|
||||||
"LND": {
|
"LND": {
|
||||||
"TLS_CERT_PATH": "",
|
"TLS_CERT_PATH": "",
|
||||||
@ -119,4 +119,4 @@
|
|||||||
"CLIGHTNING": {
|
"CLIGHTNING": {
|
||||||
"SOCKET": "__CLIGHTNING_SOCKET__"
|
"SOCKET": "__CLIGHTNING_SOCKET__"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -152,4 +152,94 @@ describe('Mempool Backend Config', () => {
|
|||||||
expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER);
|
expect(config.EXTERNAL_DATA_SERVER).toStrictEqual(fixture.EXTERNAL_DATA_SERVER);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should ensure the docker start.sh script has default values', () => {
|
||||||
|
jest.isolateModules(() => {
|
||||||
|
const startSh = fs.readFileSync(`${__dirname}/../../../docker/backend/start.sh`, 'utf-8');
|
||||||
|
const fixture = JSON.parse(fs.readFileSync(`${__dirname}/../__fixtures__/mempool-config.template.json`, 'utf8'));
|
||||||
|
|
||||||
|
function parseJson(jsonObj, root?) {
|
||||||
|
for (const [key, value] of Object.entries(jsonObj)) {
|
||||||
|
// We have a few cases where we can't follow the pattern
|
||||||
|
if (root === 'MEMPOOL' && key === 'HTTP_PORT') {
|
||||||
|
console.log('skipping check for MEMPOOL_HTTP_PORT');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'object': {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
parseJson(value, key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
//The flattened string, i.e, __MEMPOOL_ENABLED__
|
||||||
|
const replaceStr = `${root ? '__' + root + '_' : '__'}${key}__`;
|
||||||
|
|
||||||
|
//The string used as the environment variable, i.e, MEMPOOL_ENABLED
|
||||||
|
const envVarStr = `${root ? root : ''}_${key}`;
|
||||||
|
|
||||||
|
//The string used as the default value, to be checked as a regex, i.e, __MEMPOOL_ENABLED__=${MEMPOOL_ENABLED:=(.*)}
|
||||||
|
const defaultEntry = replaceStr + '=' + '\\${' + envVarStr + ':=(.*)' + '}';
|
||||||
|
|
||||||
|
console.log(`looking for ${defaultEntry} in the start.sh script`);
|
||||||
|
const re = new RegExp(defaultEntry);
|
||||||
|
expect(startSh).toMatch(re);
|
||||||
|
|
||||||
|
//The string that actually replaces the values in the config file
|
||||||
|
const sedStr = 'sed -i "s!' + replaceStr + '!${' + replaceStr + '}!g" mempool-config.json';
|
||||||
|
console.log(`looking for ${sedStr} in the start.sh script`);
|
||||||
|
expect(startSh).toContain(sedStr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parseJson(fixture);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should ensure that the mempool-config.json Docker template has all the keys', () => {
|
||||||
|
jest.isolateModules(() => {
|
||||||
|
const fixture = JSON.parse(fs.readFileSync(`${__dirname}/../__fixtures__/mempool-config.template.json`, 'utf8'));
|
||||||
|
const dockerJson = fs.readFileSync(`${__dirname}/../../../docker/backend/mempool-config.json`, 'utf-8');
|
||||||
|
|
||||||
|
function parseJson(jsonObj, root?) {
|
||||||
|
for (const [key, value] of Object.entries(jsonObj)) {
|
||||||
|
switch (typeof value) {
|
||||||
|
case 'object': {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
// numbers, arrays and booleans won't be enclosed by quotes
|
||||||
|
const replaceStr = `${root ? '__' + root + '_' : '__'}${key}__`;
|
||||||
|
expect(dockerJson).toContain(`"${key}": ${replaceStr}`);
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
//Check for top level config keys
|
||||||
|
expect(dockerJson).toContain(`"${key}"`);
|
||||||
|
parseJson(value, key);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 'string': {
|
||||||
|
// strings should be enclosed by quotes
|
||||||
|
const replaceStr = `${root ? '__' + root + '_' : '__'}${key}__`;
|
||||||
|
expect(dockerJson).toContain(`"${key}": "${replaceStr}"`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
// numbers, arrays and booleans won't be enclosed by quotes
|
||||||
|
const replaceStr = `${root ? '__' + root + '_' : '__'}${key}__`;
|
||||||
|
expect(dockerJson).toContain(`"${key}": ${replaceStr}`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
parseJson(fixture);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -6,9 +6,9 @@ const PROPAGATION_MARGIN = 180; // in seconds, time since a transaction is first
|
|||||||
|
|
||||||
class Audit {
|
class Audit {
|
||||||
auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended })
|
auditBlock(transactions: TransactionExtended[], projectedBlocks: MempoolBlockWithTransactions[], mempool: { [txId: string]: TransactionExtended })
|
||||||
: { censored: string[], added: string[], fresh: string[], score: number, similarity: number } {
|
: { censored: string[], added: string[], fresh: string[], sigop: string[], score: number, similarity: number } {
|
||||||
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
if (!projectedBlocks?.[0]?.transactionIds || !mempool) {
|
||||||
return { censored: [], added: [], fresh: [], score: 0, similarity: 1 };
|
return { censored: [], added: [], fresh: [], sigop: [], score: 0, similarity: 1 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const matches: string[] = []; // present in both mined block and template
|
const matches: string[] = []; // present in both mined block and template
|
||||||
@ -137,6 +137,7 @@ class Audit {
|
|||||||
censored: Object.keys(isCensored),
|
censored: Object.keys(isCensored),
|
||||||
added,
|
added,
|
||||||
fresh,
|
fresh,
|
||||||
|
sigop: [],
|
||||||
score,
|
score,
|
||||||
similarity,
|
similarity,
|
||||||
};
|
};
|
||||||
|
@ -415,12 +415,38 @@ class BitcoinApi implements AbstractBitcoinApi {
|
|||||||
vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript);
|
vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness && vin.witness.length > 1) {
|
if (vin.prevout.scriptpubkey_type === 'v1_p2tr' && vin.witness) {
|
||||||
const witnessScript = vin.witness[vin.witness.length - 2];
|
const witnessScript = this.witnessToP2TRScript(vin.witness);
|
||||||
vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript);
|
if (witnessScript !== null) {
|
||||||
|
vin.inner_witnessscript_asm = this.convertScriptSigAsm(witnessScript);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This function must only be called when we know the witness we are parsing
|
||||||
|
* is a taproot witness.
|
||||||
|
* @param witness An array of hex strings that represents the witness stack of
|
||||||
|
* the input.
|
||||||
|
* @returns null if the witness is not a script spend, and the hex string of
|
||||||
|
* the script item if it is a script spend.
|
||||||
|
*/
|
||||||
|
private witnessToP2TRScript(witness: string[]): string | null {
|
||||||
|
if (witness.length < 2) return null;
|
||||||
|
// Note: see BIP341 for parsing details of witness stack
|
||||||
|
|
||||||
|
// If there are at least two witness elements, and the first byte of the
|
||||||
|
// last element is 0x50, this last element is called annex a and
|
||||||
|
// is removed from the witness stack.
|
||||||
|
const hasAnnex = witness[witness.length - 1].substring(0, 2) === '50';
|
||||||
|
// If there are at least two witness elements left, script path spending is used.
|
||||||
|
// Call the second-to-last stack element s, the script.
|
||||||
|
// (Note: this phrasing from BIP341 assumes we've *removed* the annex from the stack)
|
||||||
|
if (hasAnnex && witness.length < 3) return null;
|
||||||
|
const positionOfScript = hasAnnex ? witness.length - 3 : witness.length - 2;
|
||||||
|
return witness[positionOfScript];
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BitcoinApi;
|
export default BitcoinApi;
|
||||||
|
@ -211,6 +211,8 @@ class BitcoinRoutes {
|
|||||||
bestDescendant: tx.bestDescendant || null,
|
bestDescendant: tx.bestDescendant || null,
|
||||||
descendants: tx.descendants || null,
|
descendants: tx.descendants || null,
|
||||||
effectiveFeePerVsize: tx.effectiveFeePerVsize || null,
|
effectiveFeePerVsize: tx.effectiveFeePerVsize || null,
|
||||||
|
sigops: tx.sigops,
|
||||||
|
adjustedVsize: tx.adjustedVsize,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import config from '../config';
|
|||||||
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import memPool from './mempool';
|
import memPool from './mempool';
|
||||||
import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionStripped, TransactionMinerInfo, CpfpSummary } from '../mempool.interfaces';
|
import { BlockExtended, BlockExtension, BlockSummary, PoolTag, TransactionExtended, TransactionStripped, TransactionMinerInfo, CpfpSummary, MempoolTransactionExtended } from '../mempool.interfaces';
|
||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
import diskCache from './disk-cache';
|
import diskCache from './disk-cache';
|
||||||
import transactionUtils from './transaction-utils';
|
import transactionUtils from './transaction-utils';
|
||||||
@ -76,6 +76,7 @@ class Blocks {
|
|||||||
blockHeight: number,
|
blockHeight: number,
|
||||||
onlyCoinbase: boolean,
|
onlyCoinbase: boolean,
|
||||||
quiet: boolean = false,
|
quiet: boolean = false,
|
||||||
|
addMempoolData: boolean = false,
|
||||||
): Promise<TransactionExtended[]> {
|
): Promise<TransactionExtended[]> {
|
||||||
const transactions: TransactionExtended[] = [];
|
const transactions: TransactionExtended[] = [];
|
||||||
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
||||||
@ -96,14 +97,14 @@ class Blocks {
|
|||||||
logger.debug(`Indexing tx ${i + 1} of ${txIds.length} in block #${blockHeight}`);
|
logger.debug(`Indexing tx ${i + 1} of ${txIds.length} in block #${blockHeight}`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i]);
|
const tx = await transactionUtils.$getTransactionExtended(txIds[i], false, false, false, addMempoolData);
|
||||||
transactions.push(tx);
|
transactions.push(tx);
|
||||||
transactionsFetched++;
|
transactionsFetched++;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
if (config.MEMPOOL.BACKEND === 'esplora') {
|
if (config.MEMPOOL.BACKEND === 'esplora') {
|
||||||
// Try again with core
|
// Try again with core
|
||||||
const tx = await transactionUtils.$getTransactionExtended(txIds[i], false, false, true);
|
const tx = await transactionUtils.$getTransactionExtended(txIds[i], false, false, true, addMempoolData);
|
||||||
transactions.push(tx);
|
transactions.push(tx);
|
||||||
transactionsFetched++;
|
transactionsFetched++;
|
||||||
} else {
|
} else {
|
||||||
@ -126,11 +127,13 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions.forEach((tx) => {
|
if (addMempoolData) {
|
||||||
if (!tx.cpfpChecked) {
|
transactions.forEach((tx) => {
|
||||||
Common.setRelativesAndGetCpfpInfo(tx, mempool); // Child Pay For Parent
|
if (!tx.cpfpChecked) {
|
||||||
}
|
Common.setRelativesAndGetCpfpInfo(tx as MempoolTransactionExtended, mempool); // Child Pay For Parent
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!quiet) {
|
if (!quiet) {
|
||||||
logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${transactionsFetched} fetched through backend service.`);
|
logger.debug(`${transactionsFound} of ${txIds.length} found in mempool. ${transactionsFetched} fetched through backend service.`);
|
||||||
@ -306,7 +309,7 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const asciiScriptSig = transactionUtils.hex2ascii(txMinerInfo.vin[0].scriptsig);
|
const asciiScriptSig = transactionUtils.hex2ascii(txMinerInfo.vin[0].scriptsig);
|
||||||
const address = txMinerInfo.vout[0].scriptpubkey_address;
|
const addresses = txMinerInfo.vout.map((vout) => vout.scriptpubkey_address).filter((address) => address);
|
||||||
|
|
||||||
let pools: PoolTag[] = [];
|
let pools: PoolTag[] = [];
|
||||||
if (config.DATABASE.ENABLED === true) {
|
if (config.DATABASE.ENABLED === true) {
|
||||||
@ -316,11 +319,13 @@ class Blocks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < pools.length; ++i) {
|
for (let i = 0; i < pools.length; ++i) {
|
||||||
if (address !== undefined) {
|
if (addresses.length) {
|
||||||
const addresses: string[] = typeof pools[i].addresses === 'string' ?
|
const poolAddresses: string[] = typeof pools[i].addresses === 'string' ?
|
||||||
JSON.parse(pools[i].addresses) : pools[i].addresses;
|
JSON.parse(pools[i].addresses) : pools[i].addresses;
|
||||||
if (addresses.indexOf(address) !== -1) {
|
for (let y = 0; y < poolAddresses.length; y++) {
|
||||||
return pools[i];
|
if (addresses.indexOf(poolAddresses[y]) !== -1) {
|
||||||
|
return pools[i];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -594,7 +599,15 @@ class Blocks {
|
|||||||
const verboseBlock = await bitcoinClient.getBlock(blockHash, 2);
|
const verboseBlock = await bitcoinClient.getBlock(blockHash, 2);
|
||||||
const block = BitcoinApi.convertBlock(verboseBlock);
|
const block = BitcoinApi.convertBlock(verboseBlock);
|
||||||
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
const txIds: string[] = await bitcoinApi.$getTxIdsForBlock(blockHash);
|
||||||
const transactions = await this.$getTransactionsExtended(blockHash, block.height, false);
|
const transactions = await this.$getTransactionsExtended(blockHash, block.height, false, false, true);
|
||||||
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
|
// fill in missing transaction fee data from verboseBlock
|
||||||
|
for (let i = 0; i < transactions.length; i++) {
|
||||||
|
if (!transactions[i].fee && transactions[i].txid === verboseBlock.tx[i].txid) {
|
||||||
|
transactions[i].fee = verboseBlock.tx[i].fee * 100_000_000;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
const cpfpSummary: CpfpSummary = Common.calculateCpfp(block.height, transactions);
|
const cpfpSummary: CpfpSummary = Common.calculateCpfp(block.height, transactions);
|
||||||
const blockExtended: BlockExtended = await this.$getBlockExtended(block, cpfpSummary.transactions);
|
const blockExtended: BlockExtended = await this.$getBlockExtended(block, cpfpSummary.transactions);
|
||||||
const blockSummary: BlockSummary = this.summarizeBlock(verboseBlock);
|
const blockSummary: BlockSummary = this.summarizeBlock(verboseBlock);
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Ancestor, CpfpInfo, CpfpSummary, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces';
|
import { Ancestor, CpfpInfo, CpfpSummary, EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats } from '../mempool.interfaces';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
import { NodeSocket } from '../repositories/NodesSocketsRepository';
|
||||||
import { isIP } from 'net';
|
import { isIP } from 'net';
|
||||||
@ -57,15 +57,15 @@ export class Common {
|
|||||||
return arr;
|
return arr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static findRbfTransactions(added: TransactionExtended[], deleted: TransactionExtended[]): { [txid: string]: TransactionExtended[] } {
|
static findRbfTransactions(added: MempoolTransactionExtended[], deleted: MempoolTransactionExtended[]): { [txid: string]: MempoolTransactionExtended[] } {
|
||||||
const matches: { [txid: string]: TransactionExtended[] } = {};
|
const matches: { [txid: string]: MempoolTransactionExtended[] } = {};
|
||||||
added
|
added
|
||||||
.forEach((addedTx) => {
|
.forEach((addedTx) => {
|
||||||
const foundMatches = deleted.filter((deletedTx) => {
|
const foundMatches = deleted.filter((deletedTx) => {
|
||||||
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
// The new tx must, absolutely speaking, pay at least as much fee as the replaced tx.
|
||||||
return addedTx.fee > deletedTx.fee
|
return addedTx.fee > deletedTx.fee
|
||||||
// The new transaction must pay more fee per kB than the replaced tx.
|
// The new transaction must pay more fee per kB than the replaced tx.
|
||||||
&& addedTx.feePerVsize > deletedTx.feePerVsize
|
&& addedTx.adjustedFeePerVsize > deletedTx.adjustedFeePerVsize
|
||||||
// Spends one or more of the same inputs
|
// Spends one or more of the same inputs
|
||||||
&& deletedTx.vin.some((deletedVin) =>
|
&& deletedTx.vin.some((deletedVin) =>
|
||||||
addedTx.vin.some((vin) => vin.txid === deletedVin.txid && vin.vout === deletedVin.vout));
|
addedTx.vin.some((vin) => vin.txid === deletedVin.txid && vin.vout === deletedVin.vout));
|
||||||
@ -77,6 +77,24 @@ export class Common {
|
|||||||
return matches;
|
return matches;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static findMinedRbfTransactions(minedTransactions: TransactionExtended[], spendMap: Map<string, MempoolTransactionExtended>): { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }} {
|
||||||
|
const matches: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }} = {};
|
||||||
|
for (const tx of minedTransactions) {
|
||||||
|
const replaced: Set<MempoolTransactionExtended> = new Set();
|
||||||
|
for (let i = 0; i < tx.vin.length; i++) {
|
||||||
|
const vin = tx.vin[i];
|
||||||
|
const match = spendMap.get(`${vin.txid}:${vin.vout}`);
|
||||||
|
if (match && match.txid !== tx.txid) {
|
||||||
|
replaced.add(match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (replaced.size) {
|
||||||
|
matches[tx.txid] = { replaced: Array.from(replaced), replacedBy: tx };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
static stripTransaction(tx: TransactionExtended): TransactionStripped {
|
static stripTransaction(tx: TransactionExtended): TransactionStripped {
|
||||||
return {
|
return {
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
@ -102,18 +120,18 @@ export class Common {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static setRelativesAndGetCpfpInfo(tx: TransactionExtended, memPool: { [txid: string]: TransactionExtended }): CpfpInfo {
|
static setRelativesAndGetCpfpInfo(tx: MempoolTransactionExtended, memPool: { [txid: string]: MempoolTransactionExtended }): CpfpInfo {
|
||||||
const parents = this.findAllParents(tx, memPool);
|
const parents = this.findAllParents(tx, memPool);
|
||||||
const lowerFeeParents = parents.filter((parent) => parent.feePerVsize < tx.effectiveFeePerVsize);
|
const lowerFeeParents = parents.filter((parent) => parent.adjustedFeePerVsize < tx.effectiveFeePerVsize);
|
||||||
|
|
||||||
let totalWeight = tx.weight + lowerFeeParents.reduce((prev, val) => prev + val.weight, 0);
|
let totalWeight = (tx.adjustedVsize * 4) + lowerFeeParents.reduce((prev, val) => prev + (val.adjustedVsize * 4), 0);
|
||||||
let totalFees = tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
|
let totalFees = tx.fee + lowerFeeParents.reduce((prev, val) => prev + val.fee, 0);
|
||||||
|
|
||||||
tx.ancestors = parents
|
tx.ancestors = parents
|
||||||
.map((t) => {
|
.map((t) => {
|
||||||
return {
|
return {
|
||||||
txid: t.txid,
|
txid: t.txid,
|
||||||
weight: t.weight,
|
weight: (t.adjustedVsize * 4),
|
||||||
fee: t.fee,
|
fee: t.fee,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -134,8 +152,8 @@ export class Common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private static findAllParents(tx: TransactionExtended, memPool: { [txid: string]: TransactionExtended }): TransactionExtended[] {
|
private static findAllParents(tx: MempoolTransactionExtended, memPool: { [txid: string]: MempoolTransactionExtended }): MempoolTransactionExtended[] {
|
||||||
let parents: TransactionExtended[] = [];
|
let parents: MempoolTransactionExtended[] = [];
|
||||||
tx.vin.forEach((parent) => {
|
tx.vin.forEach((parent) => {
|
||||||
if (parents.find((p) => p.txid === parent.txid)) {
|
if (parents.find((p) => p.txid === parent.txid)) {
|
||||||
return;
|
return;
|
||||||
@ -143,17 +161,17 @@ export class Common {
|
|||||||
|
|
||||||
const parentTx = memPool[parent.txid];
|
const parentTx = memPool[parent.txid];
|
||||||
if (parentTx) {
|
if (parentTx) {
|
||||||
if (tx.bestDescendant && tx.bestDescendant.fee / (tx.bestDescendant.weight / 4) > parentTx.feePerVsize) {
|
if (tx.bestDescendant && tx.bestDescendant.fee / (tx.bestDescendant.weight / 4) > parentTx.adjustedFeePerVsize) {
|
||||||
if (parentTx.bestDescendant && parentTx.bestDescendant.fee < tx.fee + tx.bestDescendant.fee) {
|
if (parentTx.bestDescendant && parentTx.bestDescendant.fee < tx.fee + tx.bestDescendant.fee) {
|
||||||
parentTx.bestDescendant = {
|
parentTx.bestDescendant = {
|
||||||
weight: tx.weight + tx.bestDescendant.weight,
|
weight: (tx.adjustedVsize * 4) + tx.bestDescendant.weight,
|
||||||
fee: tx.fee + tx.bestDescendant.fee,
|
fee: tx.fee + tx.bestDescendant.fee,
|
||||||
txid: tx.txid,
|
txid: tx.txid,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (tx.feePerVsize > parentTx.feePerVsize) {
|
} else if (tx.adjustedFeePerVsize > parentTx.adjustedFeePerVsize) {
|
||||||
parentTx.bestDescendant = {
|
parentTx.bestDescendant = {
|
||||||
weight: tx.weight,
|
weight: (tx.adjustedVsize * 4),
|
||||||
fee: tx.fee,
|
fee: tx.fee,
|
||||||
txid: tx.txid
|
txid: tx.txid
|
||||||
};
|
};
|
||||||
|
@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
|||||||
import { RowDataPacket } from 'mysql2';
|
import { RowDataPacket } from 'mysql2';
|
||||||
|
|
||||||
class DatabaseMigration {
|
class DatabaseMigration {
|
||||||
private static currentVersion = 59;
|
private static currentVersion = 61;
|
||||||
private queryTimeout = 3600_000;
|
private queryTimeout = 3600_000;
|
||||||
private statisticsAddedIndexed = false;
|
private statisticsAddedIndexed = false;
|
||||||
private uniqueLogs: string[] = [];
|
private uniqueLogs: string[] = [];
|
||||||
@ -516,6 +516,23 @@ class DatabaseMigration {
|
|||||||
// https://github.com/mempool/mempool/issues/3360
|
// https://github.com/mempool/mempool/issues/3360
|
||||||
await this.$executeQuery(`TRUNCATE prices`);
|
await this.$executeQuery(`TRUNCATE prices`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 60 && isBitcoin === true) {
|
||||||
|
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD sigop_txs JSON DEFAULT "[]"');
|
||||||
|
await this.updateToSchemaVersion(60);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (databaseSchemaVersion < 61 && isBitcoin === true) {
|
||||||
|
// Break block templates into their own table
|
||||||
|
if (! await this.$checkIfTableExists('blocks_templates')) {
|
||||||
|
await this.$executeQuery('CREATE TABLE blocks_templates AS SELECT id, template FROM blocks_summaries WHERE template != "[]"');
|
||||||
|
}
|
||||||
|
await this.$executeQuery('ALTER TABLE blocks_templates MODIFY template JSON DEFAULT "[]"');
|
||||||
|
await this.$executeQuery('ALTER TABLE blocks_templates ADD PRIMARY KEY (id)');
|
||||||
|
await this.$executeQuery('ALTER TABLE blocks_summaries DROP COLUMN template');
|
||||||
|
await this.updateToSchemaVersion(61);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -21,6 +21,7 @@ class DiskCache {
|
|||||||
private static RBF_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/rbfcache.json';
|
private static RBF_FILE_NAME = config.MEMPOOL.CACHE_DIR + '/rbfcache.json';
|
||||||
private static CHUNK_FILES = 25;
|
private static CHUNK_FILES = 25;
|
||||||
private isWritingCache = false;
|
private isWritingCache = false;
|
||||||
|
private ignoreBlocksCache = false;
|
||||||
|
|
||||||
private semaphore: { resume: (() => void)[], locks: number } = {
|
private semaphore: { resume: (() => void)[], locks: number } = {
|
||||||
resume: [],
|
resume: [],
|
||||||
@ -218,8 +219,13 @@ class DiskCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await memPool.$setMempool(data.mempool);
|
await memPool.$setMempool(data.mempool);
|
||||||
blocks.setBlocks(data.blocks);
|
if (!this.ignoreBlocksCache) {
|
||||||
blocks.setBlockSummaries(data.blockSummaries || []);
|
blocks.setBlocks(data.blocks);
|
||||||
|
blocks.setBlockSummaries(data.blockSummaries || []);
|
||||||
|
} else {
|
||||||
|
logger.info('Re-saving cache with empty recent blocks data');
|
||||||
|
await this.$saveCacheToDisk(true);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn('Failed to parse mempoool and blocks cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
|
logger.warn('Failed to parse mempoool and blocks cache. Skipping. Reason: ' + (e instanceof Error ? e.message : e));
|
||||||
}
|
}
|
||||||
@ -273,6 +279,10 @@ class DiskCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setIgnoreBlocksCache(): void {
|
||||||
|
this.ignoreBlocksCache = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new DiskCache();
|
export default new DiskCache();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { MempoolBlock, TransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats } from '../mempool.interfaces';
|
import { MempoolBlock, MempoolTransactionExtended, TransactionStripped, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats } from '../mempool.interfaces';
|
||||||
import { Common, OnlineFeeStatsCalculator } from './common';
|
import { Common, OnlineFeeStatsCalculator } from './common';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { Worker } from 'worker_threads';
|
import { Worker } from 'worker_threads';
|
||||||
@ -36,9 +36,9 @@ class MempoolBlocks {
|
|||||||
return this.mempoolBlockDeltas;
|
return this.mempoolBlockDeltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateMempoolBlocks(memPool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): MempoolBlockWithTransactions[] {
|
public updateMempoolBlocks(memPool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false): MempoolBlockWithTransactions[] {
|
||||||
const latestMempool = memPool;
|
const latestMempool = memPool;
|
||||||
const memPoolArray: TransactionExtended[] = [];
|
const memPoolArray: MempoolTransactionExtended[] = [];
|
||||||
for (const i in latestMempool) {
|
for (const i in latestMempool) {
|
||||||
if (latestMempool.hasOwnProperty(i)) {
|
if (latestMempool.hasOwnProperty(i)) {
|
||||||
memPoolArray.push(latestMempool[i]);
|
memPoolArray.push(latestMempool[i]);
|
||||||
@ -52,17 +52,17 @@ class MempoolBlocks {
|
|||||||
tx.ancestors = [];
|
tx.ancestors = [];
|
||||||
tx.cpfpChecked = false;
|
tx.cpfpChecked = false;
|
||||||
if (!tx.effectiveFeePerVsize) {
|
if (!tx.effectiveFeePerVsize) {
|
||||||
tx.effectiveFeePerVsize = tx.feePerVsize;
|
tx.effectiveFeePerVsize = tx.adjustedFeePerVsize;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// First sort
|
// First sort
|
||||||
memPoolArray.sort((a, b) => {
|
memPoolArray.sort((a, b) => {
|
||||||
if (a.feePerVsize === b.feePerVsize) {
|
if (a.adjustedFeePerVsize === b.adjustedFeePerVsize) {
|
||||||
// tie-break by lexicographic txid order for stability
|
// tie-break by lexicographic txid order for stability
|
||||||
return a.txid < b.txid ? -1 : 1;
|
return a.txid < b.txid ? -1 : 1;
|
||||||
} else {
|
} else {
|
||||||
return b.feePerVsize - a.feePerVsize;
|
return b.adjustedFeePerVsize - a.adjustedFeePerVsize;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ class MempoolBlocks {
|
|||||||
return blocks;
|
return blocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateMempoolBlocks(transactionsSorted: TransactionExtended[]): MempoolBlockWithTransactions[] {
|
private calculateMempoolBlocks(transactionsSorted: MempoolTransactionExtended[]): MempoolBlockWithTransactions[] {
|
||||||
const mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
const mempoolBlocks: MempoolBlockWithTransactions[] = [];
|
||||||
let feeStatsCalculator: OnlineFeeStatsCalculator = new OnlineFeeStatsCalculator(config.MEMPOOL.BLOCK_WEIGHT_UNITS);
|
let feeStatsCalculator: OnlineFeeStatsCalculator = new OnlineFeeStatsCalculator(config.MEMPOOL.BLOCK_WEIGHT_UNITS);
|
||||||
let onlineStats = false;
|
let onlineStats = false;
|
||||||
@ -112,7 +112,7 @@ class MempoolBlocks {
|
|||||||
let blockFees = 0;
|
let blockFees = 0;
|
||||||
const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
|
const sizeLimit = (config.MEMPOOL.BLOCK_WEIGHT_UNITS / 4) * 1.2;
|
||||||
let transactionIds: string[] = [];
|
let transactionIds: string[] = [];
|
||||||
let transactions: TransactionExtended[] = [];
|
let transactions: MempoolTransactionExtended[] = [];
|
||||||
transactionsSorted.forEach((tx, index) => {
|
transactionsSorted.forEach((tx, index) => {
|
||||||
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
|
if (blockWeight + tx.weight <= config.MEMPOOL.BLOCK_WEIGHT_UNITS
|
||||||
|| mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) {
|
|| mempoolBlocks.length === config.MEMPOOL.MEMPOOL_BLOCKS_AMOUNT - 1) {
|
||||||
@ -205,7 +205,7 @@ class MempoolBlocks {
|
|||||||
return mempoolBlockDeltas;
|
return mempoolBlockDeltas;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $makeBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
|
public async $makeBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, saveResults: boolean = false): Promise<MempoolBlockWithTransactions[]> {
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
|
|
||||||
// reset mempool short ids
|
// reset mempool short ids
|
||||||
@ -222,9 +222,10 @@ class MempoolBlocks {
|
|||||||
strippedMempool.set(entry.uid, {
|
strippedMempool.set(entry.uid, {
|
||||||
uid: entry.uid,
|
uid: entry.uid,
|
||||||
fee: entry.fee,
|
fee: entry.fee,
|
||||||
weight: entry.weight,
|
weight: (entry.adjustedVsize * 4),
|
||||||
feePerVsize: entry.fee / (entry.weight / 4),
|
sigops: entry.sigops,
|
||||||
effectiveFeePerVsize: entry.effectiveFeePerVsize || (entry.fee / (entry.weight / 4)),
|
feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
|
||||||
|
effectiveFeePerVsize: entry.effectiveFeePerVsize || entry.adjustedFeePerVsize || entry.feePerVsize,
|
||||||
inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
|
inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -268,7 +269,7 @@ class MempoolBlocks {
|
|||||||
return this.mempoolBlocks;
|
return this.mempoolBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $updateBlockTemplates(newMempool: { [txid: string]: TransactionExtended }, added: TransactionExtended[], removed: TransactionExtended[], saveResults: boolean = false): Promise<void> {
|
public async $updateBlockTemplates(newMempool: { [txid: string]: MempoolTransactionExtended }, added: MempoolTransactionExtended[], removed: MempoolTransactionExtended[], saveResults: boolean = false): Promise<void> {
|
||||||
if (!this.txSelectionWorker) {
|
if (!this.txSelectionWorker) {
|
||||||
// need to reset the worker
|
// need to reset the worker
|
||||||
await this.$makeBlockTemplates(newMempool, saveResults);
|
await this.$makeBlockTemplates(newMempool, saveResults);
|
||||||
@ -287,9 +288,10 @@ class MempoolBlocks {
|
|||||||
return {
|
return {
|
||||||
uid: entry.uid || 0,
|
uid: entry.uid || 0,
|
||||||
fee: entry.fee,
|
fee: entry.fee,
|
||||||
weight: entry.weight,
|
weight: (entry.adjustedVsize * 4),
|
||||||
feePerVsize: entry.fee / (entry.weight / 4),
|
sigops: entry.sigops,
|
||||||
effectiveFeePerVsize: entry.effectiveFeePerVsize || (entry.fee / (entry.weight / 4)),
|
feePerVsize: entry.adjustedFeePerVsize || entry.feePerVsize,
|
||||||
|
effectiveFeePerVsize: entry.effectiveFeePerVsize || entry.adjustedFeePerVsize || entry.feePerVsize,
|
||||||
inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
|
inputs: entry.vin.map(v => this.getUid(newMempool[v.txid])).filter(uid => uid != null) as number[],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -341,12 +343,12 @@ class MempoolBlocks {
|
|||||||
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
for (let blockIndex = 0; blockIndex < blocks.length; blockIndex++) {
|
||||||
const block: string[] = blocks[blockIndex];
|
const block: string[] = blocks[blockIndex];
|
||||||
let txid: string;
|
let txid: string;
|
||||||
let mempoolTx: TransactionExtended;
|
let mempoolTx: MempoolTransactionExtended;
|
||||||
let totalSize = 0;
|
let totalSize = 0;
|
||||||
let totalVsize = 0;
|
let totalVsize = 0;
|
||||||
let totalWeight = 0;
|
let totalWeight = 0;
|
||||||
let totalFees = 0;
|
let totalFees = 0;
|
||||||
const transactions: TransactionExtended[] = [];
|
const transactions: MempoolTransactionExtended[] = [];
|
||||||
for (let txIndex = 0; txIndex < block.length; txIndex++) {
|
for (let txIndex = 0; txIndex < block.length; txIndex++) {
|
||||||
txid = block[txIndex];
|
txid = block[txIndex];
|
||||||
if (txid) {
|
if (txid) {
|
||||||
@ -397,7 +399,7 @@ class MempoolBlocks {
|
|||||||
const relative = {
|
const relative = {
|
||||||
txid: txid,
|
txid: txid,
|
||||||
fee: mempool[txid].fee,
|
fee: mempool[txid].fee,
|
||||||
weight: mempool[txid].weight,
|
weight: (mempool[txid].adjustedVsize * 4),
|
||||||
};
|
};
|
||||||
if (matched) {
|
if (matched) {
|
||||||
descendants.push(relative);
|
descendants.push(relative);
|
||||||
@ -426,7 +428,7 @@ class MempoolBlocks {
|
|||||||
return mempoolBlocks;
|
return mempoolBlocks;
|
||||||
}
|
}
|
||||||
|
|
||||||
private dataToMempoolBlocks(transactionIds: string[], transactions: TransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, feeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
|
private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, feeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
|
||||||
if (!feeStats) {
|
if (!feeStats) {
|
||||||
feeStats = Common.calcEffectiveFeeStatistics(transactions);
|
feeStats = Common.calcEffectiveFeeStatistics(transactions);
|
||||||
}
|
}
|
||||||
@ -447,7 +449,7 @@ class MempoolBlocks {
|
|||||||
this.nextUid = 1;
|
this.nextUid = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setUid(tx: TransactionExtended): number {
|
private setUid(tx: MempoolTransactionExtended): number {
|
||||||
const uid = this.nextUid;
|
const uid = this.nextUid;
|
||||||
this.nextUid++;
|
this.nextUid++;
|
||||||
this.uidMap.set(uid, tx.txid);
|
this.uidMap.set(uid, tx.txid);
|
||||||
@ -455,7 +457,7 @@ class MempoolBlocks {
|
|||||||
return uid;
|
return uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getUid(tx: TransactionExtended): number | void {
|
private getUid(tx: MempoolTransactionExtended): number | void {
|
||||||
if (tx?.uid != null && this.uidMap.has(tx.uid)) {
|
if (tx?.uid != null && this.uidMap.has(tx.uid)) {
|
||||||
return tx.uid;
|
return tx.uid;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||||
import { TransactionExtended, VbytesPerSecond } from '../mempool.interfaces';
|
import { MempoolTransactionExtended, TransactionExtended, VbytesPerSecond } from '../mempool.interfaces';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
import transactionUtils from './transaction-utils';
|
import transactionUtils from './transaction-utils';
|
||||||
@ -13,13 +13,14 @@ import rbfCache from './rbf-cache';
|
|||||||
class Mempool {
|
class Mempool {
|
||||||
private inSync: boolean = false;
|
private inSync: boolean = false;
|
||||||
private mempoolCacheDelta: number = -1;
|
private mempoolCacheDelta: number = -1;
|
||||||
private mempoolCache: { [txId: string]: TransactionExtended } = {};
|
private mempoolCache: { [txId: string]: MempoolTransactionExtended } = {};
|
||||||
|
private spendMap = new Map<string, MempoolTransactionExtended>();
|
||||||
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
private mempoolInfo: IBitcoinApi.MempoolInfo = { loaded: false, size: 0, bytes: 0, usage: 0, total_fee: 0,
|
||||||
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
maxmempool: 300000000, mempoolminfee: 0.00001000, minrelaytxfee: 0.00001000 };
|
||||||
private mempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
private mempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
|
||||||
deletedTransactions: TransactionExtended[]) => void) | undefined;
|
deletedTransactions: MempoolTransactionExtended[]) => void) | undefined;
|
||||||
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: TransactionExtended; }, newTransactions: TransactionExtended[],
|
private $asyncMempoolChangedCallback: ((newMempool: {[txId: string]: MempoolTransactionExtended; }, newTransactions: MempoolTransactionExtended[],
|
||||||
deletedTransactions: TransactionExtended[]) => Promise<void>) | undefined;
|
deletedTransactions: MempoolTransactionExtended[]) => Promise<void>) | undefined;
|
||||||
|
|
||||||
private txPerSecondArray: number[] = [];
|
private txPerSecondArray: number[] = [];
|
||||||
private txPerSecond: number = 0;
|
private txPerSecond: number = 0;
|
||||||
@ -63,28 +64,38 @@ class Mempool {
|
|||||||
return this.latestTransactions;
|
return this.latestTransactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMempoolChangedCallback(fn: (newMempool: { [txId: string]: TransactionExtended; },
|
public setMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
|
||||||
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]) => void) {
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]) => void): void {
|
||||||
this.mempoolChangedCallback = fn;
|
this.mempoolChangedCallback = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: TransactionExtended; },
|
public setAsyncMempoolChangedCallback(fn: (newMempool: { [txId: string]: MempoolTransactionExtended; },
|
||||||
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]) => Promise<void>) {
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]) => Promise<void>): void {
|
||||||
this.$asyncMempoolChangedCallback = fn;
|
this.$asyncMempoolChangedCallback = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getMempool(): { [txid: string]: TransactionExtended } {
|
public getMempool(): { [txid: string]: MempoolTransactionExtended } {
|
||||||
return this.mempoolCache;
|
return this.mempoolCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $setMempool(mempoolData: { [txId: string]: TransactionExtended }) {
|
public getSpendMap(): Map<string, MempoolTransactionExtended> {
|
||||||
|
return this.spendMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $setMempool(mempoolData: { [txId: string]: MempoolTransactionExtended }) {
|
||||||
this.mempoolCache = mempoolData;
|
this.mempoolCache = mempoolData;
|
||||||
|
for (const txid of Object.keys(this.mempoolCache)) {
|
||||||
|
if (this.mempoolCache[txid].sigops == null || this.mempoolCache[txid].effectiveFeePerVsize == null) {
|
||||||
|
this.mempoolCache[txid] = transactionUtils.extendMempoolTransaction(this.mempoolCache[txid]);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.mempoolChangedCallback) {
|
if (this.mempoolChangedCallback) {
|
||||||
this.mempoolChangedCallback(this.mempoolCache, [], []);
|
this.mempoolChangedCallback(this.mempoolCache, [], []);
|
||||||
}
|
}
|
||||||
if (this.$asyncMempoolChangedCallback) {
|
if (this.$asyncMempoolChangedCallback) {
|
||||||
await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
|
await this.$asyncMempoolChangedCallback(this.mempoolCache, [], []);
|
||||||
}
|
}
|
||||||
|
this.addToSpendMap(Object.values(this.mempoolCache));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $updateMemPoolInfo() {
|
public async $updateMemPoolInfo() {
|
||||||
@ -127,7 +138,7 @@ class Mempool {
|
|||||||
const currentMempoolSize = Object.keys(this.mempoolCache).length;
|
const currentMempoolSize = Object.keys(this.mempoolCache).length;
|
||||||
this.updateTimerProgress(timer, 'got raw mempool');
|
this.updateTimerProgress(timer, 'got raw mempool');
|
||||||
const diff = transactions.length - currentMempoolSize;
|
const diff = transactions.length - currentMempoolSize;
|
||||||
const newTransactions: TransactionExtended[] = [];
|
const newTransactions: MempoolTransactionExtended[] = [];
|
||||||
|
|
||||||
this.mempoolCacheDelta = Math.abs(diff);
|
this.mempoolCacheDelta = Math.abs(diff);
|
||||||
|
|
||||||
@ -149,7 +160,7 @@ class Mempool {
|
|||||||
for (const txid of transactions) {
|
for (const txid of transactions) {
|
||||||
if (!this.mempoolCache[txid]) {
|
if (!this.mempoolCache[txid]) {
|
||||||
try {
|
try {
|
||||||
const transaction = await transactionUtils.$getTransactionExtended(txid);
|
const transaction = await transactionUtils.$getMempoolTransactionExtended(txid, false, false, false);
|
||||||
this.updateTimerProgress(timer, 'fetched new transaction');
|
this.updateTimerProgress(timer, 'fetched new transaction');
|
||||||
this.mempoolCache[txid] = transaction;
|
this.mempoolCache[txid] = transaction;
|
||||||
if (this.inSync) {
|
if (this.inSync) {
|
||||||
@ -199,7 +210,7 @@ class Mempool {
|
|||||||
}, 1000 * 60 * config.MEMPOOL.CLEAR_PROTECTION_MINUTES);
|
}, 1000 * 60 * config.MEMPOOL.CLEAR_PROTECTION_MINUTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedTransactions: TransactionExtended[] = [];
|
const deletedTransactions: MempoolTransactionExtended[] = [];
|
||||||
|
|
||||||
if (this.mempoolProtection !== 1) {
|
if (this.mempoolProtection !== 1) {
|
||||||
this.mempoolProtection = 0;
|
this.mempoolProtection = 0;
|
||||||
@ -267,7 +278,7 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public handleRbfTransactions(rbfTransactions: { [txid: string]: TransactionExtended[]; }): void {
|
public handleRbfTransactions(rbfTransactions: { [txid: string]: MempoolTransactionExtended[]; }): void {
|
||||||
for (const rbfTransaction in rbfTransactions) {
|
for (const rbfTransaction in rbfTransactions) {
|
||||||
if (this.mempoolCache[rbfTransaction] && rbfTransactions[rbfTransaction]?.length) {
|
if (this.mempoolCache[rbfTransaction] && rbfTransactions[rbfTransaction]?.length) {
|
||||||
// Store replaced transactions
|
// Store replaced transactions
|
||||||
@ -276,6 +287,34 @@ class Mempool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public handleMinedRbfTransactions(rbfTransactions: { [txid: string]: { replaced: MempoolTransactionExtended[], replacedBy: TransactionExtended }}): void {
|
||||||
|
for (const rbfTransaction in rbfTransactions) {
|
||||||
|
if (rbfTransactions[rbfTransaction].replacedBy && rbfTransactions[rbfTransaction]?.replaced?.length) {
|
||||||
|
// Store replaced transactions
|
||||||
|
rbfCache.add(rbfTransactions[rbfTransaction].replaced, transactionUtils.extendMempoolTransaction(rbfTransactions[rbfTransaction].replacedBy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public addToSpendMap(transactions: MempoolTransactionExtended[]): void {
|
||||||
|
for (const tx of transactions) {
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
this.spendMap.set(`${vin.txid}:${vin.vout}`, tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFromSpendMap(transactions: TransactionExtended[]): void {
|
||||||
|
for (const tx of transactions) {
|
||||||
|
for (const vin of tx.vin) {
|
||||||
|
const key = `${vin.txid}:${vin.vout}`;
|
||||||
|
if (this.spendMap.get(key)?.txid === tx.txid) {
|
||||||
|
this.spendMap.delete(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateTxPerSecond() {
|
private updateTxPerSecond() {
|
||||||
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
const nowMinusTimeSpan = new Date().getTime() - (1000 * config.STATISTICS.TX_PER_SECOND_SAMPLE_PERIOD);
|
||||||
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
this.txPerSecondArray = this.txPerSecondArray.filter((unixTime) => unixTime > nowMinusTimeSpan);
|
||||||
|
@ -26,7 +26,7 @@ class MiningRoutes {
|
|||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/fee-rates/:interval', this.$getHistoricalBlockFeeRates)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/sizes-weights/:interval', this.$getHistoricalBlockSizeAndWeight)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/difficulty-adjustments/:interval', this.$getDifficultyAdjustments)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlockPrediction)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/predictions/:interval', this.$getHistoricalBlocksHealth)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores', this.$getBlockAuditScores)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores', this.$getBlockAuditScores)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores/:height', this.$getBlockAuditScores)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/scores/:height', this.$getBlockAuditScores)
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/score/:hash', this.$getBlockAuditScore)
|
.get(config.MEMPOOL.API_URL_PREFIX + 'mining/blocks/audit/score/:hash', this.$getBlockAuditScore)
|
||||||
@ -244,15 +244,15 @@ class MiningRoutes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async $getHistoricalBlockPrediction(req: Request, res: Response) {
|
private async $getHistoricalBlocksHealth(req: Request, res: Response) {
|
||||||
try {
|
try {
|
||||||
const blockPredictions = await mining.$getBlockPredictionsHistory(req.params.interval);
|
const blocksHealth = await mining.$getBlocksHealthHistory(req.params.interval);
|
||||||
const blockCount = await BlocksAuditsRepository.$getPredictionsCount();
|
const blockCount = await BlocksAuditsRepository.$getBlocksHealthCount();
|
||||||
res.header('Pragma', 'public');
|
res.header('Pragma', 'public');
|
||||||
res.header('Cache-control', 'public');
|
res.header('Cache-control', 'public');
|
||||||
res.header('X-total-count', blockCount.toString());
|
res.header('X-total-count', blockCount.toString());
|
||||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||||
res.json(blockPredictions.map(prediction => [prediction.time, prediction.height, prediction.match_rate]));
|
res.json(blocksHealth.map(health => [health.time, health.height, health.match_rate]));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
res.status(500).send(e instanceof Error ? e.message : e);
|
res.status(500).send(e instanceof Error ? e.message : e);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import BlocksAuditsRepository from '../../repositories/BlocksAuditsRepository';
|
|||||||
import PricesRepository from '../../repositories/PricesRepository';
|
import PricesRepository from '../../repositories/PricesRepository';
|
||||||
import { bitcoinCoreApi } from '../bitcoin/bitcoin-api-factory';
|
import { bitcoinCoreApi } from '../bitcoin/bitcoin-api-factory';
|
||||||
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
|
import { IEsploraApi } from '../bitcoin/esplora-api.interface';
|
||||||
import database from '../../database';
|
|
||||||
|
|
||||||
class Mining {
|
class Mining {
|
||||||
private blocksPriceIndexingRunning = false;
|
private blocksPriceIndexingRunning = false;
|
||||||
@ -21,10 +20,10 @@ class Mining {
|
|||||||
public lastWeeklyHashrateIndexingDate: number | null = null;
|
public lastWeeklyHashrateIndexingDate: number | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get historical block predictions match rate
|
* Get historical blocks health
|
||||||
*/
|
*/
|
||||||
public async $getBlockPredictionsHistory(interval: string | null = null): Promise<any> {
|
public async $getBlocksHealthHistory(interval: string | null = null): Promise<any> {
|
||||||
return await BlocksAuditsRepository.$getBlockPredictionsHistory(
|
return await BlocksAuditsRepository.$getBlocksHealthHistory(
|
||||||
this.getTimeRange(interval),
|
this.getTimeRange(interval),
|
||||||
Common.getSqlInterval(interval)
|
Common.getSqlInterval(interval)
|
||||||
);
|
);
|
||||||
|
@ -41,7 +41,7 @@ class PoolsParser {
|
|||||||
public async migratePoolsJson(): Promise<void> {
|
public async migratePoolsJson(): Promise<void> {
|
||||||
// We also need to wipe the backend cache to make sure we don't serve blocks with
|
// We also need to wipe the backend cache to make sure we don't serve blocks with
|
||||||
// the wrong mining pool (usually happen with unknown blocks)
|
// the wrong mining pool (usually happen with unknown blocks)
|
||||||
diskCache.wipeCache();
|
diskCache.setIgnoreBlocksCache();
|
||||||
|
|
||||||
await this.$insertUnknownPool();
|
await this.$insertUnknownPool();
|
||||||
|
|
||||||
@ -118,10 +118,6 @@ class PoolsParser {
|
|||||||
* @param pool
|
* @param pool
|
||||||
*/
|
*/
|
||||||
private async $deleteBlocksForPool(pool: PoolTag): Promise<void> {
|
private async $deleteBlocksForPool(pool: PoolTag): Promise<void> {
|
||||||
if (config.MEMPOOL.AUTOMATIC_BLOCK_REINDEXING === false) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get oldest blocks mined by the pool and assume pools-v2.json updates only concern most recent years
|
// Get oldest blocks mined by the pool and assume pools-v2.json updates only concern most recent years
|
||||||
// Ignore early days of Bitcoin as there were no mining pool yet
|
// Ignore early days of Bitcoin as there were no mining pool yet
|
||||||
const [oldestPoolBlock]: any[] = await DB.query(`
|
const [oldestPoolBlock]: any[] = await DB.query(`
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import logger from "../logger";
|
import logger from "../logger";
|
||||||
import { TransactionExtended, TransactionStripped } from "../mempool.interfaces";
|
import { MempoolTransactionExtended, TransactionStripped } from "../mempool.interfaces";
|
||||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||||
import { Common } from "./common";
|
import { Common } from "./common";
|
||||||
|
|
||||||
@ -23,15 +23,15 @@ class RbfCache {
|
|||||||
private rbfTrees: Map<string, RbfTree> = new Map(); // sequences of consecutive replacements
|
private rbfTrees: Map<string, RbfTree> = new Map(); // sequences of consecutive replacements
|
||||||
private dirtyTrees: Set<string> = new Set();
|
private dirtyTrees: Set<string> = new Set();
|
||||||
private treeMap: Map<string, string> = new Map(); // map of txids to sequence ids
|
private treeMap: Map<string, string> = new Map(); // map of txids to sequence ids
|
||||||
private txs: Map<string, TransactionExtended> = new Map();
|
private txs: Map<string, MempoolTransactionExtended> = new Map();
|
||||||
private expiring: Map<string, number> = new Map();
|
private expiring: Map<string, number> = new Map();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
setInterval(this.cleanup.bind(this), 1000 * 60 * 10);
|
setInterval(this.cleanup.bind(this), 1000 * 60 * 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
public add(replaced: TransactionExtended[], newTxExtended: TransactionExtended): void {
|
public add(replaced: MempoolTransactionExtended[], newTxExtended: MempoolTransactionExtended): void {
|
||||||
if (!newTxExtended || !replaced?.length) {
|
if (!newTxExtended || !replaced?.length || this.txs.has(newTxExtended.txid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,7 +92,7 @@ class RbfCache {
|
|||||||
return this.replaces.get(txId);
|
return this.replaces.get(txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getTx(txId: string): TransactionExtended | undefined {
|
public getTx(txId: string): MempoolTransactionExtended | undefined {
|
||||||
return this.txs.get(txId);
|
return this.txs.get(txId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,7 +272,7 @@ class RbfCache {
|
|||||||
return deflated;
|
return deflated;
|
||||||
}
|
}
|
||||||
|
|
||||||
async importTree(root, txid, deflated, txs: Map<string, TransactionExtended>, mined: boolean = false): Promise<RbfTree | void> {
|
async importTree(root, txid, deflated, txs: Map<string, MempoolTransactionExtended>, mined: boolean = false): Promise<RbfTree | void> {
|
||||||
const treeInfo = deflated[txid];
|
const treeInfo = deflated[txid];
|
||||||
const replaces: RbfTree[] = [];
|
const replaces: RbfTree[] = [];
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { TransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
|
import { TransactionExtended, MempoolTransactionExtended, TransactionMinerInfo } from '../mempool.interfaces';
|
||||||
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
import { IEsploraApi } from './bitcoin/esplora-api.interface';
|
||||||
import { Common } from './common';
|
import { Common } from './common';
|
||||||
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
import bitcoinApi, { bitcoinCoreApi } from './bitcoin/bitcoin-api-factory';
|
||||||
|
import * as bitcoinjs from 'bitcoinjs-lib';
|
||||||
|
|
||||||
class TransactionUtils {
|
class TransactionUtils {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
@ -22,19 +23,27 @@ class TransactionUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param txId
|
* @param txId
|
||||||
* @param addPrevouts
|
* @param addPrevouts
|
||||||
* @param lazyPrevouts
|
* @param lazyPrevouts
|
||||||
* @param forceCore - See https://github.com/mempool/mempool/issues/2904
|
* @param forceCore - See https://github.com/mempool/mempool/issues/2904
|
||||||
*/
|
*/
|
||||||
public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false, forceCore = false): Promise<TransactionExtended> {
|
public async $getTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false, forceCore = false, addMempoolData = false): Promise<TransactionExtended> {
|
||||||
let transaction: IEsploraApi.Transaction;
|
let transaction: IEsploraApi.Transaction;
|
||||||
if (forceCore === true) {
|
if (forceCore === true) {
|
||||||
transaction = await bitcoinCoreApi.$getRawTransaction(txId, true);
|
transaction = await bitcoinCoreApi.$getRawTransaction(txId, true);
|
||||||
} else {
|
} else {
|
||||||
transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts, lazyPrevouts);
|
transaction = await bitcoinApi.$getRawTransaction(txId, false, addPrevouts, lazyPrevouts);
|
||||||
}
|
}
|
||||||
return this.extendTransaction(transaction);
|
if (addMempoolData || !transaction?.status?.confirmed) {
|
||||||
|
return this.extendMempoolTransaction(transaction);
|
||||||
|
} else {
|
||||||
|
return this.extendTransaction(transaction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async $getMempoolTransactionExtended(txId: string, addPrevouts = false, lazyPrevouts = false, forceCore = false): Promise<MempoolTransactionExtended> {
|
||||||
|
return (await this.$getTransactionExtended(txId, addPrevouts, lazyPrevouts, forceCore, true)) as MempoolTransactionExtended;
|
||||||
}
|
}
|
||||||
|
|
||||||
private extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
|
private extendTransaction(transaction: IEsploraApi.Transaction): TransactionExtended {
|
||||||
@ -50,8 +59,32 @@ class TransactionUtils {
|
|||||||
feePerVsize: feePerVbytes,
|
feePerVsize: feePerVbytes,
|
||||||
effectiveFeePerVsize: feePerVbytes,
|
effectiveFeePerVsize: feePerVbytes,
|
||||||
}, transaction);
|
}, transaction);
|
||||||
if (!transaction.status.confirmed) {
|
if (!transaction?.status?.confirmed && !transactionExtended.firstSeen) {
|
||||||
transactionExtended.firstSeen = Math.round((new Date().getTime() / 1000));
|
transactionExtended.firstSeen = Math.round((Date.now() / 1000));
|
||||||
|
}
|
||||||
|
return transactionExtended;
|
||||||
|
}
|
||||||
|
|
||||||
|
public extendMempoolTransaction(transaction: IEsploraApi.Transaction): MempoolTransactionExtended {
|
||||||
|
const vsize = Math.ceil(transaction.weight / 4);
|
||||||
|
const fractionalVsize = (transaction.weight / 4);
|
||||||
|
const sigops = this.countSigops(transaction);
|
||||||
|
// https://github.com/bitcoin/bitcoin/blob/e9262ea32a6e1d364fb7974844fadc36f931f8c6/src/policy/policy.cpp#L295-L298
|
||||||
|
const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor
|
||||||
|
const feePerVbytes = Math.max(Common.isLiquid() ? 0.1 : 1,
|
||||||
|
(transaction.fee || 0) / fractionalVsize);
|
||||||
|
const adjustedFeePerVsize = Math.max(Common.isLiquid() ? 0.1 : 1,
|
||||||
|
(transaction.fee || 0) / adjustedVsize);
|
||||||
|
const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, {
|
||||||
|
vsize: Math.round(transaction.weight / 4),
|
||||||
|
adjustedVsize,
|
||||||
|
sigops,
|
||||||
|
feePerVsize: feePerVbytes,
|
||||||
|
adjustedFeePerVsize: adjustedFeePerVsize,
|
||||||
|
effectiveFeePerVsize: adjustedFeePerVsize,
|
||||||
|
});
|
||||||
|
if (!transactionExtended?.status?.confirmed && !transactionExtended.firstSeen) {
|
||||||
|
transactionExtended.firstSeen = Math.round((Date.now() / 1000));
|
||||||
}
|
}
|
||||||
return transactionExtended;
|
return transactionExtended;
|
||||||
}
|
}
|
||||||
@ -63,6 +96,64 @@ class TransactionUtils {
|
|||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public countScriptSigops(script: string, isRawScript: boolean = false, witness: boolean = false): number {
|
||||||
|
let sigops = 0;
|
||||||
|
// count OP_CHECKSIG and OP_CHECKSIGVERIFY
|
||||||
|
sigops += (script.match(/OP_CHECKSIG/g)?.length || 0);
|
||||||
|
|
||||||
|
// count OP_CHECKMULTISIG and OP_CHECKMULTISIGVERIFY
|
||||||
|
if (isRawScript) {
|
||||||
|
// in scriptPubKey or scriptSig, always worth 20
|
||||||
|
sigops += 20 * (script.match(/OP_CHECKMULTISIG/g)?.length || 0);
|
||||||
|
} else {
|
||||||
|
// in redeem scripts and witnesses, worth N if preceded by OP_N, 20 otherwise
|
||||||
|
const matches = script.matchAll(/(?:OP_(\d+))? OP_CHECKMULTISIG/g);
|
||||||
|
for (const match of matches) {
|
||||||
|
const n = parseInt(match[1]);
|
||||||
|
if (Number.isInteger(n)) {
|
||||||
|
sigops += n;
|
||||||
|
} else {
|
||||||
|
sigops += 20;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return witness ? sigops : (sigops * 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
public countSigops(transaction: IEsploraApi.Transaction): number {
|
||||||
|
let sigops = 0;
|
||||||
|
|
||||||
|
for (const input of transaction.vin) {
|
||||||
|
if (input.scriptsig_asm) {
|
||||||
|
sigops += this.countScriptSigops(input.scriptsig_asm, true);
|
||||||
|
}
|
||||||
|
if (input.prevout) {
|
||||||
|
switch (true) {
|
||||||
|
case input.prevout.scriptpubkey_type === 'p2sh' && input.witness?.length === 2 && input.scriptsig && input.scriptsig.startsWith('160014'):
|
||||||
|
case input.prevout.scriptpubkey_type === 'v0_p2wpkh':
|
||||||
|
sigops += 1;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case input.prevout?.scriptpubkey_type === 'p2sh' && input.witness?.length && input.scriptsig && input.scriptsig.startsWith('220020'):
|
||||||
|
case input.prevout.scriptpubkey_type === 'v0_p2wsh':
|
||||||
|
if (input.witness?.length) {
|
||||||
|
sigops += this.countScriptSigops(bitcoinjs.script.toASM(Buffer.from(input.witness[input.witness.length - 1], 'hex')), false, true);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const output of transaction.vout) {
|
||||||
|
if (output.scriptpubkey_asm) {
|
||||||
|
sigops += this.countScriptSigops(output.scriptpubkey_asm, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sigops;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new TransactionUtils();
|
export default new TransactionUtils();
|
||||||
|
@ -48,12 +48,14 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
|||||||
weight: tx.weight,
|
weight: tx.weight,
|
||||||
feePerVsize: tx.feePerVsize,
|
feePerVsize: tx.feePerVsize,
|
||||||
effectiveFeePerVsize: tx.feePerVsize,
|
effectiveFeePerVsize: tx.feePerVsize,
|
||||||
|
sigops: tx.sigops,
|
||||||
inputs: tx.inputs || [],
|
inputs: tx.inputs || [],
|
||||||
relativesSet: false,
|
relativesSet: false,
|
||||||
ancestorMap: new Map<number, AuditTransaction>(),
|
ancestorMap: new Map<number, AuditTransaction>(),
|
||||||
children: new Set<AuditTransaction>(),
|
children: new Set<AuditTransaction>(),
|
||||||
ancestorFee: 0,
|
ancestorFee: 0,
|
||||||
ancestorWeight: 0,
|
ancestorWeight: 0,
|
||||||
|
ancestorSigops: 0,
|
||||||
score: 0,
|
score: 0,
|
||||||
used: false,
|
used: false,
|
||||||
modified: false,
|
modified: false,
|
||||||
@ -83,6 +85,7 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
|||||||
// (i.e. the package rooted in the transaction with the best ancestor score)
|
// (i.e. the package rooted in the transaction with the best ancestor score)
|
||||||
const blocks: number[][] = [];
|
const blocks: number[][] = [];
|
||||||
let blockWeight = 4000;
|
let blockWeight = 4000;
|
||||||
|
let blockSigops = 0;
|
||||||
let transactions: AuditTransaction[] = [];
|
let transactions: AuditTransaction[] = [];
|
||||||
const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => {
|
const modified: PairingHeap<AuditTransaction> = new PairingHeap((a, b): boolean => {
|
||||||
if (a.score === b.score) {
|
if (a.score === b.score) {
|
||||||
@ -118,7 +121,7 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
|||||||
|
|
||||||
if (nextTx && !nextTx?.used) {
|
if (nextTx && !nextTx?.used) {
|
||||||
// Check if the package fits into this block
|
// Check if the package fits into this block
|
||||||
if (blocks.length >= 7 || (blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS)) {
|
if (blocks.length >= 7 || ((blockWeight + nextTx.ancestorWeight < config.MEMPOOL.BLOCK_WEIGHT_UNITS) && (blockSigops + nextTx.ancestorSigops <= 80000))) {
|
||||||
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
const ancestors: AuditTransaction[] = Array.from(nextTx.ancestorMap.values());
|
||||||
// sort ancestors by dependency graph (equivalent to sorting by ascending ancestor count)
|
// 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 sortedTxSet = [...ancestors.sort((a, b) => { return (a.ancestorMap.size || 0) - (b.ancestorMap.size || 0); }), nextTx];
|
||||||
@ -127,7 +130,7 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
|||||||
cpfpClusters.set(nextTx.uid, sortedTxSet.map(tx => tx.uid));
|
cpfpClusters.set(nextTx.uid, sortedTxSet.map(tx => tx.uid));
|
||||||
isCluster = true;
|
isCluster = true;
|
||||||
}
|
}
|
||||||
const effectiveFeeRate = nextTx.ancestorFee / (nextTx.ancestorWeight / 4);
|
const effectiveFeeRate = Math.min(nextTx.dependencyRate || Infinity, nextTx.ancestorFee / (nextTx.ancestorWeight / 4));
|
||||||
const used: AuditTransaction[] = [];
|
const used: AuditTransaction[] = [];
|
||||||
while (sortedTxSet.length) {
|
while (sortedTxSet.length) {
|
||||||
const ancestor = sortedTxSet.pop();
|
const ancestor = sortedTxSet.pop();
|
||||||
@ -155,7 +158,7 @@ function makeBlockTemplates(mempool: Map<number, CompactThreadTransaction>)
|
|||||||
// remove these as valid package ancestors for any descendants remaining in the mempool
|
// remove these as valid package ancestors for any descendants remaining in the mempool
|
||||||
if (used.length) {
|
if (used.length) {
|
||||||
used.forEach(tx => {
|
used.forEach(tx => {
|
||||||
updateDescendants(tx, auditPool, modified);
|
updateDescendants(tx, auditPool, modified, effectiveFeeRate);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,9 +240,11 @@ function setRelatives(
|
|||||||
};
|
};
|
||||||
tx.ancestorFee = tx.fee || 0;
|
tx.ancestorFee = tx.fee || 0;
|
||||||
tx.ancestorWeight = tx.weight || 0;
|
tx.ancestorWeight = tx.weight || 0;
|
||||||
|
tx.ancestorSigops = tx.sigops || 0;
|
||||||
tx.ancestorMap.forEach((ancestor) => {
|
tx.ancestorMap.forEach((ancestor) => {
|
||||||
tx.ancestorFee += ancestor.fee;
|
tx.ancestorFee += ancestor.fee;
|
||||||
tx.ancestorWeight += ancestor.weight;
|
tx.ancestorWeight += ancestor.weight;
|
||||||
|
tx.ancestorSigops += ancestor.sigops;
|
||||||
});
|
});
|
||||||
tx.score = tx.ancestorFee / ((tx.ancestorWeight / 4) || 1);
|
tx.score = tx.ancestorFee / ((tx.ancestorWeight / 4) || 1);
|
||||||
tx.relativesSet = true;
|
tx.relativesSet = true;
|
||||||
@ -251,6 +256,7 @@ function updateDescendants(
|
|||||||
rootTx: AuditTransaction,
|
rootTx: AuditTransaction,
|
||||||
mempool: Map<number, AuditTransaction>,
|
mempool: Map<number, AuditTransaction>,
|
||||||
modified: PairingHeap<AuditTransaction>,
|
modified: PairingHeap<AuditTransaction>,
|
||||||
|
clusterRate: number,
|
||||||
): void {
|
): void {
|
||||||
const descendantSet: Set<AuditTransaction> = new Set();
|
const descendantSet: Set<AuditTransaction> = new Set();
|
||||||
// stack of nodes left to visit
|
// stack of nodes left to visit
|
||||||
@ -270,8 +276,10 @@ function updateDescendants(
|
|||||||
descendantTx.ancestorMap.delete(rootTx.uid);
|
descendantTx.ancestorMap.delete(rootTx.uid);
|
||||||
descendantTx.ancestorFee -= rootTx.fee;
|
descendantTx.ancestorFee -= rootTx.fee;
|
||||||
descendantTx.ancestorWeight -= rootTx.weight;
|
descendantTx.ancestorWeight -= rootTx.weight;
|
||||||
|
descendantTx.ancestorSigops -= rootTx.sigops;
|
||||||
tmpScore = descendantTx.score;
|
tmpScore = descendantTx.score;
|
||||||
descendantTx.score = descendantTx.ancestorFee / (descendantTx.ancestorWeight / 4);
|
descendantTx.score = descendantTx.ancestorFee / (descendantTx.ancestorWeight / 4);
|
||||||
|
descendantTx.dependencyRate = descendantTx.dependencyRate ? Math.min(descendantTx.dependencyRate, clusterRate) : clusterRate;
|
||||||
|
|
||||||
if (!descendantTx.modifiedNode) {
|
if (!descendantTx.modifiedNode) {
|
||||||
descendantTx.modified = true;
|
descendantTx.modified = true;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
import * as WebSocket from 'ws';
|
import * as WebSocket from 'ws';
|
||||||
import {
|
import {
|
||||||
BlockExtended, TransactionExtended, WebsocketResponse,
|
BlockExtended, TransactionExtended, MempoolTransactionExtended, WebsocketResponse,
|
||||||
OptimizedStatistic, ILoadingIndicators
|
OptimizedStatistic, ILoadingIndicators
|
||||||
} from '../mempool.interfaces';
|
} from '../mempool.interfaces';
|
||||||
import blocks from './blocks';
|
import blocks from './blocks';
|
||||||
@ -122,7 +122,7 @@ class WebsocketHandler {
|
|||||||
} else {
|
} else {
|
||||||
// tx.prevout is missing from transactions when in bitcoind mode
|
// tx.prevout is missing from transactions when in bitcoind mode
|
||||||
try {
|
try {
|
||||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
||||||
response['tx'] = fullTx;
|
response['tx'] = fullTx;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction: ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction: ' + (e instanceof Error ? e.message : e));
|
||||||
@ -130,7 +130,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const fullTx = await transactionUtils.$getTransactionExtended(client['track-tx'], true);
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(client['track-tx'], true);
|
||||||
response['tx'] = fullTx;
|
response['tx'] = fullTx;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction. ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction. ' + (e instanceof Error ? e.message : e));
|
||||||
@ -301,8 +301,8 @@ class WebsocketHandler {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async $handleMempoolChange(newMempool: { [txid: string]: TransactionExtended },
|
async $handleMempoolChange(newMempool: { [txid: string]: MempoolTransactionExtended },
|
||||||
newTransactions: TransactionExtended[], deletedTransactions: TransactionExtended[]): Promise<void> {
|
newTransactions: MempoolTransactionExtended[], deletedTransactions: MempoolTransactionExtended[]): Promise<void> {
|
||||||
if (!this.wss) {
|
if (!this.wss) {
|
||||||
throw new Error('WebSocket.Server is not set');
|
throw new Error('WebSocket.Server is not set');
|
||||||
}
|
}
|
||||||
@ -332,6 +332,8 @@ class WebsocketHandler {
|
|||||||
for (const deletedTx of deletedTransactions) {
|
for (const deletedTx of deletedTransactions) {
|
||||||
rbfCache.evict(deletedTx.txid);
|
rbfCache.evict(deletedTx.txid);
|
||||||
}
|
}
|
||||||
|
memPool.removeFromSpendMap(deletedTransactions);
|
||||||
|
memPool.addToSpendMap(newTransactions);
|
||||||
const recommendedFees = feeApi.getRecommendedFee();
|
const recommendedFees = feeApi.getRecommendedFee();
|
||||||
|
|
||||||
// update init data
|
// update init data
|
||||||
@ -397,7 +399,7 @@ class WebsocketHandler {
|
|||||||
if (tx) {
|
if (tx) {
|
||||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
try {
|
try {
|
||||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
||||||
response['tx'] = JSON.stringify(fullTx);
|
response['tx'] = JSON.stringify(fullTx);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||||
@ -417,7 +419,7 @@ class WebsocketHandler {
|
|||||||
if (someVin) {
|
if (someVin) {
|
||||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
try {
|
try {
|
||||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
||||||
foundTransactions.push(fullTx);
|
foundTransactions.push(fullTx);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||||
@ -431,7 +433,7 @@ class WebsocketHandler {
|
|||||||
if (someVout) {
|
if (someVout) {
|
||||||
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
if (config.MEMPOOL.BACKEND !== 'esplora') {
|
||||||
try {
|
try {
|
||||||
const fullTx = await transactionUtils.$getTransactionExtended(tx.txid, true);
|
const fullTx = await transactionUtils.$getMempoolTransactionExtended(tx.txid, true);
|
||||||
foundTransactions.push(fullTx);
|
foundTransactions.push(fullTx);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
logger.debug('Error finding transaction in mempool: ' + (e instanceof Error ? e.message : e));
|
||||||
@ -557,7 +559,7 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Common.indexingEnabled() && memPool.isInSync()) {
|
if (Common.indexingEnabled() && memPool.isInSync()) {
|
||||||
const { censored, added, fresh, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
const { censored, added, fresh, sigop, score, similarity } = Audit.auditBlock(transactions, projectedBlocks, auditMempool);
|
||||||
const matchRate = Math.round(score * 100 * 100) / 100;
|
const matchRate = Math.round(score * 100 * 100) / 100;
|
||||||
|
|
||||||
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
|
const stripped = projectedBlocks[0]?.transactions ? projectedBlocks[0].transactions.map((tx) => {
|
||||||
@ -584,6 +586,7 @@ class WebsocketHandler {
|
|||||||
addedTxs: added,
|
addedTxs: added,
|
||||||
missingTxs: censored,
|
missingTxs: censored,
|
||||||
freshTxs: fresh,
|
freshTxs: fresh,
|
||||||
|
sigopTxs: sigop,
|
||||||
matchRate: matchRate,
|
matchRate: matchRate,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -599,6 +602,10 @@ class WebsocketHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap());
|
||||||
|
memPool.handleMinedRbfTransactions(rbfTransactions);
|
||||||
|
memPool.removeFromSpendMap(transactions);
|
||||||
|
|
||||||
// Update mempool to remove transactions included in the new block
|
// Update mempool to remove transactions included in the new block
|
||||||
for (const txId of txIds) {
|
for (const txId of txIds) {
|
||||||
delete _memPool[txId];
|
delete _memPool[txId];
|
||||||
@ -650,8 +657,8 @@ class WebsocketHandler {
|
|||||||
|
|
||||||
if (client['track-tx']) {
|
if (client['track-tx']) {
|
||||||
const trackTxid = client['track-tx'];
|
const trackTxid = client['track-tx'];
|
||||||
if (txIds.indexOf(trackTxid) > -1) {
|
if (trackTxid && txIds.indexOf(trackTxid) > -1) {
|
||||||
response['txConfirmed'] = 'true';
|
response['txConfirmed'] = JSON.stringify(trackTxid);
|
||||||
} else {
|
} else {
|
||||||
const mempoolTx = _memPool[trackTxid];
|
const mempoolTx = _memPool[trackTxid];
|
||||||
if (mempoolTx && mempoolTx.position) {
|
if (mempoolTx && mempoolTx.position) {
|
||||||
|
@ -32,6 +32,7 @@ export interface BlockAudit {
|
|||||||
hash: string,
|
hash: string,
|
||||||
missingTxs: string[],
|
missingTxs: string[],
|
||||||
freshTxs: string[],
|
freshTxs: string[],
|
||||||
|
sigopTxs: string[],
|
||||||
addedTxs: string[],
|
addedTxs: string[],
|
||||||
matchRate: number,
|
matchRate: number,
|
||||||
}
|
}
|
||||||
@ -87,28 +88,38 @@ export interface TransactionExtended extends IEsploraApi.Transaction {
|
|||||||
uid?: number;
|
uid?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MempoolTransactionExtended extends TransactionExtended {
|
||||||
|
sigops: number;
|
||||||
|
adjustedVsize: number;
|
||||||
|
adjustedFeePerVsize: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AuditTransaction {
|
export interface AuditTransaction {
|
||||||
uid: number;
|
uid: number;
|
||||||
fee: number;
|
fee: number;
|
||||||
weight: number;
|
weight: number;
|
||||||
feePerVsize: number;
|
feePerVsize: number;
|
||||||
effectiveFeePerVsize: number;
|
effectiveFeePerVsize: number;
|
||||||
|
sigops: number;
|
||||||
inputs: number[];
|
inputs: number[];
|
||||||
relativesSet: boolean;
|
relativesSet: boolean;
|
||||||
ancestorMap: Map<number, AuditTransaction>;
|
ancestorMap: Map<number, AuditTransaction>;
|
||||||
children: Set<AuditTransaction>;
|
children: Set<AuditTransaction>;
|
||||||
ancestorFee: number;
|
ancestorFee: number;
|
||||||
ancestorWeight: number;
|
ancestorWeight: number;
|
||||||
|
ancestorSigops: number;
|
||||||
score: number;
|
score: number;
|
||||||
used: boolean;
|
used: boolean;
|
||||||
modified: boolean;
|
modified: boolean;
|
||||||
modifiedNode: HeapNode<AuditTransaction>;
|
modifiedNode: HeapNode<AuditTransaction>;
|
||||||
|
dependencyRate?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CompactThreadTransaction {
|
export interface CompactThreadTransaction {
|
||||||
uid: number;
|
uid: number;
|
||||||
fee: number;
|
fee: number;
|
||||||
weight: number;
|
weight: number;
|
||||||
|
sigops: number;
|
||||||
feePerVsize: number;
|
feePerVsize: number;
|
||||||
effectiveFeePerVsize?: number;
|
effectiveFeePerVsize?: number;
|
||||||
inputs: number[];
|
inputs: number[];
|
||||||
|
@ -6,20 +6,19 @@ import { BlockAudit, AuditScore } from '../mempool.interfaces';
|
|||||||
class BlocksAuditRepositories {
|
class BlocksAuditRepositories {
|
||||||
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
public async $saveAudit(audit: BlockAudit): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, match_rate)
|
await DB.query(`INSERT INTO blocks_audits(time, height, hash, missing_txs, added_txs, fresh_txs, sigop_txs, match_rate)
|
||||||
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?)`, [audit.time, audit.height, audit.hash, JSON.stringify(audit.missingTxs),
|
||||||
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), audit.matchRate]);
|
JSON.stringify(audit.addedTxs), JSON.stringify(audit.freshTxs), JSON.stringify(audit.sigopTxs), audit.matchRate]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||||
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
logger.debug(`Cannot save block audit for block ${audit.hash} because it has already been indexed, ignoring`);
|
||||||
} else {
|
} else {
|
||||||
logger.err(`Cannot save block audit into db. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot save block audit into db. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getBlockPredictionsHistory(div: number, interval: string | null): Promise<any> {
|
public async $getBlocksHealthHistory(div: number, interval: string | null): Promise<any> {
|
||||||
try {
|
try {
|
||||||
let query = `SELECT UNIX_TIMESTAMP(time) as time, height, match_rate FROM blocks_audits`;
|
let query = `SELECT UNIX_TIMESTAMP(time) as time, height, match_rate FROM blocks_audits`;
|
||||||
|
|
||||||
@ -32,17 +31,17 @@ class BlocksAuditRepositories {
|
|||||||
const [rows] = await DB.query(query);
|
const [rows] = await DB.query(query);
|
||||||
return rows;
|
return rows;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.err(`Cannot fetch block prediction history. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot fetch blocks health history. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async $getPredictionsCount(): Promise<number> {
|
public async $getBlocksHealthCount(): Promise<number> {
|
||||||
try {
|
try {
|
||||||
const [rows] = await DB.query(`SELECT count(hash) as count FROM blocks_audits`);
|
const [rows] = await DB.query(`SELECT count(hash) as count FROM blocks_audits`);
|
||||||
return rows[0].count;
|
return rows[0].count;
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
logger.err(`Cannot fetch block prediction history. Reason: ` + (e instanceof Error ? e.message : e));
|
logger.err(`Cannot fetch blocks health count. Reason: ` + (e instanceof Error ? e.message : e));
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -52,9 +51,10 @@ class BlocksAuditRepositories {
|
|||||||
const [rows]: any[] = await DB.query(
|
const [rows]: any[] = await DB.query(
|
||||||
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
|
`SELECT blocks.height, blocks.hash as id, UNIX_TIMESTAMP(blocks.blockTimestamp) as timestamp, blocks.size,
|
||||||
blocks.weight, blocks.tx_count,
|
blocks.weight, blocks.tx_count,
|
||||||
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, match_rate as matchRate
|
transactions, template, missing_txs as missingTxs, added_txs as addedTxs, fresh_txs as freshTxs, sigop_txs as sigopTxs, match_rate as matchRate
|
||||||
FROM blocks_audits
|
FROM blocks_audits
|
||||||
JOIN blocks ON blocks.hash = blocks_audits.hash
|
JOIN blocks ON blocks.hash = blocks_audits.hash
|
||||||
|
JOIN blocks_templates ON blocks_templates.id = blocks_audits.hash
|
||||||
JOIN blocks_summaries ON blocks_summaries.id = blocks_audits.hash
|
JOIN blocks_summaries ON blocks_summaries.id = blocks_audits.hash
|
||||||
WHERE blocks_audits.hash = "${hash}"
|
WHERE blocks_audits.hash = "${hash}"
|
||||||
`);
|
`);
|
||||||
@ -63,6 +63,7 @@ class BlocksAuditRepositories {
|
|||||||
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
rows[0].missingTxs = JSON.parse(rows[0].missingTxs);
|
||||||
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
rows[0].addedTxs = JSON.parse(rows[0].addedTxs);
|
||||||
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
rows[0].freshTxs = JSON.parse(rows[0].freshTxs);
|
||||||
|
rows[0].sigopTxs = JSON.parse(rows[0].sigopTxs);
|
||||||
rows[0].transactions = JSON.parse(rows[0].transactions);
|
rows[0].transactions = JSON.parse(rows[0].transactions);
|
||||||
rows[0].template = JSON.parse(rows[0].template);
|
rows[0].template = JSON.parse(rows[0].template);
|
||||||
|
|
||||||
|
@ -36,17 +36,16 @@ class BlocksSummariesRepository {
|
|||||||
try {
|
try {
|
||||||
const transactions = JSON.stringify(params.template?.transactions || []);
|
const transactions = JSON.stringify(params.template?.transactions || []);
|
||||||
await DB.query(`
|
await DB.query(`
|
||||||
INSERT INTO blocks_summaries (height, id, transactions, template)
|
INSERT INTO blocks_templates (id, template)
|
||||||
VALUE (?, ?, ?, ?)
|
VALUE (?, ?)
|
||||||
ON DUPLICATE KEY UPDATE
|
ON DUPLICATE KEY UPDATE
|
||||||
template = ?
|
template = ?
|
||||||
`, [params.height, blockId, '[]', transactions, transactions]);
|
`, [blockId, transactions, transactions]);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
if (e.errno === 1062) { // ER_DUP_ENTRY - This scenario is possible upon node backend restart
|
||||||
logger.debug(`Cannot save block template for ${blockId} because it has already been indexed, ignoring`);
|
logger.debug(`Cannot save block template for ${blockId} because it has already been indexed, ignoring`);
|
||||||
} else {
|
} else {
|
||||||
logger.debug(`Cannot save block template for ${blockId}. Reason: ${e instanceof Error ? e.message : e}`);
|
logger.warn(`Cannot save block template for ${blockId}. Reason: ${e instanceof Error ? e.message : e}`);
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,9 @@
|
|||||||
"ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
|
"ADVANCED_GBT_MEMPOOL": __MEMPOOL_ADVANCED_GBT_MEMPOOL__,
|
||||||
"CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__,
|
"CPFP_INDEXING": __MEMPOOL_CPFP_INDEXING__,
|
||||||
"MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__,
|
"MAX_BLOCKS_BULK_QUERY": __MEMPOOL_MAX_BLOCKS_BULK_QUERY__,
|
||||||
"DISK_CACHE_BLOCK_INTERVAL": __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__
|
"DISK_CACHE_BLOCK_INTERVAL": __MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__,
|
||||||
|
"POOLS_JSON_TREE_URL": "__MEMPOOL_POOLS_JSON_TREE_URL__",
|
||||||
|
"POOLS_JSON_URL": "__MEMPOOL_POOLS_JSON_URL__"
|
||||||
},
|
},
|
||||||
"CORE_RPC": {
|
"CORE_RPC": {
|
||||||
"HOST": "__CORE_RPC_HOST__",
|
"HOST": "__CORE_RPC_HOST__",
|
||||||
@ -61,7 +63,7 @@
|
|||||||
"DATABASE": "__DATABASE_DATABASE__",
|
"DATABASE": "__DATABASE_DATABASE__",
|
||||||
"USERNAME": "__DATABASE_USERNAME__",
|
"USERNAME": "__DATABASE_USERNAME__",
|
||||||
"PASSWORD": "__DATABASE_PASSWORD__",
|
"PASSWORD": "__DATABASE_PASSWORD__",
|
||||||
"TIMEOUT": "__DATABASE_TIMEOUT__"
|
"TIMEOUT": __DATABASE_TIMEOUT__
|
||||||
},
|
},
|
||||||
"SYSLOG": {
|
"SYSLOG": {
|
||||||
"ENABLED": __SYSLOG_ENABLED__,
|
"ENABLED": __SYSLOG_ENABLED__,
|
||||||
@ -84,13 +86,15 @@
|
|||||||
"STATS_REFRESH_INTERVAL": __LIGHTNING_STATS_REFRESH_INTERVAL__,
|
"STATS_REFRESH_INTERVAL": __LIGHTNING_STATS_REFRESH_INTERVAL__,
|
||||||
"GRAPH_REFRESH_INTERVAL": __LIGHTNING_GRAPH_REFRESH_INTERVAL__,
|
"GRAPH_REFRESH_INTERVAL": __LIGHTNING_GRAPH_REFRESH_INTERVAL__,
|
||||||
"LOGGER_UPDATE_INTERVAL": __LIGHTNING_LOGGER_UPDATE_INTERVAL__,
|
"LOGGER_UPDATE_INTERVAL": __LIGHTNING_LOGGER_UPDATE_INTERVAL__,
|
||||||
"TOPOLOGY_FOLDER": "__LIGHTNING_TOPOLOGY_FOLDER__"
|
"TOPOLOGY_FOLDER": "__LIGHTNING_TOPOLOGY_FOLDER__",
|
||||||
|
"FORENSICS_INTERVAL": __LIGHTNING_FORENSICS_INTERVAL__,
|
||||||
|
"FORENSICS_RATE_LIMIT": __LIGHTNING_FORENSICS_RATE_LIMIT__
|
||||||
},
|
},
|
||||||
"LND": {
|
"LND": {
|
||||||
"TLS_CERT_PATH": "__LND_TLS_CERT_PATH__",
|
"TLS_CERT_PATH": "__LND_TLS_CERT_PATH__",
|
||||||
"MACAROON_PATH": "__LND_MACAROON_PATH__",
|
"MACAROON_PATH": "__LND_MACAROON_PATH__",
|
||||||
"REST_API_URL": "__LND_REST_API_URL__",
|
"REST_API_URL": "__LND_REST_API_URL__",
|
||||||
"TIMEOUT": "__LND_TIMEOUT__"
|
"TIMEOUT": __LND_TIMEOUT__
|
||||||
},
|
},
|
||||||
"CLIGHTNING": {
|
"CLIGHTNING": {
|
||||||
"SOCKET": "__CLIGHTNING_SOCKET__"
|
"SOCKET": "__CLIGHTNING_SOCKET__"
|
||||||
@ -121,4 +125,4 @@
|
|||||||
"GEOLITE2_ASN": "__MAXMIND_GEOLITE2_ASN__",
|
"GEOLITE2_ASN": "__MAXMIND_GEOLITE2_ASN__",
|
||||||
"GEOIP2_ISP": "__MAXMIND_GEOIP2_ISP__"
|
"GEOIP2_ISP": "__MAXMIND_GEOIP2_ISP__"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -46,7 +46,7 @@ __ELECTRUM_TLS_ENABLED__=${ELECTRUM_TLS_ENABLED:=false}
|
|||||||
|
|
||||||
# ESPLORA
|
# ESPLORA
|
||||||
__ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000}
|
__ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000}
|
||||||
__ESPLORA_UNIX_SOCKET_PATH__=${ESPLORA_UNIX_SOCKET_PATH:=null}
|
__ESPLORA_UNIX_SOCKET_PATH__=${ESPLORA_UNIX_SOCKET_PATH:="null"}
|
||||||
__ESPLORA_RETRY_UNIX_SOCKET_AFTER__=${ESPLORA_RETRY_UNIX_SOCKET_AFTER:=30000}
|
__ESPLORA_RETRY_UNIX_SOCKET_AFTER__=${ESPLORA_RETRY_UNIX_SOCKET_AFTER:=30000}
|
||||||
|
|
||||||
# SECOND_CORE_RPC
|
# SECOND_CORE_RPC
|
||||||
@ -108,6 +108,8 @@ __LIGHTNING_TOPOLOGY_FOLDER__=${LIGHTNING_TOPOLOGY_FOLDER:=""}
|
|||||||
__LIGHTNING_STATS_REFRESH_INTERVAL__=${LIGHTNING_STATS_REFRESH_INTERVAL:=600}
|
__LIGHTNING_STATS_REFRESH_INTERVAL__=${LIGHTNING_STATS_REFRESH_INTERVAL:=600}
|
||||||
__LIGHTNING_GRAPH_REFRESH_INTERVAL__=${LIGHTNING_GRAPH_REFRESH_INTERVAL:=600}
|
__LIGHTNING_GRAPH_REFRESH_INTERVAL__=${LIGHTNING_GRAPH_REFRESH_INTERVAL:=600}
|
||||||
__LIGHTNING_LOGGER_UPDATE_INTERVAL__=${LIGHTNING_LOGGER_UPDATE_INTERVAL:=30}
|
__LIGHTNING_LOGGER_UPDATE_INTERVAL__=${LIGHTNING_LOGGER_UPDATE_INTERVAL:=30}
|
||||||
|
__LIGHTNING_FORENSICS_INTERVAL__=${LIGHTNING_FORENSICS_INTERVAL:=43200}
|
||||||
|
__LIGHTNING_FORENSICS_RATE_LIMIT__=${LIGHTNING_FORENSICS_RATE_LIMIT:=20}
|
||||||
|
|
||||||
# LND
|
# LND
|
||||||
__LND_TLS_CERT_PATH__=${LND_TLS_CERT_PATH:=""}
|
__LND_TLS_CERT_PATH__=${LND_TLS_CERT_PATH:=""}
|
||||||
@ -127,28 +129,28 @@ __MAXMIND_GEOIP2_ISP__=${MAXMIND_GEOIP2_ISP:=""}
|
|||||||
|
|
||||||
mkdir -p "${__MEMPOOL_CACHE_DIR__}"
|
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_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
|
||||||
sed -i "s/__MEMPOOL_POLL_RATE_MS__/${__MEMPOOL_POLL_RATE_MS__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_POLL_RATE_MS__!${__MEMPOOL_POLL_RATE_MS__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_CACHE_DIR__!${__MEMPOOL_CACHE_DIR__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_CACHE_DIR__!${__MEMPOOL_CACHE_DIR__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_CLEAR_PROTECTION_MINUTES__/${__MEMPOOL_CLEAR_PROTECTION_MINUTES__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_CLEAR_PROTECTION_MINUTES__!${__MEMPOOL_CLEAR_PROTECTION_MINUTES__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__/${__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__!${__MEMPOOL_RECOMMENDED_FEE_PERCENTILE__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_BLOCK_WEIGHT_UNITS__/${__MEMPOOL_BLOCK_WEIGHT_UNITS__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_BLOCK_WEIGHT_UNITS__!${__MEMPOOL_BLOCK_WEIGHT_UNITS__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_INITIAL_BLOCKS_AMOUNT__/${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_INITIAL_BLOCKS_AMOUNT__!${__MEMPOOL_INITIAL_BLOCKS_AMOUNT__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__/${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__!${__MEMPOOL_MEMPOOL_BLOCKS_AMOUNT__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_INDEXING_BLOCKS_AMOUNT__/${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_INDEXING_BLOCKS_AMOUNT__!${__MEMPOOL_INDEXING_BLOCKS_AMOUNT__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__/${__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__!${__MEMPOOL_BLOCKS_SUMMARIES_INDEXING__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__/${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__!${__MEMPOOL_USE_SECOND_NODE_FOR_MINFEE__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_EXTERNAL_ASSETS__!${__MEMPOOL_EXTERNAL_ASSETS__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_EXTERNAL_MAX_RETRY__!${__MEMPOOL_EXTERNAL_MAX_RETRY__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_EXTERNAL_RETRY_INTERVAL__!${__MEMPOOL_EXTERNAL_RETRY_INTERVAL__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_EXTERNAL_RETRY_INTERVAL__!${__MEMPOOL_EXTERNAL_RETRY_INTERVAL__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_USER_AGENT__!${__MEMPOOL_USER_AGENT__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_USER_AGENT__!${__MEMPOOL_USER_AGENT__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__/${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__!${__MEMPOOL_STDOUT_LOG_MIN_PRIORITY__}!g" mempool-config.json
|
||||||
sed -i "s/__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__/${__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__}/g" mempool-config.json
|
sed -i "s!__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__!${__MEMPOOL_AUTOMATIC_BLOCK_REINDEXING__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_POOLS_JSON_URL__!${__MEMPOOL_POOLS_JSON_URL__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_POOLS_JSON_URL__!${__MEMPOOL_POOLS_JSON_URL__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_POOLS_JSON_TREE_URL__!${__MEMPOOL_POOLS_JSON_TREE_URL__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_AUDIT__!${__MEMPOOL_AUDIT__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_AUDIT__!${__MEMPOOL_AUDIT__}!g" mempool-config.json
|
||||||
@ -158,53 +160,53 @@ sed -i "s!__MEMPOOL_CPFP_INDEXING__!${__MEMPOOL_CPFP_INDEXING__}!g" mempool-conf
|
|||||||
sed -i "s!__MEMPOOL_MAX_BLOCKS_BULK_QUERY__!${__MEMPOOL_MAX_BLOCKS_BULK_QUERY__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_MAX_BLOCKS_BULK_QUERY__!${__MEMPOOL_MAX_BLOCKS_BULK_QUERY__}!g" mempool-config.json
|
||||||
sed -i "s!__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__!${__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__}!g" mempool-config.json
|
sed -i "s!__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__!${__MEMPOOL_DISK_CACHE_BLOCK_INTERVAL__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__CORE_RPC_HOST__/${__CORE_RPC_HOST__}/g" mempool-config.json
|
sed -i "s!__CORE_RPC_HOST__!${__CORE_RPC_HOST__}!g" mempool-config.json
|
||||||
sed -i "s/__CORE_RPC_PORT__/${__CORE_RPC_PORT__}/g" mempool-config.json
|
sed -i "s!__CORE_RPC_PORT__!${__CORE_RPC_PORT__}!g" mempool-config.json
|
||||||
sed -i "s/__CORE_RPC_USERNAME__/${__CORE_RPC_USERNAME__}/g" mempool-config.json
|
sed -i "s!__CORE_RPC_USERNAME__!${__CORE_RPC_USERNAME__}!g" mempool-config.json
|
||||||
sed -i "s/__CORE_RPC_PASSWORD__/${__CORE_RPC_PASSWORD__}/g" mempool-config.json
|
sed -i "s!__CORE_RPC_PASSWORD__!${__CORE_RPC_PASSWORD__}!g" mempool-config.json
|
||||||
sed -i "s/__CORE_RPC_TIMEOUT__/${__CORE_RPC_TIMEOUT__}/g" mempool-config.json
|
sed -i "s!__CORE_RPC_TIMEOUT__!${__CORE_RPC_TIMEOUT__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__ELECTRUM_HOST__/${__ELECTRUM_HOST__}/g" mempool-config.json
|
sed -i "s!__ELECTRUM_HOST__!${__ELECTRUM_HOST__}!g" mempool-config.json
|
||||||
sed -i "s/__ELECTRUM_PORT__/${__ELECTRUM_PORT__}/g" mempool-config.json
|
sed -i "s!__ELECTRUM_PORT__!${__ELECTRUM_PORT__}!g" mempool-config.json
|
||||||
sed -i "s/__ELECTRUM_TLS_ENABLED__/${__ELECTRUM_TLS_ENABLED__}/g" mempool-config.json
|
sed -i "s!__ELECTRUM_TLS_ENABLED__!${__ELECTRUM_TLS_ENABLED__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s!__ESPLORA_REST_API_URL__!${__ESPLORA_REST_API_URL__}!g" mempool-config.json
|
sed -i "s!__ESPLORA_REST_API_URL__!${__ESPLORA_REST_API_URL__}!g" mempool-config.json
|
||||||
sed -i "s!__ESPLORA_UNIX_SOCKET_PATH__!${__ESPLORA_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
sed -i "s!__ESPLORA_UNIX_SOCKET_PATH__!${__ESPLORA_UNIX_SOCKET_PATH__}!g" mempool-config.json
|
||||||
sed -i "s!__ESPLORA_RETRY_UNIX_SOCKET_AFTER__!${__ESPLORA_RETRY_UNIX_SOCKET_AFTER__}!g" mempool-config.json
|
sed -i "s!__ESPLORA_RETRY_UNIX_SOCKET_AFTER__!${__ESPLORA_RETRY_UNIX_SOCKET_AFTER__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__SECOND_CORE_RPC_HOST__/${__SECOND_CORE_RPC_HOST__}/g" mempool-config.json
|
sed -i "s!__SECOND_CORE_RPC_HOST__!${__SECOND_CORE_RPC_HOST__}!g" mempool-config.json
|
||||||
sed -i "s/__SECOND_CORE_RPC_PORT__/${__SECOND_CORE_RPC_PORT__}/g" mempool-config.json
|
sed -i "s!__SECOND_CORE_RPC_PORT__!${__SECOND_CORE_RPC_PORT__}!g" mempool-config.json
|
||||||
sed -i "s/__SECOND_CORE_RPC_USERNAME__/${__SECOND_CORE_RPC_USERNAME__}/g" mempool-config.json
|
sed -i "s!__SECOND_CORE_RPC_USERNAME__!${__SECOND_CORE_RPC_USERNAME__}!g" mempool-config.json
|
||||||
sed -i "s/__SECOND_CORE_RPC_PASSWORD__/${__SECOND_CORE_RPC_PASSWORD__}/g" mempool-config.json
|
sed -i "s!__SECOND_CORE_RPC_PASSWORD__!${__SECOND_CORE_RPC_PASSWORD__}!g" mempool-config.json
|
||||||
sed -i "s/__SECOND_CORE_RPC_TIMEOUT__/${__SECOND_CORE_RPC_TIMEOUT__}/g" mempool-config.json
|
sed -i "s!__SECOND_CORE_RPC_TIMEOUT__!${__SECOND_CORE_RPC_TIMEOUT__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__DATABASE_ENABLED__/${__DATABASE_ENABLED__}/g" mempool-config.json
|
sed -i "s!__DATABASE_ENABLED__!${__DATABASE_ENABLED__}!g" mempool-config.json
|
||||||
sed -i "s/__DATABASE_HOST__/${__DATABASE_HOST__}/g" mempool-config.json
|
sed -i "s!__DATABASE_HOST__!${__DATABASE_HOST__}!g" mempool-config.json
|
||||||
sed -i "s!__DATABASE_SOCKET__!${__DATABASE_SOCKET__}!g" mempool-config.json
|
sed -i "s!__DATABASE_SOCKET__!${__DATABASE_SOCKET__}!g" mempool-config.json
|
||||||
|
sed -i "s!__DATABASE_PORT__!${__DATABASE_PORT__}!g" mempool-config.json
|
||||||
|
sed -i "s!__DATABASE_DATABASE__!${__DATABASE_DATABASE__}!g" mempool-config.json
|
||||||
|
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_TIMEOUT__!${__DATABASE_TIMEOUT__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__DATABASE_PORT__/${__DATABASE_PORT__}/g" mempool-config.json
|
sed -i "s!__SYSLOG_ENABLED__!${__SYSLOG_ENABLED__}!g" mempool-config.json
|
||||||
sed -i "s/__DATABASE_DATABASE__/${__DATABASE_DATABASE__}/g" mempool-config.json
|
sed -i "s!__SYSLOG_HOST__!${__SYSLOG_HOST__}!g" mempool-config.json
|
||||||
sed -i "s/__DATABASE_USERNAME__/${__DATABASE_USERNAME__}/g" mempool-config.json
|
sed -i "s!__SYSLOG_PORT__!${__SYSLOG_PORT__}!g" mempool-config.json
|
||||||
sed -i "s/__DATABASE_PASSWORD__/${__DATABASE_PASSWORD__}/g" mempool-config.json
|
sed -i "s!__SYSLOG_MIN_PRIORITY__!${__SYSLOG_MIN_PRIORITY__}!g" mempool-config.json
|
||||||
|
sed -i "s!__SYSLOG_FACILITY__!${__SYSLOG_FACILITY__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__SYSLOG_ENABLED__/${__SYSLOG_ENABLED__}/g" mempool-config.json
|
sed -i "s!__STATISTICS_ENABLED__!${__STATISTICS_ENABLED__}!g" mempool-config.json
|
||||||
sed -i "s/__SYSLOG_HOST__/${__SYSLOG_HOST__}/g" mempool-config.json
|
sed -i "s!__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__!${__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__}!g" mempool-config.json
|
||||||
sed -i "s/__SYSLOG_PORT__/${__SYSLOG_PORT__}/g" mempool-config.json
|
|
||||||
sed -i "s/__SYSLOG_MIN_PRIORITY__/${__SYSLOG_MIN_PRIORITY__}/g" mempool-config.json
|
|
||||||
sed -i "s/__SYSLOG_FACILITY__/${__SYSLOG_FACILITY__}/g" mempool-config.json
|
|
||||||
|
|
||||||
sed -i "s/__STATISTICS_ENABLED__/${__STATISTICS_ENABLED__}/g" mempool-config.json
|
sed -i "s!__BISQ_ENABLED__!${__BISQ_ENABLED__}!g" mempool-config.json
|
||||||
sed -i "s/__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__/${__STATISTICS_TX_PER_SECOND_SAMPLE_PERIOD__}/g" mempool-config.json
|
|
||||||
|
|
||||||
sed -i "s/__BISQ_ENABLED__/${__BISQ_ENABLED__}/g" mempool-config.json
|
|
||||||
sed -i "s!__BISQ_DATA_PATH__!${__BISQ_DATA_PATH__}!g" mempool-config.json
|
sed -i "s!__BISQ_DATA_PATH__!${__BISQ_DATA_PATH__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s/__SOCKS5PROXY_ENABLED__/${__SOCKS5PROXY_ENABLED__}/g" mempool-config.json
|
sed -i "s!__SOCKS5PROXY_ENABLED__!${__SOCKS5PROXY_ENABLED__}!g" mempool-config.json
|
||||||
sed -i "s/__SOCKS5PROXY_USE_ONION__/${__SOCKS5PROXY_USE_ONION__}/g" mempool-config.json
|
sed -i "s!__SOCKS5PROXY_USE_ONION__!${__SOCKS5PROXY_USE_ONION__}!g" mempool-config.json
|
||||||
sed -i "s/__SOCKS5PROXY_HOST__/${__SOCKS5PROXY_HOST__}/g" mempool-config.json
|
sed -i "s!__SOCKS5PROXY_HOST__!${__SOCKS5PROXY_HOST__}!g" mempool-config.json
|
||||||
sed -i "s/__SOCKS5PROXY_PORT__/${__SOCKS5PROXY_PORT__}/g" mempool-config.json
|
sed -i "s!__SOCKS5PROXY_PORT__!${__SOCKS5PROXY_PORT__}!g" mempool-config.json
|
||||||
sed -i "s/__SOCKS5PROXY_USERNAME__/${__SOCKS5PROXY_USERNAME__}/g" mempool-config.json
|
sed -i "s!__SOCKS5PROXY_USERNAME__!${__SOCKS5PROXY_USERNAME__}!g" mempool-config.json
|
||||||
sed -i "s/__SOCKS5PROXY_PASSWORD__/${__SOCKS5PROXY_PASSWORD__}/g" mempool-config.json
|
sed -i "s!__SOCKS5PROXY_PASSWORD__!${__SOCKS5PROXY_PASSWORD__}!g" mempool-config.json
|
||||||
|
|
||||||
sed -i "s!__PRICE_DATA_SERVER_TOR_URL__!${__PRICE_DATA_SERVER_TOR_URL__}!g" mempool-config.json
|
sed -i "s!__PRICE_DATA_SERVER_TOR_URL__!${__PRICE_DATA_SERVER_TOR_URL__}!g" mempool-config.json
|
||||||
sed -i "s!__PRICE_DATA_SERVER_CLEARNET_URL__!${__PRICE_DATA_SERVER_CLEARNET_URL__}!g" mempool-config.json
|
sed -i "s!__PRICE_DATA_SERVER_CLEARNET_URL__!${__PRICE_DATA_SERVER_CLEARNET_URL__}!g" mempool-config.json
|
||||||
@ -223,6 +225,8 @@ sed -i "s!__LIGHTNING_TOPOLOGY_FOLDER__!${__LIGHTNING_TOPOLOGY_FOLDER__}!g" memp
|
|||||||
sed -i "s!__LIGHTNING_STATS_REFRESH_INTERVAL__!${__LIGHTNING_STATS_REFRESH_INTERVAL__}!g" mempool-config.json
|
sed -i "s!__LIGHTNING_STATS_REFRESH_INTERVAL__!${__LIGHTNING_STATS_REFRESH_INTERVAL__}!g" mempool-config.json
|
||||||
sed -i "s!__LIGHTNING_GRAPH_REFRESH_INTERVAL__!${__LIGHTNING_GRAPH_REFRESH_INTERVAL__}!g" mempool-config.json
|
sed -i "s!__LIGHTNING_GRAPH_REFRESH_INTERVAL__!${__LIGHTNING_GRAPH_REFRESH_INTERVAL__}!g" mempool-config.json
|
||||||
sed -i "s!__LIGHTNING_LOGGER_UPDATE_INTERVAL__!${__LIGHTNING_LOGGER_UPDATE_INTERVAL__}!g" mempool-config.json
|
sed -i "s!__LIGHTNING_LOGGER_UPDATE_INTERVAL__!${__LIGHTNING_LOGGER_UPDATE_INTERVAL__}!g" mempool-config.json
|
||||||
|
sed -i "s!__LIGHTNING_FORENSICS_INTERVAL__!${__LIGHTNING_FORENSICS_INTERVAL__}!g" mempool-config.json
|
||||||
|
sed -i "s!__LIGHTNING_FORENSICS_RATE_LIMIT__!${__LIGHTNING_FORENSICS_RATE_LIMIT__}!g" mempool-config.json
|
||||||
|
|
||||||
# LND
|
# LND
|
||||||
sed -i "s!__LND_TLS_CERT_PATH__!${__LND_TLS_CERT_PATH__}!g" mempool-config.json
|
sed -i "s!__LND_TLS_CERT_PATH__!${__LND_TLS_CERT_PATH__}!g" mempool-config.json
|
||||||
|
@ -15,11 +15,9 @@
|
|||||||
</span>
|
</span>
|
||||||
<span class="grow"></span>
|
<span class="grow"></span>
|
||||||
<div class="container-buttons">
|
<div class="container-buttons">
|
||||||
<button *ngIf="(latestBlock$ | async) as latestBlock" type="button" class="btn btn-sm btn-success float-right">
|
<div *ngIf="(latestBlock$ | async) as latestBlock">
|
||||||
<ng-container *ngTemplateOutlet="latestBlock.height - bisqTx.blockHeight + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - bisqTx.blockHeight + 1}"></ng-container>
|
<app-confirmations [chainTip]="latestBlock?.height" [height]="bisqTx.blockHeight" [hideUnconfirmed]="true" buttonClass="float-right"></app-confirmations>
|
||||||
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
</div>
|
||||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -70,11 +70,7 @@
|
|||||||
|
|
||||||
<div class="btn-container">
|
<div class="btn-container">
|
||||||
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
<span *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
||||||
<button type="button" class="btn btn-sm btn-success mt-2">
|
<app-confirmations [chainTip]="latestBlock?.height" [height]="tx.blockHeight" [hideUnconfirmed]="true" buttonClass="mt-2"></app-confirmations>
|
||||||
<ng-container *ngTemplateOutlet="latestBlock.height - tx.blockHeight + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.blockHeight + 1}"></ng-container>
|
|
||||||
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
|
||||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</span>
|
</span>
|
||||||
<button type="button" class="btn btn-sm btn-primary mt-2" (click)="switchCurrency()">
|
<button type="button" class="btn btn-sm btn-primary mt-2" (click)="switchCurrency()">
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div class="container-xl about-page">
|
<div class="container-xl about-page">
|
||||||
|
|
||||||
<div class="intro">
|
<div class="intro">
|
||||||
<span style="margin-left: auto; margin-right: -20px; margin-bottom: -20px">™</span>
|
<span style="margin-left: auto; margin-right: -20px; margin-bottom: -20px">®</span>
|
||||||
<img class="logo" src="/resources/mempool-logo-bigger.png" />
|
<img class="logo" src="/resources/mempool-logo-bigger.png" />
|
||||||
<div class="version">
|
<div class="version">
|
||||||
v{{ packetJsonVersion }} [<a href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]
|
v{{ packetJsonVersion }} [<a href="https://github.com/mempool/mempool/commit/{{ frontendGitCommitHash }}">{{ frontendGitCommitHash }}</a>]
|
||||||
@ -13,7 +13,7 @@
|
|||||||
<p i18n>Our mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, completely self-hosted without any trusted third-parties.</p>
|
<p i18n>Our mempool and blockchain explorer for the Bitcoin community, focusing on the transaction fee market and multi-layer ecosystem, completely self-hosted without any trusted third-parties.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<video src="/resources/promo-video/mempool-promo.mp4" poster="/resources/promo-video/mempool-promo.jpg" controls loop playsinline [autoplay]="true" [muted]="true">
|
<video #promoVideo (click)="unmutePromoVideo()" (touchstart)="unmutePromoVideo()" src="/resources/promo-video/mempool-promo.mp4" poster="/resources/promo-video/mempool-promo.jpg" controls loop playsinline [autoplay]="true" [muted]="true">
|
||||||
<track label="English" kind="captions" srclang="en" src="/resources/promo-video/en.vtt" [attr.default]="showSubtitles('en') ? '' : null">
|
<track label="English" kind="captions" srclang="en" src="/resources/promo-video/en.vtt" [attr.default]="showSubtitles('en') ? '' : null">
|
||||||
<track label="日本語" kind="captions" srclang="ja" src="/resources/promo-video/ja.vtt" [attr.default]="showSubtitles('ja') ? '' : null">
|
<track label="日本語" kind="captions" srclang="ja" src="/resources/promo-video/ja.vtt" [attr.default]="showSubtitles('ja') ? '' : null">
|
||||||
<track label="中文" kind="captions" srclang="zh" src="/resources/promo-video/zh.vtt" [attr.default]="showSubtitles('zh') ? '' : null">
|
<track label="中文" kind="captions" srclang="zh" src="/resources/promo-video/zh.vtt" [attr.default]="showSubtitles('zh') ? '' : null">
|
||||||
@ -119,6 +119,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span>Gemini</span>
|
<span>Gemini</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://bullbitcoin.com/" target="_blank" title="Bull Bitcoin">
|
||||||
|
<svg aria-hidden="true" class="image" viewBox="0 -5 40 40" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g clip-path="url(#a)" fill-rule="evenodd" clip-rule="evenodd" fill="#e21924">
|
||||||
|
<path d="M21.92 14.59a1.18 1.18 0 0 0-1.18-1.18h-1.82v2.36h1.82a1.18 1.18 0 0 0 1.18-1.18ZM21 17.07h-2v2.45h2a1.23 1.23 0 1 0 0-2.45Z"/>
|
||||||
|
<path d="M36.43 0 35 5.59l-8 2.64-2.43-3.61-4.74 2.05-4.74-2.05-2.43 3.61-8-2.64L3.21 0 0 7.86l7.89 5.86-5.56 4 5.56 1.12 2.69-.49v3.17l3.59 4.38.68 3.19 5 2.87 5-2.87.68-3.19 3.59-4.38v-3.17l2.7.49 5.56-1.12-5.56-4 7.89-5.86zM24.69 18.45a2.5 2.5 0 0 1-2.5 2.5h-1.11v1.56h-1.26V21h-.9v1.56h-1.27V21H15.3v-1.42h.64a.9.9 0 0 0 .9-.9V14.3a.901.901 0 0 0-.9-.91h-.64V12h2.35v-1.5h1.27V12h.9v-1.5h1.26V12h.68A2.269 2.269 0 0 1 24 14.31a2.25 2.25 0 0 1-.92 1.82 2.52 2.52 0 0 1 1.58 2.32z"/>
|
||||||
|
</g>
|
||||||
|
<defs>
|
||||||
|
<clipPath id="a"><path fill="#fff" d="M0 0h160v32H0z"/></clipPath>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
<span>Bull Bitcoin</span>
|
||||||
|
</a>
|
||||||
<a href="https://exodus.com/" target="_blank" title="Exodus">
|
<a href="https://exodus.com/" target="_blank" title="Exodus">
|
||||||
<svg width="80" height="80" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg width="80" height="80" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<circle cx="250" cy="250" r="250" fill="#1F2033"/>
|
<circle cx="250" cy="250" r="250" fill="#1F2033"/>
|
||||||
@ -205,9 +217,9 @@
|
|||||||
<img class="image" src="/resources/profile/nix-bitcoin.png" />
|
<img class="image" src="/resources/profile/nix-bitcoin.png" />
|
||||||
<span>NixOS</span>
|
<span>NixOS</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/Start9Labs/embassy-os" target="_blank" title="EmbassyOS">
|
<a href="https://github.com/Start9Labs/start-os" target="_blank" title="StartOS">
|
||||||
<img class="image" src="/resources/profile/start9.png" />
|
<img class="image" src="/resources/profile/start9.png" />
|
||||||
<span>EmbassyOS</span>
|
<span>StartOS</span>
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/btcpayserver/btcpayserver" target="_blank" title="BTCPay Server">
|
<a href="https://github.com/btcpayserver/btcpayserver" target="_blank" title="BTCPay Server">
|
||||||
<img class="image not-rounded" src="/resources/profile/btcpayserver.svg" />
|
<img class="image not-rounded" src="/resources/profile/btcpayserver.svg" />
|
||||||
@ -384,7 +396,7 @@
|
|||||||
Trademark Notice<br>
|
Trademark Notice<br>
|
||||||
</div>
|
</div>
|
||||||
<p>
|
<p>
|
||||||
The Mempool Open Source Project™, mempool.space™, the mempool logo™, the mempool.space logos™, the mempool square logo™, and the mempool blocks logo™ are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
|
The Mempool Open Source Project™, mempool.space™, the mempool logo®, the mempool.space logos™, the mempool square logo®, and the mempool blocks logo™ are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our <a href="https://mempool.space/trademark-policy">Trademark Policy and Guidelines</a> for more details, published on <https://mempool.space/trademark-policy>.
|
While our software is available under an open source software license, the copyright license does not include an implied right or license to use our trademarks. See our <a href="https://mempool.space/trademark-policy">Trademark Policy and Guidelines</a> for more details, published on <https://mempool.space/trademark-policy>.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core';
|
import { ChangeDetectionStrategy, Component, ElementRef, Inject, LOCALE_ID, OnInit, ViewChild } from '@angular/core';
|
||||||
import { WebsocketService } from '../../services/websocket.service';
|
import { WebsocketService } from '../../services/websocket.service';
|
||||||
import { SeoService } from '../../services/seo.service';
|
import { SeoService } from '../../services/seo.service';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
@ -17,6 +17,7 @@ import { DOCUMENT } from '@angular/common';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AboutComponent implements OnInit {
|
export class AboutComponent implements OnInit {
|
||||||
|
@ViewChild('promoVideo') promoVideo: ElementRef;
|
||||||
backendInfo$: Observable<IBackendInfo>;
|
backendInfo$: Observable<IBackendInfo>;
|
||||||
sponsors$: Observable<any>;
|
sponsors$: Observable<any>;
|
||||||
translators$: Observable<ITranslators>;
|
translators$: Observable<ITranslators>;
|
||||||
@ -91,7 +92,11 @@ export class AboutComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showSubtitles(language) {
|
showSubtitles(language): boolean {
|
||||||
return ( this.locale.startsWith( language ) && !this.locale.startsWith('en') );
|
return ( this.locale.startsWith( language ) && !this.locale.startsWith('en') );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unmutePromoVideo(): void {
|
||||||
|
this.promoVideo.nativeElement.muted = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
<div class="full-container">
|
<div class="full-container">
|
||||||
<div class="card-header mb-0 mb-md-4">
|
<div class="card-header mb-0 mb-md-4">
|
||||||
<div class="d-flex d-md-block align-items-baseline">
|
<div class="d-flex d-md-block align-items-baseline">
|
||||||
<span i18n="mining.block-prediction-accuracy">Block Prediction Accuracy</span>
|
<span i18n="mining.blocks-health">Block Health</span>
|
||||||
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
|
<button class="btn p-0 pl-2" style="margin: 0 0 4px 0px" (click)="onSaveChart()">
|
||||||
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
|
<fa-icon [icon]="['fas', 'download']" [fixedWidth]="true"></fa-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -12,34 +12,34 @@
|
|||||||
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
<form [formGroup]="radioGroupForm" class="formRadioGroup" *ngIf="(statsObservable$ | async) as stats">
|
||||||
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
<div class="btn-group btn-group-toggle" name="radioBasic" [class]="{'disabled': isLoading}">
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 144" [class.active]="radioGroupForm.get('dateSpan').value === '24h'">
|
||||||
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 24h
|
<input type="radio" [value]="'24h'" fragment="24h" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 24h
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 432" [class.active]="radioGroupForm.get('dateSpan').value === '3d'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 432" [class.active]="radioGroupForm.get('dateSpan').value === '3d'">
|
||||||
<input type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 3D
|
<input type="radio" [value]="'3d'" fragment="3d" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 3D
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 1008" [class.active]="radioGroupForm.get('dateSpan').value === '1w'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 1008" [class.active]="radioGroupForm.get('dateSpan').value === '1w'">
|
||||||
<input type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 1W
|
<input type="radio" [value]="'1w'" fragment="1w" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 1W
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 4320" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
||||||
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 1M
|
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 12960" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
||||||
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 3M
|
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 3M
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 25920" [class.active]="radioGroupForm.get('dateSpan').value === '6m'">
|
||||||
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 6M
|
<input type="radio" [value]="'6m'" fragment="6m" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 6M
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 52560" [class.active]="radioGroupForm.get('dateSpan').value === '1y'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 52560" [class.active]="radioGroupForm.get('dateSpan').value === '1y'">
|
||||||
<input type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 1Y
|
<input type="radio" [value]="'1y'" fragment="1y" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 1Y
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 105120" [class.active]="radioGroupForm.get('dateSpan').value === '2y'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 105120" [class.active]="radioGroupForm.get('dateSpan').value === '2y'">
|
||||||
<input type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 2Y
|
<input type="radio" [value]="'2y'" fragment="2y" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 2Y
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 157680" [class.active]="radioGroupForm.get('dateSpan').value === '3y'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount >= 157680" [class.active]="radioGroupForm.get('dateSpan').value === '3y'">
|
||||||
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> 3Y
|
<input type="radio" [value]="'3y'" fragment="3y" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> 3Y
|
||||||
</label>
|
</label>
|
||||||
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount > 157680" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
|
<label class="btn btn-primary btn-sm" *ngIf="stats.blockCount > 157680" [class.active]="radioGroupForm.get('dateSpan').value === 'all'">
|
||||||
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" formControlName="dateSpan"> ALL
|
<input type="radio" [value]="'all'" fragment="all" [routerLink]="['/graphs/mining/block-health' | relativeUrl]" formControlName="dateSpan"> ALL
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
@ -13,9 +13,9 @@ import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pi
|
|||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-block-prediction-graph',
|
selector: 'app-block-health-graph',
|
||||||
templateUrl: './block-prediction-graph.component.html',
|
templateUrl: './block-health-graph.component.html',
|
||||||
styleUrls: ['./block-prediction-graph.component.scss'],
|
styleUrls: ['./block-health-graph.component.scss'],
|
||||||
styles: [`
|
styles: [`
|
||||||
.loadingGraphs {
|
.loadingGraphs {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -26,7 +26,7 @@ import { StateService } from '../../services/state.service';
|
|||||||
`],
|
`],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class BlockPredictionGraphComponent implements OnInit {
|
export class BlockHealthGraphComponent implements OnInit {
|
||||||
@Input() right: number | string = 45;
|
@Input() right: number | string = 45;
|
||||||
@Input() left: number | string = 75;
|
@Input() left: number | string = 75;
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.seoService.setTitle($localize`:@@d7d5fcf50179ad70c938491c517efb82de2c8146:Block Prediction Accuracy`);
|
this.seoService.setTitle($localize`:@@d7d5fcf50179ad70c938491c517efb82de2c8146:Block Health`);
|
||||||
this.miningWindowPreference = '24h';//this.miningService.getDefaultTimespan('24h');
|
this.miningWindowPreference = '24h';//this.miningService.getDefaultTimespan('24h');
|
||||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||||
@ -80,7 +80,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
this.storageService.setValue('miningWindowPreference', timespan);
|
this.storageService.setValue('miningWindowPreference', timespan);
|
||||||
this.timespan = timespan;
|
this.timespan = timespan;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
return this.apiService.getHistoricalBlockPrediction$(timespan)
|
return this.apiService.getHistoricalBlocksHealth$(timespan)
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((response) => {
|
tap((response) => {
|
||||||
this.prepareChartOptions(response.body);
|
this.prepareChartOptions(response.body);
|
||||||
@ -163,7 +163,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
hideOverlap: true,
|
hideOverlap: true,
|
||||||
padding: [0, 5],
|
padding: [0, 5],
|
||||||
},
|
},
|
||||||
data: data.map(prediction => prediction[0])
|
data: data.map(health => health[0])
|
||||||
},
|
},
|
||||||
yAxis: data.length === 0 ? undefined : [
|
yAxis: data.length === 0 ? undefined : [
|
||||||
{
|
{
|
||||||
@ -186,12 +186,12 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
series: data.length === 0 ? undefined : [
|
series: data.length === 0 ? undefined : [
|
||||||
{
|
{
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
name: $localize`Match rate`,
|
name: $localize`Health`,
|
||||||
data: data.map(prediction => ({
|
data: data.map(health => ({
|
||||||
value: prediction[2],
|
value: health[2],
|
||||||
block: prediction[1],
|
block: health[1],
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: this.getPredictionColor(prediction[2])
|
color: this.getHealthColor(health[2])
|
||||||
}
|
}
|
||||||
})),
|
})),
|
||||||
type: 'bar',
|
type: 'bar',
|
||||||
@ -257,7 +257,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
return 'rgb(' + gradient.red + ',' + gradient.green + ',' + gradient.blue + ')';
|
return 'rgb(' + gradient.red + ',' + gradient.green + ',' + gradient.blue + ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
getPredictionColor(matchRate) {
|
getHealthColor(matchRate) {
|
||||||
return this.colorGradient(
|
return this.colorGradient(
|
||||||
Math.pow((100 - matchRate) / 100, 0.5),
|
Math.pow((100 - matchRate) / 100, 0.5),
|
||||||
{red: 67, green: 171, blue: 71},
|
{red: 67, green: 171, blue: 71},
|
||||||
@ -294,7 +294,7 @@ export class BlockPredictionGraphComponent implements OnInit {
|
|||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
excludeComponents: ['dataZoom'],
|
excludeComponents: ['dataZoom'],
|
||||||
}), `block-fees-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
}), `block-health-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = prevBottom;
|
this.chartOptions.grid.bottom = prevBottom;
|
||||||
this.chartOptions.backgroundColor = 'none';
|
this.chartOptions.backgroundColor = 'none';
|
@ -37,7 +37,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
value: number;
|
value: number;
|
||||||
feerate: number;
|
feerate: number;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
scene?: BlockScene;
|
scene?: BlockScene;
|
||||||
|
|
||||||
@ -171,6 +171,7 @@ export default class TxView implements TransactionStripped {
|
|||||||
case 'censored':
|
case 'censored':
|
||||||
return auditColors.censored;
|
return auditColors.censored;
|
||||||
case 'missing':
|
case 'missing':
|
||||||
|
case 'sigop':
|
||||||
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
return marginalFeeColors[feeLevelIndex] || marginalFeeColors[mempoolFeeColors.length - 1];
|
||||||
case 'fresh':
|
case 'fresh':
|
||||||
return auditColors.missing;
|
return auditColors.missing;
|
||||||
|
@ -44,6 +44,7 @@
|
|||||||
<td *ngSwitchCase="'found'"><span class="badge badge-success" i18n="transaction.audit.match">Match</span></td>
|
<td *ngSwitchCase="'found'"><span class="badge badge-success" i18n="transaction.audit.match">Match</span></td>
|
||||||
<td *ngSwitchCase="'censored'"><span class="badge badge-danger" i18n="transaction.audit.removed">Removed</span></td>
|
<td *ngSwitchCase="'censored'"><span class="badge badge-danger" i18n="transaction.audit.removed">Removed</span></td>
|
||||||
<td *ngSwitchCase="'missing'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
<td *ngSwitchCase="'missing'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||||
|
<td *ngSwitchCase="'sigop'"><span class="badge badge-warning" i18n="transaction.audit.sigop">High sigop count</span></td>
|
||||||
<td *ngSwitchCase="'fresh'"><span class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span></td>
|
<td *ngSwitchCase="'fresh'"><span class="badge badge-warning" i18n="transaction.audit.recently-broadcasted">Recently broadcasted</span></td>
|
||||||
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
<td *ngSwitchCase="'added'"><span class="badge badge-warning" i18n="transaction.audit.added">Added</span></td>
|
||||||
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
<td *ngSwitchCase="'selected'"><span class="badge badge-warning" i18n="transaction.audit.marginal">Marginal fee rate</span></td>
|
||||||
|
@ -335,6 +335,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
const isMissing = {};
|
const isMissing = {};
|
||||||
const isSelected = {};
|
const isSelected = {};
|
||||||
const isFresh = {};
|
const isFresh = {};
|
||||||
|
const isSigop = {};
|
||||||
this.numMissing = 0;
|
this.numMissing = 0;
|
||||||
this.numUnexpected = 0;
|
this.numUnexpected = 0;
|
||||||
|
|
||||||
@ -354,6 +355,9 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
for (const txid of blockAudit.freshTxs || []) {
|
for (const txid of blockAudit.freshTxs || []) {
|
||||||
isFresh[txid] = true;
|
isFresh[txid] = true;
|
||||||
}
|
}
|
||||||
|
for (const txid of blockAudit.sigopTxs || []) {
|
||||||
|
isSigop[txid] = true;
|
||||||
|
}
|
||||||
// set transaction statuses
|
// set transaction statuses
|
||||||
for (const tx of blockAudit.template) {
|
for (const tx of blockAudit.template) {
|
||||||
tx.context = 'projected';
|
tx.context = 'projected';
|
||||||
@ -362,7 +366,7 @@ export class BlockComponent implements OnInit, OnDestroy {
|
|||||||
} else if (inBlock[tx.txid]) {
|
} else if (inBlock[tx.txid]) {
|
||||||
tx.status = 'found';
|
tx.status = 'found';
|
||||||
} else {
|
} else {
|
||||||
tx.status = isFresh[tx.txid] ? 'fresh' : 'missing';
|
tx.status = isFresh[tx.txid] ? 'fresh' : (isSigop[tx.txid] ? 'sigop' : 'missing');
|
||||||
isMissing[tx.txid] = true;
|
isMissing[tx.txid] = true;
|
||||||
this.numMissing++;
|
this.numMissing++;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<div class="fee-estimation-wrapper" *ngIf="(isLoadingWebSocket$ | async) === false && (recommendedFees$ | async) as recommendedFees; else loadingFees">
|
<div class="fee-estimation-wrapper" *ngIf="(isLoading$ | async) === false && (recommendedFees$ | async) as recommendedFees; else loadingFees">
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<div class="fee-progress-bar" [style.background]="noPriority">
|
<div class="fee-progress-bar" [style.background]="noPriority">
|
||||||
<span class="fee-label" i18n="fees-box.no-priority" i18n-ngbTooltip="Transaction feerate tooltip (economy)" ngbTooltip="Either 2x the minimum, or the Low Priority rate (whichever is lower)" placement="top">No Priority</span>
|
<span class="fee-label" i18n="fees-box.no-priority" i18n-ngbTooltip="Transaction feerate tooltip (economy)" ngbTooltip="Either 2x the minimum, or the Low Priority rate (whichever is lower)" placement="top">No Priority</span>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
|
||||||
import { StateService } from '../../services/state.service';
|
import { StateService } from '../../services/state.service';
|
||||||
import { Observable } from 'rxjs';
|
import { Observable, combineLatest } from 'rxjs';
|
||||||
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
import { Recommendedfees } from '../../interfaces/websocket.interface';
|
||||||
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
import { feeLevels, mempoolFeeColors } from '../../app.constants';
|
||||||
import { tap } from 'rxjs/operators';
|
import { map, startWith, tap } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-fees-box',
|
selector: 'app-fees-box',
|
||||||
@ -12,7 +12,7 @@ import { tap } from 'rxjs/operators';
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class FeesBoxComponent implements OnInit {
|
export class FeesBoxComponent implements OnInit {
|
||||||
isLoadingWebSocket$: Observable<boolean>;
|
isLoading$: Observable<boolean>;
|
||||||
recommendedFees$: Observable<Recommendedfees>;
|
recommendedFees$: Observable<Recommendedfees>;
|
||||||
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
gradient = 'linear-gradient(to right, #2e324e, #2e324e)';
|
||||||
noPriority = '#2e324e';
|
noPriority = '#2e324e';
|
||||||
@ -22,7 +22,12 @@ export class FeesBoxComponent implements OnInit {
|
|||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.isLoadingWebSocket$ = this.stateService.isLoadingWebSocket$;
|
this.isLoading$ = combineLatest(
|
||||||
|
this.stateService.isLoadingWebSocket$.pipe(startWith(false)),
|
||||||
|
this.stateService.loadingIndicators$.pipe(startWith({ mempool: 0 })),
|
||||||
|
).pipe(map(([socket, indicators]) => {
|
||||||
|
return socket || (indicators.mempool != null && indicators.mempool !== 100);
|
||||||
|
}));
|
||||||
this.recommendedFees$ = this.stateService.recommendedFees$
|
this.recommendedFees$ = this.stateService.recommendedFees$
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((fees) => {
|
tap((fees) => {
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
<a class="dropdown-item" routerLinkActive="active"
|
<a class="dropdown-item" routerLinkActive="active"
|
||||||
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
|
[routerLink]="['/graphs/mining/block-sizes-weights' | relativeUrl]" i18n="mining.block-sizes-weights">Block Sizes and Weights</a>
|
||||||
<a *ngIf="stateService.env.AUDIT" class="dropdown-item" routerLinkActive="active"
|
<a *ngIf="stateService.env.AUDIT" class="dropdown-item" routerLinkActive="active"
|
||||||
[routerLink]="['/graphs/mining/block-prediction' | relativeUrl]" i18n="mining.block-prediction-accuracy">Block Prediction Accuracy</a>
|
[routerLink]="['/graphs/mining/block-health' | relativeUrl]" i18n="mining.block-health">Block Health</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<ng-container *ngIf="{ val: network$ | async } as network">
|
<ng-container *ngIf="{ val: network$ | async } as network">
|
||||||
<header>
|
<header>
|
||||||
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
<nav class="navbar navbar-expand-md navbar-dark bg-dark">
|
||||||
<a class="navbar-brand" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]">
|
<a class="navbar-brand" [ngClass]="{'dual-logos': subdomain}" [routerLink]="['/' | relativeUrl]" (click)="brandClick($event)">
|
||||||
<ng-template [ngIf]="subdomain">
|
<ng-template [ngIf]="subdomain">
|
||||||
<div class="subdomain_container">
|
<div class="subdomain_container">
|
||||||
<img [src]="'/api/v1/enterprise/images/' + subdomain + '/logo'" class="subdomain_logo">
|
<img [src]="'/api/v1/enterprise/images/' + subdomain + '/logo'" class="subdomain_logo">
|
||||||
|
@ -53,4 +53,8 @@ export class MasterPageComponent implements OnInit {
|
|||||||
onResize(): void {
|
onResize(): void {
|
||||||
this.isMobile = window.innerWidth <= 767.98;
|
this.isMobile = window.innerWidth <= 767.98;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
brandClick(e): void {
|
||||||
|
this.stateService.resetScroll$.next(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -104,7 +104,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.mempoolEmptyBlocks.forEach((b) => {
|
this.mempoolEmptyBlocks.forEach((b) => {
|
||||||
this.mempoolEmptyBlockStyles.push(this.getStyleForMempoolEmptyBlock(b.index));
|
this.mempoolEmptyBlockStyles.push(this.getStyleForMempoolEmptyBlock(b.index));
|
||||||
});
|
});
|
||||||
this.reduceMempoolBlocksToFitScreen(this.mempoolEmptyBlocks);
|
this.reduceEmptyBlocksToFitScreen(this.mempoolEmptyBlocks);
|
||||||
|
|
||||||
this.mempoolBlocks.map(() => {
|
this.mempoolBlocks.map(() => {
|
||||||
this.updateMempoolBlockStyles();
|
this.updateMempoolBlockStyles();
|
||||||
@ -244,12 +244,33 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(): void {
|
onResize(): void {
|
||||||
this.animateEntry = false;
|
this.animateEntry = false;
|
||||||
|
this.reduceEmptyBlocksToFitScreen(this.mempoolEmptyBlocks);
|
||||||
}
|
}
|
||||||
|
|
||||||
trackByFn(index: number, block: MempoolBlock) {
|
trackByFn(index: number, block: MempoolBlock) {
|
||||||
return (block.isStack) ? `stack-${block.index}` : block.index;
|
return (block.isStack) ? `stack-${block.index}` : block.index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reduceEmptyBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||||
|
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
|
||||||
|
const blocksAmount = Math.min(this.stateService.env.MEMPOOL_BLOCKS_AMOUNT, Math.floor(innerWidth / (this.blockWidth + this.blockPadding)));
|
||||||
|
while (blocks.length < blocksAmount) {
|
||||||
|
blocks.push({
|
||||||
|
blockSize: 0,
|
||||||
|
blockVSize: 0,
|
||||||
|
feeRange: [],
|
||||||
|
index: blocks.length,
|
||||||
|
medianFee: 0,
|
||||||
|
nTx: 0,
|
||||||
|
totalFees: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
while (blocks.length > blocksAmount) {
|
||||||
|
blocks.pop();
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
reduceMempoolBlocksToFitScreen(blocks: MempoolBlock[]): MempoolBlock[] {
|
||||||
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
|
const innerWidth = this.stateService.env.BASE_MODULE !== 'liquid' && window.innerWidth <= 767.98 ? window.innerWidth : window.innerWidth / 2;
|
||||||
let blocksAmount;
|
let blocksAmount;
|
||||||
|
@ -24,6 +24,7 @@ import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/grap
|
|||||||
export class MempoolGraphComponent implements OnInit, OnChanges {
|
export class MempoolGraphComponent implements OnInit, OnChanges {
|
||||||
@Input() data: any[];
|
@Input() data: any[];
|
||||||
@Input() filterSize = 100000;
|
@Input() filterSize = 100000;
|
||||||
|
@Input() limitFilterFee = 1;
|
||||||
@Input() height: number | string = 200;
|
@Input() height: number | string = 200;
|
||||||
@Input() top: number | string = 20;
|
@Input() top: number | string = 20;
|
||||||
@Input() right: number | string = 10;
|
@Input() right: number | string = 10;
|
||||||
@ -40,7 +41,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
};
|
};
|
||||||
windowPreference: string;
|
windowPreference: string;
|
||||||
hoverIndexSerie = 0;
|
hoverIndexSerie = 0;
|
||||||
|
maxFee: number;
|
||||||
feeLimitIndex: number;
|
feeLimitIndex: number;
|
||||||
|
maxFeeIndex: number;
|
||||||
feeLevelsOrdered = [];
|
feeLevelsOrdered = [];
|
||||||
chartColorsOrdered = chartColors;
|
chartColorsOrdered = chartColors;
|
||||||
inverted: boolean;
|
inverted: boolean;
|
||||||
@ -98,8 +101,9 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
}
|
}
|
||||||
|
|
||||||
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
generateArray(mempoolStats: OptimizedMempoolStats[]) {
|
||||||
let finalArray: number[][][] = [];
|
const finalArray: number[][][] = [];
|
||||||
let feesArray: number[][] = [];
|
let feesArray: number[][] = [];
|
||||||
|
|
||||||
let maxTier = 0;
|
let maxTier = 0;
|
||||||
for (let index = 37; index > -1; index--) {
|
for (let index = 37; index > -1; index--) {
|
||||||
feesArray = [];
|
feesArray = [];
|
||||||
@ -111,7 +115,8 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
});
|
});
|
||||||
finalArray.push(feesArray);
|
finalArray.push(feesArray);
|
||||||
}
|
}
|
||||||
this.feeLimitIndex = maxTier;
|
this.maxFeeIndex = maxTier;
|
||||||
|
|
||||||
finalArray.reverse();
|
finalArray.reverse();
|
||||||
return finalArray;
|
return finalArray;
|
||||||
}
|
}
|
||||||
@ -124,7 +129,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
const newColors = [];
|
const newColors = [];
|
||||||
for (let index = 0; index < series.length; index++) {
|
for (let index = 0; index < series.length; index++) {
|
||||||
const value = series[index];
|
const value = series[index];
|
||||||
if (index < this.feeLimitIndex) {
|
if (index >= this.feeLimitIndex && index <= this.maxFeeIndex) {
|
||||||
newColors.push(this.chartColorsOrdered[index]);
|
newColors.push(this.chartColorsOrdered[index]);
|
||||||
seriesGraph.push({
|
seriesGraph.push({
|
||||||
zlevel: 0,
|
zlevel: 0,
|
||||||
@ -383,21 +388,26 @@ export class MempoolGraphComponent implements OnInit, OnChanges {
|
|||||||
|
|
||||||
orderLevels() {
|
orderLevels() {
|
||||||
this.feeLevelsOrdered = [];
|
this.feeLevelsOrdered = [];
|
||||||
let maxIndex = Math.min(feeLevels.length, this.feeLimitIndex);
|
const maxIndex = Math.min(feeLevels.length, this.maxFeeIndex);
|
||||||
for (let i = 0; i < maxIndex; i++) {
|
for (let i = 0; i < feeLevels.length; i++) {
|
||||||
|
if (feeLevels[i] === this.limitFilterFee) {
|
||||||
|
this.feeLimitIndex = i;
|
||||||
|
}
|
||||||
|
if (feeLevels[i] <= feeLevels[this.maxFeeIndex]) {
|
||||||
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
if (this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet') {
|
||||||
if (i === maxIndex - 1) {
|
if (i === maxIndex || feeLevels[i] == null) {
|
||||||
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)}+`);
|
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)}+`);
|
||||||
} else {
|
} else {
|
||||||
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
|
this.feeLevelsOrdered.push(`${(feeLevels[i] / 10).toFixed(1)} - ${(feeLevels[i + 1] / 10).toFixed(1)}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (i === maxIndex - 1) {
|
if (i === maxIndex || feeLevels[i] == null) {
|
||||||
this.feeLevelsOrdered.push(`${feeLevels[i]}+`);
|
this.feeLevelsOrdered.push(`${feeLevels[i]}+`);
|
||||||
} else {
|
} else {
|
||||||
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
|
this.feeLevelsOrdered.push(`${feeLevels[i]} - ${feeLevels[i + 1]}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.chartColorsOrdered = chartColors.slice(0, this.feeLevelsOrdered.length);
|
this.chartColorsOrdered = chartColors.slice(0, this.feeLevelsOrdered.length);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
markBlockSubscription: Subscription;
|
markBlockSubscription: Subscription;
|
||||||
blockCounterSubscription: Subscription;
|
blockCounterSubscription: Subscription;
|
||||||
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
|
@ViewChild('blockchainContainer') blockchainContainer: ElementRef;
|
||||||
|
resetScrollSubscription: Subscription;
|
||||||
|
|
||||||
isMobile: boolean = false;
|
isMobile: boolean = false;
|
||||||
isiOS: boolean = false;
|
isiOS: boolean = false;
|
||||||
@ -106,6 +107,12 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
}, 60 * 60 * 1000);
|
}, 60 * 60 * 1000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
this.resetScrollSubscription = this.stateService.resetScroll$.subscribe(reset => {
|
||||||
|
if (reset) {
|
||||||
|
this.resetScroll();
|
||||||
|
this.stateService.resetScroll$.next(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
@ -385,5 +392,6 @@ export class StartComponent implements OnInit, OnDestroy {
|
|||||||
this.chainTipSubscription.unsubscribe();
|
this.chainTipSubscription.unsubscribe();
|
||||||
this.markBlockSubscription.unsubscribe();
|
this.markBlockSubscription.unsubscribe();
|
||||||
this.blockCounterSubscription.unsubscribe();
|
this.blockCounterSubscription.unsubscribe();
|
||||||
|
this.resetScrollSubscription.unsubscribe();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
|
<div class="dropdown-fees" ngbDropdownMenu aria-labelledby="dropdownFees">
|
||||||
<ul>
|
<ul>
|
||||||
<ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
|
<ng-template ngFor let-feeData let-i="index" [ngForOf]="feeLevelDropdownData">
|
||||||
<ng-template [ngIf]="feeData.fee <= 400">
|
<ng-template [ngIf]="feeData.fee <= (feeLevels[maxFeeIndex])">
|
||||||
<li (click)="filterFeeIndex = feeData.fee"
|
<li (click)="filterFeeIndex = feeData.fee"
|
||||||
[class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
|
[class]="filterFeeIndex > feeData.fee ? 'inactive' : ''">
|
||||||
<span class="square" [ngStyle]="{'backgroundColor': feeData.color}"></span>
|
<span class="square" [ngStyle]="{'backgroundColor': feeData.color}"></span>
|
||||||
@ -84,7 +84,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="incoming-transactions-graph">
|
<div class="incoming-transactions-graph">
|
||||||
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'" [height]="500" [left]="65" [right]="10"
|
<app-mempool-graph #mempoolgraph dir="ltr" [template]="'advanced'"
|
||||||
|
[limitFilterFee]="filterFeeIndex" [height]="500" [left]="65" [right]="10"
|
||||||
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
|
[data]="mempoolStats && mempoolStats.length ? mempoolStats : null"></app-mempool-graph>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -30,7 +30,9 @@ export class StatisticsComponent implements OnInit {
|
|||||||
spinnerLoading = false;
|
spinnerLoading = false;
|
||||||
feeLevels = feeLevels;
|
feeLevels = feeLevels;
|
||||||
chartColors = chartColors;
|
chartColors = chartColors;
|
||||||
|
filterSize = 100000;
|
||||||
filterFeeIndex = 1;
|
filterFeeIndex = 1;
|
||||||
|
maxFeeIndex: number;
|
||||||
dropDownOpen = false;
|
dropDownOpen = false;
|
||||||
|
|
||||||
mempoolStats: OptimizedMempoolStats[] = [];
|
mempoolStats: OptimizedMempoolStats[] = [];
|
||||||
@ -134,6 +136,16 @@ export class StatisticsComponent implements OnInit {
|
|||||||
mempoolStats.reverse();
|
mempoolStats.reverse();
|
||||||
const labels = mempoolStats.map(stats => stats.added);
|
const labels = mempoolStats.map(stats => stats.added);
|
||||||
|
|
||||||
|
let maxTier = 0;
|
||||||
|
for (let index = 37; index > -1; index--) {
|
||||||
|
mempoolStats.forEach((stats) => {
|
||||||
|
if (stats.vsizes[index] >= this.filterSize) {
|
||||||
|
maxTier = Math.max(maxTier, index);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.maxFeeIndex = maxTier;
|
||||||
|
|
||||||
this.capExtremeVbytesValues();
|
this.capExtremeVbytesValues();
|
||||||
|
|
||||||
this.mempoolTransactionsWeightPerSecondData = {
|
this.mempoolTransactionsWeightPerSecondData = {
|
||||||
@ -152,27 +164,42 @@ export class StatisticsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setFeeLevelDropdownData() {
|
setFeeLevelDropdownData() {
|
||||||
let _feeLevels = feeLevels
|
let _feeLevels = feeLevels;
|
||||||
let _chartColors = chartColors;
|
let _chartColors = chartColors;
|
||||||
if (!this.inverted) {
|
if (!this.inverted) {
|
||||||
_feeLevels = [...feeLevels].reverse();
|
_feeLevels = [...feeLevels].reverse();
|
||||||
_chartColors = [...chartColors].reverse();
|
_chartColors = [...chartColors].reverse();
|
||||||
}
|
}
|
||||||
_feeLevels.forEach((fee, i) => {
|
_feeLevels.forEach((fee, i) => {
|
||||||
|
let range;
|
||||||
|
const nextIndex = this.inverted ? i + 1 : i - 1;
|
||||||
|
if (this.stateService.isLiquid()) {
|
||||||
|
if (_feeLevels[nextIndex] == null) {
|
||||||
|
range = `${(_feeLevels[i] / 10).toFixed(1)}+`;
|
||||||
|
} else {
|
||||||
|
range = `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[nextIndex] / 10).toFixed(1)}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (_feeLevels[nextIndex] == null) {
|
||||||
|
range = `${_feeLevels[i]}+`;
|
||||||
|
} else {
|
||||||
|
range = `${_feeLevels[i]} - ${_feeLevels[nextIndex]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (this.inverted) {
|
if (this.inverted) {
|
||||||
this.feeLevelDropdownData.push({
|
this.feeLevelDropdownData.push({
|
||||||
fee: fee,
|
fee: fee,
|
||||||
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i + 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i + 1]}`,
|
range,
|
||||||
color: _chartColors[i],
|
color: _chartColors[i],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.feeLevelDropdownData.push({
|
this.feeLevelDropdownData.push({
|
||||||
fee: fee,
|
fee: fee,
|
||||||
range: this.stateService.isLiquid() ? `${(_feeLevels[i] / 10).toFixed(1)} - ${(_feeLevels[i - 1] / 10).toFixed(1)}` : `${_feeLevels[i]} - ${_feeLevels[i - 1]}`,
|
range,
|
||||||
color: _chartColors[i - 1],
|
color: _chartColors[i],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -304,7 +304,7 @@
|
|||||||
|
|
||||||
<p>Also, if you are using our Marks in a way described in the sections "Uses for Which We Are Granting a License," you must include the following trademark attribution at the foot of the webpage where you have used the Mark (or, if in a book, on the credits page), on any packaging or labeling, and on advertising or marketing materials:</p>
|
<p>Also, if you are using our Marks in a way described in the sections "Uses for Which We Are Granting a License," you must include the following trademark attribution at the foot of the webpage where you have used the Mark (or, if in a book, on the credits page), on any packaging or labeling, and on advertising or marketing materials:</p>
|
||||||
|
|
||||||
<p>“The Mempool Space K.K.™, The Mempool Open Source Project™, mempool.space™, the mempool logo™, the mempool.space logos™, the mempool square logo™, and the mempool blocks logo™ are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein.”</p>
|
<p>“The Mempool Space K.K.™, The Mempool Open Source Project™, mempool.space™, the mempool logo®, the mempool.space logos™, the mempool square logo®, and the mempool blocks logo™ are either registered trademarks or trademarks of Mempool Space K.K in Japan, the United States, and/or other countries, and are used with permission. Mempool Space K.K. has no affiliation with and does not sponsor or endorse the information provided herein.”</p>
|
||||||
|
|
||||||
<li>What to Do When You See Abuse</li>
|
<li>What to Do When You See Abuse</li>
|
||||||
|
|
||||||
|
@ -18,19 +18,7 @@
|
|||||||
</span>
|
</span>
|
||||||
|
|
||||||
<div class="container-buttons">
|
<div class="container-buttons">
|
||||||
<ng-template [ngIf]="tx?.status?.confirmed">
|
<app-confirmations [chainTip]="latestBlock?.height" [height]="tx?.status?.block_height" [replaced]="replaced"></app-confirmations>
|
||||||
<button *ngIf="latestBlock" type="button" class="btn btn-sm btn-success">
|
|
||||||
<ng-container *ngTemplateOutlet="latestBlock.height - tx.status.block_height + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.status.block_height + 1}"></ng-container>
|
|
||||||
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
|
||||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
|
||||||
</button>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="tx && !tx?.status?.confirmed && replaced">
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Replaced</button>
|
|
||||||
</ng-template>
|
|
||||||
<ng-template [ngIf]="tx && !tx?.status?.confirmed && !replaced">
|
|
||||||
<button type="button" class="btn btn-sm btn-danger" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
@ -271,6 +259,10 @@
|
|||||||
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
<td i18n="transaction.vsize|Transaction Virtual Size">Virtual size</td>
|
||||||
<td [innerHTML]="'‎' + (tx.weight / 4 | vbytes: 2)"></td>
|
<td [innerHTML]="'‎' + (tx.weight / 4 | vbytes: 2)"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr *ngIf="cpfpInfo && cpfpInfo.adjustedVsize && cpfpInfo.adjustedVsize > (tx.weight / 4)">
|
||||||
|
<td i18n="transaction.adjusted-vsize|Transaction Adjusted VSize">Adjusted vsize</td>
|
||||||
|
<td [innerHTML]="'‎' + (cpfpInfo.adjustedVsize | vbytes: 2)"></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="block.weight">Weight</td>
|
<td i18n="block.weight">Weight</td>
|
||||||
<td [innerHTML]="'‎' + (tx.weight | wuBytes: 2)"></td>
|
<td [innerHTML]="'‎' + (tx.weight | wuBytes: 2)"></td>
|
||||||
@ -289,6 +281,10 @@
|
|||||||
<td i18n="transaction.locktime">Locktime</td>
|
<td i18n="transaction.locktime">Locktime</td>
|
||||||
<td [innerHTML]="'‎' + (tx.locktime | number)"></td>
|
<td [innerHTML]="'‎' + (tx.locktime | number)"></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr *ngIf="cpfpInfo && cpfpInfo.adjustedVsize && cpfpInfo.adjustedVsize > (tx.weight / 4)">
|
||||||
|
<td i18n="transaction.sigops|Transaction Sigops">Sigops</td>
|
||||||
|
<td [innerHTML]="'‎' + (cpfpInfo.sigops | number)"></td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td i18n="transaction.hex">Transaction hex</td>
|
<td i18n="transaction.hex">Transaction hex</td>
|
||||||
<td><a target="_blank" href="{{ network === '' ? '' : '/' + network }}/api/tx/{{ txId }}/hex"><fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true"></fa-icon></a></td>
|
<td><a target="_blank" href="{{ network === '' ? '' : '/' + network }}/api/tx/{{ txId }}/hex"><fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true"></fa-icon></a></td>
|
||||||
@ -477,11 +473,11 @@
|
|||||||
{{ tx.feePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
{{ tx.feePerVsize | feeRounding }} <span class="symbol" i18n="shared.sat-vbyte|sat/vB">sat/vB</span>
|
||||||
<ng-template [ngIf]="tx?.status?.confirmed">
|
<ng-template [ngIf]="tx?.status?.confirmed">
|
||||||
|
|
||||||
<app-tx-fee-rating *ngIf="tx.fee && ((cpfpInfo && !cpfpInfo?.descendants?.length && !cpfpInfo?.bestDescendant && !cpfpInfo?.ancestors?.length) || !cpfpInfo)" [tx]="tx"></app-tx-fee-rating>
|
<app-tx-fee-rating *ngIf="tx.fee && !hasEffectiveFeeRate" [tx]="tx"></app-tx-fee-rating>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr *ngIf="cpfpInfo && (cpfpInfo?.bestDescendant || cpfpInfo?.descendants?.length || cpfpInfo?.ancestors?.length)">
|
<tr *ngIf="cpfpInfo && hasEffectiveFeeRate">
|
||||||
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
<td i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="effective-fee-container">
|
<div class="effective-fee-container">
|
||||||
@ -490,7 +486,7 @@
|
|||||||
<app-tx-fee-rating class="ml-2 mr-2" *ngIf="tx.fee || tx.effectiveFeePerVsize" [tx]="tx"></app-tx-fee-rating>
|
<app-tx-fee-rating class="ml-2 mr-2" *ngIf="tx.fee || tx.effectiveFeePerVsize" [tx]="tx"></app-tx-fee-rating>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-outline-info btn-sm btn-small-height float-right" (click)="showCpfpDetails = !showCpfpDetails">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>
|
<button *ngIf="cpfpInfo.bestDescendant || cpfpInfo.descendants?.length || cpfpInfo.ancestors?.length" type="button" class="btn btn-outline-info btn-sm btn-small-height float-right" (click)="showCpfpDetails = !showCpfpDetails">CPFP <fa-icon [icon]="['fas', 'info-circle']" [fixedWidth]="true"></fa-icon></button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -86,6 +86,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
segwitEnabled: boolean;
|
segwitEnabled: boolean;
|
||||||
rbfEnabled: boolean;
|
rbfEnabled: boolean;
|
||||||
taprootEnabled: boolean;
|
taprootEnabled: boolean;
|
||||||
|
hasEffectiveFeeRate: boolean;
|
||||||
|
|
||||||
@ViewChild('graphContainer')
|
@ViewChild('graphContainer')
|
||||||
graphContainer: ElementRef;
|
graphContainer: ElementRef;
|
||||||
@ -157,6 +158,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
.subscribe((cpfpInfo) => {
|
.subscribe((cpfpInfo) => {
|
||||||
if (!cpfpInfo || !this.tx) {
|
if (!cpfpInfo || !this.tx) {
|
||||||
this.cpfpInfo = null;
|
this.cpfpInfo = null;
|
||||||
|
this.hasEffectiveFeeRate = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// merge ancestors/descendants
|
// merge ancestors/descendants
|
||||||
@ -164,16 +166,21 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) {
|
if (cpfpInfo.bestDescendant && !cpfpInfo.descendants?.length) {
|
||||||
relatives.push(cpfpInfo.bestDescendant);
|
relatives.push(cpfpInfo.bestDescendant);
|
||||||
}
|
}
|
||||||
let totalWeight =
|
const hasRelatives = !!relatives.length;
|
||||||
this.tx.weight +
|
if (!cpfpInfo.effectiveFeePerVsize && hasRelatives) {
|
||||||
relatives.reduce((prev, val) => prev + val.weight, 0);
|
let totalWeight =
|
||||||
let totalFees =
|
this.tx.weight +
|
||||||
this.tx.fee +
|
relatives.reduce((prev, val) => prev + val.weight, 0);
|
||||||
relatives.reduce((prev, val) => prev + val.fee, 0);
|
let totalFees =
|
||||||
|
this.tx.fee +
|
||||||
this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
|
relatives.reduce((prev, val) => prev + val.fee, 0);
|
||||||
|
this.tx.effectiveFeePerVsize = totalFees / (totalWeight / 4);
|
||||||
|
} else {
|
||||||
|
this.tx.effectiveFeePerVsize = cpfpInfo.effectiveFeePerVsize;
|
||||||
|
}
|
||||||
|
|
||||||
this.cpfpInfo = cpfpInfo;
|
this.cpfpInfo = cpfpInfo;
|
||||||
|
this.hasEffectiveFeeRate = hasRelatives || (this.tx.effectiveFeePerVsize && (Math.abs(this.tx.effectiveFeePerVsize - this.tx.feePerVsize) > 0.01));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.fetchRbfSubscription = this.fetchRbfHistory$
|
this.fetchRbfSubscription = this.fetchRbfHistory$
|
||||||
@ -359,6 +366,8 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
ancestors: tx.ancestors,
|
ancestors: tx.ancestors,
|
||||||
bestDescendant: tx.bestDescendant,
|
bestDescendant: tx.bestDescendant,
|
||||||
};
|
};
|
||||||
|
const hasRelatives = !!(tx.ancestors.length || tx.bestDescendant);
|
||||||
|
this.hasEffectiveFeeRate = hasRelatives || (tx.effectiveFeePerVsize && (Math.abs(tx.effectiveFeePerVsize - tx.feePerVsize) > 0.01));
|
||||||
} else {
|
} else {
|
||||||
this.fetchCpfp$.next(this.tx.txid);
|
this.fetchCpfp$.next(this.tx.txid);
|
||||||
}
|
}
|
||||||
@ -382,7 +391,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.blocksSubscription = this.stateService.blocks$.subscribe(([block, txConfirmed]) => {
|
this.blocksSubscription = this.stateService.blocks$.subscribe(([block, txConfirmed]) => {
|
||||||
this.latestBlock = block;
|
this.latestBlock = block;
|
||||||
|
|
||||||
if (txConfirmed && this.tx && !this.tx.status.confirmed) {
|
if (txConfirmed && this.tx && !this.tx.status.confirmed && txConfirmed === this.tx.txid) {
|
||||||
this.tx.status = {
|
this.tx.status = {
|
||||||
confirmed: true,
|
confirmed: true,
|
||||||
block_height: block.height,
|
block_height: block.height,
|
||||||
@ -500,6 +509,7 @@ export class TransactionComponent implements OnInit, AfterViewInit, OnDestroy {
|
|||||||
this.replaced = false;
|
this.replaced = false;
|
||||||
this.transactionTime = -1;
|
this.transactionTime = -1;
|
||||||
this.cpfpInfo = null;
|
this.cpfpInfo = null;
|
||||||
|
this.hasEffectiveFeeRate = false;
|
||||||
this.rbfInfo = null;
|
this.rbfInfo = null;
|
||||||
this.rbfReplaces = [];
|
this.rbfReplaces = [];
|
||||||
this.showCpfpDetails = false;
|
this.showCpfpDetails = false;
|
||||||
|
@ -298,14 +298,7 @@
|
|||||||
|
|
||||||
<div class="float-right">
|
<div class="float-right">
|
||||||
<ng-container *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
<ng-container *ngIf="showConfirmations && latestBlock$ | async as latestBlock">
|
||||||
<button *ngIf="tx.status.confirmed; else unconfirmedButton" type="button" class="btn btn-sm btn-success mt-2">
|
<app-confirmations [chainTip]="latestBlock?.height" [height]="tx?.status?.block_height" buttonClass="mt-2"></app-confirmations>
|
||||||
<ng-container *ngTemplateOutlet="latestBlock.height - tx.status.block_height + 1 == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: latestBlock.height - tx.status.block_height + 1}"></ng-container>
|
|
||||||
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
|
||||||
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
|
||||||
</button>
|
|
||||||
<ng-template #unconfirmedButton>
|
|
||||||
<button type="button" class="btn btn-sm btn-danger mt-2" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
|
||||||
</ng-template>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
|
<button *ngIf="address === ''; else viewingAddress" type="button" class="btn btn-sm btn-primary mt-2 ml-2" (click)="switchCurrency()">
|
||||||
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
<ng-template [ngIf]="(network === 'liquid' || network === 'liquidtestnet') && haveBlindedOutputValues(tx)" [ngIfElse]="defaultAmount" i18n="shared.confidential">Confidential</ng-template>
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
<div class="mempool-graph">
|
<div class="mempool-graph">
|
||||||
<app-mempool-graph
|
<app-mempool-graph
|
||||||
[template]="'widget'"
|
[template]="'widget'"
|
||||||
[filterSize]="1000000"
|
|
||||||
[data]="mempoolStats.value?.mempool"
|
[data]="mempoolStats.value?.mempool"
|
||||||
[windowPreferenceOverride]="'2h'"
|
[windowPreferenceOverride]="'2h'"
|
||||||
></app-mempool-graph>
|
></app-mempool-graph>
|
||||||
|
@ -21,7 +21,7 @@ import { DashboardComponent } from '../dashboard/dashboard.component';
|
|||||||
import { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component';
|
import { MiningDashboardComponent } from '../components/mining-dashboard/mining-dashboard.component';
|
||||||
import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component';
|
import { HashrateChartComponent } from '../components/hashrate-chart/hashrate-chart.component';
|
||||||
import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.component';
|
import { HashrateChartPoolsComponent } from '../components/hashrates-chart-pools/hashrate-chart-pools.component';
|
||||||
import { BlockPredictionGraphComponent } from '../components/block-prediction-graph/block-prediction-graph.component';
|
import { BlockHealthGraphComponent } from '../components/block-health-graph/block-health-graph.component';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -46,7 +46,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
LbtcPegsGraphComponent,
|
LbtcPegsGraphComponent,
|
||||||
HashrateChartComponent,
|
HashrateChartComponent,
|
||||||
HashrateChartPoolsComponent,
|
HashrateChartPoolsComponent,
|
||||||
BlockPredictionGraphComponent,
|
BlockHealthGraphComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
import { BlockPredictionGraphComponent } from '../components/block-prediction-graph/block-prediction-graph.component';
|
import { BlockHealthGraphComponent } from '../components/block-health-graph/block-health-graph.component';
|
||||||
import { BlockFeeRatesGraphComponent } from '../components/block-fee-rates-graph/block-fee-rates-graph.component';
|
import { BlockFeeRatesGraphComponent } from '../components/block-fee-rates-graph/block-fee-rates-graph.component';
|
||||||
import { BlockFeesGraphComponent } from '../components/block-fees-graph/block-fees-graph.component';
|
import { BlockFeesGraphComponent } from '../components/block-fees-graph/block-fees-graph.component';
|
||||||
import { BlockRewardsGraphComponent } from '../components/block-rewards-graph/block-rewards-graph.component';
|
import { BlockRewardsGraphComponent } from '../components/block-rewards-graph/block-rewards-graph.component';
|
||||||
@ -143,9 +143,9 @@ const routes: Routes = [
|
|||||||
redirectTo: 'mempool',
|
redirectTo: 'mempool',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'mining/block-prediction',
|
path: 'mining/block-health',
|
||||||
data: { networks: ['bitcoin'] },
|
data: { networks: ['bitcoin'] },
|
||||||
component: BlockPredictionGraphComponent,
|
component: BlockHealthGraphComponent,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -24,6 +24,9 @@ export interface CpfpInfo {
|
|||||||
ancestors: Ancestor[];
|
ancestors: Ancestor[];
|
||||||
descendants?: Ancestor[];
|
descendants?: Ancestor[];
|
||||||
bestDescendant?: BestDescendant | null;
|
bestDescendant?: BestDescendant | null;
|
||||||
|
effectiveFeePerVsize?: number;
|
||||||
|
sigops?: number;
|
||||||
|
adjustedVsize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RbfInfo {
|
export interface RbfInfo {
|
||||||
@ -155,7 +158,7 @@ export interface TransactionStripped {
|
|||||||
fee: number;
|
fee: number;
|
||||||
vsize: number;
|
vsize: number;
|
||||||
value: number;
|
value: number;
|
||||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RbfTransaction extends TransactionStripped {
|
interface RbfTransaction extends TransactionStripped {
|
||||||
|
@ -6,7 +6,7 @@ export interface WebsocketResponse {
|
|||||||
block?: BlockExtended;
|
block?: BlockExtended;
|
||||||
blocks?: BlockExtended[];
|
blocks?: BlockExtended[];
|
||||||
conversions?: any;
|
conversions?: any;
|
||||||
txConfirmed?: boolean;
|
txConfirmed?: string;
|
||||||
historicalDate?: string;
|
historicalDate?: string;
|
||||||
mempoolInfo?: MempoolInfo;
|
mempoolInfo?: MempoolInfo;
|
||||||
vBytesPerSecond?: number;
|
vBytesPerSecond?: number;
|
||||||
@ -76,7 +76,7 @@ export interface TransactionStripped {
|
|||||||
vsize: number;
|
vsize: number;
|
||||||
value: number;
|
value: number;
|
||||||
rate?: number; // effective fee rate
|
rate?: number; // effective fee rate
|
||||||
status?: 'found' | 'missing' | 'fresh' | 'added' | 'censored' | 'selected';
|
status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'added' | 'censored' | 'selected';
|
||||||
context?: 'projected' | 'actual';
|
context?: 'projected' | 'actual';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,20 +253,4 @@ export class NodeFeeChartComponent implements OnInit {
|
|||||||
isMobile() {
|
isMobile() {
|
||||||
return (window.innerWidth <= 767.98);
|
return (window.innerWidth <= 767.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSaveChart() {
|
|
||||||
// @ts-ignore
|
|
||||||
const prevBottom = this.chartOptions.grid.bottom;
|
|
||||||
// @ts-ignore
|
|
||||||
this.chartOptions.grid.bottom = 40;
|
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
|
||||||
download(this.chartInstance.getDataURL({
|
|
||||||
pixelRatio: 2,
|
|
||||||
}), `node-fee-chart.svg`);
|
|
||||||
// @ts-ignore
|
|
||||||
this.chartOptions.grid.bottom = prevBottom;
|
|
||||||
this.chartOptions.backgroundColor = 'none';
|
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -252,21 +252,4 @@ export class NodeStatisticsChartComponent implements OnInit {
|
|||||||
isMobile() {
|
isMobile() {
|
||||||
return (window.innerWidth <= 767.98);
|
return (window.innerWidth <= 767.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
onSaveChart() {
|
|
||||||
// @ts-ignore
|
|
||||||
const prevBottom = this.chartOptions.grid.bottom;
|
|
||||||
const now = new Date();
|
|
||||||
// @ts-ignore
|
|
||||||
this.chartOptions.grid.bottom = 40;
|
|
||||||
this.chartOptions.backgroundColor = '#11131f';
|
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
|
||||||
download(this.chartInstance.getDataURL({
|
|
||||||
pixelRatio: 2,
|
|
||||||
}), `block-sizes-weights-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
|
||||||
// @ts-ignore
|
|
||||||
this.chartOptions.grid.bottom = prevBottom;
|
|
||||||
this.chartOptions.backgroundColor = 'none';
|
|
||||||
this.chartInstance.setOption(this.chartOptions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -442,7 +442,7 @@ export class NodesNetworksChartComponent implements OnInit {
|
|||||||
download(this.chartInstance.getDataURL({
|
download(this.chartInstance.getDataURL({
|
||||||
pixelRatio: 2,
|
pixelRatio: 2,
|
||||||
excludeComponents: ['dataZoom'],
|
excludeComponents: ['dataZoom'],
|
||||||
}), `block-sizes-weights-${this.timespan}-${Math.round(now.getTime() / 1000)}.svg`);
|
}), `lightning-nodes-per-network-${Math.round(now.getTime() / 1000)}.svg`);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
this.chartOptions.grid.bottom = prevBottom;
|
this.chartOptions.grid.bottom = prevBottom;
|
||||||
this.chartOptions.backgroundColor = 'none';
|
this.chartOptions.backgroundColor = 'none';
|
||||||
|
@ -238,7 +238,7 @@ export class ApiService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistoricalBlockPrediction$(interval: string | undefined) : Observable<any> {
|
getHistoricalBlocksHealth$(interval: string | undefined) : Observable<any> {
|
||||||
return this.httpClient.get<any[]>(
|
return this.httpClient.get<any[]>(
|
||||||
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/predictions` +
|
this.apiBaseUrl + this.apiBasePath + `/api/v1/mining/blocks/predictions` +
|
||||||
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
(interval !== undefined ? `/${interval}` : ''), { observe: 'response' }
|
||||||
|
@ -92,7 +92,7 @@ export class StateService {
|
|||||||
|
|
||||||
networkChanged$ = new ReplaySubject<string>(1);
|
networkChanged$ = new ReplaySubject<string>(1);
|
||||||
lightningChanged$ = new ReplaySubject<boolean>(1);
|
lightningChanged$ = new ReplaySubject<boolean>(1);
|
||||||
blocks$: ReplaySubject<[BlockExtended, boolean]>;
|
blocks$: ReplaySubject<[BlockExtended, string]>;
|
||||||
transactions$ = new ReplaySubject<TransactionStripped>(6);
|
transactions$ = new ReplaySubject<TransactionStripped>(6);
|
||||||
conversions$ = new ReplaySubject<any>(1);
|
conversions$ = new ReplaySubject<any>(1);
|
||||||
bsqPrice$ = new ReplaySubject<number>(1);
|
bsqPrice$ = new ReplaySubject<number>(1);
|
||||||
@ -126,6 +126,7 @@ export class StateService {
|
|||||||
keyNavigation$ = new Subject<KeyboardEvent>();
|
keyNavigation$ = new Subject<KeyboardEvent>();
|
||||||
|
|
||||||
blockScrolling$: Subject<boolean> = new Subject<boolean>();
|
blockScrolling$: Subject<boolean> = new Subject<boolean>();
|
||||||
|
resetScroll$: Subject<boolean> = new Subject<boolean>();
|
||||||
timeLtr: BehaviorSubject<boolean>;
|
timeLtr: BehaviorSubject<boolean>;
|
||||||
hideFlow: BehaviorSubject<boolean>;
|
hideFlow: BehaviorSubject<boolean>;
|
||||||
hideAudit: BehaviorSubject<boolean>;
|
hideAudit: BehaviorSubject<boolean>;
|
||||||
@ -163,7 +164,7 @@ export class StateService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.blocks$ = new ReplaySubject<[BlockExtended, boolean]>(this.env.KEEP_BLOCKS_AMOUNT);
|
this.blocks$ = new ReplaySubject<[BlockExtended, string]>(this.env.KEEP_BLOCKS_AMOUNT);
|
||||||
|
|
||||||
if (this.env.BASE_MODULE === 'bisq') {
|
if (this.env.BASE_MODULE === 'bisq') {
|
||||||
this.network = this.env.BASE_MODULE;
|
this.network = this.env.BASE_MODULE;
|
||||||
|
@ -241,7 +241,7 @@ export class WebsocketService {
|
|||||||
blocks.forEach((block: BlockExtended) => {
|
blocks.forEach((block: BlockExtended) => {
|
||||||
if (block.height > this.stateService.latestBlockHeight) {
|
if (block.height > this.stateService.latestBlockHeight) {
|
||||||
maxHeight = Math.max(maxHeight, block.height);
|
maxHeight = Math.max(maxHeight, block.height);
|
||||||
this.stateService.blocks$.next([block, false]);
|
this.stateService.blocks$.next([block, '']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.stateService.updateChainTip(maxHeight);
|
this.stateService.updateChainTip(maxHeight);
|
||||||
@ -258,7 +258,7 @@ export class WebsocketService {
|
|||||||
if (response.block) {
|
if (response.block) {
|
||||||
if (response.block.height > this.stateService.latestBlockHeight) {
|
if (response.block.height > this.stateService.latestBlockHeight) {
|
||||||
this.stateService.updateChainTip(response.block.height);
|
this.stateService.updateChainTip(response.block.height);
|
||||||
this.stateService.blocks$.next([response.block, !!response.txConfirmed]);
|
this.stateService.blocks$.next([response.block, response.txConfirmed || '']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.txConfirmed) {
|
if (response.txConfirmed) {
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
<ng-template [ngIf]="confirmations">
|
||||||
|
<button type="button" class="btn btn-sm btn-success {{buttonClass}}">
|
||||||
|
<ng-container *ngTemplateOutlet="confirmations == 1 ? confirmationSingular : confirmationPlural; context: {$implicit: confirmations}"></ng-container>
|
||||||
|
<ng-template #confirmationSingular let-i i18n="shared.confirmation-count.singular|Transaction singular confirmation count">{{ i }} confirmation</ng-template>
|
||||||
|
<ng-template #confirmationPlural let-i i18n="shared.confirmation-count.plural|Transaction plural confirmation count">{{ i }} confirmations</ng-template>
|
||||||
|
</button>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && replaced">
|
||||||
|
<button type="button" class="btn btn-sm btn-danger {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Replaced</button>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template [ngIf]="!hideUnconfirmed && !confirmations && !replaced">
|
||||||
|
<button type="button" class="btn btn-sm btn-danger {{buttonClass}}" i18n="transaction.unconfirmed|Transaction unconfirmed state">Unconfirmed</button>
|
||||||
|
</ng-template>
|
@ -0,0 +1,26 @@
|
|||||||
|
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-confirmations',
|
||||||
|
templateUrl: './confirmations.component.html',
|
||||||
|
styleUrls: ['./confirmations.component.scss'],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
})
|
||||||
|
export class ConfirmationsComponent implements OnChanges {
|
||||||
|
@Input() chainTip: number;
|
||||||
|
@Input() height: number;
|
||||||
|
@Input() replaced: boolean = false;
|
||||||
|
@Input() hideUnconfirmed: boolean = false;
|
||||||
|
@Input() buttonClass: string = '';
|
||||||
|
|
||||||
|
confirmations: number = 0;
|
||||||
|
|
||||||
|
ngOnChanges(): void {
|
||||||
|
if (this.chainTip != null && this.height != null) {
|
||||||
|
this.confirmations = Math.max(1, this.chainTip - this.height + 1);
|
||||||
|
} else {
|
||||||
|
this.confirmations = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -85,6 +85,7 @@ import { SatsComponent } from './components/sats/sats.component';
|
|||||||
import { TruncateComponent } from './components/truncate/truncate.component';
|
import { TruncateComponent } from './components/truncate/truncate.component';
|
||||||
import { SearchResultsComponent } from '../components/search-form/search-results/search-results.component';
|
import { SearchResultsComponent } from '../components/search-form/search-results/search-results.component';
|
||||||
import { TimestampComponent } from './components/timestamp/timestamp.component';
|
import { TimestampComponent } from './components/timestamp/timestamp.component';
|
||||||
|
import { ConfirmationsComponent } from './components/confirmations/confirmations.component';
|
||||||
import { ToggleComponent } from './components/toggle/toggle.component';
|
import { ToggleComponent } from './components/toggle/toggle.component';
|
||||||
import { GeolocationComponent } from '../shared/components/geolocation/geolocation.component';
|
import { GeolocationComponent } from '../shared/components/geolocation/geolocation.component';
|
||||||
import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.component';
|
import { TestnetAlertComponent } from './components/testnet-alert/testnet-alert.component';
|
||||||
@ -175,6 +176,7 @@ import { ClockMempoolComponent } from '../components/clock/clock-mempool.compone
|
|||||||
TruncateComponent,
|
TruncateComponent,
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
TimestampComponent,
|
TimestampComponent,
|
||||||
|
ConfirmationsComponent,
|
||||||
ToggleComponent,
|
ToggleComponent,
|
||||||
GeolocationComponent,
|
GeolocationComponent,
|
||||||
TestnetAlertComponent,
|
TestnetAlertComponent,
|
||||||
@ -289,6 +291,7 @@ import { ClockMempoolComponent } from '../components/clock/clock-mempool.compone
|
|||||||
TruncateComponent,
|
TruncateComponent,
|
||||||
SearchResultsComponent,
|
SearchResultsComponent,
|
||||||
TimestampComponent,
|
TimestampComponent,
|
||||||
|
ConfirmationsComponent,
|
||||||
ToggleComponent,
|
ToggleComponent,
|
||||||
GeolocationComponent,
|
GeolocationComponent,
|
||||||
PreviewTitleComponent,
|
PreviewTitleComponent,
|
||||||
|
@ -356,10 +356,10 @@ ELEMENTS_REPO_BRANCH=master
|
|||||||
ELEMENTS_LATEST_RELEASE=elements-22.1
|
ELEMENTS_LATEST_RELEASE=elements-22.1
|
||||||
echo -n '.'
|
echo -n '.'
|
||||||
|
|
||||||
BITCOIN_ELECTRS_REPO_URL=https://github.com/blockstream/electrs
|
BITCOIN_ELECTRS_REPO_URL=https://github.com/mempool/electrs
|
||||||
BITCOIN_ELECTRS_REPO_NAME=electrs
|
BITCOIN_ELECTRS_REPO_NAME=electrs
|
||||||
BITCOIN_ELECTRS_REPO_BRANCH=new-index
|
BITCOIN_ELECTRS_REPO_BRANCH=mempool
|
||||||
BITCOIN_ELECTRS_LATEST_RELEASE=new-index
|
BITCOIN_ELECTRS_LATEST_RELEASE=mempool
|
||||||
|
|
||||||
ELEMENTS_ELECTRS_REPO_URL=https://github.com/blockstream/electrs
|
ELEMENTS_ELECTRS_REPO_URL=https://github.com/blockstream/electrs
|
||||||
ELEMENTS_ELECTRS_REPO_NAME=electrs
|
ELEMENTS_ELECTRS_REPO_NAME=electrs
|
||||||
@ -1312,8 +1312,13 @@ case $OS in
|
|||||||
osSudo "${ROOT_USER}" pw usermod ${MEMPOOL_USER} -G "${CLN_GROUP}"
|
osSudo "${ROOT_USER}" pw usermod ${MEMPOOL_USER} -G "${CLN_GROUP}"
|
||||||
osSudo "${ROOT_USER}" chsh -s `which zsh` "${CLN_USER}"
|
osSudo "${ROOT_USER}" chsh -s `which zsh` "${CLN_USER}"
|
||||||
echo "export PATH=$PATH:$HOME/.local/bin" >> "${CLN_HOME}/.zshrc"
|
echo "export PATH=$PATH:$HOME/.local/bin" >> "${CLN_HOME}/.zshrc"
|
||||||
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/bitcoin"
|
||||||
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning" "${CLN_HOME}/.lightning/{bitcoin,signet,testnet}"
|
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/signet"
|
||||||
|
osSudo "${ROOT_USER}" mkdir -p "${CLN_HOME}/.lightning/testnet"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/bitcoin"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/signet"
|
||||||
|
osSudo "${ROOT_USER}" chmod 750 "${CLN_HOME}" "${CLN_HOME}/.lightning/testnet"
|
||||||
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
osSudo "${ROOT_USER}" chown -R "${CLN_USER}:${CLN_GROUP}" "${CLN_HOME}"
|
||||||
echo "[*] Creating symlink to .bitcoin folder"
|
echo "[*] Creating symlink to .bitcoin folder"
|
||||||
osSudo "${CLN_USER}" ln -s "${BITCOIN_HOME}/.bitcoin" "${CLN_HOME}/.bitcoin"
|
osSudo "${CLN_USER}" ln -s "${BITCOIN_HOME}/.bitcoin" "${CLN_HOME}/.bitcoin"
|
||||||
@ -1321,16 +1326,18 @@ case $OS in
|
|||||||
echo "[*] Installing Core Lightning package"
|
echo "[*] Installing Core Lightning package"
|
||||||
osPackageInstall ${CLN_PKG}
|
osPackageInstall ${CLN_PKG}
|
||||||
|
|
||||||
echo "[*] Installing Core Lightning mainnet Cronjob"
|
######## FIXME: this code doesn't work properly, needs fixing
|
||||||
public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' )
|
#
|
||||||
public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' )
|
# echo "[*] Installing Core Lightning mainnet Cronjob"
|
||||||
|
# public_ipv4=$( ifconfig | grep 'inet ' | awk -F ' ' '{ print $2 }' | grep -v '^103\.165\.192\.' | grep -v '^127\.0\.0\.1' )
|
||||||
crontab_cln+="@reboot sleep 10 ; screen -dmS main lightningd --rpc-file-mode 0660 --alias `hostname` --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
# public_ipv6=$( ifconfig | grep 'inet6' | awk -F ' ' '{ print $2 }' | grep -v '^2001:df6:7280::' | grep -v '^fe80::' | grep -v '^::1' )
|
||||||
crontab_cln+="@reboot sleep 10 ; screen -dmS tes lightningd --rpc-file-mode 0660 --alias `hostname` --network testnet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
#
|
||||||
crontab_cln+="@reboot sleep 10 ; screen -dmS sig lightningd --rpc-file-mode 0660 --alias `hostname` --network signet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6 \n"
|
# crontab_cln+="@reboot sleep 10 ; screen -dmS main lightningd --rpc-file-mode 0660 --alias `hostname` --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||||
crontab_cln+="@reboot sleep 20 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
# crontab_cln+="@reboot sleep 10 ; screen -dmS tes lightningd --rpc-file-mode 0660 --alias `hostname` --network testnet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6\n"
|
||||||
crontab_cln+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
# crontab_cln+="@reboot sleep 10 ; screen -dmS sig lightningd --rpc-file-mode 0660 --alias `hostname` --network signet --disable-ip-discovery --autolisten false --bind-addr $public_ipv4 --announce-addr $public_ipv4 --bind-addr $public_ipv6 --announce-addr $public_ipv6 \n"
|
||||||
echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
|
# crontab_cln+="@reboot sleep 20 ; /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||||
|
# crontab_cln+="1 * * * * /mempool/mempool.space/lightning-seeder >/dev/null 2>&1\n"
|
||||||
|
# echo "${crontab_cln}" | crontab -u "${CLN_USER}" -
|
||||||
;;
|
;;
|
||||||
Debian)
|
Debian)
|
||||||
;;
|
;;
|
||||||
@ -1813,7 +1820,7 @@ echo "[*] Adding MySQL configuration"
|
|||||||
case $OS in
|
case $OS in
|
||||||
|
|
||||||
FreeBSD)
|
FreeBSD)
|
||||||
osSudo "${ROOT_USER}" service mysql-server start
|
osSudo "${ROOT_USER}" service mysql-server onestart
|
||||||
;;
|
;;
|
||||||
Debian)
|
Debian)
|
||||||
osSudo "${ROOT_USER}" service mysql start
|
osSudo "${ROOT_USER}" service mysql start
|
||||||
|
@ -59,7 +59,7 @@ location = / {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# cache /<lang>/main.f40e91d908a068a2.js forever since they never change
|
# cache /<lang>/main.f40e91d908a068a2.js forever since they never change
|
||||||
location ~ ^/([a-z][a-z])/(.+\..+\.(js|css)) {
|
location ~ ^/([a-z][a-z])/(.+\..+\.(js|css))$ {
|
||||||
try_files $uri =404;
|
try_files $uri =404;
|
||||||
expires 1y;
|
expires 1y;
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ location /resources/config. {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# cache /main.f40e91d908a068a2.js forever since they never change
|
# cache /main.f40e91d908a068a2.js forever since they never change
|
||||||
location ~* ^/.+\..+\.(js|css) {
|
location ~* ^/.+\..+\.(js|css)$ {
|
||||||
try_files /$lang/$uri /en-US/$uri =404;
|
try_files /$lang/$uri /en-US/$uri =404;
|
||||||
expires 1y;
|
expires 1y;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user