Config
Integrity

Integrity & Empty Data

AIOpen as plain markdown for AI

RPC nodes may return stale, empty, or structurally invalid data — especially near the tip of the chain. eRPC's integrity layer enforces block tracking, validates response structure, and handles empty or unavailable results by retrying across upstreams automatically. All controls live in directiveDefaults on the network (or networkDefaults) and in failsafe[].retry for empty-result tuning.

Combine with retry and consensus policies for automatic failover when integrity checks fail.

You can configure:

  • Block trackingenforceHighestBlock, enforceGetLogsBlockRange, enforceNonNullTaggedBlocks
  • Transaction and header validationvalidateTransactionsRoot, validateTransactionFields, validateTransactionBlockInfo, validateHeaderFieldLengths
  • Log and receipt validationvalidateLogFields, validateLogsBloomEmptiness, validateLogsBloomMatch, enforceLogIndexStrictIncrements, validateTxHashUniqueness, validateTransactionIndex
  • Receipt cross-checksvalidateReceiptTransactionMatch, validateContractCreation, receiptsCountExact, receiptsCountAtLeast, validationExpectedBlockHash, validationExpectedBlockNumber
  • Empty/missing result handlingretry.emptyResultAccept, emptyResultConfidence, emptyResultMaxAttempts, emptyResultDelay, blockUnavailableDelay
  • Per-network empty-as-error promotionevm.markEmptyAsErrorMethods

Minimum useful config

Enable the three most common block-tracking directives and configure empty-result retry behavior:

projectsnetworks[]directiveDefaults
erpc.yaml
projects:  - id: main    networks:      - architecture: evm        evm:          chainId: 1        directiveDefaults:          enforceHighestBlock: true          # serve the freshest latest/finalized across all upstreams          enforceGetLogsBlockRange: true     # skip upstreams that don't have the requested range          enforceNonNullTaggedBlocks: true   # treat null for tagged blocks as an error          retryEmpty: true                   # retry empty responses on other upstreams

Response validation for indexers

Validation directives are disabled by default (they add JSON-parsing overhead). Enable them for high-integrity workloads like indexing, where structurally corrupt responses must be rejected and retried:

projectsnetworks[]directiveDefaults
erpc.yaml
projects:  - id: main    networks:      - architecture: evm        evm:          chainId: 1        directiveDefaults:          validateLogsBloomEmptiness: true     # logs exist iff bloom is non-zero          validateLogsBloomMatch: true         # recompute bloom from logs and verify          enforceLogIndexStrictIncrements: true          validateTxHashUniqueness: true          validateTransactionIndex: true        # Recommended for indexers: hedge + consensus + retry        # Invalid responses are rejected, valid ones are compared, retries if all fail        failsafe:          - matchMethod: eth_getBlockReceipts            hedge:              maxCount: 3              delay: 100ms            consensus:              maxParticipants: 3              agreementThreshold: 2            retry:              maxAttempts: 5

Empty-result retry tuning (chain-specific)

This example tunes empty-result handling for Polygon (2.3 s blocks). blockUnavailableDelay and emptyResultDelay only fire when relevant — for finalized blocks the delay is skipped automatically, so one retry policy works across all finalities.

projectsnetworks[]failsafe[]
erpc.yaml
projects:  - id: main    networks:      - architecture: evm        evm:          chainId: 137        directiveDefaults:          retryEmpty: true        failsafe:          # Unfinalized logs/receipts: consensus guards against reorg splits          - matchMethod: eth_getLogs|eth_getBlockReceipts            matchFinality: [unfinalized, unknown]            retry: &retry-polygon              maxAttempts: 6              delay: 0ms              blockUnavailableDelay: 1s              emptyResultDelay: 500ms              emptyResultMaxAttempts: 3              emptyResultConfidence: blockHead              emptyResultAccept: [eth_getLogs]            consensus:              maxParticipants: 3              agreementThreshold: 2          # All other logs/receipts          - matchMethod: eth_getLogs|eth_getBlockReceipts            retry: *retry-polygon          # eth_call: empty is a valid contract return          - matchMethod: eth_call            retry:              <<: *retry-polygon              emptyResultAccept: [eth_call]          # Everything else          - matchMethod: '*'            retry:              <<: *retry-polygon              emptyResultMaxAttempts: 2
Copy for your AI assistant — full integrity & empty-data referenceExpand for every option, default, and edge case — or copy this entire section into your AI assistant.

