diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 35b8d0011..d097318ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,7 +9,7 @@ jobs: if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" strategy: matrix: - node: ["16", "17", "18", "20"] + node: ["18", "20"] flavor: ["dev", "prod"] fail-fast: false runs-on: "ubuntu-latest" @@ -67,7 +67,7 @@ jobs: if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" strategy: matrix: - node: ["16", "17", "18", "20"] + node: ["18", "20"] flavor: ["dev", "prod"] fail-fast: false runs-on: "ubuntu-latest" diff --git a/.github/workflows/cypress.yml b/.github/workflows/cypress.yml index d067136bf..f12aebe8b 100644 --- a/.github/workflows/cypress.yml +++ b/.github/workflows/cypress.yml @@ -38,7 +38,7 @@ jobs: - name: Setup node uses: actions/setup-node@v3 with: - node-version: 18 + node-version: 20 cache: "npm" cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json diff --git a/.github/workflows/get_backend_hash.yml b/.github/workflows/get_backend_hash.yml new file mode 100644 index 000000000..57950dee4 --- /dev/null +++ b/.github/workflows/get_backend_hash.yml @@ -0,0 +1,19 @@ +name: 'Print backend hashes' + +on: [workflow_dispatch] + +jobs: + print-backend-sha: + runs-on: 'ubuntu-latest' + name: Print backend hashes + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: repo + + - name: Run script + working-directory: repo + run: | + chmod +x ./scripts/get_backend_hash.sh + sh ./scripts/get_backend_hash.sh diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml index 5d8d71104..55a5585cc 100644 --- a/.github/workflows/on-tag.yml +++ b/.github/workflows/on-tag.yml @@ -68,17 +68,17 @@ jobs: run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - name: Checkout project - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Init repo for Dockerization run: docker/init.sh "$TAG" - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 id: qemu - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 id: buildx - name: Available platforms @@ -98,7 +98,7 @@ jobs: docker buildx build \ --cache-from "type=local,src=/tmp/.buildx-cache" \ --cache-to "type=local,dest=/tmp/.buildx-cache" \ - --platform linux/amd64,linux/arm64,linux/arm/v7 \ + --platform linux/amd64,linux/arm64 \ --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \ --output "type=registry" ./${{ matrix.service }}/ \ diff --git a/.nvmrc b/.nvmrc index f274881e5..a9b234d51 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v16.16.0 +v20.8.0 diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100755 index de1144025..000000000 --- a/GNUmakefile +++ /dev/null @@ -1,47 +0,0 @@ -# If you see pwd_unknown showing up check permissions -PWD ?= pwd_unknown - -# DATABASE DEPLOY FOLDER CONFIG - default ./data -ifeq ($(data),) -DATA := data -export DATA -else -DATA := $(data) -export DATA -endif - -.PHONY: help -help: - @echo '' - @echo '' - @echo ' Usage: make [COMMAND]' - @echo '' - @echo ' make all # build init mempool and electrs' - @echo ' make init # setup some useful configs' - @echo ' make mempool # build q dockerized mempool.space' - @echo ' make electrs # build a docker electrs image' - @echo '' - -.PHONY: init -init: - @echo '' - mkdir -p $(DATA) $(DATA)/mysql $(DATA)/mysql/data - #REF: https://github.com/mempool/mempool/blob/master/docker/README.md - cat docker/docker-compose.yml > docker-compose.yml - cat backend/mempool-config.sample.json > backend/mempool-config.json -.PHONY: mempool -mempool: init - @echo '' - docker-compose up --force-recreate --always-recreate-deps - @echo '' -.PHONY: electrs -electrum: - #REF: https://hub.docker.com/r/beli/electrum - @echo '' - docker build -f docker/electrum/Dockerfile . - @echo '' -.PHONY: all -all: init - make mempool -####################### --include Makefile diff --git a/Makefile b/Makefile deleted file mode 100644 index 53016c66f..000000000 --- a/Makefile +++ /dev/null @@ -1 +0,0 @@ -# For additional configs/scripting diff --git a/README.md b/README.md index dd2e62478..9b56dc436 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Mempool can be conveniently installed on the following full-node distros: - [RoninDojo](https://code.samourai.io/ronindojo/RoninDojo) - [myNode](https://github.com/mynodebtc/mynode) - [Start9](https://github.com/Start9Labs/embassy-os) +- [nix-bitcoin](https://github.com/fort-nix/nix-bitcoin/blob/a1eacce6768ca4894f365af8f79be5bbd594e1c3/examples/configuration.nix#L129) **We highly recommend you deploy your own Mempool instance this way.** No matter which option you pick, you'll be able to get your own fully-sovereign instance of Mempool up quickly without needing to fiddle with any settings. diff --git a/backend/mempool-config.sample.json b/backend/mempool-config.sample.json index 00fe95cc5..0825ce810 100644 --- a/backend/mempool-config.sample.json +++ b/backend/mempool-config.sample.json @@ -51,6 +51,8 @@ "REST_API_URL": "http://127.0.0.1:3000", "UNIX_SOCKET_PATH": "/tmp/esplora-bitcoin-mainnet", "RETRY_UNIX_SOCKET_AFTER": 30000, + "REQUEST_TIMEOUT": 10000, + "FALLBACK_TIMEOUT": 5000, "FALLBACK": [] }, "SECOND_CORE_RPC": { @@ -68,7 +70,8 @@ "DATABASE": "mempool", "USERNAME": "mempool", "PASSWORD": "mempool", - "TIMEOUT": 180000 + "TIMEOUT": 180000, + "PID_DIR": "" }, "SYSLOG": { "ENABLED": true, diff --git a/backend/package-lock.json b/backend/package-lock.json index f5452e908..33f19a26d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -9,12 +9,12 @@ "version": "3.0.0-dev", "license": "GNU Affero General Public License v3.0", "dependencies": { - "@babel/core": "^7.21.3", + "@babel/core": "^7.23.2", "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", - "axios": "~1.4.0", + "axios": "~1.5.0", "bitcoinjs-lib": "~6.1.3", - "crypto-js": "~4.1.1", + "crypto-js": "~4.2.0", "express": "~4.18.2", "maxmind": "~4.3.11", "mysql2": "~3.6.0", @@ -26,7 +26,7 @@ }, "devDependencies": { "@babel/code-frame": "^7.18.6", - "@babel/core": "^7.21.3", + "@babel/core": "^7.23.2", "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", @@ -65,47 +65,48 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", - "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", - "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-compilation-targets": "^7.21.4", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.4", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.4", - "@babel/types": "^7.21.4", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" @@ -115,13 +116,19 @@ "url": "https://opencollective.com/babel" } }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, "node_modules/@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -145,87 +152,84 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", - "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.21.4", - "@babel/helper-validator-option": "^7.21.0", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" }, "engines": { "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", - "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "dependencies": { - "@babel/types": "^7.21.4" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, "node_modules/@babel/helper-plugin-utils": { @@ -238,78 +242,78 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "dependencies": { - "@babel/types": "^7.20.2" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "dependencies": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -317,9 +321,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -506,33 +510,33 @@ } }, "node_modules/@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -541,13 +545,13 @@ } }, "node_modules/@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2321,9 +2325,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", + "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -2590,9 +2594,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -2602,13 +2606,17 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -2700,9 +2708,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001473", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz", - "integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==", + "version": "1.0.30001547", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", + "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", "dev": true, "funding": [ { @@ -2892,9 +2900,9 @@ } }, "node_modules/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "node_modules/debug": { "version": "4.3.4", @@ -3023,9 +3031,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "node_modules/electron-to-chromium": { - "version": "1.4.348", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz", - "integrity": "sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ==", + "version": "1.4.551", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz", + "integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==", "dev": true }, "node_modules/emittery": { @@ -6184,9 +6192,9 @@ "dev": true }, "node_modules/node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "node_modules/normalize-path": { @@ -7399,9 +7407,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { @@ -7411,6 +7419,10 @@ { "type": "tidelift", "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" } ], "dependencies": { @@ -7418,7 +7430,7 @@ "picocolors": "^1.0.0" }, "bin": { - "browserslist-lint": "cli.js" + "update-browserslist-db": "cli.js" }, "peerDependencies": { "browserslist": ">= 4.21.0" @@ -7683,50 +7695,59 @@ } }, "@babel/code-frame": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.21.4.tgz", - "integrity": "sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "requires": { - "@babel/highlight": "^7.18.6" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.4.tgz", - "integrity": "sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.2.tgz", + "integrity": "sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==", "dev": true }, "@babel/core": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.4.tgz", - "integrity": "sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.2.tgz", + "integrity": "sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==", "dev": true, "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-compilation-targets": "^7.21.4", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.4", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.4", - "@babel/types": "^7.21.4", - "convert-source-map": "^1.7.0", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-compilation-targets": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.2", + "@babel/parser": "^7.23.0", + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "dependencies": { + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + } } }, "@babel/generator": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.4.tgz", - "integrity": "sha512-NieM3pVIYW2SwGzKoqfPrQsf4xGs9M9AIG3ThppsSRmO+m7eQhmI6amajKMUeIO37wFfsvnvcxQFx6x6iqxDnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "requires": { - "@babel/types": "^7.21.4", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -7746,66 +7767,63 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.21.4.tgz", - "integrity": "sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz", + "integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==", "dev": true, "requires": { - "@babel/compat-data": "^7.21.4", - "@babel/helper-validator-option": "^7.21.0", - "browserslist": "^4.21.3", + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.15", + "browserslist": "^4.21.9", "lru-cache": "^5.1.1", - "semver": "^6.3.0" + "semver": "^6.3.1" } }, "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true }, "@babel/helper-function-name": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", - "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" } }, "@babel/helper-hoist-variables": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", - "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-module-imports": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz", - "integrity": "sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dev": true, "requires": { - "@babel/types": "^7.21.4" + "@babel/types": "^7.22.15" } }, "@babel/helper-module-transforms": { - "version": "7.21.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", - "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" } }, "@babel/helper-plugin-utils": { @@ -7815,67 +7833,67 @@ "dev": true }, "@babel/helper-simple-access": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", - "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", "dev": true, "requires": { - "@babel/types": "^7.20.2" + "@babel/types": "^7.22.5" } }, "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", - "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", "dev": true, "requires": { - "@babel/types": "^7.18.6" + "@babel/types": "^7.22.5" } }, "@babel/helper-string-parser": { - "version": "7.19.4", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", - "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "dev": true }, "@babel/helper-validator-identifier": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", - "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", - "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz", + "integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==", "dev": true }, "@babel/helpers": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", - "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", + "integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "dev": true, "requires": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" + "@babel/template": "^7.22.15", + "@babel/traverse": "^7.23.2", + "@babel/types": "^7.23.0" } }, "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.4.tgz", - "integrity": "sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -8005,42 +8023,42 @@ } }, "@babel/template": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", - "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" } }, "@babel/traverse": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.4.tgz", - "integrity": "sha512-eyKrRHKdyZxqDm+fV1iqL9UAHMoIg0nDaGqfIOd8rKH17m5snv7Gn4qgjBoFfLz9APvjFU/ICT00NVCv1Epp8Q==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "requires": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.21.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.4.tgz", - "integrity": "sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -9397,9 +9415,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.5.0.tgz", + "integrity": "sha512-D4DdjDo5CY50Qms0qGQTTw6Q44jl7zRwY7bthds06pUGfChBCTcQs+N743eFWGEd6pRTMd6A+I87aWyFV5wiZQ==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -9615,15 +9633,15 @@ } }, "browserslist": { - "version": "4.21.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", - "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", + "node-releases": "^2.0.13", + "update-browserslist-db": "^1.0.13" } }, "bs-logger": { @@ -9694,9 +9712,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001473", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001473.tgz", - "integrity": "sha512-ewDad7+D2vlyy+E4UJuVfiBsU69IL+8oVmTuZnH5Q6CIUbxNfI50uVpRHbUPDD6SUaN2o0Lh4DhTrvLG/Tn1yg==", + "version": "1.0.30001547", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", + "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", "dev": true }, "chalk": { @@ -9832,9 +9850,9 @@ } }, "crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==" + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" }, "debug": { "version": "4.3.4", @@ -9924,9 +9942,9 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" }, "electron-to-chromium": { - "version": "1.4.348", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.348.tgz", - "integrity": "sha512-gM7TdwuG3amns/1rlgxMbeeyNoBFPa+4Uu0c7FeROWh4qWmvSOnvcslKmWy51ggLKZ2n/F/4i2HJ+PVNxH9uCQ==", + "version": "1.4.551", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.551.tgz", + "integrity": "sha512-/Ng/W/kFv7wdEHYzxdK7Cv0BHEGSkSB3M0Ssl8Ndr1eMiYeas/+Mv4cNaDqamqWx6nd2uQZfPz6g25z25M/sdw==", "dev": true }, "emittery": { @@ -12280,9 +12298,9 @@ "dev": true }, "node-releases": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", - "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", "dev": true }, "normalize-path": { @@ -13118,9 +13136,9 @@ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" }, "update-browserslist-db": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", - "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "requires": { "escalade": "^3.1.1", diff --git a/backend/package.json b/backend/package.json index 500cbf93c..bdce50902 100644 --- a/backend/package.json +++ b/backend/package.json @@ -38,12 +38,12 @@ "rust-build": "cd rust-gbt && npm run build-release" }, "dependencies": { - "@babel/core": "^7.21.3", + "@babel/core": "^7.23.2", "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", - "axios": "~1.4.0", + "axios": "~1.5.0", "bitcoinjs-lib": "~6.1.3", - "crypto-js": "~4.1.1", + "crypto-js": "~4.2.0", "express": "~4.18.2", "maxmind": "~4.3.11", "mysql2": "~3.6.0", @@ -55,7 +55,7 @@ }, "devDependencies": { "@babel/code-frame": "^7.18.6", - "@babel/core": "^7.21.3", + "@babel/core": "^7.23.2", "@types/compression": "^1.7.2", "@types/crypto-js": "^4.1.1", "@types/express": "^4.17.17", diff --git a/backend/src/__fixtures__/mempool-config.template.json b/backend/src/__fixtures__/mempool-config.template.json index 1b6c8d411..0e555014c 100644 --- a/backend/src/__fixtures__/mempool-config.template.json +++ b/backend/src/__fixtures__/mempool-config.template.json @@ -52,6 +52,8 @@ "REST_API_URL": "__ESPLORA_REST_API_URL__", "UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__", "RETRY_UNIX_SOCKET_AFTER": 888, + "REQUEST_TIMEOUT": 10000, + "FALLBACK_TIMEOUT": 5000, "FALLBACK": [] }, "SECOND_CORE_RPC": { @@ -69,6 +71,7 @@ "DATABASE": "__DATABASE_DATABASE__", "USERNAME": "__DATABASE_USERNAME__", "PASSWORD": "__DATABASE_PASSWORD__", + "PID_DIR": "__DATABASE_PID_FILE__", "TIMEOUT": 3000 }, "SYSLOG": { diff --git a/backend/src/__tests__/config.test.ts b/backend/src/__tests__/config.test.ts index 8097a2465..fbf1e3f0a 100644 --- a/backend/src/__tests__/config.test.ts +++ b/backend/src/__tests__/config.test.ts @@ -56,6 +56,8 @@ describe('Mempool Backend Config', () => { REST_API_URL: 'http://127.0.0.1:3000', UNIX_SOCKET_PATH: null, RETRY_UNIX_SOCKET_AFTER: 30000, + REQUEST_TIMEOUT: 10000, + FALLBACK_TIMEOUT: 5000, FALLBACK: [], }); @@ -84,6 +86,7 @@ describe('Mempool Backend Config', () => { USERNAME: 'mempool', PASSWORD: 'mempool', TIMEOUT: 180000, + PID_DIR: '' }); expect(config.SYSLOG).toStrictEqual({ diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index 90a31ecae..240fb07ce 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -478,7 +478,7 @@ class BitcoinRoutes { } let nextHash = startFromHash; - for (let i = 0; i < 10 && nextHash; i++) { + for (let i = 0; i < 15 && nextHash; i++) { const localBlock = blocks.getBlocks().find((b) => b.id === nextHash); if (localBlock) { returnBlocks.push(localBlock); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index d6d4327cb..0f3c6290d 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -75,9 +75,9 @@ class FailoverRouter { const results = await Promise.allSettled(this.hosts.map(async (host) => { if (host.socket) { - return this.pollConnection.get('/blocks/tip/height', { socketPath: host.host, timeout: 5000 }); + return this.pollConnection.get('/blocks/tip/height', { socketPath: host.host, timeout: config.ESPLORA.FALLBACK_TIMEOUT }); } else { - return this.pollConnection.get(host.host + '/blocks/tip/height', { timeout: 5000 }); + return this.pollConnection.get(host.host + '/blocks/tip/height', { timeout: config.ESPLORA.FALLBACK_TIMEOUT }); } })); const maxHeight = results.reduce((max, result) => Math.max(max, result.status === 'fulfilled' ? result.value?.data || 0 : 0), 0); @@ -168,10 +168,10 @@ class FailoverRouter { let axiosConfig; let url; if (host.socket) { - axiosConfig = { socketPath: host.host, timeout: 10000, responseType }; + axiosConfig = { socketPath: host.host, timeout: config.ESPLORA.REQUEST_TIMEOUT, responseType }; url = path; } else { - axiosConfig = { timeout: 10000, responseType }; + axiosConfig = { timeout: config.ESPLORA.REQUEST_TIMEOUT, responseType }; url = host.host + path; } return (method === 'post' diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index b7dc39493..89ef7a7be 100644 --- a/backend/src/api/database-migration.ts +++ b/backend/src/api/database-migration.ts @@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository'; import { RowDataPacket } from 'mysql2'; class DatabaseMigration { - private static currentVersion = 65; + private static currentVersion = 66; private queryTimeout = 3600_000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -553,6 +553,11 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"'); await this.updateToSchemaVersion(65); } + + if (databaseSchemaVersion < 66) { + await this.$executeQuery('ALTER TABLE `statistics` ADD min_fee FLOAT UNSIGNED DEFAULT NULL'); + await this.updateToSchemaVersion(66); + } } /** diff --git a/backend/src/api/explorer/nodes.routes.ts b/backend/src/api/explorer/nodes.routes.ts index e2dbcb0b6..3636abe2b 100644 --- a/backend/src/api/explorer/nodes.routes.ts +++ b/backend/src/api/explorer/nodes.routes.ts @@ -42,6 +42,12 @@ class NodesRoutes { switch (config.MEMPOOL.NETWORK) { case 'testnet': nodesList = [ + '0259db43b4e4ac0ff12a805f2d81e521253ba2317f6739bc611d8e2fa156d64256', + '0352b9944b9a52bd2116c91f1ba70c4ef851ac5ba27e1b20f1d92da3ade010dd10', + '03424f5a7601eaa47482cb17100b31a84a04d14fb44b83a57eeceffd8e299878e3', + '032850492ee61a5f7006a2fda6925e4b4ec3782f2b6de2ff0e439ef5a38c3b2470', + '022c80bace98831c44c32fb69755f2b353434e0ee9e7fbda29507f7ef8abea1421', + '02c3559c833e6f99f9ca05fe503e0b4e7524dea9121344edfd3e811101e0c28680', '032c7c7819276c4f706a04df1a0f1e10a5495994a7be4c1d3d28ca766e5a2b957b', '025a7e38c2834dd843591a4d23d5f09cdeb77ddca85f673c2d944a14220ff14cf7', '0395e2731a1673ef21d7a16a727c4fc4d4c35a861c428ce2c819c53d2b81c8bd55', @@ -64,6 +70,12 @@ class NodesRoutes { break; case 'signet': nodesList = [ + '029fe3621fc0c6e08056a14b868f8fb9acca1aa28a129512f6cea0f0d7654d9f92', + '02f60cd7a3a4f1c953dd9554a6ebd51a34f8b10b8124b7fc43a0b381139b55c883', + '03cbbf581774700865eebd1be42d022bc004ba30881274ab304e088a25d70e773d', + '0243348cb3741cfe2d8485fa8375c29c7bc7cbb67577c363cb6987a5e5fd0052cc', + '02cb73e631af44bee600d80f8488a9194c9dc5c7590e575c421a070d1be05bc8e9', + '0306f55ee631aa1e2cd4d9b2bfcbc14404faec5c541cef8b2e6f779061029d09c4', '03ddab321b760433cbf561b615ef62ac7d318630c5f51d523aaf5395b90b751956', '033d92c7bfd213ef1b34c90e985fb5dc77f9ec2409d391492484e57a44c4aca1de', '02ad010dda54253c1eb9efe38b0760657a3b43ecad62198c359c051c9d99d45781', @@ -86,6 +98,12 @@ class NodesRoutes { break; default: nodesList = [ + '02b12b889fe3c943cb05645921040ef13d6d397a2e7a4ad000e28500c505ff26d6', + '0302240ac9d71b39617cbde2764837ec3d6198bd6074b15b75d2ff33108e89d2e1', + '03364a8ace313376e5e4b68c954e287c6388e16df9e9fdbaf0363ecac41105cbf6', + '03229ab4b7f692753e094b93df90530150680f86b535b5183b0cffd75b3df583fc', + '03a696eb7acde991c1be97a58a9daef416659539ae462b897f5e9ae361f990228e', + '0248bf26cf3a63ab8870f34dc0ec9e6c8c6288cdba96ba3f026f34ec0f13ac4055', '03fbc17549ec667bccf397ababbcb4cdc0e3394345e4773079ab2774612ec9be61', '03da9a8623241ccf95f19cd645c6cecd4019ac91570e976eb0a128bebbc4d8a437', '03ca5340cf85cb2e7cf076e489f785410838de174e40be62723e8a60972ad75144', diff --git a/backend/src/api/fee-api.ts b/backend/src/api/fee-api.ts index 82778825e..97dfc29d2 100644 --- a/backend/src/api/fee-api.ts +++ b/backend/src/api/fee-api.ts @@ -3,21 +3,30 @@ import { Common } from './common'; import mempool from './mempool'; import projectedBlocks from './mempool-blocks'; +interface RecommendedFees { + fastestFee: number, + halfHourFee: number, + hourFee: number, + economyFee: number, + minimumFee: number, +} + class FeeApi { constructor() { } defaultFee = Common.isLiquid() ? 0.1 : 1; - public getRecommendedFee() { + public getRecommendedFee(): RecommendedFees { const pBlocks = projectedBlocks.getMempoolBlocks(); const mPool = mempool.getMempoolInfo(); const minimumFee = Math.ceil(mPool.mempoolminfee * 100000); + const defaultMinFee = Math.max(minimumFee, this.defaultFee); if (!pBlocks.length) { return { - 'fastestFee': this.defaultFee, - 'halfHourFee': this.defaultFee, - 'hourFee': this.defaultFee, + 'fastestFee': defaultMinFee, + 'halfHourFee': defaultMinFee, + 'hourFee': defaultMinFee, 'economyFee': minimumFee, 'minimumFee': minimumFee, }; @@ -27,11 +36,15 @@ class FeeApi { const secondMedianFee = pBlocks[1] ? this.optimizeMedianFee(pBlocks[1], pBlocks[2], firstMedianFee) : this.defaultFee; const thirdMedianFee = pBlocks[2] ? this.optimizeMedianFee(pBlocks[2], pBlocks[3], secondMedianFee) : this.defaultFee; + // explicitly enforce a minimum of ceil(mempoolminfee) on all recommendations. + // simply rounding up recommended rates is insufficient, as the purging rate + // can exceed the median rate of projected blocks in some extreme scenarios + // (see https://bitcoin.stackexchange.com/a/120024) return { - 'fastestFee': firstMedianFee, - 'halfHourFee': secondMedianFee, - 'hourFee': thirdMedianFee, - 'economyFee': Math.min(2 * minimumFee, thirdMedianFee), + 'fastestFee': Math.max(minimumFee, firstMedianFee), + 'halfHourFee': Math.max(minimumFee, secondMedianFee), + 'hourFee': Math.max(minimumFee, thirdMedianFee), + 'economyFee': Math.max(minimumFee, Math.min(2 * minimumFee, thirdMedianFee)), 'minimumFee': minimumFee, }; } diff --git a/backend/src/api/memory-cache.ts b/backend/src/api/memory-cache.ts index fe4162420..e71aaa6a2 100644 --- a/backend/src/api/memory-cache.ts +++ b/backend/src/api/memory-cache.ts @@ -31,7 +31,7 @@ class MemoryCache { } private cleanup() { - this.cache = this.cache.filter((cache) => cache.expires < (new Date())); + this.cache = this.cache.filter((cache) => cache.expires > (new Date())); } } diff --git a/backend/src/api/statistics/statistics-api.ts b/backend/src/api/statistics/statistics-api.ts index 9df12d704..5c6896619 100644 --- a/backend/src/api/statistics/statistics-api.ts +++ b/backend/src/api/statistics/statistics-api.ts @@ -15,6 +15,7 @@ class StatisticsApi { mempool_byte_weight, fee_data, total_fee, + min_fee, vsize_1, vsize_2, vsize_3, @@ -54,7 +55,7 @@ class StatisticsApi { vsize_1800, vsize_2000 ) - VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + VALUES (NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)`; const [result]: any = await DB.query(query); return result.insertId; @@ -73,6 +74,7 @@ class StatisticsApi { mempool_byte_weight, fee_data, total_fee, + min_fee, vsize_1, vsize_2, vsize_3, @@ -112,7 +114,7 @@ class StatisticsApi { vsize_1800, vsize_2000 ) - VALUES (${statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, + VALUES (${statistics.added}, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`; const params: (string | number)[] = [ @@ -122,6 +124,7 @@ class StatisticsApi { statistics.mempool_byte_weight, statistics.fee_data, statistics.total_fee, + statistics.min_fee, statistics.vsize_1, statistics.vsize_2, statistics.vsize_3, @@ -171,7 +174,9 @@ class StatisticsApi { private getQueryForDaysAvg(div: number, interval: string) { return `SELECT UNIX_TIMESTAMP(added) as added, + CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions, CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second, + CAST(avg(min_fee) as DOUBLE) as min_fee, CAST(avg(vsize_1) as DOUBLE) as vsize_1, CAST(avg(vsize_2) as DOUBLE) as vsize_2, CAST(avg(vsize_3) as DOUBLE) as vsize_3, @@ -219,7 +224,9 @@ class StatisticsApi { private getQueryForDays(div: number, interval: string) { return `SELECT UNIX_TIMESTAMP(added) as added, + CAST(avg(unconfirmed_transactions) as DOUBLE) as unconfirmed_transactions, CAST(avg(vbytes_per_second) as DOUBLE) as vbytes_per_second, + CAST(avg(min_fee) as DOUBLE) as min_fee, vsize_1, vsize_2, vsize_3, @@ -401,9 +408,11 @@ class StatisticsApi { return statistic.map((s) => { return { added: s.added, + count: s.unconfirmed_transactions, vbytes_per_second: s.vbytes_per_second, mempool_byte_weight: s.mempool_byte_weight, total_fee: s.total_fee, + min_fee: s.min_fee, vsizes: [ s.vsize_1, s.vsize_2, diff --git a/backend/src/api/statistics/statistics.ts b/backend/src/api/statistics/statistics.ts index 27554f36d..494777aad 100644 --- a/backend/src/api/statistics/statistics.ts +++ b/backend/src/api/statistics/statistics.ts @@ -89,6 +89,9 @@ class Statistics { } }); + // get minFee and convert to sats/vb + const minFee = memPool.getMempoolInfo().mempoolminfee * 100000; + try { const insertId = await statisticsApi.$create({ added: 'NOW()', @@ -98,6 +101,7 @@ class Statistics { mempool_byte_weight: totalWeight, total_fee: totalFee, fee_data: '', + min_fee: minFee, vsize_1: weightVsizeFees['1'] || 0, vsize_2: weightVsizeFees['2'] || 0, vsize_3: weightVsizeFees['3'] || 0, diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 41cb6b99c..c50941f39 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -486,6 +486,7 @@ class WebsocketHandler { // pre-compute address transactions const addressCache = this.makeAddressCache(newTransactions); + const removedAddressCache = this.makeAddressCache(deletedTransactions); this.wss.clients.forEach(async (client) => { if (client.readyState !== WebSocket.OPEN) { @@ -526,11 +527,15 @@ class WebsocketHandler { } if (client['track-address']) { - const foundTransactions = Array.from(addressCache[client['track-address']]?.values() || []); + const newTransactions = Array.from(addressCache[client['track-address']]?.values() || []); + const removedTransactions = Array.from(removedAddressCache[client['track-address']]?.values() || []); // txs may be missing prevouts in non-esplora backends // so fetch the full transactions now - const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(foundTransactions) : foundTransactions; + const fullTransactions = (config.MEMPOOL.BACKEND !== 'esplora') ? await this.getFullTransactions(newTransactions) : newTransactions; + if (removedTransactions.length) { + response['address-removed-transactions'] = JSON.stringify(removedTransactions); + } if (fullTransactions.length) { response['address-transactions'] = JSON.stringify(fullTransactions); } diff --git a/backend/src/config.ts b/backend/src/config.ts index ed320d957..6ea7c48f8 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -44,6 +44,8 @@ interface IConfig { REST_API_URL: string; UNIX_SOCKET_PATH: string | void | null; RETRY_UNIX_SOCKET_AFTER: number; + REQUEST_TIMEOUT: number; + FALLBACK_TIMEOUT: number; FALLBACK: string[]; }; LIGHTNING: { @@ -93,6 +95,7 @@ interface IConfig { USERNAME: string; PASSWORD: string; TIMEOUT: number; + PID_DIR: string; }; SYSLOG: { ENABLED: boolean; @@ -189,6 +192,8 @@ const defaults: IConfig = { 'REST_API_URL': 'http://127.0.0.1:3000', 'UNIX_SOCKET_PATH': null, 'RETRY_UNIX_SOCKET_AFTER': 30000, + 'REQUEST_TIMEOUT': 10000, + 'FALLBACK_TIMEOUT': 5000, 'FALLBACK': [], }, 'ELECTRUM': { @@ -219,6 +224,7 @@ const defaults: IConfig = { 'USERNAME': 'mempool', 'PASSWORD': 'mempool', 'TIMEOUT': 180000, + 'PID_DIR': '', }, 'SYSLOG': { 'ENABLED': true, diff --git a/backend/src/database.ts b/backend/src/database.ts index 6ad545fda..c27f28d23 100644 --- a/backend/src/database.ts +++ b/backend/src/database.ts @@ -1,3 +1,5 @@ +import * as fs from 'fs'; +import path from 'path'; import config from './config'; import { createPool, Pool, PoolConnection } from 'mysql2/promise'; import logger from './logger'; @@ -101,6 +103,33 @@ import { FieldPacket, OkPacket, PoolOptions, ResultSetHeader, RowDataPacket } fr } } + public getPidLock(): boolean { + const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`); + if (fs.existsSync(filePath)) { + const pid = fs.readFileSync(filePath).toString(); + if (pid !== `${process.pid}`) { + const msg = `Already running on PID ${pid} (or pid file '${filePath}' is stale)`; + logger.err(msg); + throw new Error(msg); + } else { + return true; + } + } else { + fs.writeFileSync(filePath, `${process.pid}`); + return true; + } + } + + public releasePidLock(): void { + const filePath = path.join(config.DATABASE.PID_DIR || __dirname, `/mempool-${config.DATABASE.DATABASE}.pid`); + if (fs.existsSync(filePath)) { + const pid = fs.readFileSync(filePath).toString(); + if (pid === `${process.pid}`) { + fs.unlinkSync(filePath); + } + } + } + private async getPool(): Promise { if (this.pool === null) { this.pool = createPool(this.poolConfig); diff --git a/backend/src/index.ts b/backend/src/index.ts index 9d0fa07f5..e7e1afa3d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -91,11 +91,18 @@ class Server { async startServer(worker = false): Promise { logger.notice(`Starting Mempool Server${worker ? ' (worker)' : ''}... (${backendInfo.getShortCommitHash()})`); + // Register cleanup listeners for exit events + ['exit', 'SIGINT', 'SIGTERM', 'SIGUSR1', 'SIGUSR2', 'uncaughtException', 'unhandledRejection'].forEach(event => { + process.on(event, () => { this.onExit(event); }); + }); + if (config.MEMPOOL.BACKEND === 'esplora') { bitcoinApi.startHealthChecks(); } if (config.DATABASE.ENABLED) { + DB.getPidLock(); + await DB.checkDbConnection(); try { if (process.env.npm_config_reindex_blocks === 'true') { // Re-index requests @@ -306,6 +313,15 @@ class Server { this.lastHeapLogTime = now; } } + + onExit(exitEvent): void { + if (config.DATABASE.ENABLED) { + DB.releasePidLock(); + } + process.exit(0); + } } + + ((): Server => new Server())(); diff --git a/backend/src/mempool.interfaces.ts b/backend/src/mempool.interfaces.ts index b013f2f26..cb212512c 100644 --- a/backend/src/mempool.interfaces.ts +++ b/backend/src/mempool.interfaces.ts @@ -300,6 +300,7 @@ export interface Statistic { total_fee: number; mempool_byte_weight: number; fee_data: string; + min_fee: number; vsize_1: number; vsize_2: number; @@ -346,6 +347,7 @@ export interface OptimizedStatistic { vbytes_per_second: number; total_fee: number; mempool_byte_weight: number; + min_fee: number; vsizes: number[]; } diff --git a/contributors/TKone7.txt b/contributors/TKone7.txt new file mode 100644 index 000000000..0112e34a3 --- /dev/null +++ b/contributors/TKone7.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of October 23, 2023. + +Signed: TKone7 diff --git a/contributors/fanquake.txt b/contributors/fanquake.txt new file mode 100644 index 000000000..3212bdcbf --- /dev/null +++ b/contributors/fanquake.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of October 23, 2023. + +Signed: fanquake diff --git a/contributors/fubz.txt b/contributors/fubz.txt new file mode 100644 index 000000000..e799d641d --- /dev/null +++ b/contributors/fubz.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of January 25, 2022. + +Signed: fubz diff --git a/contributors/orangesurf.txt b/contributors/orangesurf.txt new file mode 100644 index 000000000..c760a9125 --- /dev/null +++ b/contributors/orangesurf.txt @@ -0,0 +1,3 @@ +I hereby accept the terms of the Contributor License Agreement in the CONTRIBUTING.md file of the mempool/mempool git repository as of September 12, 2023. + +Signed: orange surf diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index bbe4df3d2..96b1a2d5b 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.16.0-buster-slim AS builder +FROM node:20.8.0-buster-slim AS builder ARG commitHash ENV MEMPOOL_COMMIT_HASH=${commitHash} @@ -17,7 +17,7 @@ ENV PATH="/root/.cargo/bin:$PATH" RUN npm install --omit=dev --omit=optional RUN npm run package -FROM node:16.16.0-buster-slim +FROM node:20.8.0-buster-slim WORKDIR /backend diff --git a/docker/backend/mempool-config.json b/docker/backend/mempool-config.json index aa084133f..0e445b358 100644 --- a/docker/backend/mempool-config.json +++ b/docker/backend/mempool-config.json @@ -52,6 +52,8 @@ "REST_API_URL": "__ESPLORA_REST_API_URL__", "UNIX_SOCKET_PATH": "__ESPLORA_UNIX_SOCKET_PATH__", "RETRY_UNIX_SOCKET_AFTER": __ESPLORA_RETRY_UNIX_SOCKET_AFTER__, + "REQUEST_TIMEOUT": __ESPLORA_REQUEST_TIMEOUT__, + "FALLBACK_TIMEOUT": __ESPLORA_FALLBACK_TIMEOUT__, "FALLBACK": __ESPLORA_FALLBACK__ }, "SECOND_CORE_RPC": { @@ -69,7 +71,8 @@ "DATABASE": "__DATABASE_DATABASE__", "USERNAME": "__DATABASE_USERNAME__", "PASSWORD": "__DATABASE_PASSWORD__", - "TIMEOUT": __DATABASE_TIMEOUT__ + "TIMEOUT": __DATABASE_TIMEOUT__, + "PID_DIR": "__DATABASE_PID_DIR__" }, "SYSLOG": { "ENABLED": __SYSLOG_ENABLED__, diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 2e293ce34..3987dbc23 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -53,6 +53,8 @@ __ELECTRUM_TLS_ENABLED__=${ELECTRUM_TLS_ENABLED:=false} __ESPLORA_REST_API_URL__=${ESPLORA_REST_API_URL:=http://127.0.0.1:3000} __ESPLORA_UNIX_SOCKET_PATH__=${ESPLORA_UNIX_SOCKET_PATH:="null"} __ESPLORA_RETRY_UNIX_SOCKET_AFTER__=${ESPLORA_RETRY_UNIX_SOCKET_AFTER:=30000} +__ESPLORA_REQUEST_TIMEOUT__=${ESPLORA_REQUEST_TIMEOUT:=5000} +__ESPLORA_FALLBACK_TIMEOUT__=${ESPLORA_FALLBACK_TIMEOUT:=5000} __ESPLORA_FALLBACK__=${ESPLORA_FALLBACK:=[]} # SECOND_CORE_RPC @@ -71,6 +73,7 @@ __DATABASE_DATABASE__=${DATABASE_DATABASE:=mempool} __DATABASE_USERNAME__=${DATABASE_USERNAME:=mempool} __DATABASE_PASSWORD__=${DATABASE_PASSWORD:=mempool} __DATABASE_TIMEOUT__=${DATABASE_TIMEOUT:=180000} +__DATABASE_PID_DIR__=${DATABASE_PID_DIR:=""} # SYSLOG __SYSLOG_ENABLED__=${SYSLOG_ENABLED:=false} @@ -139,7 +142,7 @@ __MEMPOOL_SERVICES_API__=${MEMPOOL_SERVICES_API:=""} __MEMPOOL_SERVICES_ACCELERATIONS__=${MEMPOOL_SERVICES_ACCELERATIONS:=false} # REDIS -__REDIS_ENABLED__=${REDIS_ENABLED:=true} +__REDIS_ENABLED__=${REDIS_ENABLED:=false} __REDIS_UNIX_SOCKET_PATH__=${REDIS_UNIX_SOCKET_PATH:=true} mkdir -p "${__MEMPOOL_CACHE_DIR__}" @@ -193,6 +196,8 @@ sed -i "s!__ELECTRUM_TLS_ENABLED__!${__ELECTRUM_TLS_ENABLED__}!g" mempool-config 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_RETRY_UNIX_SOCKET_AFTER__!${__ESPLORA_RETRY_UNIX_SOCKET_AFTER__}!g" mempool-config.json +sed -i "s!__ESPLORA_REQUEST_TIMEOUT__!${__ESPLORA_REQUEST_TIMEOUT__}!g" mempool-config.json +sed -i "s!__ESPLORA_FALLBACK_TIMEOUT__!${__ESPLORA_FALLBACK_TIMEOUT__}!g" mempool-config.json sed -i "s!__ESPLORA_FALLBACK__!${__ESPLORA_FALLBACK__}!g" mempool-config.json sed -i "s!__SECOND_CORE_RPC_HOST__!${__SECOND_CORE_RPC_HOST__}!g" mempool-config.json @@ -209,6 +214,7 @@ 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_PID_DIR__!${__DATABASE_PID_DIR__}!g" mempool-config.json sed -i "s!__SYSLOG_ENABLED__!${__SYSLOG_ENABLED__}!g" mempool-config.json sed -i "s!__SYSLOG_HOST__!${__SYSLOG_HOST__}!g" mempool-config.json diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 68e73a1c8..4e1094306 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -38,7 +38,7 @@ services: MYSQL_USER: "mempool" MYSQL_PASSWORD: "mempool" MYSQL_ROOT_PASSWORD: "admin" - image: mariadb:10.5.8 + image: mariadb:10.5.21 user: "1000:1000" restart: on-failure stop_grace_period: 1m diff --git a/docker/electrum/Dockerfile b/docker/electrum/Dockerfile deleted file mode 100644 index b7af48989..000000000 --- a/docker/electrum/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM ubuntu:18.04 -MAINTAINER mempool.space developers -EXPOSE 50002 - -# runs as UID 1000 GID 1000 inside the container - -ENV VERSION 4.0.9 -RUN set -x \ - && apt-get update \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends gpg gpg-agent dirmngr \ - && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends wget xpra python3-pyqt5 python3-wheel python3-pip python3-setuptools libsecp256k1-0 libsecp256k1-dev python3-numpy python3-dev build-essential \ - && wget -O /tmp/Electrum-${VERSION}.tar.gz https://download.electrum.org/${VERSION}/Electrum-${VERSION}.tar.gz \ - && wget -O /tmp/Electrum-${VERSION}.tar.gz.asc https://download.electrum.org/${VERSION}/Electrum-${VERSION}.tar.gz.asc \ - && gpg --keyserver keys.gnupg.net --recv-keys 6694D8DE7BE8EE5631BED9502BD5824B7F9470E6 \ - && gpg --verify /tmp/Electrum-${VERSION}.tar.gz.asc /tmp/Electrum-${VERSION}.tar.gz \ - && pip3 install /tmp/Electrum-${VERSION}.tar.gz \ - && test -f /usr/local/bin/electrum \ - && rm -vrf /tmp/Electrum-${VERSION}.tar.gz /tmp/Electrum-${VERSION}.tar.gz.asc ${HOME}/.gnupg \ - && apt-get purge --autoremove -y python3-wheel python3-pip python3-setuptools python3-dev build-essential libsecp256k1-dev curl gpg gpg-agent dirmngr \ - && apt-get clean && rm -rf /var/lib/apt/lists/* \ - && useradd -d /home/mempool -m mempool \ - && mkdir /electrum \ - && ln -s /electrum /home/mempool/.electrum \ - && chown mempool:mempool /electrum - -USER mempool -ENV HOME /home/mempool -WORKDIR /home/mempool -VOLUME /electrum - -CMD ["/usr/bin/xpra", "start", ":100", "--start-child=/usr/local/bin/electrum", "--bind-tcp=0.0.0.0:50002","--daemon=yes", "--notifications=no", "--mdns=no", "--pulseaudio=no", "--html=off", "--speaker=disabled", "--microphone=disabled", "--webcam=no", "--printing=no", "--dbus-launch=", "--exit-with-children"] -ENTRYPOINT ["electrum"] diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index b54612e3d..4d04ae88f 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.16.0-buster-slim AS builder +FROM node:20.8.0-buster-slim AS builder ARG commitHash ENV DOCKER_COMMIT_HASH=${commitHash} @@ -13,7 +13,7 @@ RUN npm install --omit=dev --omit=optional RUN npm run build -FROM nginx:1.17.8-alpine +FROM nginx:1.24.0-alpine WORKDIR /patch diff --git a/docker/frontend/entrypoint.sh b/docker/frontend/entrypoint.sh index 7d5ee313d..4e14aefac 100644 --- a/docker/frontend/entrypoint.sh +++ b/docker/frontend/entrypoint.sh @@ -39,6 +39,7 @@ __AUDIT__=${AUDIT:=false} __MAINNET_BLOCK_AUDIT_START_HEIGHT__=${MAINNET_BLOCK_AUDIT_START_HEIGHT:=0} __TESTNET_BLOCK_AUDIT_START_HEIGHT__=${TESTNET_BLOCK_AUDIT_START_HEIGHT:=0} __SIGNET_BLOCK_AUDIT_START_HEIGHT__=${SIGNET_BLOCK_AUDIT_START_HEIGHT:=0} +__ACCELERATOR__=${ACCELERATOR:=false} __HISTORICAL_PRICE__=${HISTORICAL_PRICE:=true} # Export as environment variables to be used by envsubst @@ -65,6 +66,7 @@ export __AUDIT__ export __MAINNET_BLOCK_AUDIT_START_HEIGHT__ export __TESTNET_BLOCK_AUDIT_START_HEIGHT__ export __SIGNET_BLOCK_AUDIT_START_HEIGHT__ +export __ACCELERATOR__ export __HISTORICAL_PRICE__ folder=$(find /var/www/mempool -name "config.js" | xargs dirname) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 666bfc33c..49410de87 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -59,9 +59,9 @@ "optionalDependencies": { "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", - "cypress": "^12.17.2", - "cypress-fail-on-console-error": "~4.0.3", - "cypress-wait-until": "^2.0.0", + "cypress": "^13.3.0", + "cypress-fail-on-console-error": "~5.0.0", + "cypress-wait-until": "^2.0.1", "mock-socket": "~9.2.1", "start-server-and-test": "~2.0.0" } @@ -1250,11 +1250,12 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==" }, "node_modules/@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dependencies": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" }, "engines": { "node": ">=6.9.0" @@ -1464,20 +1465,33 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name/node_modules/@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "dependencies": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -1627,9 +1641,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "engines": { "node": ">=6.9.0" } @@ -1669,12 +1683,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, "engines": { @@ -1682,9 +1696,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", - "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2879,18 +2893,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", - "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.7", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/types": "^7.22.5", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2898,13 +2912,36 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/traverse/node_modules/@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "dependencies": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -3016,9 +3053,9 @@ } }, "node_modules/@cypress/request": { - "version": "2.88.11", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz", - "integrity": "sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "optional": true, "dependencies": { "aws-sign2": "~0.7.0", @@ -3034,9 +3071,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.10.3", + "qs": "6.10.4", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" }, @@ -4333,9 +4370,9 @@ "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" }, "node_modules/@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.17.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", + "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==" }, "node_modules/@types/qrcode": { "version": "1.5.0", @@ -5574,9 +5611,9 @@ "optional": true }, "node_modules/bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "node_modules/body-parser": { "version": "1.20.1", @@ -5871,25 +5908,28 @@ } }, "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", + "elliptic": "^6.5.4", "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 4" } }, "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "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", @@ -7113,15 +7153,15 @@ "peer": true }, "node_modules/cypress": { - "version": "12.17.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.2.tgz", - "integrity": "sha512-hxWAaWbqQBzzMuadSGSuQg5PDvIGOovm6xm0hIfpCVcORsCAj/gF2p0EvfnJ4f+jK2PCiDgP6D2eeE9/FK4Mjg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz", + "integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==", "hasInstallScript": true, "optional": true, "dependencies": { - "@cypress/request": "^2.88.11", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", + "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", @@ -7154,6 +7194,7 @@ "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "semver": "^7.5.3", @@ -7166,13 +7207,13 @@ "cypress": "bin/cypress" }, "engines": { - "node": "^14.0.0 || ^16.0.0 || >=18.0.0" + "node": "^16.0.0 || ^18.0.0 || >=20.0.0" } }, "node_modules/cypress-fail-on-console-error": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-4.0.3.tgz", - "integrity": "sha512-v2nPupd2brtxKLkDQX58SbEPWRF/2nDbqPTnYyhPIYHqG7U3P2dGUZ3zraETKKoLhU3+C0otjgB6Vg/bHhocQw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz", + "integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==", "optional": true, "dependencies": { "chai": "^4.3.4", @@ -7182,19 +7223,9 @@ } }, "node_modules/cypress-wait-until": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.0.tgz", - "integrity": "sha512-ulUZyrWBn+OuC8oiQuGKAScDYfpaWnE3dEE/raUo64w4RHQxZrQ/iMIWT4ZjGMMPr3P+BFEALCRnjQeRqzZj6g==", - "optional": true, - "engines": { - "node": ">=18.16.0", - "npm": ">=9.5.1" - } - }, - "node_modules/cypress/node_modules/@types/node": { - "version": "14.18.53", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.53.tgz", - "integrity": "sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.1.tgz", + "integrity": "sha512-+IyVnYNiaX1+C+V/LazrJWAi/CqiwfNoRSrFviECQEyolW1gDRy765PZosL2alSSGK8V10Y7BGfOQyZUDgmnjQ==", "optional": true }, "node_modules/cypress/node_modules/ansi-styles": { @@ -10976,28 +11007,6 @@ "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsdom/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/jsesc": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", @@ -15682,16 +15691,25 @@ } }, "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" }, "engines": { - "node": ">=0.8" + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "engines": { + "node": ">= 4.0.0" } }, "node_modules/tr46": { @@ -17790,11 +17808,12 @@ "integrity": "sha512-H71nDOOL8Y7kWRLqf6Sums+01Q5msqBW2KhDUTemh1tvY04eSkSXrK0uj/4mmY0Xr16/3zyZmsrxN7CKuRbNRg==" }, "@babel/code-frame": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", - "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "requires": { - "@babel/highlight": "^7.22.5" + "@babel/highlight": "^7.22.13", + "chalk": "^2.4.2" } }, "@babel/compat-data": { @@ -17959,17 +17978,29 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "requires": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "dependencies": { + "@babel/template": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", + "requires": { + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" + } + } } }, "@babel/helper-hoist-variables": { @@ -18071,9 +18102,9 @@ "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==" }, "@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==" + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==" }, "@babel/helper-validator-option": { "version": "7.22.5", @@ -18101,19 +18132,19 @@ } }, "@babel/highlight": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", - "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "requires": { - "@babel/helper-validator-identifier": "^7.22.5", - "chalk": "^2.0.0", + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.22.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", - "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==" + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.22.5", @@ -18890,29 +18921,51 @@ } }, "@babel/traverse": { - "version": "7.22.8", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", - "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "requires": { - "@babel/code-frame": "^7.22.5", - "@babel/generator": "^7.22.7", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.7", - "@babel/types": "^7.22.5", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" + }, + "dependencies": { + "@babel/generator": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", + "requires": { + "@babel/types": "^7.23.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@jridgewell/trace-mapping": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz", + "integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==", + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + } } }, "@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "requires": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" } }, @@ -19010,9 +19063,9 @@ } }, "@cypress/request": { - "version": "2.88.11", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.11.tgz", - "integrity": "sha512-M83/wfQ1EkspjkE2lNWNV5ui2Cv7UCv1swW1DqljahbzLVWltcsexQh8jYtuS/vzFXP+HySntGM83ZXA9fn17w==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", + "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "optional": true, "requires": { "aws-sign2": "~0.7.0", @@ -19028,9 +19081,9 @@ "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", - "qs": "~6.10.3", + "qs": "6.10.4", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", + "tough-cookie": "^4.1.3", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" } @@ -19927,9 +19980,9 @@ "integrity": "sha512-Jus9s4CDbqwocc5pOAnh8ShfrnMcPHuJYzVcSUU7lrh8Ni5HuIqX3oilL86p3dlTrk0LzHRCgA/GQ7uNCw6l2Q==" }, "@types/node": { - "version": "18.11.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz", - "integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==" + "version": "18.17.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", + "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==" }, "@types/qrcode": { "version": "1.5.0", @@ -20868,9 +20921,9 @@ "optional": true }, "bn.js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.0.tgz", - "integrity": "sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.1.tgz", + "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==" }, "body-parser": { "version": "1.20.1", @@ -21214,25 +21267,25 @@ } }, "browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.2.tgz", + "integrity": "sha512-1rudGyeYY42Dk6texmv7c4VcQ0EsvVbLwZkA+AQB7SxvXxmcD93jcHie8bzecJ+ChDlmAm2Qyu0+Ccg5uhZXCg==", "requires": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", + "bn.js": "^5.2.1", + "browserify-rsa": "^4.1.0", "create-hash": "^1.2.0", "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", + "elliptic": "^6.5.4", "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" + "parse-asn1": "^5.1.6", + "readable-stream": "^3.6.2", + "safe-buffer": "^5.2.1" }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "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", @@ -22065,14 +22118,14 @@ "peer": true }, "cypress": { - "version": "12.17.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-12.17.2.tgz", - "integrity": "sha512-hxWAaWbqQBzzMuadSGSuQg5PDvIGOovm6xm0hIfpCVcORsCAj/gF2p0EvfnJ4f+jK2PCiDgP6D2eeE9/FK4Mjg==", + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.3.0.tgz", + "integrity": "sha512-mpI8qcTwLGiA4zEQvTC/U1xGUezVV4V8HQCOYjlEOrVmU1etVvxOjkCXHGwrlYdZU/EPmUiWfsO3yt1o+Q2bgw==", "optional": true, "requires": { - "@cypress/request": "^2.88.11", + "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^14.14.31", + "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", @@ -22105,6 +22158,7 @@ "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", + "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "semver": "^7.5.3", @@ -22114,12 +22168,6 @@ "yauzl": "^2.10.0" }, "dependencies": { - "@types/node": { - "version": "14.18.53", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.53.tgz", - "integrity": "sha512-soGmOpVBUq+gaBMwom1M+krC/NNbWlosh4AtGA03SyWNDiqSKtwp7OulO1M6+mg8YkHMvJ/y0AkCeO8d1hNb7A==", - "optional": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -22236,9 +22284,9 @@ } }, "cypress-fail-on-console-error": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-4.0.3.tgz", - "integrity": "sha512-v2nPupd2brtxKLkDQX58SbEPWRF/2nDbqPTnYyhPIYHqG7U3P2dGUZ3zraETKKoLhU3+C0otjgB6Vg/bHhocQw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cypress-fail-on-console-error/-/cypress-fail-on-console-error-5.0.0.tgz", + "integrity": "sha512-xui/aSu8rmExZjZNgId3iX0MsGZih6ZoFH+54vNHrK3HaqIZZX5hUuNhAcmfSoM1rIDc2DeITeVaMn/hiQ9IWQ==", "optional": true, "requires": { "chai": "^4.3.4", @@ -22248,9 +22296,9 @@ } }, "cypress-wait-until": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.0.tgz", - "integrity": "sha512-ulUZyrWBn+OuC8oiQuGKAScDYfpaWnE3dEE/raUo64w4RHQxZrQ/iMIWT4ZjGMMPr3P+BFEALCRnjQeRqzZj6g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-2.0.1.tgz", + "integrity": "sha512-+IyVnYNiaX1+C+V/LazrJWAi/CqiwfNoRSrFviECQEyolW1gDRy765PZosL2alSSGK8V10Y7BGfOQyZUDgmnjQ==", "optional": true }, "d": { @@ -24967,22 +25015,6 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==" - }, - "tough-cookie": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", - "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" } } }, @@ -28497,13 +28529,21 @@ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" }, "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", + "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==" + } } }, "tr46": { diff --git a/frontend/package.json b/frontend/package.json index 2bb2ab2cd..0c7874c30 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,7 +35,7 @@ "start:local-staging": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-staging", "start:mixed": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c mixed", "build": "npm run generate-config && npm run ng -- build --configuration production --localize && npm run sync-assets && npm run build-mempool.js", - "sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources'", + "sync-assets": "rsync -av ./src/resources ./dist/mempool/browser && node sync-assets.js 'dist/mempool/browser/resources/'", "sync-assets-dev": "node sync-assets.js 'src/resources/'", "generate-config": "node generate-config.js", "build-mempool.js": "npm run build-mempool-js && npm run build-mempool-liquid-js && npm run build-mempool-bisq-js", @@ -85,7 +85,6 @@ "clipboard": "^2.0.11", "domino": "^2.1.6", "echarts": "~5.4.3", - "echarts-gl": "^2.0.9", "lightweight-charts": "~3.8.0", "ngx-echarts": "~16.0.0", "ngx-infinite-scroll": "^16.0.0", @@ -111,9 +110,9 @@ "optionalDependencies": { "@cypress/schematic": "^2.5.0", "@types/cypress": "^1.1.3", - "cypress": "^12.17.2", - "cypress-fail-on-console-error": "~4.0.3", - "cypress-wait-until": "^2.0.0", + "cypress": "^13.3.0", + "cypress-fail-on-console-error": "~5.0.0", + "cypress-wait-until": "^2.0.1", "mock-socket": "~9.2.1", "start-server-and-test": "~2.0.0" }, diff --git a/frontend/src/app/app-routing.module.ts b/frontend/src/app/app-routing.module.ts index 79a8e1c02..7c2ac1274 100644 --- a/frontend/src/app/app-routing.module.ts +++ b/frontend/src/app/app-routing.module.ts @@ -4,6 +4,8 @@ import { AppPreloadingStrategy } from './app.preloading-strategy' import { StartComponent } from './components/start/start.component'; import { TransactionComponent } from './components/transaction/transaction.component'; import { BlockComponent } from './components/block/block.component'; +import { BlockViewComponent } from './components/block-view/block-view.component'; +import { MempoolBlockViewComponent } from './components/mempool-block-view/mempool-block-view.component'; import { ClockComponent } from './components/clock/clock.component'; import { AddressComponent } from './components/address/address.component'; import { MasterPageComponent } from './components/master-page/master-page.component'; @@ -373,6 +375,14 @@ let routes: Routes = [ path: 'clock/:mode/:index', component: ClockComponent, }, + { + path: 'view/block/:id', + component: BlockViewComponent, + }, + { + path: 'view/mempool-block/:index', + component: MempoolBlockViewComponent, + }, { path: 'status', data: { networks: ['bitcoin', 'liquid'] }, diff --git a/frontend/src/app/bisq/bisq-block/bisq-block.component.ts b/frontend/src/app/bisq/bisq-block/bisq-block.component.ts index 42f62fbc0..59bb16a9e 100644 --- a/frontend/src/app/bisq/bisq-block/bisq-block.component.ts +++ b/frontend/src/app/bisq/bisq-block/bisq-block.component.ts @@ -69,6 +69,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy { this.location.replaceState( this.router.createUrlTree(['/bisq/block/', hash]).toString() ); + this.seoService.updateCanonical(this.location.path()); return this.bisqApiService.getBlock$(this.blockHash) .pipe(catchError(this.caughtHttpError.bind(this))); }), diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 6bff838aa..ad05b7d71 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -10,7 +10,7 @@
-
The Mempool Open Source Project
+
The Mempool Open Source Project ®

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.

