Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Rapidblocks with split proofs: execution proofs that outlive their state

Дата публикации: 14-10-2026 07:58:50

Author: @cskiraly
TL;DR
Split proofs: a block’s execution proof splits in three. \Pi_T proves that the transactions, run on the block’s inputs, the values it reads, give its outputs; it names no state root. \Pi_\mathrm{in} and \Pi_\mathrm{out} tie inputs and outputs to state roots, and are Merkle work.
Proofs over committed inputs: the header commits to the inputs instead of a pre-state root, so for fixed transactions and context, \Pi_T stays valid across slots, over any state change that misses them. Anyone can prove an available, correctly declared block.
The input rule: a block is in iff its slot’s value is that block, its proof is valid, its inputs are still current and its context matches the horizon. It replaces the previous post’s Hist rule: compatibility comes out of it, and a skipped block can take down only blocks that read, directly or not, what it changed.
Two tracks: the window vote agrees on blocks every slot, with voters that check inputs without holding state; state roots follow on a track of their own, proven recursively.
The later proof round: the vote can ask about availability in round A and about the proof d rounds later, which works like raising a to a + d.
Our previous post designed a pipelined consensus for rapidblocks: the window vote, which decides one value per slot, and the Hist rule, which turns those values into the canonical set. A block builds on the state of a set of earlier blocks, its Hist, and only respects the filters of the more recent, concurrent ones, so skipping a block costs no reorg. Validators there re-execute every block before voting on it. This post asks what changes when they verify an execution proof instead.
Our answer, which we call split proofs, is to split the proof in three, and to let it commit to the block’s inputs, the values it reads. A proof made that way stays valid across slots, over any state changes that miss those inputs. A block then stays valid whatever happens around it, as long as its inputs are still current. That changes the rapidblocks block model more than it changes the proof, and it lets agreement on blocks run ahead of agreement on state roots. We started discussing these ideas with people around Devconnect Argentina, and this post is where we’ve got to since. It’s an ideas post: the block-level rules pass property tests in a small executable model, and a model of the vote, with inputs abstracted to dependencies between slots, passes bounded checks of small instances, but none of it is proven, there’s no simulation or measurement of the design itself yet, and the open questions at the end are real.
We use the previous post’s parameters: the slot \tau, the network bound \Delta = \tau/4, the window L, the rounds a from a block’s header vote to its body vote, and the vote cadence m. The working values are \tau = 2 s, \Delta = 0.5 s, L = 4, a = 1 and m = 1.
One proof per block doesn’t chain
In rapidblocks, block b executes on the state after its Hist. That is usually not the state just before b in the chain:
In the typical one-slot pipeline, b_{j+2} builds on everything up to slot j, but in the chain it runs after b_{j+1}.
When decisions are slow, \mathrm{Hist}(b) need not even be a prefix of the chain. The state b executes on is then one the chain never has.
A monolithic proof, “b, run on the state with root r, gives the state with root r'”, is therefore about the wrong state. Its producer needs the root of a state the chain may never have, and the chain’s own roots need b proven again on the state it ran on there. Re-executing validators get around this. A block that reads nothing a concurrent block wrote runs the same in either state, so they can execute the chain in slot order. A proof should say exactly that.
Split the proof in three
For a block b:
inputs I(b): the values, before b, of the keys in A(b): everything b reads or writes, except credits, as in the previous post. For an account b only credits, the input is at most its status, and only where execution depends on it: whether the account exists and isn’t empty (question 4);
outputs O(b): the values b writes. For balance credits, the amounts added: two concurrent blocks can pay the same fee recipient, and their credits add up;
the transactions T(b), and the context X(b). Slot and fee recipient come from the header. Base fee and block hashes come from the horizon, the decided past, so that they don’t depend on concurrent blocks. Randomness and the system contracts still need such rules (question 8).
proof
statement
state roots
\Pi_T
T(b), run on inputs I(b) in context X(b), gives outputs O(b), with these receipts and this gas
none
\Pi_\mathrm{in}
the inputs are the values in state \sigma
\sigma
\Pi_\mathrm{out}
applying the outputs to \sigma gives \sigma'
\sigma, \sigma'
The monolithic proof is their composition; split proofs keep them apart. \Pi_T is the expensive part, the EVM. \Pi_\mathrm{in} and \Pi_\mathrm{out} are Merkle work: multiproofs, or SNARKs over them.
\Pi_T has to be complete and binding. Every state read the execution depends on, including the reads only gas depends on, must be in I(b). In the prover this is a rule, not an extra proof: the guest serves every lookup of the state before the block from I(b), and aborts on a key outside it instead of defaulting to zero. Later reads in the block come from its own writes, and SSTORE’s original values are tracked per transaction. The guest must also check that it used every entry, so that padding I(b) can’t bloat the witnesses. Absent keys and the code a block calls, by hash, need entries of their own. Its inputs, outputs and filter must be the ones the header commits to. The header then commits to I(b) in place of a pre-state root, and the body carries the values and their witnesses. Since \Pi_T outputs which keys the block reads and writes, it also fixes the block’s filter, so a producer can’t claim a filter fuller or emptier than its writes. Once the proof is out, anyone can check that from the header and the proof, without the body.
Blocks would have to publish the values they read. EIP-7928’s block-level access list records read keys without their values, and balances as post-balances. Committing to inputs needs the read values, and deltas for credits.
Anyone can prove an available block. \Pi_T needs only the transactions, the inputs and the context, all of which are public once the body is, plus the code the block calls, which travels with the witnesses or comes from state. So any prover can prove a correctly declared block, not just its producer. If the declared outputs are wrong, no proof exists, and the block goes out. Whether a block runs as it declares is then a function of available data, as with delayed execution. Proving it on time is an assumption about provers, not about the producer.
Commit to the inputs
\Pi_T reads state only through I(b). So for fixed transactions and context, it holds on any state that agrees with I(b) on the keys b reads.
Figure 1: b_{j+2} was built on the state after b_j, but in the chain it runs after b_{j+1}. The two states differ only in y, which b_{j+2} doesn’t read, so a proof over its inputs holds on both.
That suggests a different validity rule, the input rule:
A block is in iff its slot’s value is that block, its proof is valid, its inputs are current (what it read is still the state just before its slot), and its context matches the horizon.
Under the Hist rule, b_j is in iff:
its slot’s value is b_j;
everything in its Hist is in;
every block already in from before its window is in its Hist;
it is compatible with the concurrent blocks already in.
The input rule replaces all of that but the first. It is optimistic concurrency control at the level of blocks, validated by value: a block commits iff its inputs equal the state produced by the earlier blocks that are in.
Compatibility comes out of it. If a concurrent earlier block that is in leaves a key b reads with a different value at b’s place, b is out. The earlier slot still wins, but the check is on values.
Skips spread along reads. Under the Hist rule, if b_5 is voted out, the blocks with b_5 in their Hist go with it. Here, a skip can affect only blocks that read, directly or through other blocks it affects, a key whose value b_5 changed, and the exact input check decides which go: those for which a value they read now differs. A producer that built on b_5 but read nothing it changed, directly or transitively, keeps its block.
A proof outlives the state it was made on. With its transactions and context fixed, it holds over any change that misses its inputs: by concurrent blocks, by dropped blocks its producer built on, or since an older state. That is how one proof stays valid through several slots.
Same-value writes don’t conflict. Execution depends only on the values read. If a concurrent block wrote the value that b read, b runs the same.
Exact sets. If the filter test on references goes (question 6), a filter hit becomes guidance for producers rather than a condition of validity, and a false positive no longer makes a block invalid. Either way, the filter keeps its job as the next producers’ early signal of what a block modifies, before its body arrives.
The execution layer needs no DAG. It goes through the slots in order and applies each decided block whose \Pi_T is verified and whose inputs and context match. Every other slot stays empty. Hist and refs remain useful to producers, but validity no longer depends on them, unless the filter test on references stays (question 6).
Figure 2: b_5 is voted out. Under the Hist rule, every block with b_5 in its Hist goes with it. Under the input rule only b_7 goes, because it read the value b_5 wrote to x, which no other block supplies. b_8 built on b_5 but read only y, so it stays. Other Hist edges are left out.
None of this needs proofs. A re-executing validator can apply the same rule, as long as blocks commit to the values they read. Proofs make it stateless.
Blocks now, state roots later
With the split, agreement on blocks and agreement on state roots come apart into two tracks. This is a different split from @fradamt’s decoupled consensus, which separates the available chain from finality; the two are orthogonal.
The block track, every slot. This adapts the window vote. In round A, a voter checks that:
the header was timely;
the body is available, with Merkle witnesses for its inputs (below);
\Pi_T is valid;
the inputs are current.
The voter needs no state for the last check. For each key the block reads, the value comes from the last block in the window that wrote it, among those in by the voter’s values; the voter reads it from that block’s access list. Otherwise it comes from the state at the horizon, L+1 slots back, with a Merkle witness against that root. For a balance, the value is the last post-balance written in the window, or the horizon’s if there is none, plus the credits since. This check is \Pi_\mathrm{in}, done by the voter itself, so nobody has to prove it in advance.
The state track, per horizon slot. Once the prefix up to the horizon is decided, it fixes one correct root \sigma_H. The roots come from \Pi_\mathrm{out} over the canonical outputs: a recursive proof from the last finalized root, bound to the decided prefix, so that a stateless voter can check \sigma_H itself. The checkpoint carries the latest proven root, so finality covers it and the recursion has a base. The roots carry no MEV. Anyone with the state, or with witnesses for the keys being updated, can prove them.
Round A for slot j needs the root of slot j - L - 1. Counted from the start of that slot, the state track has (L+1+a)\tau + \Delta - D until then, where D is the time until the prefix through that slot is decided:
(L+a)\tau - \Delta = 9.5 s with the typical decision at 6\Delta;
a\tau + 2\Delta = 3 s when the decision takes until its deadline, (L+1)\tau - \Delta.
That time also has to cover getting the root to the voters, and it assumes the roots before it are done. If the state track falls further behind, the header can name the root its witnesses are against: any proven root at or before the horizon. Voters then add the access lists of the in-blocks since that root. So a slow state track costs lookups in round A rather than block liveness, as long as those access lists stay available and checking them fits the deadline. What it delays is how far the finalized roots reach.
Figure 3: Round A of slot j checks inputs against \sigma_H, the root after slot H = j - L - 1, plus the access lists of the L slots in between. The bars are the state track’s time from the decision of the prefix through H to that round.
What changes in the vote
The window vote decides one value per slot and leaves dependencies to a feasibility check. That stays. What changes is the dependency. A block now depends on every earlier window slot whose block changed, or could change, a key it reads: the slots whose outcome can affect whether its inputs are current. Typically, these are:
in, for a block it read from;
out, for one that would overwrite what it read.
Another block can write the same value, so these are slots to wait for, not outcomes the block needs; the exact input check decides. A voter finds these slots from the window blocks’ access lists, or from the filter of a block whose body it lacks, which can only add slots.
Feasibility becomes the input check, under the voter’s own values. Closure has to wait for every slot a block depends on: a round’s votes confirm a block only if each of those slots was confirmed, either way, at least m rounds earlier. The exact input check then decides.
Honest producers read only from confirmed blocks. By the window vote’s timeliness rule, a producer holds every header that can still get in by the time it fixes its block. It avoids the keys of each one that is still unconfirmed: by its access list if it has it, by its filter otherwise. So an honest block depends only on confirmed slots. The previous post argues that honest blocks are never dropped. It names one gap, conflicts with earlier blocks a block neither builds on nor references, which its feasibility check and closure ignored, and a fix that its model check supports. Here both come with the input check, and closure waits in both directions, as in that fix. So the argument should carry over, under the window vote’s assumptions and with witnesses and proofs on time. Among those assumptions are synchrony, an honest majority in every round, m \le a + 1, and decisions settled by their deadline. We haven’t proven that, but a bounded check of a small model finds no violation (question 3).
A later proof round. Since anyone can prove an available block, the vote could ask about availability in round A, and about the proof d rounds later. Until its proof is verified, a block is available but unproven: it can’t be confirmed, and stateless voters can’t use its outputs to check later blocks. The proof round then becomes the block’s validity round, which works like raising a to a + d. Growing the window L by d keeps the follow rounds. Keeping L leaves \lfloor (L-a-d)/m \rfloor of them, which weakens the window vote’s bound on missed deadlines, \beta^{1+\lfloor (L-a)/m \rfloor}, where \beta is the adversary’s share of the stake. Confirmations also come later, so more slots are concurrent, unless producers that execute blocks themselves build on a block before its proof arrives. Their risk: if the proof misses its round, the block is voted out, and blocks that read from it can go too, as the exact input check decides.
Related work
Each piece has a precedent; we haven’t found them combined at the block level. Hyperledger Fabric decides a transaction’s validity by its read set after ordering, by version, and Aria’s basic rule executes a batch on one snapshot and lets the earlier transaction win a conflict. Validating by value instead is sound when the check is made at the serialization point, as NOrec’s value-based validation shows, and same-value writes then cause no conflict. zkEVMs already prove state access apart from execution, but inside one proof between two roots, as in the PSE zkEVM. Zcash spends are proven against an earlier anchor, while nodes check the nullifiers (Zebra), and Monad’s proposals carry the state root from three blocks earlier. Closest to this post, the State BALs variant in the block-level access list thread put read values in the access list to decouple execution from state. EIP-7928 left them out for size, and the balance deltas of its first draft became post-balances. This proposal needs both back (question 2).
Open questions
Proving latency. The EF’s real-time proving target is a mainnet block in at most 10 s at P99, on hardware under USD 100k and 10 kW (Shipping an L1 zkEVM #1). In December 2025, zkVMs were reported to prove 99% of blocks within it (#2).
At today’s gas rate, a 2 s slice carries about a sixth of a block’s gas.
From fixing the block to round A, its producer has a\tau + 2\Delta = 3 s, so about a\tau + \Delta = 2.5 s once \Delta is kept for getting the proof out: a quarter of the target time. A prover that gets the body only 2\Delta into the slot has about a\tau - 2\Delta = 1 s, a tenth.
Fixed costs per proof, such as setup, recursion and getting the proof out, come six times as often. So a sixth of the gas isn’t a sixth of the cost.
Without BLS, as with post-quantum signatures, the window vote’s votes are aggregated by recursive proofs too. The whole vote latency, aggregation and delivery, must stay within m\tau - 2\Delta = 1 s at m = 1 for safety, and within m\tau - 3\Delta = 0.5 s to keep the pipeline. Block proofs and vote proofs then share the slot, and perhaps the provers.
Can \Pi_T be done by round A, or one round later, alongside the vote’s own proofs, and on what hardware?
Size, in the critical path. Read values grow the access list itself: the State BALs proposal estimated about 1.51 MiB against 0.93 MiB in the worst case, at a 36M gas limit. Witnesses add more: every input that the window’s access lists don’t cover needs one against the named root, and that may be most of them. All of it travels in the body, inside the round-A budget that proving shares. Code is a large part of such witnesses, since provers need the bytecode. How large is this for a 2 s slice, with today’s Merkle-Patricia trie and with a binary tree (EIP-7864)?
Safety with these dependencies. We extended the previous post’s Quint model with dependencies in both directions, abstracting a block’s inputs to the slots it read from and the slots that would overwrite them. Apalache finds no violation of the safety properties in any run of two small instances: three slots over four rounds at a = 1, which reaches the third block’s round A but not its follow rounds, and two slots over five rounds with the later proof round as a = 2. Both are at m = 1; the model has no equivocation, and proofs appear only as a later round A. Do the window vote’s lemmas hold in general, and at m = 2?
Inputs that aren’t values. Some reads are predicates: whether an account exists or is empty (EIP-161), whether a contract-creation target has storage (EIP-7610). How should a block commit to them, and which credits depend on them at all? A fee credit’s effect doesn’t depend on whether the recipient exists, but a value transfer’s gas does.
Granularity. Inputs per block make a block all-or-nothing. With inputs per transaction, or per bundle, a stale input would drop only what read it. Can \Pi_T prove that cheaply?
The filter test on references. In the previous post, a block is invalid if a key of A(b) hits the filter of a block it references. With exact input checks, the blocks that rule rejects and the input check wouldn’t are filter false positives, and overlaps that leave the inputs current. Is a check from headers alone still worth having?
When roots arrive. A state root can be proven once the prefix through its slot is decided: typically about 1.5 slots after the slot starts, and at most L+1 slots, plus proving time. When should roots be published and finalized, and what do light clients follow until then? And how far back may a header’s witness root go?
Context and system steps. Randomness, and the system contracts that read and write the same keys in every block, such as the request queues of EIP-7002 and EIP-7251, need rules that don’t depend on concurrent blocks.
Who pays. A block whose data was published but that is voted out, or whose proof never arrives, has still used data availability, and proofs need provers. Who pays for \Pi_T and \Pi_\mathrm{out}, and should fees be charged up front, as delayed execution (EIP-7886) proposes?
A slow mode. Blocks are agreed before their state root is proven, and whether a block runs as it declares is a function of available data. So when proving can’t keep up, the chain needn’t stop: the window vote could keep deciding availability, and check current inputs provisionally, against the outputs that unproven blocks declare. Confirmation and validity would wait for proofs, or for re-execution, and nodes that fall behind could follow slowly and check later. The cost is risk: until a block is proven, the blocks that read from it depend on it, and a stateless node can’t tell a slow proof from one that can never exist. Proof deadlines that stretch past the window, and validity still open there, need rules of their own. How long may proofs lag, and what should decide it?
Future directions
Partial-state nodes. Voters and \Pi_T provers hold no state of their own, and \Pi_\mathrm{out} needs only witnesses for the keys it updates. So nodes that each keep part of the state could serve witnesses and prove the state track between them, each for its part. Producers are harder, since all of a block’s inputs must be in what its producer holds or is given. In a first look at 501 mainnet blocks, with contracts ranked on the first half and coverage measured on the second, a producer holding the 1,000 most-used contracts could have included 75% of the transactions but only 43% of the gas, if users supplied the witnesses for senders and other accounts without code; a random half of the contracts touched gave 53% and 20%, on average over 20 draws.
An elastic timeline. Proving time varies with a block’s size and content, and the slow mode is one end of a range. In rapidblocks, skipping a block needs no agreement, so much of the pipeline can adapt per block: a producer could declare its block’s round A, and with it its proof deadline, in the header, within an agreed bound. Its block is then confirmed later, its filter keeps hot keys blocked longer for the others, and the window has to be sized for the bound. The vote’s round structure, its cadence and window, still needs agreement. We are exploring a rule that fixes it per slot, when the slot is created, from data the chain has already settled, and backs off when the finality recorded on chain grows stale.
Comments, holes and counterexamples very welcome.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Rapidblocks, take two: a pipelined consensus for independent blocks05.7813-10-2026
2All Core Devs - Consensus (ACDC) #183, July 23 2026015.1813-07-2026
3Informed Diffusion: making home builders publish fast09.313-10-2026
4ERC-8337: Agent Memory State07.6526-07-2026
5EIP-8037: State Creation Gas Cost Increase019.6728-07-2026
6ERC-8312: Bounded Agent Actions012.7427-07-2026
7Measuring priority fees, positions and transaction classification of Ethereum blocks, July 2025 to October 2026010.8316-10-2026
8ERC-8340: Transaction Metadata Encoding034.2115-07-2026
9All Core Devs - Execution (ACDE) #242, July 30, 2026012.5320-07-2026
10Price of Censorship Resistance in Multiple Proposer Protocols Under Sybils05.0408-10-2026

Классификация: . Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 5.9. Источник: ethresear.ch.