# Integrity & Empty Data > Source: https://docs.erpc.cloud/config/failsafe/integrity > Integrity directives enforce block tracking, response validation, and empty/missing-data handling. Configure via directiveDefaults on networks or per-request headers. > Format: machine-readable markdown export of the docs page above. > All collapsible AI sections are inlined and fully expanded. # Integrity & Empty Data 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. > **TIP** > Combine with [retry](/config/failsafe/retry.llms.txt) and [consensus](/config/failsafe/consensus.llms.txt) policies for automatic failover when integrity checks fail. **You can configure:** - **Block tracking** — `enforceHighestBlock`, `enforceGetLogsBlockRange`, `enforceNonNullTaggedBlocks` - **Transaction and header validation** — `validateTransactionsRoot`, `validateTransactionFields`, `validateTransactionBlockInfo`, `validateHeaderFieldLengths` - **Log and receipt validation** — `validateLogFields`, `validateLogsBloomEmptiness`, `validateLogsBloomMatch`, `enforceLogIndexStrictIncrements`, `validateTxHashUniqueness`, `validateTransactionIndex` - **Receipt cross-checks** — `validateReceiptTransactionMatch`, `validateContractCreation`, `receiptsCountExact`, `receiptsCountAtLeast`, `validationExpectedBlockHash`, `validationExpectedBlockNumber` - **Empty/missing result handling** — `retry.emptyResultAccept`, `emptyResultConfidence`, `emptyResultMaxAttempts`, `emptyResultDelay`, `blockUnavailableDelay` - **Per-network empty-as-error promotion** — `evm.markEmptyAsErrorMethods` ## Minimum useful config Enable the three most common block-tracking directives and configure empty-result retry behavior: **Config path:** `projects > networks[] > directiveDefaults` **YAML — `erpc.yaml`:** ```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 ``` **TypeScript — `erpc.ts`:** ```typescript import { createConfig } from "@erpc-cloud/config"; export default createConfig({ projects: [{ id: "main", networks: [{ architecture: "evm", evm: { chainId: 1 }, directiveDefaults: { enforceHighestBlock: true, enforceGetLogsBlockRange: true, enforceNonNullTaggedBlocks: true, retryEmpty: true, }, }], }], }); ``` ## 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: **Config path:** `projects > networks[] > directiveDefaults` **YAML — `erpc.yaml`:** ```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 ``` **TypeScript — `erpc.ts`:** ```typescript import { createConfig } from "@erpc-cloud/config"; export default createConfig({ projects: [{ id: "main", networks: [{ architecture: "evm", evm: { chainId: 1 }, directiveDefaults: { validateLogsBloomEmptiness: true, validateLogsBloomMatch: true, enforceLogIndexStrictIncrements: true, validateTxHashUniqueness: true, validateTransactionIndex: true, }, 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. **Config path:** `projects > networks[] > failsafe[]` **YAML — `erpc.yaml`:** ```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 ``` **TypeScript — `erpc.ts`:** ```typescript import { createConfig } from "@erpc-cloud/config"; const retryPolygon = { maxAttempts: 6, delay: "0ms", blockUnavailableDelay: "1s", emptyResultDelay: "500ms", emptyResultMaxAttempts: 3, emptyResultConfidence: "blockHead" as const, emptyResultAccept: ["eth_getLogs"], }; export default createConfig({ projects: [{ id: "main", networks: [{ architecture: "evm", evm: { chainId: 137 }, directiveDefaults: { retryEmpty: true }, failsafe: [ { matchMethod: "eth_getLogs|eth_getBlockReceipts", matchFinality: ["unfinalized", "unknown"], retry: { ...retryPolygon }, consensus: { maxParticipants: 3, agreementThreshold: 2 }, }, { matchMethod: "eth_getLogs|eth_getBlockReceipts", retry: { ...retryPolygon }, }, { matchMethod: "eth_call", retry: { ...retryPolygon, emptyResultAccept: ["eth_call"] }, }, { matchMethod: "*", retry: { ...retryPolygon, emptyResultMaxAttempts: 2 }, }, ], }], }], }); ``` --- ### Copy for your AI assistant — full integrity & empty-data reference ### Block tracking directives (`directiveDefaults`) | Directive | Default | Notes | |---|---|---| | `enforceHighestBlock` | `true` | Track 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`. | | `enforceGetLogsBlockRange` | `true` | Before 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`. | | `enforceNonNullTaggedBlocks` | `true` | Convert `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. | Directive | Header | Notes | |---|---|---| | `validateTransactionsRoot` | `X-ERPC-Validate-Transactions-Root` | Recompute the transactions root from the block's transaction list and verify it matches the header. Catches truncated or reordered tx lists. | | `validateTransactionFields` | `X-ERPC-Validate-Transaction-Fields` | Validate field formats (hex lengths, required keys) for each transaction in a block. Rejects responses with malformed fields. | | `validateTransactionBlockInfo` | `X-ERPC-Validate-Transaction-Block-Info` | Verify that each transaction's embedded `blockHash` and `blockNumber` match the containing block. Catches upstreams returning cross-contaminated responses. | | `validateHeaderFieldLengths` | `X-ERPC-Validate-Header-Field-Lengths` | Check byte lengths of block header fields (hash, parentHash, etc.). Rejects headers with truncated or padded values. | ### Log and receipt validation directives | Directive | Header | Notes | |---|---|---| | `validateLogFields` | `X-ERPC-Validate-Log-Fields` | Validate log address and topic lengths. Rejects logs with malformed addresses (not 20 bytes) or topics (not 32 bytes). | | `validateLogsBloomEmptiness` | `X-ERPC-Validate-Logs-Bloom-Emptiness` | Consistency 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. | | `validateLogsBloomMatch` | `X-ERPC-Validate-Logs-Bloom-Match` | Recompute 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). | | `enforceLogIndexStrictIncrements` | `X-ERPC-Enforce-Log-Index-Strict-Increments` | Log indices must increment by exactly 1 across all receipts in a block response. Catches upstreams that return receipts with gaps or duplicated log indices. | | `validateTxHashUniqueness` | `X-ERPC-Validate-Tx-Hash-Uniqueness` | No transaction hash may appear more than once in a block's receipt set. Catches duplicate-receipt bugs in some upstreams. | | `validateTransactionIndex` | `X-ERPC-Validate-Transaction-Index` | Receipt transaction indices must be sequential starting from `0`. Rejects responses with out-of-order or missing indices. | ### Receipt cross-check directives | Directive | Header | Notes | |---|---|---| | `validateReceiptTransactionMatch` | `X-ERPC-Validate-Receipt-Transaction-Match` | Cross-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). | | `validateContractCreation` | `X-ERPC-Validate-Contract-Creation` | Verify that `contractAddress` is populated when `to` is null (contract creation tx) and absent otherwise. | | `receiptsCountExact` | `X-ERPC-Receipts-Count-Exact` | Integer. The `eth_getBlockReceipts` response must contain exactly this many receipts. Useful when the caller knows the transaction count from the block header. | | `receiptsCountAtLeast` | `X-ERPC-Receipts-Count-At-Least` | Integer. The response must contain at least this many receipts. Softer than `receiptsCountExact` — allows appended receipts (e.g. from internal transactions on some chains). | | `validationExpectedBlockHash` | `X-ERPC-Validation-Expected-Block-Hash` | Hex string. All receipts in the response must carry this block hash. Rejects responses that mix receipts from different blocks. | | `validationExpectedBlockNumber` | `X-ERPC-Validation-Expected-Block-Number` | Hex or decimal. All receipts must carry this block number. Same purpose as `validationExpectedBlockHash` but keyed by number. | ### Empty/missing data handling — `failsafe[].retry` fields | Field | Default | Notes | |---|---|---| | `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. | | `emptyResultConfidence` | `blockHead` | When 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` | = `maxAttempts` | Separate attempt cap for empty-result retries. Set lower than `maxAttempts` when you want aggressive retries for errors but lighter retries for empties. | | `emptyResultDelay` | = `delay` | Fixed delay between empty-result retries. Overrides the normal `delay` for this case. Set to roughly one block time divided by your `emptyResultMaxAttempts`. | | `blockUnavailableDelay` | dynamic | Fallback 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:** | Chain | Block time | `emptyResultDelay` | `emptyResultMaxAttempts` | |---|---|---|---| | Ethereum | 12 s | `2000ms` | 3 | | Polygon | 2.3 s | `500ms` | 5-6 | | Base / Optimism | 2 s | `500ms` | 4-5 | | Arbitrum | 250 ms | `200ms` | 5 | | Monad | 500 ms | `200ms` | 3 | ### `directiveDefaults.retryEmpty` | Field | Default | Notes | |---|---|---| | `retryEmpty` | `true` | When `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): ```yaml 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): | Field | Path | Notes | |---|---|---| | `checkLogIndexStrictIncrements` | `upstreams[].evm` | Upstream-local equivalent of `enforceLogIndexStrictIncrements`. Marks the upstream unhealthy when its receipts fail this check, without requiring the network-wide directive. | | `checkLogsBloom` | `upstreams[].evm.integrity.eth_getBlockReceipts` | Upstream-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`: ```yaml # 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](/config/projects/networks.llms.txt) 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 `markEmptyAsErrorMethods`** — `emptyResultAccept` 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. --- > **TIP** > 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.