Ethras Blog
Ethereum Block Tags: Build a Consistent View with latest, safe, and finalized
Choose a block policy for Ethereum reads, anchor related requests to one block identity, and handle reorgs and unavailable history without silently changing the answer.
A dashboard loads an account balance, a transaction count, and a contract view. Every request succeeds, but the numbers do not describe the same moment. The calls each used latest, and a new block arrived between them.
For application developers, consistency starts with an explicit block policy. Decide how fresh the answer needs to be, which block it describes, and what happens when that block cannot be queried. A successful HTTP response alone does not answer those questions.
Choose freshness before writing the request
Ethereum exposes several block tags through its JSON-RPC block parameter. latest follows the node's current canonical head; safe and finalized request the corresponding consensus-informed views. eth_getBlockByNumber can resolve a tag into a header containing a number and hash.
The distinction has a protocol basis. The Engine API fork-choice structure carries separate head, safe, and finalized hashes. Its definition of safe explicitly depends on synchrony and honesty assumptions. These are different chain-progress signals, rather than three spellings for the newest block.
| Application need | Possible starting policy | Application responsibility |
|---|---|---|
| A frequently refreshed activity display | latest |
Mark recent results provisional and support replacement |
| A view that can accept some delay | safe |
Verify endpoint support and define how stale is acceptable |
| A workflow waiting for consensus finality | finalized |
Handle stalled progress and preserve its block identity |
The right choice depends on the operation. A preview and a durable accounting export may need different policies even inside one application.
Ethereum's proof-of-stake explanation describes finality through checkpoint votes and economic penalties for conflicting finalized history. It is a strong consensus property with assumptions. Avoid translating it into an unconditional promise about every RPC response, infrastructure failure, or exceptional network event. Also avoid a timer that declares finality after an assumed fixed number of seconds.
Resolve once, then anchor related reads
Start by obtaining the header for the chosen policy:
{
"jsonrpc": "2.0",
"id": 1,
"method": "eth_getBlockByNumber",
"params": ["finalized", false]
}
Validate that the response contains a usable header. Save its number and hash together with the network identity and retrieval time. Subsequent related reads should use that selected block, rather than resolving finalized again independently for every field.
A numeric height removes ordinary head drift, but a height alone does not uniquely identify a block through a reorganization. Two different blocks can occupy the same height at different times. For methods covered by EIP-1898, the block parameter can contain blockHash; requireCanonical: true additionally asks the responding node to reject a known noncanonical block. Confirm that the target endpoint implements this form before depending on it.
The following JavaScript is integration pseudocode. rpc is your validated JSON-RPC helper; the address is a synthetic example, not a customer account:
const header = await rpc('eth_getBlockByNumber', ['finalized', false]);
if (!header?.hash || !header?.number) throw new Error('Missing anchor');
const address = '0x0000000000000000000000000000000000000001';
const at = { blockHash: header.hash, requireCanonical: true };
const [balance, transactionCount] = await Promise.all([
rpc('eth_getBalance', [address, at]),
rpc('eth_getTransactionCount', [address, at]),
]);
const view = { blockNumber: header.number, blockHash: header.hash,
balance, transactionCount };
The hash gives the reads a shared block identity. The canonical check expresses a policy at request time; it does not freeze the network for the lifetime of the resulting page. Reject an incomplete group of reads instead of mixing successful values with a fallback from another block.
Make unavailable data a visible outcome
Specifying a block does not ensure that an endpoint retains its state. A historical header can be available while a state read at that header fails. Treat capability, retention, and finality as separate concerns when choosing infrastructure.
Define your fallback before an incident. For an exploratory UI, showing an explicit unavailable state with the anchor may be enough. For an export, fail the job and retry the same identity against an approved source with the necessary history. Substituting latest changes the question and can make an apparently complete export internally inconsistent.
If your endpoint only accepts numeric blocks for a method, preserve the selected hash, check the header around the read group, and document the weaker race handling. A before-and-after check is useful detection, but separate RPC calls do not create an atomic snapshot. Workflows needing strict identity should use a supported hash-based interface or another explicitly validated mechanism.
Test changes in block identity
Build fixtures around situations your application must distinguish: a tag advancing between calls, a missing historical state, a canonicality error, and a replaced block at an already observed height. Assert that a group is either returned with one anchor or rejected. Keep error classification separate from business values; an error must not become a zero balance.
Include the anchor in cache keys and diagnostic records. A cache entry identified only by account and latest is hard to explain after the head moves. Recording the actual block hash makes a result traceable and gives the application a concrete item to invalidate when its canonical-chain view changes.
For request syntax, review the block method documentation. Then write down the application's freshness and recovery policy beside the code that chooses the anchor. That policy is what makes several reads a coherent view.
Share this article
Share on XReview your account and post on X. Opening X or copying text does not send a post.