Data access · 8 min read

Verifiable is not complete: validating onchain data beyond proofs

A block header proves what is inside the block. It says nothing about what was left out. On Polygon, an entire class of activity falls into that gap: state-sync, the mechanism that relays deposits and messages from Ethereum. It runs as transactions of its own, and until a late-2025 hard fork none of the block's Merkle commitments covered any part of them, not the transaction, not its receipt, not the logs it emits. That makes state-sync the clean case of the one data property cryptographic verification cannot give you, which is completeness. This guide shows what a state-sync transaction is, why a proof or a bloom filter cannot tell you any are missing, and how to read the full set. Every figure is from a real query against SQD's Portal, pinned to a fixed Polygon block.

Updated 2026-06-30 · By the SQD team

1. What a state-sync transaction is

Polygon PoS runs on two layers. A validator layer, Heimdall, watches Ethereum and reaches consensus on what crossed over; a block-producing layer, bor, executes it. Bridging works through this pair: when something is deposited or a message is sent from Ethereum, Heimdall relays the record, and at each sprint boundary, every 16 blocks, bor commits the pending records through the state-receiver system contract at 0x0000…1001. That contract calls onStateReceive on the target contract meant to receive the data, and emits a StateCommitted event recording whether the call succeeded. The deposit or message that was delivered emits its own logs alongside that marker. Polygon documents the flow in its state-sync architecture notes.

None of this is a user transaction. No account signs it and no gas is paid. To make it visible to ordinary tooling, bor wraps each block's state-sync work in a single synthetic transaction appended after the real ones, with a hash derived from the block number and block hash rather than from a signature (keccak256("matic-bor-receipt-" + number + hash)). It is a full transaction: it has its own receipt, and that receipt carries the StateCommitted markers and the bridge events as logs. A standard node exposes every part of it, the transaction through eth_getBlockByNumber and the receipt with all its logs through eth_getTransactionReceipt. So the transaction, its receipt, and its logs all exist and are retrievable. The difference is in what secures them, and what does not.

2. What the block header commits to

An Ethereum-style block header carries three commitments over the block's transactions. transactionsRoot is a Merkle root over the transaction list. receiptsRoot is a Merkle root over the receipts, which is where logs are committed. logsBloom is a bloom filter summarizing every log address and topic in the block, so a consumer can rule a block out without fetching its logs. Together they let anyone prove a transaction, a receipt, or a log belongs to a block without trusting the source.

State-sync sidesteps all three. Because its execution is not in the canonical transaction list, it feeds none of those commitments: the transaction is absent from transactionsRoot, its receipt from receiptsRoot, its logs from logsBloom. Polygon's own proposal to fix this, PIP-74, states it plainly: these executions "are not recorded in the block's transaction list, so they do not affect transactionsRoot, receiptsRoot, or logsBloom." The state still changes, so stateRoot moves. Everything that would let you find or verify the transaction, its receipt, or its logs is left out.

Header commitmentState-sync coverage
  • transactionsRoot (root over the tx list)No: the state-sync transaction is not listed
  • receiptsRoot (root over the receipts)No: its receipt is excluded
  • logsBloom (bloom over the block's logs)No: its logs are not summarized
  • stateRoot (root over world state)Yes: the state change is applied
What the Polygon block header commits to, before the Madhugiri fork

This is a historical shape, not a permanent one. The Madhugiri hard fork that activated PIP-74 at Polygon block 80,084,800 on 9 December 2025 gives state-sync a canonical type 0x7F transaction whose receipt and logs are folded into the roots and the bloom, so blocks after it commit to the whole state-sync transaction (bor v2.5.2 release notes). Every block before it, which is the large majority of Polygon's history, keeps the uncommitted shape.

3. Reading them from Portal

In SQD's Portal, state-sync logs are ordinary logs. This request asks for the StateCommitted events from the state-receiver contract in a single block, 74,614,768, a sprint boundary on 2025-07-31. It is one keyless HTTP call:

your terminal
curl https://portal.sqd.dev/datasets/polygon-mainnet/stream \
-H 'content-type: application/json' \
-d '{
"type": "evm",
"fromBlock": 74614768,
"toBlock": 74614768,
"logs": [{
"address": ["0x0000000000000000000000000000000000001001"],
"topic0": ["0x5a22725590b0a51c923940223f7458512164b1113359a735e86e7f27f44791ee"]
}],
"fields": {
"log": {
"logIndex": true, "transactionIndex": true, "transactionHash": true,
"address": true, "topics": true, "data": true
}
}
}'

