Keone’s blog
← All writing

Deterministic RaptorCast

protocol

RaptorCast is Monad's block propagation protocol. The leader erasure-codes the block and disperses chunks across the validator set in two hops, getting the block to every validator efficiently regardless of block size or validator count.

Deterministic RaptorCast (v1) is a redesign specified in MIP-10 and implemented in monad-bft#2811. The topology and the use of Raptor codes are unchanged. What changes is how the encoding is produced and committed to: the seed driving the encoding becomes a public function of the round, the leader identity, and the proposal timestamp; the position of every encoded symbol is fixed by that seed; and a single Merkle root covers the entire encoding instead of one root per 32-packet batch.

The headline payoff is that validators can vote on the Merkle root as soon as they've verified a single chunk against it, no longer needing to wait for the full block to be reassembled. Additionally, v1 defends against two classes of leader misbehavior that could allow a leader to slow down the network.

A quick recap of v0

The job of RaptorCast is to get a large block proposal from the leader to every validator as quickly as possible. The two obvious approaches both fail at the throughput Monad targets.

Gossip (used by Ethereum and most other blockchains) has each node forward the block to a few peers, who forward it onward. Bandwidth is spread across the network, but transmission can require many hops, and there's no strong bound on how many hops the block takes to reach a given validator.

Direct transmission from the leader to every validator gives a predictable hop count of one, but pays for it in leader bandwidth. A leader with a 1 Gbps uplink, sending a 2 MB block to 1,000 validators serially, would about 16 seconds. Supporting Monad's high throughput target would require validators to have tens of Gbps of upload, increasing barriers to participation and impacting decentralization. (See Category Labs' design writeup for a longer version of this argument.)

RaptorCast threads the needle. Every validator receives all the data needed to reconstruct a block in two hops, and each validator's bandwidth requirement is minimal — roughly 3x the block size, regardless of validator count.

Four design choices make that work:

  • UDP with per-chunk authentication. TCP's reliability guarantees come with retransmission and head-of-line blocking that show up as latency. RaptorCast runs over UDP and accepts packet loss as a normal condition, which means each chunk has to carry enough metadata for a recipient to verify it came from the leader and wasn't tampered with — a signature and a proof, on every packet.

  • Erasure coding for loss tolerance. Each block is encoded with R10 Raptor codes into more chunks than strictly needed. Any sufficiently large subset of chunks decodes back to the original block, regardless of which specific chunks arrived. As long as the receiver collects roughly K chunks out of the ~2.5K transmitted, decoding succeeds.

  • Merkle batching to amortize signatures. Signing every chunk individually with ECDSA would dominate the leader's CPU. Instead, the leader groups 32 chunks at a time, builds a small Merkle tree over them, and signs only the root. Each packet carries the signed root plus a ~100-byte Merkle proof of its leaf position. One signature covers 32 chunks, and any chunk can be verified independently against the root.

  • Two-hop fan-out, stake-proportional. The leader sends each validator a share of the chunks roughly proportional to that validator's stake. Each validator re-broadcasts its share to everyone else. With 2.5x redundancy, even an adversary controlling the maximum 1/3 of stake who drops everything they receive can't keep honest validators from collecting the ~K chunks they need. Aggregate network bandwidth, not the leader's link, becomes the limit.

v1 changes two pieces of how v0 works.

In v0, the Merkle tree over each group of 32 chunks is purely local to that group. It exists as a signature amortization trick: a single ECDSA signature on the root covers all 32 chunks, and each chunk carries its proof of membership. The roots for different groups don't tie to each other or to the block being encoded. v1 puts every chunk in the encoding under one Merkle tree and signs that single root. The tree is deeper, so per-packet proofs grow from ~100 bytes to up to 280. In return, the root becomes a binding commitment to the entire block: a validator that has verified a single chunk against the root knows what the leader committed to, and can vote on the block without waiting to decode it.

v0 also leaves the leader's choice of encoding symbol IDs (ESIs) unconstrained; nothing in the protocol fixes which ESIs the leader produces chunks for. That flexibility doesn't buy anything in practice, and it opens the network up to partial slowdown attacks. v1 specifies the ESI-to-position mapping deterministically from public data, so there's exactly one valid encoding per round.

Two attacks v1 also closes

v0's freedom in encoding choice opens two specific attacks worth flagging.

The first is asymmetric liveness. Raptor codes have a degree distribution, with a small fraction of chunks being 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 v0 leader can pick its ESI set adversarially — send the singletons to favored validators and only high-degree repair chunks to the targets — and v0 has no way to detect it, because every chunk the leader sends is individually valid.

The second is mixed-commitment equivocation. Because v0's Merkle roots only cover 32 chunks at a time and the ESI mapping isn't fixed, a malicious leader can construct a single root whose 32 leaves are drawn from the encodings of two different payloads. Validators receiving different subsets endorse the same commitment while holding chunks from different blocks. Nothing the leader signs contradicts itself, so the protocol has no attributable evidence of the equivocation.

What v1 changes

A canonical seed

The Raptor encoding for a round is derived from a seed:

seed = H(round, leader_id, proposal_timestamp)

