Correctness is a ceiling
≤ 89
Below 99% correct, the composite is capped no matter how fast the provider is. A node returning wrong data cannot be presented as healthy.
Reference
Every rule below is the rule the running system actually applies, and every weight is read live from the settings the scorer uses — not from this page's prose.
Correctness is a ceiling
≤ 89
Below 99% correct, the composite is capped no matter how fast the provider is. A node returning wrong data cannot be presented as healthy.
Below this, no score
30
samples in the window. Under it a provider is reported as insufficient data rather than given a number the sample count cannot support.
Every published figure traces to a collected observation. Where a measurement does not exist, the interface says so rather than substituting a zero. “Never measured” and “measured as zero” are opposite facts and are never rendered the same way.
A rate limit, an unsupported method and a TLS failure are three entirely different facts about a provider. Collapsing them into one error count destroys the ability to say anything useful, so each is recorded and treated separately.
Failures caused by our own measurement software are recorded in their own categories and excluded from provider scoring entirely. A defect in RPCNode.IO must never appear as a provider failing.
Latency percentiles are computed in the database from mergeable t-digest states, so a 30-day p95 is a genuine merge of daily states rather than an average of averages. Averaging percentiles is a common shortcut that produces plausible, false numbers.
Read live from the running configuration. If a weight or a threshold changes, this table changes with it.
| Setting | Value |
|---|---|
| benchmark.cold_interval_s | 900 |
| benchmark.default_interval_s | 60 |
| benchmark.default_jitter_pct | 20 |
| capability.confirm_threshold | 3 |
| freshness.cohort_min_providers | 2 |
| freshness.cohort_window_seconds | 90 |
| freshness.max_ahead_seconds | 60 |
| freshness.stale_behind_seconds | 30 |
| score.min_samples | 30 |
| score.weight.availability | 0.4 |
| score.weight.correctness | 0.2 |
| score.weight.freshness | 0.1 |
| score.weight.latency | 0.3 |
| score.winsorize_pct | 5 |
| stake.min_epochs | 1 |
| stake.weight.commission_stability | 0.15 |
| stake.weight.decentralisation | 0.15 |
| stake.weight.performance | 0.35 |
| stake.weight.yield | 0.35 |
| status.degraded_success_rate | 0.95 |
| status.hysteresis_windows | 3 |
| status.offline_success_rate | 0.5 |
Every observation is stored with the version of the scenario that produced it. A figure computed under version 1 and one computed under version 2 are not the same measurement, and this is how a consumer of the API can tell — the version is the contract, not the method name.
| Method | Family | Scenario | Version |
|---|---|---|---|
| eth_blockNumber | evm | evm.block_number | 1 |
| eth_call | evm | evm.call_erc20_balance | 1 |
| eth_chainId | evm | evm.chain_id | 1 |
| eth_estimateGas | evm | evm.estimate_gas | 1 |
| eth_feeHistory | evm | evm.fee_history | 1 |
| eth_gasPrice | evm | evm.gas_price | 1 |
| eth_getBalance | evm | evm.get_balance | 1 |
| eth_getBlockByNumber | evm | evm.get_block_by_number | 1 |
| eth_getLogs | evm | evm.get_logs | 1 |
| eth_getTransactionCount | evm | evm.get_transaction_count | 1 |
| eth_getTransactionReceipt | evm | evm.get_transaction_receipt | 1 |
| eth_maxPriorityFeePerGas | evm | evm.priority_fee | 1 |
| eth_syncing | evm | evm.syncing | 1 |
| net_version | evm | evm.net_version | 1 |
| web3_clientVersion | evm | evm.client_version | 1 |
| eth_subscribe.newHeads | evm | evm.ws_new_heads | 1 |
| geyser.subscribeBlocksMeta | solana | sol.geyser_blocks_meta | 1 |
| geyser.subscribeSlots | solana | sol.geyser_slots | 1 |
| geyser.subscribeTransactions | solana | sol.geyser_transactions | 1 |
| getAccountInfo | solana | sol.account_info | 1 |
| getBalance | solana | sol.balance | 1 |
| getBlock | solana | sol.block | 1 |
| getBlockHeight | solana | sol.block_height | 1 |
| getClusterNodes | solana | sol.cluster_nodes | 1 |
| getEpochInfo | solana | sol.epoch_info | 1 |
| getHealth | solana | sol.health | 1 |
| getLatestBlockhash | solana | sol.latest_blockhash | 1 |
| getMultipleAccounts | solana | sol.multiple_accounts | 1 |
| getProgramAccounts | solana | sol.program_accounts | 1 |
| getSignaturesForAddress | solana | sol.signatures_for_address | 1 |
| getSlot | solana | sol.slot | 1 |
| getSupply | solana | sol.supply | 1 |
| getTransaction | solana | sol.transaction | 1 |
| getVersion | solana | sol.version | 1 |
| getVoteAccounts | solana | sol.vote_accounts | 1 |
| logsSubscribe | solana | sol.ws_logs | 1 |
| slotSubscribe | solana | sol.ws_slot | 1 |
The three chains differ, and the site does not paper over it.
What can be derived for Ethereum is the execution address in the withdrawal credentials, and from it two things: how many other validators withdraw to the same address, and — where that address is documented by the operator itself — who the operator is.
The first covers every validator and is what makes staking concentration visible. The second is deliberately rare. A mapping is added only when it is verifiable from the operator's own documentation or from a contract whose identity is not in dispute. An explorer's name tag is not enough: those are crowd-sourced, and attaching one here would publish a claim about who controls a large share of the stake on the basis of a wiki edit. Where nothing is published, the site says so and shows the address.