A Quick Refresher on How RaptorCast Works
The core job of RaptorCast is to deliver a large block from the leader (the validator whose turn it is to propose) to everyone else, as quickly as possible. Sounds simple, but there are traps.
There are a few classic approaches to this problem:
Gossip (what Ethereum uses) — each node forwards the block to a few neighbors, who forward it further. Bandwidth gets spread across the network, but the block might travel many hops, and there's no strong guarantee it arrives quickly.
Direct transmission from the leader to everyone — sounds straightforward, but think about it: 1,000 validators, a 2 MB block, a 1 Gbps uplink. Just sending it out takes around 16 seconds. That's not a solution.
RaptorCast threads the needle. The block gets encoded with redundancy (2.5x), split into chunks, and each validator receives its share — roughly proportional to its stake. Then every validator re-broadcasts its chunks to everyone else. Two hops, and everyone has enough data to reconstruct the block. Each participant's bandwidth requirement stays minimal, regardless of how large the network grows.
A few technical pieces that make this work:
UDP instead of TCP — no retransmissions, no head-of-line blocking, just speed. Each packet carries enough metadata to verify it came from the leader and wasn't tampered with.
Raptor codes — a type of erasure coding where any K chunks out of 2.5K are sufficient to reconstruct the original block.
Merkle batching for signatures — signing every chunk individually with ECDSA would be a CPU bottleneck. Instead, the leader groups 32 chunks, builds a small Merkle tree over them, and signs just the root. Each packet carries that signed root plus a ~100-byte proof of its membership in the tree. Signature count drops by a factor of 32.
Byzantine fault tolerance — even if an adversary controls 1/3 of the stake and drops everything they receive, honest validators still collect enough chunks to reconstruct the block.
What v1 Actually Changes
The core idea behind v1 is simple: take away any freedom the leader has in how it produces the encoding.
In the old version, the leader got to choose which ESIs (encoding symbol IDs) to send. Every individual chunk was valid, but that freedom opened a window for manipulation. v1 closes it.
One Merkle Tree for the Entire Block
Previously, each group of 32 chunks had its own separate Merkle tree. Those roots had no relationship to each other, and no relationship to the block itself.
Now all chunks go into a single large tree, and one root gets signed. The tree is deeper, so proofs grow from ~100 bytes to up to ~280 bytes per packet — a small price. In return, that root becomes a binding commitment to the entire block. A validator that verifies even a single chunk against this root already knows exactly what the leader committed to.
A Deterministic Seed for the Encoding
A Raptor encoder can produce an enormous number of different encodings for the same block, depending on the seed it uses. In v0, the leader picked the seed freely.
v1 fixes that: the seed is computed as a hash of public round data — seed = H(round, leader_id, proposal_timestamp). The position of every chunk is now uniquely determined. Any validator can independently compute what the encoding should look like. The leader has nothing left to choose.
To prevent the leader from grinding through timestamps looking for a favorable seed, validators reject chunks whose timestamp falls outside an acceptable clock window.
The full round commitment looks like this:
Code
(c_1, ..., c_n) = RaptorEnc(B, seed)
R = MerkleRoot(c_1, ..., c_n)
σ = Sign(round, timestamp, R)
Every packet carries R, σ, the chunk's position i, its Merkle proof π_i, and the chunk c_i itself.
Two Attacks That v1 Closes
That freedom of ESI choice in the old version opened up two specific attack vectors worth understanding.
Asymmetric liveness attack. Raptor codes have a degree distribution, and a small fraction of chunks are singletons — degree-1 chunks that directly reveal one intermediate symbol. The peeling decoder relies on these to make progress; without enough of them it falls back to slow Gaussian elimination. A malicious leader in v0 could send all the singletons to favored validators and only send high-degree repair chunks to everyone else. Every individual chunk would be valid, and the protocol would have no way to detect anything wrong.
Mixed-commitment equivocation. Because each Merkle tree in v0 only covered 32 chunks and the ESI mapping wasn't fixed, a malicious leader could construct a root whose 32 leaves were drawn from the encodings of two different blocks. Different validators would receive chunks from different blocks but agree on the same root. Nothing the leader signed would contradict itself, so there'd be no attributable evidence of the equivocation.
v1 closes both at once. One encoding, one seed, no choices left for the leader. And if a validator sees a second commitment from the same leader in the same round with a different R or σ — that's a signed piece of evidence of equivocation, attributable to that leader.
Voting Before Full Decoding
Here's where things get interesting from a performance standpoint.
In v0, voting before receiving the full block wasn't safe. The Merkle root didn't guarantee a unique payload — two honest validators could see the same root and reconstruct different blocks.
That's now impossible. If two validators have each verified even a single chunk against the same R, they're guaranteed to reconstruct the same block. So a validator can vote as soon as it verifies its first chunk, without waiting for the rest to arrive.
This isn't just a convenience — it's the core throughput improvement of v1. Pulling decoding off the consensus critical path changes how fast the whole system can move.
How It's Implemented in monad-bft
The main work lands in PR #2811. v1 is added alongside v0, with a four-stage rollout so the network can transition gradually.
Three earlier PRs laid the groundwork:
#2866 — unified the chunk validation pipeline so v0 and v1 share the same parsing, validation, and decoding logic. Chunk validation moved under
RaptorcastPacketmethods, and the decoding cache stopped doing app-message hash validation.#2970 — added a round-robin assignment mode to the stake-proportional chunk assigner. Same per-validator chunk counts as before, but with chunk IDs interleaved. v1 uses this to make assignment predictable from the seed.
#2978 — replaced the dynamic-dispatch
ChunkAssignertrait with concreteEvenPartitionandStakePartitiontypes, making thePartition → OrderedNodes → ChunkAssignment → materialize()pipeline explicit. Fisher-Yates shuffling and round-robin assignment came in the same change.
One wire-format detail: v1 drops the recipient_hash field (20 bytes) from the chunk header. In v0, forwarding nodes used it to figure out whether a given packet was meant for them. In v1, every validator can compute its expected chunk IDs directly from the seed and the validator set, so the field is no longer needed.
The decoder cache also rekeyed: v0 indexed by app message hash, v1 indexes by Merkle root, since the root is the canonical commitment for the round.
Everything else carries over unchanged — the 2.5x/2.0x redundancy factors, the two-hop fan-out, the secondary RaptorCast group structure, and rate limiting.
What This Actually Unlocks
A quick reminder of context: Monad decouples consensus from execution. When a validator votes on a block, it's not saying "I verified all the transactions in this block" — execution runs D blocks behind. The vote attests to the consensus payload itself: the proposer's header and a Merkle root that should match the validator's own execution result from D blocks ago.
In v0, a vote meant: "I have this full block and it passes block validity rules." That required the full block. In v1, a vote means: "I have this signed header and at least one chunk that verifies against R, where R commits to the deterministic encoding under the canonical seed." A quorum certificate over those votes proves that a supermajority received the same header and the same R.
What happens when enough chunks finally arrive? The validator decodes the block, re-encodes it under the same seed, recomputes the Merkle root, and checks it matches R. If it doesn't — the block gets discarded, the votes are treated as cast on a false premise, and the slot is treated as if the proposer missed it. Every honest validator that decodes will reach the same conclusion independently, which is exactly what makes committing to (header, R) at vote time safe.
And the big payoff: the next leader can start aggregating votes into a QC and begin producing the next block while chunks of the previous one are still in flight. Block time is no longer bounded by full-block propagation.
Deterministic RaptorCast isn't a revolution — it's a precise, surgical improvement. Remove the leader's freedom of choice where it isn't needed, and get back security, predictability, and the ability to vote earlier. A natural next step for a protocol that's already one of the strongest designs in this space.

