Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
2087ba4
chore: include `ribasushi-rpc-checks` to test forest against external…
EclesioMeloJunior Aug 11, 2026
39db12b
chore: update CHANGELOG.md
EclesioMeloJunior Aug 11, 2026
170db74
chore: introduce validation and timeouts
EclesioMeloJunior Aug 11, 2026
23532b3
Merge branch 'main' of github.com:ChainSafe/forest into forest-rpc-ch…
EclesioMeloJunior Aug 11, 2026
6eceded
chore: includes `prepare.sh` and `ribasushi-rpc-checks.yaml`
EclesioMeloJunior Aug 12, 2026
2b16fd1
add `Ribasushi` to .dict
EclesioMeloJunior Aug 12, 2026
71dc887
chore: change to `data.riba.plus`
EclesioMeloJunior Aug 13, 2026
3a0a918
chore: removing some missing refs to `Ribasushi`
EclesioMeloJunior Aug 13, 2026
aea766b
chore: address comments, making `./setup.sh`
EclesioMeloJunior Aug 14, 2026
4027d12
Merge branch 'main' into forest-rpc-checks
EclesioMeloJunior Aug 14, 2026
f0aef7a
chore: address yaml lint problem
EclesioMeloJunior Aug 14, 2026
55bd2b2
Merge branch 'forest-rpc-checks' of github.com:ChainSafe/forest into …
EclesioMeloJunior Aug 14, 2026
15632ce
chore: address rabbit comments
EclesioMeloJunior Aug 14, 2026
5d7382b
chore: transform resolve->download->backfill into a init container
EclesioMeloJunior Aug 17, 2026
5bf2c2b
Merge branch 'main' into forest-rpc-checks
EclesioMeloJunior Aug 17, 2026
4b85d55
Merge branch 'main' into forest-rpc-checks
EclesioMeloJunior Aug 17, 2026
295979e
chore: address comments
EclesioMeloJunior Aug 18, 2026
6eacd4e
Merge branch 'main' into forest-rpc-checks
EclesioMeloJunior Aug 18, 2026
be73015
Merge branch 'forest-rpc-checks' of github.com:ChainSafe/forest into …
EclesioMeloJunior Aug 18, 2026
f36aecb
fix RPC verification routine
EclesioMeloJunior Aug 18, 2026
b9f2c65
Merge branch 'main' into forest-rpc-checks
EclesioMeloJunior Aug 18, 2026
4c718cc
chore: dockerize index backfill verification
EclesioMeloJunior Aug 18, 2026
11dfefa
Merge branch 'forest-rpc-checks' of github.com:ChainSafe/forest into …
EclesioMeloJunior Aug 18, 2026
8da371b
Merge branch 'main' into forest-rpc-checks
EclesioMeloJunior Aug 18, 2026
6b9724b
Merge branch 'main' into forest-rpc-checks
EclesioMeloJunior Aug 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
title: "[automated] External dataset RPC checks failure"
labels: ["Bug"]
---

## Description

Latest RPC checks against the external `data.riba.plus` dataset failed. Please [check the logs]({{ env.WORKFLOW_URL }}) for more information.
40 changes: 40 additions & 0 deletions .github/workflows/external-rpc-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: data.riba.plus RPC checks
permissions:
contents: read
issues: write

concurrency:
group: ${{ github.workflow }}

on:
workflow_dispatch:
Comment thread
EclesioMeloJunior marked this conversation as resolved.
schedule:
- cron: "0 13 * * *" # Runs every day at 13:00 UTC

jobs:
rpc-checks:
name: RPC checks against the data.riba.plus dataset
runs-on: warp-ubuntu-2404-x64-8x
defaults:
run:
working-directory: scripts/tests/external-rpc-checks
steps:
- name: Checkout Sources
uses: actions/checkout@v7
- name: Run the RPC checks
run: ./setup.sh
- 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/EXTERNAL_RPC_CHECKS_ISSUE_TEMPLATE.md
6 changes: 6 additions & 0 deletions docs/docs/developers/guides/rpc_api_compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ 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 `data.riba.plus` dataset of recorded
RPC responses, see `.github/workflows/external-rpc-checks.yml` and
`scripts/tests/external-rpc-checks`.

## Gateway

The `lotus-gateway` executable is a reverse-proxy that sanitizes RPC calls
Expand Down
2 changes: 2 additions & 0 deletions scripts/tests/external-rpc-checks/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FOREST_RPC_PORT=2345
FOREST_HEALTHZ_RPC_PORT=2346
68 changes: 68 additions & 0 deletions scripts/tests/external-rpc-checks/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: external-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:
# Resolves yesterday's snapshot, imports it and back-fills the chain index.
init:
<<: *forest-service
Comment thread
EclesioMeloJunior marked this conversation as resolved.
volumes:
- node-data:/data
- ./init.sh:/init.sh:ro
entrypoint: ["/bin/bash", "/init.sh"]

forest:
<<: *forest-service
environment:
- FOREST_PATH=/data
- FOREST_CHAIN_INDEXER_ENABLED=1
command:
- --chain=calibnet
- --encrypt-keystore=false
- --no-gc
- --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:
[
"CMD",
"forest-cli",
"healthcheck",
"live",
"--healthcheck-port",
"${FOREST_HEALTHZ_RPC_PORT}",
]
interval: 30s
timeout: 10s
retries: 3
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
environment:
FOREST_RPC_URL: forest:${FOREST_RPC_PORT}/rpc/v1

volumes:
node-data:
30 changes: 30 additions & 0 deletions scripts/tests/external-rpc-checks/init.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/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.

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 --import-snapshot="${url}" --halt-after-import

# Indexes the 1000 epochs below the snapshot head, inclusive on both ends
forest-tool index backfill --chain=calibnet --from="${epoch}" --to="$((epoch - 1000))"
25 changes: 25 additions & 0 deletions scripts/tests/external-rpc-checks/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/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}"

# This should not be needed in GH. It is useful for running locally.
docker compose down --remove-orphans --volumes

# 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))

docker compose up --detach --wait forest

docker compose run --rm verify "${START}" "${END}"

docker compose run --rm rpc-checks "${START}" "${END}"

popd
26 changes: 26 additions & 0 deletions scripts/tests/external-rpc-checks/verify.rb
Comment thread
EclesioMeloJunior marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/tool/offline_server/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ pub async fn start_offline_server(
}

// Validate tipsets since the {height} EPOCH when `height >= 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 {
Expand Down
Loading