Block tracking directives (directiveDefaults)

DirectiveDefaultNotes
enforceHighestBlocktrueTrack the highest block seen across all upstreams. For eth_blockNumber, replaces stale values with the known highest. For eth_getBlockByNumber("latest"|"finalized"), retries on other upstreams when the response is behind the known tip. Metrics: erpc_upstream_stale_latest_block_total, erpc_upstream_stale_finalized_block_total.
enforceGetLogsBlockRangetrueBefore sending eth_getLogs, trace_filter, or arbtrace_filter, verify the upstream has the requested block range. Skips upstreams whose toBlock exceeds their latest, or whose fromBlock is below their available window (maxAvailableRecentBlocks). Forces a fresh poll if the upstream's latest is stale before deciding. Metrics: erpc_upstream_evm_get_logs_stale_upper_bound_total, erpc_upstream_evm_get_logs_stale_lower_bound_total.
enforceNonNullTaggedBlockstrueConvert null responses to errors for eth_getBlockByNumber when called with "latest", "pending", "safe", "finalized", or "earliest". Numeric block requests are always errors when null regardless of this setting. Set false for chains that legitimately return null for some tags (e.g. zkSync).

Transaction and header validation directives

All disabled by default. Enable for high-integrity use-cases where the overhead of JSON parsing is acceptable.

DirectiveHeaderNotes
validateTransactionsRootX-ERPC-Validate-Transactions-RootRecompute the transactions root from the block's transaction list and verify it matches the header. Catches truncated or reordered tx lists.
validateTransactionFieldsX-ERPC-Validate-Transaction-FieldsValidate field formats (hex lengths, required keys) for each transaction in a block. Rejects responses with malformed fields.
validateTransactionBlockInfoX-ERPC-Validate-Transaction-Block-InfoVerify that each transaction's embedded blockHash and blockNumber match the containing block. Catches upstreams returning cross-contaminated responses.
validateHeaderFieldLengthsX-ERPC-Validate-Header-Field-LengthsCheck byte lengths of block header fields (hash, parentHash, etc.). Rejects headers with truncated or padded values.

Log and receipt validation directives

DirectiveHeaderNotes
validateLogFieldsX-ERPC-Validate-Log-FieldsValidate log address and topic lengths. Rejects logs with malformed addresses (not 20 bytes) or topics (not 32 bytes).
validateLogsBloomEmptinessX-ERPC-Validate-Logs-Bloom-EmptinessConsistency check: if logs are present the bloom must be non-zero; if logs are absent the bloom must be zero. Catches upstreams that zero out the bloom without zeroing the logs.
validateLogsBloomMatchX-ERPC-Validate-Logs-Bloom-MatchRecompute the bloom filter from the actual logs and compare to the header. Most expensive validation — only enable when you need to guarantee bloom correctness (e.g. indexers that rely on bloom-based filtering).
enforceLogIndexStrictIncrementsX-ERPC-Enforce-Log-Index-Strict-IncrementsLog indices must increment by exactly 1 across all receipts in a block response. Catches upstreams that return receipts with gaps or duplicated log indices.
validateTxHashUniquenessX-ERPC-Validate-Tx-Hash-UniquenessNo transaction hash may appear more than once in a block's receipt set. Catches duplicate-receipt bugs in some upstreams.
validateTransactionIndexX-ERPC-Validate-Transaction-IndexReceipt transaction indices must be sequential starting from 0. Rejects responses with out-of-order or missing indices.