The topic0 is keccak256("StateCommitted(uint256,bool)"). The response is the two state syncs committed in that block, both successful, with consecutive state IDs:

{
"header": {},
"logs": [
{
"logIndex": 851,
"transactionIndex": 114,
"transactionHash": "0x167f905bee2860765a0f4996085e826f9a9aa60ec4fff225b454026ef1783ec7",
"address": "0x0000000000000000000000000000000000001001",
"data": "0x0000000000000000000000000000000000000000000000000000000000000001",
"topics": [
"0x5a22725590b0a51c923940223f7458512164b1113359a735e86e7f27f44791ee",
"0x00000000000000000000000000000000000000000000000000000000002f2536"
]
},
{
"logIndex": 855,
"transactionIndex": 114,
"transactionHash": "0x167f905bee2860765a0f4996085e826f9a9aa60ec4fff225b454026ef1783ec7",
"address": "0x0000000000000000000000000000000000001001",
"data": "0x0000000000000000000000000000000000000000000000000000000000000001",
"topics": [
"0x5a22725590b0a51c923940223f7458512164b1113359a735e86e7f27f44791ee",
"0x00000000000000000000000000000000000000000000000000000000002f2537"
]
}
]
}

Both logs sit on transactionIndex 114. Ask the same dataset for the block's transactions and you get 115 records; the last, at index 114, is that synthetic state-sync receipt, hash 0x167f…3ec7, with no signer and no fee. The logs are in the answer because Portal indexes the full set bor exposes, not because a header vouched for them.

4. Why a proof cannot catch a missing one

The reason this matters is not that state-sync logs are hard to fetch. It is that their absence is undetectable by the usual checks. A consumer built on the header commitments has no way to notice they are gone, and the failure is silent in three concrete ways.

A pipeline that prefilters blocks by logsBloom, a standard optimization to skip blocks that cannot contain a log you care about, skips every block whose only matching logs are state-sync, because the bloom does not list them. A validator that checks a dataset's logs against receiptsRoot accepts a set that omits them, because the root never committed them in the first place. And a light client cannot prove a state-sync log even when it has one in hand, because there is no Merkle path from the log to the header. In each case the proof passes and the data is incomplete.

This is not hypothetical. For this comparison we queried Dune's polygon.logs, and it shows the gap directly. Ask it for the state-receiver's logs in block 74,614,768 and it returns nothing:

Dune · polygon.logs
SELECT block_number, index AS log_index, tx_hash,
contract_address, topic0
FROM polygon.logs
WHERE block_number = 74614768
AND contract_address = 0x0000000000000000000000000000000000001001
ORDER BY index
-- 0 rows

Its polygon.transactions for that block holds 114 records and omits the synthetic state-sync receipt entirely, so the eight logs in that receipt are absent, including the two StateCommitted markers Portal returned above. The block is present and 848 of its 856 logs are present; only the uncommitted ones are gone. The same shape holds in aggregate, and only where the chain left the commitments empty:

ScopePortalDune
  • Block 74,614,768, state-receiver logs20
  • Blocks 74,600,000-74,630,000, commit logs14295
  • Same range, distinct state-sync transactions9065
  • Control 75,000,000-75,030,000, commit logs154154
State-sync commit logs: Dune polygon.logs vs SQD Portal (queried 2026-06-30)

In the control window the two agree to the log. In the window around 2025-07-31 Dune is missing 47 of the 142 commit logs Portal has, across 25 state-sync transactions, and no receiptsRoot or logsBloom check on those blocks would register the difference, because those logs were never part of either.

