MIP-10: Deterministic RaptorCast
Monad Foundation
@monad_dev (opens in new tab)- Published on
- · 10 min read
RaptorCast is Monad's block propagation protocol, and it is already a frontier design. 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. It is what makes Monad's high-throughput target compatible with a large, geographically distributed validator set running on commodity uplinks.
Deterministic RaptorCast (RaptorCast v1) is an upcoming upgrade specified in MIP-10 and implemented in monad-bft#2811. It keeps everything that already works about RaptorCast — the two-hop topology, erasure coding, and Byzantine Fault-Tolerance — and improves 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. v1 also defends against two classes of leader misbehavior that could allow a leader to slow down the network.
A quick recap of RaptorCast today
The job of RaptorCast is to get a large block proposal from the leader to every validator as quickly as possible. To understand RaptorCast, it helps to consider alternative approaches at solving this problem:
Gossip — what Ethereum and most existing networks use — 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 take 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.
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.
RaptorCast: Designing a Messaging Layer provides an excellent overview of the design enabling these properties. A quick summary:
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; instead, each packet carries 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 with a 2.5x redundancy factor. A receiver can reconstruct the original block from any
Kof the2.5Kchunks.Merkle batching to amortize signatures. Signing every chunk individually with ECDSA would create a CPU bottleneck, both during encoding and decoding. 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 proving that that chunk was in the Merkle trie. This reduces the signature count by a factor of 32.
Two-hop fan-out, stake-proportional. The leader sends each validator a share of the
2.5Kchunks roughly proportional to that validator's stake. Each validator re-broadcasts its share to everyone else. With 2.5x redundancy, even an adversary controlling 1/3 of stake who drops everything they receive can't keep honest validators from collecting theKchunks they need, thus preserving Byzantine fault tolerance.
v1 keeps all of that and changes two pieces of how v0 works.
Changes in Deterministic RaptorCast
v0 gives the leader freedom in certain places; v1 makes the encoding fully deterministic given the block being communicated.
This determinism stems from the following two changes:
One Merkle root for the entire encoding. In v0, the leader groups chunks into batches of 32, builds a small Merkle tree over each batch, and signs the root. The trees are a signature-amortization trick: one ECDSA signature covers 32 chunks, and each chunk carries a ~100-byte proof of membership against its batch root. The batch roots have no relation to each other or to the block being encoded.
v1 puts every chunk in the encoding under a single Merkle tree and signs that one root. The tree is deeper, so per-packet proofs grow from ~100 bytes to up to 280 bytes. In exchange, the root becomes a binding commitment to the entire block. A validator that has verified one chunk against that root knows what the leader committed to and can vote on the block before the rest of the chunks arrive.
A deterministic encoding. A Raptor encoder can produce a practically unlimited number of encodings (sets of chunks) for a given block, each tagged with an encoding symbol ID (ESI). v0 leaves the leader free to choose which ESIs to send. There is no reason to give the leader that choice, and it opens the door to attacks where the leader biases the encoding against specific validators (covered in the next section).
v1 fixes the encoding deterministically. The leader derives a seed from public round data — seed = H(round, leader_id, proposal_timestamp) — and the seed pins the chunk at every position. There is exactly one valid encoding per round, every validator can compute it, and the leader has no remaining choice. 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 encoding.
The full round commitment is then:
(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.
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.
v1 closes both attacks at once. With exactly one valid encoding per round, the leader has no adversarial ESI choice to make, and the asymmetric-liveness attack disappears. Each validator also records the first encoding commitment (R, σ) it sees from a leader in a given round; a second commitment with a different R or σ in the same round is signed evidence of equivocation attributable to that leader.
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 is most of the throughput benefit of v1.
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 picked 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 for RaptorCast. v1 lets the system 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.