ethlambda: how we made our blocks arrive on time
In our previous blog post, we mentioned devnet 5 introduced full-block aggregation, making each block include a single proof for all its attestations. This makes the required bandwidth constant, not dependent on the number of attestations. However, this moves a lot of aggregation to the proposer, which quickly becomes the bottleneck.
In this post, we talk about how slow the proposer really is, and show how it can be made fast enough, without changing the underlying cryptographic primitives.
Current status
Block proposing is slow. Building an empty block takes 0.4s, leaving only another 0.4s to propagate the block if we start building it at the start of a slot. Adding a single attestation already takes us over the interval, at 1.3s. This is a big problem, since late blocks lose attester support and get reorged, increasing instability of the network.
Why not just increase the slot time?
A slot is composed of 5 intervals of 800ms each: block proposal, attestation propagation, aggregation, safe target computation, and view-merge.

One quick solution is to increase slot times and/or increase the duration of the block proposal interval. However, this brings its own problems for the future.
One of the goals of Lean Ethereum is to reduce slot times to improve block confirmation times and UX. If we give up on this right now, we are just moving the goalposts without even trying.
So what are the alternatives? Aside from optimizing leanVM, we can still improve how we invest our time budget.
Where the block building budget goes
Some weeks ago, an optimization to leanVM dropped aggregation latency by half. We shared a performance comparison between both versions, including a decomposition of where block building time goes:

We can see here that the base cost, turning the single proposer signature into a block proof, takes around ~0.4s. Adding one attestation to the block takes ~0.3s, but most of the time is spent in proof-compaction, which takes ~0.7s, half the block building time. This gives us an idea of where our block building budget goes.
First optimization: the early bird gets the slot
Block proposal time is at the start of the slot, giving a maximum propagation time of 0.8s. This is, however, not the whole window where we can build our block.
At the start of the last interval of a slot, all validators merge their "new" attestation set into their "known" set, called a view-merge. We can start right after this, giving us an additional 0.8s, an interval, to build our block.

In our original comparison, this optimization gives us the whole 0.8s interval to propagate our empty blocks. For single attestation blocks, this still leaves us publishing our block at ~0.5s into the slot, giving us only ~0.3s for block propagation. Including more attestations will undoubtedly push us into the next interval, but we can still do better.
Second optimization: avoid proof compaction during proposal
The cost decomposition showed us that proof compaction is nearly half the block building cost. This makes sense, since it adds a second round of recursive proof aggregation, the first one being the generation of the block proof. If we could remove it, the block building would take only half the time. With the previous optimization on top, all single-attestation blocks would be published right at the start of a slot.
It turns out that proof compaction isn't necessary for the network to work. If we just choose the proof with the most coverage we can find without aggregating, we can reduce proposer aggregation work by half. In fact, the PQ heartbeat proposal would make proof compaction happen in the background, with only a small set of signatures in the hot path. This change leads us in that direction.
The downside of this change is more attestations being included per block, since the following blocks will have to include the proofs not aggregated now.
Third optimization: aggregating a single signature is wasteful; let's not do it
On the cost decomposition visualization, one thing that seems wrong at first glance is the base cost of an empty block. An empty block should, by definition, contain no useful consensus information, so it shouldn't require any actual work from the proposer. However, a proposer has to sign the block and turn the signature into a block proof. The first part doesn't take much time, and the actual work is turning it into a block proof. This can be improved.
The optimization we propose here is moving the proposer signature out of the block proof. Since this is a protocol breaking change, it will require coordination between the client teams, but we think it's worth it.
// Before:
struct SignedBlock {
// The block
message: Block,
// Aggregated proof of proposer signature and all attestation signatures
proof: MultiMessageAggregate
}
// After:
struct SignedBlock { message: Block, proof: BlockProof }
struct BlockProof {
// raw XMSS sig (reuses the existing fixed-size wire type)
proposer_signature: XmssSignature,
// Multi-message aggregate over body attestations only (empty bytes if none)
attestation_proof: MultiMessageAggregate,
}
By removing the proposer signature from the aggregation path, we reduce the number of proofs to aggregate by one. This reduces aggregation times by 1 attestation: aggregating a single attestation would mean doing the same work as an empty block does now, 2-attestation blocks would be the same work as a single attestation block now, and so on.
This optimization reduces aggregation work almost for free: the only downside is including an additional 2KB signature alongside the ~250KB block proof, and having to verify it with a runtime cost of less than a millisecond.
Conclusion
We ran devnets with these three optimizations and made a side-by-side comparison. For reference, each devnet was run on a single server with 8 cores and 16 threads, and 64 GB of RAM. The devnet configuration consisted of 32 ethlambda nodes in 4 aggregation committees, and a single aggregator on each committee.

Running a node with all the listed optimizations gives us 2-attestation blocks published at ~150ms into the slot, with enough time left for block propagation. This might not scale to bigger validator counts, but until that becomes a problem, we believe we should keep the current slot times.
Follow us
Join us on Telegram for daily development updates and follow us on X for announcements and our weekly community calls.