diff --git a/.github/dependabot.yml b/.github/dependabot.yml index afdc6d828a..8bad3e3cca 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -10,7 +10,7 @@ updates: default-days: 7 semver-major-days: 30 semver-minor-days: 14 - semver-patch-days: 3 + semver-patch-days: 7 ignore: - dependency-name: "*" update-types: @@ -42,7 +42,7 @@ updates: default-days: 7 semver-major-days: 30 semver-minor-days: 14 - semver-patch-days: 3 + semver-patch-days: 7 ignore: - dependency-name: "*" update-types: @@ -55,7 +55,7 @@ updates: schedule: interval: weekly cooldown: - default-days: 3 + default-days: 7 ignore: - dependency-name: "*" update-types: @@ -66,7 +66,7 @@ updates: schedule: interval: weekly cooldown: - default-days: 3 + default-days: 7 ignore: - dependency-name: "*" update-types: @@ -77,7 +77,7 @@ updates: schedule: interval: weekly cooldown: - default-days: 3 + default-days: 7 ignore: - dependency-name: "*" update-types: diff --git a/.github/workflows/backend-integration.yml b/.github/workflows/backend-integration.yml index 0814e91ef6..43f76b3c16 100644 --- a/.github/workflows/backend-integration.yml +++ b/.github/workflows/backend-integration.yml @@ -23,12 +23,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.node }}/integration - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" @@ -36,7 +36,7 @@ jobs: cache-dependency-path: '${{ matrix.node }}/integration/backend/package-lock.json' - name: Cache node modules - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.node }}/integration/backend/node_modules key: ${{ runner.os }}-backend-integration-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.node }}/integration/backend/package-lock.json') }} @@ -50,7 +50,7 @@ jobs: working-directory: ${{ matrix.node }}/integration - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | ~/.cargo/bin/ @@ -69,7 +69,7 @@ jobs: toolchain: ${{ steps.gettoolchain.outputs.toolchain }} - name: Install dependencies - run: npm ci + run: bash meta/scripts/check-install-scripts.sh && npm ci working-directory: ${{ matrix.node }}/integration/backend - name: Build backend diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 468571f54f..cb1e247ff5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,12 +23,12 @@ jobs: name: Backend (${{ matrix.flavor }}) - node ${{ matrix.node }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.node }}/${{ matrix.flavor }} - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" @@ -39,7 +39,7 @@ jobs: run: npm install -g npm@11.12.0 - name: Cache node modules - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.node }}/${{ matrix.flavor }}/backend/node_modules key: ${{ runner.os }}-backend-${{ matrix.flavor }}-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.node }}/${{ matrix.flavor }}/backend/package-lock.json') }} @@ -53,7 +53,7 @@ jobs: working-directory: ${{ matrix.node }}/${{ matrix.flavor }} - name: Cache Rust dependencies - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | ~/.cargo/bin/ @@ -75,12 +75,12 @@ jobs: - name: Install if: ${{ matrix.flavor == 'dev'}} - run: npm ci + run: bash meta/scripts/check-install-scripts.sh && npm ci working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend - name: Install (Prod dependencies only) if: ${{ matrix.flavor == 'prod'}} - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/backend - name: Lint @@ -106,12 +106,12 @@ jobs: runs-on: mempool-ci steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: assets - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" @@ -122,7 +122,7 @@ jobs: run: npm install -g npm@11.12.0 - name: Cache node modules for frontend - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: assets/frontend/node_modules key: ${{ runner.os }}-cache-frontend-node-${{ matrix.node }}-${{ hashFiles('assets/frontend/package-lock.json') }} @@ -131,13 +131,13 @@ jobs: ${{ runner.os }}-cache-frontend- - name: Install (Prod dependencies only) - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional working-directory: assets/frontend - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -146,7 +146,7 @@ jobs: - name: Restore promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip @@ -179,20 +179,20 @@ jobs: run: zip -jrq promo-video-assets.zip assets/frontend/src/resources/promo-video/* - name: Upload mining pool assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: mining-pool-assets path: mining-pool-assets.zip - name: Upload promo video assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: promo-video-assets path: promo-video-assets.zip - name: Save mining pool assets cache id: cache-mining-pool-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -200,7 +200,7 @@ jobs: - name: Save promo video assets cache id: cache-promo-video-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip @@ -219,12 +219,12 @@ jobs: name: Frontend (${{ matrix.flavor }}) - node ${{ matrix.node }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.node }}/${{ matrix.flavor }} - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node }} registry-url: "https://registry.npmjs.org" @@ -235,7 +235,7 @@ jobs: run: npm install -g npm@11.12.0 - name: Cache node modules - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.node }}/${{ matrix.flavor }}/frontend/node_modules key: ${{ runner.os }}-frontend-${{ matrix.flavor }}-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.node }}/${{ matrix.flavor }}/frontend/package-lock.json') }} @@ -244,13 +244,13 @@ jobs: ${{ runner.os }}-frontend-${{ matrix.flavor }}- - name: Install (Prod dependencies only) - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional if: ${{ matrix.flavor == 'prod'}} working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend - name: Install if: ${{ matrix.flavor == 'dev'}} - run: npm ci + run: bash meta/scripts/check-install-scripts.sh && npm ci working-directory: ${{ matrix.node }}/${{ matrix.flavor }}/frontend - name: Lint @@ -264,7 +264,7 @@ jobs: - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -273,14 +273,14 @@ jobs: - name: Restore promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip key: promo-video-assets-cache - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: mining-pool-assets @@ -288,7 +288,7 @@ jobs: run: unzip -o mining-pool-assets.zip -d ${{ matrix.node }}/${{ matrix.flavor }}/frontend/src/resources/mining-pools - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: promo-video-assets @@ -322,12 +322,12 @@ jobs: name: E2E tests for ${{ matrix.module }} steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: ${{ matrix.module }} - name: Setup node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: ${{ matrix.node }} cache: "npm" @@ -337,7 +337,7 @@ jobs: run: npm install -g npm@11.12.0 - name: Cache node modules for e2e - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ${{ matrix.module }}/frontend/node_modules key: ${{ runner.os }}-e2e-${{ matrix.module }}-node-${{ matrix.node }}-${{ hashFiles('${{ matrix.module }}/frontend/package-lock.json') }} @@ -348,7 +348,7 @@ jobs: - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -357,14 +357,14 @@ jobs: - name: Restore cached promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip key: promo-video-assets-cache - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: mining-pool-assets @@ -372,7 +372,7 @@ jobs: run: unzip -o mining-pool-assets.zip -d ${{ matrix.module }}/frontend/src/resources/mining-pools - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: promo-video-assets @@ -382,7 +382,7 @@ jobs: # mempool - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'mempool' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -407,7 +407,7 @@ jobs: # liquid - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'liquid' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -432,7 +432,7 @@ jobs: # testnet - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'testnet4' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -460,7 +460,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: docker diff --git a/.github/workflows/dependabot-provenance-check.yml b/.github/workflows/dependabot-provenance-check.yml index 41fb2abd95..592f18859a 100644 --- a/.github/workflows/dependabot-provenance-check.yml +++ b/.github/workflows/dependabot-provenance-check.yml @@ -18,17 +18,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Fetch Dependabot metadata id: metadata - uses: dependabot/fetch-metadata@v2 + uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2 with: github-token: "${{ secrets.GITHUB_TOKEN }}" - name: Setup Node if: steps.metadata.outputs.package-ecosystem == 'npm_and_yarn' - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: "24.13.0" diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 83a115fe68..b20e88d6f8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -24,7 +24,7 @@ jobs: name: Test built Docker images steps: - name: Checkout project - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Add SHORT_SHA env property with commit short sha run: | @@ -59,7 +59,7 @@ jobs: run: docker/init.sh "$TAG" - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Build frontend image locally run: | @@ -288,13 +288,13 @@ jobs: - name: Login to Docker for building - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Checkout project - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 # For PRs: use package.json version + short sha as TAG - name: Set TAG from service package.json for pull requests @@ -319,13 +319,13 @@ jobs: run: docker/init.sh "$TAG" - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: platforms: linux/amd64,linux/arm64 id: qemu - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: platforms: linux/amd64,linux/arm64 driver-opts: | @@ -336,7 +336,7 @@ jobs: run: echo ${{ steps.buildx.outputs.platforms }} - name: Cache Docker layers - uses: actions/cache@v3 + uses: actions/cache@6f8efc29b200d32929f49075959781ed54ec270c # v3 id: cache with: path: /tmp/.buildx-cache @@ -375,19 +375,19 @@ jobs: run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3 with: platforms: linux/amd64,linux/arm64 - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 with: platforms: linux/amd64,linux/arm64 driver-opts: | network=host - name: Login to Docker Hub - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} diff --git a/.github/workflows/e2e_parameterized.yml b/.github/workflows/e2e_parameterized.yml index e32f1c2fb6..bf5a09518c 100644 --- a/.github/workflows/e2e_parameterized.yml +++ b/.github/workflows/e2e_parameterized.yml @@ -38,25 +38,25 @@ jobs: fi - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: ref: ${{ steps.determine-ref.outputs.ref }} path: assets - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: node-version: 24.13.0 registry-url: "https://registry.npmjs.org" - name: Install (Prod dependencies only) - run: npm ci --omit=dev --omit=optional + run: bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional working-directory: assets/frontend - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -65,7 +65,7 @@ jobs: - name: Restore promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip @@ -98,20 +98,20 @@ jobs: run: zip -jrq promo-video-assets.zip assets/frontend/src/resources/promo-video/* - name: Upload mining pool assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: mining-pool-assets path: mining-pool-assets.zip - name: Upload promo video assets as artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: promo-video-assets path: promo-video-assets.zip - name: Save mining pool assets cache id: cache-mining-pool-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -119,7 +119,7 @@ jobs: - name: Save promo video assets cache id: cache-promo-video-save - uses: actions/cache/save@v4 + uses: actions/cache/save@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip @@ -146,13 +146,13 @@ jobs: fi - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: ref: ${{ steps.determine-ref.outputs.ref }} path: ${{ matrix.module }} - name: Setup node - uses: actions/setup-node@v3 + uses: actions/setup-node@3235b876344d2a9aa001b8d1453c930bba69e610 # v3 with: node-version: 24.13.0 cache: "npm" @@ -161,7 +161,7 @@ jobs: - name: Restore cached mining pool assets continue-on-error: true id: cache-mining-pool-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | mining-pool-assets.zip @@ -170,14 +170,14 @@ jobs: - name: Restore cached promo video assets continue-on-error: true id: cache-promo-video-restore - uses: actions/cache/restore@v4 + uses: actions/cache/restore@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | promo-video-assets.zip key: promo-video-assets-cache - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: mining-pool-assets @@ -185,7 +185,7 @@ jobs: run: unzip -o mining-pool-assets.zip -d ${{ matrix.module }}/frontend/src/resources/mining-pools - name: Download artifact - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: name: promo-video-assets @@ -195,7 +195,7 @@ jobs: # mempool - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'mempool' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -223,7 +223,7 @@ jobs: # liquid - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'liquid' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend @@ -251,7 +251,7 @@ jobs: # testnet - name: Chrome browser tests (${{ matrix.module }}) if: ${{ matrix.module == 'testnet4' }} - uses: cypress-io/github-action@v5 + uses: cypress-io/github-action@248bde77443c376edc45906ede03a1aba9da0462 # v5 with: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend diff --git a/.github/workflows/get_backend_block_height.yml b/.github/workflows/get_backend_block_height.yml index 90f712eeec..70d85b20f6 100644 --- a/.github/workflows/get_backend_block_height.yml +++ b/.github/workflows/get_backend_block_height.yml @@ -11,7 +11,7 @@ jobs: name: Get block height steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: repo diff --git a/.github/workflows/get_backend_hash.yml b/.github/workflows/get_backend_hash.yml index 9479703627..ee0bc6696b 100644 --- a/.github/workflows/get_backend_hash.yml +++ b/.github/workflows/get_backend_hash.yml @@ -11,7 +11,7 @@ jobs: name: Print backend hashes steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: repo diff --git a/.github/workflows/get_image_digest.yml b/.github/workflows/get_image_digest.yml index c8260f0a99..5c03c8d78c 100644 --- a/.github/workflows/get_image_digest.yml +++ b/.github/workflows/get_image_digest.yml @@ -18,7 +18,7 @@ jobs: name: Print digest for images steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 with: path: digest diff --git a/.github/workflows/project-review-status.yml b/.github/workflows/project-review-status.yml index 4d01cd6282..83be4de23b 100644 --- a/.github/workflows/project-review-status.yml +++ b/.github/workflows/project-review-status.yml @@ -11,7 +11,7 @@ permissions: jobs: project-automation: - uses: mempool/.github/.github/workflows/project-board-automation.yml@master + uses: mempool/.github/.github/workflows/project-board-automation.yml@267e0f38846bfb8cf1b118798dfb12def19ccb35 # master with: project-number: 8 secrets: diff --git a/.github/workflows/supply-chain-audit.yml b/.github/workflows/supply-chain-audit.yml index 521c7c5437..763e7d4683 100644 --- a/.github/workflows/supply-chain-audit.yml +++ b/.github/workflows/supply-chain-audit.yml @@ -17,10 +17,10 @@ jobs: name: Backend install-script audit steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: "24.13.0" registry-url: "https://registry.npmjs.org" @@ -40,10 +40,10 @@ jobs: name: Frontend install-script audit steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3 - name: Setup Node - uses: actions/setup-node@v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: "24.13.0" registry-url: "https://registry.npmjs.org" diff --git a/backend/.npmrc b/backend/.npmrc new file mode 100644 index 0000000000..7253a5ceee --- /dev/null +++ b/backend/.npmrc @@ -0,0 +1 @@ +min-release-age=7 diff --git a/backend/src/__e2e__/cluster-mempool/harness/run-harness.ts b/backend/src/__e2e__/cluster-mempool/harness/run-harness.ts index 3e252122b1..78c19844c2 100644 --- a/backend/src/__e2e__/cluster-mempool/harness/run-harness.ts +++ b/backend/src/__e2e__/cluster-mempool/harness/run-harness.ts @@ -244,8 +244,10 @@ class Harness { const added = await this.fetchTransactions(addedTxids); - // Apply diff before deleting from cache — processRemovals needs the tx data - const diff: MempoolDiff = { added, removed: removedTxids, accelerations: {} }; + const removed = removedTxids + .map(txid => this.mempool[txid]) + .filter((tx): tx is MempoolTransactionExtended => !!tx); + const diff: MempoolDiff = { added, removed, accelerations: {} }; const t0 = Date.now(); this.clusterMempool.applyMempoolChange(diff); const dt = Date.now() - t0; diff --git a/backend/src/__tests__/cluster-mempool/cluster-mempool.test.ts b/backend/src/__tests__/cluster-mempool/cluster-mempool.test.ts index 11d99b6eb6..0e857bd33c 100644 --- a/backend/src/__tests__/cluster-mempool/cluster-mempool.test.ts +++ b/backend/src/__tests__/cluster-mempool/cluster-mempool.test.ts @@ -95,18 +95,36 @@ describe('ClusterMempool', () => { expect(cm.getTxCount()).toBe(2); }); + it('should skip duplicate tx additions', () => { + const mempool = buildMempool([]); + const cm = new ClusterMempool(mempool); + const tx = makeTx(txid('a1'), 100, 100); + mempool[tx.txid] = tx; + + cm.applyMempoolChange({ + added: [tx, tx], + removed: [], + accelerations: {}, + }); + + expect(cm.getClusterCount()).toBe(1); + expect(cm.getTxCount()).toBe(1); + expect(cm.getBlocks(1)[0].txids).toEqual([tx.txid]); + }); + it('should handle removing a tx', () => { const parentId = txid('a1'); const childId = txid('a2'); + const childTx = makeTx(childId, 5000, 100, [parentId]); const mempool = buildMempool([ makeTx(parentId, 100, 100), - makeTx(childId, 5000, 100, [parentId]), + childTx, ]); const cm = new ClusterMempool(mempool); cm.applyMempoolChange({ added: [], - removed: [childId], + removed: [childTx], accelerations: {}, }); @@ -115,13 +133,61 @@ describe('ClusterMempool', () => { expect(cm.getClusterInfo(parentId)).not.toBeNull(); }); + it('should clean spentBy when removed tx is already missing from mempool', () => { + const parentId = txid('a1'); + const childId = txid('a2'); + const childTx = makeTx(childId, 5000, 100, [parentId]); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + childTx, + ]); + const cm = new ClusterMempool(mempool); + const spentBy = (cm as unknown as { spentBy: Map }).spentBy; + + expect(spentBy.get(`${parentId}:0`)).toBe(childId); + + delete mempool[childId]; + cm.applyMempoolChange({ + added: [], + removed: [childTx], + accelerations: {}, + }); + + expect(spentBy.has(`${parentId}:0`)).toBe(false); + }); + + it('should not delete spentBy for a replacement transaction', () => { + const parentId = txid('a1'); + const replacedId = txid('a2'); + const replacementId = txid('a3'); + const replacedTx = makeTx(replacedId, 5000, 100, [parentId]); + const replacementTx = makeTx(replacementId, 6000, 100, [parentId]); + const mempool = buildMempool([ + makeTx(parentId, 100, 100), + replacementTx, + ]); + const cm = new ClusterMempool(mempool); + const spentBy = (cm as unknown as { spentBy: Map }).spentBy; + + expect(spentBy.get(`${parentId}:0`)).toBe(replacementId); + + cm.applyMempoolChange({ + added: [], + removed: [replacedTx], + accelerations: {}, + }); + + expect(spentBy.get(`${parentId}:0`)).toBe(replacementId); + }); + it('should split cluster when middle tx is removed', () => { const a = txid('a1'); const b = txid('b1'); const c = txid('c1'); + const bTx = makeTx(b, 200, 100, [a]); const mempool = buildMempool([ makeTx(a, 100, 100), - makeTx(b, 200, 100, [a]), + bTx, makeTx(c, 300, 100, [b]), ]); const cm = new ClusterMempool(mempool); @@ -129,7 +195,7 @@ describe('ClusterMempool', () => { cm.applyMempoolChange({ added: [], - removed: [b], + removed: [bTx], accelerations: {}, }); @@ -260,7 +326,7 @@ describe('ClusterMempool', () => { const cm = new ClusterMempool(mempool); expect(cm.getClusterCount()).toBe(1); - cm.applyMempoolChange({ added: [], removed: [center], accelerations: {} }); + cm.applyMempoolChange({ added: [], removed: [centerTx], accelerations: {} }); expect(cm.getTxCount()).toBe(5); expect(cm.getClusterCount()).toBe(5); @@ -280,7 +346,7 @@ describe('ClusterMempool', () => { const cm = new ClusterMempool(mempool); expect(cm.getClusterCount()).toBe(1); - cm.applyMempoolChange({ added: [], removed: [c], accelerations: {} }); + cm.applyMempoolChange({ added: [], removed: [cTx], accelerations: {} }); expect(cm.getClusterCount()).toBe(1); expect(cm.getTxCount()).toBe(2); @@ -289,13 +355,14 @@ describe('ClusterMempool', () => { it('should produce singleton when tx is removed from 2-tx cluster', () => { const a = txid('a1'); const b = txid('b1'); + const bTx = makeTx(b, 200, 100, [a]); const mempool = buildMempool([ makeTx(a, 100, 100), - makeTx(b, 200, 100, [a]), + bTx, ]); const cm = new ClusterMempool(mempool); - cm.applyMempoolChange({ added: [], removed: [b], accelerations: {} }); + cm.applyMempoolChange({ added: [], removed: [bTx], accelerations: {} }); expect(cm.getClusterCount()).toBe(1); expect(cm.getTxCount()).toBe(1); @@ -322,7 +389,7 @@ describe('ClusterMempool', () => { const cm = new ClusterMempool(mempool); expect(cm.getClusterCount()).toBe(1); - cm.applyMempoolChange({ added: [], removed: [hub], accelerations: {} }); + cm.applyMempoolChange({ added: [], removed: [hubTx], accelerations: {} }); expect(cm.getClusterCount()).toBe(3); expect(cm.getTxCount()).toBe(3); @@ -467,7 +534,7 @@ describe('ClusterMempool', () => { cm.applyMempoolChange({ added: [], - removed: [txid('nonexistent')], + removed: [makeTx(txid('nonexistent'), 100, 100)], accelerations: {}, }); diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 613b779323..0d3631928b 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -511,7 +511,7 @@ class Blocks { if (config.MEMPOOL.CLUSTER_MEMPOOL) { memPool.clusterMempool?.applyMempoolChange({ added: [], - removed: txIds, + removed: transactions, accelerations: mempool.getAccelerations(), }); } @@ -520,6 +520,7 @@ class Blocks { delete _memPool[txId]; rbfCache.mined(txId); } + redisCache.queueTransactionsForRemoval(txIds); let candidates; let transactionIds: string[]; @@ -1288,7 +1289,7 @@ class Blocks { if (config.REDIS.ENABLED) { await redisCache.$updateBlocks(this.blocks); await redisCache.$updateBlockSummaries(this.blockSummaries); - await redisCache.$removeTransactions(txIds); + await redisCache.$removeTransactions(); await rbfCache.updateCache(); } diff --git a/backend/src/api/mempool.ts b/backend/src/api/mempool.ts index 1bd919c6c0..bee3e10dbb 100644 --- a/backend/src/api/mempool.ts +++ b/backend/src/api/mempool.ts @@ -382,6 +382,7 @@ class Mempool { for (const tx of deletedTransactions) { delete this.mempoolCache[tx.txid]; } + redisCache.queueTransactionsForRemoval(deletedTransactions.map(tx => tx.txid)); } const candidates = await this.getNextCandidates(minFeeMempool, minFeeTip, deletedTransactions); @@ -398,7 +399,7 @@ class Mempool { if (config.MEMPOOL.CLUSTER_MEMPOOL && (newTransactions.length || deletedTransactions.length || accelerationDelta.length)) { this.clusterMempool?.applyMempoolChange({ added: newTransactions, - removed: deletedTransactions.map(tx => tx.txid), + removed: deletedTransactions, accelerations: this.getAccelerations(), }); } @@ -428,7 +429,7 @@ class Mempool { // Update Redis cache if (config.REDIS.ENABLED) { await redisCache.$flushTransactions(); - await redisCache.$removeTransactions(deletedTransactions.map(tx => tx.txid)); + await redisCache.$removeTransactions(); await rbfCache.updateCache(); } diff --git a/backend/src/api/redis-cache.ts b/backend/src/api/redis-cache.ts index 80a947c12f..5858603c71 100644 --- a/backend/src/api/redis-cache.ts +++ b/backend/src/api/redis-cache.ts @@ -23,11 +23,14 @@ class RedisCache { private pauseFlush: boolean = false; private cacheQueue: MempoolTransactionExtended[] = []; - private removeQueue: string[] = []; + private removeQueue = new Set(); + private removeQueueFlushInProgress: boolean = false; private rbfCacheQueue: { type: string, txid: string, value: any }[] = []; private rbfRemoveQueue: { type: string, txid: string }[] = []; private txFlushLimit: number = 10000; private ignoreBlocksCache = false; + private reconciliationInProgress: boolean = false; + private reconciliationCursor: string = '0'; constructor() { if (config.REDIS.ENABLED) { @@ -39,6 +42,7 @@ class RedisCache { }; void this.$ensureConnected(); setInterval(() => { void this.$ensureConnected(); }, 10000); + setInterval(() => { void this.$reconcileMempoolTransactions(); }, 30000); } } @@ -92,7 +96,7 @@ class RedisCache { private async $onConnected(): Promise { await this.$flushTransactions(); - await this.$removeTransactions([]); + await this.$removeTransactions(); await this.$flushRbfQueues(); } @@ -134,6 +138,7 @@ class RedisCache { if (!config.REDIS.ENABLED) { return; } + this.removeQueue.delete(tx.txid); this.cacheQueue.push(tx); if (this.cacheQueue.length >= this.txFlushLimit) { if (!this.pauseFlush) { @@ -182,32 +187,88 @@ class RedisCache { } } + queueTransactionsForRemoval(transactions: string[]): void { + if (!config.REDIS.ENABLED) { + return; + } + + for (const txid of transactions) { + this.removeQueue.add(txid); + } + } + /** @asyncSafe */ - async $removeTransactions(transactions: string[]): Promise { + async $removeTransactions(transactions: string[] = []): Promise { if (!config.REDIS.ENABLED) { return; } - const toRemove = this.removeQueue.concat(transactions); - this.removeQueue = []; - let failed: string[] = []; - let numRemoved = 0; - if (this.connected) { + for (const txid of transactions) { + this.removeQueue.add(txid); + } + await this.$flushQueuedMempoolTxRemovals(); + } + + // incrementally reconcile the redis cache with the in-memory mempool + // by scanning for cached txs no longer in the mempool and marking for deletion + // each invocation scans 1000 keys, cursor loops back to the start after completing a full scan + /** @asyncSafe */ + private async $reconcileMempoolTransactions(): Promise { + if (!config.REDIS.ENABLED || !this.connected || this.reconciliationInProgress || !memPool.isInSync()) { + return; + } + + this.reconciliationInProgress = true; + try { + const result = await this.client.scan(this.reconciliationCursor, { + MATCH: 'mempool:tx:*', + COUNT: 1000 + }); + const mempool = memPool.getMempool(); + let staleCount = 0; + this.reconciliationCursor = result.cursor.toString(); + + for (const key of result.keys) { + const txid = key.slice('mempool:tx:'.length); + if (!mempool[txid]) { + this.removeQueue.add(txid); + staleCount++; + } + } + + if (staleCount) { + logger.debug(`Removing ${staleCount} stale transactions from the redis cache`); + void this.$removeTransactions(); + } + } catch (e) { + logger.warn(`Failed to reconcile Redis mempool cache: ${e instanceof Error ? e.message : e}`); + } finally { + this.reconciliationInProgress = false; + } + } + + /** @asyncSafe */ + private async $flushQueuedMempoolTxRemovals(): Promise { + if (!this.connected || !this.removeQueue.size || this.removeQueueFlushInProgress) { + return; + } + + this.removeQueueFlushInProgress = true; + const toRemove = Array.from(this.removeQueue); + this.removeQueue.clear(); + try { const sliceLength = config.REDIS.BATCH_QUERY_BASE_SIZE; for (let i = 0; i < Math.ceil(toRemove.length / sliceLength); i++) { const slice = toRemove.slice(i * sliceLength, (i + 1) * sliceLength); try { await this.client.unlink(slice.map(txid => `mempool:tx:${txid}`)); - numRemoved+= sliceLength; logger.debug(`Deleted ${slice.length} transactions from the Redis cache`); } catch (e) { logger.warn(`Failed to remove ${slice.length} transactions from Redis cache: ${e instanceof Error ? e.message : e}`); - failed = failed.concat(slice); + this.queueTransactionsForRemoval(slice); } } - // concat instead of replace, in case more txs have been added in the meantime - this.removeQueue = this.removeQueue.concat(failed); - } else { - this.removeQueue = this.removeQueue.concat(toRemove); + } finally { + this.removeQueueFlushInProgress = false; } } @@ -308,7 +369,7 @@ class RedisCache { } /** @asyncSafe */ - async $getMempool(): Promise<{ [txid: string]: MempoolTransactionExtended }> { + async $getMempool(validTxids?: Set): Promise<{ [txid: string]: MempoolTransactionExtended }> { if (!config.REDIS.ENABLED) { return {}; } @@ -319,7 +380,9 @@ class RedisCache { const start = Date.now(); const mempool = {}; try { - const mempoolList = await this.scanKeys('mempool:tx:*'); + const mempoolList = validTxids?.size + ? await this.loadKeys('mempool:tx:*', Array.from(validTxids)) + : await this.scanKeys('mempool:tx:*'); for (const tx of mempoolList) { mempool[tx.key] = tx.value; } @@ -350,14 +413,14 @@ class RedisCache { } /** @asyncUnsafe */ - async $loadCache(): Promise { + async $loadCache(validTxids?: Set): Promise { if (!config.REDIS.ENABLED) { return; } logger.info('Restoring mempool and blocks data from Redis cache'); // Load mempool - const loadedMempool = await this.$getMempool(); + const loadedMempool = await this.$getMempool(validTxids); this.inflateLoadedTxs(loadedMempool); // Load rbf data const rbfTxs = await this.$getRbfEntries('tx'); @@ -432,6 +495,29 @@ class RedisCache { return result; } + /** @asyncUnsafe */ + private async loadKeys(pattern, keys: string[]): Promise<{ key: string, value: T }[]> { + const prefix = pattern.slice(0, -1); + const result: { key: string, value: T }[] = []; + let count = 0; + /** @asyncUnsafe */ + const processValues = async (slice: string[]): Promise => { + const values = await this.client.MGET(slice.map(key => `${prefix}${key}`)); + for (let i = 0; i < values.length; i++) { + if (values[i]) { + result.push({ key: slice[i], value: JSON.parse(values[i]) }); + count++; + } + } + logger.info(`loaded ${count} entries from Redis cache`); + }; + for (let i = 0; i < Math.ceil(keys.length / 10000); i++) { + const slice = keys.slice(i * 10000, (i + 1) * 10000); + await processValues(slice); + } + return result; + } + public setIgnoreBlocksCache(): void { this.ignoreBlocksCache = true; } diff --git a/backend/src/cluster-mempool/cluster-mempool.ts b/backend/src/cluster-mempool/cluster-mempool.ts index 98d94553cd..92e1bd5fa6 100644 --- a/backend/src/cluster-mempool/cluster-mempool.ts +++ b/backend/src/cluster-mempool/cluster-mempool.ts @@ -6,7 +6,7 @@ import logger from '../logger'; export interface MempoolDiff { added: MempoolTransactionExtended[]; - removed: string[]; + removed: MempoolTransactionExtended[]; accelerations: { [txid: string]: { feeDelta: number } }; } @@ -42,7 +42,6 @@ const DEFAULT_COST_BUDGET = 75000; export class ClusterMempool { private clusters = new Map(); private txToCluster = new Map(); - private parentMap = new Map>(); private spentBy = new Map(); private mempool: Readonly<{ [txid: string]: MempoolTransactionExtended }>; private accelerations: { [txid: string]: { feeDelta: number } } = {}; @@ -147,15 +146,15 @@ export class ClusterMempool { } private buildFromMempool(): void { - this.buildRelativeMaps(); - const components = this.findMempoolComponents(); + const parentMap = this.buildRelativeMaps(); + const components = this.findMempoolComponents(parentMap); for (const component of components) { - this.createClusterFromTxids(component); + this.createClusterFromTxids(component, parentMap); } } - private buildRelativeMaps(): void { - this.parentMap.clear(); + private buildRelativeMaps(): Map> { + const parentMap = new Map>(); this.spentBy.clear(); for (const txid in this.mempool) { const tx = this.mempool[txid]; @@ -167,18 +166,19 @@ export class ClusterMempool { } } if (txParents.size > 0) { - this.parentMap.set(txid, txParents); + parentMap.set(txid, txParents); } } + return parentMap; } - private findMempoolComponents(): Set[] { + private findMempoolComponents(parentMap: Map>): Set[] { const visited = new Set(); const components: Set[] = []; for (const txid in this.mempool) { if (!visited.has(txid)) { - const component = this.dfsComponent(txid, visited); + const component = this.dfsComponent(txid, visited, parentMap); components.push(component); } } @@ -187,7 +187,8 @@ export class ClusterMempool { private dfsComponent( startTxid: string, - visited: Set + visited: Set, + parentMap: Map> ): Set { const component = new Set(); const stack = [startTxid]; @@ -197,7 +198,7 @@ export class ClusterMempool { visited.add(current); component.add(current); - const txParents = this.parentMap.get(current); + const txParents = parentMap.get(current); if (txParents) { for (const p of txParents) { if (!visited.has(p)) { @@ -229,7 +230,8 @@ export class ClusterMempool { } private createClusterFromTxids( - txids: Set + txids: Set, + parentMap: Map> ): Cluster | null { const clusterId = this.nextClusterId++; const depgraph = new DepGraph(); @@ -246,7 +248,7 @@ export class ClusterMempool { } for (const txid of txids) { - const txParents = this.parentMap.get(txid); + const txParents = parentMap.get(txid); if (txParents) { for (const parentTxid of txParents) { if (txids.has(parentTxid)) { @@ -349,27 +351,25 @@ export class ClusterMempool { return relatives; } - private processRemovals(removed: string[]): void { - for (const txid of removed) { - const tx = this.mempool[txid]; - if (tx) { - for (const vin of tx.vin) { - if (!vin.is_coinbase) { - this.spentBy.delete(`${vin.txid}:${vin.vout}`); + private processRemovals(removed: MempoolTransactionExtended[]): void { + for (const tx of removed) { + for (const vin of tx.vin) { + if (!vin.is_coinbase) { + const spentOutpoint = `${vin.txid}:${vin.vout}`; + if (this.spentBy.get(spentOutpoint) === tx.txid) { + this.spentBy.delete(spentOutpoint); } } - } else if (this.txToCluster.has(txid)) { - logger.warn(`ClusterMempool.processRemovals: ${txid} missing from mempool, spentBy cleanup skipped`); } } - for (const txid of removed) { - const match = this.getClusterForTx(txid); + for (const tx of removed) { + const match = this.getClusterForTx(tx.txid); if (match) { match.cluster.depgraph.removeTransactions(new Set([match.clusterTx])); - match.cluster.txs.delete(txid); + match.cluster.txs.delete(tx.txid); match.cluster.linearization = match.cluster.linearization.filter(t => t !== match.clusterTx); - this.txToCluster.delete(txid); + this.txToCluster.delete(tx.txid); match.cluster.dirty = true; } } @@ -431,6 +431,12 @@ export class ClusterMempool { for (const tx of added) { const txid = tx.txid; + // sanity check for duplicate transactions + if (this.getClusterForTx(txid) !== null) { + logger.warn(`ClusterMempool.processAdditions: ${txid} was already added, skipping`); + continue; + } + for (const vin of tx.vin) { if (!vin.is_coinbase) { this.spentBy.set(`${vin.txid}:${vin.vout}`, txid); diff --git a/backend/src/index.ts b/backend/src/index.ts index 2e7e9a13d4..3b22960914 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -177,8 +177,14 @@ class Server { if (config.MEMPOOL.CACHE_ENABLED) { await diskCache.$loadMempoolCache(); } else if (config.REDIS.ENABLED) { + let currentMempoolTxids: Set | undefined; + try { + currentMempoolTxids = new Set(await bitcoinApi.$getRawMempool()); + } catch (e) { + logger.warn(`Failed to fetch raw mempool before loading Redis cache. Reason: ${e instanceof Error ? e.message : e}`); + } /** @asyncUnsafe */ - await redisCache.$loadCache(); + await redisCache.$loadCache(currentMempoolTxids); } } diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 049f13f6fb..1d4eb0132d 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -19,7 +19,7 @@ ENV PATH="/usr/local/cargo/bin:$PATH" COPY --from=backend . . COPY --from=rustgbt . ../rust/ ENV FD=/build/rust-gbt -RUN npm install --omit=dev --omit=optional +RUN bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional WORKDIR /build RUN npm run package diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index c17f241d3d..90bc9a57eb 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -9,7 +9,7 @@ COPY . . RUN apt-get update RUN apt-get install -y build-essential rsync RUN cp mempool-frontend-config.sample.json mempool-frontend-config.json -RUN npm install --omit=dev --omit=optional +RUN bash meta/scripts/check-install-scripts.sh && npm ci --omit=dev --omit=optional RUN npm run build diff --git a/frontend/.npmrc b/frontend/.npmrc new file mode 100644 index 0000000000..7253a5ceee --- /dev/null +++ b/frontend/.npmrc @@ -0,0 +1 @@ +min-release-age=7