codepythonmit
rpcprobe
which public ethereum rpcs answer today, and what they let you do: log ranges, archive state, batches, the blob fee, eth_config. the fallback lists in the other scripts come from it.
head 72929452026-09-1522 files330 lines of python
record
- commits
- 13 on main, 2026-09-10 to 2026-09-15
- signed
- 5 good signature from 17BA…F3AB
5 from the daily data workflow no signature
3 from dependabot signed by github's key B5690EEEBB952194 for dependabot, not checked here - tests
- 8 passed 2 skipped
python 3.11.9 on my machine, 2026-09-23 - ci
- ci.yml lists python 3.10, 3.12, 3.13, 3.14.
- history
- every commit up to 7292945, each with its signed record. the history itself stays off this site, so from here you cannot tie a file to a commit.
- source
- rpcprobe.tar.gz, 14.5 KB · sha256
2aab885c6410…: the files at 7292945, without README.md, which is shown below. - license
- mit (LICENSE)
readme
rendered from README.md at 7292945. i left out the badges, which load from github and shields.io, and one link, which shows as plain text. the install lines point at github, which answers 404 for this account at the moment.
rpcprobe
which public ethereum rpcs answer today, and what they let you do. every endpoint that is listed
somewhere as "no key needed" gets the same dozen small json-rpc requests: the chain id, five pings,
log queries of growing width, one archive read, a batch, 1,024 blocks of fee history, the blob
fee, eth_config, and whether a web page may read the answer. a table, json, and a dataset that
fills itself every night.
$ rpcprobe
public ethereum rpcs, checked 2026-09-10 14:21 utc from this machine: 14 of 21 answer, head 25,947,555
endpoint p50 ms lag logs archive batch receipts fee hist blob fee eth_config browser client
tenderly-gw 238 1 10,000 yes yes yes 1024 yes no yes tenderly 1.0
nownodes 246 0 10 no yes yes 1024 yes yes yes geth 1.17.5
publicnode 246 0 50 no yes yes 1024 yes yes yes geth 1.17.1
tenderly 250 1 10,000 yes yes yes 1024 yes no yes tenderly 1.0
drpc 253 0 10,000 yes yes yes 1024 no no yes geth 10.0.0
nodies 273 1 50 yes yes yes 1024 yes yes yes reth 2.4.1
blockrazor 311 0 10 no yes yes 1024 yes yes yes geth 1.17.5
blastapi 343 0 10 yes yes no 1024 no no yes reth 2.4.0
mevblocker 348 0 10,000 yes yes yes 1024 yes no yes mevblocker
merkle 358 ? - no no no - no no yes
zan 361 0 - no yes no 1024 yes no yes geth 1.17.5
1rpc 370 1 - no yes no 1 yes yes yes geth 1.16.7
meowrpc 401 0 - no no yes 1024 no no yes
flashbots 550 0 10,000 no yes yes 1024 yes no yes reth 1.11.2
no answer, or not without an account:
onfinality http 429
blockpi http 521
cloudflare Cannot fulfill request
llamarpc http 525
ankr Unauthorized: You must authenticate your request with an API key.
omniatech http 521
gatewayfm http 503
p50: median of five eth_blockNumber round trips from here. lag: blocks behind the highest head, read at one moment.
logs: the widest range of blocks answered for one address and one event (tried 10, 50, 100, 1,000, 10,000).
archive: an address's balance at block 1,000,000. receipts: eth_getBlockReceipts for one block.
browser: the answer carries access-control-allow-origin.
fourteen of twenty-one at 14:21 utc on 10 september, twelve half an hour earlier: nownodes and merkle come and go. every script i publish carries a fallback list of endpoints, and this is how that list gets made: tenderly, drpc and mevblocker answer a 10,000 block log query and know old state; publicnode is among the fastest but stops at 50 blocks of logs and keeps no archive; ankr wants an account now; cloudflare's and llamarpc's endpoints, still in a lot of tutorials, did not answer at all.
install
pipx install git+https://github.com/alinaschanz/rpcprobe
or clone it and run python -m rpcprobe from the folder. python 3.10 or newer, no dependencies.
use
rpcprobe # every built-in endpoint, three at a time, about a minute
rpcprobe --only publicnode,drpc,tenderly # just these (--list shows the names)
rpcprobe --rpc http://localhost:8545 # your own node next to them
rpcprobe --json # everything, including why a check said no
rpcprobe --csv today.csv
rpcprobe --summary-append data/daily.csv --quiet # one row per endpoint per day, the dataset
what is checked
| check | request | why it matters |
|---|---|---|
| mainnet | eth_chainId, with an Origin header |
a url that serves another chain is worse than one that is down |
| p50 | five eth_blockNumber round trips, the median |
the fast path of every script |
| lag | one eth_blockNumber to all working endpoints at the same moment |
a node minutes behind answers happily with old data |
| logs | eth_getLogs for the uniswap v3 factory's PoolCreated, 10 / 50 / 100 / 1,000 / 10,000 blocks |
the event is rare, so a refusal is about the width of the range, never about the number of results |
| archive | eth_getBalance of an address at block 1,000,000 |
only an archive node still has that state |
| batch | two calls in one request | fewer round trips for anything that reads many blocks |
| receipts | eth_getBlockReceipts for one recent block |
every receipt of a block in one call: blobwatch --receipts lives on it |
| fee hist | eth_feeHistory for 1,024 blocks, how many came back |
gasweek pages through a week this way |
| blob fee | eth_blobBaseFee |
the fee for blob space, eip-4844 |
| eth_config | eth_config, eip-7910 |
the fork parameters, blob target and max included, straight from the node |
| browser | access-control-allow-origin on the first answer |
whether a web page may use the endpoint at all |
| client | web3_clientVersion, shortened |
some gateways report a made-up one |
each endpoint gets about fifteen small requests, three endpoints at a time. when a check says no, the
json says why, in the endpoint's own words (Block range too large: maximum allowed is 50 blocks,
historical state is not available).
reading the table
- latency is from wherever this runs. from a laptop in berlin it is not what a server in virginia sees; compare endpoints within one run, and days within the dataset.
logs 50is not "broken": most scripts ask for 25 to 50 blocks at a time on a public node anyway.-means even 10 blocks were refused, usually a rate limit on that method.- a rate limit (429) today can be fine tomorrow. the dataset is there so that one bad hour does not decide anything.
the dataset
data/daily.csv gets one row per endpoint every night at 00:53 utc, from a github runner, so the
latency column compares from day to day. columns are in data/README.md; a rerun on
the same day replaces the day.
exit codes and scripting
0 when at least one endpoint answered, 2 when none did or a flag was wrong. rpcprobe --json | jq '.endpoints[] | select(.ok and .archive and .logs_range >= 1000) | .url' is a fallback list for a
script that reads history.
see also
- blobwatch, gasweek, bigmoves: the scripts that live on these endpoints
- the notes: alinaschanz.life, the short version on x
verify a release
every release carries the sdist and the wheel, a SHA256SUMS file, an opentimestamps proof of that
file, and a build provenance attestation made in github's own signing flow. with the files downloaded
into one folder:
sha256sum -c SHA256SUMS
gh attestation verify ./*.whl --owner alinaschanz
ots verify SHA256SUMS.ots
the commit itself is signed.
license
mit. the endpoints belong to the people who run them; this only asks them simple questions.
files
| path | lines | bytes |
|---|---|---|
.editorconfig# | 15 | 233 |
.github/dependabot.yml# | 8 | 237 |
.github/workflows/ci.yml# | 47 | 1,217 |
.github/workflows/daily.yml# | 29 | 858 |
.github/workflows/pypi.yml# | 30 | 1,099 |
.github/workflows/release.yml# | 43 | 1,522 |
.github/workflows/scorecard.yml# | 33 | 889 |
.gitignore# | 8 | 79 |
.pre-commit-config.yaml# | 13 | 321 |
CHANGELOG.md# | 17 | 820 |
LICENSE# | 21 | 1,069 |
README.md# | shown above | |
data/README.md# | 21 | 1,388 |
data/daily.csv# | 106 | 9,661 |
pyproject.toml# | 42 | 1,180 |
rpcprobe/__init__.py# | 3 | 90 |
rpcprobe/__main__.py# | 3 | 48 |
rpcprobe/cli.py# | 125 | 6,741 |
rpcprobe/endpoints.py# | 52 | 2,331 |
rpcprobe/probe.py# | 203 | 8,292 |
tests/test_live.py# | 33 | 1,536 |
tests/test_offline.py# | 143 | 7,482 |
history
the latest 10 commits. all 13, with the signature of each: the commit list.