diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6d2fc387f..976fe5a7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,16 +3,19 @@ name: CI Pipeline for the Backend and Frontend on: pull_request: types: [opened, review_requested, synchronize] + push: + branches: + - master jobs: backend: - if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" + if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["20", "21"] + node: ["22.14.0"] flavor: ["dev", "prod"] fail-fast: false - runs-on: "ubuntu-latest" + runs-on: ubuntu-latest name: Backend (${{ matrix.flavor }}) - node ${{ matrix.node }} steps: @@ -66,7 +69,7 @@ jobs: cache: name: "Cache assets for builds" - runs-on: "ubuntu-latest" + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 @@ -157,13 +160,13 @@ jobs: frontend: needs: cache - if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" + if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" strategy: matrix: - node: ["20", "21"] + node: ["22.14.0"] flavor: ["dev", "prod"] fail-fast: false - runs-on: "ubuntu-latest" + runs-on: ubuntu-latest name: Frontend (${{ matrix.flavor }}) - node ${{ matrix.node }} steps: @@ -245,8 +248,8 @@ jobs: VERBOSE: 1 e2e: - if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" - runs-on: "ubuntu-latest" + if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" + runs-on: ubuntu-latest needs: frontend strategy: fail-fast: false @@ -263,7 +266,7 @@ jobs: - name: Setup node uses: actions/setup-node@v3 with: - node-version: 20 + node-version: 22 cache: "npm" cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json @@ -309,7 +312,7 @@ jobs: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend build: npm run config:defaults:${{ matrix.module }} - start: npm run start:local-staging + start: npm run start:parameterized wait-on: "http://localhost:4200" wait-on-timeout: 120 record: true @@ -334,7 +337,7 @@ jobs: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend build: npm run config:defaults:${{ matrix.module }} - start: npm run start:local-staging + start: npm run start:parameterized wait-on: "http://localhost:4200" wait-on-timeout: 120 record: true @@ -359,7 +362,7 @@ jobs: tag: ${{ github.event_name }} working-directory: ${{ matrix.module }}/frontend build: npm run config:defaults:mempool - start: npm run start:local-staging + start: npm run start:parameterized wait-on: "http://localhost:4200" wait-on-timeout: 120 record: true @@ -375,10 +378,9 @@ jobs: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} - validate_docker_json: - if: "!contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')" - runs-on: "ubuntu-latest" + if: "(github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'ops') && !contains(github.head_ref, 'ops/')) || github.event_name == 'push'" + runs-on: ubuntu-latest name: Validate generated backend Docker JSON steps: diff --git a/.github/workflows/docker_update_latest_tag.yml b/.github/workflows/docker_update_latest_tag.yml new file mode 100644 index 000000000..5d21697d5 --- /dev/null +++ b/.github/workflows/docker_update_latest_tag.yml @@ -0,0 +1,181 @@ +name: Docker - Update latest tag + +on: + workflow_dispatch: + inputs: + tag: + description: 'The Docker image tag to pull' + required: true + type: string + +jobs: + retag-and-push: + strategy: + matrix: + service: + - frontend + - backend + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Docker Buildx + uses: docker/setup-buildx-action@v3 + id: buildx + with: + install: true + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Get source image manifest and SHAs + id: source-manifest + run: | + set -e + echo "Fetching source manifest..." + MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:${{ github.event.inputs.tag }}) + if [ -z "$MANIFEST" ]; then + echo "No manifest found. Assuming single-arch image." + exit 1 + fi + + echo "Original source manifest:" + echo "$MANIFEST" | jq . + + AMD64_SHA=$(echo "$MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest') + ARM64_SHA=$(echo "$MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="arm64" and .platform.os=="linux") | .digest') + + if [ -z "$AMD64_SHA" ] || [ -z "$ARM64_SHA" ]; then + echo "Source image is not multi-arch (missing amd64 or arm64)" + exit 1 + fi + + echo "Source amd64 manifest digest: $AMD64_SHA" + echo "Source arm64 manifest digest: $ARM64_SHA" + + echo "amd64_sha=$AMD64_SHA" >> $GITHUB_OUTPUT + echo "arm64_sha=$ARM64_SHA" >> $GITHUB_OUTPUT + + - name: Pull and retag architecture-specific images + run: | + set -e + + docker buildx inspect --bootstrap + + # Remove any existing local images to avoid cache interference + echo "Removing existing local images if they exist..." + docker image rm ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:${{ github.event.inputs.tag }} || true + + # Pull amd64 image by digest + echo "Pulling amd64 image by digest..." + docker pull --platform linux/amd64 ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} + PULLED_AMD64_MANIFEST_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} --format '{{index .RepoDigests 0}}' | cut -d@ -f2) + PULLED_AMD64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} --format '{{.Id}}') + echo "Pulled amd64 manifest digest: $PULLED_AMD64_MANIFEST_DIGEST" + echo "Pulled amd64 image ID (sha256): $PULLED_AMD64_IMAGE_ID" + + # Pull arm64 image by digest + echo "Pulling arm64 image by digest..." + docker pull --platform linux/arm64 ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} + PULLED_ARM64_MANIFEST_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} --format '{{index .RepoDigests 0}}' | cut -d@ -f2) + PULLED_ARM64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} --format '{{.Id}}') + echo "Pulled arm64 manifest digest: $PULLED_ARM64_MANIFEST_DIGEST" + echo "Pulled arm64 image ID (sha256): $PULLED_ARM64_IMAGE_ID" + + # Tag the images + docker tag ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 + docker tag ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 + + # Verify tagged images + TAGGED_AMD64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 --format '{{.Id}}') + TAGGED_ARM64_IMAGE_ID=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 --format '{{.Id}}') + echo "Tagged amd64 image ID (sha256): $TAGGED_AMD64_IMAGE_ID" + echo "Tagged arm64 image ID (sha256): $TAGGED_ARM64_IMAGE_ID" + + - name: Push architecture-specific images + run: | + set -e + + echo "Pushing amd64 image..." + docker push ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 + PUSHED_AMD64_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 --format '{{index .RepoDigests 0}}' | cut -d@ -f2) + echo "Pushed amd64 manifest digest (local): $PUSHED_AMD64_DIGEST" + + # Fetch manifest from registry after push + echo "Fetching pushed amd64 manifest from registry..." + PUSHED_AMD64_REGISTRY_MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64) + PUSHED_AMD64_REGISTRY_DIGEST=$(echo "$PUSHED_AMD64_REGISTRY_MANIFEST" | jq -r '.config.digest') + echo "Pushed amd64 manifest digest (registry): $PUSHED_AMD64_REGISTRY_DIGEST" + + echo "Pushing arm64 image..." + docker push ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 + PUSHED_ARM64_DIGEST=$(docker inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 --format '{{index .RepoDigests 0}}' | cut -d@ -f2) + echo "Pushed arm64 manifest digest (local): $PUSHED_ARM64_DIGEST" + + # Fetch manifest from registry after push + echo "Fetching pushed arm64 manifest from registry..." + PUSHED_ARM64_REGISTRY_MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64) + PUSHED_ARM64_REGISTRY_DIGEST=$(echo "$PUSHED_ARM64_REGISTRY_MANIFEST" | jq -r '.config.digest') + echo "Pushed arm64 manifest digest (registry): $PUSHED_ARM64_REGISTRY_DIGEST" + + - name: Create and push multi-arch manifest with original digests + run: | + set -e + + echo "Creating multi-arch manifest with original digests..." + docker manifest create ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest \ + ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.amd64_sha }} \ + ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}@${{ steps.source-manifest.outputs.arm64_sha }} + + echo "Pushing multi-arch manifest..." + docker manifest push ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest + + - name: Clean up intermediate tags + if: success() + run: | + docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-amd64 || true + docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest-arm64 || true + docker rmi ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:${{ github.event.inputs.tag }} || true + + - name: Verify final manifest + run: | + set -e + echo "Fetching final generated manifest..." + FINAL_MANIFEST=$(docker manifest inspect ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest) + echo "Generated final manifest:" + echo "$FINAL_MANIFEST" | jq . + + FINAL_AMD64_SHA=$(echo "$FINAL_MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="amd64" and .platform.os=="linux") | .digest') + FINAL_ARM64_SHA=$(echo "$FINAL_MANIFEST" | jq -r '.manifests[] | select(.platform.architecture=="arm64" and .platform.os=="linux") | .digest') + + echo "Final amd64 manifest digest: $FINAL_AMD64_SHA" + echo "Final arm64 manifest digest: $FINAL_ARM64_SHA" + + # Compare all digests + echo "Comparing digests..." + echo "Source amd64 digest: ${{ steps.source-manifest.outputs.amd64_sha }}" + echo "Pulled amd64 manifest digest: $PULLED_AMD64_MANIFEST_DIGEST" + echo "Pushed amd64 manifest digest (local): $PUSHED_AMD64_DIGEST" + echo "Pushed amd64 manifest digest (registry): $PUSHED_AMD64_REGISTRY_DIGEST" + echo "Final amd64 digest: $FINAL_AMD64_SHA" + echo "Source arm64 digest: ${{ steps.source-manifest.outputs.arm64_sha }}" + echo "Pulled arm64 manifest digest: $PULLED_ARM64_MANIFEST_DIGEST" + echo "Pushed arm64 manifest digest (local): $PUSHED_ARM64_DIGEST" + echo "Pushed arm64 manifest digest (registry): $PUSHED_ARM64_REGISTRY_DIGEST" + echo "Final arm64 digest: $FINAL_ARM64_SHA" + + if [ "$FINAL_AMD64_SHA" != "${{ steps.source-manifest.outputs.amd64_sha }}" ] || [ "$FINAL_ARM64_SHA" != "${{ steps.source-manifest.outputs.arm64_sha }}" ]; then + echo "Error: Final manifest SHAs do not match source SHAs" + exit 1 + fi + + echo "Successfully created multi-arch ${{ secrets.DOCKER_USERNAME }}/${{ matrix.service }}:latest from ${{ github.event.inputs.tag }}" diff --git a/.github/workflows/e2e_parameterized.yml b/.github/workflows/e2e_parameterized.yml new file mode 100644 index 000000000..8b07ffe82 --- /dev/null +++ b/.github/workflows/e2e_parameterized.yml @@ -0,0 +1,273 @@ +name: 'Parameterized e2e tests' + +on: + workflow_dispatch: + inputs: + ref: + description: 'Branch name or Pull Request number (e.g., master or 6102)' + required: true + default: 'master' + type: string + mempool_hostname: + description: 'Mempool Hostname' + required: true + default: 'mempool.space' + type: string + liquid_hostname: + description: 'Liquid Hostname' + required: true + default: 'liquid.network' + type: string + +jobs: + cache: + name: "Cache assets for builds" + runs-on: ubuntu-latest + steps: + - name: Determine checkout ref + id: determine-ref + run: | + REF_INPUT="${{ github.event.inputs.ref }}" + if [[ "$REF_INPUT" =~ ^[0-9]+$ ]]; then + echo "ref=refs/pull/$REF_INPUT/head" >> $GITHUB_OUTPUT + else + echo "ref=$REF_INPUT" >> $GITHUB_OUTPUT + fi + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ steps.determine-ref.outputs.ref }} + path: assets + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 22.14.0 + registry-url: "https://registry.npmjs.org" + + - name: Install (Prod dependencies only) + run: 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 + with: + path: | + mining-pool-assets.zip + key: mining-pool-assets-cache + + - name: Restore promo video assets + continue-on-error: true + id: cache-promo-video-restore + uses: actions/cache/restore@v4 + with: + path: | + promo-video-assets.zip + key: promo-video-assets-cache + + - name: Unzip assets before building (src/resources) + continue-on-error: true + run: unzip -o mining-pool-assets.zip -d assets/frontend/src/resources/mining-pools + + - name: Unzip assets before building (src/resources) + continue-on-error: true + run: unzip -o promo-video-assets.zip -d assets/frontend/src/resources/promo-video + + # - name: Unzip assets before building (dist) + # continue-on-error: true + # run: unzip assets.zip -d assets/frontend/dist/mempool/browser/resources + + - name: Sync-assets + run: npm run sync-assets-dev + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MEMPOOL_CDN: 1 + VERBOSE: 1 + working-directory: assets/frontend + + - name: Zip mining-pool assets + run: zip -jrq mining-pool-assets.zip assets/frontend/src/resources/mining-pools/* + + - name: Zip promo-video assets + 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 + with: + name: mining-pool-assets + path: mining-pool-assets.zip + + - name: Upload promo video assets as artifact + uses: actions/upload-artifact@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 + with: + path: | + mining-pool-assets.zip + key: mining-pool-assets-cache + + - name: Save promo video assets cache + id: cache-promo-video-save + uses: actions/cache/save@v4 + with: + path: | + promo-video-assets.zip + key: promo-video-assets-cache + + e2e: + runs-on: ubuntu-latest + needs: cache + strategy: + fail-fast: false + matrix: + module: ["mempool", "liquid", "testnet4"] + + name: E2E tests for ${{ matrix.module }} + steps: + - name: Determine checkout ref + id: determine-ref + run: | + REF_INPUT="${{ github.event.inputs.ref }}" + if [[ "$REF_INPUT" =~ ^[0-9]+$ ]]; then + echo "ref=refs/pull/$REF_INPUT/head" >> $GITHUB_OUTPUT + else + echo "ref=$REF_INPUT" >> $GITHUB_OUTPUT + fi + + - name: Checkout + uses: actions/checkout@v3 + with: + ref: ${{ steps.determine-ref.outputs.ref }} + path: ${{ matrix.module }} + + - name: Setup node + uses: actions/setup-node@v3 + with: + node-version: 22.14.0 + cache: "npm" + cache-dependency-path: ${{ matrix.module }}/frontend/package-lock.json + + - name: Restore cached mining pool assets + continue-on-error: true + id: cache-mining-pool-restore + uses: actions/cache/restore@v4 + with: + path: | + mining-pool-assets.zip + key: mining-pool-assets-cache + + - name: Restore cached promo video assets + continue-on-error: true + id: cache-promo-video-restore + uses: actions/cache/restore@v4 + with: + path: | + promo-video-assets.zip + key: promo-video-assets-cache + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: mining-pool-assets + + - name: Unzip assets before building (src/resources) + run: unzip -o mining-pool-assets.zip -d ${{ matrix.module }}/frontend/src/resources/mining-pools + + - name: Download artifact + uses: actions/download-artifact@v4 + with: + name: promo-video-assets + + - name: Unzip assets before building (src/resources) + run: unzip -o promo-video-assets.zip -d ${{ matrix.module }}/frontend/src/resources/promo-video + + # mempool + - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'mempool' }} + uses: cypress-io/github-action@v5 + with: + tag: ${{ github.event_name }} + working-directory: ${{ matrix.module }}/frontend + build: npm run config:defaults:${{ matrix.module }} + start: npm run start:parameterized + wait-on: "http://localhost:4200" + wait-on-timeout: 120 + record: true + parallel: true + spec: | + cypress/e2e/mainnet/*.spec.ts + cypress/e2e/signet/*.spec.ts + group: Tests on Chrome (${{ matrix.module }}) + browser: "chrome" + ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" + env: + COMMIT_INFO_MESSAGE: ${{ github.event_name }} (${{ github.sha }}) - ref ${{ github.event.inputs.ref }} - ${{ github.event.inputs.mempool_hostname }} - ${{ github.event.inputs.liquid_hostname }} + MEMPOOL_HOSTNAME: ${{ github.event.inputs.mempool_hostname }} + LIQUID_HOSTNAME: ${{ github.event.inputs.liquid_hostname }} + MEMPOOL_CI_API_KEY: ${{ secrets.MEMPOOL_CI_API_KEY }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + + # liquid + - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'liquid' }} + uses: cypress-io/github-action@v5 + with: + tag: ${{ github.event_name }} + working-directory: ${{ matrix.module }}/frontend + build: npm run config:defaults:${{ matrix.module }} + start: npm run start:parameterized + wait-on: "http://localhost:4200" + wait-on-timeout: 120 + record: true + parallel: true + spec: | + cypress/e2e/liquid/liquid.spec.ts + cypress/e2e/liquidtestnet/liquidtestnet.spec.ts + group: Tests on Chrome (${{ matrix.module }}) + browser: "chrome" + ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" + env: + COMMIT_INFO_MESSAGE: ${{ github.event_name }} (${{ github.sha }}) - ref ${{ github.event.inputs.ref }} - ${{ github.event.inputs.mempool_hostname }} - ${{ github.event.inputs.liquid_hostname }} + MEMPOOL_HOSTNAME: ${{ github.event.inputs.mempool_hostname }} + LIQUID_HOSTNAME: ${{ github.event.inputs.liquid_hostname }} + MEMPOOL_CI_API_KEY: ${{ secrets.MEMPOOL_CI_API_KEY }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} + + # testnet + - name: Chrome browser tests (${{ matrix.module }}) + if: ${{ matrix.module == 'testnet4' }} + uses: cypress-io/github-action@v5 + with: + tag: ${{ github.event_name }} + working-directory: ${{ matrix.module }}/frontend + build: npm run config:defaults:mempool + start: npm run start:parameterized + wait-on: "http://localhost:4200" + wait-on-timeout: 120 + record: true + parallel: true + spec: | + cypress/e2e/testnet4/*.spec.ts + group: Tests on Chrome (${{ matrix.module }}) + browser: "chrome" + ci-build-id: "${{ github.sha }}-${{ github.workflow }}-${{ github.event_name }}" + env: + COMMIT_INFO_MESSAGE: ${{ github.event_name }} (${{ github.sha }}) - ref ${{ github.event.inputs.ref }} - ${{ github.event.inputs.mempool_hostname }} - ${{ github.event.inputs.liquid_hostname }} + MEMPOOL_HOSTNAME: ${{ github.event.inputs.mempool_hostname }} + LIQUID_HOSTNAME: ${{ github.event.inputs.liquid_hostname }} + MEMPOOL_CI_API_KEY: ${{ secrets.MEMPOOL_CI_API_KEY }} + CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + CYPRESS_PROJECT_ID: ${{ secrets.CYPRESS_PROJECT_ID }} diff --git a/.github/workflows/get_backend_block_height.yml b/.github/workflows/get_backend_block_height.yml index 52f3b038c..ae30188e5 100644 --- a/.github/workflows/get_backend_block_height.yml +++ b/.github/workflows/get_backend_block_height.yml @@ -4,7 +4,7 @@ on: [workflow_dispatch] jobs: print-backend-sha: - runs-on: 'ubuntu-latest' + runs-on: ubuntu-latest name: Get block height steps: - name: Checkout diff --git a/.github/workflows/get_backend_hash.yml b/.github/workflows/get_backend_hash.yml index 57950dee4..0e31735b6 100644 --- a/.github/workflows/get_backend_hash.yml +++ b/.github/workflows/get_backend_hash.yml @@ -4,7 +4,7 @@ on: [workflow_dispatch] jobs: print-backend-sha: - runs-on: 'ubuntu-latest' + runs-on: ubuntu-latest name: Print backend hashes steps: - name: Checkout diff --git a/.github/workflows/get_image_digest.yml b/.github/workflows/get_image_digest.yml index 7414eeb08..18ad39fde 100644 --- a/.github/workflows/get_image_digest.yml +++ b/.github/workflows/get_image_digest.yml @@ -10,7 +10,7 @@ on: type: string jobs: print-images-sha: - runs-on: 'ubuntu-latest' + runs-on: ubuntu-latest name: Print digest for images steps: - name: Checkout diff --git a/.github/workflows/on-tag.yml b/.github/workflows/on-tag.yml index 634a27ab9..1447ec4ab 100644 --- a/.github/workflows/on-tag.yml +++ b/.github/workflows/on-tag.yml @@ -2,7 +2,7 @@ name: Docker build on tag env: DOCKER_CLI_EXPERIMENTAL: enabled TAG_FMT: "^refs/tags/(((.?[0-9]+){3,4}))$" - DOCKER_BUILDKIT: 0 + DOCKER_BUILDKIT: 1 # Enable BuildKit for better performance COMPOSE_DOCKER_CLI_BUILD: 0 on: @@ -25,13 +25,12 @@ jobs: timeout-minutes: 120 name: Build and push to DockerHub steps: - # Workaround based on JonasAlfredsson/docker-on-tmpfs@v1.0.1 - name: Replace the current swap file shell: bash run: | - sudo swapoff /mnt/swapfile - sudo rm -v /mnt/swapfile - sudo fallocate -l 13G /mnt/swapfile + sudo swapoff /mnt/swapfile || true + sudo rm -f /mnt/swapfile + sudo fallocate -l 16G /mnt/swapfile sudo chmod 600 /mnt/swapfile sudo mkswap /mnt/swapfile sudo swapon /mnt/swapfile @@ -50,7 +49,7 @@ jobs: echo "Directory '/var/lib/docker' not found" exit 1 fi - sudo mount -t tmpfs -o size=10G tmpfs /var/lib/docker + sudo mount -t tmpfs -o size=12G tmpfs /var/lib/docker sudo systemctl restart docker sudo df -h | grep docker @@ -75,10 +74,16 @@ jobs: - name: Set up QEMU uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 id: qemu - name: Setup Docker buildx action uses: docker/setup-buildx-action@v3 + with: + platforms: linux/amd64,linux/arm64 + driver-opts: | + network=host id: buildx - name: Available platforms @@ -89,19 +94,19 @@ jobs: id: cache with: path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ github.sha }} + key: ${{ runner.os }}-buildx-${{ matrix.service }}-${{ github.sha }} restore-keys: | - ${{ runner.os }}-buildx- + ${{ runner.os }}-buildx-${{ matrix.service }}- - name: Run Docker buildx for ${{ matrix.service }} against tag run: | docker buildx build \ --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ --platform linux/amd64,linux/arm64 \ --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ - --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \ --build-context rustgbt=./rust \ --build-context backend=./backend \ - --output "type=registry" ./${{ matrix.service }}/ \ - --build-arg commitHash=$SHORT_SHA + --output "type=registry,push=true" \ + --build-arg commitHash=$SHORT_SHA \ + ./${{ matrix.service }}/ \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index a9b234d51..53d1c14db 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v20.8.0 +v22 diff --git a/LICENSE b/LICENSE index 1c368c00a..b6e67e523 100644 --- a/LICENSE +++ b/LICENSE @@ -10,7 +10,7 @@ However, this copyright license does not include an implied right or license to use any trademarks, service marks, logos, or trade names of Mempool Space K.K. or any other contributor to The Mempool Open Source Project. -The Mempool Open Source Project®, Mempool Accelerator™, Mempool Enterprise®, +The Mempool Open Source Project®, Mempool Accelerator®, Mempool Enterprise®, Mempool Liquidity™, mempool.space®, Be your own explorer™, Explore the full Bitcoin ecosystem™, Mempool Goggles™, the mempool Logo, the mempool Square Logo, the mempool block visualization Logo, the mempool Blocks Logo, the mempool diff --git a/backend/package-lock.json b/backend/package-lock.json index 3f66fa25b..e24af1a0a 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,23 +1,23 @@ { "name": "mempool-backend", - "version": "3.1.0-dev", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mempool-backend", - "version": "3.1.0-dev", + "version": "3.2.0", "hasInstallScript": true, "license": "GNU Affero General Public License v3.0", "dependencies": { "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", - "axios": "1.7.2", + "axios": "1.8.1", "bitcoinjs-lib": "~6.1.3", "crypto-js": "~4.2.0", "express": "~4.21.1", "maxmind": "~4.3.11", - "mysql2": "~3.12.0", + "mysql2": "~3.13.0", "redis": "^4.7.0", "rust-gbt": "file:./rust-gbt", "socks-proxy-agent": "~7.0.0", @@ -2275,9 +2275,9 @@ } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", + "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -6173,9 +6173,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/mysql2": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz", - "integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.13.0.tgz", + "integrity": "sha512-M6DIQjTqKeqXH5HLbLMxwcK5XfXHw30u5ap6EZmu7QVmcF/gnh2wS/EOiQ4MTbXz/vQeoXrmycPlVRM00WSslg==", "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.1", @@ -9459,9 +9459,9 @@ "integrity": "sha512-+H+kuK34PfMaI9PNU/NSjBKL5hh/KDM9J72kwYeYEm0A8B1AC4fuCy3qsjnA7lxklgyXsB68yn8Z2xoZEjgwCQ==" }, "axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", + "integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", "requires": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -12337,9 +12337,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "mysql2": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.12.0.tgz", - "integrity": "sha512-C8fWhVysZoH63tJbX8d10IAoYCyXy4fdRFz2Ihrt9jtPILYynFEKUUzpp1U7qxzDc3tMbotvaBH+sl6bFnGZiw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.13.0.tgz", + "integrity": "sha512-M6DIQjTqKeqXH5HLbLMxwcK5XfXHw30u5ap6EZmu7QVmcF/gnh2wS/EOiQ4MTbXz/vQeoXrmycPlVRM00WSslg==", "requires": { "aws-ssl-profiles": "^1.1.1", "denque": "^2.1.0", diff --git a/backend/package.json b/backend/package.json index ee5944f93..53825449d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -1,6 +1,6 @@ { "name": "mempool-backend", - "version": "3.1.0-dev", + "version": "3.2.0", "description": "Bitcoin mempool visualizer and blockchain explorer backend", "license": "GNU Affero General Public License v3.0", "homepage": "https://mempool.space", @@ -41,12 +41,12 @@ "dependencies": { "@mempool/electrum-client": "1.1.9", "@types/node": "^18.15.3", - "axios": "1.7.2", + "axios": "1.8.1", "bitcoinjs-lib": "~6.1.3", "crypto-js": "~4.2.0", "express": "~4.21.1", "maxmind": "~4.3.11", - "mysql2": "~3.12.0", + "mysql2": "~3.13.0", "rust-gbt": "file:./rust-gbt", "redis": "^4.7.0", "socks-proxy-agent": "~7.0.0", diff --git a/backend/src/api/bitcoin/bitcoin.routes.ts b/backend/src/api/bitcoin/bitcoin.routes.ts index b56ca4861..3cf2923f1 100644 --- a/backend/src/api/bitcoin/bitcoin.routes.ts +++ b/backend/src/api/bitcoin/bitcoin.routes.ts @@ -55,6 +55,8 @@ class BitcoinRoutes { .post(config.MEMPOOL.API_URL_PREFIX + 'psbt/addparents', this.postPsbtCompletion) .get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from', this.getBlocksByBulk.bind(this)) .get(config.MEMPOOL.API_URL_PREFIX + 'blocks-bulk/:from/:to', this.getBlocksByBulk.bind(this)) + .post(config.MEMPOOL.API_URL_PREFIX + 'prevouts', this.$getPrevouts) + .post(config.MEMPOOL.API_URL_PREFIX + 'cpfp', this.getCpfpLocalTxs) // Temporarily add txs/package endpoint for all backends until esplora supports it .post(config.MEMPOOL.API_URL_PREFIX + 'txs/package', this.$submitPackage) // Internal routes @@ -404,8 +406,8 @@ class BitcoinRoutes { res.setHeader('Expires', new Date(Date.now() + 1000 * cacheDuration).toUTCString()); res.json(block); - } catch (e) { - handleError(req, res, 500, 'Failed to get block'); + } catch (e: any) { + handleError(req, res, e?.response?.status === 404 ? 404 : 500, 'Failed to get block'); } } @@ -981,6 +983,92 @@ class BitcoinRoutes { } } + private async $getPrevouts(req: Request, res: Response) { + try { + const outpoints = req.body; + if (!Array.isArray(outpoints) || outpoints.some((item) => !/^[a-fA-F0-9]{64}$/.test(item.txid) || typeof item.vout !== 'number')) { + handleError(req, res, 400, 'Invalid outpoints format'); + return; + } + + if (outpoints.length > 100) { + handleError(req, res, 400, 'Too many outpoints requested'); + return; + } + + const result = Array(outpoints.length).fill(null); + const memPool = mempool.getMempool(); + + for (let i = 0; i < outpoints.length; i++) { + const outpoint = outpoints[i]; + let prevout: IEsploraApi.Vout | null = null; + let unconfirmed: boolean | null = null; + + const mempoolTx = memPool[outpoint.txid]; + if (mempoolTx) { + if (outpoint.vout < mempoolTx.vout.length) { + prevout = mempoolTx.vout[outpoint.vout]; + unconfirmed = true; + } + } else { + try { + const rawPrevout = await bitcoinClient.getTxOut(outpoint.txid, outpoint.vout, false); + if (rawPrevout) { + prevout = { + value: Math.round(rawPrevout.value * 100000000), + scriptpubkey: rawPrevout.scriptPubKey.hex, + scriptpubkey_asm: rawPrevout.scriptPubKey.asm ? transactionUtils.convertScriptSigAsm(rawPrevout.scriptPubKey.hex) : '', + scriptpubkey_type: transactionUtils.translateScriptPubKeyType(rawPrevout.scriptPubKey.type), + scriptpubkey_address: rawPrevout.scriptPubKey && rawPrevout.scriptPubKey.address ? rawPrevout.scriptPubKey.address : '', + }; + unconfirmed = false; + } + } catch (e) { + // Ignore bitcoin client errors, just leave prevout as null + } + } + + if (prevout) { + result[i] = { prevout, unconfirmed }; + } + } + + res.json(result); + + } catch (e) { + handleError(req, res, 500, 'Failed to get prevouts'); + } + } + + private getCpfpLocalTxs(req: Request, res: Response) { + try { + const transactions = req.body; + + if (!Array.isArray(transactions) || transactions.some(tx => + !tx || typeof tx !== 'object' || + !/^[a-fA-F0-9]{64}$/.test(tx.txid) || + typeof tx.weight !== 'number' || + typeof tx.sigops !== 'number' || + typeof tx.fee !== 'number' || + !Array.isArray(tx.vin) || + !Array.isArray(tx.vout) + )) { + handleError(req, res, 400, 'Invalid transactions format'); + return; + } + + if (transactions.length > 1) { + handleError(req, res, 400, 'More than one transaction is not supported yet'); + return; + } + + const cpfpInfo = calculateMempoolTxCpfp(transactions[0], mempool.getMempool(), true); + res.json([cpfpInfo]); + + } catch (e) { + handleError(req, res, 500, 'Failed to calculate CPFP info'); + } + } } export default new BitcoinRoutes(); diff --git a/backend/src/api/bitcoin/esplora-api.ts b/backend/src/api/bitcoin/esplora-api.ts index 8035d92c0..6ee51fda4 100644 --- a/backend/src/api/bitcoin/esplora-api.ts +++ b/backend/src/api/bitcoin/esplora-api.ts @@ -36,7 +36,7 @@ class FailoverRouter { maxHeight: number = 0; hosts: FailoverHost[]; multihost: boolean; - gitHashInterval: number = 600000; // 10 minutes + gitHashInterval: number = 60000; // 1 minute pollInterval: number = 60000; // 1 minute pollTimer: NodeJS.Timeout | null = null; pollConnection = axios.create(); @@ -111,7 +111,7 @@ class FailoverRouter { for (const host of this.hosts) { try { const result = await (host.socket - ? this.pollConnection.get('/blocks/tip/height', { socketPath: host.host, timeout: config.ESPLORA.FALLBACK_TIMEOUT }) + ? this.pollConnection.get('http://api/blocks/tip/height', { socketPath: host.host, timeout: config.ESPLORA.FALLBACK_TIMEOUT }) : this.pollConnection.get(host.host + '/blocks/tip/height', { timeout: config.ESPLORA.FALLBACK_TIMEOUT }) ); if (result) { @@ -288,7 +288,7 @@ class FailoverRouter { let url; if (host.socket) { axiosConfig = { socketPath: host.host, timeout: config.ESPLORA.REQUEST_TIMEOUT, responseType }; - url = path; + url = 'http://api' + path; } else { axiosConfig = { timeout: config.ESPLORA.REQUEST_TIMEOUT, responseType }; url = host.host + path; diff --git a/backend/src/api/blocks.ts b/backend/src/api/blocks.ts index 102601594..d475470db 100644 --- a/backend/src/api/blocks.ts +++ b/backend/src/api/blocks.ts @@ -1391,7 +1391,7 @@ class Blocks { } public async $getBlockAuditSummary(hash: string): Promise { - if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { + if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) { return BlocksAuditsRepository.$getBlockAudit(hash); } else { return null; @@ -1399,7 +1399,7 @@ class Blocks { } public async $getBlockTxAuditSummary(hash: string, txid: string): Promise { - if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK)) { + if (['mainnet', 'testnet', 'signet'].includes(config.MEMPOOL.NETWORK) && Common.auditIndexingEnabled()) { return BlocksAuditsRepository.$getBlockTxAudit(hash, txid); } else { return null; @@ -1469,11 +1469,11 @@ class Blocks { if (rows && Array.isArray(rows)) { return rows.map(r => r.definition_hash); } else { - logger.debug(`Unable to retreive list of blocks.definition_hash from db (no result)`); + logger.debug(`Unable to retrieve list of blocks.definition_hash from db (no result)`); return null; } } catch (e) { - logger.debug(`Unable to retreive list of blocks.definition_hash from db (exception: ${e})`); + logger.debug(`Unable to retrieve list of blocks.definition_hash from db (exception: ${e})`); return null; } } @@ -1484,11 +1484,11 @@ class Blocks { if (rows && Array.isArray(rows)) { return rows.map(r => r.hash); } else { - logger.debug(`Unable to retreive list of blocks for definition hash ${definitionHash} from db (no result)`); + logger.debug(`Unable to retrieve list of blocks for definition hash ${definitionHash} from db (no result)`); return null; } } catch (e) { - logger.debug(`Unable to retreive list of blocks for definition hash ${definitionHash} from db (exception: ${e})`); + logger.debug(`Unable to retrieve list of blocks for definition hash ${definitionHash} from db (exception: ${e})`); return null; } } diff --git a/backend/src/api/common.ts b/backend/src/api/common.ts index 2dc7ef4b8..8ac21ce5e 100644 --- a/backend/src/api/common.ts +++ b/backend/src/api/common.ts @@ -739,6 +739,13 @@ export class Common { ); } + static auditIndexingEnabled(): boolean { + return ( + Common.indexingEnabled() && + config.MEMPOOL.AUDIT === true + ); + } + static gogglesIndexingEnabled(): boolean { return ( Common.blocksSummariesIndexingEnabled() && diff --git a/backend/src/api/cpfp.ts b/backend/src/api/cpfp.ts index 9da11328b..953664fcc 100644 --- a/backend/src/api/cpfp.ts +++ b/backend/src/api/cpfp.ts @@ -167,8 +167,10 @@ export function calculateGoodBlockCpfp(height: number, transactions: MempoolTran /** * Takes a mempool transaction and a copy of the current mempool, and calculates the CPFP data for * that transaction (and all others in the same cluster) + * If the passed transaction is not guaranteed to be in the mempool, set localTx to true: this will + * prevent updating the CPFP data of other transactions in the cluster */ -export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool: { [txid: string]: MempoolTransactionExtended }): CpfpInfo { +export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool: { [txid: string]: MempoolTransactionExtended }, localTx: boolean = false): CpfpInfo { if (tx.cpfpUpdated && Date.now() < (tx.cpfpUpdated + CPFP_UPDATE_INTERVAL)) { tx.cpfpDirty = false; return { @@ -198,17 +200,26 @@ export function calculateMempoolTxCpfp(tx: MempoolTransactionExtended, mempool: totalFee += tx.fees.base; } const effectiveFeePerVsize = totalFee / totalVsize; - for (const tx of cluster.values()) { - mempool[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize; - mempool[tx.txid].ancestors = Array.from(tx.ancestors.values()).map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fees.base })); - mempool[tx.txid].descendants = Array.from(cluster.values()).filter(entry => entry.txid !== tx.txid && !tx.ancestors.has(entry.txid)).map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fees.base })); - mempool[tx.txid].bestDescendant = null; - mempool[tx.txid].cpfpChecked = true; - mempool[tx.txid].cpfpDirty = true; - mempool[tx.txid].cpfpUpdated = Date.now(); - } - tx = mempool[tx.txid]; + if (localTx) { + tx.effectiveFeePerVsize = effectiveFeePerVsize; + tx.ancestors = Array.from(cluster.get(tx.txid)?.ancestors.values() || []).map(ancestor => ({ txid: ancestor.txid, weight: ancestor.weight, fee: ancestor.fees.base })); + tx.descendants = Array.from(cluster.values()).filter(entry => entry.txid !== tx.txid && !cluster.get(tx.txid)?.ancestors.has(entry.txid)).map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fees.base })); + tx.bestDescendant = null; + } else { + for (const tx of cluster.values()) { + mempool[tx.txid].effectiveFeePerVsize = effectiveFeePerVsize; + mempool[tx.txid].ancestors = Array.from(tx.ancestors.values()).map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fees.base })); + mempool[tx.txid].descendants = Array.from(cluster.values()).filter(entry => entry.txid !== tx.txid && !tx.ancestors.has(entry.txid)).map(tx => ({ txid: tx.txid, weight: tx.weight, fee: tx.fees.base })); + mempool[tx.txid].bestDescendant = null; + mempool[tx.txid].cpfpChecked = true; + mempool[tx.txid].cpfpDirty = true; + mempool[tx.txid].cpfpUpdated = Date.now(); + } + + tx = mempool[tx.txid]; + + } return { ancestors: tx.ancestors || [], diff --git a/backend/src/api/database-migration.ts b/backend/src/api/database-migration.ts index 4f43bd9d2..299cd309b 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 = 95; + private static currentVersion = 96; private queryTimeout = 3600_000; private statisticsAddedIndexed = false; private uniqueLogs: string[] = []; @@ -1130,6 +1130,11 @@ class DatabaseMigration { await this.$executeQuery('ALTER TABLE blocks ADD INDEX `definition_hash` (`definition_hash`)'); await this.updateToSchemaVersion(95); } + + if (databaseSchemaVersion < 96) { + await this.$executeQuery(`ALTER TABLE blocks_audits MODIFY time timestamp NOT NULL DEFAULT 0`); + await this.updateToSchemaVersion(96); + } } /** diff --git a/backend/src/api/pools-parser.ts b/backend/src/api/pools-parser.ts index 2fd55d6c5..2895da5a5 100644 --- a/backend/src/api/pools-parser.ts +++ b/backend/src/api/pools-parser.ts @@ -8,6 +8,7 @@ import mining from './mining/mining'; import transactionUtils from './transaction-utils'; import BlocksRepository from '../repositories/BlocksRepository'; import redisCache from './redis-cache'; +import blocks from './blocks'; class PoolsParser { miningPools: any[] = []; @@ -42,6 +43,8 @@ class PoolsParser { await this.$insertUnknownPool(); let reindexUnknown = false; + let clearCache = false; + for (const pool of this.miningPools) { if (!pool.id) { @@ -78,17 +81,20 @@ class PoolsParser { logger.debug(`Inserting new mining pool ${pool.name}`); await PoolsRepository.$insertNewMiningPool(pool, slug); reindexUnknown = true; + clearCache = true; } else { if (poolDB.name !== pool.name) { // Pool has been renamed const newSlug = pool.name.replace(/[^a-z0-9]/gi, '').toLowerCase(); logger.warn(`Renaming ${poolDB.name} mining pool to ${pool.name}. Slug has been updated. Maybe you want to make a redirection from 'https://mempool.space/mining/pool/${poolDB.slug}' to 'https://mempool.space/mining/pool/${newSlug}`); await PoolsRepository.$renameMiningPool(poolDB.id, newSlug, pool.name); + clearCache = true; } if (poolDB.link !== pool.link) { // Pool link has changed logger.debug(`Updating link for ${pool.name} mining pool`); await PoolsRepository.$updateMiningPoolLink(poolDB.id, pool.link); + clearCache = true; } if (JSON.stringify(pool.addresses) !== poolDB.addresses || JSON.stringify(pool.regexes) !== poolDB.regexes) { @@ -96,6 +102,7 @@ class PoolsParser { logger.notice(`Updating addresses and/or coinbase tags for ${pool.name} mining pool.`); await PoolsRepository.$updateMiningPoolTags(poolDB.id, pool.addresses, pool.regexes); reindexUnknown = true; + clearCache = true; await this.$reindexBlocksForPool(poolDB.id); } } @@ -111,6 +118,19 @@ class PoolsParser { } await this.$reindexBlocksForPool(unknownPool.id); } + + // refresh the in-memory block cache with the reindexed data + if (clearCache) { + for (const block of blocks.getBlocks()) { + const reindexedBlock = await blocks.$indexBlock(block.height); + if (reindexedBlock.id === block.id) { + block.extras.pool = reindexedBlock.extras.pool; + } + } + // update persistent cache with the reindexed data + diskCache.$saveCacheToDisk(); + redisCache.$updateBlocks(blocks.getBlocks()); + } } public matchBlockMiner(scriptsig: string, addresses: string[], pools: PoolTag[]): PoolTag | undefined { diff --git a/backend/src/api/services/wallets.ts b/backend/src/api/services/wallets.ts index dd4d7ebc9..f498a80ad 100644 --- a/backend/src/api/services/wallets.ts +++ b/backend/src/api/services/wallets.ts @@ -30,6 +30,7 @@ const POLL_FREQUENCY = 5 * 60 * 1000; // 5 minutes class WalletApi { private wallets: Record = {}; private syncing = false; + private lastSync = 0; constructor() { this.wallets = config.WALLETS.ENABLED ? (config.WALLETS.WALLETS as string[]).reduce((acc, wallet) => { @@ -47,7 +48,38 @@ class WalletApi { if (!config.WALLETS.ENABLED || this.syncing) { return; } + this.syncing = true; + + if (config.WALLETS.AUTO && (Date.now() - this.lastSync) > POLL_FREQUENCY) { + try { + // update list of active wallets + this.lastSync = Date.now(); + const response = await axios.get(config.MEMPOOL_SERVICES.API + `/wallets`); + const walletList: string[] = response.data; + if (walletList) { + // create a quick lookup dictionary of active wallets + const newWallets: Record = Object.fromEntries( + walletList.map(wallet => [wallet, true]) + ); + for (const wallet of walletList) { + // don't overwrite existing wallets + if (!this.wallets[wallet]) { + this.wallets[wallet] = { name: wallet, addresses: {}, lastPoll: 0 }; + } + } + // remove wallets that are no longer active + for (const wallet of Object.keys(this.wallets)) { + if (!newWallets[wallet]) { + delete this.wallets[wallet]; + } + } + } + } catch (e) { + logger.err(`Error updating active wallets: ${(e instanceof Error ? e.message : e)}`); + } + } + for (const walletKey of Object.keys(this.wallets)) { const wallet = this.wallets[walletKey]; if (wallet.lastPoll < (Date.now() - POLL_FREQUENCY)) { @@ -72,6 +104,7 @@ class WalletApi { } } } + this.syncing = false; } diff --git a/backend/src/api/transaction-utils.ts b/backend/src/api/transaction-utils.ts index 28fa72bba..519527d5c 100644 --- a/backend/src/api/transaction-utils.ts +++ b/backend/src/api/transaction-utils.ts @@ -420,6 +420,29 @@ class TransactionUtils { return { prioritized, deprioritized }; } + + // Copied from https://github.com/mempool/mempool/blob/14e49126c3ca8416a8d7ad134a95c5e090324d69/backend/src/api/bitcoin/bitcoin-api.ts#L324 + public translateScriptPubKeyType(outputType: string): string { + const map = { + 'pubkey': 'p2pk', + 'pubkeyhash': 'p2pkh', + 'scripthash': 'p2sh', + 'witness_v0_keyhash': 'v0_p2wpkh', + 'witness_v0_scripthash': 'v0_p2wsh', + 'witness_v1_taproot': 'v1_p2tr', + 'nonstandard': 'nonstandard', + 'multisig': 'multisig', + 'anchor': 'anchor', + 'nulldata': 'op_return' + }; + + if (map[outputType]) { + return map[outputType]; + } else { + return 'unknown'; + } + } + } export default new TransactionUtils(); diff --git a/backend/src/api/websocket-handler.ts b/backend/src/api/websocket-handler.ts index 390896caa..09e56630a 100644 --- a/backend/src/api/websocket-handler.ts +++ b/backend/src/api/websocket-handler.ts @@ -1011,15 +1011,19 @@ class WebsocketHandler { const blockTransactions = structuredClone(transactions); this.printLogs(); - await statistics.runStatistics(); + if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) { + await statistics.runStatistics(); + } const _memPool = memPool.getMempool(); const candidateTxs = await memPool.getMempoolCandidates(); let candidates: GbtCandidates | undefined = (memPool.limitGBT && candidateTxs) ? { txs: candidateTxs, added: [], removed: [] } : undefined; let transactionIds: string[] = (memPool.limitGBT) ? Object.keys(candidates?.txs || {}) : Object.keys(_memPool); - const accelerations = Object.values(mempool.getAccelerations()); - await accelerationRepository.$indexAccelerationsForBlock(block, accelerations, structuredClone(transactions)); + if (config.DATABASE.ENABLED) { + const accelerations = Object.values(mempool.getAccelerations()); + await accelerationRepository.$indexAccelerationsForBlock(block, accelerations, structuredClone(transactions)); + } const rbfTransactions = Common.findMinedRbfTransactions(transactions, memPool.getSpendMap()); memPool.handleRbfTransactions(rbfTransactions); @@ -1095,7 +1099,9 @@ class WebsocketHandler { if (config.CORE_RPC.DEBUG_LOG_PATH && block.extras) { const firstSeen = getRecentFirstSeen(block.id); if (firstSeen) { - BlocksRepository.$saveFirstSeenTime(block.id, firstSeen); + if (config.DATABASE.ENABLED) { + BlocksRepository.$saveFirstSeenTime(block.id, firstSeen); + } block.extras.firstSeen = firstSeen; } } @@ -1392,7 +1398,9 @@ class WebsocketHandler { }); } - await statistics.runStatistics(); + if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED) { + await statistics.runStatistics(); + } } public handleNewStratumJob(job: StratumJob): void { diff --git a/backend/src/config.ts b/backend/src/config.ts index a1050a7d5..3fe3db2ee 100644 --- a/backend/src/config.ts +++ b/backend/src/config.ts @@ -164,6 +164,7 @@ interface IConfig { }, WALLETS: { ENABLED: boolean; + AUTO: boolean; WALLETS: string[]; }, STRATUM: { @@ -334,6 +335,7 @@ const defaults: IConfig = { }, 'WALLETS': { 'ENABLED': false, + 'AUTO': false, 'WALLETS': [], }, 'STRATUM': { diff --git a/backend/src/index.ts b/backend/src/index.ts index dc6a8ae1a..d8fc90f4a 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -131,6 +131,9 @@ class Server { this.app .use((req: Request, res: Response, next: NextFunction) => { res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); + res.setHeader('Access-Control-Allow-Headers', 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With'); + res.setHeader('Access-Control-Expose-Headers', 'X-Total-Count,X-Mempool-Auth'); next(); }) .use(express.urlencoded({ extended: true })) @@ -153,7 +156,9 @@ class Server { await poolsUpdater.updatePoolsJson(); // Needs to be done before loading the disk cache because we sometimes wipe it await syncAssets.syncAssets$(); - await mempoolBlocks.updatePools$(); + if (config.DATABASE.ENABLED) { + await mempoolBlocks.updatePools$(); + } if (config.MEMPOOL.ENABLED) { if (config.MEMPOOL.CACHE_ENABLED) { await diskCache.$loadMempoolCache(); diff --git a/backend/src/repositories/HashratesRepository.ts b/backend/src/repositories/HashratesRepository.ts index ec44afebe..93aa2d53f 100644 --- a/backend/src/repositories/HashratesRepository.ts +++ b/backend/src/repositories/HashratesRepository.ts @@ -93,7 +93,7 @@ class HashratesRepository { const [rows]: any[] = await DB.query(query); return rows.map(row => row.timestamp); } catch (e) { - logger.err('Cannot retreive indexed weekly hashrate timestamps. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining); + logger.err('Cannot retrieve indexed weekly hashrate timestamps. Reason: ' + (e instanceof Error ? e.message : e), logger.tags.mining); throw e; } } diff --git a/backend/src/tasks/pools-updater.ts b/backend/src/tasks/pools-updater.ts index 38e816d74..8a1a779a1 100644 --- a/backend/src/tasks/pools-updater.ts +++ b/backend/src/tasks/pools-updater.ts @@ -98,7 +98,8 @@ class PoolsUpdater { logger.info(`Mining pools-v2.json (${githubSha}) import completed`, this.tag); } catch (e) { - this.lastRun = now - 600; // Try again in 10 minutes + // fast-forward lastRun to 10 minutes before the next scheduled update + this.lastRun = now - Math.max(config.MEMPOOL.POOLS_UPDATE_DELAY - 600, 600); logger.err(`PoolsUpdater failed. Will try again in 10 minutes. Exception: ${JSON.stringify(e)}`, this.tag); } } diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index 60d663f20..942a8f9c8 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -1,20 +1,20 @@ -FROM node:20.15.0-buster-slim AS builder +FROM rust:1.84-bookworm AS builder ARG commitHash ENV MEMPOOL_COMMIT_HASH=${commitHash} WORKDIR /build + +RUN apt-get update && \ + apt-get install -y curl ca-certificates && \ + curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y nodejs=22.14.0-1nodesource1 build-essential python3 pkg-config && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + COPY . . -RUN apt-get update -RUN apt-get install -y build-essential python3 pkg-config curl ca-certificates - -# Install Rust via rustup -RUN CPU_ARCH=$(uname -m); if [ "$CPU_ARCH" = "armv7l" ]; then c_rehash; fi -#RUN curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain stable -#Workaround to run on github actions from https://github.com/rust-lang/rustup/issues/2700#issuecomment-1367488985 -RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sed 's#/proc/self/exe#\/bin\/sh#g' | sh -s -- -y --default-toolchain stable -ENV PATH="/root/.cargo/bin:$PATH" +ENV PATH="/usr/local/cargo/bin:$PATH" COPY --from=backend . . COPY --from=rustgbt . ../rust/ @@ -24,7 +24,14 @@ RUN npm install --omit=dev --omit=optional WORKDIR /build RUN npm run package -FROM node:20.15.0-buster-slim +FROM rust:1.84-bookworm AS runtime + +RUN apt-get update && \ + apt-get install -y curl ca-certificates && \ + curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \ + apt-get install -y nodejs=22.14.0-1nodesource1 && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* WORKDIR /backend diff --git a/docker/backend/start.sh b/docker/backend/start.sh index 8adb631da..ae0bc616f 100755 --- a/docker/backend/start.sh +++ b/docker/backend/start.sh @@ -26,7 +26,7 @@ __MEMPOOL_EXTERNAL_MAX_RETRY__=${MEMPOOL_EXTERNAL_MAX_RETRY:=1} __MEMPOOL_EXTERNAL_RETRY_INTERVAL__=${MEMPOOL_EXTERNAL_RETRY_INTERVAL:=0} __MEMPOOL_USER_AGENT__=${MEMPOOL_USER_AGENT:=mempool} __MEMPOOL_STDOUT_LOG_MIN_PRIORITY__=${MEMPOOL_STDOUT_LOG_MIN_PRIORITY:=info} -__MEMPOOL_AUTOMATIC_POOLS_UPDATE__=${MEMPOOL_AUTOMATIC_POOLS_UPDATE:=false} +__MEMPOOL_AUTOMATIC_POOLS_UPDATE__=${MEMPOOL_AUTOMATIC_POOLS_UPDATE:=true} __MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json} __MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master} __MEMPOOL_POOLS_UPDATE_DELAY__=${MEMPOOL_POOLS_UPDATE_DELAY:=604800} diff --git a/docker/frontend/Dockerfile b/docker/frontend/Dockerfile index 8374ebe49..23c1da3d4 100644 --- a/docker/frontend/Dockerfile +++ b/docker/frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.15.0-buster-slim AS builder +FROM node:22.14.0-bookworm-slim AS builder ARG commitHash ENV DOCKER_COMMIT_HASH=${commitHash} diff --git a/frontend/angular.json b/frontend/angular.json index 3aa1cb6a8..c5da6a09a 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -261,20 +261,14 @@ "proxyConfig": "proxy.conf.mixed.js", "verbose": true }, - "staging": { - "proxyConfig": "proxy.conf.js", - "disableHostCheck": true, - "host": "0.0.0.0", - "verbose": true - }, "local-prod": { "proxyConfig": "proxy.conf.js", "disableHostCheck": true, "host": "0.0.0.0", "verbose": false }, - "local-staging": { - "proxyConfig": "proxy.conf.staging.js", + "parameterized": { + "proxyConfig": "proxy.conf.parameterized.js", "disableHostCheck": true, "host": "0.0.0.0", "verbose": false @@ -371,4 +365,4 @@ } } } -} +} \ No newline at end of file diff --git a/frontend/custom-sv-config.json b/frontend/custom-sv-config.json index dee3dab18..9a61704a2 100644 --- a/frontend/custom-sv-config.json +++ b/frontend/custom-sv-config.json @@ -16,10 +16,10 @@ "mobileOrder": 4 }, { - "component": "balance", + "component": "walletBalance", "mobileOrder": 1, "props": { - "address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo" + "wallet": "ONBTC" } }, { @@ -30,21 +30,22 @@ } }, { - "component": "address", + "component": "wallet", "mobileOrder": 2, "props": { - "address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo", - "period": "1m" + "wallet": "ONBTC", + "period": "1m", + "label": "bitcoin.gob.sv" } }, { "component": "blocks" }, { - "component": "addressTransactions", + "component": "walletTransactions", "mobileOrder": 3, "props": { - "address": "32ixEdVJWo3kmvJGMTZq5jAQVZZeuwnqzo" + "wallet": "ONBTC" } } ] diff --git a/frontend/cypress/e2e/liquid/liquid.spec.ts b/frontend/cypress/e2e/liquid/liquid.spec.ts index c7d2a92ee..4fb7431d9 100644 --- a/frontend/cypress/e2e/liquid/liquid.spec.ts +++ b/frontend/cypress/e2e/liquid/liquid.spec.ts @@ -57,11 +57,6 @@ describe('Liquid', () => { }); }); - it('loads the tv page - desktop', () => { - cy.visit(`${basePath}/tv`); - cy.waitForSkeletonGone(); - }); - it('loads the graphs page - mobile', () => { cy.visit(`${basePath}`) cy.waitForSkeletonGone(); diff --git a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts index 54e355ce8..7befda49f 100644 --- a/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts +++ b/frontend/cypress/e2e/liquidtestnet/liquidtestnet.spec.ts @@ -57,11 +57,6 @@ describe('Liquid Testnet', () => { cy.waitForSkeletonGone(); }); - it('loads the tv page - desktop', () => { - cy.visit(`${basePath}/tv`); - cy.waitForSkeletonGone(); - }); - it('loads the graphs page - mobile', () => { cy.visit(`${basePath}`) cy.waitForSkeletonGone(); diff --git a/frontend/cypress/e2e/mainnet/mainnet.spec.ts b/frontend/cypress/e2e/mainnet/mainnet.spec.ts index 7e17c09cd..a664f333c 100644 --- a/frontend/cypress/e2e/mainnet/mainnet.spec.ts +++ b/frontend/cypress/e2e/mainnet/mainnet.spec.ts @@ -1,4 +1,4 @@ -import { emitMempoolInfo, dropWebSocket } from '../../support/websocket'; +import { emitMempoolInfo, dropWebSocket, receiveWebSocketMessageFromServer } from '../../support/websocket'; const baseModule = Cypress.env('BASE_MODULE'); @@ -216,6 +216,69 @@ describe('Mainnet', () => { cy.get('[data-cy="tx-1"] .table-tx-vout .highlight').its('length').should('equal', 2); cy.get('[data-cy="tx-1"] .table-tx-vout .highlight').invoke('text').should('contain', `${address}`); }); + + describe('address poisoning', () => { + it('highlights potential address poisoning attacks on outputs, prefix and infix', () => { + const txid = '152a5dea805f95d6f83e50a9fd082630f542a52a076ebabdb295723eaf53fa30'; + const prefix = '1DonatePLease'; + const infix1 = 'SenderAddressXVXCmAY'; + const infix2 = '5btcToSenderXXWBoKhB'; + + cy.visit(`/tx/${txid}`); + cy.waitForSkeletonGone(); + cy.get('.alert-mempool').should('exist'); + cy.get('.poison-alert').its('length').should('equal', 2); + + cy.get('.prefix') + .should('have.length', 2) + .each(($el) => { + cy.wrap($el).should('have.text', prefix); + }); + + cy.get('.infix') + .should('have.length', 2) + .then(($infixes) => { + cy.wrap($infixes[0]).should('have.text', infix1); + cy.wrap($infixes[1]).should('have.text', infix2); + }); + }); + + it('highlights potential address poisoning attacks on inputs and outputs, prefix, infix and postfix', () => { + const txid = '44544516084ea916ff1eb69c675c693e252addbbaf77102ffff86e3979ac6132'; + const prefix = 'bc1qge8'; + const infix1 = '6gqjnk8aqs3nvv7ejrvcd4zq6qur3'; + const infix2 = 'xyxjex6zzzx5g8hh65vsel4e548p2'; + const postfix1 = '6p6e3r'; + const postfix2 = '6p6e3r'; + + cy.visit(`/tx/${txid}`); + cy.waitForSkeletonGone(); + cy.get('.alert-mempool').should('exist'); + cy.get('.poison-alert').its('length').should('equal', 2); + + cy.get('.prefix') + .should('have.length', 2) + .each(($el) => { + cy.wrap($el).should('have.text', prefix); + }); + + cy.get('.infix') + .should('have.length', 2) + .then(($infixes) => { + cy.wrap($infixes[0]).should('have.text', infix1); + cy.wrap($infixes[1]).should('have.text', infix2); + }); + + cy.get('.postfix') + .should('have.length', 2) + .then(($postfixes) => { + cy.wrap($postfixes[0]).should('include.text', postfix1); + cy.wrap($postfixes[1]).should('include.text', postfix2); + }); + + }); + }); + }); describe('blocks navigation', () => { @@ -397,6 +460,7 @@ describe('Mainnet', () => { cy.get('#dropdownFees').should('be.visible'); cy.get('.btn-group').should('be.visible'); }); + it('check buttons - tablet', () => { cy.viewport('ipad-2'); cy.visit('/graphs'); @@ -405,6 +469,7 @@ describe('Mainnet', () => { cy.get('#dropdownFees').should('be.visible'); cy.get('.btn-group').should('be.visible'); }); + it('check buttons - desktop', () => { cy.viewport('macbook-16'); cy.visit('/graphs'); @@ -415,26 +480,6 @@ describe('Mainnet', () => { }); }); - it('loads the tv screen - desktop', () => { - cy.viewport('macbook-16'); - cy.visit('/graphs/mempool'); - cy.waitForSkeletonGone(); - cy.get('#btn-tv').click().then(() => { - cy.viewport('macbook-16'); - cy.get('.chart-holder'); - cy.get('.blockchain-wrapper').should('be.visible'); - cy.get('#mempool-block-0').should('be.visible'); - }); - }); - - it('loads the tv screen - mobile', () => { - cy.viewport('iphone-6'); - cy.visit('/tv'); - cy.waitForSkeletonGone(); - cy.get('.chart-holder'); - cy.get('.blockchain-wrapper').should('not.visible'); - }); - it('loads the api screen', () => { cy.visit('/'); cy.waitForSkeletonGone(); @@ -516,7 +561,44 @@ describe('Mainnet', () => { }); describe('RBF transactions', () => { - it('shows RBF transactions properly (mobile)', () => { + it('RBF page gets updated over websockets', () => { + cy.intercept('/api/v1/replacements', { + statusCode: 200, + body: [] + }); + + cy.intercept('/api/v1/fullrbf/replacements', { + statusCode: 200, + body: [] + }); + + cy.mockMempoolSocketV2(); + + cy.visit('/rbf'); + cy.get('.no-replacements'); + cy.get('.tree').should('have.length', 0); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'rbf_page/rbf_01.json' + } + } + }); + + cy.get('.tree').should('have.length', 1); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'rbf_page/rbf_02.json' + } + } + }); + cy.get('.tree').should('have.length', 2); + }); + + it('shows RBF transactions properly (mobile - details)', () => { cy.intercept('/api/v1/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f/cached', { fixture: 'mainnet_tx_cached.json' }).as('cached_tx'); @@ -527,7 +609,7 @@ describe('Mainnet', () => { cy.viewport('iphone-xr'); cy.mockMempoolSocket(); - cy.visit('/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f'); + cy.visit('/tx/21518a98d1aa9df524865d2f88c578499f524eb1d0c4d3e70312ab863508692f?mode=details'); cy.waitForSkeletonGone(); @@ -545,7 +627,120 @@ describe('Mainnet', () => { } }); + cy.get('.alert-mempool').should('be.visible'); + }); + + it('shows RBF transactions properly (mobile - tracker)', () => { + cy.mockMempoolSocketV2(); + cy.viewport('iphone-xr'); + + // API Mocks + cy.intercept('/api/v1/mining/pools/1w', { + fixture: 'details_rbf/api_mining_pools_1w.json' + }).as('api_mining_1w'); + + cy.intercept('/api/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29', { + statusCode: 404, + body: 'Transaction not found' + }).as('api_tx01_404'); + + cy.intercept('/api/v1/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29/cached', { + fixture: 'details_rbf/tx01_api_cached.json' + }).as('api_tx01_cached'); + + cy.intercept('/api/v1/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29/rbf', { + fixture: 'details_rbf/tx01_api_rbf.json' + }).as('api_tx01_rbf'); + + cy.visit('/tx/242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29?mode=tracker'); + cy.wait('@api_tx01_rbf'); + + // Start sending mocked WS messages + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'details_rbf/tx01_ws_stratum_jobs.json' + } + } + }); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'details_rbf/tx01_ws_blocks_01.json' + } + } + }); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'details_rbf/tx01_ws_tx_replaced.json' + } + } + }); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'details_rbf/tx01_ws_mempool_blocks_01.json' + } + } + }); + cy.get('.alert-replaced').should('be.visible'); + cy.get('.explainer').should('be.visible'); + cy.get('svg[data-icon=timeline]').should('be.visible'); + + // Second TX setup + cy.intercept('/api/tx/b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698', { + fixture: 'details_rbf/tx02_api_tx.json' + }).as('tx02_api'); + + cy.intercept('/api/v1/transaction-times?txId%5B%5D=b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698', { + fixture: 'details_rbf/tx02_api_tx_times.json' + }).as('tx02_api_tx_times'); + + cy.intercept('/api/v1/tx/b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698/rbf', { + fixture: 'details_rbf/tx02_api_rbf.json' + }).as('tx02_api_rbf'); + + cy.intercept('/api/v1/cpfp/b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698', { + fixture: 'details_rbf/tx02_api_cpfp.json' + }).as('tx02_api_cpfp'); + + // Go to the replacement tx + cy.get('.alert-replaced a').click(); + + cy.wait('@tx02_api_cpfp'); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'details_rbf/tx02_ws_tx_position.json' + } + } + }); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'details_rbf/tx02_ws_mempool_blocks_01.json' + } + } + }); + + cy.get('svg[data-icon=hourglass-half]').should('be.visible'); + + receiveWebSocketMessageFromServer({ + params: { + file: { + path: 'details_rbf/tx02_ws_block.json' + } + } + }); + cy.get('app-confirmations'); + cy.get('svg[data-icon=circle-check]').should('be.visible'); }); it('shows RBF transactions properly (desktop)', () => { diff --git a/frontend/cypress/e2e/signet/signet.spec.ts b/frontend/cypress/e2e/signet/signet.spec.ts index 11c47d14d..ae591c6a7 100644 --- a/frontend/cypress/e2e/signet/signet.spec.ts +++ b/frontend/cypress/e2e/signet/signet.spec.ts @@ -60,30 +60,6 @@ describe('Signet', () => { }); }); - describe.skip('tv mode', () => { - it('loads the tv screen - desktop', () => { - cy.viewport('macbook-16'); - cy.visit('/signet/graphs'); - cy.waitForSkeletonGone(); - cy.get('#btn-tv').click().then(() => { - cy.get('.chart-holder').should('be.visible'); - cy.get('#mempool-block-0').should('be.visible'); - cy.get('.tv-only').should('not.exist'); - }); - }); - - it('loads the tv screen - mobile', () => { - cy.visit('/signet/graphs'); - cy.waitForSkeletonGone(); - cy.get('#btn-tv').click().then(() => { - cy.viewport('iphone-8'); - cy.get('.chart-holder').should('be.visible'); - cy.get('.tv-only').should('not.exist'); - cy.get('#mempool-block-0').should('be.visible'); - }); - }); - }); - it('loads the api screen', () => { cy.visit('/signet'); cy.waitForSkeletonGone(); diff --git a/frontend/cypress/e2e/testnet4/testnet4.spec.ts b/frontend/cypress/e2e/testnet4/testnet4.spec.ts index c67d2414b..97af0e08e 100644 --- a/frontend/cypress/e2e/testnet4/testnet4.spec.ts +++ b/frontend/cypress/e2e/testnet4/testnet4.spec.ts @@ -60,30 +60,6 @@ describe('Testnet4', () => { }); }); - describe('tv mode', () => { - it('loads the tv screen - desktop', () => { - cy.viewport('macbook-16'); - cy.visit('/testnet4/graphs'); - cy.waitForSkeletonGone(); - cy.get('#btn-tv').click().then(() => { - cy.wait(1000); - cy.get('.tv-only').should('not.exist'); - cy.get('#mempool-block-0').should('be.visible'); - }); - }); - - it('loads the tv screen - mobile', () => { - cy.visit('/testnet4/graphs'); - cy.waitForSkeletonGone(); - cy.get('#btn-tv').click().then(() => { - cy.viewport('iphone-6'); - cy.wait(1000); - cy.get('.tv-only').should('not.exist'); - }); - }); - }); - - it('loads the api screen', () => { cy.visit('/testnet4'); cy.waitForSkeletonGone(); diff --git a/frontend/cypress/fixtures/details_rbf/api_accelerator_estimate.json b/frontend/cypress/fixtures/details_rbf/api_accelerator_estimate.json new file mode 100644 index 000000000..889c5d763 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/api_accelerator_estimate.json @@ -0,0 +1,60 @@ +{ + "txSummary": { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "effectiveVsize": 224, + "effectiveFee": 960, + "ancestorCount": 1 + }, + "cost": 1000, + "targetFeeRate": 3, + "nextBlockFee": 672, + "userBalance": 0, + "mempoolBaseFee": 50000, + "vsizeFee": 0, + "pools": [ + 36, + 102, + 112, + 44, + 4, + 2, + 6, + 94, + 143, + 43, + 105, + 115, + 142, + 111 + ], + "options": [ + { + "fee": 1000 + }, + { + "fee": 2000 + }, + { + "fee": 10000 + } + ], + "hasAccess": false, + "availablePaymentMethods": { + "bitcoin": { + "enabled": true, + "min": 1000, + "max": 10000000 + }, + "applePay": { + "enabled": true, + "min": 10, + "max": 1000 + }, + "googlePay": { + "enabled": true, + "min": 10, + "max": 1000 + } + }, + "unavailable": false +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/api_accelerator_version.json b/frontend/cypress/fixtures/details_rbf/api_accelerator_version.json new file mode 100644 index 000000000..a01c899b8 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/api_accelerator_version.json @@ -0,0 +1,3 @@ +{ + "gitCommit": "62f80296" +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/api_block_history.json b/frontend/cypress/fixtures/details_rbf/api_block_history.json new file mode 100644 index 000000000..0637a088a --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/api_block_history.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/api_mining_pools_1w.json b/frontend/cypress/fixtures/details_rbf/api_mining_pools_1w.json new file mode 100644 index 000000000..3a678ca02 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/api_mining_pools_1w.json @@ -0,0 +1,260 @@ +{ + "pools": [ + { + "poolId": 112, + "name": "Foundry USA", + "link": "https://foundrydigital.com", + "blockCount": 323, + "rank": 1, + "emptyBlocks": 0, + "slug": "foundryusa", + "avgMatchRate": 99.96, + "avgFeeDelta": "-0.01971455", + "poolUniqueId": 111 + }, + { + "poolId": 45, + "name": "AntPool", + "link": "https://www.antpool.com", + "blockCount": 171, + "rank": 2, + "emptyBlocks": 0, + "slug": "antpool", + "avgMatchRate": 99.99, + "avgFeeDelta": "-0.04227368", + "poolUniqueId": 44 + }, + { + "poolId": 74, + "name": "ViaBTC", + "link": "https://viabtc.com", + "blockCount": 166, + "rank": 3, + "emptyBlocks": 0, + "slug": "viabtc", + "avgMatchRate": 99.99, + "avgFeeDelta": "-0.02530964", + "poolUniqueId": 73 + }, + { + "poolId": 37, + "name": "F2Pool", + "link": "https://www.f2pool.com", + "blockCount": 104, + "rank": 4, + "emptyBlocks": 0, + "slug": "f2pool", + "avgMatchRate": 99.99, + "avgFeeDelta": "-0.03299327", + "poolUniqueId": 36 + }, + { + "poolId": 116, + "name": "MARA Pool", + "link": "https://marapool.com", + "blockCount": 66, + "rank": 5, + "emptyBlocks": 0, + "slug": "marapool", + "avgMatchRate": 99.97, + "avgFeeDelta": "0.02366061", + "poolUniqueId": 115 + }, + { + "poolId": 103, + "name": "SpiderPool", + "link": "https://www.spiderpool.com", + "blockCount": 46, + "rank": 6, + "emptyBlocks": 1, + "slug": "spiderpool", + "avgMatchRate": 97.82, + "avgFeeDelta": "-0.07258913", + "poolUniqueId": 102 + }, + { + "poolId": 142, + "name": "SECPOOL", + "link": "https://www.secpool.com", + "blockCount": 30, + "rank": 7, + "emptyBlocks": 1, + "slug": "secpool", + "avgMatchRate": 96.67, + "avgFeeDelta": "-0.06596000", + "poolUniqueId": 141 + }, + { + "poolId": 106, + "name": "Binance Pool", + "link": "https://pool.binance.com", + "blockCount": 28, + "rank": 8, + "emptyBlocks": 0, + "slug": "binancepool", + "avgMatchRate": 99.99, + "avgFeeDelta": "-0.05834286", + "poolUniqueId": 105 + }, + { + "poolId": 5, + "name": "Luxor", + "link": "https://mining.luxor.tech", + "blockCount": 28, + "rank": 9, + "emptyBlocks": 0, + "slug": "luxor", + "avgMatchRate": 100, + "avgFeeDelta": "-0.05496071", + "poolUniqueId": 4 + }, + { + "poolId": 143, + "name": "OCEAN", + "link": "https://ocean.xyz/", + "blockCount": 12, + "rank": 10, + "emptyBlocks": 0, + "slug": "ocean", + "avgMatchRate": 91.9, + "avgFeeDelta": "-0.14650833", + "poolUniqueId": 142 + }, + { + "poolId": 44, + "name": "Braiins Pool", + "link": "https://braiins.com/pool", + "blockCount": 12, + "rank": 11, + "emptyBlocks": 0, + "slug": "braiinspool", + "avgMatchRate": 100, + "avgFeeDelta": "-0.03553333", + "poolUniqueId": 43 + }, + { + "poolId": 113, + "name": "SBI Crypto", + "link": "https://sbicrypto.com", + "blockCount": 8, + "rank": 12, + "emptyBlocks": 0, + "slug": "sbicrypto", + "avgMatchRate": 98.65, + "avgFeeDelta": "-0.04246250", + "poolUniqueId": 112 + }, + { + "poolId": 152, + "name": "Carbon Negative", + "link": "https://github.com/bitcoin-data/mining-pools/issues/48", + "blockCount": 7, + "rank": 13, + "emptyBlocks": 0, + "slug": "carbonnegative", + "avgMatchRate": 99.75, + "avgFeeDelta": "-0.04407143", + "poolUniqueId": 151 + }, + { + "poolId": 7, + "name": "BTC.com", + "link": "https://pool.btc.com", + "blockCount": 5, + "rank": 14, + "emptyBlocks": 0, + "slug": "btccom", + "avgMatchRate": 99.98, + "avgFeeDelta": "-0.02496000", + "poolUniqueId": 6 + }, + { + "poolId": 162, + "name": "Mining Squared", + "link": "https://pool.bsquared.network/", + "blockCount": 4, + "rank": 15, + "emptyBlocks": 0, + "slug": "miningsquared", + "avgMatchRate": 100, + "avgFeeDelta": "-0.00915000", + "poolUniqueId": 161 + }, + { + "poolId": 95, + "name": "Poolin", + "link": "https://www.poolin.com", + "blockCount": 4, + "rank": 16, + "emptyBlocks": 0, + "slug": "poolin", + "avgMatchRate": 100, + "avgFeeDelta": "-0.26485000", + "poolUniqueId": 94 + }, + { + "poolId": 1, + "name": "Unknown", + "link": "https://learnmeabitcoin.com/technical/coinbase-transaction", + "blockCount": 4, + "rank": 17, + "emptyBlocks": 0, + "slug": "unknown", + "avgMatchRate": 100, + "avgFeeDelta": "-0.06490000", + "poolUniqueId": 0 + }, + { + "poolId": 144, + "name": "WhitePool", + "link": "https://whitebit.com/mining-pool", + "blockCount": 3, + "rank": 18, + "emptyBlocks": 0, + "slug": "whitepool", + "avgMatchRate": 100, + "avgFeeDelta": "-0.01293333", + "poolUniqueId": 143 + }, + { + "poolId": 3, + "name": "ULTIMUSPOOL", + "link": "https://www.ultimuspool.com", + "blockCount": 1, + "rank": 19, + "emptyBlocks": 0, + "slug": "ultimuspool", + "avgMatchRate": 100, + "avgFeeDelta": "-0.16130000", + "poolUniqueId": 2 + }, + { + "poolId": 50, + "name": "Solo CK", + "link": "https://solo.ckpool.org", + "blockCount": 1, + "rank": 20, + "emptyBlocks": 0, + "slug": "solock", + "avgMatchRate": 100, + "avgFeeDelta": "-0.01510000", + "poolUniqueId": 49 + }, + { + "poolId": 158, + "name": "BitFuFuPool", + "link": "https://www.bitfufu.com/pool", + "blockCount": 1, + "rank": 21, + "emptyBlocks": 0, + "slug": "bitfufupool", + "avgMatchRate": 100, + "avgFeeDelta": "-0.01630000", + "poolUniqueId": 157 + } + ], + "blockCount": 1024, + "lastEstimatedHashrate": 786391245138648900000, + "lastEstimatedHashrate3d": 797683179385121300000, + "lastEstimatedHashrate1w": 827836055441520300000 +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx01_api_cached.json b/frontend/cypress/fixtures/details_rbf/tx01_api_cached.json new file mode 100644 index 000000000..62184cc9d --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx01_api_cached.json @@ -0,0 +1,55 @@ +{ + "txid": "242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "fb16c141d22a3a7af5e44e7478a8663866c35d554cd85107ec8a99b97e5a72e9", + "vout": 0, + "prevout": { + "scriptpubkey": "76a914099f831c49289c7b9cc07a9a632867ecd51a105a88ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 099f831c49289c7b9cc07a9a632867ecd51a105a OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "1stAprEZapjCYGUACUcXohQqSHF9MU5Kj", + "value": 50000 + }, + "scriptsig": "483045022100f44a50e894c30b28400933da120b872ff15bd2e66dd034ffbbb925fd054dd60f02206ba477a9da3fae68a9f420f53bc25493270bd987d13b75fef39cf514aea9cdb3014104ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df", + "scriptsig_asm": "OP_PUSHBYTES_72 3045022100f44a50e894c30b28400933da120b872ff15bd2e66dd034ffbbb925fd054dd60f02206ba477a9da3fae68a9f420f53bc25493270bd987d13b75fef39cf514aea9cdb301 OP_PUSHBYTES_65 04ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df", + "is_coinbase": false, + "sequence": 4294967293 + } + ], + "vout": [ + { + "scriptpubkey": "5120a2fc5cb445755389eed295ab9d594b0facd3e00840a3e477fa7c025412c53795", + "scriptpubkey_asm": "OP_PUSHNUM_1 OP_PUSHBYTES_32 a2fc5cb445755389eed295ab9d594b0facd3e00840a3e477fa7c025412c53795", + "scriptpubkey_type": "v1_p2tr", + "scriptpubkey_address": "bc1p5t79edz9w4fcnmkjjk4e6k2tp7kd8cqggz37gal60sp9gyk9x72sk4mk0f", + "value": 49394 + } + ], + "size": 233, + "weight": 932, + "sigops": 0, + "fee": 606, + "status": { + "confirmed": false + }, + "order": 701313494, + "vsize": 233, + "adjustedVsize": 233, + "feePerVsize": 2.6008583690987126, + "adjustedFeePerVsize": 2.6008583690987126, + "effectiveFeePerVsize": 2.6008583690987126, + "firstSeen": 1743541407, + "inputs": [], + "cpfpDirty": false, + "ancestors": [], + "descendants": [], + "bestDescendant": null, + "position": { + "block": 0, + "vsize": 318595.5 + }, + "flags": 1099511645193 +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx01_api_rbf.json b/frontend/cypress/fixtures/details_rbf/tx01_api_rbf.json new file mode 100644 index 000000000..5278f9ffe --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx01_api_rbf.json @@ -0,0 +1,34 @@ +{ + "replacements": { + "tx": { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "fee": 960, + "vsize": 224, + "value": 49040, + "rate": 4.285714285714286, + "time": 1743541726, + "rbf": true, + "fullRbf": false + }, + "time": 1743541726, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29", + "fee": 606, + "vsize": 233, + "value": 49394, + "rate": 2.6008583690987126, + "time": 1743541407, + "rbf": true + }, + "time": 1743541407, + "interval": 319, + "fullRbf": false, + "replaces": [] + } + ] + }, + "replaces": null +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx01_ws_blocks_01.json b/frontend/cypress/fixtures/details_rbf/tx01_ws_blocks_01.json new file mode 100644 index 000000000..32b6578fc --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx01_ws_blocks_01.json @@ -0,0 +1,579 @@ +{ + "blocks": [ + { + "id": "0000000000000000000079f5b74b6533abb0b1ece06570d8d157b5bebd1460b4", + "height": 890440, + "version": 559235072, + "timestamp": 1743535677, + "bits": 386038124, + "nonce": 2920325684, + "difficulty": 113757508810854, + "merkle_root": "c793d5fdbfb1ebe99e14a13a6d65370057d311774d33c71da166663b18722474", + "tx_count": 3823, + "size": 1578209, + "weight": 3993461, + "previousblockhash": "000000000000000000020fb2e24425793e17e60e188205dc1694d221790348b2", + "mediantime": 1743532406, + "stale": false, + "extras": { + "reward": 319838750, + "coinbaseRaw": "0348960d082f5669614254432f2cfabe6d6d294719da11c017243828bf32c405341db7f19387fee92c25413c45e114907f9810000000000000001058bf9601429f9fa7a6c160d10d00000000000000", + "orphans": [], + "medianFee": 4, + "feeRange": [ + 3, + 3, + 3.0191082802547773, + 3.980952380952381, + 5, + 10, + 427.748502994012 + ], + "totalFees": 7338750, + "avgFee": 1920, + "avgFeeRate": 7, + "utxoSetChange": 4093, + "avgTxSize": 412.71000000000004, + "totalInputs": 7430, + "totalOutputs": 11523, + "totalOutputAmt": 547553568373, + "segwitTotalTxs": 3432, + "segwitTotalSize": 1467920, + "segwitTotalWeight": 3552413, + "feePercentiles": null, + "virtualSize": 998365.25, + "coinbaseAddress": "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4", + "coinbaseAddresses": [ + "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4" + ], + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 fb37342f6275b13936799def06f2eb4c0f201515 OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "\u0003H–\r\b/ViaBTC/,ú¾mm)G\u0019Ú\u0011À\u0017$8(¿2Ä\u00054\u001d·ñ“‡þé,%Aìg/Foundry USA Pool #dropgold/\u0001S\nEaç\u0002\u0000\u0000\u0000\u0000\u0000", + "header": "00a03220b46014bdbeb557d1d87065e0ecb1b0ab33654bb7f579000000000000000000003ed60f06cec16df4399b5dafa7077036c2eb58cc6a16e6cdca559b9e2f7e4525bb3eec676c790217b7b3c9cb", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 111, + "name": "Foundry USA", + "slug": "foundryusa", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 2792968, + "expectedWeight": 3991959, + "similarity": 0.9951416839808291 + } + }, + { + "id": "000000000000000000014845978a876b3d8bf5d489b9d87e88873952b06ddfce", + "height": 890442, + "version": 557981696, + "timestamp": 1743536834, + "bits": 386038124, + "nonce": 470697326, + "difficulty": 113757508810854, + "merkle_root": "5e92e681c1db2797a5b3e5016729059f8b60a256cafb51d835dac2b3964c0db4", + "tx_count": 3566, + "size": 1628328, + "weight": 3993552, + "previousblockhash": "00000000000000000000ef5a27459785ea4c91e05f64adfad306af6dfc0cd19c", + "mediantime": 1743532867, + "stale": false, + "extras": { + "reward": 318057766, + "coinbaseRaw": "034a960d194d696e656420627920416e74506f6f6c204d000201e15e2989fabe6d6dd599e9dfa40be51f1517c8f512c5c3d51c7656182f1df335d34b98ee02c527db080000000000000000004f92b702000000000000", + "orphans": [], + "medianFee": 3.00860164711668, + "feeRange": [ + 1.5174418604651163, + 2.0140845070422535, + 2.492354740061162, + 3, + 4.020942408376963, + 7, + 200 + ], + "totalFees": 5557766, + "avgFee": 1558, + "avgFeeRate": 5, + "utxoSetChange": 1971, + "avgTxSize": 456.48, + "totalInputs": 7938, + "totalOutputs": 9909, + "totalOutputAmt": 900044492230, + "segwitTotalTxs": 3214, + "segwitTotalSize": 1526463, + "segwitTotalWeight": 3586200, + "feePercentiles": null, + "virtualSize": 998388, + "coinbaseAddress": "37jKPSmbEGwgfacCr2nayn1wTaqMAbA94Z", + "coinbaseAddresses": [ + "37jKPSmbEGwgfacCr2nayn1wTaqMAbA94Z", + "39C7fxSzEACPjM78Z7xdPxhf7mKxJwvfMJ" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 42402a28dd61f2718a4b27ae72a4791d5bbdade7 OP_EQUAL", + "coinbaseSignatureAscii": "\u0003J–\r\u0019Mined by AntPool M\u0000\u0002\u0001á^)‰ú¾mmՙéߤ\u000bå\u001f\u0015\u0017Èõ\u0012ÅÃÕ\u001cvV\u0018/\u001dó5ÓK˜î\u0002Å'Û\b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000O’·\u0002\u0000\u0000\u0000\u0000\u0000\u0000", + "header": "002042219cd10cfc6daf06d3faad645fe0914cea859745275aef00000000000000000000b40d4c96b3c2da35d851fbca56a2608b9f05296701e5b3a59727dbc181e6925ec242ec676c7902176e450e1c", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 44, + "name": "AntPool", + "slug": "antpool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 5764747, + "expectedWeight": 3991786, + "similarity": 0.9029319155137951 + } + }, + { + "id": "000000000000000000026e08b270834273511b353bed30d54706211adc96f5f6", + "height": 890443, + "version": 706666496, + "timestamp": 1743537197, + "bits": 386038124, + "nonce": 321696065, + "difficulty": 113757508810854, + "merkle_root": "3d7574f7eca741fa94b4690868a242e5b286f8a0417ad0275d4ab05893e96350", + "tx_count": 2155, + "size": 1700002, + "weight": 3993715, + "previousblockhash": "000000000000000000014845978a876b3d8bf5d489b9d87e88873952b06ddfce", + "mediantime": 1743533789, + "stale": false, + "extras": { + "reward": 315112344, + "coinbaseRaw": "034b960d21202020204d696e656420627920536563706f6f6c2020202070000b05e388958c01fabe6d6db7ae4bfa7b1294e16e800b4563f1f5ddeb5c0740319eba45600f3f05d2d7272910000000000000000000c2cb7e020000", + "orphans": [], + "medianFee": 1.4360674424569184, + "feeRange": [ + 1, + 1.0135135135135136, + 1.09717868338558, + 2.142857142857143, + 3.009584664536741, + 4.831858407079646, + 196.07843137254903 + ], + "totalFees": 2612344, + "avgFee": 1212, + "avgFeeRate": 2, + "utxoSetChange": -2880, + "avgTxSize": 788.64, + "totalInputs": 9773, + "totalOutputs": 6893, + "totalOutputAmt": 264603969671, + "segwitTotalTxs": 1933, + "segwitTotalSize": 1556223, + "segwitTotalWeight": 3418707, + "feePercentiles": null, + "virtualSize": 998428.75, + "coinbaseAddress": "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "coinbaseAddresses": [ + "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "3Awm3FNpmwrbvAFVThRUFqgpbVuqWisni9" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 8ee90177614ecde53314fd67c46162f315852a07 OP_EQUAL", + "coinbaseSignatureAscii": "\u0003K–\r! Mined by Secpool p\u0000\u000b\u0005㈕Œ\u0001ú¾mm·®Kú{\u0012”án€\u000bEcñõÝë\\\u0007@1žºE`\u000f?\u0005Ò×')\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000ÂË~\u0002\u0000\u0000", + "header": "00e01e2acedf6db0523987887ed8b989d4f58b3d6b878a974548010000000000000000005063e99358b04a5d27d07a41a0f886b2e542a2680869b494fa41a7ecf774753d2d44ec676c79021741b12c13", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 141, + "name": "SECPOOL", + "slug": "secpool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 2623934, + "expectedWeight": 3991917, + "similarity": 0.9951244468050102 + } + }, + { + "id": "00000000000000000001b36ec470ae4ec39f5d975f665d6f40e9d35bfa65290d", + "height": 890444, + "version": 671080448, + "timestamp": 1743539347, + "bits": 386038124, + "nonce": 994357124, + "difficulty": 113757508810854, + "merkle_root": "c891d4bf68e22916274b667eb3287d50da2ddd63f8dad892da045cc2ad4a7b21", + "tx_count": 3797, + "size": 1500309, + "weight": 3993525, + "previousblockhash": "000000000000000000026e08b270834273511b353bed30d54706211adc96f5f6", + "mediantime": 1743533986, + "stale": false, + "extras": { + "reward": 318708524, + "coinbaseRaw": "034c960d082f5669614254432f2cfabe6d6d45b7fd7ab53a0914da7dcc9d21fe44f0936f5354169a56df9d5139f07afbc2b41000000000000000106fc0eb03f0ac2e851d18d8d9f85ad70000000000", + "orphans": [], + "medianFee": 4.064775540157046, + "feeRange": [ + 3.014354066985646, + 3.18368700265252, + 3.602836879432624, + 4.231825525040388, + 5.581730769230769, + 10, + 697.7151162790698 + ], + "totalFees": 6208524, + "avgFee": 1635, + "avgFeeRate": 6, + "utxoSetChange": 5755, + "avgTxSize": 395.02, + "totalInputs": 6681, + "totalOutputs": 12436, + "totalOutputAmt": 835839828101, + "segwitTotalTxs": 3351, + "segwitTotalSize": 1354446, + "segwitTotalWeight": 3410181, + "feePercentiles": null, + "virtualSize": 998381.25, + "coinbaseAddress": "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4", + "coinbaseAddresses": [ + "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4" + ], + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 fb37342f6275b13936799def06f2eb4c0f201515 OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "\u0003L–\r\b/ViaBTC/,ú¾mmE·ýzµ:\t\u0014Ú}̝!þDð“oST\u0016šVߝQ9ðzû´\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0010oÀë\u0003ð¬.…\u001d\u0018ØÙøZ×\u0000\u0000\u0000\u0000\u0000", + "header": "00e0ff27f6f596dc1a210647d530ed3b351b5173428370b2086e02000000000000000000217b4aadc25c04da92d8daf863dd2dda507d28b37e664b271629e268bfd491c8934cec676c79021784af443b", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 73, + "name": "ViaBTC", + "slug": "viabtc", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 6253024, + "expectedWeight": 3991868, + "similarity": 0.9862862477811569 + } + }, + { + "id": "00000000000000000001402065f940b9475159bdc962c92a66d3c6652ffa338a", + "height": 890445, + "version": 601202688, + "timestamp": 1743539574, + "bits": 386038124, + "nonce": 1647397133, + "difficulty": 113757508810854, + "merkle_root": "61d8294afa8f6bafa4d979a77d187dee5f75a6392f957ea647d96eefbbbc5e9b", + "tx_count": 3579, + "size": 1659862, + "weight": 3993406, + "previousblockhash": "00000000000000000001b36ec470ae4ec39f5d975f665d6f40e9d35bfa65290d", + "mediantime": 1743535677, + "stale": false, + "extras": { + "reward": 315617086, + "coinbaseRaw": "034d960d04764dec672f466f756e6472792055534120506f6f6c202364726f70676f6c642f4fac7c451540000000000000", + "orphans": [], + "medianFee": 2.5565329189526835, + "feeRange": [ + 1.521613832853026, + 2, + 2.2411347517730498, + 3, + 3, + 3.954954954954955, + 162.78343949044586 + ], + "totalFees": 3117086, + "avgFee": 871, + "avgFeeRate": 3, + "utxoSetChange": 1881, + "avgTxSize": 463.65000000000003, + "totalInputs": 7893, + "totalOutputs": 9774, + "totalOutputAmt": 324878597485, + "segwitTotalTxs": 3189, + "segwitTotalSize": 1538741, + "segwitTotalWeight": 3509030, + "feePercentiles": null, + "virtualSize": 998351.5, + "coinbaseAddress": "bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63", + "coinbaseAddresses": [ + "bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63", + "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38" + ], + "coinbaseSignature": "OP_PUSHNUM_1 OP_PUSHBYTES_32 0f9dab1a72f7c48da8a1df2f913bef649bfc0d77072dffd11329b8048293d7a3", + "coinbaseSignatureAscii": "\u0003M–\r\u0004vMìg/Foundry USA Pool #dropgold/O¬|E\u0015@\u0000\u0000\u0000\u0000\u0000\u0000", + "header": "00a0d5230d2965fa5bd3e9406f5d665f975d9fc34eae70c46eb3010000000000000000009b5ebcbbef6ed947a67e952f39a6755fee7d187da779d9a4af6b8ffa4a29d861764dec676c7902170d493162", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 111, + "name": "Foundry USA", + "slug": "foundryusa", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 3145370, + "expectedWeight": 3991903, + "similarity": 0.9903353189076812 + } + }, + { + "id": "00000000000000000001fe3d26b02146d0fc2192db06cbc12478d4b347f5306b", + "height": 890446, + "version": 537722880, + "timestamp": 1743541107, + "bits": 386038124, + "nonce": 826569764, + "difficulty": 113757508810854, + "merkle_root": "d9b320d7cb5aace80ca20b934b13b4a272121fbdd59f3aaba690e0326ca2c144", + "tx_count": 3998, + "size": 1541360, + "weight": 3993545, + "previousblockhash": "00000000000000000001402065f940b9475159bdc962c92a66d3c6652ffa338a", + "mediantime": 1743535803, + "stale": false, + "extras": { + "reward": 317976882, + "coinbaseRaw": "034e960d20202020204d696e656420627920536563706f6f6c2020202070001b04fad5fdfefabe6d6d59dd8ebce6e5aab8fb943bbdcede474b6f2d00a395a717970104a6958c17f1ca100000000000000000008089c9350200", + "orphans": [], + "medianFee": 3.3750830641948864, + "feeRange": [ + 2.397163120567376, + 3, + 3, + 3.463647199046484, + 4.49438202247191, + 7.213930348258707, + 476.1904761904762 + ], + "totalFees": 5476882, + "avgFee": 1370, + "avgFeeRate": 5, + "utxoSetChange": 4951, + "avgTxSize": 385.41, + "totalInputs": 7054, + "totalOutputs": 12005, + "totalOutputAmt": 983289729453, + "segwitTotalTxs": 3538, + "segwitTotalSize": 1396505, + "segwitTotalWeight": 3414233, + "feePercentiles": null, + "virtualSize": 998386.25, + "coinbaseAddress": "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "coinbaseAddresses": [ + "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "3Awm3FNpmwrbvAFVThRUFqgpbVuqWisni9" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 8ee90177614ecde53314fd67c46162f315852a07 OP_EQUAL", + "coinbaseSignatureAscii": "\u0003N–\r Mined by Secpool p\u0000\u001b\u0004úÕýþú¾mmYݎ¼æåª¸û”;½ÎÞGKo-\u0000£•§\u0017—\u0001\u0004¦•Œ\u0017ñÊ\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000€‰É5\u0002\u0000", + "header": "00000d208a33fa2f65c6d3662ac962c9bd595147b940f96520400100000000000000000044c1a26c32e090a6ab3a9fd5bd1f1272a2b4134b930ba20ce8ac5acbd720b3d97353ec676c79021724744431", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 141, + "name": "SECPOOL", + "slug": "secpool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 5601814, + "expectedWeight": 3991928, + "similarity": 0.9537877497871488 + } + }, + { + "id": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "height": 890447, + "version": 568860672, + "timestamp": 1743541240, + "bits": 386038124, + "nonce": 4008077709, + "difficulty": 113757508810854, + "merkle_root": "8c3b098e4e50b67075a4fc52bf4cd603aaa450c240c18a865c9ddc0f27104f5f", + "tx_count": 1919, + "size": 1747789, + "weight": 3993172, + "previousblockhash": "00000000000000000001fe3d26b02146d0fc2192db06cbc12478d4b347f5306b", + "mediantime": 1743536834, + "stale": false, + "extras": { + "reward": 314435106, + "coinbaseRaw": "034f960d0f2f736c7573682f65000002fba05ef1fabe6d6df8d29032ea6f9ab1debd223651f30887df779c6195869e70a7b787b3a15f4b1710000000000000000000ee5f0c00b20200000000", + "orphans": [], + "medianFee": 1.4653828213500366, + "feeRange": [ + 1.0845070422535212, + 1.2, + 1.51, + 2.0141129032258065, + 2.3893805309734515, + 4.025477707006369, + 300.0065359477124 + ], + "totalFees": 1935106, + "avgFee": 1008, + "avgFeeRate": 1, + "utxoSetChange": -4244, + "avgTxSize": 910.58, + "totalInputs": 9909, + "totalOutputs": 5665, + "totalOutputAmt": 210763861504, + "segwitTotalTxs": 1720, + "segwitTotalSize": 1629450, + "segwitTotalWeight": 3519924, + "feePercentiles": null, + "virtualSize": 998293, + "coinbaseAddress": "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11", + "coinbaseAddresses": [ + "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 1f0cbbec8bc4c945e4e16249b11eee911eded55f OP_EQUAL", + "coinbaseSignatureAscii": "\u0003O–\r\u000f/slush/e\u0000\u0000\u0002û ^ñú¾mmøÒ2êoš±Þ½\"6Qó\b‡ßwœa•†žp§·‡³¡_K\u0017\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000î_\f\u0000²\u0002\u0000\u0000\u0000\u0000", + "header": "0020e8216b30f547b3d47824c1cb06db9221fcd04621b0263dfe010000000000000000005f4f10270fdc9d5c868ac140c250a4aa03d64cbf52fca47570b6504e8e093b8cf853ec676c7902178d69e6ee", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 43, + "name": "Braiins Pool", + "slug": "braiinspool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 2059571, + "expectedWeight": 3991720, + "similarity": 0.9149852183486826 + } + } + ], + "mempool-blocks": [ + { + "blockSize": 1779311, + "blockVSize": 997968.5, + "nTx": 2132, + "totalFees": 2902870, + "medianFee": 2.0479263387949875, + "feeRange": [ + 1.0721153846153846, + 1.9980563654033041, + 2.2195704057279237, + 3.009493670886076, + 3.4955223880597015, + 6.0246913580246915, + 218.1818181818182 + ] + }, + { + "blockSize": 1959636, + "blockVSize": 997903.5, + "nTx": 497, + "totalFees": 1093076, + "medianFee": 1.102049424602265, + "feeRange": [ + 1.0401794819498267, + 1.0548148148148149, + 1.0548148148148149, + 1.0548148148148149, + 1.0548148148148149, + 1.0761096766260911, + 1.1021605957228275 + ] + }, + { + "blockSize": 1477260, + "blockVSize": 997997.25, + "nTx": 720, + "totalFees": 1016195, + "medianFee": 1.007409072434199, + "feeRange": [ + 1, + 1.0019120458891013, + 1.0040863981319323, + 1.0081019768823594, + 1.018450184501845, + 1.0203327171903882, + 1.0485018498190837 + ] + }, + { + "blockSize": 1021308, + "blockVSize": 431071.5, + "nTx": 823, + "totalFees": 432342, + "medianFee": 0, + "feeRange": [ + 1, + 1, + 1, + 1.0028011204481793, + 1.0042075736325387, + 1.0053475935828877, + 1.0068649885583525 + ] + } + ] +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx01_ws_mempool_blocks_01.json b/frontend/cypress/fixtures/details_rbf/tx01_ws_mempool_blocks_01.json new file mode 100644 index 000000000..47a685757 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx01_ws_mempool_blocks_01.json @@ -0,0 +1,68 @@ +{ + "mempool-blocks": [ + { + "blockSize": 1780038, + "blockVSize": 997989.75, + "nTx": 2134, + "totalFees": 2919589, + "medianFee": 2.0479263387949875, + "feeRange": [ + 1.0101010101010102, + 2, + 2.235576923076923, + 3.010452961672474, + 3.5240274599542336, + 6.032085561497326, + 218.1818181818182 + ] + }, + { + "blockSize": 1958446, + "blockVSize": 997996, + "nTx": 503, + "totalFees": 1093277, + "medianFee": 1.102049424602265, + "feeRange": [ + 1.0101010101010102, + 1.0548148148148149, + 1.0548148148148149, + 1.0548148148148149, + 1.067677314564158, + 1.0761096766260911, + 1.1021605957228275 + ] + }, + { + "blockSize": 1477611, + "blockVSize": 997927.5, + "nTx": 725, + "totalFees": 1016311, + "medianFee": 1.0075971559364956, + "feeRange": [ + 1, + 1.0019334049409236, + 1.0042075736325387, + 1.0081019768823594, + 1.018450184501845, + 1.0203327171903882, + 1.0548148148148149 + ] + }, + { + "blockSize": 1028219, + "blockVSize": 435137, + "nTx": 833, + "totalFees": 436414, + "medianFee": 0, + "feeRange": [ + 1, + 1, + 1, + 1.0028011204481793, + 1.004231311706629, + 1.0053475935828877, + 1.0068649885583525 + ] + } + ] +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx01_ws_stratum_jobs.json b/frontend/cypress/fixtures/details_rbf/tx01_ws_stratum_jobs.json new file mode 100644 index 000000000..48d2fe01e --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx01_ws_stratum_jobs.json @@ -0,0 +1,1235 @@ +{ + "stratumJobs": { + "2": { + "jobId": "1743541734_2517178", + "extraNonce": "00004237", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff510350960d142f756c74696d75732f373833e5005202fe934b78fabe6d6d4bec519f2060f58bc5ba10289fbedb1f9456cc864a15f153cbfed624324382181000000000000000", + "coinbase2": "ffffffff05220200000000000017a914332c82217820c32fb9c107b67356cfdc8f8da6a6876271cc120000000017a91472c52c9c71c5f644cf6e712661710503e65e69ad870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f524501ebbaf365b0d5fa072e2b2429db23696291f2c0383d81f61600af32dd72a5ac21aaf1274a3862af6d00000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "15992cde4826516b7dda391bcec363f8da4f6f335199ec76550b535d667c6bcc", + "4703e3571e20a0fa02766ca895c2043bd1c16364b4163a5d7fe3b669fa47b371", + "1d3822d7192f4c8978ca54d5e3b460ab420907199ba2d751ca682b76b9d8e030", + "55b47a1a7ab09d05ea95e4c36b2ece4f441f1787b6548656a67c93850bad28ca", + "1a231983cfb2837a358b85d7bba94851eeedb3f1af915a67165c49ca36c4ea42", + "1ef6e6152101ed2948ef74ebd6d62959f6b21e5c8c404def4faf88976a999c12", + "d28d9eb88badf7d180885592f79da466d0db6dbfd50b440d1c85a2c9ff1b8f01", + "eddef6dd40e0ed35c83129b484f4848021d781cc05da764df8bac3bc82745d12", + "b62ac40bb764914198729c39526751adc6bb047d3f0990ca742c863028a201da", + "7ec1b0edb5aef0b4a71ebf222d22220eb2c38b9fb07603485c9503b65ff43440", + "b4c149bc9f2f48b6887800706e50c669c86af320759914bcd2a53f7f89b92eda", + "c1cde7654ebfad4609b999082eba464c5a20eb1434e46ccd6d81510a13aa853b" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e6", + "cleanJobs": false, + "received": 1743541734950, + "pool": 2, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff510350960d142f756c74696d75732f373833e5005202fe934b78fabe6d6d4bec519f2060f58bc5ba10289fbedb1f9456cc864a15f153cbfed624324382181000000000000000000042370000000000000000ffffffff05220200000000000017a914332c82217820c32fb9c107b67356cfdc8f8da6a6876271cc120000000017a91472c52c9c71c5f644cf6e712661710503e65e69ad870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f524501ebbaf365b0d5fa072e2b2429db23696291f2c0383d81f61600af32dd72a5ac21aaf1274a3862af6d00000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541734, + "reward": 315388804, + "scriptsig": "0350960d142f756c74696d75732f373833e5005202fe934b78fabe6d6d4bec519f2060f58bc5ba10289fbedb1f9456cc864a15f153cbfed624324382181000000000000000000042370000000000000000" + }, + "4": { + "jobId": "450915", + "extraNonce": "00", + "extraNonce2Size": 7, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff580350960d1b4d696e6564206279204c75786f72205465636868005702fe933c3bfabe6d6d8ae1255df7029c6f68cd8ea446d227ef3dc39ef1ac7a55085969307c7bfaa736100000000000000000005813", + "coinbase2": "ffffffff05220200000000000017a914bf73ad4cf3a107812bad3deb310611bee49a3c79875173cc120000000017a914056adde53ebc396a1b3b678bb0d3a5c116ff430c870000000000000000266a24aa21a9ed0844cc9ee7cc307219c6002e18930cdde972a552bc8d1eabad7976208a0e1b8000000000000000002f6a2d434f524501a37cf4faa0758b26dca666f3e36d42fa15cc0106f459cc4ca322d298304ff163b2a360d756c5db8400000000000000002b6a2952534b424c4f434b3a1357f52232e7585b34c3e0cd5d433a50aac01937aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "71b686070fe7bdfaf6a8812ab487fd91d15e21c90fcf5d6bafca745e9b474a19", + "802911f2f657223b6cf06eec9b523b005b6bc2bec56b8fb35d777ef275398ba7", + "7a2626e535df0e88542c11e2c939de4d49e29a654da437bdbbf9d4b89ad8cea1", + "c49488289a1335c2bcf24b8661ce01ace0e6c35e0105a116fb55ccf981e46c59", + "8f8908b51423dc8ea13a97524f4159d629754f89c8ddfab140b8c695442215f3", + "48cc791e2e2fd0d16c9bf774967162a333ee2eeeedbace83f9ac5368bbc1dc97", + "8e25b97a71a9121cbec116aae71825cc1f8679687c44ef7dd94d2841244e16d9", + "e5328ff6cc72161b5c5e97152a4544f3900ac0a63abde9564919ffa30e28505d", + "3f12570807f882825eaa97b598308bd27e7f36d8ba79e039d81dcfe6cbf1fdd6", + "6afcf4b7a3fd41e8b6290459722bf83f0aff5a36b2b54e9f4210f9cb8bef51b9", + "1a9bdc30e04b9705106827938046b8485384b584254c2f93afdc50ed42a189b2" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e6", + "cleanJobs": false, + "received": 1743541735146, + "pool": 4, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff580350960d1b4d696e6564206279204c75786f72205465636868005702fe933c3bfabe6d6d8ae1255df7029c6f68cd8ea446d227ef3dc39ef1ac7a55085969307c7bfaa7361000000000000000000058130000000000000000ffffffff05220200000000000017a914bf73ad4cf3a107812bad3deb310611bee49a3c79875173cc120000000017a914056adde53ebc396a1b3b678bb0d3a5c116ff430c870000000000000000266a24aa21a9ed0844cc9ee7cc307219c6002e18930cdde972a552bc8d1eabad7976208a0e1b8000000000000000002f6a2d434f524501a37cf4faa0758b26dca666f3e36d42fa15cc0106f459cc4ca322d298304ff163b2a360d756c5db8400000000000000002b6a2952534b424c4f434b3a1357f52232e7585b34c3e0cd5d433a50aac01937aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541734, + "reward": 315389299, + "scriptsig": "0350960d1b4d696e6564206279204c75786f72205465636868005702fe933c3bfabe6d6d8ae1255df7029c6f68cd8ea446d227ef3dc39ef1ac7a55085969307c7bfaa7361000000000000000000058130000000000000000" + }, + "36": { + "jobId": "B75cp1aWq", + "extraNonce": "00", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff640350960d2cfabe6d6da35ece7974edab96759db21f0953f4eaa2200241771a0976d18c5c6e27c5340a10000000f09f909f092f4632506f6f6c2f6b000000000000000000000000000000000000000000000000000000000000000000000005", + "coinbase2": "0722020000000000001976a914c6740a12d0a7d556f89782bf5faf0e12cf25a63988ac3a4acb12000000001976a91469f2a01f4ff9e6ac24df9062e9828753474b348088ac0000000000000000266a24aa21a9ed534cb114f7fd20c22a47d45e6bb6c5460bf3a9cce404265e0961cfd6c881190700000000000000002f6a2d434f5245017c706ca44a28fdd25761250961276bd61d5aa87be7ec323813c943336c579e238228a8ebd096a7e50000000000000000126a10455853415401051b0f0e0e0b1f1200130000000000000000266a2448617468241c617362765014f47be645944e22747340fba7c5a705548dbae9b4a84e62ff00000000000000002c6a4c2952534b424c4f434b3a81cc66e44e74bd510bfde3b83b286662e6a2e597aa8a53c435370b130070fc91dc608334", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "723847223fbee3db7918859bb384999905cba259070859ec77ebfa329b05065a", + "cd7913bf8e499b13ad83c84b2dcc5de216823ff59e45cc76af8f47570114476e", + "2cec318ccfb9046dd1b929e52e7d131cfbf0499fa0f17470944bd0a39d7392a7", + "98531b18c2ce0bfd02dbc7efc9463a4a891cb30c3dda244ebc17d34a52e6e2b9", + "66a73af7f957129069b86ec454ab2e8ac4399e2f6549d6e0e4367844abc8d546", + "6c30f7579f6b6734d6ce96b86e33d318513bafeee287870a960c5a7081244a0c", + "aac144dd768fb69fb813f0eebdd02afb8b55e00d0a61101cf167ecda07a1bc2b", + "ed81f05edaf48584c328a7d4c1f2de3720c2eaba0fa7aa37e4f0792603c60b67", + "98ce77b30480722596a787892493dfdac342b5c717aa5d2a3a4ca1a4b461321c", + "d1372489dd7304c4c44214da63b071c987ee084440861140fb56d5ea03ba8e39", + "efeeb673f474c3d4724bf2503a0903ff73ff01afabc37ffa694f8c79022d7078" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55cb", + "cleanJobs": false, + "received": 1743541719279, + "pool": 36, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff640350960d2cfabe6d6da35ece7974edab96759db21f0953f4eaa2200241771a0976d18c5c6e27c5340a10000000f09f909f092f4632506f6f6c2f6b0000000000000000000000000000000000000000000000000000000000000000000000050000000000000000000722020000000000001976a914c6740a12d0a7d556f89782bf5faf0e12cf25a63988ac3a4acb12000000001976a91469f2a01f4ff9e6ac24df9062e9828753474b348088ac0000000000000000266a24aa21a9ed534cb114f7fd20c22a47d45e6bb6c5460bf3a9cce404265e0961cfd6c881190700000000000000002f6a2d434f5245017c706ca44a28fdd25761250961276bd61d5aa87be7ec323813c943336c579e238228a8ebd096a7e50000000000000000126a10455853415401051b0f0e0e0b1f1200130000000000000000266a2448617468241c617362765014f47be645944e22747340fba7c5a705548dbae9b4a84e62ff00000000000000002c6a4c2952534b424c4f434b3a81cc66e44e74bd510bfde3b83b286662e6a2e597aa8a53c435370b130070fc91dc608334", + "height": 890448, + "timestamp": 1743541707, + "reward": 315313244, + "scriptsig": "0350960d2cfabe6d6da35ece7974edab96759db21f0953f4eaa2200241771a0976d18c5c6e27c5340a10000000f09f909f092f4632506f6f6c2f6b0000000000000000000000000000000000000000000000000000000000000000000000050000000000" + }, + "43": { + "jobId": "131d", + "extraNonce": "00", + "extraNonce2Size": 6, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4c0350960d0f2f736c7573682f65000303fe9334c4fabe6d6dbb9c929fe497398b9a628aa161b72ed26b10e8ca5f29c485f42b468355043a15100000000000000000004e802e", + "coinbase2": "ffffffff038473cc120000000017a9141f0cbbec8bc4c945e4e16249b11eee911eded55f870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "15992cde4826516b7dda391bcec363f8da4f6f335199ec76550b535d667c6bcc", + "4703e3571e20a0fa02766ca895c2043bd1c16364b4163a5d7fe3b669fa47b371", + "1d3822d7192f4c8978ca54d5e3b460ab420907199ba2d751ca682b76b9d8e030", + "55b47a1a7ab09d05ea95e4c36b2ece4f441f1787b6548656a67c93850bad28ca", + "1a231983cfb2837a358b85d7bba94851eeedb3f1af915a67165c49ca36c4ea42", + "1ef6e6152101ed2948ef74ebd6d62959f6b21e5c8c404def4faf88976a999c12", + "d28d9eb88badf7d180885592f79da466d0db6dbfd50b440d1c85a2c9ff1b8f01", + "eddef6dd40e0ed35c83129b484f4848021d781cc05da764df8bac3bc82745d12", + "b62ac40bb764914198729c39526751adc6bb047d3f0990ca742c863028a201da", + "7ec1b0edb5aef0b4a71ebf222d22220eb2c38b9fb07603485c9503b65ff43440", + "b4c149bc9f2f48b6887800706e50c669c86af320759914bcd2a53f7f89b92eda", + "c1cde7654ebfad4609b999082eba464c5a20eb1434e46ccd6d81510a13aa853b" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e6", + "cleanJobs": false, + "received": 1743541734908, + "pool": 43, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4c0350960d0f2f736c7573682f65000303fe9334c4fabe6d6dbb9c929fe497398b9a628aa161b72ed26b10e8ca5f29c485f42b468355043a15100000000000000000004e802e00000000000000ffffffff038473cc120000000017a9141f0cbbec8bc4c945e4e16249b11eee911eded55f870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541734, + "reward": 315388804, + "scriptsig": "0350960d0f2f736c7573682f65000303fe9334c4fabe6d6dbb9c929fe497398b9a628aa161b72ed26b10e8ca5f29c485f42b468355043a15100000000000000000004e802e00000000000000" + }, + "44": { + "jobId": "3750742", + "extraNonce": "00002c10", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff580350960d1b4d696e656420627920416e74506f6f6c3830369c001700fe93b694fabe6d6d433177d6fe558d8cee3035bb1b481156f8b411c21bddef7fb8aec740e4617c501000000000000000", + "coinbase2": "ffffffff06220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade7876271cc120000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d54e3ecda72cb7961caa4b541b1e322bcfe0b5a0300000000000000000146a12455853415401000d130f0e0e0b041f12001300000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "15992cde4826516b7dda391bcec363f8da4f6f335199ec76550b535d667c6bcc", + "4703e3571e20a0fa02766ca895c2043bd1c16364b4163a5d7fe3b669fa47b371", + "1d3822d7192f4c8978ca54d5e3b460ab420907199ba2d751ca682b76b9d8e030", + "55b47a1a7ab09d05ea95e4c36b2ece4f441f1787b6548656a67c93850bad28ca", + "1a231983cfb2837a358b85d7bba94851eeedb3f1af915a67165c49ca36c4ea42", + "1ef6e6152101ed2948ef74ebd6d62959f6b21e5c8c404def4faf88976a999c12", + "d28d9eb88badf7d180885592f79da466d0db6dbfd50b440d1c85a2c9ff1b8f01", + "eddef6dd40e0ed35c83129b484f4848021d781cc05da764df8bac3bc82745d12", + "b62ac40bb764914198729c39526751adc6bb047d3f0990ca742c863028a201da", + "7ec1b0edb5aef0b4a71ebf222d22220eb2c38b9fb07603485c9503b65ff43440", + "b4c149bc9f2f48b6887800706e50c669c86af320759914bcd2a53f7f89b92eda", + "c1cde7654ebfad4609b999082eba464c5a20eb1434e46ccd6d81510a13aa853b" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e7", + "cleanJobs": false, + "received": 1743541735750, + "pool": 44, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff580350960d1b4d696e656420627920416e74506f6f6c3830369c001700fe93b694fabe6d6d433177d6fe558d8cee3035bb1b481156f8b411c21bddef7fb8aec740e4617c50100000000000000000002c100000000000000000ffffffff06220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade7876271cc120000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d54e3ecda72cb7961caa4b541b1e322bcfe0b5a0300000000000000000146a12455853415401000d130f0e0e0b041f12001300000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541735, + "reward": 315388804, + "scriptsig": "0350960d1b4d696e656420627920416e74506f6f6c3830369c001700fe93b694fabe6d6d433177d6fe558d8cee3035bb1b481156f8b411c21bddef7fb8aec740e4617c50100000000000000000002c100000000000000000" + }, + "49": { + "jobId": "67444bb00005e280", + "extraNonce": "1eecec72", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff350350960d0004d155ec67047a5741090c", + "coinbase2": "0a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672fffffffff03dd276b12000000001976a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac483a60000000000016001451ed61d2f6aa260cc72cdf743e4e436a82c010270000000000000000266a24aa21a9ed6c1f16b33ae5c1c628b53c6011f38866955c8b59448e5aee14f9967c8cb7ab1c00000000", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "723847223fbee3db7918859bb384999905cba259070859ec77ebfa329b05065a", + "cd7913bf8e499b13ad83c84b2dcc5de216823ff59e45cc76af8f47570114476e", + "2cec318ccfb9046dd1b929e52e7d131cfbf0499fa0f17470944bd0a39d7392a7", + "98531b18c2ce0bfd02dbc7efc9463a4a891cb30c3dda244ebc17d34a52e6e2b9", + "66a73af7f957129069b86ec454ab2e8ac4399e2f6549d6e0e4367844abc8d546", + "6c30f7579f6b6734d6ce96b86e33d318513bafeee287870a960c5a7081244a0c", + "717d5dac15b3253aeec8e1079141760337bebe0101d0b4e4c43384416ca132ad", + "9eba7f7def1ad83bb3d7df151f880fa76c4b4ec3b531a4e0856a1e7437d3d060", + "0558088354e8bf625f19e28e6ec02231d61a31c26f215d2e1253baf0452064bb", + "6c25959fb0244719945408c5da48055f0d9ad30240ebc520c2d81b5df1b0f3d7", + "150163eb46086007fafbef5c2c71faf3f8b4690c338e2d74a966ff2650df63c3" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55d1", + "cleanJobs": false, + "received": 1743541713511, + "pool": 49, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff350350960d0004d155ec67047a5741090c1eecec7200000000000000000a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672fffffffff03dd276b12000000001976a91462e907b15cbf27d5425399ebf6f0fb50ebb88f1888ac483a60000000000016001451ed61d2f6aa260cc72cdf743e4e436a82c010270000000000000000266a24aa21a9ed6c1f16b33ae5c1c628b53c6011f38866955c8b59448e5aee14f9967c8cb7ab1c00000000", + "height": 890448, + "timestamp": 1743541713, + "reward": 315318821, + "scriptsig": "0350960d0004d155ec67047a5741090c1eecec7200000000000000000a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672f" + }, + "73": { + "jobId": "8711", + "extraNonce": "41fade6a", + "extraNonce2Size": 4, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff610350960d1e2f5669614254432f4d696e6564206279206d6f6e6f6e6175746963616c2f2cfabe6d6d0d586d106170cc5c7380e81f3e79c40726b2313bdb6263eedba507d9cdc53bca1000000000000000101187c609caa65f88", + "coinbase2": "ffffffff040388cb12000000001976a914fb37342f6275b13936799def06f2eb4c0f20151588ac00000000000000002b6a2952534b424c4f434b3a280a7381d0ccac30cd6dcfd0beae78f22cb58d6caa8a53c435370b120070fc920000000000000000146a124558534154011508000113021b1a1f1200130000000000000000266a24aa21a9ed10c31ffb387781fdab75b9b2e4994223622049c78f1f81d05a4a421ae178626000000000", + "merkleBranches": [ + "b4efa4f5b9d41f9fc63480038ba7586dac490d08fd738aaa691d49bcbee1dd54", + "97c694c3a659a43a7ed8f7e2ae4d06927a152b348f33e7a51c1b5dd242631e72", + "09350f4bc26ad644fadeec9bea500daadb35bf43f8f39d82a8adb708661a8f18", + "e5f3663e21ccc3f4565529a73b11811dd68abcc9ac701aa494b7b18782eefa23", + "bcdfb143b322e8677b8edb90417a1ec739a6d9eb8efc84e5ba6424e7f3adfc0f", + "b90794158874321306985105e45aa872a9646a5bf683a070d7ca6602450ad8e5", + "26e4fb36ec030a6510c9047120575dca2a8b8760674af4284f5c27e53f03f46b", + "e6a15e522b650f9cebfdd003c0d29f5ca571bf704d63b9aa627ce915acb229f7", + "392d3e593f80e9180a3bce84ebaa044d5406cad39897c78e58e66ca59e7499c8", + "ce8409542b60ed5ca9c567b89fa9a7c2e5e8e2bbf2b42b0ce8edb0836a7a2e82", + "0e8201e0367364ed40263747547d3a0c26926d2b94799b414c22a25c6fc04d43", + "33e95cd9da59d9e33e239b9b4e647a2da98f36ef6a137f40ba428782bb1c4021" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55d7", + "cleanJobs": false, + "received": 1743541719896, + "pool": 73, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff610350960d1e2f5669614254432f4d696e6564206279206d6f6e6f6e6175746963616c2f2cfabe6d6d0d586d106170cc5c7380e81f3e79c40726b2313bdb6263eedba507d9cdc53bca1000000000000000101187c609caa65f8841fade6a00000000ffffffff040388cb12000000001976a914fb37342f6275b13936799def06f2eb4c0f20151588ac00000000000000002b6a2952534b424c4f434b3a280a7381d0ccac30cd6dcfd0beae78f22cb58d6caa8a53c435370b120070fc920000000000000000146a124558534154011508000113021b1a1f1200130000000000000000266a24aa21a9ed10c31ffb387781fdab75b9b2e4994223622049c78f1f81d05a4a421ae178626000000000", + "height": 890448, + "timestamp": 1743541719, + "reward": 315328515, + "scriptsig": "0350960d1e2f5669614254432f4d696e6564206279206d6f6e6f6e6175746963616c2f2cfabe6d6d0d586d106170cc5c7380e81f3e79c40726b2313bdb6263eedba507d9cdc53bca1000000000000000101187c609caa65f8841fade6a00000000" + }, + "94": { + "jobId": "1186343", + "extraNonce": "0000776f", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff570350960d1a2f706f6f6c696e2e636f6d2f66702f3936369c01b704fe93ba97fabe6d6d89255a8f9c17d3a3dc753a3e2b2eb55a116c1d7280bcf4b4f76a0af1f2e12ce31000000000000000", + "coinbase2": "ffffffff038473cc120000000017a9141366dca425687186b9b54e27e4ed48163b5beb80870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "15992cde4826516b7dda391bcec363f8da4f6f335199ec76550b535d667c6bcc", + "4703e3571e20a0fa02766ca895c2043bd1c16364b4163a5d7fe3b669fa47b371", + "1d3822d7192f4c8978ca54d5e3b460ab420907199ba2d751ca682b76b9d8e030", + "55b47a1a7ab09d05ea95e4c36b2ece4f441f1787b6548656a67c93850bad28ca", + "1a231983cfb2837a358b85d7bba94851eeedb3f1af915a67165c49ca36c4ea42", + "1ef6e6152101ed2948ef74ebd6d62959f6b21e5c8c404def4faf88976a999c12", + "d28d9eb88badf7d180885592f79da466d0db6dbfd50b440d1c85a2c9ff1b8f01", + "eddef6dd40e0ed35c83129b484f4848021d781cc05da764df8bac3bc82745d12", + "b62ac40bb764914198729c39526751adc6bb047d3f0990ca742c863028a201da", + "7ec1b0edb5aef0b4a71ebf222d22220eb2c38b9fb07603485c9503b65ff43440", + "b4c149bc9f2f48b6887800706e50c669c86af320759914bcd2a53f7f89b92eda", + "c1cde7654ebfad4609b999082eba464c5a20eb1434e46ccd6d81510a13aa853b" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e7", + "cleanJobs": false, + "received": 1743541735127, + "pool": 94, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff570350960d1a2f706f6f6c696e2e636f6d2f66702f3936369c01b704fe93ba97fabe6d6d89255a8f9c17d3a3dc753a3e2b2eb55a116c1d7280bcf4b4f76a0af1f2e12ce310000000000000000000776f0000000000000000ffffffff038473cc120000000017a9141366dca425687186b9b54e27e4ed48163b5beb80870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541735, + "reward": 315388804, + "scriptsig": "0350960d1a2f706f6f6c696e2e636f6d2f66702f3936369c01b704fe93ba97fabe6d6d89255a8f9c17d3a3dc753a3e2b2eb55a116c1d7280bcf4b4f76a0af1f2e12ce310000000000000000000776f0000000000000000" + }, + "102": { + "jobId": "33", + "extraNonce": "04264b21", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff570350960d04d655ec67537069646572506f6f6c2f7573383838382ffabe6d6d013135a66fcdf604440c12e28ffb6f448d1d7b633d16737217081646c41912270100000000000000b286b3fc", + "coinbase2": "ffffffff04b55dcb12000000001976a914717a4c9074577a05af94271c32b249d298a22d9888ac0000000000000000266a24aa21a9eded8df9e053ff66fe09268a9df4e369d60a48437a012a1b157e143bb967379cab00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d596a6689031f48a857d344e1a42fdb272bb15d6210000000000000000126a10455853415401120f080304111f12001300000000", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "723847223fbee3db7918859bb384999905cba259070859ec77ebfa329b05065a", + "cd7913bf8e499b13ad83c84b2dcc5de216823ff59e45cc76af8f47570114476e", + "2cec318ccfb9046dd1b929e52e7d131cfbf0499fa0f17470944bd0a39d7392a7", + "98531b18c2ce0bfd02dbc7efc9463a4a891cb30c3dda244ebc17d34a52e6e2b9", + "66a73af7f957129069b86ec454ab2e8ac4399e2f6549d6e0e4367844abc8d546", + "6c30f7579f6b6734d6ce96b86e33d318513bafeee287870a960c5a7081244a0c", + "717d5dac15b3253aeec8e1079141760337bebe0101d0b4e4c43384416ca132ad", + "9eba7f7def1ad83bb3d7df151f880fa76c4b4ec3b531a4e0856a1e7437d3d060", + "5fb573157a34ed041274de444f9dd86e210189cf325bdbde44a92edded8f1b90", + "1e77c2e67bf442dd78a2e078fe9b7bb1bece599bf7e77e2086e5d0659afedf9e", + "e6c75e3d1859a1d8d6a10ac5fe990969202fa37d0715c7ca50c2159e3b3a463e" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55ce", + "cleanJobs": false, + "received": 1743541719031, + "pool": 102, + "coinbase": "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff570350960d04d655ec67537069646572506f6f6c2f7573383838382ffabe6d6d013135a66fcdf604440c12e28ffb6f448d1d7b633d16737217081646c41912270100000000000000b286b3fc04264b210000000000000000ffffffff04b55dcb12000000001976a914717a4c9074577a05af94271c32b249d298a22d9888ac0000000000000000266a24aa21a9eded8df9e053ff66fe09268a9df4e369d60a48437a012a1b157e143bb967379cab00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d596a6689031f48a857d344e1a42fdb272bb15d6210000000000000000126a10455853415401120f080304111f12001300000000", + "height": 890448, + "timestamp": 1743541710, + "reward": 315317685, + "scriptsig": "0350960d04d655ec67537069646572506f6f6c2f7573383838382ffabe6d6d013135a66fcdf604440c12e28ffb6f448d1d7b633d16737217081646c41912270100000000000000b286b3fc04264b210000000000000000" + }, + "105": { + "jobId": "4287955", + "extraNonce": "0000e9a5", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff500350960d1362696e616e63652f3832319c001606fe93bd58fabe6d6d2cbf0bfed0a1d41f829160053963a92e7a2c44942646e992955ce00fd53f62991000000000000000", + "coinbase2": "ffffffff05220200000000000017a914265ae1340f5d442d099ffc27b443ffdba4bc0346876271cc120000000017a9149e3e8a50d9acb3ac7649625432e2207c25e0faf8870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f5245012d058b58dcf4b0db11168c62d3109f6e02710b02221456a6c24c9891680d295eb6bc57c6fb68e8f100000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "15992cde4826516b7dda391bcec363f8da4f6f335199ec76550b535d667c6bcc", + "4703e3571e20a0fa02766ca895c2043bd1c16364b4163a5d7fe3b669fa47b371", + "1d3822d7192f4c8978ca54d5e3b460ab420907199ba2d751ca682b76b9d8e030", + "55b47a1a7ab09d05ea95e4c36b2ece4f441f1787b6548656a67c93850bad28ca", + "1a231983cfb2837a358b85d7bba94851eeedb3f1af915a67165c49ca36c4ea42", + "1ef6e6152101ed2948ef74ebd6d62959f6b21e5c8c404def4faf88976a999c12", + "d28d9eb88badf7d180885592f79da466d0db6dbfd50b440d1c85a2c9ff1b8f01", + "eddef6dd40e0ed35c83129b484f4848021d781cc05da764df8bac3bc82745d12", + "b62ac40bb764914198729c39526751adc6bb047d3f0990ca742c863028a201da", + "7ec1b0edb5aef0b4a71ebf222d22220eb2c38b9fb07603485c9503b65ff43440", + "b4c149bc9f2f48b6887800706e50c669c86af320759914bcd2a53f7f89b92eda", + "c1cde7654ebfad4609b999082eba464c5a20eb1434e46ccd6d81510a13aa853b" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e7", + "cleanJobs": false, + "received": 1743541735177, + "pool": 105, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff500350960d1362696e616e63652f3832319c001606fe93bd58fabe6d6d2cbf0bfed0a1d41f829160053963a92e7a2c44942646e992955ce00fd53f629910000000000000000000e9a50000000000000000ffffffff05220200000000000017a914265ae1340f5d442d099ffc27b443ffdba4bc0346876271cc120000000017a9149e3e8a50d9acb3ac7649625432e2207c25e0faf8870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f5245012d058b58dcf4b0db11168c62d3109f6e02710b02221456a6c24c9891680d295eb6bc57c6fb68e8f100000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541735, + "reward": 315388804, + "scriptsig": "0350960d1362696e616e63652f3832319c001606fe93bd58fabe6d6d2cbf0bfed0a1d41f829160053963a92e7a2c44942646e992955ce00fd53f629910000000000000000000e9a50000000000000000" + }, + "111": { + "jobId": "7", + "extraNonce": "31a044e7", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff310350960d04e155ec672f466f756e6472792055534120506f6f6c202364726f70676f6c642f", + "coinbase2": "ffffffff0522020000000000002251200f9dab1a72f7c48da8a1df2f913bef649bfc0d77072dffd11329b8048293d7a37f28cc12000000002200207086320071974eef5e72eaa01dd9096e10c0383483855ea6b344259c244f73c20000000000000000266a24aa21a9eda9d49baa733153f684d3e885e731818571f650adfef974d99fe1e11b69af0bc600000000000000002f6a2d434f524501359b5fbc5b294e953dbec5cbb769e2186bb30e56e6d18fda214e5b9f350ffc7b6cf3058b9026e76500000000000000002b6a2952534b424c4f434b3aaadde8fc40282f7f5a2c02a16700e42ad9f6bc5aaa8a53c435370b120070fc9200000000", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "470ea829e1e3085254952441894afcb97582e5675fbb35c226edf79a226890d5", + "09a4875a0b37d78e9643154473bb974b2eba5c800e3cd58c99adfb196f383358", + "2cec318ccfb9046dd1b929e52e7d131cfbf0499fa0f17470944bd0a39d7392a7", + "98531b18c2ce0bfd02dbc7efc9463a4a891cb30c3dda244ebc17d34a52e6e2b9", + "66a73af7f957129069b86ec454ab2e8ac4399e2f6549d6e0e4367844abc8d546", + "2af55ecd2164a88f5df8c3de727633690dabb2de4b61827ac86adcd927cd3e24", + "8601c35a421ac857f22a1ea05b53f003771aaaaf1e65bea339bdc80a9986e671", + "230d09f9ee198e35e1b1be1de2f87acf1a1e73690d680b85ba96bd21790e8766", + "8f4cee6b0fc9bca8608134c4a93116cf4de4ab753863219201cd20ff06b18d1e", + "4de04f813d0c4fbd5b4fb79e490f76ba4da31391d25f0447f1e9eccc935c9fe4", + "c3de6f84d58eeb5277be653e241331dddd19d056ce00ff7d3d9ecdefadfa9067" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e1", + "cleanJobs": false, + "received": 1743541729815, + "pool": 111, + "coinbase": "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff310350960d04e155ec672f466f756e6472792055534120506f6f6c202364726f70676f6c642f31a044e70000000000000000ffffffff0522020000000000002251200f9dab1a72f7c48da8a1df2f913bef649bfc0d77072dffd11329b8048293d7a37f28cc12000000002200207086320071974eef5e72eaa01dd9096e10c0383483855ea6b344259c244f73c20000000000000000266a24aa21a9eda9d49baa733153f684d3e885e731818571f650adfef974d99fe1e11b69af0bc600000000000000002f6a2d434f524501359b5fbc5b294e953dbec5cbb769e2186bb30e56e6d18fda214e5b9f350ffc7b6cf3058b9026e76500000000000000002b6a2952534b424c4f434b3aaadde8fc40282f7f5a2c02a16700e42ad9f6bc5aaa8a53c435370b120070fc9200000000", + "height": 890448, + "timestamp": 1743541729, + "reward": 315370145, + "scriptsig": "0350960d04e155ec672f466f756e6472792055534120506f6f6c202364726f70676f6c642f31a044e70000000000000000" + }, + "112": { + "jobId": "12", + "extraNonce": "050300c0", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff290350960d04db55ec672f53424943727970746f2e636f6d20506f6f6c2f", + "coinbase2": "ffffffff02ffc8cb1200000000160014cab30e9b367d646f326ace7fcaf3c9ce4afc37530000000000000000266a24aa21a9edd0dd8d14ec5a97c6fd3de15f02c86d889e932e0262807795fa7c127e5a8fa0be00000000", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "b41e3afa7672a36d6a462612f3bdc9ee8000388471b29ba1d187135b435f3efa", + "4236249ae2883af9d68c5570f893aa808a2a15d61ea654444cebfb9b3ba2c598", + "2cec318ccfb9046dd1b929e52e7d131cfbf0499fa0f17470944bd0a39d7392a7", + "98531b18c2ce0bfd02dbc7efc9463a4a891cb30c3dda244ebc17d34a52e6e2b9", + "66a73af7f957129069b86ec454ab2e8ac4399e2f6549d6e0e4367844abc8d546", + "4b5b7ee5fe7813f0e1ae5d1265215062d449d5c50b5edede2816274216e88a8d", + "6624f27663e2330c010fe68f5f397ebef70a1f6188ccb104e9eff97a9e48bb02", + "b94d8d0e4cc4b8b62bd4f4d70fc4497f85b05280b43e0b5dc399cc8778a1074a", + "a97d374c628f07cc5283b577558c3c0e3f2f3c8dac8df6ccdea7cf26b6776fa5", + "190f5f1bc8535d731fd095dd8e97ec3c33facdbce8614ca654017f3baf84a3fe", + "102b7769fe8b53ca163151eb461c767d331c465d95056f556e38a07298e6046a" + ], + "version": "20000004", + "bits": "1702796c", + "time": "67ec55db", + "cleanJobs": false, + "received": 1743541728640, + "pool": 112, + "coinbase": "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff290350960d04db55ec672f53424943727970746f2e636f6d20506f6f6c2f050300c00000000000000000ffffffff02ffc8cb1200000000160014cab30e9b367d646f326ace7fcaf3c9ce4afc37530000000000000000266a24aa21a9edd0dd8d14ec5a97c6fd3de15f02c86d889e932e0262807795fa7c127e5a8fa0be00000000", + "height": 890448, + "timestamp": 1743541723, + "reward": 315345151, + "scriptsig": "0350960d04db55ec672f53424943727970746f2e636f6d20506f6f6c2f050300c00000000000000000" + }, + "141": { + "jobId": "1832214", + "extraNonce": "0000ac40", + "extraNonce2Size": 4, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff510350960d184d696e656420627920536563506f6f6c58007904fe939454fabe6d6d2e07daf9ae89d5bfcdb87dd64455e7357ca9fb3708a2cf08ca8f30a38142a68e1000000000000000", + "coinbase2": "ffffffff05220200000000000017a9148ee90177614ecde53314fd67c46162f315852a07875173cc120000000017a9146582f2551e2a47e1ae8b03fb666401ed7c4552ef870000000000000000266a24aa21a9ed0844cc9ee7cc307219c6002e18930cdde972a552bc8d1eabad7976208a0e1b8000000000000000002f6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733942c01262fa046fdb8ba0dfce3753405c74aed0e00000000000000002b6a2952534b424c4f434b3a0374e206447243d32da96205942c62a298fddbd7aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "71b686070fe7bdfaf6a8812ab487fd91d15e21c90fcf5d6bafca745e9b474a19", + "802911f2f657223b6cf06eec9b523b005b6bc2bec56b8fb35d777ef275398ba7", + "7a2626e535df0e88542c11e2c939de4d49e29a654da437bdbbf9d4b89ad8cea1", + "c49488289a1335c2bcf24b8661ce01ace0e6c35e0105a116fb55ccf981e46c59", + "8f8908b51423dc8ea13a97524f4159d629754f89c8ddfab140b8c695442215f3", + "48cc791e2e2fd0d16c9bf774967162a333ee2eeeedbace83f9ac5368bbc1dc97", + "8e25b97a71a9121cbec116aae71825cc1f8679687c44ef7dd94d2841244e16d9", + "e5328ff6cc72161b5c5e97152a4544f3900ac0a63abde9564919ffa30e28505d", + "3f12570807f882825eaa97b598308bd27e7f36d8ba79e039d81dcfe6cbf1fdd6", + "6afcf4b7a3fd41e8b6290459722bf83f0aff5a36b2b54e9f4210f9cb8bef51b9", + "1a9bdc30e04b9705106827938046b8485384b584254c2f93afdc50ed42a189b2" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e6", + "cleanJobs": false, + "received": 1743541735015, + "pool": 141, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff510350960d184d696e656420627920536563506f6f6c58007904fe939454fabe6d6d2e07daf9ae89d5bfcdb87dd64455e7357ca9fb3708a2cf08ca8f30a38142a68e10000000000000000000ac4000000000ffffffff05220200000000000017a9148ee90177614ecde53314fd67c46162f315852a07875173cc120000000017a9146582f2551e2a47e1ae8b03fb666401ed7c4552ef870000000000000000266a24aa21a9ed0844cc9ee7cc307219c6002e18930cdde972a552bc8d1eabad7976208a0e1b8000000000000000002f6a2d434f524501a21cbd3caa4fe89bccd1d716c92ce4533e4d4733942c01262fa046fdb8ba0dfce3753405c74aed0e00000000000000002b6a2952534b424c4f434b3a0374e206447243d32da96205942c62a298fddbd7aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541734, + "reward": 315389299, + "scriptsig": "0350960d184d696e656420627920536563506f6f6c58007904fe939454fabe6d6d2e07daf9ae89d5bfcdb87dd64455e7357ca9fb3708a2cf08ca8f30a38142a68e10000000000000000000ac4000000000" + }, + "142": { + "jobId": "67ec55c62cc0f502", + "extraNonce": "b10cf017", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1e0350960d163c204f4345414e2e58595a203e0f0f30304f43423400024087ffffffff140000000000000000166a144f4342313fd6fdce13000000290000002c029c30562b26000000000016001472fb714ecb210311655a3da22729ff8626bdea590000000000000000106a0e1a27", + "coinbase2": "f65ccf020000000017a9142b636a84e5b12177cd49c687b5a1ebd07fb83fbf8783c76e0200000000220020ac11a29a5721e9602e396acd7e5216c9196b7dbb14cf573921c46e6fea38ac09d6632e020000000017a91449b58fb476449dab3a2e175fe2c1f0d753a33e92878b8ad9010000000017a914cc888b5846746a5a7f449eea90082feaa09b9bf387833cbe01000000001600146983f7fe983e96dc5b43677fb14822a075596364b136ae0100000000220020dc406857cac1b9d3f53c6986acb78e7774c604281076d10d3fc8e873e18df6d0663e6e0100000000160014d192719c5be868068f023b9383b3cbecb6afbb1639205300000000001976a91412c00ad1271bd4c91e4f8ff11e738c285181399d88ac49844e000000000017a9141ef31e9bc9ccb532b8c01950727990cca190656a877aa148000000000016001404afd9f6e36ba7cadb62133c458a5318f41a33de731b42000000000016001415c0899a887e36f6620619b621855d40b41cbf838b5a3b0000000000220020e65791d3c340710f7aebf117d1f38e1c5383c1df0345a2082e8ebff945e9994646d83a00000000001600140345e587cb42c201c30e505ea9f6f5fb935876ac5df731000000000017a914278dadd16d1314122e4dde67ff9152a61c3a9f4787254f3000000000001600146d095474bec41ccc8ad40f94c74dd25b79b83579e85b7e020000000017a914413b5901fe4e591c95405fd446b5b002da575bf0870000000000000000266a24aa21a9ed2a3f54b40a557c2884545b770a050154a46b5dc132eec3604f01714f413b32a300000000", + "merkleBranches": [ + "2b797d78bf7de288eccd666e7b81bf04bc96beb0672b6ece12a8e66c8ab3e989", + "81890975bdb859a5c5527a40a7354d0d60b133fa4eaef69f1738ece447714e2c", + "a7fe518c62a02f13cc9e18d4622af75b5fcd4ebad10f129ea0e9eee165cae6b5", + "626773fea3f749684748ec54e167fceb58e3520cbdcf7926717ab7d3b481a2f1", + "de27b0f244aeed874d5b8e03730e7c386905016563058919a476a7efa0414921", + "52903b24bb6d22b7df633c6a426d014cab6c592069043d51ec4b9a910bb98c3e", + "0834ec8e29e9cea5ff2b05cea6f5ee6ce71088569a44fac069c94f777366e806", + "f792f333b3687ca868e80449265720091bcc29a8bd986b5e5d35a297986f9ea2", + "6ea6eca3a29cf603146043e1e5d6bb20e0460dbc72157bedfe585bf38c7e6c56", + "36fe42124e6a5e5250af65cc12e6d7b6370793e559b5fc76b7a46760a83396d1", + "f1c2b5b11af512e743b9c9f219192dc82b8826760b4678683ea16303e2b65508" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55c5", + "cleanJobs": false, + "received": 1743541714701, + "pool": 142, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff1e0350960d163c204f4345414e2e58595a203e0f0f30304f43423400024087ffffffff140000000000000000166a144f4342313fd6fdce13000000290000002c029c30562b26000000000016001472fb714ecb210311655a3da22729ff8626bdea590000000000000000106a0e1a27b10cf0170000000000000000f65ccf020000000017a9142b636a84e5b12177cd49c687b5a1ebd07fb83fbf8783c76e0200000000220020ac11a29a5721e9602e396acd7e5216c9196b7dbb14cf573921c46e6fea38ac09d6632e020000000017a91449b58fb476449dab3a2e175fe2c1f0d753a33e92878b8ad9010000000017a914cc888b5846746a5a7f449eea90082feaa09b9bf387833cbe01000000001600146983f7fe983e96dc5b43677fb14822a075596364b136ae0100000000220020dc406857cac1b9d3f53c6986acb78e7774c604281076d10d3fc8e873e18df6d0663e6e0100000000160014d192719c5be868068f023b9383b3cbecb6afbb1639205300000000001976a91412c00ad1271bd4c91e4f8ff11e738c285181399d88ac49844e000000000017a9141ef31e9bc9ccb532b8c01950727990cca190656a877aa148000000000016001404afd9f6e36ba7cadb62133c458a5318f41a33de731b42000000000016001415c0899a887e36f6620619b621855d40b41cbf838b5a3b0000000000220020e65791d3c340710f7aebf117d1f38e1c5383c1df0345a2082e8ebff945e9994646d83a00000000001600140345e587cb42c201c30e505ea9f6f5fb935876ac5df731000000000017a914278dadd16d1314122e4dde67ff9152a61c3a9f4787254f3000000000001600146d095474bec41ccc8ad40f94c74dd25b79b83579e85b7e020000000017a914413b5901fe4e591c95405fd446b5b002da575bf0870000000000000000266a24aa21a9ed2a3f54b40a557c2884545b770a050154a46b5dc132eec3604f01714f413b32a300000000", + "height": 890448, + "timestamp": 1743541701, + "reward": 315238004, + "scriptsig": "0350960d163c204f4345414e2e58595a203e0f0f30304f43423400024087" + }, + "161": { + "jobId": "1611979", + "extraNonce": "000002dc", + "extraNonce2Size": 8, + "prevHash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "coinbase1": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4f0350960d122f62737175617265642fe5006405fe934bb9fabe6d6d2db144ad7b2eed39c89a2cde9f88f06d9082613911584463126673a700303e881000000000000000", + "coinbase2": "ffffffff05220200000000000017a9141bd79ae4d7811daea94bd25db93761b10fecccd7876271cc120000000017a9146547a37cae8db12fd4c8f191f69d4a52cfa886fa870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f524501307f36ff0aff7000ebd4eea1e8e9bbbfa0e1134cb203d7822f404faca98985eb083e8fd9e16ad64700000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "merkleBranches": [ + "15992cde4826516b7dda391bcec363f8da4f6f335199ec76550b535d667c6bcc", + "4703e3571e20a0fa02766ca895c2043bd1c16364b4163a5d7fe3b669fa47b371", + "1d3822d7192f4c8978ca54d5e3b460ab420907199ba2d751ca682b76b9d8e030", + "55b47a1a7ab09d05ea95e4c36b2ece4f441f1787b6548656a67c93850bad28ca", + "1a231983cfb2837a358b85d7bba94851eeedb3f1af915a67165c49ca36c4ea42", + "1ef6e6152101ed2948ef74ebd6d62959f6b21e5c8c404def4faf88976a999c12", + "d28d9eb88badf7d180885592f79da466d0db6dbfd50b440d1c85a2c9ff1b8f01", + "eddef6dd40e0ed35c83129b484f4848021d781cc05da764df8bac3bc82745d12", + "b62ac40bb764914198729c39526751adc6bb047d3f0990ca742c863028a201da", + "7ec1b0edb5aef0b4a71ebf222d22220eb2c38b9fb07603485c9503b65ff43440", + "b4c149bc9f2f48b6887800706e50c669c86af320759914bcd2a53f7f89b92eda", + "c1cde7654ebfad4609b999082eba464c5a20eb1434e46ccd6d81510a13aa853b" + ], + "version": "20000000", + "bits": "1702796c", + "time": "67ec55e6", + "cleanJobs": false, + "received": 1743541734876, + "pool": 161, + "coinbase": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff4f0350960d122f62737175617265642fe5006405fe934bb9fabe6d6d2db144ad7b2eed39c89a2cde9f88f06d9082613911584463126673a700303e881000000000000000000002dc0000000000000000ffffffff05220200000000000017a9141bd79ae4d7811daea94bd25db93761b10fecccd7876271cc120000000017a9146547a37cae8db12fd4c8f191f69d4a52cfa886fa870000000000000000266a24aa21a9ed93bbaa9811f4ac5527dcd50345b7b15a3d319778bf43ea027337d6fd660826fe00000000000000002f6a2d434f524501307f36ff0aff7000ebd4eea1e8e9bbbfa0e1134cb203d7822f404faca98985eb083e8fd9e16ad64700000000000000002b6a2952534b424c4f434b3a7ad7ddd490a0e384e30c34942e26100ab1e97b25aa8a53c435370b100070fc9300000000", + "height": 890448, + "timestamp": 1743541734, + "reward": 315388804, + "scriptsig": "0350960d122f62737175617265642fe5006405fe934bb9fabe6d6d2db144ad7b2eed39c89a2cde9f88f06d9082613911584463126673a700303e881000000000000000000002dc0000000000000000" + } + }, + "mempoolInfo": { + "loaded": true, + "size": 4173, + "bytes": 3427792, + "usage": 16974288, + "total_fee": 0.05445324, + "maxmempool": 300000000, + "mempoolminfee": 0.00001, + "minrelaytxfee": 0.00001, + "incrementalrelayfee": 0.00001, + "unbroadcastcount": 0, + "fullrbf": true + }, + "vBytesPerSecond": 1415, + "mempool-blocks": [ + { + "blockSize": 1779311, + "blockVSize": 997968.5, + "nTx": 2132, + "totalFees": 2902870, + "medianFee": 2.0479263387949875, + "feeRange": [ + 1.0721153846153846, + 1.9980563654033041, + 2.2195704057279237, + 3.009493670886076, + 3.4955223880597015, + 6.0246913580246915, + 218.1818181818182 + ] + }, + { + "blockSize": 1959636, + "blockVSize": 997903.5, + "nTx": 497, + "totalFees": 1093076, + "medianFee": 1.102049424602265, + "feeRange": [ + 1.0401794819498267, + 1.0548148148148149, + 1.0548148148148149, + 1.0548148148148149, + 1.0548148148148149, + 1.0761096766260911, + 1.1021605957228275 + ] + }, + { + "blockSize": 1477260, + "blockVSize": 997997.25, + "nTx": 720, + "totalFees": 1016195, + "medianFee": 1.007409072434199, + "feeRange": [ + 1, + 1.0019120458891013, + 1.0040863981319323, + 1.0081019768823594, + 1.018450184501845, + 1.0203327171903882, + 1.0485018498190837 + ] + }, + { + "blockSize": 1021308, + "blockVSize": 431071.5, + "nTx": 823, + "totalFees": 432342, + "medianFee": 0, + "feeRange": [ + 1, + 1, + 1, + 1.0028011204481793, + 1.0042075736325387, + 1.0053475935828877, + 1.0068649885583525 + ] + } + ], + "transactions": [ + { + "txid": "bccfbff8d129df7dcfb65b7587186a807ab2c7e4b8494188ac27733705d9e482", + "fee": 564, + "vsize": 140.25, + "value": 160404, + "rate": 4.021390374331551, + "time": 1743541739 + }, + { + "txid": "94c81b3fc40369520fd7607ed54f18416057aab46bc6da328c0588e5967a2ebd", + "fee": 1044, + "vsize": 315.75, + "value": 64814, + "rate": 3.3064133016627077, + "time": 1743541739 + }, + { + "txid": "a16276de4ce8b2eba0013428af73255060ad04e685bee98731504bf973d49bf7", + "fee": 426, + "vsize": 141.25, + "value": 349522, + "rate": 3.015929203539823, + "time": 1743541739 + }, + { + "txid": "bb25deab784cb213dc4cba234f5a41a6c8950c432a05b5858053b39ceda6d1a3", + "fee": 356, + "vsize": 152.25, + "value": 1562308, + "rate": 2.3382594417077174, + "time": 1743541738 + }, + { + "txid": "17a9e5194345d8da287d49c0bf595474e08fc81434646925d6640964929aa7f9", + "fee": 11400, + "vsize": 189.5, + "value": 3352669, + "rate": 60.15831134564644, + "time": 1743541738 + }, + { + "txid": "ced722a2f9007182b8268240af19fee486e8af07da83efccea6e2374aa484d07", + "fee": 534, + "vsize": 177, + "value": 302121849, + "rate": 3.016949152542373, + "time": 1743541736 + } + ], + "loadingIndicators": {}, + "fees": { + "fastestFee": 3, + "halfHourFee": 3, + "hourFee": 3, + "economyFee": 2, + "minimumFee": 1 + }, + "rbfSummary": [ + { + "txid": "51f468b28c4ee790bdfa3152a3add656f29e908828b55fb295cf55dc6a33a855", + "mined": false, + "fullRbf": false, + "oldFee": 19437, + "oldVsize": 166.5, + "newFee": 19770, + "newVsize": 166.5 + }, + { + "txid": "8fb448ef9a26ca3cee8341e3627adf57ae9374c37b83082ad5d90491955cc38c", + "mined": false, + "fullRbf": false, + "oldFee": 19270, + "oldVsize": 166.25, + "newFee": 19603, + "newVsize": 166.5 + }, + { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "mined": false, + "fullRbf": false, + "oldFee": 606, + "oldVsize": 233, + "newFee": 960, + "newVsize": 224 + }, + { + "txid": "a2b7c8a1e8d0f17108c5089198ccbf6c50341c99abb91959a25a41f9bb44fa60", + "mined": false, + "fullRbf": false, + "oldFee": 504, + "oldVsize": 225, + "newFee": 958, + "newVsize": 226 + }, + { + "txid": "798fb7eb84e69c58a596478436f48d3a620a825624ff88d5f15a0b1e17fa8fdf", + "mined": false, + "fullRbf": false, + "oldFee": 1680, + "oldVsize": 381.25, + "newFee": 2240, + "newVsize": 381.5 + }, + { + "txid": "86dd928e524e12fdfbb6d2ba51e1a0e4d8557d05552184bb20c0427c3e0d19f8", + "mined": false, + "fullRbf": false, + "oldFee": 1476, + "oldVsize": 328.5, + "newFee": 1968, + "newVsize": 328.5 + } + ], + "backend": "esplora", + "blocks": [ + { + "id": "0000000000000000000079f5b74b6533abb0b1ece06570d8d157b5bebd1460b4", + "height": 890440, + "version": 559235072, + "timestamp": 1743535677, + "bits": 386038124, + "nonce": 2920325684, + "difficulty": 113757508810854, + "merkle_root": "c793d5fdbfb1ebe99e14a13a6d65370057d311774d33c71da166663b18722474", + "tx_count": 3823, + "size": 1578209, + "weight": 3993461, + "previousblockhash": "000000000000000000020fb2e24425793e17e60e188205dc1694d221790348b2", + "mediantime": 1743532406, + "stale": false, + "extras": { + "reward": 319838750, + "coinbaseRaw": "0348960d082f5669614254432f2cfabe6d6d294719da11c017243828bf32c405341db7f19387fee92c25413c45e114907f9810000000000000001058bf9601429f9fa7a6c160d10d00000000000000", + "orphans": [], + "medianFee": 4, + "feeRange": [ + 3, + 3, + 3.0191082802547773, + 3.980952380952381, + 5, + 10, + 427.748502994012 + ], + "totalFees": 7338750, + "avgFee": 1920, + "avgFeeRate": 7, + "utxoSetChange": 4093, + "avgTxSize": 412.71000000000004, + "totalInputs": 7430, + "totalOutputs": 11523, + "totalOutputAmt": 547553568373, + "segwitTotalTxs": 3432, + "segwitTotalSize": 1467920, + "segwitTotalWeight": 3552413, + "feePercentiles": null, + "virtualSize": 998365.25, + "coinbaseAddress": "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4", + "coinbaseAddresses": [ + "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4" + ], + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 fb37342f6275b13936799def06f2eb4c0f201515 OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "\u0003H–\r\b/ViaBTC/,ú¾mm)G\u0019Ú\u0011À\u0017$8(¿2Ä\u00054\u001d·ñ“‡þé,%Aìg/Foundry USA Pool #dropgold/\u0001S\nEaç\u0002\u0000\u0000\u0000\u0000\u0000", + "header": "00a03220b46014bdbeb557d1d87065e0ecb1b0ab33654bb7f579000000000000000000003ed60f06cec16df4399b5dafa7077036c2eb58cc6a16e6cdca559b9e2f7e4525bb3eec676c790217b7b3c9cb", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 111, + "name": "Foundry USA", + "slug": "foundryusa", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 2792968, + "expectedWeight": 3991959, + "similarity": 0.9951416839808291 + } + }, + { + "id": "000000000000000000014845978a876b3d8bf5d489b9d87e88873952b06ddfce", + "height": 890442, + "version": 557981696, + "timestamp": 1743536834, + "bits": 386038124, + "nonce": 470697326, + "difficulty": 113757508810854, + "merkle_root": "5e92e681c1db2797a5b3e5016729059f8b60a256cafb51d835dac2b3964c0db4", + "tx_count": 3566, + "size": 1628328, + "weight": 3993552, + "previousblockhash": "00000000000000000000ef5a27459785ea4c91e05f64adfad306af6dfc0cd19c", + "mediantime": 1743532867, + "stale": false, + "extras": { + "reward": 318057766, + "coinbaseRaw": "034a960d194d696e656420627920416e74506f6f6c204d000201e15e2989fabe6d6dd599e9dfa40be51f1517c8f512c5c3d51c7656182f1df335d34b98ee02c527db080000000000000000004f92b702000000000000", + "orphans": [], + "medianFee": 3.00860164711668, + "feeRange": [ + 1.5174418604651163, + 2.0140845070422535, + 2.492354740061162, + 3, + 4.020942408376963, + 7, + 200 + ], + "totalFees": 5557766, + "avgFee": 1558, + "avgFeeRate": 5, + "utxoSetChange": 1971, + "avgTxSize": 456.48, + "totalInputs": 7938, + "totalOutputs": 9909, + "totalOutputAmt": 900044492230, + "segwitTotalTxs": 3214, + "segwitTotalSize": 1526463, + "segwitTotalWeight": 3586200, + "feePercentiles": null, + "virtualSize": 998388, + "coinbaseAddress": "37jKPSmbEGwgfacCr2nayn1wTaqMAbA94Z", + "coinbaseAddresses": [ + "37jKPSmbEGwgfacCr2nayn1wTaqMAbA94Z", + "39C7fxSzEACPjM78Z7xdPxhf7mKxJwvfMJ" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 42402a28dd61f2718a4b27ae72a4791d5bbdade7 OP_EQUAL", + "coinbaseSignatureAscii": "\u0003J–\r\u0019Mined by AntPool M\u0000\u0002\u0001á^)‰ú¾mmՙéߤ\u000bå\u001f\u0015\u0017Èõ\u0012ÅÃÕ\u001cvV\u0018/\u001dó5ÓK˜î\u0002Å'Û\b\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000O’·\u0002\u0000\u0000\u0000\u0000\u0000\u0000", + "header": "002042219cd10cfc6daf06d3faad645fe0914cea859745275aef00000000000000000000b40d4c96b3c2da35d851fbca56a2608b9f05296701e5b3a59727dbc181e6925ec242ec676c7902176e450e1c", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 44, + "name": "AntPool", + "slug": "antpool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 5764747, + "expectedWeight": 3991786, + "similarity": 0.9029319155137951 + } + }, + { + "id": "000000000000000000026e08b270834273511b353bed30d54706211adc96f5f6", + "height": 890443, + "version": 706666496, + "timestamp": 1743537197, + "bits": 386038124, + "nonce": 321696065, + "difficulty": 113757508810854, + "merkle_root": "3d7574f7eca741fa94b4690868a242e5b286f8a0417ad0275d4ab05893e96350", + "tx_count": 2155, + "size": 1700002, + "weight": 3993715, + "previousblockhash": "000000000000000000014845978a876b3d8bf5d489b9d87e88873952b06ddfce", + "mediantime": 1743533789, + "stale": false, + "extras": { + "reward": 315112344, + "coinbaseRaw": "034b960d21202020204d696e656420627920536563706f6f6c2020202070000b05e388958c01fabe6d6db7ae4bfa7b1294e16e800b4563f1f5ddeb5c0740319eba45600f3f05d2d7272910000000000000000000c2cb7e020000", + "orphans": [], + "medianFee": 1.4360674424569184, + "feeRange": [ + 1, + 1.0135135135135136, + 1.09717868338558, + 2.142857142857143, + 3.009584664536741, + 4.831858407079646, + 196.07843137254903 + ], + "totalFees": 2612344, + "avgFee": 1212, + "avgFeeRate": 2, + "utxoSetChange": -2880, + "avgTxSize": 788.64, + "totalInputs": 9773, + "totalOutputs": 6893, + "totalOutputAmt": 264603969671, + "segwitTotalTxs": 1933, + "segwitTotalSize": 1556223, + "segwitTotalWeight": 3418707, + "feePercentiles": null, + "virtualSize": 998428.75, + "coinbaseAddress": "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "coinbaseAddresses": [ + "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "3Awm3FNpmwrbvAFVThRUFqgpbVuqWisni9" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 8ee90177614ecde53314fd67c46162f315852a07 OP_EQUAL", + "coinbaseSignatureAscii": "\u0003K–\r! Mined by Secpool p\u0000\u000b\u0005㈕Œ\u0001ú¾mm·®Kú{\u0012”án€\u000bEcñõÝë\\\u0007@1žºE`\u000f?\u0005Ò×')\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000ÂË~\u0002\u0000\u0000", + "header": "00e01e2acedf6db0523987887ed8b989d4f58b3d6b878a974548010000000000000000005063e99358b04a5d27d07a41a0f886b2e542a2680869b494fa41a7ecf774753d2d44ec676c79021741b12c13", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 141, + "name": "SECPOOL", + "slug": "secpool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 2623934, + "expectedWeight": 3991917, + "similarity": 0.9951244468050102 + } + }, + { + "id": "00000000000000000001b36ec470ae4ec39f5d975f665d6f40e9d35bfa65290d", + "height": 890444, + "version": 671080448, + "timestamp": 1743539347, + "bits": 386038124, + "nonce": 994357124, + "difficulty": 113757508810854, + "merkle_root": "c891d4bf68e22916274b667eb3287d50da2ddd63f8dad892da045cc2ad4a7b21", + "tx_count": 3797, + "size": 1500309, + "weight": 3993525, + "previousblockhash": "000000000000000000026e08b270834273511b353bed30d54706211adc96f5f6", + "mediantime": 1743533986, + "stale": false, + "extras": { + "reward": 318708524, + "coinbaseRaw": "034c960d082f5669614254432f2cfabe6d6d45b7fd7ab53a0914da7dcc9d21fe44f0936f5354169a56df9d5139f07afbc2b41000000000000000106fc0eb03f0ac2e851d18d8d9f85ad70000000000", + "orphans": [], + "medianFee": 4.064775540157046, + "feeRange": [ + 3.014354066985646, + 3.18368700265252, + 3.602836879432624, + 4.231825525040388, + 5.581730769230769, + 10, + 697.7151162790698 + ], + "totalFees": 6208524, + "avgFee": 1635, + "avgFeeRate": 6, + "utxoSetChange": 5755, + "avgTxSize": 395.02, + "totalInputs": 6681, + "totalOutputs": 12436, + "totalOutputAmt": 835839828101, + "segwitTotalTxs": 3351, + "segwitTotalSize": 1354446, + "segwitTotalWeight": 3410181, + "feePercentiles": null, + "virtualSize": 998381.25, + "coinbaseAddress": "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4", + "coinbaseAddresses": [ + "1PuJjnF476W3zXfVYmJfGnouzFDAXakkL4" + ], + "coinbaseSignature": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 fb37342f6275b13936799def06f2eb4c0f201515 OP_EQUALVERIFY OP_CHECKSIG", + "coinbaseSignatureAscii": "\u0003L–\r\b/ViaBTC/,ú¾mmE·ýzµ:\t\u0014Ú}̝!þDð“oST\u0016šVߝQ9ðzû´\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0010oÀë\u0003ð¬.…\u001d\u0018ØÙøZ×\u0000\u0000\u0000\u0000\u0000", + "header": "00e0ff27f6f596dc1a210647d530ed3b351b5173428370b2086e02000000000000000000217b4aadc25c04da92d8daf863dd2dda507d28b37e664b271629e268bfd491c8934cec676c79021784af443b", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 73, + "name": "ViaBTC", + "slug": "viabtc", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 6253024, + "expectedWeight": 3991868, + "similarity": 0.9862862477811569 + } + }, + { + "id": "00000000000000000001402065f940b9475159bdc962c92a66d3c6652ffa338a", + "height": 890445, + "version": 601202688, + "timestamp": 1743539574, + "bits": 386038124, + "nonce": 1647397133, + "difficulty": 113757508810854, + "merkle_root": "61d8294afa8f6bafa4d979a77d187dee5f75a6392f957ea647d96eefbbbc5e9b", + "tx_count": 3579, + "size": 1659862, + "weight": 3993406, + "previousblockhash": "00000000000000000001b36ec470ae4ec39f5d975f665d6f40e9d35bfa65290d", + "mediantime": 1743535677, + "stale": false, + "extras": { + "reward": 315617086, + "coinbaseRaw": "034d960d04764dec672f466f756e6472792055534120506f6f6c202364726f70676f6c642f4fac7c451540000000000000", + "orphans": [], + "medianFee": 2.5565329189526835, + "feeRange": [ + 1.521613832853026, + 2, + 2.2411347517730498, + 3, + 3, + 3.954954954954955, + 162.78343949044586 + ], + "totalFees": 3117086, + "avgFee": 871, + "avgFeeRate": 3, + "utxoSetChange": 1881, + "avgTxSize": 463.65000000000003, + "totalInputs": 7893, + "totalOutputs": 9774, + "totalOutputAmt": 324878597485, + "segwitTotalTxs": 3189, + "segwitTotalSize": 1538741, + "segwitTotalWeight": 3509030, + "feePercentiles": null, + "virtualSize": 998351.5, + "coinbaseAddress": "bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63", + "coinbaseAddresses": [ + "bc1pp7w6kxnj7lzgm29pmuhezwl0vjdlcrthqukll5gn9xuqfq5n673smy4m63", + "bc1qwzrryqr3ja8w7hnja2spmkgfdcgvqwp5swz4af4ngsjecfz0w0pqud7k38" + ], + "coinbaseSignature": "OP_PUSHNUM_1 OP_PUSHBYTES_32 0f9dab1a72f7c48da8a1df2f913bef649bfc0d77072dffd11329b8048293d7a3", + "coinbaseSignatureAscii": "\u0003M–\r\u0004vMìg/Foundry USA Pool #dropgold/O¬|E\u0015@\u0000\u0000\u0000\u0000\u0000\u0000", + "header": "00a0d5230d2965fa5bd3e9406f5d665f975d9fc34eae70c46eb3010000000000000000009b5ebcbbef6ed947a67e952f39a6755fee7d187da779d9a4af6b8ffa4a29d861764dec676c7902170d493162", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 111, + "name": "Foundry USA", + "slug": "foundryusa", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 3145370, + "expectedWeight": 3991903, + "similarity": 0.9903353189076812 + } + }, + { + "id": "00000000000000000001fe3d26b02146d0fc2192db06cbc12478d4b347f5306b", + "height": 890446, + "version": 537722880, + "timestamp": 1743541107, + "bits": 386038124, + "nonce": 826569764, + "difficulty": 113757508810854, + "merkle_root": "d9b320d7cb5aace80ca20b934b13b4a272121fbdd59f3aaba690e0326ca2c144", + "tx_count": 3998, + "size": 1541360, + "weight": 3993545, + "previousblockhash": "00000000000000000001402065f940b9475159bdc962c92a66d3c6652ffa338a", + "mediantime": 1743535803, + "stale": false, + "extras": { + "reward": 317976882, + "coinbaseRaw": "034e960d20202020204d696e656420627920536563706f6f6c2020202070001b04fad5fdfefabe6d6d59dd8ebce6e5aab8fb943bbdcede474b6f2d00a395a717970104a6958c17f1ca100000000000000000008089c9350200", + "orphans": [], + "medianFee": 3.3750830641948864, + "feeRange": [ + 2.397163120567376, + 3, + 3, + 3.463647199046484, + 4.49438202247191, + 7.213930348258707, + 476.1904761904762 + ], + "totalFees": 5476882, + "avgFee": 1370, + "avgFeeRate": 5, + "utxoSetChange": 4951, + "avgTxSize": 385.41, + "totalInputs": 7054, + "totalOutputs": 12005, + "totalOutputAmt": 983289729453, + "segwitTotalTxs": 3538, + "segwitTotalSize": 1396505, + "segwitTotalWeight": 3414233, + "feePercentiles": null, + "virtualSize": 998386.25, + "coinbaseAddress": "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "coinbaseAddresses": [ + "3Eif1JfqeMERRsQHtvGEacNN9hhuvnsfe9", + "3Awm3FNpmwrbvAFVThRUFqgpbVuqWisni9" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 8ee90177614ecde53314fd67c46162f315852a07 OP_EQUAL", + "coinbaseSignatureAscii": "\u0003N–\r Mined by Secpool p\u0000\u001b\u0004úÕýþú¾mmYݎ¼æåª¸û”;½ÎÞGKo-\u0000£•§\u0017—\u0001\u0004¦•Œ\u0017ñÊ\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000€‰É5\u0002\u0000", + "header": "00000d208a33fa2f65c6d3662ac962c9bd595147b940f96520400100000000000000000044c1a26c32e090a6ab3a9fd5bd1f1272a2b4134b930ba20ce8ac5acbd720b3d97353ec676c79021724744431", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 141, + "name": "SECPOOL", + "slug": "secpool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 5601814, + "expectedWeight": 3991928, + "similarity": 0.9537877497871488 + } + }, + { + "id": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "height": 890447, + "version": 568860672, + "timestamp": 1743541240, + "bits": 386038124, + "nonce": 4008077709, + "difficulty": 113757508810854, + "merkle_root": "8c3b098e4e50b67075a4fc52bf4cd603aaa450c240c18a865c9ddc0f27104f5f", + "tx_count": 1919, + "size": 1747789, + "weight": 3993172, + "previousblockhash": "00000000000000000001fe3d26b02146d0fc2192db06cbc12478d4b347f5306b", + "mediantime": 1743536834, + "stale": false, + "extras": { + "reward": 314435106, + "coinbaseRaw": "034f960d0f2f736c7573682f65000002fba05ef1fabe6d6df8d29032ea6f9ab1debd223651f30887df779c6195869e70a7b787b3a15f4b1710000000000000000000ee5f0c00b20200000000", + "orphans": [], + "medianFee": 1.4653828213500366, + "feeRange": [ + 1.0845070422535212, + 1.2, + 1.51, + 2.0141129032258065, + 2.3893805309734515, + 4.025477707006369, + 300.0065359477124 + ], + "totalFees": 1935106, + "avgFee": 1008, + "avgFeeRate": 1, + "utxoSetChange": -4244, + "avgTxSize": 910.58, + "totalInputs": 9909, + "totalOutputs": 5665, + "totalOutputAmt": 210763861504, + "segwitTotalTxs": 1720, + "segwitTotalSize": 1629450, + "segwitTotalWeight": 3519924, + "feePercentiles": null, + "virtualSize": 998293, + "coinbaseAddress": "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11", + "coinbaseAddresses": [ + "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 1f0cbbec8bc4c945e4e16249b11eee911eded55f OP_EQUAL", + "coinbaseSignatureAscii": "\u0003O–\r\u000f/slush/e\u0000\u0000\u0002û ^ñú¾mmøÒ2êoš±Þ½\"6Qó\b‡ßwœa•†žp§·‡³¡_K\u0017\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000î_\f\u0000²\u0002\u0000\u0000\u0000\u0000", + "header": "0020e8216b30f547b3d47824c1cb06db9221fcd04621b0263dfe010000000000000000005f4f10270fdc9d5c868ac140c250a4aa03d64cbf52fca47570b6504e8e093b8cf853ec676c7902178d69e6ee", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 43, + "name": "Braiins Pool", + "slug": "braiinspool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 2059571, + "expectedWeight": 3991720, + "similarity": 0.9149852183486826 + } + } + ], + "conversions": { + "time": 1743541504, + "USD": 85181, + "EUR": 78939, + "GBP": 65914, + "CAD": 121813, + "CHF": 75399, + "AUD": 135492, + "JPY": 12753000 + }, + "backendInfo": { + "hostname": "node205.tk7.mempool.space", + "version": "3.1.0-dev", + "gitCommit": "3c08b5c72", + "lightning": false, + "backend": "esplora" + }, + "da": { + "progressPercent": 68.99801587301587, + "difficultyChange": 2.7058667283164084, + "estimatedRetargetDate": 1743907120500, + "remainingBlocks": 625, + "remainingTime": 365382500, + "previousRetarget": 1.433804484570416, + "previousTime": 1742728542, + "nextRetargetHeight": 891072, + "timeAvg": 584612, + "adjustedTimeAvg": 584612, + "timeOffset": 0, + "expectedBlocks": 1355.3266666666666 + } +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx01_ws_tx_replaced.json b/frontend/cypress/fixtures/details_rbf/tx01_ws_tx_replaced.json new file mode 100644 index 000000000..7af723546 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx01_ws_tx_replaced.json @@ -0,0 +1,5 @@ +{ + "txReplaced": { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698" + } +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_api_cpfp.json b/frontend/cypress/fixtures/details_rbf/tx02_api_cpfp.json new file mode 100644 index 000000000..478eecfbc --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_api_cpfp.json @@ -0,0 +1,9 @@ +{ + "ancestors": [], + "bestDescendant": null, + "descendants": [], + "effectiveFeePerVsize": 4.285714285714286, + "sigops": 4, + "fee": 960, + "adjustedVsize": 224 +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_api_rbf.json b/frontend/cypress/fixtures/details_rbf/tx02_api_rbf.json new file mode 100644 index 000000000..96bd1efdd --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_api_rbf.json @@ -0,0 +1,36 @@ +{ + "replacements": { + "tx": { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "fee": 960, + "vsize": 224, + "value": 49040, + "rate": 4.285714285714286, + "time": 1743541726, + "rbf": true, + "fullRbf": false + }, + "time": 1743541726, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29", + "fee": 606, + "vsize": 233, + "value": 49394, + "rate": 2.6008583690987126, + "time": 1743541407, + "rbf": true + }, + "time": 1743541407, + "interval": 319, + "fullRbf": false, + "replaces": [] + } + ] + }, + "replaces": [ + "242f3fff9ca7d5aea7a7a57d886f3fa7329e24fac948598a991b3a3dd631cd29" + ] +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_api_tx.json b/frontend/cypress/fixtures/details_rbf/tx02_api_tx.json new file mode 100644 index 000000000..cbcb4fe8c --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_api_tx.json @@ -0,0 +1,38 @@ +{ + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "version": 2, + "locktime": 0, + "vin": [ + { + "txid": "fb16c141d22a3a7af5e44e7478a8663866c35d554cd85107ec8a99b97e5a72e9", + "vout": 0, + "prevout": { + "scriptpubkey": "76a914099f831c49289c7b9cc07a9a632867ecd51a105a88ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 099f831c49289c7b9cc07a9a632867ecd51a105a OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "1stAprEZapjCYGUACUcXohQqSHF9MU5Kj", + "value": 50000 + }, + "scriptsig": "483045022100ab59cf33b33eab1f76286a0fa6962c12171f2133931fec3616f96883551e6c9d02205cacbae788359f2a32ff629e732be0d95843440a3d1c222a63095f83fb69f438014104ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df", + "scriptsig_asm": "OP_PUSHBYTES_72 3045022100ab59cf33b33eab1f76286a0fa6962c12171f2133931fec3616f96883551e6c9d02205cacbae788359f2a32ff629e732be0d95843440a3d1c222a63095f83fb69f43801 OP_PUSHBYTES_65 04ea83ffe426ee6b6827029c72fbdcb0a1602829c1fe384637a7d178aa62a6a1e3d7f29250e001ee708a93ca9771f50ee638aaaaef8941c8a7e2bad5494b23e0df", + "is_coinbase": false, + "sequence": 4294967293 + } + ], + "vout": [ + { + "scriptpubkey": "76a914099f831c49289c7b9cc07a9a632867ecd51a105a88ac", + "scriptpubkey_asm": "OP_DUP OP_HASH160 OP_PUSHBYTES_20 099f831c49289c7b9cc07a9a632867ecd51a105a OP_EQUALVERIFY OP_CHECKSIG", + "scriptpubkey_type": "p2pkh", + "scriptpubkey_address": "1stAprEZapjCYGUACUcXohQqSHF9MU5Kj", + "value": 49040 + } + ], + "size": 224, + "weight": 896, + "sigops": 4, + "fee": 960, + "status": { + "confirmed": false + } +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_api_tx_times.json b/frontend/cypress/fixtures/details_rbf/tx02_api_tx_times.json new file mode 100644 index 000000000..365c3885e --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_api_tx_times.json @@ -0,0 +1,3 @@ +[ + 1743541726 +] \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_ws_block.json b/frontend/cypress/fixtures/details_rbf/tx02_ws_block.json new file mode 100644 index 000000000..9fc358795 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_ws_block.json @@ -0,0 +1,116 @@ +{ + "block": { + "id": "000000000000000000019bfe551da67e0d93dda4b355d16f1fdb4031ba7e5d61", + "height": 890448, + "version": 626941952, + "timestamp": 1743541850, + "bits": 386038124, + "nonce": 1177284424, + "difficulty": 113757508810854, + "merkle_root": "563d862ef4ec95ea97735ca5561e347ca6e216cb56bd451e8bedb1014078d6c3", + "tx_count": 2229, + "size": 1763153, + "weight": 3993275, + "previousblockhash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "mediantime": 1743537197, + "stale": false, + "extras": { + "reward": 315498786, + "coinbaseRaw": "0350960d0f2f736c7573682fe500c702ff43d142fabe6d6def42d9effd800b9839c832dcfdc6899e137547f15c8a598dbe3a1363f999a0a310000000000000000000e9a2050064dc00000000", + "orphans": [], + "medianFee": 2.144206217858874, + "feeRange": [ + 1.0845921450151057, + 2, + 2.2448979591836733, + 3, + 3.5985915492957745, + 6, + 217.0212765957447 + ], + "totalFees": 2998786, + "avgFee": 1345, + "avgFeeRate": 3, + "utxoSetChange": -3073, + "avgTxSize": 790.84, + "totalInputs": 9558, + "totalOutputs": 6485, + "totalOutputAmt": 442206797883, + "segwitTotalTxs": 1986, + "segwitTotalSize": 1676431, + "segwitTotalWeight": 3646495, + "feePercentiles": null, + "virtualSize": 998318.75, + "coinbaseAddress": "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11", + "coinbaseAddresses": [ + "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 1f0cbbec8bc4c945e4e16249b11eee911eded55f OP_EQUAL", + "coinbaseSignatureAscii": "\u0003P–\r\u000f/slush/å\u0000Ç\u0002ÿCÑBú¾mmïBÙïý€\u000b˜9È2ÜýƉž\u0013uGñ\\ŠY¾:\u0013cù™ £\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000é¢\u0005\u0000dÜ\u0000\u0000\u0000\u0000", + "header": "00605e25abe2e310c10e724cfb641859658fee6570ec6062cc0400000000000000000000c3d6784001b1ed8b1e45bd56cb16e2a67c341e56a55c7397ea95ecf42e863d565a56ec676c79021748ef2b46", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 43, + "name": "Braiins Pool", + "slug": "braiinspool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 3287140, + "expectedWeight": 3991809, + "similarity": 0.9079894021278392 + } + }, + "mempool-blocks": [ + { + "blockSize": 1979907, + "blockVSize": 997974.75, + "nTx": 461, + "totalFees": 1429178, + "medianFee": 1.1020797417745662, + "feeRange": [ + 1.0666666666666667, + 1.0746847720659554, + 1.102059530141031, + 3, + 3.4233409610983982, + 5.017605633802817, + 148.4084084084084 + ] + }, + { + "blockSize": 1363691, + "blockVSize": 997986.5, + "nTx": 1080, + "totalFees": 1023741, + "medianFee": 1.014827018121911, + "feeRange": [ + 1, + 1.0036011703803736, + 1.0054683365672958, + 1.0186757215619695, + 1.0548148148148149, + 1.0548148148148149, + 1.068146618482189 + ] + }, + { + "blockSize": 1337253, + "blockVSize": 563516.25, + "nTx": 901, + "totalFees": 564834, + "medianFee": 1.0028011204481793, + "feeRange": [ + 1, + 1, + 1, + 1.0025062656641603, + 1.004231311706629, + 1.0053475935828877, + 1.0068649885583525 + ] + } + ], + "txConfirmed": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698" +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_ws_block_confirmation.json b/frontend/cypress/fixtures/details_rbf/tx02_ws_block_confirmation.json new file mode 100644 index 000000000..9fc358795 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_ws_block_confirmation.json @@ -0,0 +1,116 @@ +{ + "block": { + "id": "000000000000000000019bfe551da67e0d93dda4b355d16f1fdb4031ba7e5d61", + "height": 890448, + "version": 626941952, + "timestamp": 1743541850, + "bits": 386038124, + "nonce": 1177284424, + "difficulty": 113757508810854, + "merkle_root": "563d862ef4ec95ea97735ca5561e347ca6e216cb56bd451e8bedb1014078d6c3", + "tx_count": 2229, + "size": 1763153, + "weight": 3993275, + "previousblockhash": "0000000000000000000004cc6260ec7065ee8f65591864fb4c720ec110e3e2ab", + "mediantime": 1743537197, + "stale": false, + "extras": { + "reward": 315498786, + "coinbaseRaw": "0350960d0f2f736c7573682fe500c702ff43d142fabe6d6def42d9effd800b9839c832dcfdc6899e137547f15c8a598dbe3a1363f999a0a310000000000000000000e9a2050064dc00000000", + "orphans": [], + "medianFee": 2.144206217858874, + "feeRange": [ + 1.0845921450151057, + 2, + 2.2448979591836733, + 3, + 3.5985915492957745, + 6, + 217.0212765957447 + ], + "totalFees": 2998786, + "avgFee": 1345, + "avgFeeRate": 3, + "utxoSetChange": -3073, + "avgTxSize": 790.84, + "totalInputs": 9558, + "totalOutputs": 6485, + "totalOutputAmt": 442206797883, + "segwitTotalTxs": 1986, + "segwitTotalSize": 1676431, + "segwitTotalWeight": 3646495, + "feePercentiles": null, + "virtualSize": 998318.75, + "coinbaseAddress": "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11", + "coinbaseAddresses": [ + "34XC8GbijKCCvppNvhw4Ra8QZdWsg8tC11" + ], + "coinbaseSignature": "OP_HASH160 OP_PUSHBYTES_20 1f0cbbec8bc4c945e4e16249b11eee911eded55f OP_EQUAL", + "coinbaseSignatureAscii": "\u0003P–\r\u000f/slush/å\u0000Ç\u0002ÿCÑBú¾mmïBÙïý€\u000b˜9È2ÜýƉž\u0013uGñ\\ŠY¾:\u0013cù™ £\u0010\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000é¢\u0005\u0000dÜ\u0000\u0000\u0000\u0000", + "header": "00605e25abe2e310c10e724cfb641859658fee6570ec6062cc0400000000000000000000c3d6784001b1ed8b1e45bd56cb16e2a67c341e56a55c7397ea95ecf42e863d565a56ec676c79021748ef2b46", + "utxoSetSize": null, + "totalInputAmt": null, + "pool": { + "id": 43, + "name": "Braiins Pool", + "slug": "braiinspool", + "minerNames": null + }, + "matchRate": 100, + "expectedFees": 3287140, + "expectedWeight": 3991809, + "similarity": 0.9079894021278392 + } + }, + "mempool-blocks": [ + { + "blockSize": 1979907, + "blockVSize": 997974.75, + "nTx": 461, + "totalFees": 1429178, + "medianFee": 1.1020797417745662, + "feeRange": [ + 1.0666666666666667, + 1.0746847720659554, + 1.102059530141031, + 3, + 3.4233409610983982, + 5.017605633802817, + 148.4084084084084 + ] + }, + { + "blockSize": 1363691, + "blockVSize": 997986.5, + "nTx": 1080, + "totalFees": 1023741, + "medianFee": 1.014827018121911, + "feeRange": [ + 1, + 1.0036011703803736, + 1.0054683365672958, + 1.0186757215619695, + 1.0548148148148149, + 1.0548148148148149, + 1.068146618482189 + ] + }, + { + "blockSize": 1337253, + "blockVSize": 563516.25, + "nTx": 901, + "totalFees": 564834, + "medianFee": 1.0028011204481793, + "feeRange": [ + 1, + 1, + 1, + 1.0025062656641603, + 1.004231311706629, + 1.0053475935828877, + 1.0068649885583525 + ] + } + ], + "txConfirmed": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698" +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_ws_mempool_blocks_01.json b/frontend/cypress/fixtures/details_rbf/tx02_ws_mempool_blocks_01.json new file mode 100644 index 000000000..4c83cf797 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_ws_mempool_blocks_01.json @@ -0,0 +1,75 @@ +{ + "mempool-blocks": [ + { + "blockSize": 1779823, + "blockVSize": 997995.25, + "nTx": 2133, + "totalFees": 2922926, + "medianFee": 2.0479263387949875, + "feeRange": [ + 1.0825892857142858, + 2, + 2.2439024390243905, + 3.010452961672474, + 3.554973821989529, + 6.032085561497326, + 218.1818181818182 + ] + }, + { + "blockSize": 1957833, + "blockVSize": 997953, + "nTx": 500, + "totalFees": 1093270, + "medianFee": 1.102049424602265, + "feeRange": [ + 1.0548148148148149, + 1.0548148148148149, + 1.0548148148148149, + 1.0548148148148149, + 1.067677314564158, + 1.0766488413547237, + 1.1021605957228275 + ] + }, + { + "blockSize": 1477864, + "blockVSize": 997999, + "nTx": 730, + "totalFees": 1016458, + "medianFee": 1.0075971559364956, + "feeRange": [ + 1, + 1.0019552465783186, + 1.004255319148936, + 1.0081019768823594, + 1.018450184501845, + 1.0203327171903882, + 1.0548148148148149 + ] + }, + { + "blockSize": 1030954, + "blockVSize": 436613.5, + "nTx": 838, + "totalFees": 437891, + "medianFee": 0, + "feeRange": [ + 1, + 1, + 1, + 1.0026525198938991, + 1.004231311706629, + 1.0053475935828877, + 1.0068649885583525 + ] + } + ], + "txPosition": { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "position": { + "block": 0, + "vsize": 111102 + } + } +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_ws_next_block.json b/frontend/cypress/fixtures/details_rbf/tx02_ws_next_block.json new file mode 100644 index 000000000..4245d4611 --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_ws_next_block.json @@ -0,0 +1,75 @@ +{ + "mempool-blocks": [ + { + "blockSize": 1719945, + "blockVSize": 997952.25, + "nTx": 2558, + "totalFees": 3287140, + "medianFee": 2.4046448299072485, + "feeRange": [ + 1.073446327683616, + 2, + 2.2567567567567566, + 3.0106761565836297, + 3.6169014084507043, + 6.015037593984962, + 218.1818181818182 + ] + }, + { + "blockSize": 2022898, + "blockVSize": 997983.25, + "nTx": 131, + "totalFees": 1098129, + "medianFee": 1.1020797417745662, + "feeRange": [ + 1.0625, + 1.0691217722793642, + 1.073436083408885, + 1.0761096766260911, + 1.080091533180778, + 1.102110739151618, + 1.1021909190121146 + ] + }, + { + "blockSize": 1363844, + "blockVSize": 997998.5, + "nTx": 1073, + "totalFees": 1023651, + "medianFee": 1.014827018121911, + "feeRange": [ + 1, + 1.003584229390681, + 1.0054683365672958, + 1.0186757215619695, + 1.0548148148148149, + 1.0548148148148149, + 1.068146618482189 + ] + }, + { + "blockSize": 1335390, + "blockVSize": 562453.5, + "nTx": 902, + "totalFees": 563772, + "medianFee": 1.0028011204481793, + "feeRange": [ + 1, + 1, + 1, + 1.0025402201524132, + 1.004231311706629, + 1.0053475935828877, + 1.0068649885583525 + ] + } + ], + "txPosition": { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "position": { + "block": 0, + "vsize": 128920 + } + } +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/details_rbf/tx02_ws_tx_position.json b/frontend/cypress/fixtures/details_rbf/tx02_ws_tx_position.json new file mode 100644 index 000000000..c4a2db35a --- /dev/null +++ b/frontend/cypress/fixtures/details_rbf/tx02_ws_tx_position.json @@ -0,0 +1,9 @@ +{ + "txPosition": { + "txid": "b6a53d8a8025c0c5b194345925a99741b645cae0b1f180f0613273029f2bf698", + "position": { + "block": 0, + "vsize": 110880 + } + } +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/rbf_page/rbf_01.json b/frontend/cypress/fixtures/rbf_page/rbf_01.json new file mode 100644 index 000000000..2c79f142a --- /dev/null +++ b/frontend/cypress/fixtures/rbf_page/rbf_01.json @@ -0,0 +1,37 @@ +{ + "rbfLatest": [ + { + "tx": { + "txid": "f4bae4f626036250fd00d68490e572f65f66417452003a0f4c4d76f17a9fde68", + "fee": 1185, + "vsize": 223, + "value": 41729, + "rate": 5.313901345291479, + "time": 1743587177, + "rbf": true, + "fullRbf": false, + "mined": true + }, + "time": 1743587177, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "12945412dfc455e0ed6049dc2ee8737756c8d9e2d9a2eb26f366cd5019a0369f", + "fee": 504, + "vsize": 222, + "value": 42410, + "rate": 2.27027027027027, + "time": 1743586081, + "rbf": true + }, + "time": 1743586081, + "interval": 1096, + "fullRbf": false, + "replaces": [] + } + ], + "mined": true + } + ] +} \ No newline at end of file diff --git a/frontend/cypress/fixtures/rbf_page/rbf_02.json b/frontend/cypress/fixtures/rbf_page/rbf_02.json new file mode 100644 index 000000000..3b5011002 --- /dev/null +++ b/frontend/cypress/fixtures/rbf_page/rbf_02.json @@ -0,0 +1,68 @@ +{ + "rbfLatest": [ + { + "tx": { + "txid": "d313b479acfbae719afb488a078e0fe0e052a67b9f65f73f7c75d3d95fd36acc", + "fee": 672, + "vsize": 167.25, + "value": 29996328, + "rate": 4.017937219730942, + "time": 1743587365, + "rbf": true, + "fullRbf": false + }, + "time": 1743587365, + "fullRbf": false, + "replaces": [ + { + "tx": { + "txid": "eb5aa786cabda307cc9642cfb9c41a3b405ac20a391eefbe54be7930bea61865", + "fee": 336, + "vsize": 167.5, + "value": 29996664, + "rate": 2.005970149253731, + "time": 1743586424, + "rbf": true + }, + "time": 1743586424, + "interval": 941, + "fullRbf": false, + "replaces": [] + } + ] + }, + { + "tx": { + "txid": "f4bae4f626036250fd00d68490e572f65f66417452003a0f4c4d76f17a9fde68", + "fee": 1185, + "vsize": 223, + "value": 41729, + "rate": 5.313901345291479, + "time": 1743587177, + "rbf": true, + "fullRbf": false, + "mined": true + }, + "time": 1743587177, + "fullRbf": true, + "replaces": [ + { + "tx": { + "txid": "12945412dfc455e0ed6049dc2ee8737756c8d9e2d9a2eb26f366cd5019a0369f", + "fee": 504, + "vsize": 222, + "value": 42410, + "rate": 2.27027027027027, + "time": 1743586081, + "rbf": true + }, + "time": 1743586081, + "interval": 1096, + "fullRbf": false, + "replaces": [] + } + ], + "mined": true + } + ] +} \ No newline at end of file diff --git a/frontend/cypress/support/commands.ts b/frontend/cypress/support/commands.ts index 018f63569..2ce198241 100644 --- a/frontend/cypress/support/commands.ts +++ b/frontend/cypress/support/commands.ts @@ -44,6 +44,7 @@ import { PageIdleDetector } from './PageIdleDetector'; import { mockWebSocket } from './websocket'; +import { mockWebSocketV2 } from './websocket'; /* global Cypress */ const codes = { @@ -72,6 +73,10 @@ Cypress.Commands.add('mockMempoolSocket', () => { mockWebSocket(); }); +Cypress.Commands.add('mockMempoolSocketV2', () => { + mockWebSocketV2(); +}); + Cypress.Commands.add('changeNetwork', (network: "testnet" | "testnet4" | "signet" | "liquid" | "mainnet") => { cy.get('.dropdown-toggle').click().then(() => { cy.get(`a.${network}`).click().then(() => { diff --git a/frontend/cypress/support/index.d.ts b/frontend/cypress/support/index.d.ts index 2c5328301..21ffe6a2d 100644 --- a/frontend/cypress/support/index.d.ts +++ b/frontend/cypress/support/index.d.ts @@ -5,6 +5,7 @@ declare namespace Cypress { waitForSkeletonGone(): Chainable waitForPageIdle(): Chainable mockMempoolSocket(): Chainable + mockMempoolSocketV2(): Chainable changeNetwork(network: "testnet"|"testnet4"|"signet"|"liquid"|"mainnet"): Chainable } } \ No newline at end of file diff --git a/frontend/cypress/support/websocket.ts b/frontend/cypress/support/websocket.ts index 1356ccc76..b067cc6e8 100644 --- a/frontend/cypress/support/websocket.ts +++ b/frontend/cypress/support/websocket.ts @@ -27,6 +27,37 @@ const createMock = (url: string) => { return mocks[url]; }; +export const mockWebSocketV2 = () => { + cy.on('window:before:load', (win) => { + const winWebSocket = win.WebSocket; + cy.stub(win, 'WebSocket').callsFake((url) => { + console.log(url); + if ((new URL(url).pathname.indexOf('/sockjs-node/') !== 0)) { + const { server, websocket } = createMock(url); + + win.mockServer = server; + win.mockServer.on('connection', (socket) => { + win.mockSocket = socket; + }); + + win.mockServer.on('message', (message) => { + console.log(message); + }); + + return websocket; + } else { + return new winWebSocket(url); + } + }); + }); + + cy.on('window:before:unload', () => { + for (const url in mocks) { + cleanupMock(url); + } + }); +}; + export const mockWebSocket = () => { cy.on('window:before:load', (win) => { const winWebSocket = win.WebSocket; @@ -65,6 +96,27 @@ export const mockWebSocket = () => { }); }; +export const receiveWebSocketMessageFromServer = ({ + params +}: { params?: any } = {}) => { + cy.window().then((win) => { + if (params.message) { + console.log('sending message'); + win.mockSocket.send(params.message.contents); + } + + if (params.file) { + cy.readFile(`cypress/fixtures/${params.file.path}`, 'utf-8').then((fixture) => { + console.log('sending payload'); + win.mockSocket.send(JSON.stringify(fixture)); + }); + + } + }); + return; +}; + + export const emitMempoolInfo = ({ params }: { params?: any } = {}) => { @@ -82,16 +134,22 @@ export const emitMempoolInfo = ({ switch (params.command) { case "init": { win.mockSocket.send('{"conversions":{"USD":32365.338815782445}}'); - cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'ascii').then((fixture) => { + cy.readFile('cypress/fixtures/mainnet_live2hchart.json', 'utf-8').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); }); - cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'ascii').then((fixture) => { + cy.readFile('cypress/fixtures/mainnet_mempoolInfo.json', 'utf-8').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); }); break; } case "rbfTransaction": { - cy.readFile('cypress/fixtures/mainnet_rbf.json', 'ascii').then((fixture) => { + cy.readFile('cypress/fixtures/mainnet_rbf.json', 'utf-8').then((fixture) => { + win.mockSocket.send(JSON.stringify(fixture)); + }); + break; + } + case 'trackTx': { + cy.readFile('cypress/fixtures/track_tx.json', 'utf-8').then((fixture) => { win.mockSocket.send(JSON.stringify(fixture)); }); break; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 932979e2b..4a3f7a4a2 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "mempool-frontend", - "version": "3.1.0-dev", + "version": "3.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mempool-frontend", - "version": "3.1.0-dev", + "version": "3.2.0", "license": "GNU Affero General Public License v3.0", "dependencies": { "@angular-devkit/build-angular": "^17.3.1", diff --git a/frontend/package.json b/frontend/package.json index b50085a54..3651f7a43 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "mempool-frontend", - "version": "3.1.0-dev", + "version": "3.2.0", "description": "Bitcoin mempool visualizer and blockchain explorer backend", "license": "GNU Affero General Public License v3.0", "homepage": "https://mempool.space", @@ -25,14 +25,12 @@ "i18n-extract-from-source": "npm run ng -- extract-i18n --out-file ./src/locale/messages.xlf", "i18n-pull-from-transifex": "tx pull -a --parallel --minimum-perc 1 --force", "serve": "npm run generate-config && npm run ng -- serve -c local", - "serve:stg": "npm run generate-config && npm run ng -- serve -c staging", "serve:local-prod": "npm run generate-config && npm run ng -- serve -c local-prod", - "serve:local-staging": "npm run generate-config && npm run ng -- serve -c local-staging", + "serve:parameterized": "npm run generate-config && npm run ng -- serve -c parameterized", "start": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local", + "start:parameterized": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c parameterized", "start:local-esplora": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-esplora", - "start:stg": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c staging", "start:local-prod": "npm run generate-config && npm run sync-assets-dev && npm run ng -- serve -c local-prod", - "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-dev && 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/'", @@ -58,8 +56,8 @@ "cypress:run:record": "cypress run --record", "cypress:open:ci": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:open", "cypress:run:ci": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-prod 4200 cypress:run:record", - "cypress:open:ci:staging": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:open", - "cypress:run:ci:staging": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:local-staging 4200 cypress:run:record" + "cypress:open:ci:parameterized": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:parameterized 4200 cypress:open", + "cypress:run:ci:parameterized": "node update-config.js TESTNET_ENABLED=true TESTNET4_ENABLED=true SIGNET_ENABLED=true LIQUID_ENABLED=true ITEMS_PER_PAGE=25 && npm run generate-config && start-server-and-test serve:parameterized 4200 cypress:run:record" }, "dependencies": { "@angular-devkit/build-angular": "^17.3.1", diff --git a/frontend/proxy.conf.parameterized.js b/frontend/proxy.conf.parameterized.js new file mode 100644 index 000000000..eee9bf1f7 --- /dev/null +++ b/frontend/proxy.conf.parameterized.js @@ -0,0 +1,36 @@ +const fs = require('fs'); + +const PROXY_CONFIG = require('./proxy.conf'); + +const addApiKeyHeader = (proxyReq, req, res) => { + if (process.env.MEMPOOL_CI_API_KEY) { + proxyReq.setHeader('X-Mempool-Auth', process.env.MEMPOOL_CI_API_KEY); + } +}; + +PROXY_CONFIG.forEach((entry) => { + const mempoolHostname = process.env.MEMPOOL_HOSTNAME + ? process.env.MEMPOOL_HOSTNAME + : 'mempool.space'; + + const liquidHostname = process.env.LIQUID_HOSTNAME + ? process.env.LIQUID_HOSTNAME + : 'liquid.network'; + + entry.target = entry.target.replace('mempool.space', mempoolHostname); + entry.target = entry.target.replace('liquid.network', liquidHostname); + + if (entry.onProxyReq) { + const originalProxyReq = entry.onProxyReq; + entry.onProxyReq = (proxyReq, req, res) => { + originalProxyReq(proxyReq, req, res); + if (process.env.MEMPOOL_CI_API_KEY) { + proxyReq.setHeader('X-Mempool-Auth', process.env.MEMPOOL_CI_API_KEY); + } + }; + } else { + entry.onProxyReq = addApiKeyHeader; + } +}); + +module.exports = PROXY_CONFIG; diff --git a/frontend/proxy.conf.staging.js b/frontend/proxy.conf.staging.js deleted file mode 100644 index 260b222c0..000000000 --- a/frontend/proxy.conf.staging.js +++ /dev/null @@ -1,12 +0,0 @@ -const fs = require('fs'); - -let PROXY_CONFIG = require('./proxy.conf'); - -PROXY_CONFIG.forEach(entry => { - const hostname = process.env.CYPRESS_REROUTE_TESTNET === 'true' ? 'mempool-staging.fra.mempool.space' : 'node201.fmt.mempool.space'; - console.log(`e2e tests running against ${hostname}`); - entry.target = entry.target.replace("mempool.space", hostname); - entry.target = entry.target.replace("liquid.network", "liquid-staging.fmt.mempool.space"); -}); - -module.exports = PROXY_CONFIG; diff --git a/frontend/src/app/components/about/about.component.html b/frontend/src/app/components/about/about.component.html index 817b81e53..552f8f6bb 100644 --- a/frontend/src/app/components/about/about.component.html +++ b/frontend/src/app/components/about/about.component.html @@ -11,6 +11,7 @@
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.

+
Be your own explorer™