From 2087ba4d5584f4a320468869d1a536fcb3678400 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 11 Aug 2026 11:05:59 -0400 Subject: [PATCH 01/14] chore: include `ribasushi-rpc-checks` to test forest against external dataset --- .../ribasushi-rpc-checks/docker-compose.yaml | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 scripts/tests/ribasushi-rpc-checks/docker-compose.yaml diff --git a/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml b/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml new file mode 100644 index 00000000000..dd690e2eb3b --- /dev/null +++ b/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml @@ -0,0 +1,73 @@ +name: rpc-checks + +# docker compose up --build --abort-on-container-exit --exit-code-from rpc-checks + +x-env: &env + CHAIN: ${CHAIN:-calibnet} + DAYS_AGO: ${DAYS_AGO:-2} + EPOCHS: ${EPOCHS:-1000} + +services: + snapshot: + image: alpine:3.24 + environment: *env + volumes: + - ./snapshots:/snapshots + entrypoint: ["/bin/sh", "-euc"] + command: + - | + apk add --no-cache curl jq >/dev/null + + day=$$(date -u -d "@$$(( $$(date -u +%s) - DAYS_AGO * 86400 ))" +%F) + url=$$(curl -sSf "https://forest-archive.chainsafe.dev/list/$$CHAIN/latest-v2?format=json" | + jq -r --arg d "$$day" '[.items[].url | select(contains("_"+$$d+"_"))] | first') + [ "$$url" != null ] || { echo "no $$CHAIN snapshot for $$day"; exit 1; } + + file=$$(basename "$$url") + head=$$(echo "$$file" | sed 's/.*_height_\([0-9]*\)\..*/\1/') + + # only downloads if the snapshot is not present + [ -f "/snapshots/$$file" ] || curl -sSfL -o "/snapshots/$$file" "$$url" + + # define env vars used later on the next services + printf 'SNAPSHOT=%s\nSTART=%s\nEND=%s\nBACKFILL=%s\n' \ + "$$file" "$$((head - EPOCHS))" "$$((head - 1))" "$$((EPOCHS + 1))" > /snapshots/env + cat /snapshots/env + + # The RPC port opens only after the backfill finishes, so "healthy" means ready. + forest: + image: ghcr.io/chainsafe/forest:latest + depends_on: + snapshot: + condition: service_completed_successfully + environment: *env + volumes: + - ./snapshots:/snapshots:ro + ports: + - "2345:2345" + entrypoint: ["/bin/sh", "-euc"] + command: + - | + . /snapshots/env + exec forest-tool api serve "/snapshots/$$SNAPSHOT" --chain "$$CHAIN" \ + --port 2345 --height 0 --index-backfill-epochs "$$BACKFILL" + healthcheck: + test: ["CMD", "forest-cli", "chain", "head"] + interval: 30s + timeout: 10s + retries: 10 + + rpc-checks: + image: ghcr.io/chainsafe/forest-rpc-checks:latest + depends_on: + forest: + condition: service_healthy + environment: + FOREST_RPC_URL: forest:2345/rpc/v1 + volumes: + - ./snapshots:/snapshots:ro + entrypoint: ["/bin/sh", "-euc"] + command: + - | + . /snapshots/env + exec bundle exec ruby check_rpc.rb "$$START" "$$END" From 39db12bd31c62f3bc231f240c5c9cf9ac5aa1746 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 11 Aug 2026 11:14:33 -0400 Subject: [PATCH 02/14] chore: update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index bc99b6da65d..ac494be6a01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,8 @@ - [#7408](https://github.com/ChainSafe/forest/pull/7408): Impl `Forest.IndexBackfill`, `Forest.IndexBackfillStatus` and `Forest.IndexBackfillCancel` RPC methods and the `forest-cli index backfill` command to back-fill the chain index (Ethereum mappings, events, block blooms) through the running daemon, so the node no longer needs to be stopped. An interrupted or cancelled run can be resumed with `--resume`. +- [#7477](https://github.com/ChainSafe/forest/pull/7477) Include `scripts/tests/ribasushi-rpc-checks` that tests forest against external ribasushi dataset + ### Changed ### Removed From 170db7465c6121d57fb08c220949f7ff98ab432e Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 11 Aug 2026 14:45:13 -0400 Subject: [PATCH 03/14] chore: introduce validation and timeouts --- CHANGELOG.md | 2 +- .../ribasushi-rpc-checks/docker-compose.yaml | 23 +++++++++++++------ 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ac494be6a01..2bd0b2c9514 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ - [#7408](https://github.com/ChainSafe/forest/pull/7408): Impl `Forest.IndexBackfill`, `Forest.IndexBackfillStatus` and `Forest.IndexBackfillCancel` RPC methods and the `forest-cli index backfill` command to back-fill the chain index (Ethereum mappings, events, block blooms) through the running daemon, so the node no longer needs to be stopped. An interrupted or cancelled run can be resumed with `--resume`. -- [#7477](https://github.com/ChainSafe/forest/pull/7477) Include `scripts/tests/ribasushi-rpc-checks` that tests forest against external ribasushi dataset +- [#7270](https://github.com/ChainSafe/forest/issues/7270): Include `scripts/tests/ribasushi-rpc-checks` that tests Forest against the external Ribasushi dataset ### Changed diff --git a/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml b/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml index dd690e2eb3b..b20d18c27b3 100644 --- a/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml +++ b/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml @@ -17,18 +17,27 @@ services: command: - | apk add --no-cache curl jq >/dev/null - + + # validates DAYS_AGO and EPOCHS + case "$$DAYS_AGO" in ''|*[!0-9]*) echo "DAYS_AGO must be a non-negative integer: '$$DAYS_AGO'"; exit 1;; esac + case "$$EPOCHS" in ''|*[!0-9]*) echo "EPOCHS must be a non-negative integer: '$$EPOCHS'"; exit 1;; esac + [ "$$EPOCHS" -ge 1 ] || { echo "EPOCHS must be at least 1"; exit 1; } + day=$$(date -u -d "@$$(( $$(date -u +%s) - DAYS_AGO * 86400 ))" +%F) - url=$$(curl -sSf "https://forest-archive.chainsafe.dev/list/$$CHAIN/latest-v2?format=json" | + url=$$(curl -sSf --connect-timeout 10 --retry 3 \ + "https://forest-archive.chainsafe.dev/list/$$CHAIN/latest-v2?format=json" | jq -r --arg d "$$day" '[.items[].url | select(contains("_"+$$d+"_"))] | first') [ "$$url" != null ] || { echo "no $$CHAIN snapshot for $$day"; exit 1; } file=$$(basename "$$url") head=$$(echo "$$file" | sed 's/.*_height_\([0-9]*\)\..*/\1/') - + case "$$head" in ''|*[!0-9]*) echo "could not parse a height from '$$file'"; exit 1;; esac + [ "$$EPOCHS" -le "$$head" ] || { echo "EPOCHS ($$EPOCHS) exceeds head ($$head)"; exit 1; } + # only downloads if the snapshot is not present - [ -f "/snapshots/$$file" ] || curl -sSfL -o "/snapshots/$$file" "$$url" - + [ -f "/snapshots/$$file" ] || + curl -sSfL --connect-timeout 10 --retry 3 -o "/snapshots/$$file" "$$url" + # define env vars used later on the next services printf 'SNAPSHOT=%s\nSTART=%s\nEND=%s\nBACKFILL=%s\n' \ "$$file" "$$((head - EPOCHS))" "$$((head - 1))" "$$((EPOCHS + 1))" > /snapshots/env @@ -53,9 +62,9 @@ services: --port 2345 --height 0 --index-backfill-epochs "$$BACKFILL" healthcheck: test: ["CMD", "forest-cli", "chain", "head"] - interval: 30s + interval: 15s timeout: 10s - retries: 10 + retries: 480 rpc-checks: image: ghcr.io/chainsafe/forest-rpc-checks:latest From 6ecededd1920b252a60a611d823c36a8dbb708bf Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 12 Aug 2026 15:42:15 -0400 Subject: [PATCH 04/14] chore: includes `prepare.sh` and `ribasushi-rpc-checks.yaml` --- .../RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md | 8 ++ .github/workflows/ribasushi-rpc-checks.yml | 52 ++++++++ CHANGELOG.md | 2 +- .../guides/rpc_api_compatibility.md | 9 ++ scripts/tests/ribasushi-rpc-checks/.gitignore | 3 + .../ribasushi-rpc-checks/docker-compose.yaml | 82 ++++-------- scripts/tests/ribasushi-rpc-checks/prepare.sh | 125 ++++++++++++++++++ src/tool/offline_server/server.rs | 2 +- 8 files changed, 222 insertions(+), 61 deletions(-) create mode 100644 .github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md create mode 100644 .github/workflows/ribasushi-rpc-checks.yml create mode 100644 scripts/tests/ribasushi-rpc-checks/.gitignore create mode 100755 scripts/tests/ribasushi-rpc-checks/prepare.sh diff --git a/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md b/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md new file mode 100644 index 00000000000..9fa0b1589b6 --- /dev/null +++ b/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md @@ -0,0 +1,8 @@ +--- +title: "[automated] Ribasushi RPC checks failure" +labels: ["Bug"] +--- + +## Description + +Latest RPC checks against the external Ribasushi dataset failed. Please [check the logs]({{ env.WORKFLOW_URL }}) for more information. diff --git a/.github/workflows/ribasushi-rpc-checks.yml b/.github/workflows/ribasushi-rpc-checks.yml new file mode 100644 index 00000000000..f39474092c0 --- /dev/null +++ b/.github/workflows/ribasushi-rpc-checks.yml @@ -0,0 +1,52 @@ +name: Ribasushi RPC checks +permissions: + contents: read + issues: write + +concurrency: + group: ${{ github.workflow }} + +on: + workflow_dispatch: + schedule: + - cron: "0 13 * * *" # Runs every day at 13:00 UTC + +jobs: + rpc-checks: + name: RPC checks against the Ribasushi dataset + runs-on: warp-ubuntu-2404-x64-8x + timeout-minutes: 180 + defaults: + run: + working-directory: scripts/tests/ribasushi-rpc-checks + steps: + - name: Checkout Sources + uses: actions/checkout@v7 + - name: Free up disk space + working-directory: . + run: ./scripts/optimize_disk_space_gh_worker.sh + - name: Install aria2 + run: sudo apt-get install -y aria2 + - name: Prepare the snapshot and pin the images + run: | + # Images are tagged with their build date and the short commit hash. + tag="$(TZ=UTC git show -s --format=%cd --date=format-local:%Y-%m-%d HEAD)-$(git rev-parse --short=7 HEAD)" + echo "Testing ghcr.io/chainsafe/forest:$tag" + ./prepare.sh "$tag" + - name: Run the RPC checks + run: docker compose up --abort-on-container-exit --exit-code-from rpc-checks + - name: Dump docker logs + if: always() + uses: jwalton/gh-docker-logs@v2 + - name: Set WORKFLOW_URL + if: always() + run: | + export WORKFLOW_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + echo "${WORKFLOW_URL}" + echo "WORKFLOW_URL=${WORKFLOW_URL}" >> "$GITHUB_ENV" + - uses: JasonEtco/create-an-issue@v2 + if: github.ref == 'refs/heads/main' && failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + filename: .github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md diff --git a/CHANGELOG.md b/CHANGELOG.md index e49e6375939..973c296a470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ - [#7408](https://github.com/ChainSafe/forest/pull/7408): Impl `Forest.IndexBackfill`, `Forest.IndexBackfillStatus` and `Forest.IndexBackfillCancel` RPC methods and the `forest-cli index backfill` command to back-fill the chain index (Ethereum mappings, events, block blooms) through the running daemon, so the node no longer needs to be stopped. An interrupted or cancelled run can be resumed with `--resume`. -- [#7270](https://github.com/ChainSafe/forest/issues/7270): Include `scripts/tests/ribasushi-rpc-checks` that tests Forest against the external Ribasushi dataset +- [#7270](https://github.com/ChainSafe/forest/issues/7270): Include `scripts/tests/ribasushi-rpc-checks` that tests Forest against the external Ribasushi dataset, run daily in CI at 13:00 UTC ### Changed diff --git a/docs/docs/developers/guides/rpc_api_compatibility.md b/docs/docs/developers/guides/rpc_api_compatibility.md index 470d22ab7a1..17ad53f02cf 100644 --- a/docs/docs/developers/guides/rpc_api_compatibility.md +++ b/docs/docs/developers/guides/rpc_api_compatibility.md @@ -50,6 +50,15 @@ Forest. Forest does not yet support mining and none of the mining-related RPC calls will be implemented in the foreseeable future. +## External dataset checks + +Forest is also checked against the external Ribasushi dataset of recorded RPC +responses (`chain.data.riba.plus`). The `Ribasushi RPC checks` workflow runs daily +at 13:00 UTC: it serves a recent calibnet snapshot with `forest-tool api serve` +and replays the dataset's queries against it. To run it locally, execute +`./prepare.sh` in `scripts/tests/ribasushi-rpc-checks` followed by +`docker compose up --abort-on-container-exit --exit-code-from rpc-checks`. + ## Gateway The `lotus-gateway` executable is a reverse-proxy that sanitizes RPC calls diff --git a/scripts/tests/ribasushi-rpc-checks/.gitignore b/scripts/tests/ribasushi-rpc-checks/.gitignore new file mode 100644 index 00000000000..0bc164e73fa --- /dev/null +++ b/scripts/tests/ribasushi-rpc-checks/.gitignore @@ -0,0 +1,3 @@ +# Both are generated by `prepare.sh`. +/snapshots/ +/.env diff --git a/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml b/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml index b20d18c27b3..d977770d02a 100644 --- a/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml +++ b/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml @@ -1,65 +1,34 @@ name: rpc-checks -# docker compose up --build --abort-on-container-exit --exit-code-from rpc-checks - -x-env: &env - CHAIN: ${CHAIN:-calibnet} - DAYS_AGO: ${DAYS_AGO:-2} - EPOCHS: ${EPOCHS:-1000} +# WARNING: this file is not usable on its own. `./prepare.sh` downloads the +# snapshot and writes the `.env` file every `${...}` below is interpolated from, +# so it has to run first. Without it the services come up with empty images and +# arguments. +# +# ./prepare.sh +# docker compose up --abort-on-container-exit --exit-code-from rpc-checks services: - snapshot: - image: alpine:3.24 - environment: *env - volumes: - - ./snapshots:/snapshots - entrypoint: ["/bin/sh", "-euc"] - command: - - | - apk add --no-cache curl jq >/dev/null - - # validates DAYS_AGO and EPOCHS - case "$$DAYS_AGO" in ''|*[!0-9]*) echo "DAYS_AGO must be a non-negative integer: '$$DAYS_AGO'"; exit 1;; esac - case "$$EPOCHS" in ''|*[!0-9]*) echo "EPOCHS must be a non-negative integer: '$$EPOCHS'"; exit 1;; esac - [ "$$EPOCHS" -ge 1 ] || { echo "EPOCHS must be at least 1"; exit 1; } - - day=$$(date -u -d "@$$(( $$(date -u +%s) - DAYS_AGO * 86400 ))" +%F) - url=$$(curl -sSf --connect-timeout 10 --retry 3 \ - "https://forest-archive.chainsafe.dev/list/$$CHAIN/latest-v2?format=json" | - jq -r --arg d "$$day" '[.items[].url | select(contains("_"+$$d+"_"))] | first') - [ "$$url" != null ] || { echo "no $$CHAIN snapshot for $$day"; exit 1; } - - file=$$(basename "$$url") - head=$$(echo "$$file" | sed 's/.*_height_\([0-9]*\)\..*/\1/') - case "$$head" in ''|*[!0-9]*) echo "could not parse a height from '$$file'"; exit 1;; esac - [ "$$EPOCHS" -le "$$head" ] || { echo "EPOCHS ($$EPOCHS) exceeds head ($$head)"; exit 1; } - - # only downloads if the snapshot is not present - [ -f "/snapshots/$$file" ] || - curl -sSfL --connect-timeout 10 --retry 3 -o "/snapshots/$$file" "$$url" - - # define env vars used later on the next services - printf 'SNAPSHOT=%s\nSTART=%s\nEND=%s\nBACKFILL=%s\n' \ - "$$file" "$$((head - EPOCHS))" "$$((head - 1))" "$$((EPOCHS + 1))" > /snapshots/env - cat /snapshots/env - # The RPC port opens only after the backfill finishes, so "healthy" means ready. forest: - image: ghcr.io/chainsafe/forest:latest - depends_on: - snapshot: - condition: service_completed_successfully - environment: *env + image: ${FOREST_IMAGE} volumes: - ./snapshots:/snapshots:ro ports: - "2345:2345" - entrypoint: ["/bin/sh", "-euc"] + entrypoint: ["forest-tool"] command: - - | - . /snapshots/env - exec forest-tool api serve "/snapshots/$$SNAPSHOT" --chain "$$CHAIN" \ - --port 2345 --height 0 --index-backfill-epochs "$$BACKFILL" + - api + - serve + - /snapshots/${SNAPSHOT} + - --chain + - ${CHAIN} + - --port + - "2345" + - --height + - "0" + - --index-backfill-epochs + - ${BACKFILL} healthcheck: test: ["CMD", "forest-cli", "chain", "head"] interval: 15s @@ -67,16 +36,11 @@ services: retries: 480 rpc-checks: - image: ghcr.io/chainsafe/forest-rpc-checks:latest + image: ghcr.io/chainsafe/forest-rpc-checks:2026-08-11-43c77da depends_on: forest: condition: service_healthy environment: FOREST_RPC_URL: forest:2345/rpc/v1 - volumes: - - ./snapshots:/snapshots:ro - entrypoint: ["/bin/sh", "-euc"] - command: - - | - . /snapshots/env - exec bundle exec ruby check_rpc.rb "$$START" "$$END" + # The image already entrypoints into `bundle exec ruby check_rpc.rb`. + command: ["${START}", "${END}"] diff --git a/scripts/tests/ribasushi-rpc-checks/prepare.sh b/scripts/tests/ribasushi-rpc-checks/prepare.sh new file mode 100755 index 00000000000..50d4ef4ddd8 --- /dev/null +++ b/scripts/tests/ribasushi-rpc-checks/prepare.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash +# Prepares everything the sibling `docker-compose.yaml` interpolates: +# +# * picks and downloads a chain snapshot into ./snapshots, +# * pins both container images to an immutable `date-digest` tag, +# * writes the resulting variables to ./.env, which `docker compose` reads +# automatically from the project directory. +# +# Usage: +# +# ./prepare.sh [FOREST_IMAGE_TAG] +# docker compose up --abort-on-container-exit --exit-code-from rpc-checks +# +# FOREST_IMAGE_TAG is a `date-digest` tag such as `2026-08-10-0b42ef6`. If not +# set then will use the `date-digest` for the current HEAD. +# +# Knobs, all optional: +# +# CHAIN network to test, defaults to calibnet +# DAYS_AGO age of the snapshot to fetch, defaults to 2 +# EPOCHS how many epochs to back-fill and check, defaults to 1000 + +set -euo pipefail + +SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +SNAPSHOT_DIR="$SCRIPT_DIR/snapshots" +ENV_FILE="$SCRIPT_DIR/.env" + +CHAIN="${CHAIN:-calibnet}" +DAYS_AGO="${DAYS_AGO:-2}" +EPOCHS="${EPOCHS:-1000}" + +ARCHIVE_URL="https://forest-archive.chainsafe.dev/list/$CHAIN/latest-v2?format=json" + +fail() { + echo "$*" >&2 + exit 1 +} + +is_non_negative_integer() { + [[ $1 =~ ^[0-9]+$ ]] +} + +# Prints the UTC date, `$1` days in the past, as YYYY-MM-DD. GNU and BSD `date` +# spell date arithmetic differently, and only GNU understands `--version`, which +# is what tells the two apart here. CI will be 99% of the time GNU. +utc_day_offset() { + if date --version >/dev/null 2>&1; then + date -u -d "$1 days ago" +%F + else + date -u -v-"$1"d +%F + fi +} + +# Prints the `date-digest` tag of the image built from the checked out `HEAD`, +# e.g. `2026-08-10-0b42ef6` +forest_tag_for_head() { + local date short + date=$(TZ=UTC git -C "$SCRIPT_DIR" show -s --format=%cd --date=format-local:%Y-%m-%d HEAD) + short=$(git -C "$SCRIPT_DIR" rev-parse --short=7 HEAD) + echo "$date-$short" +} + +forest_image_tag="${1:-$(forest_tag_for_head)}" +[[ $forest_image_tag =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9a-f]{7,}$ ]] || + fail "FOREST_IMAGE_TAG must look like 2026-08-10-0b42ef6, got '$forest_image_tag'" + +is_non_negative_integer "$DAYS_AGO" || fail "DAYS_AGO must be a non-negative integer, got '$DAYS_AGO'" +is_non_negative_integer "$EPOCHS" || fail "EPOCHS must be a non-negative integer, got '$EPOCHS'" +((EPOCHS >= 1)) || fail "EPOCHS must be at least 1" + +# find the most recent snapshot download URL for the $DAYS_AGO argument +day=$(utc_day_offset "$DAYS_AGO") +url=$(curl --silent --show-error --fail --location --connect-timeout 10 --retry 3 --max-time 60 "$ARCHIVE_URL" | + jq --raw-output --arg day "$day" '[.items[].url | select(contains("_" + $day + "_"))] | first') +[[ -n $url && $url != null ]] || fail "no $CHAIN snapshot published for $day" + +file=$(basename "$url") + +# Snapshot names carry the epoch of their head tipset, as in +# `forest_snapshot_calibnet_2026-08-10_height_3000000.forest.car.zst`. +# Strip everything up to and including `_height_`, then everything from the first `.`. +head_epoch=${file##*_height_} +head_epoch=${head_epoch%%.*} +is_non_negative_integer "$head_epoch" || fail "could not parse a head epoch out of '$file'" +((EPOCHS <= head_epoch)) || fail "EPOCHS ($EPOCHS) exceeds the snapshot head epoch ($head_epoch)" + +mkdir -p "$SNAPSHOT_DIR" +destination="$SNAPSHOT_DIR/$file" + +# aria2 is a better option for heavy file downloads +# it keeps a control file next to a download and removes it +# once the transfer finishes, --continue helps for abrupt +# interruptions resuming the download instead of starting over +if [[ -f $destination && ! -f $destination.aria2 ]]; then + echo "Reusing the snapshot already downloaded at $destination" +else + aria2c \ + --max-connection-per-server=5 \ + --split=5 \ + --continue=true \ + --auto-file-renaming=false \ + --console-log-level=warn \ + --summary-interval=30 \ + --dir="$SNAPSHOT_DIR" \ + --out="$file" \ + "$url" +fi + +forest_image="${FOREST_IMAGE:-ghcr.io/chainsafe/forest:$forest_image_tag}" + +# The checks cover the epochs the node has an index for: back-filling N epochs +# from the head leaves the range [head - N, head - 1] queryable. +cat >"$ENV_FILE" <= 0`, - // or valiadte the last {-height} EPOCH(s) when `height < 0` + // or validate the last {-height} EPOCH(s) when `height < 0` let validate_until_epoch = if height > 0 { height } else { From 2b16fd192d231dac071496fbbad023bb084743d1 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Wed, 12 Aug 2026 15:55:21 -0400 Subject: [PATCH 05/14] add `Ribasushi` to .dict --- .config/forest.dic | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.config/forest.dic b/.config/forest.dic index 60add798833..3366e58fc82 100644 --- a/.config/forest.dic +++ b/.config/forest.dic @@ -1,4 +1,4 @@ -294 +295 Algorand/M API's API/SM @@ -212,6 +212,7 @@ README repo/S retag Reth +Ribasushi RLP RNG roadmap/S From 71dc887fc03d782035e2ed093ff9423fe38b7322 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 13 Aug 2026 09:28:19 -0400 Subject: [PATCH 06/14] chore: change to `data.riba.plus` --- .config/forest.dic | 1 - .github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md | 4 ++-- .../{ribasushi-rpc-checks.yml => external-rpc-checks.yml} | 6 +++--- CHANGELOG.md | 2 -- docs/docs/developers/guides/rpc_api_compatibility.md | 6 +++--- .../.gitignore | 0 .../docker-compose.yaml | 0 .../prepare.sh | 0 8 files changed, 8 insertions(+), 11 deletions(-) rename .github/workflows/{ribasushi-rpc-checks.yml => external-rpc-checks.yml} (91%) rename scripts/tests/{ribasushi-rpc-checks => external-rpc-checks}/.gitignore (100%) rename scripts/tests/{ribasushi-rpc-checks => external-rpc-checks}/docker-compose.yaml (100%) rename scripts/tests/{ribasushi-rpc-checks => external-rpc-checks}/prepare.sh (100%) diff --git a/.config/forest.dic b/.config/forest.dic index 3366e58fc82..67823ad0fd2 100644 --- a/.config/forest.dic +++ b/.config/forest.dic @@ -212,7 +212,6 @@ README repo/S retag Reth -Ribasushi RLP RNG roadmap/S diff --git a/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md b/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md index 9fa0b1589b6..16ca0671b69 100644 --- a/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md +++ b/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md @@ -1,8 +1,8 @@ --- -title: "[automated] Ribasushi RPC checks failure" +title: "[automated] `data.riba.plus` RPC checks failure" labels: ["Bug"] --- ## Description -Latest RPC checks against the external Ribasushi dataset failed. Please [check the logs]({{ env.WORKFLOW_URL }}) for more information. +Latest RPC checks against the external `data.riba.plus` dataset failed. Please [check the logs]({{ env.WORKFLOW_URL }}) for more information. diff --git a/.github/workflows/ribasushi-rpc-checks.yml b/.github/workflows/external-rpc-checks.yml similarity index 91% rename from .github/workflows/ribasushi-rpc-checks.yml rename to .github/workflows/external-rpc-checks.yml index f39474092c0..4fd9cdff08e 100644 --- a/.github/workflows/ribasushi-rpc-checks.yml +++ b/.github/workflows/external-rpc-checks.yml @@ -1,4 +1,4 @@ -name: Ribasushi RPC checks +name: data.riba.plus RPC checks permissions: contents: read issues: write @@ -13,12 +13,12 @@ on: jobs: rpc-checks: - name: RPC checks against the Ribasushi dataset + name: RPC checks against the data.riba.plus dataset runs-on: warp-ubuntu-2404-x64-8x timeout-minutes: 180 defaults: run: - working-directory: scripts/tests/ribasushi-rpc-checks + working-directory: scripts/tests/external-rpc-checks steps: - name: Checkout Sources uses: actions/checkout@v7 diff --git a/CHANGELOG.md b/CHANGELOG.md index 973c296a470..45ac5b99b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,8 +37,6 @@ - [#7408](https://github.com/ChainSafe/forest/pull/7408): Impl `Forest.IndexBackfill`, `Forest.IndexBackfillStatus` and `Forest.IndexBackfillCancel` RPC methods and the `forest-cli index backfill` command to back-fill the chain index (Ethereum mappings, events, block blooms) through the running daemon, so the node no longer needs to be stopped. An interrupted or cancelled run can be resumed with `--resume`. -- [#7270](https://github.com/ChainSafe/forest/issues/7270): Include `scripts/tests/ribasushi-rpc-checks` that tests Forest against the external Ribasushi dataset, run daily in CI at 13:00 UTC - ### Changed ### Removed diff --git a/docs/docs/developers/guides/rpc_api_compatibility.md b/docs/docs/developers/guides/rpc_api_compatibility.md index 17ad53f02cf..2324aac6db2 100644 --- a/docs/docs/developers/guides/rpc_api_compatibility.md +++ b/docs/docs/developers/guides/rpc_api_compatibility.md @@ -52,11 +52,11 @@ be implemented in the foreseeable future. ## External dataset checks -Forest is also checked against the external Ribasushi dataset of recorded RPC -responses (`chain.data.riba.plus`). The `Ribasushi RPC checks` workflow runs daily +Forest is also checked against the external `data.riba.plus` dataset of recorded RPC +responses (`chain.data.riba.plus`). The `data.riba.plus RPC checks` workflow runs daily at 13:00 UTC: it serves a recent calibnet snapshot with `forest-tool api serve` and replays the dataset's queries against it. To run it locally, execute -`./prepare.sh` in `scripts/tests/ribasushi-rpc-checks` followed by +`./prepare.sh` in `scripts/tests/external-rpc-checks` followed by `docker compose up --abort-on-container-exit --exit-code-from rpc-checks`. ## Gateway diff --git a/scripts/tests/ribasushi-rpc-checks/.gitignore b/scripts/tests/external-rpc-checks/.gitignore similarity index 100% rename from scripts/tests/ribasushi-rpc-checks/.gitignore rename to scripts/tests/external-rpc-checks/.gitignore diff --git a/scripts/tests/ribasushi-rpc-checks/docker-compose.yaml b/scripts/tests/external-rpc-checks/docker-compose.yaml similarity index 100% rename from scripts/tests/ribasushi-rpc-checks/docker-compose.yaml rename to scripts/tests/external-rpc-checks/docker-compose.yaml diff --git a/scripts/tests/ribasushi-rpc-checks/prepare.sh b/scripts/tests/external-rpc-checks/prepare.sh similarity index 100% rename from scripts/tests/ribasushi-rpc-checks/prepare.sh rename to scripts/tests/external-rpc-checks/prepare.sh From 3a0a918a32aee81d5c16fda0ea38b0bb12727891 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Thu, 13 Aug 2026 13:46:55 -0400 Subject: [PATCH 07/14] chore: removing some missing refs to `Ribasushi` --- ...SSUE_TEMPLATE.md => EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md} | 2 +- .github/workflows/external-rpc-checks.yml | 2 +- docs/docs/developers/guides/rpc_api_compatibility.md | 4 ++-- scripts/tests/external-rpc-checks/docker-compose.yaml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) rename .github/{RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md => EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md} (76%) diff --git a/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md b/.github/EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md similarity index 76% rename from .github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md rename to .github/EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md index 16ca0671b69..162c901975c 100644 --- a/.github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md +++ b/.github/EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md @@ -1,5 +1,5 @@ --- -title: "[automated] `data.riba.plus` RPC checks failure" +title: "[automated] External dataset RPC checks failure" labels: ["Bug"] --- diff --git a/.github/workflows/external-rpc-checks.yml b/.github/workflows/external-rpc-checks.yml index 4fd9cdff08e..0aa7d2f11dd 100644 --- a/.github/workflows/external-rpc-checks.yml +++ b/.github/workflows/external-rpc-checks.yml @@ -49,4 +49,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - filename: .github/RIBASUSHI_RPC_CHECKS_ISSUE_TEMPLATE.md + filename: .github/EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md diff --git a/docs/docs/developers/guides/rpc_api_compatibility.md b/docs/docs/developers/guides/rpc_api_compatibility.md index 2324aac6db2..958f0cb40bc 100644 --- a/docs/docs/developers/guides/rpc_api_compatibility.md +++ b/docs/docs/developers/guides/rpc_api_compatibility.md @@ -52,8 +52,8 @@ be implemented in the foreseeable future. ## External dataset checks -Forest is also checked against the external `data.riba.plus` dataset of recorded RPC -responses (`chain.data.riba.plus`). The `data.riba.plus RPC checks` workflow runs daily +Forest is also checked against the external `data.riba.plus` dataset of recorded +RPC responses. The `data.riba.plus RPC checks` workflow runs daily at 13:00 UTC: it serves a recent calibnet snapshot with `forest-tool api serve` and replays the dataset's queries against it. To run it locally, execute `./prepare.sh` in `scripts/tests/external-rpc-checks` followed by diff --git a/scripts/tests/external-rpc-checks/docker-compose.yaml b/scripts/tests/external-rpc-checks/docker-compose.yaml index d977770d02a..79c18a2e413 100644 --- a/scripts/tests/external-rpc-checks/docker-compose.yaml +++ b/scripts/tests/external-rpc-checks/docker-compose.yaml @@ -1,4 +1,4 @@ -name: rpc-checks +name: external-rpc-checks # WARNING: this file is not usable on its own. `./prepare.sh` downloads the # snapshot and writes the `.env` file every `${...}` below is interpolated from, From aea766b3318487d914715797349301629b4a9edc Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 14 Aug 2026 14:57:38 -0400 Subject: [PATCH 08/14] chore: address comments, making `./setup.sh` --- .github/workflows/external-rpc-checks.yml | 14 +- .../guides/rpc_api_compatibility.md | 9 +- scripts/tests/external-rpc-checks/.gitignore | 3 - .../external-rpc-checks/docker-compose.yaml | 90 ++++++++----- scripts/tests/external-rpc-checks/prepare.sh | 125 ------------------ scripts/tests/external-rpc-checks/setup.sh | 60 +++++++++ 6 files changed, 126 insertions(+), 175 deletions(-) delete mode 100644 scripts/tests/external-rpc-checks/.gitignore delete mode 100755 scripts/tests/external-rpc-checks/prepare.sh create mode 100755 scripts/tests/external-rpc-checks/setup.sh diff --git a/.github/workflows/external-rpc-checks.yml b/.github/workflows/external-rpc-checks.yml index 0aa7d2f11dd..aa954427ff1 100644 --- a/.github/workflows/external-rpc-checks.yml +++ b/.github/workflows/external-rpc-checks.yml @@ -15,26 +15,14 @@ jobs: rpc-checks: name: RPC checks against the data.riba.plus dataset runs-on: warp-ubuntu-2404-x64-8x - timeout-minutes: 180 defaults: run: working-directory: scripts/tests/external-rpc-checks steps: - name: Checkout Sources uses: actions/checkout@v7 - - name: Free up disk space - working-directory: . - run: ./scripts/optimize_disk_space_gh_worker.sh - - name: Install aria2 - run: sudo apt-get install -y aria2 - - name: Prepare the snapshot and pin the images - run: | - # Images are tagged with their build date and the short commit hash. - tag="$(TZ=UTC git show -s --format=%cd --date=format-local:%Y-%m-%d HEAD)-$(git rev-parse --short=7 HEAD)" - echo "Testing ghcr.io/chainsafe/forest:$tag" - ./prepare.sh "$tag" - name: Run the RPC checks - run: docker compose up --abort-on-container-exit --exit-code-from rpc-checks + run: ./setup.sh - name: Dump docker logs if: always() uses: jwalton/gh-docker-logs@v2 diff --git a/docs/docs/developers/guides/rpc_api_compatibility.md b/docs/docs/developers/guides/rpc_api_compatibility.md index 958f0cb40bc..00f4de2b67c 100644 --- a/docs/docs/developers/guides/rpc_api_compatibility.md +++ b/docs/docs/developers/guides/rpc_api_compatibility.md @@ -54,10 +54,11 @@ be implemented in the foreseeable future. Forest is also checked against the external `data.riba.plus` dataset of recorded RPC responses. The `data.riba.plus RPC checks` workflow runs daily -at 13:00 UTC: it serves a recent calibnet snapshot with `forest-tool api serve` -and replays the dataset's queries against it. To run it locally, execute -`./prepare.sh` in `scripts/tests/external-rpc-checks` followed by -`docker compose up --abort-on-container-exit --exit-code-from rpc-checks`. +at 13:00 UTC: it imports a calibnet snapshot into a regular Forest daemon, waits +for the node to be ready, back-fills the chain index for the epochs under test +and replays the dataset's queries against them. Everything runs in containers, +so only docker is needed to reproduce it locally; the commands are listed at the +top of `scripts/tests/external-rpc-checks/docker-compose.yaml`. ## Gateway diff --git a/scripts/tests/external-rpc-checks/.gitignore b/scripts/tests/external-rpc-checks/.gitignore deleted file mode 100644 index 0bc164e73fa..00000000000 --- a/scripts/tests/external-rpc-checks/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Both are generated by `prepare.sh`. -/snapshots/ -/.env diff --git a/scripts/tests/external-rpc-checks/docker-compose.yaml b/scripts/tests/external-rpc-checks/docker-compose.yaml index 79c18a2e413..65d008e7356 100644 --- a/scripts/tests/external-rpc-checks/docker-compose.yaml +++ b/scripts/tests/external-rpc-checks/docker-compose.yaml @@ -1,46 +1,76 @@ name: external-rpc-checks -# WARNING: this file is not usable on its own. `./prepare.sh` downloads the -# snapshot and writes the `.env` file every `${...}` below is interpolated from, -# so it has to run first. Without it the services come up with empty images and -# arguments. -# -# ./prepare.sh -# docker compose up --abort-on-container-exit --exit-code-from rpc-checks +x-forest-service: &forest-service + image: ghcr.io/chainsafe/forest:edge-fat + volumes: + - node-data:/data + environment: + # keeps the database in the container's volume, shared across containers + - FOREST_PATH=/data + user: 0:0 services: - # The RPC port opens only after the backfill finishes, so "healthy" means ready. + # Downloads yesterday's snapshot, imports it and saves the JWT token. + snapshot: + <<: *forest-service + command: + - --chain=calibnet + - --encrypt-keystore=false + - --no-gc + - --height=-50 + - --import-snapshot=${SNAPSHOT_URL} + - --halt-after-import + - --save-token=/data/forest-token + + # Serves RPC while syncing. The ports are published so ./setup.sh can query it. + # It deliberately does not `depends_on` init: ./setup.sh runs the import first, + # and a dependency here would re-run it from every downstream `compose run`. forest: - image: ${FOREST_IMAGE} - volumes: - - ./snapshots:/snapshots:ro + <<: *forest-service + environment: + - FOREST_PATH=/data + - FOREST_CHAIN_INDEXER_ENABLED=1 ports: - "2345:2345" - entrypoint: ["forest-tool"] + - "2346:2346" command: - - api - - serve - - /snapshots/${SNAPSHOT} - - --chain - - ${CHAIN} - - --port - - "2345" - - --height - - "0" - - --index-backfill-epochs - - ${BACKFILL} + - --chain=calibnet + - --encrypt-keystore=false + - --no-gc + - --rpc-address=0.0.0.0:2345 + - --healthcheck-address=0.0.0.0:2346 healthcheck: - test: ["CMD", "forest-cli", "chain", "head"] - interval: 15s + # `/livez` from inside the container: RPC answering and peers connected. + test: ["CMD", "forest-cli", "healthcheck", "live", "--healthcheck-port", "2346"] + interval: 30s timeout: 10s - retries: 480 + retries: 3 + start_period: 5m + start_interval: 5s - rpc-checks: - image: ghcr.io/chainsafe/forest-rpc-checks:2026-08-11-43c77da + # Indexes the 1000 epochs below the snapshot head. `--from` counts back + # inclusively, so one extra tipset covers the whole range. + backfill: + <<: *forest-service depends_on: forest: condition: service_healthy + environment: + - FULLNODE_API_INFO=${FULLNODE_API_INFO} + entrypoint: ["forest-cli"] + command: + - index + - backfill + - --from=${SNAPSHOT_EPOCH} + - --n-tipsets=1001 + - --recompute + - --allow-near-head + + # The image entrypoints into `bundle exec ruby check_rpc.rb`, so only the epochs are appended. + rpc-checks: + image: ghcr.io/chainsafe/forest-rpc-checks:latest environment: FOREST_RPC_URL: forest:2345/rpc/v1 - # The image already entrypoints into `bundle exec ruby check_rpc.rb`. - command: ["${START}", "${END}"] + +volumes: + node-data: diff --git a/scripts/tests/external-rpc-checks/prepare.sh b/scripts/tests/external-rpc-checks/prepare.sh deleted file mode 100755 index 50d4ef4ddd8..00000000000 --- a/scripts/tests/external-rpc-checks/prepare.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/usr/bin/env bash -# Prepares everything the sibling `docker-compose.yaml` interpolates: -# -# * picks and downloads a chain snapshot into ./snapshots, -# * pins both container images to an immutable `date-digest` tag, -# * writes the resulting variables to ./.env, which `docker compose` reads -# automatically from the project directory. -# -# Usage: -# -# ./prepare.sh [FOREST_IMAGE_TAG] -# docker compose up --abort-on-container-exit --exit-code-from rpc-checks -# -# FOREST_IMAGE_TAG is a `date-digest` tag such as `2026-08-10-0b42ef6`. If not -# set then will use the `date-digest` for the current HEAD. -# -# Knobs, all optional: -# -# CHAIN network to test, defaults to calibnet -# DAYS_AGO age of the snapshot to fetch, defaults to 2 -# EPOCHS how many epochs to back-fill and check, defaults to 1000 - -set -euo pipefail - -SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) -SNAPSHOT_DIR="$SCRIPT_DIR/snapshots" -ENV_FILE="$SCRIPT_DIR/.env" - -CHAIN="${CHAIN:-calibnet}" -DAYS_AGO="${DAYS_AGO:-2}" -EPOCHS="${EPOCHS:-1000}" - -ARCHIVE_URL="https://forest-archive.chainsafe.dev/list/$CHAIN/latest-v2?format=json" - -fail() { - echo "$*" >&2 - exit 1 -} - -is_non_negative_integer() { - [[ $1 =~ ^[0-9]+$ ]] -} - -# Prints the UTC date, `$1` days in the past, as YYYY-MM-DD. GNU and BSD `date` -# spell date arithmetic differently, and only GNU understands `--version`, which -# is what tells the two apart here. CI will be 99% of the time GNU. -utc_day_offset() { - if date --version >/dev/null 2>&1; then - date -u -d "$1 days ago" +%F - else - date -u -v-"$1"d +%F - fi -} - -# Prints the `date-digest` tag of the image built from the checked out `HEAD`, -# e.g. `2026-08-10-0b42ef6` -forest_tag_for_head() { - local date short - date=$(TZ=UTC git -C "$SCRIPT_DIR" show -s --format=%cd --date=format-local:%Y-%m-%d HEAD) - short=$(git -C "$SCRIPT_DIR" rev-parse --short=7 HEAD) - echo "$date-$short" -} - -forest_image_tag="${1:-$(forest_tag_for_head)}" -[[ $forest_image_tag =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9a-f]{7,}$ ]] || - fail "FOREST_IMAGE_TAG must look like 2026-08-10-0b42ef6, got '$forest_image_tag'" - -is_non_negative_integer "$DAYS_AGO" || fail "DAYS_AGO must be a non-negative integer, got '$DAYS_AGO'" -is_non_negative_integer "$EPOCHS" || fail "EPOCHS must be a non-negative integer, got '$EPOCHS'" -((EPOCHS >= 1)) || fail "EPOCHS must be at least 1" - -# find the most recent snapshot download URL for the $DAYS_AGO argument -day=$(utc_day_offset "$DAYS_AGO") -url=$(curl --silent --show-error --fail --location --connect-timeout 10 --retry 3 --max-time 60 "$ARCHIVE_URL" | - jq --raw-output --arg day "$day" '[.items[].url | select(contains("_" + $day + "_"))] | first') -[[ -n $url && $url != null ]] || fail "no $CHAIN snapshot published for $day" - -file=$(basename "$url") - -# Snapshot names carry the epoch of their head tipset, as in -# `forest_snapshot_calibnet_2026-08-10_height_3000000.forest.car.zst`. -# Strip everything up to and including `_height_`, then everything from the first `.`. -head_epoch=${file##*_height_} -head_epoch=${head_epoch%%.*} -is_non_negative_integer "$head_epoch" || fail "could not parse a head epoch out of '$file'" -((EPOCHS <= head_epoch)) || fail "EPOCHS ($EPOCHS) exceeds the snapshot head epoch ($head_epoch)" - -mkdir -p "$SNAPSHOT_DIR" -destination="$SNAPSHOT_DIR/$file" - -# aria2 is a better option for heavy file downloads -# it keeps a control file next to a download and removes it -# once the transfer finishes, --continue helps for abrupt -# interruptions resuming the download instead of starting over -if [[ -f $destination && ! -f $destination.aria2 ]]; then - echo "Reusing the snapshot already downloaded at $destination" -else - aria2c \ - --max-connection-per-server=5 \ - --split=5 \ - --continue=true \ - --auto-file-renaming=false \ - --console-log-level=warn \ - --summary-interval=30 \ - --dir="$SNAPSHOT_DIR" \ - --out="$file" \ - "$url" -fi - -forest_image="${FOREST_IMAGE:-ghcr.io/chainsafe/forest:$forest_image_tag}" - -# The checks cover the epochs the node has an index for: back-filling N epochs -# from the head leaves the range [head - N, head - 1] queryable. -cat >"$ENV_FILE" < /dev/null 2>&1; then + date -u -d "$1 days ago" +%F + else + date -u -v-"$1"d +%F + fi +} + +# The dataset lags the chain, so yesterday's snapshot is the one to test against. +day=$(utc_day_offset 1) +url=$(curl --silent --show-error --fail --retry 3 --connect-timeout 10 --max-time 60 "${ARCHIVE_URL}" | + jq --raw-output --arg day "${day}" '[.items[].url | select(contains("_" + $day + "_"))] | first') +[[ ${url} == https* ]] || { + echo "no calibnet snapshot published for ${day}" + exit 1 +} + +# Snapshot names end in the epoch of their head tipset. +epoch=${url##*_height_} +epoch=${epoch%%.*} +export SNAPSHOT_EPOCH="${epoch}" +START=$((epoch - 1000)) +END=$((epoch - 1)) + +# Confirms the back-fill indexed an epoch, which is what the checks query. +verify_epoch_indexed() { + curl --silent --show-error --fail --retry 5 --retry-delay 5 --connect-timeout 10 \ + --header 'Content-Type: application/json' \ + --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getBlockByNumber\",\"params\":[\"$(printf '0x%x' "$1")\",false]}" \ + "${RPC_URL}" | jq -e '.result.number' > /dev/null +} + +export SNAPSHOT_URL="${url}" + +docker compose run --rm snapshot +docker compose up --detach forest + +FULLNODE_API_INFO="$(docker compose exec -T forest cat /data/forest-token)" +export FULLNODE_API_INFO="${FULLNODE_API_INFO}:/dns/forest/tcp/2345/http" +docker compose run --rm backfill + +verify_epoch_indexed "${START}" +verify_epoch_indexed "${END}" + +docker compose run --rm rpc-checks "${START}" "${END}" + +popd From f0aef7a85b104bd9d60bbbf8e0d3f5af058e4944 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 14 Aug 2026 15:10:30 -0400 Subject: [PATCH 09/14] chore: address yaml lint problem --- .../tests/external-rpc-checks/docker-compose.yaml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/scripts/tests/external-rpc-checks/docker-compose.yaml b/scripts/tests/external-rpc-checks/docker-compose.yaml index 65d008e7356..a364681bd39 100644 --- a/scripts/tests/external-rpc-checks/docker-compose.yaml +++ b/scripts/tests/external-rpc-checks/docker-compose.yaml @@ -22,9 +22,6 @@ services: - --halt-after-import - --save-token=/data/forest-token - # Serves RPC while syncing. The ports are published so ./setup.sh can query it. - # It deliberately does not `depends_on` init: ./setup.sh runs the import first, - # and a dependency here would re-run it from every downstream `compose run`. forest: <<: *forest-service environment: @@ -41,7 +38,15 @@ services: - --healthcheck-address=0.0.0.0:2346 healthcheck: # `/livez` from inside the container: RPC answering and peers connected. - test: ["CMD", "forest-cli", "healthcheck", "live", "--healthcheck-port", "2346"] + test: + [ + "CMD", + "forest-cli", + "healthcheck", + "live", + "--healthcheck-port", + "2346", + ] interval: 30s timeout: 10s retries: 3 From 15632ce6a1815376a06e43b83d193113f97c87c0 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Fri, 14 Aug 2026 15:56:50 -0400 Subject: [PATCH 10/14] chore: address rabbit comments --- docs/docs/developers/guides/rpc_api_compatibility.md | 7 ++++--- scripts/tests/external-rpc-checks/setup.sh | 5 ++++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/docs/developers/guides/rpc_api_compatibility.md b/docs/docs/developers/guides/rpc_api_compatibility.md index 00f4de2b67c..6bf783bd162 100644 --- a/docs/docs/developers/guides/rpc_api_compatibility.md +++ b/docs/docs/developers/guides/rpc_api_compatibility.md @@ -56,9 +56,10 @@ Forest is also checked against the external `data.riba.plus` dataset of recorded RPC responses. The `data.riba.plus RPC checks` workflow runs daily at 13:00 UTC: it imports a calibnet snapshot into a regular Forest daemon, waits for the node to be ready, back-fills the chain index for the epochs under test -and replays the dataset's queries against them. Everything runs in containers, -so only docker is needed to reproduce it locally; the commands are listed at the -top of `scripts/tests/external-rpc-checks/docker-compose.yaml`. +and replays the dataset's queries against them. To reproduce it locally, run +`./setup.sh` in `scripts/tests/external-rpc-checks`, which drives the whole +sequence. It needs Docker, `curl` and `jq` on the host; everything else, the +snapshot included, stays in containers. ## Gateway diff --git a/scripts/tests/external-rpc-checks/setup.sh b/scripts/tests/external-rpc-checks/setup.sh index 1f856bc4f49..2055f5f7a98 100755 --- a/scripts/tests/external-rpc-checks/setup.sh +++ b/scripts/tests/external-rpc-checks/setup.sh @@ -2,7 +2,7 @@ # Runs the RPC checks against the external data.riba.plus dataset. # Needs docker, curl and jq; the snapshot is downloaded into a docker volume. -set -euxo pipefail +set -euo pipefail PARENT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) pushd "${PARENT_PATH}" @@ -45,6 +45,9 @@ verify_epoch_indexed() { export SNAPSHOT_URL="${url}" +# This should not be needed in GH. It is useful for running locally. +docker compose down --remove-orphans --volumes + docker compose run --rm snapshot docker compose up --detach forest From 5d7382b9d251ba3894901a6c0d3fc1358b4be821 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Mon, 17 Aug 2026 13:28:12 -0400 Subject: [PATCH 11/14] chore: transform resolve->download->backfill into a init container --- .../external-rpc-checks/docker-compose.yaml | 34 +++------------- scripts/tests/external-rpc-checks/init.sh | 34 ++++++++++++++++ scripts/tests/external-rpc-checks/setup.sh | 39 +++---------------- 3 files changed, 46 insertions(+), 61 deletions(-) create mode 100755 scripts/tests/external-rpc-checks/init.sh diff --git a/scripts/tests/external-rpc-checks/docker-compose.yaml b/scripts/tests/external-rpc-checks/docker-compose.yaml index a364681bd39..6856289a69f 100644 --- a/scripts/tests/external-rpc-checks/docker-compose.yaml +++ b/scripts/tests/external-rpc-checks/docker-compose.yaml @@ -10,17 +10,13 @@ x-forest-service: &forest-service user: 0:0 services: - # Downloads yesterday's snapshot, imports it and saves the JWT token. - snapshot: + # Resolves yesterday's snapshot, imports it and back-fills the chain index. + init: <<: *forest-service - command: - - --chain=calibnet - - --encrypt-keystore=false - - --no-gc - - --height=-50 - - --import-snapshot=${SNAPSHOT_URL} - - --halt-after-import - - --save-token=/data/forest-token + volumes: + - node-data:/data + - ./init.sh:/init.sh:ro + entrypoint: ["/bin/bash", "/init.sh"] forest: <<: *forest-service @@ -53,24 +49,6 @@ services: start_period: 5m start_interval: 5s - # Indexes the 1000 epochs below the snapshot head. `--from` counts back - # inclusively, so one extra tipset covers the whole range. - backfill: - <<: *forest-service - depends_on: - forest: - condition: service_healthy - environment: - - FULLNODE_API_INFO=${FULLNODE_API_INFO} - entrypoint: ["forest-cli"] - command: - - index - - backfill - - --from=${SNAPSHOT_EPOCH} - - --n-tipsets=1001 - - --recompute - - --allow-near-head - # The image entrypoints into `bundle exec ruby check_rpc.rb`, so only the epochs are appended. rpc-checks: image: ghcr.io/chainsafe/forest-rpc-checks:latest diff --git a/scripts/tests/external-rpc-checks/init.sh b/scripts/tests/external-rpc-checks/init.sh new file mode 100755 index 00000000000..25d7402e1f1 --- /dev/null +++ b/scripts/tests/external-rpc-checks/init.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Runs in the `init` service: resolves the snapshot to test against, imports it +# and back-fills the chain index, all before the daemon starts. Keeping it here +# means the date handling is always Linux, whatever the host. + +set -euo pipefail + +# The Forest image ships neither curl nor jq. +apt-get update -qq +apt-get install -y -qq --no-install-recommends curl jq + +# The dataset lags the chain, so yesterday's snapshot is the one to test against. +day=$(date -u -d '1 day ago' +%F) +url=$(curl --silent --show-error --fail --retry 3 --connect-timeout 10 --max-time 60 \ + "https://forest-archive.chainsafe.dev/list/calibnet/latest-v2?format=json" | + jq --raw-output --arg day "${day}" '[.items[].url | select(contains("_" + $day + "_"))] | first') +[[ ${url} == https* ]] || { + echo "no calibnet snapshot published for ${day}" + exit 1 +} + +# Snapshot names end in the epoch of their head tipset. ./setup.sh reads it back +# to pick the range to check. +epoch=${url##*_height_} +epoch=${epoch%%.*} +printf '%s\n' "${epoch}" > /data/snapshot-epoch + +forest --chain=calibnet --encrypt-keystore=false --no-gc \ + --height=-50 --import-snapshot="${url}" --halt-after-import + +# Indexes the 1000 epochs below the snapshot head; `--from` counts back +# inclusively, so one extra tipset covers the whole range. The offline +# back-fill already recomputes missing state and indexes up to the head. +forest-tool index backfill --chain=calibnet --from="${epoch}" --n-tipsets=1001 diff --git a/scripts/tests/external-rpc-checks/setup.sh b/scripts/tests/external-rpc-checks/setup.sh index 2055f5f7a98..9f821d04d17 100755 --- a/scripts/tests/external-rpc-checks/setup.sh +++ b/scripts/tests/external-rpc-checks/setup.sh @@ -7,34 +7,8 @@ set -euo pipefail PARENT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) pushd "${PARENT_PATH}" -ARCHIVE_URL="https://forest-archive.chainsafe.dev/list/calibnet/latest-v2?format=json" RPC_URL="http://127.0.0.1:2345/rpc/v1" -# GNU and BSD `date` spell date arithmetic differently, only GNU has `--version`. -utc_day_offset() { - if date --version > /dev/null 2>&1; then - date -u -d "$1 days ago" +%F - else - date -u -v-"$1"d +%F - fi -} - -# The dataset lags the chain, so yesterday's snapshot is the one to test against. -day=$(utc_day_offset 1) -url=$(curl --silent --show-error --fail --retry 3 --connect-timeout 10 --max-time 60 "${ARCHIVE_URL}" | - jq --raw-output --arg day "${day}" '[.items[].url | select(contains("_" + $day + "_"))] | first') -[[ ${url} == https* ]] || { - echo "no calibnet snapshot published for ${day}" - exit 1 -} - -# Snapshot names end in the epoch of their head tipset. -epoch=${url##*_height_} -epoch=${epoch%%.*} -export SNAPSHOT_EPOCH="${epoch}" -START=$((epoch - 1000)) -END=$((epoch - 1)) - # Confirms the back-fill indexed an epoch, which is what the checks query. verify_epoch_indexed() { curl --silent --show-error --fail --retry 5 --retry-delay 5 --connect-timeout 10 \ @@ -43,17 +17,16 @@ verify_epoch_indexed() { "${RPC_URL}" | jq -e '.result.number' > /dev/null } -export SNAPSHOT_URL="${url}" - # This should not be needed in GH. It is useful for running locally. docker compose down --remove-orphans --volumes -docker compose run --rm snapshot -docker compose up --detach forest +# Imports the snapshot and back-fills the index, recording the head epoch. +docker compose run --rm init +SNAPSHOT_EPOCH="$(docker compose run --rm --no-TTY --entrypoint cat init /data/snapshot-epoch)" +START=$((SNAPSHOT_EPOCH - 1000)) +END=$((SNAPSHOT_EPOCH - 1)) -FULLNODE_API_INFO="$(docker compose exec -T forest cat /data/forest-token)" -export FULLNODE_API_INFO="${FULLNODE_API_INFO}:/dns/forest/tcp/2345/http" -docker compose run --rm backfill +docker compose up --detach --wait forest verify_epoch_indexed "${START}" verify_epoch_indexed "${END}" From 295979ec3ad0c21ef785d385124a563026fe7d4c Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 18 Aug 2026 09:05:34 -0400 Subject: [PATCH 12/14] chore: address comments --- .../docs/developers/guides/rpc_api_compatibility.md | 9 ++------- scripts/tests/external-rpc-checks/.env | 2 ++ .../tests/external-rpc-checks/docker-compose.yaml | 11 +++++------ scripts/tests/external-rpc-checks/init.sh | 12 ++++-------- scripts/tests/external-rpc-checks/setup.sh | 13 +++++++------ 5 files changed, 20 insertions(+), 27 deletions(-) create mode 100644 scripts/tests/external-rpc-checks/.env diff --git a/docs/docs/developers/guides/rpc_api_compatibility.md b/docs/docs/developers/guides/rpc_api_compatibility.md index 6bf783bd162..032b83c14c5 100644 --- a/docs/docs/developers/guides/rpc_api_compatibility.md +++ b/docs/docs/developers/guides/rpc_api_compatibility.md @@ -53,13 +53,8 @@ be implemented in the foreseeable future. ## External dataset checks Forest is also checked against the external `data.riba.plus` dataset of recorded -RPC responses. The `data.riba.plus RPC checks` workflow runs daily -at 13:00 UTC: it imports a calibnet snapshot into a regular Forest daemon, waits -for the node to be ready, back-fills the chain index for the epochs under test -and replays the dataset's queries against them. To reproduce it locally, run -`./setup.sh` in `scripts/tests/external-rpc-checks`, which drives the whole -sequence. It needs Docker, `curl` and `jq` on the host; everything else, the -snapshot included, stays in containers. +RPC responses, see `.github/workflows/external-rpc-checks.yml` and +`scripts/tests/external-rpc-checks`. ## Gateway diff --git a/scripts/tests/external-rpc-checks/.env b/scripts/tests/external-rpc-checks/.env new file mode 100644 index 00000000000..bd2497d7964 --- /dev/null +++ b/scripts/tests/external-rpc-checks/.env @@ -0,0 +1,2 @@ +FOREST_RPC_PORT=2345 +FOREST_HEALTHZ_RPC_PORT=2346 diff --git a/scripts/tests/external-rpc-checks/docker-compose.yaml b/scripts/tests/external-rpc-checks/docker-compose.yaml index 6856289a69f..cec6aec6045 100644 --- a/scripts/tests/external-rpc-checks/docker-compose.yaml +++ b/scripts/tests/external-rpc-checks/docker-compose.yaml @@ -24,14 +24,13 @@ services: - FOREST_PATH=/data - FOREST_CHAIN_INDEXER_ENABLED=1 ports: - - "2345:2345" - - "2346:2346" + - "${FOREST_RPC_PORT}:${FOREST_RPC_PORT}" command: - --chain=calibnet - --encrypt-keystore=false - --no-gc - - --rpc-address=0.0.0.0:2345 - - --healthcheck-address=0.0.0.0:2346 + - --rpc-address=0.0.0.0:${FOREST_RPC_PORT} + - --healthcheck-address=0.0.0.0:${FOREST_HEALTHZ_RPC_PORT} healthcheck: # `/livez` from inside the container: RPC answering and peers connected. test: @@ -41,7 +40,7 @@ services: "healthcheck", "live", "--healthcheck-port", - "2346", + "${FOREST_HEALTHZ_RPC_PORT}", ] interval: 30s timeout: 10s @@ -53,7 +52,7 @@ services: rpc-checks: image: ghcr.io/chainsafe/forest-rpc-checks:latest environment: - FOREST_RPC_URL: forest:2345/rpc/v1 + FOREST_RPC_URL: forest:${FOREST_RPC_PORT}/rpc/v1 volumes: node-data: diff --git a/scripts/tests/external-rpc-checks/init.sh b/scripts/tests/external-rpc-checks/init.sh index 25d7402e1f1..1d8339d43be 100755 --- a/scripts/tests/external-rpc-checks/init.sh +++ b/scripts/tests/external-rpc-checks/init.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash # Runs in the `init` service: resolves the snapshot to test against, imports it -# and back-fills the chain index, all before the daemon starts. Keeping it here -# means the date handling is always Linux, whatever the host. +# and back-fills the chain index, all before the daemon starts. set -euo pipefail @@ -25,10 +24,7 @@ epoch=${url##*_height_} epoch=${epoch%%.*} printf '%s\n' "${epoch}" > /data/snapshot-epoch -forest --chain=calibnet --encrypt-keystore=false --no-gc \ - --height=-50 --import-snapshot="${url}" --halt-after-import +forest --chain=calibnet --encrypt-keystore=false --import-snapshot="${url}" --halt-after-import -# Indexes the 1000 epochs below the snapshot head; `--from` counts back -# inclusively, so one extra tipset covers the whole range. The offline -# back-fill already recomputes missing state and indexes up to the head. -forest-tool index backfill --chain=calibnet --from="${epoch}" --n-tipsets=1001 +# Indexes the 1000 epochs below the snapshot head, inclusive on both ends +forest-tool index backfill --chain=calibnet --from="${epoch}" --to="$((epoch - 1000))" diff --git a/scripts/tests/external-rpc-checks/setup.sh b/scripts/tests/external-rpc-checks/setup.sh index 9f821d04d17..ac082a0b895 100755 --- a/scripts/tests/external-rpc-checks/setup.sh +++ b/scripts/tests/external-rpc-checks/setup.sh @@ -1,20 +1,21 @@ #!/usr/bin/env bash # Runs the RPC checks against the external data.riba.plus dataset. -# Needs docker, curl and jq; the snapshot is downloaded into a docker volume. set -euo pipefail PARENT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) pushd "${PARENT_PATH}" -RPC_URL="http://127.0.0.1:2345/rpc/v1" +# Ports live in `.env`, which `docker compose` reads on its own. +# shellcheck source=.env +source ./.env +RPC_URL="http://127.0.0.1:${FOREST_RPC_PORT}/rpc/v1" # Confirms the back-fill indexed an epoch, which is what the checks query. verify_epoch_indexed() { - curl --silent --show-error --fail --retry 5 --retry-delay 5 --connect-timeout 10 \ - --header 'Content-Type: application/json' \ - --data "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"eth_getBlockByNumber\",\"params\":[\"$(printf '0x%x' "$1")\",false]}" \ - "${RPC_URL}" | jq -e '.result.number' > /dev/null + jq -nc --arg epoch "$(printf '0x%x' "$1")" \ + '{jsonrpc:"2.0",id:1,method:"eth_getBlockByNumber",params:[$epoch,false]}' | curl --silent \ + --show-error --fail --retry 5 --data @- "${RPC_URL}" | jq -e '.result.number' > /dev/null } # This should not be needed in GH. It is useful for running locally. From f36aecbf84e89dbe93b6b3d7e20c68727c55a734 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 18 Aug 2026 10:34:27 -0400 Subject: [PATCH 13/14] fix RPC verification routine --- scripts/tests/external-rpc-checks/setup.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/tests/external-rpc-checks/setup.sh b/scripts/tests/external-rpc-checks/setup.sh index ac082a0b895..eb19951572a 100755 --- a/scripts/tests/external-rpc-checks/setup.sh +++ b/scripts/tests/external-rpc-checks/setup.sh @@ -14,8 +14,10 @@ RPC_URL="http://127.0.0.1:${FOREST_RPC_PORT}/rpc/v1" # Confirms the back-fill indexed an epoch, which is what the checks query. verify_epoch_indexed() { jq -nc --arg epoch "$(printf '0x%x' "$1")" \ - '{jsonrpc:"2.0",id:1,method:"eth_getBlockByNumber",params:[$epoch,false]}' | curl --silent \ - --show-error --fail --retry 5 --data @- "${RPC_URL}" | jq -e '.result.number' > /dev/null + '{jsonrpc:"2.0",id:1,method:"eth_getBlockByNumber",params:[$epoch,false]}' | + curl --silent --show-error --fail --retry 5 --connect-timeout 10 \ + --header 'Content-Type: application/json' --data @- "${RPC_URL}" | + jq -e '.result.number' > /dev/null } # This should not be needed in GH. It is useful for running locally. From 4c718cc34e49e2752ff3071659bd3f75accaf413 Mon Sep 17 00:00:00 2001 From: EclesioMeloJunior Date: Tue, 18 Aug 2026 14:10:40 -0400 Subject: [PATCH 14/14] chore: dockerize index backfill verification --- .../external-rpc-checks/docker-compose.yaml | 14 ++++++++-- scripts/tests/external-rpc-checks/setup.sh | 18 ++----------- scripts/tests/external-rpc-checks/verify.rb | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 18 deletions(-) create mode 100644 scripts/tests/external-rpc-checks/verify.rb diff --git a/scripts/tests/external-rpc-checks/docker-compose.yaml b/scripts/tests/external-rpc-checks/docker-compose.yaml index cec6aec6045..ad49d91976d 100644 --- a/scripts/tests/external-rpc-checks/docker-compose.yaml +++ b/scripts/tests/external-rpc-checks/docker-compose.yaml @@ -23,8 +23,6 @@ services: environment: - FOREST_PATH=/data - FOREST_CHAIN_INDEXER_ENABLED=1 - ports: - - "${FOREST_RPC_PORT}:${FOREST_RPC_PORT}" command: - --chain=calibnet - --encrypt-keystore=false @@ -48,6 +46,18 @@ services: start_period: 5m start_interval: 5s + # Probes the node for the epochs the checks are about to query. + verify: + image: ghcr.io/chainsafe/forest-rpc-checks:latest + depends_on: + forest: + condition: service_healthy + volumes: + - ./verify.rb:/verify.rb:ro + entrypoint: ["ruby", "/verify.rb"] + environment: + FOREST_RPC_URL: forest:${FOREST_RPC_PORT}/rpc/v1 + # The image entrypoints into `bundle exec ruby check_rpc.rb`, so only the epochs are appended. rpc-checks: image: ghcr.io/chainsafe/forest-rpc-checks:latest diff --git a/scripts/tests/external-rpc-checks/setup.sh b/scripts/tests/external-rpc-checks/setup.sh index eb19951572a..de63c8d5260 100755 --- a/scripts/tests/external-rpc-checks/setup.sh +++ b/scripts/tests/external-rpc-checks/setup.sh @@ -1,25 +1,12 @@ #!/usr/bin/env bash # Runs the RPC checks against the external data.riba.plus dataset. +# Needs docker only; everything else happens in containers. set -euo pipefail PARENT_PATH=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P) pushd "${PARENT_PATH}" -# Ports live in `.env`, which `docker compose` reads on its own. -# shellcheck source=.env -source ./.env -RPC_URL="http://127.0.0.1:${FOREST_RPC_PORT}/rpc/v1" - -# Confirms the back-fill indexed an epoch, which is what the checks query. -verify_epoch_indexed() { - jq -nc --arg epoch "$(printf '0x%x' "$1")" \ - '{jsonrpc:"2.0",id:1,method:"eth_getBlockByNumber",params:[$epoch,false]}' | - curl --silent --show-error --fail --retry 5 --connect-timeout 10 \ - --header 'Content-Type: application/json' --data @- "${RPC_URL}" | - jq -e '.result.number' > /dev/null -} - # This should not be needed in GH. It is useful for running locally. docker compose down --remove-orphans --volumes @@ -31,8 +18,7 @@ END=$((SNAPSHOT_EPOCH - 1)) docker compose up --detach --wait forest -verify_epoch_indexed "${START}" -verify_epoch_indexed "${END}" +docker compose run --rm verify "${START}" "${END}" docker compose run --rm rpc-checks "${START}" "${END}" diff --git a/scripts/tests/external-rpc-checks/verify.rb b/scripts/tests/external-rpc-checks/verify.rb new file mode 100644 index 00000000000..4dc1bbe7158 --- /dev/null +++ b/scripts/tests/external-rpc-checks/verify.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# Confirms the back-fill indexed the epochs the checks query. Runs in a container +# so the node's RPC port never has to be published. + +require 'json' +require 'net/http' + +url = URI("http://#{ENV.fetch('FOREST_RPC_URL')}") + +ARGV.each do |argument| + epoch = Integer(argument) + request = Net::HTTP::Post.new(url, 'Content-Type' => 'application/json') + request.body = { + jsonrpc: '2.0', + id: 1, + method: 'eth_getBlockByNumber', + params: ["0x#{epoch.to_s(16)}", false] + }.to_json + + response = Net::HTTP.start(url.hostname, url.port) { |http| http.request(request) } + body = JSON.parse(response.body) + abort "epoch #{epoch} is not indexed: #{response.code} #{response.body}" unless body.dig('result', 'number') + + puts "epoch #{epoch} is indexed" +end