What to watch on a node you run, and which readings are not what they look like.
The four readings that matter
| Reading | How | Healthy |
|---|---|---|
| Height | eth_blockNumber | Advancing, and within a few blocks of a public endpoint |
| Peers | net_peerCount | Non-zero and stable |
| Genesis | eth_getBlockByNumber("0x0") | 0xd86d57a899cbfa580669f0e1c7cd4ae5f525247c3c823f8f75df176a0c5d7f1a |
| Memory | jstat -gcutil <pid> | Old generation not pinned near 100%, full collections rare |
Readings that lie, and what to do instead
A node that does not answer is not a node reporting zero. While a node is catching up it may not answer RPC at all. A height or peer count read as zero in that moment measures nothing. Treat "no answer" as its own state, and when the node may be down, read the log rather than the RPC.
"It advanced once" is not "it is keeping up". A node can import one block from its own database and then sit frozen with no peers. A liveness check needs three things at once: peers non-zero, a minimum amount of growth, and both measured across the whole window rather than at its ends.
An empty block is not a stall. The chain produces a block every half second whether or not there are transactions, so most blocks carry none. Liveness is the height advancing, not the transaction count.
Comparing against a public endpoint fails when the endpoint is the one that is behind. If your reference is itself stuck, a comparison declares your healthy node unhealthy. When the reference is behind, judge by local progress instead.
Disk
Headers are roughly 95% of the database on this chain, and anchor headers are large. Measure growth as size divided by height over the life of the database, with du on the directory, not with df on the filesystem. A compacting database reports wildly different rates over short windows.
Logs
EIP-4788 beacon roots is active in genesis and the beacon roots contract has no code on this chain, so the system call fails on every block. It is a logging condition, not a consensus one. Filter that one message rather than silencing the class, so that real errors from the same component still reach you. A log that is permanently full of errors trains its reader to ignore it.