That is the mechanical difference between verifiable and complete. Verification asks whether the rows you have are genuine, and the chain's commitments answer it. Completeness asks whether you have all the rows, and for state-sync the chain offers no answer at all. The only way to know your state-sync logs are complete is to compare counts, block by block, against an independent source that also carries them, which is reconciliation, not verification. It is not unique to Polygon either. Any chain that injects protocol-level execution outside the user transaction list, OP Stack system and deposit transactions among them, has the same blind spot.

5. Bridge deposits and audit trails

State-sync is the path the Polygon PoS bridge uses to deliver deposits from Ethereum, so its logs record bridged deposits arriving on Polygon. The synthetic receipt in the block above carries eight logs from five contracts: the two StateCommitted markers from the state-receiver, plus the events of the bridge activity that state sync relayed, viewable in full on the explorer. None of the eight are reflected in the block's receiptsRoot or logsBloom.

For anyone following inflows or building a chain-of-custody trail, a dataset that drops state-sync transactions has a hole precisely where deposits land, and an audit anchored to the block's transaction roots cannot see that the hole is there. The missing rows are not flagged as missing; they are simply absent, and the proofs say everything is fine. That gap between provable and complete is the part of data quality a regulated or forensic reader pays for, and it is the subject of the compliance work; for the reconciliation discipline behind it, the multi-chain indexing guide covers how correctness is held across chains.

6. State-sync data with SQD

The query above is the actual interface. Portal returns the state-sync transaction and its logs as ordinary records across all of Polygon's history, so the activity the header never committed to is in scope by default rather than something you reconstruct. The completeness comes from how the data is checked on the way in: SQD reconciles its counts against independent sources block by block, which is the only test that can catch a state-sync record that a proof would happily declare absent and unprovable in the same breath.

To pull these into your own store, the Squid and Pipes SDKs stream the same logs with the same filters, so a state-sync deposit feed or a bridge-inflow table updates as blocks arrive. The same query shape runs against every EVM dataset SQD indexes, changing only the network name in the endpoint. For where completeness like this is load-bearing, see the compliance solution and the analytics solution.

Frequently asked questions

What is a Polygon state-sync transaction?
It is a synthetic system transaction that the bor client appends to a block to expose state-sync work, the mechanism that relays deposits and messages from Ethereum to Polygon. No account signs it and no gas is paid for it; its hash is derived from the block number and block hash rather than from a signature. It emits the state-receiver contract's StateCommitted events, and bor exposes it through eth_getBlockByNumber and eth_getTransactionReceipt like any other transaction.
Why are state-sync logs missing from some datasets?
Before the December 2025 Madhugiri hard fork, state-sync executions were not part of the canonical transaction list, so they did not feed the block's transactionsRoot, receiptsRoot, or logsBloom. A pipeline that prefilters blocks by the header bloom skips them, and a check that validates a dataset against the receipts root accepts a set that omits them, because the root never included them. No Merkle proof reveals the gap. The only check that catches a missing state-sync log is a count reconciliation against an independent source that also has it.
How do I query Polygon state-sync logs?
With SQD's Portal you query logs and filter by the state-receiver contract 0x0000000000000000000000000000000000001001, optionally with the StateCommitted topic for the commit markers, over any block range. The full set of logs the state-sync transaction carries shares its transactionIndex, so you can also pull every log on that synthetic receipt. The request is a single keyless HTTP call shown in this guide.
Did the Madhugiri (PIP-74) hard fork fix this?
Going forward, yes. From block 80,084,800 on 9 December 2025, PIP-74 gives state-sync a canonical type 0x7F transaction that is included in transactionsRoot, receiptsRoot, and logsBloom, so new blocks commit to it and the logs become provable. Every block before that fork keeps the old shape, and that is the large majority of Polygon's history, so completeness across historical data still depends on the indexer rather than on the header.
Is this only a Polygon problem?
No. Any chain that injects protocol-level execution outside the user transaction list has the same blind spot. OP Stack chains insert system and deposit transactions with their own quirks, and other chains add validator or bridge bookkeeping the same way. The general lesson holds across all of them: a block header proves what it commits to, and completeness of anything it leaves out is not a property you can prove from the chain.

Need complete onchain data, not just verifiable data?

See how validated, complete datasets feed regulated and analytics work on the compliance solution page.