diff --git a/frontend/src/app/components/about/about.component.ts b/frontend/src/app/components/about/about.component.ts index 3cda0dc20..8aa0422e8 100644 --- a/frontend/src/app/components/about/about.component.ts +++ b/frontend/src/app/components/about/about.component.ts @@ -43,7 +43,7 @@ export class AboutComponent implements OnInit { ngOnInit() { this.backendInfo$ = this.stateService.backendInfo$; this.seoService.setTitle($localize`:@@004b222ff9ef9dd4771b777950ca1d0e4cd4348a:About`); - this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project™\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`); + this.seoService.setDescription($localize`:@@meta.description.about:Learn more about The Mempool Open Source Project®\: enterprise sponsors, individual sponsors, integrations, who contributes, FOSS licensing, and more.`); this.websocketService.want(['blocks']); this.profiles$ = this.apiService.getAboutPageProfiles$().pipe( diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts index e9cd0189b..0e10b207f 100644 --- a/frontend/src/app/components/address/address.component.ts +++ b/frontend/src/app/components/address/address.component.ts @@ -174,6 +174,11 @@ export class AddressComponent implements OnInit, OnDestroy { this.addTransaction(tx); }); + this.stateService.mempoolRemovedTransactions$ + .subscribe(tx => { + this.removeTransaction(tx); + }); + this.stateService.blockTransactions$ .subscribe((transaction) => { const tx = this.transactions.find((t) => t.txid === transaction.txid); @@ -222,6 +227,30 @@ export class AddressComponent implements OnInit, OnDestroy { return true; } + removeTransaction(transaction: Transaction): boolean { + const index = this.transactions.findIndex(((tx) => tx.txid === transaction.txid)); + if (index === -1) { + return false; + } + + this.transactions.splice(index, 1); + this.transactions = this.transactions.slice(); + this.txCount--; + + transaction.vin.forEach((vin) => { + if (vin?.prevout?.scriptpubkey_address === this.address.address) { + this.sent -= vin.prevout.value; + } + }); + transaction.vout.forEach((vout) => { + if (vout?.scriptpubkey_address === this.address.address) { + this.received -= vout.value; + } + }); + + return true; + } + loadMore() { if (this.isLoadingTransactions || !this.totalConfirmedTxCount || this.loadedConfirmedTxCount >= this.totalConfirmedTxCount) { return; diff --git a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts index b4c4e9a3b..c4d061927 100644 --- a/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts +++ b/frontend/src/app/components/block-fee-rates-graph/block-fee-rates-graph.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; import { Observable, Subscription, combineLatest } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from '../../services/api.service'; diff --git a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts index 722929f9e..fc71fb575 100644 --- a/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts +++ b/frontend/src/app/components/block-fees-graph/block-fees-graph.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; -import { EChartsOption, graphic } from 'echarts'; +import { echarts, EChartsOption } from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from '../../services/api.service'; @@ -123,11 +123,11 @@ export class BlockFeesGraphComponent implements OnInit { this.chartOptions = { title: title, color: [ - new graphic.LinearGradient(0, 0, 0, 1, [ + new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#FDD835' }, { offset: 1, color: '#FB8C00' }, ]), - new graphic.LinearGradient(0, 0, 0, 1, [ + new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#C0CA33' }, { offset: 1, color: '#1B5E20' }, ]), diff --git a/frontend/src/app/components/block-health-graph/block-health-graph.component.ts b/frontend/src/app/components/block-health-graph/block-health-graph.component.ts index 299044dbb..4fea6f245 100644 --- a/frontend/src/app/components/block-health-graph/block-health-graph.component.ts +++ b/frontend/src/app/components/block-health-graph/block-health-graph.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnInit } from '@angular/core'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from '../../services/api.service'; diff --git a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts index 505da17a5..9c987fb57 100644 --- a/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts +++ b/frontend/src/app/components/block-rewards-graph/block-rewards-graph.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; -import { EChartsOption, graphic } from 'echarts'; +import { echarts, EChartsOption } from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from '../../services/api.service'; @@ -123,11 +123,11 @@ export class BlockRewardsGraphComponent implements OnInit { title: title, animation: false, color: [ - new graphic.LinearGradient(0, 0, 0, 1, [ + new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#FDD835' }, { offset: 1, color: '#FB8C00' }, ]), - new graphic.LinearGradient(0, 0, 0, 1, [ + new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: '#C0CA33' }, { offset: 1, color: '#1B5E20' }, ]), diff --git a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts index e42c6a8df..5d01e48e9 100644 --- a/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts +++ b/frontend/src/app/components/block-sizes-weights-graph/block-sizes-weights-graph.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; -import { EChartsOption} from 'echarts'; +import { EChartsOption} from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from '../../services/api.service'; diff --git a/frontend/src/app/components/block-view/block-view.component.html b/frontend/src/app/components/block-view/block-view.component.html new file mode 100644 index 000000000..9a2ddf373 --- /dev/null +++ b/frontend/src/app/components/block-view/block-view.component.html @@ -0,0 +1,14 @@ +
+
+ +
+
diff --git a/frontend/src/app/components/block-view/block-view.component.scss b/frontend/src/app/components/block-view/block-view.component.scss new file mode 100644 index 000000000..782d416d8 --- /dev/null +++ b/frontend/src/app/components/block-view/block-view.component.scss @@ -0,0 +1,22 @@ +.block-wrapper { + width: 100vw; + height: 100vh; + background: #181b2d; +} + +.block-container { + flex-grow: 0; + flex-shrink: 0; + width: 100vw; + max-width: 100vh; + height: 100vh; + padding: 0; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + + * { + flex-grow: 1; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/block-view/block-view.component.ts b/frontend/src/app/components/block-view/block-view.component.ts new file mode 100644 index 000000000..5c3b7719c --- /dev/null +++ b/frontend/src/app/components/block-view/block-view.component.ts @@ -0,0 +1,180 @@ +import { Component, OnInit, OnDestroy, ViewChild, HostListener } from '@angular/core'; +import { ActivatedRoute, ParamMap, Router } from '@angular/router'; +import { ElectrsApiService } from '../../services/electrs-api.service'; +import { switchMap, tap, catchError, shareReplay, filter } from 'rxjs/operators'; +import { of, Subscription } from 'rxjs'; +import { StateService } from '../../services/state.service'; +import { SeoService } from '../../services/seo.service'; +import { BlockExtended, TransactionStripped } from '../../interfaces/node-api.interface'; +import { ApiService } from '../../services/api.service'; +import { seoDescriptionNetwork } from '../../shared/common.utils'; +import { BlockOverviewGraphComponent } from '../block-overview-graph/block-overview-graph.component'; +import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; + +function bestFitResolution(min, max, n): number { + const target = (min + max) / 2; + let bestScore = Infinity; + let best = null; + for (let i = min; i <= max; i++) { + const remainder = (n % i); + if (remainder < bestScore || (remainder === bestScore && (Math.abs(i - target) < Math.abs(best - target)))) { + bestScore = remainder; + best = i; + } + } + return best; +} + +@Component({ + selector: 'app-block-view', + templateUrl: './block-view.component.html', + styleUrls: ['./block-view.component.scss'] +}) +export class BlockViewComponent implements OnInit, OnDestroy { + network = ''; + block: BlockExtended; + blockHeight: number; + blockHash: string; + rawId: string; + isLoadingBlock = true; + strippedTransactions: TransactionStripped[]; + isLoadingOverview = true; + autofit: boolean = false; + resolution: number = 80; + + overviewSubscription: Subscription; + networkChangedSubscription: Subscription; + queryParamsSubscription: Subscription; + + @ViewChild('blockGraph') blockGraph: BlockOverviewGraphComponent; + + constructor( + private route: ActivatedRoute, + private router: Router, + private electrsApiService: ElectrsApiService, + public stateService: StateService, + private seoService: SeoService, + private apiService: ApiService + ) { } + + ngOnInit(): void { + this.network = this.stateService.network; + + this.queryParamsSubscription = this.route.queryParams.subscribe((params) => { + this.autofit = params.autofit === 'true'; + if (this.autofit) { + this.onResize(); + } + }); + + const block$ = this.route.paramMap.pipe( + switchMap((params: ParamMap) => { + this.rawId = params.get('id') || ''; + + const blockHash: string = params.get('id') || ''; + this.block = undefined; + + let isBlockHeight = false; + if (/^[0-9]+$/.test(blockHash)) { + isBlockHeight = true; + } else { + this.blockHash = blockHash; + } + + this.isLoadingBlock = true; + this.isLoadingOverview = true; + + if (isBlockHeight) { + return this.electrsApiService.getBlockHashFromHeight$(parseInt(blockHash, 10)) + .pipe( + switchMap((hash) => { + if (hash) { + this.blockHash = hash; + return this.apiService.getBlock$(hash); + } else { + return null; + } + }), + catchError(() => { + return of(null); + }), + ); + } + return this.apiService.getBlock$(blockHash); + }), + filter((block: BlockExtended | void) => block != null), + tap((block: BlockExtended) => { + this.block = block; + this.blockHeight = block.height; + + this.seoService.setTitle($localize`:@@block.component.browser-title:Block ${block.height}:BLOCK_HEIGHT:: ${block.id}:BLOCK_ID:`); + if( this.stateService.network === 'liquid' || this.stateService.network === 'liquidtestnet' ) { + this.seoService.setDescription($localize`:@@meta.description.liquid.block:See size, weight, fee range, included transactions, and more for Liquid${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`); + } else { + this.seoService.setDescription($localize`:@@meta.description.bitcoin.block:See size, weight, fee range, included transactions, audit (expected v actual), and more for Bitcoin${seoDescriptionNetwork(this.stateService.network)} block ${block.height}:BLOCK_HEIGHT: (${block.id}:BLOCK_ID:).`); + } + this.isLoadingBlock = false; + this.isLoadingOverview = true; + }), + shareReplay(1) + ); + + this.overviewSubscription = block$.pipe( + switchMap((block) => this.apiService.getStrippedBlockTransactions$(block.id) + .pipe( + catchError(() => { + return of([]); + }), + switchMap((transactions) => { + return of(transactions); + }) + ) + ), + ) + .subscribe((transactions: TransactionStripped[]) => { + this.strippedTransactions = transactions; + this.isLoadingOverview = false; + if (this.blockGraph) { + this.blockGraph.destroy(); + this.blockGraph.setup(this.strippedTransactions); + } + }, + () => { + this.isLoadingOverview = false; + if (this.blockGraph) { + this.blockGraph.destroy(); + } + }); + + this.networkChangedSubscription = this.stateService.networkChanged$ + .subscribe((network) => this.network = network); + } + + onTxClick(event: { tx: TransactionStripped, keyModifier: boolean }): void { + const url = new RelativeUrlPipe(this.stateService).transform(`/tx/${event.tx.txid}`); + if (!event.keyModifier) { + this.router.navigate([url]); + } else { + window.open(url, '_blank'); + } + } + + @HostListener('window:resize', ['$event']) + onResize(): void { + if (this.autofit) { + this.resolution = bestFitResolution(64, 96, Math.min(window.innerWidth, window.innerHeight)); + } + } + + ngOnDestroy(): void { + if (this.overviewSubscription) { + this.overviewSubscription.unsubscribe(); + } + if (this.networkChangedSubscription) { + this.networkChangedSubscription.unsubscribe(); + } + if (this.queryParamsSubscription) { + this.queryParamsSubscription.unsubscribe(); + } + } +} diff --git a/frontend/src/app/components/block/block.component.ts b/frontend/src/app/components/block/block.component.ts index e226807e3..bb83494c5 100644 --- a/frontend/src/app/components/block/block.component.ts +++ b/frontend/src/app/components/block/block.component.ts @@ -166,7 +166,6 @@ export class BlockComponent implements OnInit, OnDestroy { this.page = 1; this.error = undefined; this.fees = undefined; - this.stateService.markBlock$.next({}); if (history.state.data && history.state.data.blockHeight) { this.blockHeight = history.state.data.blockHeight; @@ -176,6 +175,7 @@ export class BlockComponent implements OnInit, OnDestroy { let isBlockHeight = false; if (/^[0-9]+$/.test(blockHash)) { isBlockHeight = true; + this.stateService.markBlock$.next({ blockHeight: parseInt(blockHash, 10)}); } else { this.blockHash = blockHash; } @@ -202,6 +202,7 @@ export class BlockComponent implements OnInit, OnDestroy { this.location.replaceState( this.router.createUrlTree([(this.network ? '/' + this.network : '') + '/block/', hash]).toString() ); + this.seoService.updateCanonical(this.location.path()); return this.apiService.getBlock$(hash).pipe( catchError((err) => { this.error = err; diff --git a/frontend/src/app/components/blockchain/blockchain.component.html b/frontend/src/app/components/blockchain/blockchain.component.html index 2c3f1ad8c..5f625e4b3 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.html +++ b/frontend/src/app/components/blockchain/blockchain.component.html @@ -1,5 +1,5 @@
-
+
diff --git a/frontend/src/app/components/blockchain/blockchain.component.scss b/frontend/src/app/components/blockchain/blockchain.component.scss index 135a8b842..eacd16118 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.scss +++ b/frontend/src/app/components/blockchain/blockchain.component.scss @@ -26,15 +26,7 @@ position: absolute; left: 0; top: 75px; - --divider-offset: 50vw; - --mempool-offset: 0px; - transform: translateX(calc(var(--divider-offset) + var(--mempool-offset))); -} - -.blockchain-wrapper.time-ltr { - .position-container { - transform: translateX(calc(100vw - var(--divider-offset) - var(--mempool-offset))); - } + transform: translateX(1280px); } .black-background { diff --git a/frontend/src/app/components/blockchain/blockchain.component.ts b/frontend/src/app/components/blockchain/blockchain.component.ts index 56b7c39e6..2293b9479 100644 --- a/frontend/src/app/components/blockchain/blockchain.component.ts +++ b/frontend/src/app/components/blockchain/blockchain.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, HostListener, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core'; +import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, Output, EventEmitter, ChangeDetectorRef, OnChanges, SimpleChanges } from '@angular/core'; import { firstValueFrom, Subscription } from 'rxjs'; import { StateService } from '../../services/state.service'; @@ -27,8 +27,11 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { loadingTip: boolean = true; connected: boolean = true; - dividerOffset: number = 0; - mempoolOffset: number = 0; + dividerOffset: number | null = null; + mempoolOffset: number | null = null; + positionStyle = { + transform: "translateX(1280px)", + }; constructor( public stateService: StateService, @@ -40,6 +43,7 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { this.network = this.stateService.network; this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { this.timeLtr = !!ltr; + this.updateStyle(); }); this.connectionStateSubscription = this.stateService.connectionState$.subscribe(state => { this.connected = (state === 2); @@ -63,29 +67,47 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { const prevOffset = this.mempoolOffset; this.mempoolOffset = 0; this.mempoolOffsetChange.emit(0); + this.updateStyle(); setTimeout(() => { this.ltrTransitionEnabled = true; this.flipping = true; this.stateService.timeLtr.next(!this.timeLtr); + this.cd.markForCheck(); setTimeout(() => { this.ltrTransitionEnabled = false; this.flipping = false; this.mempoolOffset = prevOffset; - this.mempoolOffsetChange.emit(this.mempoolOffset); + this.mempoolOffsetChange.emit((this.mempoolOffset || 0)); + this.updateStyle(); + this.cd.markForCheck(); }, 1000); }, 0); - this.cd.markForCheck(); } onMempoolWidthChange(width): void { if (this.flipping) { return; } - this.mempoolOffset = Math.max(0, width - this.dividerOffset); - this.cd.markForCheck(); + this.mempoolOffset = Math.max(0, width - (this.dividerOffset || 0)); + this.updateStyle(); this.mempoolOffsetChange.emit(this.mempoolOffset); } + updateStyle(): void { + if (this.dividerOffset == null || this.mempoolOffset == null) { + return; + } + const oldTransform = this.positionStyle.transform; + this.positionStyle = this.timeLtr ? { + transform: `translateX(calc(100vw - ${this.dividerOffset + this.mempoolOffset}px)`, + } : { + transform: `translateX(${this.dividerOffset + this.mempoolOffset}px)`, + }; + if (oldTransform !== this.positionStyle.transform) { + this.cd.detectChanges(); + } + } + ngOnChanges(changes: SimpleChanges): void { if (changes.containerWidth) { this.onResize(); @@ -107,6 +129,6 @@ export class BlockchainComponent implements OnInit, OnDestroy, OnChanges { this.dividerOffset = width * 0.95; } } - this.cd.markForCheck(); + this.updateStyle(); } } diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.html b/frontend/src/app/components/blocks-list/blocks-list.component.html index 39fbb95e0..85e2ea17f 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.html +++ b/frontend/src/app/components/blocks-list/blocks-list.component.html @@ -1,6 +1,6 @@ -
+

Blocks

@@ -9,28 +9,28 @@
- - + - - + - - - - + + - - + + - - - - - - - - - - - - - - - - - - diff --git a/frontend/src/app/components/blocks-list/blocks-list.component.ts b/frontend/src/app/components/blocks-list/blocks-list.component.ts index aa361d9dc..fb57519a9 100644 --- a/frontend/src/app/components/blocks-list/blocks-list.component.ts +++ b/frontend/src/app/components/blocks-list/blocks-list.component.ts @@ -19,6 +19,7 @@ export class BlocksList implements OnInit { blocks$: Observable = undefined; + isMempoolModule = false; indexingAvailable = false; auditAvailable = false; isLoading = true; @@ -39,6 +40,7 @@ export class BlocksList implements OnInit { private cd: ChangeDetectorRef, private seoService: SeoService, ) { + this.isMempoolModule = this.stateService.env.BASE_MODULE === 'mempool'; } ngOnInit(): void { @@ -75,11 +77,10 @@ export class BlocksList implements OnInit { this.lastBlockHeight = Math.max(...blocks.map(o => o.height)); }), map(blocks => { - if (this.indexingAvailable) { + if (this.stateService.env.BASE_MODULE === 'mempool') { for (const block of blocks) { // @ts-ignore: Need to add an extra field for the template - block.extras.pool.logo = `/resources/mining-pools/` + - block.extras.pool.slug + '.svg'; + block.extras.pool.logo = `/resources/mining-pools/` + block.extras.pool.slug + '.svg'; } } if (this.widget) { @@ -110,7 +111,7 @@ export class BlocksList implements OnInit { } if (blocks[1]) { this.blocksCount = Math.max(this.blocksCount, blocks[1][0].height) + 1; - if (this.stateService.env.MINING_DASHBOARD) { + if (this.isMempoolModule) { // @ts-ignore: Need to add an extra field for the template blocks[1][0].extras.pool.logo = `/resources/mining-pools/` + blocks[1][0].extras.pool.slug + '.svg'; @@ -121,9 +122,11 @@ export class BlocksList implements OnInit { return acc; }, []), switchMap((blocks) => { - blocks.forEach(block => { - block.extras.feeDelta = block.extras.expectedFees ? (block.extras.totalFees - block.extras.expectedFees) / block.extras.expectedFees : 0; - }); + if (this.isMempoolModule && this.auditAvailable) { + blocks.forEach(block => { + block.extras.feeDelta = block.extras.expectedFees ? (block.extras.totalFees - block.extras.expectedFees) / block.extras.expectedFees : 0; + }); + } return of(blocks); }) ); diff --git a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts index 592aba60b..5f17938ae 100644 --- a/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts +++ b/frontend/src/app/components/hashrate-chart/hashrate-chart.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; -import { EChartsOption, graphic } from 'echarts'; +import { echarts, EChartsOption } from '../../graphs/echarts'; import { merge, Observable, of } from 'rxjs'; import { map, mergeMap, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from '../../services/api.service'; @@ -204,7 +204,7 @@ export class HashrateChartComponent implements OnInit { title: title, animation: false, color: [ - new graphic.LinearGradient(0, 0, 0, 0.65, [ + new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [ { offset: 0, color: '#F4511E99' }, { offset: 0.25, color: '#FB8C0099' }, { offset: 0.5, color: '#FFB30099' }, @@ -212,7 +212,7 @@ export class HashrateChartComponent implements OnInit { { offset: 1, color: '#7CB34299' } ]), '#D81B60', - new graphic.LinearGradient(0, 0, 0, 0.65, [ + new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [ { offset: 0, color: '#F4511E' }, { offset: 0.25, color: '#FB8C00' }, { offset: 0.5, color: '#FFB300' }, @@ -342,7 +342,7 @@ export class HashrateChartComponent implements OnInit { type: 'value', axisLabel: { color: 'rgb(110, 112, 121)', - formatter: (val) => { + formatter: (val): string => { const selectedPowerOfTen: any = selectPowerOfTen(val); const newVal = Math.round(val / selectedPowerOfTen.divider); return `${newVal} ${selectedPowerOfTen.unit}H/s`; @@ -364,9 +364,9 @@ export class HashrateChartComponent implements OnInit { position: 'right', axisLabel: { color: 'rgb(110, 112, 121)', - formatter: (val) => { + formatter: (val): string => { if (this.stateService.network === 'signet') { - return val; + return `${val}`; } const selectedPowerOfTen: any = selectPowerOfTen(val); const newVal = Math.round(val / selectedPowerOfTen.divider); diff --git a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts index e7e3685d3..b0557ca7c 100644 --- a/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts +++ b/frontend/src/app/components/hashrates-chart-pools/hashrate-chart-pools.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { delay, map, retryWhen, share, startWith, switchMap, tap } from 'rxjs/operators'; import { ApiService } from '../../services/api.service'; diff --git a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts index 219811e9c..1b68a5a99 100644 --- a/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts +++ b/frontend/src/app/components/incoming-transactions-graph/incoming-transactions-graph.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnInit, OnDestroy } from '@angular/core'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; import { OnChanges } from '@angular/core'; import { StorageService } from '../../services/storage.service'; import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/graphs.utils'; @@ -37,6 +37,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On }; windowPreference: string; chartInstance: any = undefined; + MA: number[][] = []; weightMode: boolean = false; rateUnitSub: Subscription; @@ -62,6 +63,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On return; } this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference'); + this.MA = this.calculateMA(this.data.series[0]); this.mountChart(); } @@ -72,7 +74,101 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On this.isLoading = false; } + /// calculate the moving average of maData + calculateMA(maData): number[][] { + //update const variables that are not changed + const ma: number[][] = []; + let sum = 0; + let i = 0; + const len = maData.length; + + //Adjust window length based on the length of the data + //5% appeared as a good amount from tests + //TODO: make this a text box in the UI + const maWindowLen = Math.ceil(len * 0.05); + + //calculate the center of the moving average window + const center = Math.floor(maWindowLen / 2); + + //calculate the centered moving average + for (i = center; i < len - center; i++) { + sum = 0; + //build out ma as we loop through the data + ma[i] = []; + ma[i].push(maData[i][0]); + for (let j = i - center; j <= i + center; j++) { + sum += maData[j][1]; + } + + ma[i].push(sum / maWindowLen); + } + + //return the moving average array + return ma; + } + mountChart(): void { + //create an array for the echart series + //similar to how it is done in mempool-graph.component.ts + const seriesGraph = []; + seriesGraph.push({ + zlevel: 0, + name: 'data', + data: this.data.series[0], + type: 'line', + smooth: false, + showSymbol: false, + symbol: 'none', + lineStyle: { + width: 3, + }, + markLine: { + silent: true, + symbol: 'none', + lineStyle: { + color: '#fff', + opacity: 1, + width: 2, + }, + data: [{ + yAxis: 1667, + label: { + show: false, + color: '#ffffff', + } + }], + } + }, + { + zlevel: 0, + name: 'MA', + data: this.MA, + type: 'line', + smooth: false, + showSymbol: false, + symbol: 'none', + lineStyle: { + width: 1, + color: "white", + }, + markLine: { + silent: true, + symbol: 'none', + lineStyle: { + color: '#fff', + opacity: 1, + width: 2, + }, + data: [{ + yAxis: 1667, + label: { + show: false, + color: '#ffffff', + } + }], + } + }); + this.mempoolStatsChartOption = { grid: { height: this.height, @@ -122,16 +218,20 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On type: 'line', }, formatter: (params: any) => { - const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue); + const axisValueLabel: string = formatterXAxis(this.locale, this.windowPreference, params[0].axisValue); const colorSpan = (color: string) => ``; let itemFormatted = '
' + axisValueLabel + '
'; params.map((item: any, index: number) => { - if (index < 26) { - itemFormatted += `
-
${colorSpan(item.color)}
-
-
${formatNumber(this.weightMode ? item.value[1] * 4 : item.value[1], this.locale, '1.0-0')} ${this.weightMode ? 'WU' : 'vB'}/s
-
`; + + //Do no include MA in tooltip legend! + if (item.seriesName !== 'MA') { + if (index < 26) { + itemFormatted += `
+
${colorSpan(item.color)}
+
+
${formatNumber(item.value[1], this.locale, '1.0-0')}vB/s
+
`; + } } }); return `
${itemFormatted}
`; @@ -171,35 +271,7 @@ export class IncomingTransactionsGraphComponent implements OnInit, OnChanges, On } } }, - series: [ - { - zlevel: 0, - data: this.data.series[0], - type: 'line', - smooth: false, - showSymbol: false, - symbol: 'none', - lineStyle: { - width: 3, - }, - markLine: { - silent: true, - symbol: 'none', - lineStyle: { - color: '#fff', - opacity: 1, - width: 2, - }, - data: [{ - yAxis: 1667, - label: { - show: false, - color: '#ffffff', - } - }], - } - }, - ], + series: seriesGraph, visualMap: { show: false, top: 50, diff --git a/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.ts b/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.ts index e4aa95492..c4e8cbf91 100644 --- a/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.ts +++ b/frontend/src/app/components/lbtc-pegs-graph/lbtc-pegs-graph.component.ts @@ -1,6 +1,6 @@ import { Component, Inject, LOCALE_ID, ChangeDetectionStrategy, Input, OnChanges, OnInit } from '@angular/core'; import { formatDate, formatNumber } from '@angular/common'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; @Component({ selector: 'app-lbtc-pegs-graph', diff --git a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html new file mode 100644 index 000000000..9d51ff4e9 --- /dev/null +++ b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.html @@ -0,0 +1,5 @@ +
+
+ +
+
\ No newline at end of file diff --git a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.scss b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.scss new file mode 100644 index 000000000..782d416d8 --- /dev/null +++ b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.scss @@ -0,0 +1,22 @@ +.block-wrapper { + width: 100vw; + height: 100vh; + background: #181b2d; +} + +.block-container { + flex-grow: 0; + flex-shrink: 0; + width: 100vw; + max-width: 100vh; + height: 100vh; + padding: 0; + margin: auto; + display: flex; + justify-content: center; + align-items: center; + + * { + flex-grow: 1; + } +} \ No newline at end of file diff --git a/frontend/src/app/components/mempool-block-view/mempool-block-view.component.ts b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.ts new file mode 100644 index 000000000..ebeb0801c --- /dev/null +++ b/frontend/src/app/components/mempool-block-view/mempool-block-view.component.ts @@ -0,0 +1,85 @@ +import { Component, OnInit, OnDestroy, HostListener } from '@angular/core'; +import { ActivatedRoute, ParamMap } from '@angular/router'; +import { Subscription, filter, map, switchMap, tap } from 'rxjs'; +import { StateService } from '../../services/state.service'; +import { WebsocketService } from '../../services/websocket.service'; + +function bestFitResolution(min, max, n): number { + const target = (min + max) / 2; + let bestScore = Infinity; + let best = null; + for (let i = min; i <= max; i++) { + const remainder = (n % i); + if (remainder < bestScore || (remainder === bestScore && (Math.abs(i - target) < Math.abs(best - target)))) { + bestScore = remainder; + best = i; + } + } + return best; +} + +@Component({ + selector: 'app-mempool-block-view', + templateUrl: './mempool-block-view.component.html', + styleUrls: ['./mempool-block-view.component.scss'] +}) +export class MempoolBlockViewComponent implements OnInit, OnDestroy { + autofit: boolean = false; + resolution: number = 80; + index: number = 0; + + routeParamsSubscription: Subscription; + queryParamsSubscription: Subscription; + + constructor( + private route: ActivatedRoute, + private websocketService: WebsocketService, + public stateService: StateService, + ) { } + + ngOnInit(): void { + this.websocketService.want(['blocks', 'mempool-blocks']); + + this.routeParamsSubscription = this.route.paramMap + .pipe( + switchMap((params: ParamMap) => { + this.index = parseInt(params.get('index'), 10) || 0; + return this.stateService.mempoolBlocks$ + .pipe( + map((blocks) => { + if (!blocks.length) { + return [{ index: 0, blockSize: 0, blockVSize: 0, feeRange: [0, 0], medianFee: 0, nTx: 0, totalFees: 0 }]; + } + return blocks; + }), + filter((mempoolBlocks) => mempoolBlocks.length > 0), + tap((mempoolBlocks) => { + while (!mempoolBlocks[this.index]) { + this.index--; + } + }) + ); + }) + ).subscribe(); + + this.queryParamsSubscription = this.route.queryParams.subscribe((params) => { + this.autofit = params.autofit === 'true'; + if (this.autofit) { + this.onResize(); + } + }); + } + + + @HostListener('window:resize', ['$event']) + onResize(): void { + if (this.autofit) { + this.resolution = bestFitResolution(64, 96, Math.min(window.innerWidth, window.innerHeight)); + } + } + + ngOnDestroy(): void { + this.routeParamsSubscription.unsubscribe(); + this.queryParamsSubscription.unsubscribe(); + } +} diff --git a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts index 484389cd3..0ddbbd4b7 100644 --- a/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts +++ b/frontend/src/app/components/mempool-blocks/mempool-blocks.component.ts @@ -97,6 +97,10 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { ngOnInit() { this.chainTip = this.stateService.latestBlockHeight; + const width = this.containerOffset + (this.stateService.env.MEMPOOL_BLOCKS_AMOUNT) * this.blockOffset; + this.mempoolWidth = width; + this.widthChange.emit(this.mempoolWidth); + if (['', 'testnet', 'signet'].includes(this.stateService.network)) { this.enabledMiningInfoIfNeeded(this.location.path()); this.location.onUrlChange((url) => this.enabledMiningInfoIfNeeded(url)); @@ -161,11 +165,11 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { return this.mempoolBlocks; }), tap(() => { - this.cd.markForCheck(); const width = this.containerOffset + this.mempoolBlocks.length * this.blockOffset; if (this.mempoolWidth !== width) { this.mempoolWidth = width; this.widthChange.emit(this.mempoolWidth); + this.cd.markForCheck(); } }) ); @@ -215,11 +219,13 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { if (isNewBlock && (block?.extras?.similarity == null || block?.extras?.similarity > 0.5) && !this.tabHidden) { this.blockIndex++; } + this.cd.markForCheck(); }); this.chainTipSubscription = this.stateService.chainTip$.subscribe((height) => { if (this.chainTip === -1) { this.chainTip = height; + this.cd.markForCheck(); } }); @@ -257,6 +263,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { this.blockPadding = 0.24 * this.blockWidth; this.containerOffset = 0.32 * this.blockWidth; this.blockOffset = this.blockWidth + this.blockPadding; + this.cd.markForCheck(); } } @@ -275,6 +282,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { onResize(): void { this.animateEntry = false; this.reduceEmptyBlocksToFitScreen(this.mempoolEmptyBlocks); + this.cd.markForCheck(); } trackByFn(index: number, block: MempoolBlock) { diff --git a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts index 6c9795c89..435711e48 100644 --- a/frontend/src/app/components/mempool-graph/mempool-graph.component.ts +++ b/frontend/src/app/components/mempool-graph/mempool-graph.component.ts @@ -1,11 +1,12 @@ import { Component, OnInit, Input, Inject, LOCALE_ID, ChangeDetectionStrategy, OnChanges } from '@angular/core'; import { VbytesPipe } from '../../shared/pipes/bytes-pipe/vbytes.pipe'; import { WuBytesPipe } from '../../shared/pipes/bytes-pipe/wubytes.pipe'; +import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe'; import { formatNumber } from '@angular/common'; import { OptimizedMempoolStats } from '../../interfaces/node-api.interface'; import { StateService } from '../../services/state.service'; import { StorageService } from '../../services/storage.service'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; import { feeLevels, chartColors } from '../../app.constants'; import { download, formatterXAxis, formatterXAxisLabel } from '../../shared/graphs.utils'; @@ -26,6 +27,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { @Input() data: any[]; @Input() filterSize = 100000; @Input() limitFilterFee = 1; + @Input() hideCount: boolean = true; @Input() height: number | string = 200; @Input() top: number | string = 20; @Input() right: number | string = 10; @@ -50,10 +52,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges { inverted: boolean; chartInstance: any = undefined; weightMode: boolean = false; + isWidget: boolean = false; + showCount: boolean = false; constructor( private vbytesPipe: VbytesPipe, private wubytesPipe: WuBytesPipe, + private amountShortenerPipe: AmountShortenerPipe, private stateService: StateService, private storageService: StorageService, @Inject(LOCALE_ID) private locale: string, @@ -62,12 +67,16 @@ export class MempoolGraphComponent implements OnInit, OnChanges { ngOnInit(): void { this.isLoading = true; this.inverted = this.storageService.getValue('inverted-graph') === 'true'; + this.isWidget = this.template === 'widget'; + this.showCount = !this.isWidget && !this.hideCount; } - ngOnChanges() { + ngOnChanges(changes) { if (!this.data) { return; } + this.isWidget = this.template === 'widget'; + this.showCount = !this.isWidget && !this.hideCount; this.windowPreference = this.windowPreferenceOverride ? this.windowPreferenceOverride : this.storageService.getValue('graphWindowPreference'); this.mempoolVsizeFeesData = this.handleNewMempoolData(this.data.concat([])); this.mountFeeChart(); @@ -96,10 +105,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges { mempoolStats.reverse(); const labels = mempoolStats.map(stats => stats.added); const finalArrayVByte = this.generateArray(mempoolStats); + const finalArrayCount = this.generateCountArray(mempoolStats); return { labels: labels, - series: finalArrayVByte + series: finalArrayVByte, + countSeries: finalArrayCount, }; } @@ -124,9 +135,13 @@ export class MempoolGraphComponent implements OnInit, OnChanges { return finalArray; } + generateCountArray(mempoolStats: OptimizedMempoolStats[]) { + return mempoolStats.filter(stats => stats.count > 0).map(stats => [stats.added * 1000, stats.count]); + } + mountFeeChart() { this.orderLevels(); - const { series } = this.mempoolVsizeFeesData; + const { series, countSeries } = this.mempoolVsizeFeesData; const seriesGraph = []; const newColors = []; @@ -178,6 +193,29 @@ export class MempoolGraphComponent implements OnInit, OnChanges { }); } } + if (this.showCount) { + newColors.push('white'); + seriesGraph.push({ + zlevel: 1, + yAxisIndex: 1, + name: 'count', + type: 'line', + stack: 'count', + smooth: false, + markPoint: false, + lineStyle: { + width: 2, + opacity: 1, + }, + symbol: 'none', + silent: true, + areaStyle: { + color: null, + opacity: 0, + }, + data: countSeries, + }); + } this.mempoolVsizeFeesOptions = { series: this.inverted ? [...seriesGraph].reverse() : seriesGraph, @@ -201,7 +239,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges { label: { formatter: (params: any) => { if (params.axisDimension === 'y') { - return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true) + if (params.axisIndex === 0) { + return this.vbytesPipe.transform(params.value, 2, 'vB', 'MvB', true); + } else { + return this.amountShortenerPipe.transform(params.value, 2, undefined, true); + } } else { return formatterXAxis(this.locale, this.windowPreference, params.value); } @@ -214,7 +256,11 @@ export class MempoolGraphComponent implements OnInit, OnChanges { const itemFormatted = []; let totalParcial = 0; let progressPercentageText = ''; - const items = this.inverted ? [...params].reverse() : params; + let countItem; + let items = this.inverted ? [...params].reverse() : params; + if (items[items.length - 1].seriesName === 'count') { + countItem = items.pop(); + } items.map((item: any, index: number) => { totalParcial += item.value[1]; const progressPercentage = (item.value[1] / totalValue) * 100; @@ -276,6 +322,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { `); }); const classActive = (this.template === 'advanced') ? 'fees-wrapper-tooltip-chart-advanced' : ''; + const titleCount = $localize`Count`; const titleRange = $localize`Range`; const titleSize = $localize`:@@7faaaa08f56427999f3be41df1093ce4089bbd75:Size`; const titleSum = $localize`Sum`; @@ -286,6 +333,25 @@ export class MempoolGraphComponent implements OnInit, OnChanges { ${this.vbytesPipe.transform(totalValue, 2, 'vB', 'MvB', false)} + ` + + (this.showCount && countItem ? ` +
HeightHeightPoolTimestampTimestampHealthRewardFeesFeesTXsTransactionsSizeTransactionsSize
{{ block.height }} -
+
+
@@ -38,11 +38,17 @@ {{ block.extras.coinbaseRaw | hex2ascii }}
+
+ + {{ block.extras.pool.name }} + {{ block.extras.coinbaseRaw | hex2ascii }} +
+ ‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm' }} + Unknown + + + {{ block.extras.feeDelta > 0 ? '+' : '' }}{{ (block.extras.feeDelta * 100) | amountShortener: 2 }}% + {{ block.tx_count | number }} +
@@ -82,34 +88,34 @@
+ + + + + + + + + +
+ + + + + + +
+ + + ${titleCount} + + + ${this.amountShortenerPipe.transform(countItem.value[1], 2, undefined, true)} +
+ ` : '') + + ` @@ -305,12 +371,12 @@ export class MempoolGraphComponent implements OnInit, OnChanges { `; } }, - dataZoom: (this.template === 'widget' && this.isMobile()) ? null : [{ + dataZoom: (this.isWidget && this.isMobile()) ? null : [{ type: 'inside', realtime: true, - zoomLock: (this.template === 'widget') ? true : false, + zoomLock: (this.isWidget) ? true : false, zoomOnMouseWheel: (this.template === 'advanced') ? true : false, - moveOnMouseMove: (this.template === 'widget') ? true : false, + moveOnMouseMove: (this.isWidget) ? true : false, maxSpan: 100, minSpan: 10, }, { @@ -339,7 +405,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { }, xAxis: [ { - name: this.template === 'widget' ? '' : formatterXAxisLabel(this.locale, this.windowPreference), + name: this.isWidget ? '' : formatterXAxisLabel(this.locale, this.windowPreference), nameLocation: 'middle', nameTextStyle: { padding: [20, 0, 0, 0], @@ -357,7 +423,7 @@ export class MempoolGraphComponent implements OnInit, OnChanges { }, } ], - yAxis: { + yAxis: [{ type: 'value', axisLine: { onZero: false }, axisLabel: { @@ -371,7 +437,17 @@ export class MempoolGraphComponent implements OnInit, OnChanges { opacity: 0.25, } } - }, + }, this.showCount ? { + type: 'value', + position: 'right', + axisLine: { onZero: false }, + axisLabel: { + formatter: (value: number) => (`${this.amountShortenerPipe.transform(value, 2, undefined, true)}`), + }, + splitLine: { + show: false, + } + } : null], }; } diff --git a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts index 91475040c..392cdf8c5 100644 --- a/frontend/src/app/components/pool-ranking/pool-ranking.component.ts +++ b/frontend/src/app/components/pool-ranking/pool-ranking.component.ts @@ -1,7 +1,7 @@ import { ChangeDetectionStrategy, Component, Input, NgZone, OnInit, HostBinding } from '@angular/core'; import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { EChartsOption, PieSeriesOption } from 'echarts'; +import { EChartsOption, PieSeriesOption } from '../../graphs/echarts'; import { merge, Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { SeoService } from '../../services/seo.service'; diff --git a/frontend/src/app/components/pool/pool-preview.component.ts b/frontend/src/app/components/pool/pool-preview.component.ts index b2302b9a7..e0c786082 100644 --- a/frontend/src/app/components/pool/pool-preview.component.ts +++ b/frontend/src/app/components/pool/pool-preview.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Inject, LOCALE_ID, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { EChartsOption, graphic } from 'echarts'; +import { echarts, EChartsOption } from '../../graphs/echarts'; import { Observable, of } from 'rxjs'; import { map, switchMap, catchError } from 'rxjs/operators'; import { PoolStat } from '../../interfaces/node-api.interface'; @@ -127,7 +127,7 @@ export class PoolPreviewComponent implements OnInit { title: title, animation: false, color: [ - new graphic.LinearGradient(0, 0, 0, 0.65, [ + new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [ { offset: 0, color: '#F4511E' }, { offset: 0.25, color: '#FB8C00' }, { offset: 0.5, color: '#FFB300' }, diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts index 0d465bc3c..86fdeda18 100644 --- a/frontend/src/app/components/pool/pool.component.ts +++ b/frontend/src/app/components/pool/pool.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { EChartsOption, graphic } from 'echarts'; +import { echarts, EChartsOption } from '../../graphs/echarts'; import { BehaviorSubject, Observable, of, timer } from 'rxjs'; import { catchError, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; import { BlockExtended, PoolStat } from '../../interfaces/node-api.interface'; @@ -131,7 +131,7 @@ export class PoolComponent implements OnInit { title: title, animation: false, color: [ - new graphic.LinearGradient(0, 0, 0, 0.65, [ + new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [ { offset: 0, color: '#F4511E' }, { offset: 0.25, color: '#FB8C00' }, { offset: 0.5, color: '#FFB300' }, diff --git a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts index 7a44070eb..b98390731 100644 --- a/frontend/src/app/components/privacy-policy/privacy-policy.component.ts +++ b/frontend/src/app/components/privacy-policy/privacy-policy.component.ts @@ -17,6 +17,6 @@ export class PrivacyPolicyComponent { ngOnInit(): void { this.seoService.setTitle('Privacy Policy'); - this.seoService.setDescription('Trusted third parties are security holes, as are trusted first parties...you should only trust your own self-hosted instance of The Mempool Open Source Project™.'); + this.seoService.setDescription('Trusted third parties are security holes, as are trusted first parties...you should only trust your own self-hosted instance of The Mempool Open Source Project®.'); } } diff --git a/frontend/src/app/components/start/start.component.html b/frontend/src/app/components/start/start.component.html index 709a230d9..862baf80a 100644 --- a/frontend/src/app/components/start/start.component.html +++ b/frontend/src/app/components/start/start.component.html @@ -14,6 +14,7 @@
this.blockchainContainer.nativeElement.scrollWidth && this.shiftPagesBack() && lastScrollLeft !== this.scrollLeft) { + lastScrollLeft = this.scrollLeft; + this.scrollLeft -= this.pageWidth; + } + this.blockchainContainer.nativeElement.scrollLeft = this.scrollLeft; + } + this.cd.detectChanges(); } applyPendingMarkArrow(): void { - if (this.pendingMark != null) { + if (this.pendingMark != null && this.pendingMark <= this.chainTip) { if (this.pendingMark < 0) { this.scrollToBlock(this.chainTip - this.pendingMark); } else { @@ -191,6 +225,7 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { window.clearTimeout(this.menuTimeout); this.menuTimeout = window.setTimeout(() => { this.menuSliding = false; + this.cd.markForCheck(); }, 300); } @@ -200,34 +235,33 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { this.isMobile = this.chainWidth <= 767.98; let firstVisibleBlock; let offset; - if (this.blockchainContainer?.nativeElement != null) { - this.pages.forEach(page => { - const left = page.offset - this.getConvertedScrollOffset(); - const right = left + this.pageWidth; - if (left <= 0 && right > 0) { - const blockIndex = Math.max(0, Math.floor(left / -this.blockWidth)); - firstVisibleBlock = page.height - blockIndex; - offset = left + (blockIndex * this.blockWidth); - } - }); - } + this.pages.forEach(page => { + const left = page.offset - this.getConvertedScrollOffset(this.scrollLeft); + const right = left + this.pageWidth; + if (left <= 0 && right > 0) { + const blockIndex = Math.max(0, Math.floor(left / -this.blockWidth)); + firstVisibleBlock = page.height - blockIndex; + offset = left + (blockIndex * this.blockWidth); + } + }); this.blocksPerPage = Math.ceil(this.chainWidth / this.blockWidth); this.pageWidth = this.blocksPerPage * this.blockWidth; - this.minScrollWidth = this.firstPageWidth + (this.pageWidth * 2); + this.minScrollWidth = 40 + (8 * this.blockWidth) + (this.pageWidth * 2); if (firstVisibleBlock != null) { - this.scrollToBlock(firstVisibleBlock, offset + (this.isMobile ? this.blockWidth : 0)); + this.scrollToBlock(firstVisibleBlock, offset); } else { this.updatePages(); } + this.cd.markForCheck(); } onMouseDown(event: MouseEvent) { if (!(event.which > 1 || event.button > 0)) { this.mouseDragStartX = event.clientX; this.resetMomentum(event.clientX); - this.blockchainScrollLeftInit = this.blockchainContainer.nativeElement.scrollLeft; + this.blockchainScrollLeftInit = this.scrollLeft; } } onPointerDown(event: PointerEvent) { @@ -253,8 +287,8 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { if (this.mouseDragStartX != null) { this.updateVelocity(event.clientX); this.stateService.setBlockScrollingInProgress(true); - this.blockchainContainer.nativeElement.scrollLeft = - this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX; + this.scrollLeft = this.blockchainScrollLeftInit + this.mouseDragStartX - event.clientX; + this.applyScrollLeft(); } } @HostListener('document:mouseup', []) @@ -310,25 +344,31 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { } else { this.velocity += dv; } - this.blockchainContainer.nativeElement.scrollLeft -= displacement; + this.scrollLeft -= displacement; + this.applyScrollLeft(); this.animateMomentum(); } }); } onScroll(e) { + if (this.blockchainContainer?.nativeElement?.scrollLeft == null) { + return; + } + this.scrollLeft = this.blockchainContainer?.nativeElement?.scrollLeft; const middlePage = this.pageIndex === 0 ? this.pages[0] : this.pages[1]; // compensate for css transform const translation = (this.isMobile ? this.chainWidth * 0.95 : this.chainWidth * 0.5); const backThreshold = middlePage.offset + (this.pageWidth * 0.5) + translation; const forwardThreshold = middlePage.offset - (this.pageWidth * 0.5) + translation; - const scrollLeft = this.getConvertedScrollOffset(); - if (scrollLeft > backThreshold) { + this.scrollLeft = this.blockchainContainer.nativeElement.scrollLeft; + const offsetScroll = this.getConvertedScrollOffset(this.scrollLeft); + if (offsetScroll > backThreshold) { if (this.shiftPagesBack()) { this.addConvertedScrollOffset(-this.pageWidth); this.blockchainScrollLeftInit -= this.pageWidth; } - } else if (scrollLeft < forwardThreshold) { + } else if (offsetScroll < forwardThreshold) { if (this.shiftPagesForward()) { this.addConvertedScrollOffset(this.pageWidth); this.blockchainScrollLeftInit += this.pageWidth; @@ -337,10 +377,6 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { } scrollToBlock(height, blockOffset = 0) { - if (!this.blockchainContainer?.nativeElement) { - setTimeout(() => { this.scrollToBlock(height, blockOffset); }, 50); - return; - } if (this.isMobile) { blockOffset -= this.blockWidth; } @@ -348,15 +384,15 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { const pages = []; this.pageIndex = Math.max(viewingPageIndex - 1, 0); let viewingPage = this.getPageAt(viewingPageIndex); - const isLastPage = viewingPage.height < this.blocksPerPage; + const isLastPage = viewingPage.height <= 0; if (isLastPage) { this.pageIndex = Math.max(viewingPageIndex - 2, 0); viewingPage = this.getPageAt(viewingPageIndex); } - const left = viewingPage.offset - this.getConvertedScrollOffset(); + const left = viewingPage.offset - this.getConvertedScrollOffset(this.scrollLeft); const blockIndex = viewingPage.height - height; const targetOffset = (this.blockWidth * blockIndex) + left; - let deltaOffset = targetOffset - blockOffset; + const deltaOffset = targetOffset - blockOffset; if (isLastPage) { pages.push(this.getPageAt(viewingPageIndex - 2)); @@ -386,6 +422,7 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { pages.push(this.getPageAt(this.pageIndex + 1)); pages.push(this.getPageAt(this.pageIndex + 2)); this.pages = pages; + this.cd.markForCheck(); } shiftPagesBack(): boolean { @@ -439,44 +476,40 @@ export class StartComponent implements OnInit, OnDestroy, DoCheck { blockInViewport(height: number): boolean { const firstHeight = this.pages[0].height; const translation = (this.isMobile ? this.chainWidth * 0.95 : this.chainWidth * 0.5); - const firstX = this.pages[0].offset - this.getConvertedScrollOffset() + translation; + const firstX = this.pages[0].offset - this.getConvertedScrollOffset(this.scrollLeft) + translation; const xPos = firstX + ((firstHeight - height) * 155); return xPos > -55 && xPos < (this.chainWidth - 100); } - getConvertedScrollOffset(): number { + getConvertedScrollOffset(scrollLeft): number { if (this.timeLtr) { - return -(this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset; + return -(scrollLeft || 0) - (this.mempoolOffset || 0); } else { - return (this.blockchainContainer?.nativeElement?.scrollLeft || 0) - this.mempoolOffset; + return (scrollLeft || 0) - (this.mempoolOffset || 0); } } setScrollLeft(offset: number): void { if (this.timeLtr) { - this.blockchainContainer.nativeElement.scrollLeft = offset - this.mempoolOffset; + this.scrollLeft = offset - (this.mempoolOffset || 0); } else { - this.blockchainContainer.nativeElement.scrollLeft = offset + this.mempoolOffset; + this.scrollLeft = offset + (this.mempoolOffset || 0); } + this.applyScrollLeft(); } addConvertedScrollOffset(offset: number): void { - if (!this.blockchainContainer?.nativeElement) { - this.pendingOffset = offset; - return; - } if (this.timeLtr) { - this.blockchainContainer.nativeElement.scrollLeft -= offset; + this.scrollLeft -= offset; } else { - this.blockchainContainer.nativeElement.scrollLeft += offset; + this.scrollLeft += offset; } + this.applyScrollLeft(); } ngOnDestroy() { - if (this.blockchainContainer?.nativeElement) { - // clean up scroll position to prevent caching wrong scroll in Firefox - this.setScrollLeft(0); - } + // clean up scroll position to prevent caching wrong scroll in Firefox + this.setScrollLeft(0); this.timeLtrSubscription.unsubscribe(); this.chainTipSubscription.unsubscribe(); this.markBlockSubscription.unsubscribe(); diff --git a/frontend/src/app/components/statistics/statistics.component.html b/frontend/src/app/components/statistics/statistics.component.html index 29089e43d..02a26ed52 100644 --- a/frontend/src/app/components/statistics/statistics.component.html +++ b/frontend/src/app/components/statistics/statistics.component.html @@ -69,6 +69,12 @@ -
-
+
@@ -222,7 +222,7 @@
+
diff --git a/frontend/src/app/components/transactions-list/transactions-list.component.scss b/frontend/src/app/components/transactions-list/transactions-list.component.scss index 14559089a..b80c4da4c 100644 --- a/frontend/src/app/components/transactions-list/transactions-list.component.scss +++ b/frontend/src/app/components/transactions-list/transactions-list.component.scss @@ -46,7 +46,16 @@ } td.amount { - width: 32.5%; + width: 36%; + @media (max-width: 576px) { + width: 50%; + } +} +td.amount.large { + width: 45%; + @media (max-width: 576px) { + width: 60%; + } } .extra-info { diff --git a/frontend/src/app/docs/api-docs/api-docs.component.html b/frontend/src/app/docs/api-docs/api-docs.component.html index 40a7ae486..48a8bf418 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.html +++ b/frontend/src/app/docs/api-docs/api-docs.component.html @@ -137,8 +137,16 @@

A mempool explorer is a tool that enables you to view real-time and historical information about a node's mempool, visualize its transactions, and search and view those transactions.

The mempool.space website invented the concept of visualizing a Bitcoin node's mempool as projected blocks. These blocks are the inspiration for our half-filled block logo.

Projected blocks are on the left of the dotted white line, and confirmed blocks are on the right.

-
- +
+
+ +
+ + +
+
+
+
diff --git a/frontend/src/app/docs/api-docs/api-docs.component.scss b/frontend/src/app/docs/api-docs/api-docs.component.scss index b90b843d9..f90274046 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.scss +++ b/frontend/src/app/docs/api-docs/api-docs.component.scss @@ -259,13 +259,46 @@ h3 { } .blockchain-wrapper { - position: relative; + display: block; + height: 100%; width: 100%; - overflow: auto; - scrollbar-width: none; + min-height: 220px; + position: relative; + overflow: hidden; + + .position-container { + position: absolute; + left: 50%; + bottom: 150px; + } + + #divider { + width: 2px; + height: 175px; + left: 0; + top: -40px; + position: absolute; + } + + &.time-ltr { + .blocks-wrapper { + transform: scaleX(-1); + } + } } -.blockchain-wrapper::-webkit-scrollbar { - display: none; + +:host-context(.ltr-layout) { + .blockchain-wrapper.time-ltr .blocks-wrapper, + .blockchain-wrapper .blocks-wrapper { + direction: ltr; + } +} + +:host-context(.rtl-layout) { + .blockchain-wrapper.time-ltr .blocks-wrapper, + .blockchain-wrapper .blocks-wrapper { + direction: rtl; + } } #disclaimer { diff --git a/frontend/src/app/docs/api-docs/api-docs.component.ts b/frontend/src/app/docs/api-docs/api-docs.component.ts index b0ae5967d..333bb01ad 100644 --- a/frontend/src/app/docs/api-docs/api-docs.component.ts +++ b/frontend/src/app/docs/api-docs/api-docs.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Input, QueryList, AfterViewInit, ViewChildren } from '@angular/core'; import { Env, StateService } from '../../services/state.service'; -import { Observable, merge, of, Subject } from 'rxjs'; +import { Observable, merge, of, Subject, Subscription } from 'rxjs'; import { tap, takeUntil } from 'rxjs/operators'; import { ActivatedRoute } from "@angular/router"; import { faqData, restApiDocsData, wsApiDocsData } from './api-docs-data'; @@ -30,6 +30,8 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { officialMempoolInstance: boolean; auditEnabled: boolean; mobileViewport: boolean = false; + timeLtrSubscription: Subscription; + timeLtr: boolean = this.stateService.timeLtr.value; @ViewChildren(FaqTemplateDirective) faqTemplates: QueryList; dict = {}; @@ -104,12 +106,17 @@ export class ApiDocsComponent implements OnInit, AfterViewInit { this.electrsPort = 51302; break; } }); + + this.timeLtrSubscription = this.stateService.timeLtr.subscribe((ltr) => { + this.timeLtr = !!ltr; + }); } ngOnDestroy(): void { this.destroy$.next(true); this.destroy$.complete(); window.removeEventListener('scroll', this.onDocScroll); + this.timeLtrSubscription.unsubscribe(); } onDocScroll() { diff --git a/frontend/src/app/graphs/echarts.ts b/frontend/src/app/graphs/echarts.ts new file mode 100644 index 000000000..588e5e5f1 --- /dev/null +++ b/frontend/src/app/graphs/echarts.ts @@ -0,0 +1,17 @@ +// Import tree-shakeable echarts +import * as echarts from 'echarts/core'; +import { LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart } from 'echarts/charts'; +import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, GeoComponent, DataZoomComponent, VisualMapComponent } from 'echarts/components'; +import { SVGRenderer, CanvasRenderer } from 'echarts/renderers'; +// Typescript interfaces +import { EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption } from 'echarts'; + + +echarts.use([ + SVGRenderer, CanvasRenderer, + TitleComponent, TooltipComponent, GridComponent, + LegendComponent, GeoComponent, DataZoomComponent, + VisualMapComponent, + LineChart, LinesChart, BarChart, TreemapChart, PieChart, ScatterChart +]); +export { echarts, EChartsOption, TreemapSeriesOption, LineSeriesOption, PieSeriesOption }; \ No newline at end of file diff --git a/frontend/src/app/graphs/graphs.module.ts b/frontend/src/app/graphs/graphs.module.ts index 87e8a620b..a2160977c 100644 --- a/frontend/src/app/graphs/graphs.module.ts +++ b/frontend/src/app/graphs/graphs.module.ts @@ -53,7 +53,7 @@ import { CommonModule } from '@angular/common'; SharedModule, GraphsRoutingModule, NgxEchartsModule.forRoot({ - echarts: () => import('echarts') + echarts: () => import('./echarts').then(m => m.echarts), }) ], exports: [ diff --git a/frontend/src/app/interfaces/node-api.interface.ts b/frontend/src/app/interfaces/node-api.interface.ts index a9f069b56..08de11a6a 100644 --- a/frontend/src/app/interfaces/node-api.interface.ts +++ b/frontend/src/app/interfaces/node-api.interface.ts @@ -2,6 +2,7 @@ import { Block, Transaction } from "./electrs.interface"; export interface OptimizedMempoolStats { added: number; + count: number; vbytes_per_second: number; total_fee: number; mempool_byte_weight: number; diff --git a/frontend/src/app/lightning/group/group-preview.component.ts b/frontend/src/app/lightning/group/group-preview.component.ts index fc81eab38..35bcb6e0f 100644 --- a/frontend/src/app/lightning/group/group-preview.component.ts +++ b/frontend/src/app/lightning/group/group-preview.component.ts @@ -57,7 +57,7 @@ export class GroupPreviewComponent implements OnInit { return of(null); } - return this.lightningApiService.getNodGroupNodes$(this.groupId); + return this.lightningApiService.getNodeGroup$(this.groupId); }), map((nodes) => { for (const node of nodes) { diff --git a/frontend/src/app/lightning/group/group.component.ts b/frontend/src/app/lightning/group/group.component.ts index 0786076ed..4c2cd4dd9 100644 --- a/frontend/src/app/lightning/group/group.component.ts +++ b/frontend/src/app/lightning/group/group.component.ts @@ -41,7 +41,7 @@ export class GroupComponent implements OnInit { this.seoService.setTitle(`Mempool.space Lightning Nodes`); this.seoService.setDescription(`See all Lightning nodes run by mempool.space -- these are the nodes that provide the data on the mempool.space Lightning dashboard.`); - this.nodes$ = this.lightningApiService.getNodGroupNodes$('mempool.space') + this.nodes$ = this.lightningApiService.getNodeGroup$('mempool.space') .pipe( map((nodes) => { for (const node of nodes) { diff --git a/frontend/src/app/lightning/lightning-api.service.ts b/frontend/src/app/lightning/lightning-api.service.ts index bdcc78f3f..fda93d446 100644 --- a/frontend/src/app/lightning/lightning-api.service.ts +++ b/frontend/src/app/lightning/lightning-api.service.ts @@ -27,7 +27,7 @@ export class LightningApiService { return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/' + publicKey); } - getNodGroupNodes$(name: string): Observable { + getNodeGroup$(name: string): Observable { return this.httpClient.get(this.apiBasePath + '/api/v1/lightning/nodes/group/' + name); } diff --git a/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts index f20142e47..ad667bcf6 100644 --- a/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts +++ b/frontend/src/app/lightning/node-fee-chart/node-fee-chart.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; import { switchMap } from 'rxjs/operators'; import { download } from '../../shared/graphs.utils'; import { LightningApiService } from '../lightning-api.service'; diff --git a/frontend/src/app/lightning/node-statistics-chart/node-statistics-chart.component.ts b/frontend/src/app/lightning/node-statistics-chart/node-statistics-chart.component.ts index 9b10152b5..a9308a887 100644 --- a/frontend/src/app/lightning/node-statistics-chart/node-statistics-chart.component.ts +++ b/frontend/src/app/lightning/node-statistics-chart/node-statistics-chart.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; -import { EChartsOption } from 'echarts'; +import { EChartsOption } from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { switchMap, tap } from 'rxjs/operators'; import { formatNumber } from '@angular/common'; diff --git a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts index 3090a803c..01978c324 100644 --- a/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts +++ b/frontend/src/app/lightning/nodes-channels-map/nodes-channels-map.component.ts @@ -6,8 +6,7 @@ import { AssetsService } from '../../services/assets.service'; import { ActivatedRoute, ParamMap, Router } from '@angular/router'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { StateService } from '../../services/state.service'; -import { EChartsOption, registerMap } from 'echarts'; -import 'echarts-gl'; +import { EChartsOption, echarts } from '../../graphs/echarts'; import { isMobile } from '../../shared/common.utils'; @Component({ @@ -88,7 +87,7 @@ export class NodesChannelsMap implements OnInit { this.style !== 'channelpage' ? this.apiService.getChannelsGeo$(params.get('public_key') ?? undefined, this.style) : [''], [params.get('public_key') ?? undefined] ).pipe(tap((data) => { - registerMap('world', data[0]); + echarts.registerMap('world', data[0]); const channelsLoc = []; const nodes = []; diff --git a/frontend/src/app/lightning/nodes-channels/node-channels.component.ts b/frontend/src/app/lightning/nodes-channels/node-channels.component.ts index 91d43f6ab..be596d8f9 100644 --- a/frontend/src/app/lightning/nodes-channels/node-channels.component.ts +++ b/frontend/src/app/lightning/nodes-channels/node-channels.component.ts @@ -1,7 +1,7 @@ import { formatNumber } from '@angular/common'; import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, NgZone, OnChanges } from '@angular/core'; import { Router } from '@angular/router'; -import { ECharts, EChartsOption, TreemapSeriesOption } from 'echarts'; +import { EChartsOption, TreemapSeriesOption } from '../../graphs/echarts'; import { Observable, share, switchMap, tap } from 'rxjs'; import { lerpColor } from '../../shared/graphs.utils'; import { AmountShortenerPipe } from '../../shared/pipes/amount-shortener.pipe'; @@ -18,7 +18,7 @@ import { StateService } from '../../services/state.service'; export class NodeChannels implements OnChanges { @Input() publicKey: string; - chartInstance: ECharts; + chartInstance: any; chartOptions: EChartsOption = {}; chartInitOptions = { renderer: 'svg', @@ -129,7 +129,7 @@ export class NodeChannels implements OnChanges { }; } - onChartInit(ec: ECharts): void { + onChartInit(ec: any): void { this.chartInstance = ec; this.chartInstance.on('click', (e) => { diff --git a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts index 4f74723cc..5e655b584 100644 --- a/frontend/src/app/lightning/nodes-map/nodes-map.component.ts +++ b/frontend/src/app/lightning/nodes-map/nodes-map.component.ts @@ -3,7 +3,7 @@ import { SeoService } from '../../services/seo.service'; import { ApiService } from '../../services/api.service'; import { Observable, BehaviorSubject, switchMap, tap, combineLatest } from 'rxjs'; import { AssetsService } from '../../services/assets.service'; -import { EChartsOption, registerMap } from 'echarts'; +import { EChartsOption, echarts } from '../../graphs/echarts'; import { lerpColor } from '../../shared/graphs.utils'; import { Router } from '@angular/router'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; @@ -63,7 +63,7 @@ export class NodesMap implements OnInit, OnChanges { this.assetsService.getWorldMapJson$, this.nodes$ ).pipe(tap((data) => { - registerMap('world', data[0]); + echarts.registerMap('world', data[0]); let maxLiquidity = data[1].maxLiquidity; let inputNodes: any[] = data[1].nodes; @@ -88,7 +88,7 @@ export class NodesMap implements OnInit, OnChanges { node.public_key, node.alias, node.capacity, - node.channels, + node.active_channel_count, node.country, node.iso_code, ]); @@ -114,7 +114,7 @@ export class NodesMap implements OnInit, OnChanges { node[3], // Alias node[2], // Public key node[5], // Channels - node[6].en, // Country + node[6]?.en, // Country node[7], // ISO Code ]); } diff --git a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts index f62a6a244..7352d884d 100644 --- a/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts +++ b/frontend/src/app/lightning/nodes-networks-chart/nodes-networks-chart.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; -import { EChartsOption, graphic, LineSeriesOption} from 'echarts'; +import { echarts, EChartsOption, LineSeriesOption } from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { formatNumber } from '@angular/common'; @@ -152,7 +152,7 @@ export class NodesNetworksChartComponent implements OnInit { opacity: 0.5, }, stack: 'Total', - color: new graphic.LinearGradient(0, 0.75, 0, 1, [ + color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [ { offset: 0, color: '#D81B60' }, { offset: 1, color: '#D81B60AA' }, ]), @@ -174,7 +174,7 @@ export class NodesNetworksChartComponent implements OnInit { opacity: 0.5, }, stack: 'Total', - color: new graphic.LinearGradient(0, 0.75, 0, 1, [ + color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [ { offset: 0, color: '#be7d4c' }, { offset: 1, color: '#be7d4cAA' }, ]), @@ -195,7 +195,7 @@ export class NodesNetworksChartComponent implements OnInit { opacity: 0.5, }, stack: 'Total', - color: new graphic.LinearGradient(0, 0.75, 0, 1, [ + color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [ { offset: 0, color: '#FFB300' }, { offset: 1, color: '#FFB300AA' }, ]), @@ -216,7 +216,7 @@ export class NodesNetworksChartComponent implements OnInit { opacity: 0.5, }, stack: 'Total', - color: new graphic.LinearGradient(0, 0.75, 0, 1, [ + color: new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [ { offset: 0, color: '#7D4698' }, { offset: 1, color: '#7D4698AA' }, ]), diff --git a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts index 5bfa0fc2c..e305d8959 100644 --- a/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-country-chart/nodes-per-country-chart.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone } from '@angular/core'; import { Router } from '@angular/router'; -import { EChartsOption, PieSeriesOption } from 'echarts'; +import { EChartsOption, PieSeriesOption } from '../../graphs/echarts'; import { map, Observable, share, tap } from 'rxjs'; import { chartColors } from '../../app.constants'; import { ApiService } from '../../services/api.service'; diff --git a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts index 67f393bc2..5342f616b 100644 --- a/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts +++ b/frontend/src/app/lightning/nodes-per-isp-chart/nodes-per-isp-chart.component.ts @@ -1,6 +1,6 @@ import { ChangeDetectionStrategy, Component, OnInit, HostBinding, NgZone, Input } from '@angular/core'; import { Router } from '@angular/router'; -import { EChartsOption, PieSeriesOption } from 'echarts'; +import { EChartsOption, PieSeriesOption } from '../../graphs/echarts'; import { combineLatest, map, Observable, share, startWith, Subject, switchMap, tap } from 'rxjs'; import { chartColors } from '../../app.constants'; import { ApiService } from '../../services/api.service'; diff --git a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts index 41e170de6..7417a35cd 100644 --- a/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts +++ b/frontend/src/app/lightning/statistics-chart/lightning-statistics-chart.component.ts @@ -1,5 +1,5 @@ import { Component, Inject, Input, LOCALE_ID, OnInit, HostBinding } from '@angular/core'; -import { EChartsOption, graphic } from 'echarts'; +import { echarts, EChartsOption } from '../../graphs/echarts'; import { Observable } from 'rxjs'; import { map, share, startWith, switchMap, tap } from 'rxjs/operators'; import { SeoService } from '../../services/seo.service'; @@ -132,7 +132,7 @@ export class LightningStatisticsChartComponent implements OnInit { animation: false, color: [ '#FFB300', - new graphic.LinearGradient(0, 0.75, 0, 1, [ + new echarts.graphic.LinearGradient(0, 0.75, 0, 1, [ { offset: 0, color: '#D81B60' }, { offset: 1, color: '#D81B60AA' }, ]), diff --git a/frontend/src/app/services/seo.service.ts b/frontend/src/app/services/seo.service.ts index 2584cbaed..3d095e1c3 100644 --- a/frontend/src/app/services/seo.service.ts +++ b/frontend/src/app/services/seo.service.ts @@ -12,6 +12,8 @@ export class SeoService { baseTitle = 'mempool'; baseDescription = 'Explore the full Bitcoin ecosystem with The Mempool Open Project™.'; + canonicalLink: HTMLElement = document.getElementById('canonical'); + constructor( private titleService: Title, private metaService: Meta, @@ -65,6 +67,16 @@ export class SeoService { this.metaService.updateTag({ property: 'og:description', content: this.getDescription()}); } + updateCanonical(path) { + let domain = 'mempool.space'; + if (this.stateService.env.BASE_MODULE === 'liquid') { + domain = 'liquid.network'; + } else if (this.stateService.env.BASE_MODULE === 'bisq') { + domain = 'bisq.markets'; + } + this.canonicalLink.setAttribute('href', 'https://' + domain + path); + } + getTitle(): string { if (this.network === 'testnet') return this.baseTitle + ' - Bitcoin Testnet'; diff --git a/frontend/src/app/services/state.service.ts b/frontend/src/app/services/state.service.ts index 878edf359..db1268379 100644 --- a/frontend/src/app/services/state.service.ts +++ b/frontend/src/app/services/state.service.ts @@ -117,6 +117,7 @@ export class StateService { difficultyAdjustment$ = new ReplaySubject(1); mempoolTransactions$ = new Subject(); mempoolTxPosition$ = new Subject<{ txid: string, position: MempoolPosition, cpfp: CpfpInfo | null}>(); + mempoolRemovedTransactions$ = new Subject(); blockTransactions$ = new Subject(); isLoadingWebSocket$ = new ReplaySubject(1); isLoadingMempool$ = new BehaviorSubject(true); diff --git a/frontend/src/app/services/websocket.service.ts b/frontend/src/app/services/websocket.service.ts index af2a15e8c..22da49f06 100644 --- a/frontend/src/app/services/websocket.service.ts +++ b/frontend/src/app/services/websocket.service.ts @@ -358,6 +358,12 @@ export class WebsocketService { }); } + if (response['address-removed-transactions']) { + response['address-removed-transactions'].forEach((addressTransaction: Transaction) => { + this.stateService.mempoolRemovedTransactions$.next(addressTransaction); + }); + } + if (response['block-transactions']) { response['block-transactions'].forEach((addressTransaction: Transaction) => { this.stateService.blockTransactions$.next(addressTransaction); diff --git a/frontend/src/app/shared/components/geolocation/geolocation.component.ts b/frontend/src/app/shared/components/geolocation/geolocation.component.ts index 9cce1ea08..1a498a1b2 100644 --- a/frontend/src/app/shared/components/geolocation/geolocation.component.ts +++ b/frontend/src/app/shared/components/geolocation/geolocation.component.ts @@ -20,6 +20,11 @@ export class GeolocationComponent implements OnChanges { formattedLocation: string = ''; ngOnChanges(): void { + if (!this.data) { + this.formattedLocation = '-'; + return; + } + const city = this.data.city ? this.data.city : ''; const subdivisionLikeCity = this.data.city === this.data.subdivision; let subdivision = this.data.subdivision; diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.html b/frontend/src/app/shared/components/global-footer/global-footer.component.html index 34d47379e..a571e33c5 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.html +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.html @@ -84,6 +84,7 @@ GitHub Twitter + YouTube Matrix diff --git a/frontend/src/app/shared/components/global-footer/global-footer.component.scss b/frontend/src/app/shared/components/global-footer/global-footer.component.scss index 3bdc239a9..148383cb4 100644 --- a/frontend/src/app/shared/components/global-footer/global-footer.component.scss +++ b/frontend/src/app/shared/components/global-footer/global-footer.component.scss @@ -88,7 +88,14 @@ footer .row.link-tree { footer .row.social-links { text-align: center; - margin: 24px 0; + display: flex; + flex-wrap: wrap; + width: fit-content; + margin: 0 auto; + + @media (max-width: 450px){ + width: 250px; + } } footer .row.social-links a { @@ -97,6 +104,7 @@ footer .row.social-links a { footer .row.social-links svg { width: 20px; + margin: 10px 0 10px 0; } footer .row.version { @@ -189,10 +197,6 @@ footer .sponsor { margin-top: 15px; } - footer .row.social-links { - margin: 48px 0 24px 0; - } - footer .selector:not(:last-child) { margin-right: 10px; } @@ -236,10 +240,6 @@ footer .sponsor { margin-top: 15px; } - footer .services.row.social-links { - margin: 48px 0 24px 0; - } - footer .services.selector:not(:last-child) { margin-right: 10px; } diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts index f7c253a96..dce65bfae 100644 --- a/frontend/src/app/shared/shared.module.ts +++ b/frontend/src/app/shared/shared.module.ts @@ -97,6 +97,8 @@ import { AcceleratePreviewComponent } from '../components/accelerate-preview/acc import { AccelerateFeeGraphComponent } from '../components/accelerate-preview/accelerate-fee-graph.component'; import { MempoolErrorComponent } from './components/mempool-error/mempool-error.component'; +import { BlockViewComponent } from '../components/block-view/block-view.component'; +import { MempoolBlockViewComponent } from '../components/mempool-block-view/mempool-block-view.component'; import { MempoolBlockOverviewComponent } from '../components/mempool-block-overview/mempool-block-overview.component'; import { ClockchainComponent } from '../components/clockchain/clockchain.component'; import { ClockFaceComponent } from '../components/clock-face/clock-face.component'; @@ -134,6 +136,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir FiatCurrencyPipe, ColoredPriceDirective, BlockchainComponent, + BlockViewComponent, + MempoolBlockViewComponent, MempoolBlocksComponent, BlockchainBlocksComponent, AmountComponent, @@ -196,6 +200,8 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir AccelerateFeeGraphComponent, CalculatorComponent, BitcoinsatoshisPipe, + BlockViewComponent, + MempoolBlockViewComponent, MempoolBlockOverviewComponent, ClockchainComponent, ClockComponent, diff --git a/frontend/src/index.mempool.html b/frontend/src/index.mempool.html index 03edc08ef..def14434e 100644 --- a/frontend/src/index.mempool.html +++ b/frontend/src/index.mempool.html @@ -7,17 +7,17 @@ - + - + - + diff --git a/frontend/sync-assets.js b/frontend/sync-assets.js index 49618dea3..a92c3b21d 100644 --- a/frontend/sync-assets.js +++ b/frontend/sync-assets.js @@ -1,6 +1,19 @@ var https = require('https'); var fs = require('fs'); var crypto = require('crypto'); +var path = require('node:path'); +const LOG_TAG = '[sync-assets]'; +let verbose = false; + +if (parseInt(process.env.SKIP_SYNC) === 1) { + console.log(`${LOG_TAG} SKIP_SYNC is set, not checking any assets`); + process.exit(0); +} + +if (parseInt(process.env.VERBOSE) === 1) { + console.log(`${LOG_TAG} VERBOSE is set, logs will be more verbose`); + verbose = true; +} const CONFIG_FILE_NAME = 'mempool-frontend-config.json'; let configContent = {}; @@ -8,6 +21,13 @@ let configContent = {}; var PATH; if (process.argv[2]) { PATH = process.argv[2]; + PATH += PATH.endsWith("/") ? "" : "/" + PATH = path.normalize(PATH); + console.log(`[sync-assets] using PATH ${PATH}`); + if (!fs.existsSync(PATH)){ + console.log(`${LOG_TAG} ${PATH} does not exist, creating`); + fs.mkdirSync(PATH, { recursive: true }); + } } if (!PATH) { @@ -17,12 +37,12 @@ if (!PATH) { try { const rawConfig = fs.readFileSync(CONFIG_FILE_NAME); configContent = JSON.parse(rawConfig); - console.log(`${CONFIG_FILE_NAME} file found, using provided config`); + console.log(`${LOG_TAG} ${CONFIG_FILE_NAME} file found, using provided config`); } catch (e) { if (e.code !== 'ENOENT') { throw new Error(e); } else { - console.log(`${CONFIG_FILE_NAME} file not found, using default config`); + console.log(`${LOG_TAG} ${CONFIG_FILE_NAME} file not found, using default config`); } } @@ -37,6 +57,11 @@ function download(filename, url) { }) .on('error', function(e) { throw new Error(e); + }) + .on('finish', () => { + if (verbose) { + console.log(`${LOG_TAG} Finished downloading ${url} to ${filename}`); + } }); } @@ -44,12 +69,18 @@ function getLocalHash(filePath) { const size = fs.statSync(filePath); const buffer = fs.readFileSync(filePath); const bufferWithHeader = Buffer.concat([Buffer.from('blob '), Buffer.from(`${size.size}`), Buffer.from('\0'), buffer]); - return crypto.createHash('sha1').update(bufferWithHeader).digest('hex'); + const hash = crypto.createHash('sha1').update(bufferWithHeader).digest('hex'); + + if (verbose) { + console.log(`${LOG_TAG} \tgetLocalHash ${filePath} ${hash}`); + } + + return hash; } function downloadMiningPoolLogos$() { return new Promise((resolve, reject) => { - console.log('Checking if mining pool logos needs downloading or updating...'); + console.log(`${LOG_TAG} Checking if mining pool logos needs downloading or updating...`); const options = { host: 'api.github.com', path: '/repos/mempool/mining-pool-logos/contents/', @@ -58,7 +89,7 @@ function downloadMiningPoolLogos$() { }; if (githubSecret) { - console.log('Downloading the mining pool logos with authentication'); + console.log(`${LOG_TAG} Downloading the mining pool logos with authentication`); options.headers['authorization'] = `Bearer ${githubSecret}`; options.headers['X-GitHub-Api-Version'] = '2022-11-28'; } @@ -79,21 +110,29 @@ function downloadMiningPoolLogos$() { } let downloadedCount = 0; for (const poolLogo of poolLogos) { - const filePath = `${PATH}/mining-pools/${poolLogo.name}`; + const filePath = PATH + `mining-pools/${poolLogo.name}`; if (fs.existsSync(filePath)) { const localHash = getLocalHash(filePath); + if (verbose) { + console.log(`${LOG_TAG} Remote ${poolLogo.name} logo hash ${poolLogo.sha}`); + console.log(`${LOG_TAG} \tchecking if ${filePath} exists: ${fs.existsSync(filePath)}`); + } if (localHash !== poolLogo.sha) { - console.log(`${poolLogo.name} is different on the remote, downloading...`); + console.log(`${LOG_TAG} \t\t${poolLogo.name} is different on the remote, downloading...`); download(filePath, poolLogo.download_url); downloadedCount++; } } else { - console.log(`${poolLogo.name} is missing, downloading...`); + console.log(`${LOG_TAG} ${poolLogo.name} is missing, downloading...`); + const miningPoolsDir = PATH + `mining-pools/`; + if (!fs.existsSync(miningPoolsDir)){ + fs.mkdirSync(miningPoolsDir, { recursive: true }); + } download(filePath, poolLogo.download_url); downloadedCount++; } } - console.log(`Downloaded ${downloadedCount} and skipped ${poolLogos.length - downloadedCount} existing mining pool logos`); + console.log(`${LOG_TAG} Downloaded ${downloadedCount} and skipped ${poolLogos.length - downloadedCount} existing mining pool logos`); resolve(); } catch (e) { reject(`Unable to download mining pool logos. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); @@ -109,7 +148,7 @@ function downloadMiningPoolLogos$() { function downloadPromoVideoSubtiles$() { return new Promise((resolve, reject) => { - console.log('Checking if promo video subtitles needs downloading or updating...'); + console.log(`${LOG_TAG} Checking if promo video subtitles needs downloading or updating...`); const options = { host: 'api.github.com', path: '/repos/mempool/mempool-promo/contents/subtitles', @@ -118,7 +157,7 @@ function downloadPromoVideoSubtiles$() { }; if (githubSecret) { - console.log('Downloading the promo video subtitles with authentication'); + console.log(`${LOG_TAG} Downloading the promo video subtitles with authentication`); options.headers['authorization'] = `Bearer ${githubSecret}`; options.headers['X-GitHub-Api-Version'] = '2022-11-28'; } @@ -140,21 +179,30 @@ function downloadPromoVideoSubtiles$() { } let downloadedCount = 0; for (const language of videoLanguages) { - const filePath = `${PATH}/promo-video/${language.name}`; + const filePath = PATH + `promo-video/${language.name}`; if (fs.existsSync(filePath)) { + if (verbose) { + console.log(`${LOG_TAG} ${language.name} remote promo video hash ${language.sha}`); + } const localHash = getLocalHash(filePath); + if (localHash !== language.sha) { - console.log(`${language.name} is different on the remote, updating`); + console.log(`${LOG_TAG} ${language.name} is different on the remote, updating`); download(filePath, language.download_url); downloadedCount++; } } else { - console.log(`${language.name} is missing, downloading`); + console.log(`${LOG_TAG} ${language.name} is missing, downloading`); + const promoVideosDir = PATH + `promo-video/`; + if (!fs.existsSync(promoVideosDir)){ + fs.mkdirSync(promoVideosDir, { recursive: true }); + } + download(filePath, language.download_url); downloadedCount++; } } - console.log(`Downloaded ${downloadedCount} and skipped ${videoLanguages.length - downloadedCount} existing video subtitles`); + console.log(`${LOG_TAG} Downloaded ${downloadedCount} and skipped ${videoLanguages.length - downloadedCount} existing video subtitles`); resolve(); } catch (e) { reject(`Unable to download video subtitles. Trying again at next restart. Reason: ${e instanceof Error ? e.message : e}`); @@ -170,7 +218,7 @@ function downloadPromoVideoSubtiles$() { function downloadPromoVideo$() { return new Promise((resolve, reject) => { - console.log('Checking if promo video needs downloading or updating...'); + console.log(`${LOG_TAG} Checking if promo video needs downloading or updating...`); const options = { host: 'api.github.com', path: '/repos/mempool/mempool-promo/contents', @@ -179,7 +227,7 @@ function downloadPromoVideo$() { }; if (githubSecret) { - console.log('Downloading the promo videos with authentication'); + console.log(`${LOG_TAG} Downloading the promo video with authentication`); options.headers['authorization'] = `Bearer ${githubSecret}`; options.headers['X-GitHub-Api-Version'] = '2022-11-28'; } @@ -202,18 +250,19 @@ function downloadPromoVideo$() { if (item.name !== 'promo.mp4') { continue; } - const filePath = `${PATH}/promo-video/mempool-promo.mp4`; + const filePath = PATH + `promo-video/mempool-promo.mp4`; if (fs.existsSync(filePath)) { const localHash = getLocalHash(filePath); + if (localHash !== item.sha) { - console.log(`mempool-promo.mp4 is different on the remote, updating`); + console.log(`${LOG_TAG} \tmempool-promo.mp4 is different on the remote, updating`); download(filePath, item.download_url); - console.log('mempool-promo.mp4 downloaded.'); + console.log(`${LOG_TAG} \tmempool-promo.mp4 downloaded.`); } else { - console.log(`mempool-promo.mp4 is already up to date. Skipping.`); + console.log(`${LOG_TAG} \tmempool-promo.mp4 is already up to date. Skipping.`); } } else { - console.log(`mempool-promo.mp4 is missing, downloading`); + console.log(`${LOG_TAG} \tmempool-promo.mp4 is missing, downloading`); download(filePath, item.download_url); } } @@ -232,30 +281,47 @@ function downloadPromoVideo$() { } - -let assetsJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.json'; -let assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/mempool/asset_registry_db/master/index.minimal.json'; - if (configContent.BASE_MODULE && configContent.BASE_MODULE === 'liquid') { - assetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.json'; - assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.minimal.json'; + const assetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.json'; + const assetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_db/master/index.minimal.json'; + const testnetAssetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.json'; + const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json'; + + console.log(`${LOG_TAG} Downloading assets`); + download(PATH + 'assets.json', assetsJsonUrl); + + console.log(`${LOG_TAG} Downloading assets minimal`); + download(PATH + 'assets.minimal.json', assetsMinimalJsonUrl); + + console.log(`${LOG_TAG} Downloading testnet assets`); + download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl); + + console.log(`${LOG_TAG} Downloading testnet assets minimal`); + download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl); +} else { + if (verbose) { + console.log(`${LOG_TAG} BASE_MODULE is not set to Liquid (${configContent.BASE_MODULE}), skipping downloading assets`); + } } -const testnetAssetsJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.json'; -const testnetAssetsMinimalJsonUrl = 'https://raw.githubusercontent.com/Blockstream/asset_registry_testnet_db/master/index.minimal.json'; - -console.log('Downloading assets'); -download(PATH + 'assets.json', assetsJsonUrl); -console.log('Downloading assets minimal'); -download(PATH + 'assets.minimal.json', assetsMinimalJsonUrl); -console.log('Downloading testnet assets'); -download(PATH + 'assets-testnet.json', testnetAssetsJsonUrl); -console.log('Downloading testnet assets minimal'); -download(PATH + 'assets-testnet.minimal.json', testnetAssetsMinimalJsonUrl); - -downloadMiningPoolLogos$() - .then(() => downloadPromoVideoSubtiles$()) - .then(() => downloadPromoVideo$()) +(() => { + if (verbose) { + console.log(`${LOG_TAG} Downloading mining pool logos`); + } + downloadMiningPoolLogos$() + .then(() => { + if (verbose) { + console.log(`${LOG_TAG} Downloading promo video subtitles`); + } + downloadPromoVideoSubtiles$(); + }) + .then(() => { + if (verbose) { + console.log(`${LOG_TAG} Downloading promo video`); + } + downloadPromoVideo$(); + }) .catch((error) => { throw new Error(error); }); +})(); \ No newline at end of file diff --git a/production/install b/production/install index 9761d2b33..18ee06233 100755 --- a/production/install +++ b/production/install @@ -332,7 +332,7 @@ BITCOIN_REPO_URL=https://github.com/bitcoin/bitcoin BITCOIN_REPO_NAME=bitcoin BITCOIN_REPO_BRANCH=master #BITCOIN_LATEST_RELEASE=$(curl -s https://api.github.com/repos/bitcoin/bitcoin/releases/latest|grep tag_name|head -1|cut -d '"' -f4) -BITCOIN_LATEST_RELEASE=v25.0 +BITCOIN_LATEST_RELEASE=v25.1 echo -n '.' BISQ_REPO_URL=https://github.com/bisq-network/bisq @@ -1046,8 +1046,8 @@ echo "[*] Installing nvm.sh from GitHub" osSudo "${MEMPOOL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh' echo "[*] Building NodeJS v20.5.1 via nvm.sh" -osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.5.1 --shared-zlib' -osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm alias default 20.5.1' +osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.7.0 --shared-zlib' +osSudo "${MEMPOOL_USER}" zsh -c 'source ~/.zshrc ; nvm alias default 20.7.0' #################### # Tor installation # @@ -1489,7 +1489,7 @@ EOF osSudo "${UNFURL_USER}" sh -c 'curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | zsh' echo "[*] Building NodeJS via nvm.sh" - osSudo "${UNFURL_USER}" zsh -c 'source ~/.zshrc ; nvm install v16.16.0 --shared-zlib' + osSudo "${UNFURL_USER}" zsh -c 'source ~/.zshrc ; nvm install v20.7.0 --shared-zlib' ;; esac @@ -1660,15 +1660,15 @@ case $OS in crontab_bitcoin=() if [ "${BITCOIN_MAINNET_ENABLE}" = ON ];then echo "[*] Installing Electrs Mainnet Cronjob" - crontab_bitcoin+="@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/electrs-start-mainnet\n" + crontab_bitcoin+="@reboot sleep 30 ; screen -dmS mainnet /bitcoin/electrs/start mainnet\n" fi if [ "${BITCOIN_TESTNET_ENABLE}" = ON ];then echo "[*] Installing Electrs Testnet Cronjob" - crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/electrs-start-testnet\n" + crontab_bitcoin+="@reboot sleep 70 ; screen -dmS testnet /bitcoin/electrs/start testnet\n" fi if [ "${BITCOIN_SIGNET_ENABLE}" = ON ];then echo "[*] Installing Electrs Signet Cronjob" - crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/electrs-start-signet\n" + crontab_bitcoin+="@reboot sleep 90 ; screen -dmS signet /bitcoin/electrs/start signet\n" fi if [ "${BITCOIN_MAINNET_ENABLE}" = ON -o "${BITCOIN_TESTNET_ENABLE}" = ON -o "${BITCOIN_SIGNET_ENABLE}" = ON ];then echo "${crontab_bitcoin}" | crontab -u "${BITCOIN_USER}" - diff --git a/production/mempool-config.liquid.json b/production/mempool-config.liquid.json index d67d7b794..a4f4bcd81 100644 --- a/production/mempool-config.liquid.json +++ b/production/mempool-config.liquid.json @@ -25,6 +25,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-mainnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3001", + "http://node202.va1.mempool.space:3001", + "http://node203.va1.mempool.space:3001", + "http://node204.va1.mempool.space:3001", + "http://node205.va1.mempool.space:3001", + "http://node206.va1.mempool.space:3001", "http://node201.fmt.mempool.space:3001", "http://node202.fmt.mempool.space:3001", "http://node203.fmt.mempool.space:3001", diff --git a/production/mempool-config.liquidtestnet.json b/production/mempool-config.liquidtestnet.json index 3a76b4c86..cf2d70045 100644 --- a/production/mempool-config.liquidtestnet.json +++ b/production/mempool-config.liquidtestnet.json @@ -25,6 +25,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/elements/socket/esplora-liquid-testnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3004", + "http://node202.va1.mempool.space:3004", + "http://node203.va1.mempool.space:3004", + "http://node204.va1.mempool.space:3004", + "http://node205.va1.mempool.space:3004", + "http://node206.va1.mempool.space:3004", "http://node201.fmt.mempool.space:3004", "http://node202.fmt.mempool.space:3004", "http://node203.fmt.mempool.space:3004", diff --git a/production/mempool-config.mainnet-lightning.json b/production/mempool-config.mainnet-lightning.json index 41e42a5bd..6bd326bfa 100644 --- a/production/mempool-config.mainnet-lightning.json +++ b/production/mempool-config.mainnet-lightning.json @@ -16,8 +16,33 @@ "PASSWORD": "__BITCOIN_RPC_PASS__" }, "ESPLORA": { - "REST_API_URL": "http://127.0.0.1:5000", - "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet" + "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet", + "FALLBACK": [ + "http://node201.va1.mempool.space:3000", + "http://node202.va1.mempool.space:3000", + "http://node203.va1.mempool.space:3000", + "http://node204.va1.mempool.space:3000", + "http://node205.va1.mempool.space:3000", + "http://node206.va1.mempool.space:3000", + "http://node201.fmt.mempool.space:3000", + "http://node202.fmt.mempool.space:3000", + "http://node203.fmt.mempool.space:3000", + "http://node204.fmt.mempool.space:3000", + "http://node205.fmt.mempool.space:3000", + "http://node206.fmt.mempool.space:3000", + "http://node201.fra.mempool.space:3000", + "http://node202.fra.mempool.space:3000", + "http://node203.fra.mempool.space:3000", + "http://node204.fra.mempool.space:3000", + "http://node205.fra.mempool.space:3000", + "http://node206.fra.mempool.space:3000", + "http://node201.tk7.mempool.space:3000", + "http://node202.tk7.mempool.space:3000", + "http://node203.tk7.mempool.space:3000", + "http://node204.tk7.mempool.space:3000", + "http://node205.tk7.mempool.space:3000", + "http://node206.tk7.mempool.space:3000" + ] }, "LIGHTNING": { "ENABLED": true, diff --git a/production/mempool-config.mainnet.json b/production/mempool-config.mainnet.json index d4222bd05..b2878ceef 100644 --- a/production/mempool-config.mainnet.json +++ b/production/mempool-config.mainnet.json @@ -37,6 +37,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-mainnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3000", + "http://node202.va1.mempool.space:3000", + "http://node203.va1.mempool.space:3000", + "http://node204.va1.mempool.space:3000", + "http://node205.va1.mempool.space:3000", + "http://node206.va1.mempool.space:3000", "http://node201.fmt.mempool.space:3000", "http://node202.fmt.mempool.space:3000", "http://node203.fmt.mempool.space:3000", @@ -74,6 +80,12 @@ "AUDIT": true, "AUDIT_START_HEIGHT": 774000, "SERVERS": [ + "node201.va1.mempool.space", + "node202.va1.mempool.space", + "node203.va1.mempool.space", + "node204.va1.mempool.space", + "node205.va1.mempool.space", + "node206.va1.mempool.space", "node201.fmt.mempool.space", "node202.fmt.mempool.space", "node203.fmt.mempool.space", diff --git a/production/mempool-config.signet-lightning.json b/production/mempool-config.signet-lightning.json index 9971729e2..229b226be 100644 --- a/production/mempool-config.signet-lightning.json +++ b/production/mempool-config.signet-lightning.json @@ -16,8 +16,33 @@ "PASSWORD": "__BITCOIN_RPC_PASS__" }, "ESPLORA": { - "REST_API_URL": "http://127.0.0.1:5003", - "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet" + "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet", + "FALLBACK": [ + "http://node201.va1.mempool.space:3003", + "http://node202.va1.mempool.space:3003", + "http://node203.va1.mempool.space:3003", + "http://node204.va1.mempool.space:3003", + "http://node205.va1.mempool.space:3003", + "http://node206.va1.mempool.space:3003", + "http://node201.fmt.mempool.space:3003", + "http://node202.fmt.mempool.space:3003", + "http://node203.fmt.mempool.space:3003", + "http://node204.fmt.mempool.space:3003", + "http://node205.fmt.mempool.space:3003", + "http://node206.fmt.mempool.space:3003", + "http://node201.fra.mempool.space:3003", + "http://node202.fra.mempool.space:3003", + "http://node203.fra.mempool.space:3003", + "http://node204.fra.mempool.space:3003", + "http://node205.fra.mempool.space:3003", + "http://node206.fra.mempool.space:3003", + "http://node201.tk7.mempool.space:3003", + "http://node202.tk7.mempool.space:3003", + "http://node203.tk7.mempool.space:3003", + "http://node204.tk7.mempool.space:3003", + "http://node205.tk7.mempool.space:3003", + "http://node206.tk7.mempool.space:3003" + ] }, "LIGHTNING": { "ENABLED": true, diff --git a/production/mempool-config.signet.json b/production/mempool-config.signet.json index 38d59c0e9..c4b84f11b 100644 --- a/production/mempool-config.signet.json +++ b/production/mempool-config.signet.json @@ -27,6 +27,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-signet", "FALLBACK": [ + "http://node201.va1.mempool.space:3003", + "http://node202.va1.mempool.space:3003", + "http://node203.va1.mempool.space:3003", + "http://node204.va1.mempool.space:3003", + "http://node205.va1.mempool.space:3003", + "http://node206.va1.mempool.space:3003", "http://node201.fmt.mempool.space:3003", "http://node202.fmt.mempool.space:3003", "http://node203.fmt.mempool.space:3003", diff --git a/production/mempool-config.testnet-lightning.json b/production/mempool-config.testnet-lightning.json index ff7d4766f..60ee128a2 100644 --- a/production/mempool-config.testnet-lightning.json +++ b/production/mempool-config.testnet-lightning.json @@ -16,8 +16,33 @@ "PASSWORD": "__BITCOIN_RPC_PASS__" }, "ESPLORA": { - "REST_API_URL": "http://127.0.0.1:5002", - "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet" + "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet", + "FALLBACK": [ + "http://node201.va1.mempool.space:3002", + "http://node202.va1.mempool.space:3002", + "http://node203.va1.mempool.space:3002", + "http://node204.va1.mempool.space:3002", + "http://node205.va1.mempool.space:3002", + "http://node206.va1.mempool.space:3002", + "http://node201.fmt.mempool.space:3002", + "http://node202.fmt.mempool.space:3002", + "http://node203.fmt.mempool.space:3002", + "http://node204.fmt.mempool.space:3002", + "http://node205.fmt.mempool.space:3002", + "http://node206.fmt.mempool.space:3002", + "http://node201.fra.mempool.space:3002", + "http://node202.fra.mempool.space:3002", + "http://node203.fra.mempool.space:3002", + "http://node204.fra.mempool.space:3002", + "http://node205.fra.mempool.space:3002", + "http://node206.fra.mempool.space:3002", + "http://node201.tk7.mempool.space:3002", + "http://node202.tk7.mempool.space:3002", + "http://node203.tk7.mempool.space:3002", + "http://node204.tk7.mempool.space:3002", + "http://node205.tk7.mempool.space:3002", + "http://node206.tk7.mempool.space:3002" + ] }, "LIGHTNING": { "ENABLED": true, diff --git a/production/mempool-config.testnet.json b/production/mempool-config.testnet.json index c5bdfc8d7..b3d7cfadd 100644 --- a/production/mempool-config.testnet.json +++ b/production/mempool-config.testnet.json @@ -27,6 +27,12 @@ "ESPLORA": { "UNIX_SOCKET_PATH": "/bitcoin/socket/esplora-bitcoin-testnet", "FALLBACK": [ + "http://node201.va1.mempool.space:3002", + "http://node202.va1.mempool.space:3002", + "http://node203.va1.mempool.space:3002", + "http://node204.va1.mempool.space:3002", + "http://node205.va1.mempool.space:3002", + "http://node206.va1.mempool.space:3002", "http://node201.fmt.mempool.space:3002", "http://node202.fmt.mempool.space:3002", "http://node203.fmt.mempool.space:3002", diff --git a/production/mempool-start-all b/production/mempool-start-all index 5b4f85577..0fc9b17c9 100755 --- a/production/mempool-start-all +++ b/production/mempool-start-all @@ -1,7 +1,7 @@ #!/usr/bin/env zsh export NVM_DIR="$HOME/.nvm" source "$NVM_DIR/nvm.sh" -nvm use v20.5.1 +nvm use v20.7.0 # start all mempool backends that exist for site in mainnet mainnet-lightning testnet testnet-lightning signet signet-lightning bisq liquid liquidtestnet;do diff --git a/production/nginx/http-proxy-cache.conf b/production/nginx/http-proxy-cache.conf index 92d769b81..5505b094b 100644 --- a/production/nginx/http-proxy-cache.conf +++ b/production/nginx/http-proxy-cache.conf @@ -5,3 +5,4 @@ proxy_cache_path /var/cache/nginx/slurper keys_zone=slurper:20m levels=1:2 inact proxy_cache_path /var/cache/nginx/services keys_zone=services:20m levels=1:2 inactive=365d max_size=100m; proxy_cache_path /var/cache/nginx/markets keys_zone=markets:20m levels=1:2 inactive=365d max_size=100m; types_hash_max_size 4096; +proxy_buffer_size 8k; \ No newline at end of file diff --git a/scripts/get_backend_hash.sh b/scripts/get_backend_hash.sh new file mode 100644 index 000000000..1e3935557 --- /dev/null +++ b/scripts/get_backend_hash.sh @@ -0,0 +1,7 @@ +for LOCATION in fmt va1 fra tk7 +do + for NODE in 201 202 203 204 205 206 + do + echo $(curl -sk https://node$NODE.$LOCATION.mempool.space/api/v1/backend-info) + done +done \ No newline at end of file diff --git a/unfurler/src/config.ts b/unfurler/src/config.ts index 76bc2a75f..445ae4514 100644 --- a/unfurler/src/config.ts +++ b/unfurler/src/config.ts @@ -1,4 +1,16 @@ -const configFile = require('../config.json'); +const fs = require('fs'); +const path = require('path'); + +const configPath = process.env.UNFURLER_CONFIG || './config.json'; +const absolutePath = path.resolve(configPath); +let config; + +try { + config = JSON.parse(fs.readFileSync(absolutePath, 'utf8')); +} catch (e) { + console.error(`Could not read config file ${absolutePath}: ${e}`); + process.exit(-1); +} interface IConfig { SERVER: { @@ -57,7 +69,7 @@ class Config implements IConfig { SYSLOG: IConfig['SYSLOG']; constructor() { - const configs = this.merge(configFile, defaults); + const configs = this.merge(config, defaults); this.SERVER = configs.SERVER; this.MEMPOOL = configs.MEMPOOL; this.PUPPETEER = configs.PUPPETEER;