Receipt cross-check directives

DirectiveHeaderNotes
validateReceiptTransactionMatchX-ERPC-Validate-Receipt-Transaction-MatchCross-validate that each receipt's fields match the corresponding transaction (e.g. to, from, contractAddress). Requires the ground-truth transactions to be available (library/indexer mode).
validateContractCreationX-ERPC-Validate-Contract-CreationVerify that contractAddress is populated when to is null (contract creation tx) and absent otherwise.
receiptsCountExactX-ERPC-Receipts-Count-ExactInteger. The eth_getBlockReceipts response must contain exactly this many receipts. Useful when the caller knows the transaction count from the block header.
receiptsCountAtLeastX-ERPC-Receipts-Count-At-LeastInteger. The response must contain at least this many receipts. Softer than receiptsCountExact — allows appended receipts (e.g. from internal transactions on some chains).
validationExpectedBlockHashX-ERPC-Validation-Expected-Block-HashHex string. All receipts in the response must carry this block hash. Rejects responses that mix receipts from different blocks.
validationExpectedBlockNumberX-ERPC-Validation-Expected-Block-NumberHex or decimal. All receipts must carry this block number. Same purpose as validationExpectedBlockHash but keyed by number.

Empty/missing data handling — failsafe[].retry fields

FieldDefaultNotes
emptyResultAccept["eth_getLogs", "eth_call"]Methods where an empty response is valid data — never retry on empty for these. eth_getLogs returns [] for blocks with no matching logs. eth_call returns 0x for calls that return nothing.
emptyResultConfidenceblockHeadWhen to trust an empty result: blockHead (block ≤ upstream's latest) or finalizedBlock (block ≤ finalized). finalizedBlock is more conservative: empties below the finalized horizon are trusted; empties in unfinalized range are retried.
emptyResultMaxAttempts= maxAttemptsSeparate attempt cap for empty-result retries. Set lower than maxAttempts when you want aggressive retries for errors but lighter retries for empties.
emptyResultDelay= delayFixed delay between empty-result retries. Overrides the normal delay for this case. Set to roughly one block time divided by your emptyResultMaxAttempts.
blockUnavailableDelaydynamicFallback delay when ALL upstreams lack the requested block. When the network's dynamic block-time estimate is available, the delay is derived automatically as blockTime × blockUnavailableDelayMultiplier (default 0.8). This static value is only used during warmup (first few seconds). Tune the multiplier via evm.blockUnavailableDelayMultiplier on the network.

Choosing emptyResultDelay by chain:

ChainBlock timeemptyResultDelayemptyResultMaxAttempts
Ethereum12 s2000ms3
Polygon2.3 s500ms5-6
Base / Optimism2 s500ms4-5
Arbitrum250 ms200ms5
Monad500 ms200ms3

directiveDefaults.retryEmpty

FieldDefaultNotes
retryEmptytrueWhen true, eRPC treats empty responses (null, [], 0x) from non-emptyResultAccept methods as retryable — the request is replayed on other upstreams. When false, the first empty response is returned as-is. Disable only if your application explicitly handles empty responses itself.

evm.markEmptyAsErrorMethods (per-network)

Promote empty responses to hard errors (not just retryable empties) for specific methods. Retried AND counted as upstream errors (affects scoring and circuit breaker):

networks:
  - architecture: evm
    evm:
      chainId: 1
      markEmptyAsErrorMethods:
        - eth_getTransactionReceipt   # empty means tx missing, not pending on this chain

eRPC ships with sensible defaults that cover common point-lookup methods (eth_getBlockByNumber, eth_getBlockReceipts, eth_getTransactionByHash, traces, etc.). Only add entries when an empty response on that method specifically means the upstream lacks the data rather than "not found."

eth_getBlockByHash and eth_getTransactionReceipt are intentionally excluded from defaults: subgraph upstreams commonly return null for eth_getBlockByHash, and eth_getTransactionReceipt returns null for pending transactions.

Per-upstream integrity — eth_getBlockReceipts checks

Two per-upstream flags gate which checks run locally on that upstream's responses (in addition to whatever directiveDefaults enable network-wide):

FieldPathNotes
checkLogIndexStrictIncrementsupstreams[].evmUpstream-local equivalent of enforceLogIndexStrictIncrements. Marks the upstream unhealthy when its receipts fail this check, without requiring the network-wide directive.
checkLogsBloomupstreams[].evm.integrity.eth_getBlockReceiptsUpstream-local bloom check for eth_getBlockReceipts responses: recomputes the bloom union of all receipt logs and verifies it matches the block header's logsBloom. Catches missing logs in the receipts response. Marks the upstream unhealthy on mismatch, without requiring the network-wide directive.

checkLogsBloom vs validateLogsBloomMatch — two different checks at two different layers:

  • evm.integrity.eth_getBlockReceipts.checkLogsBloom (upstream level) — checks that the bloom union computed from the receipts' logs equals the block header's logsBloom. Fires on eth_getBlockReceipts responses only. Catches upstreams that return receipts with missing or truncated log entries.
  • directiveDefaults.validateLogsBloomMatch (network level) — checks that the addresses and topics in eth_getLogs results are consistent with the block headers' bloom filters. Fires on eth_getLogs responses. Catches upstreams that drop log entries from a getLogs response without reflecting that in the bloom.

Deprecated: evm.integrity block

The old network.evm.integrity block is deprecated. It is still accepted but emits a deprecation warning. Migrate to directiveDefaults:

# Old (deprecated — still works):
networks:
  - architecture: evm
    evm:
      chainId: 1
      integrity:
        enforceHighestBlock: true
        enforceGetLogsBlockRange: true
        enforceNonNullTaggedBlocks: true
 
# New (use this):
networks:
  - architecture: evm
    evm: { chainId: 1 }
    directiveDefaults:
      enforceHighestBlock: true
      enforceGetLogsBlockRange: true
      enforceNonNullTaggedBlocks: true

The directiveDefaults form is a superset — it exposes all transaction, log, and receipt validation fields that the old integrity block never covered. See Networks for the full directiveDefaults reference.

Common pitfalls

  • Validation directives are off by default — they add JSON-parsing overhead on every response. Enable only what you actually need. For most non-indexer use-cases, the block-tracking directives (enforceHighestBlock, enforceGetLogsBlockRange) are sufficient.
  • validateLogsBloomMatch is the most expensive — it recomputes the bloom filter from every log in the response. Enable only on eth_getBlockReceipts or similar receipt-heavy methods, not network-wide.
  • emptyResultAccept vs markEmptyAsErrorMethodsemptyResultAccept says "empty is valid, don't retry." markEmptyAsErrorMethods says "empty is an error, retry AND penalize the upstream." They are opposites. Adding a method to both has undefined behavior; pick one.
  • emptyResultConfidence: finalizedBlock is more conservative — empties on unfinalized blocks are still retried even for methods in emptyResultAccept. Use blockHead (default) unless you specifically need finalized-only trust.
  • blockUnavailableDelay static value is rarely needed — the dynamic block-time estimate takes over within the first few seconds. Only set it if your use-case requires a specific floor during the warmup window.
  • enforceNonNullTaggedBlocks: false for zkSync-style chains — some L2s legitimately return null for certain block tags (e.g. "pending" on chains without a mempool). Disable only for those specific networks.
  • Using deprecated evm.integrity with new directiveDefaults — if both are present, directiveDefaults takes precedence and the integrity block is ignored. Migrate fully.

Append .llms.txt to this URL (or use the AI link above) to fetch the entire expanded reference as plain markdown for an AI assistant.