Every validator can compute this seed from data already in the chunk header. Once the seed is fixed, the encoding is fixed: the ESI at position i is exactly i, and the chunk c_i is whatever Raptor encoding of the block produces at ESI i under that seed.

Validators reject chunks whose timestamp falls outside an acceptable clock window, so the leader can't seed-grind by trying many timestamps to find a favorable degree distribution. Within the window, no choice of seed gives the leader meaningful control over the resulting encoding.

A single global Merkle root

v1 builds one Merkle tree over all n chunks of the encoding rather than one tree per 32-chunk batch. The tree is deeper (up to depth 15, vs. 6 in v0), so the per-packet Merkle proof grows from 100 bytes to up to 280 bytes. The whole encoding then lives under one commitment:

(c_1, ..., c_n) = RaptorEnc(B, seed)
R               = MerkleRoot(c_1, ..., c_n)
σ               = Sign(round, timestamp, R)

The pair (R, σ) is the round's encoding commitment. Every chunk packet carries the same R and σ, plus its position i, its Merkle proof π_i, and the chunk c_i.

Per-round commitment tracking

Each validator records the encoding commitment (R, σ) it sees first for a given round. If a chunk later arrives in the same round with a different R or σ from the same leader, the two commitments together are signed evidence of equivocation, attributable to the leader.

This is what closes both attacks: v1 has exactly one valid encoding per round, so adversarial ESI choice isn't a degree of freedom, and any two contradictory commitments from the same leader are signed evidence the protocol can act on.

Decode-re-encode verification

After collecting enough chunks to decode, a validator recovers a payload B, re-runs the encoder on B under the same seed, and checks:

MerkleRoot(RaptorEnc(B, seed)) == R

This works because the seed pins the ESI-to-position mapping; re-encoding B produces the same chunks in the same order that the original commitment was over. v0 had no equivalent check available, because the ESI set wasn't fixed by anything public.

A validator that decodes a payload not consistent with R discards it.

Voting before decoding

The protocol now guarantees that any two correct validators that decode chunks consistent with R recover the same payload. There is no longer a state where two honest validators see the same root and reconstruct different blocks.

A validator can therefore vote on R as soon as it has verified a single chunk against the signed root, without waiting for full decoding. Whatever it eventually decodes will match what every other honest voter decodes. In v0, voting on a Merkle root before decoding wasn't safe, because the root wasn't binding to a unique payload.

Pulling decoding off the consensus critical path delivers a significant throughput benefit.

Implementation in monad-bft

PR #2811 lands the v1 protocol alongside v0, with a four-stage rollout that lets the network transition gradually. Three earlier PRs cleaned up the v0 code to make this possible:

  • #2866 unified the chunk validation pipeline so v0 and v1 share parsing, validation, and decoding. Chunk validation moved under RaptorcastPacket methods, 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 the existing proportional strategy, but with chunk IDs interleaved. v1 uses this to make assignment predictable from the seed.

  • #2978 replaced the dynamic-dispatch ChunkAssigner trait with concrete EvenPartition and StakePartition types, clarifying the Partition → OrderedNodes → ChunkAssignment → materialize() pipeline. Secondary RaptorCast also picks up Fisher-Yates shuffling and round-robin assignment in the same change.

The v1 packet drops one field v0 carried: the 20-byte recipient_hash in the chunk header. v0 used it to tell a forwarding node whether a given packet was meant for it. v1's assignment is computable from the seed and the validator set, so each validator can derive its expected chunk IDs directly and drop anything that doesn't match. The decoder cache also rekeys: v0 keyed by app message hash, v1 keys by Merkle root, since the root is the canonical commitment for the round.

The wire format, the 2.5x / 2.0x redundancy factors, the two-hop fan-out, the secondary RaptorCast group structure, and the rate limiting all carry over unchanged.

What this unlocks

The headline benefit is that validators can vote much faster, because they no longer need the full block in hand before signing.

Monad already decouples consensus from execution. A vote isn't a verdict on this block's transactions — execution runs D blocks behind. The vote attests to the consensus payload itself: the proposer's header and a delayed Merkle root that should match the validator's own execution result from D blocks ago.

v1 changes what "received the consensus payload" requires. In v0, the vote means "I have this block and it satisfies the block validity rules" — for instance, that all of the transactions within the block are valid. That needs the full block. In v1, the vote means "I have this signed header plus at least one chunk that verifies against the Merkle root R, where R commits to the deterministic encoding under the canonical seed." A QC over those votes proves that a supermajority received the same header and the same R.

The proposer is kept honest after the fact. Once enough chunks arrive to decode the block, the validator re-encodes it under the seed, recomputes the Merkle root, and confirms it matches R. If it doesn't, the validator discards the block, treats the QC's votes as invalidated — they were cast on a false premise — and proceeds as if the proposer had missed their slot. Determinism means every honest validator that decodes reaches the same conclusion independently, which is what makes committing to (header, R) at vote time safe.

The pipelining payoff makes Deterministic RaptorCast a natural next step. v1 allows the system to continue pushing the limits of information transmission, by letting validators vote as soon as they receive a chunk. The next leader can begin aggregating votes into a QC and start producing the next block while chunks of the previous one are still in flight. Block times stop being bounded by full-block propagation.

Check it out: MIP-10.

Originally published on X on May 9, 2026.

More writing →