All articles

, 25 min read

Pass the torch: handing off consensus safely

Changing the validators of a running blockchain is a nightmare. When HotStuff was designed in 2018, it had a very specific shape in mind. One committee of 3f + 1 validators, fixed for the lifetime of the system, of which at most f misbehave. Every theorem in that paper, and in the long line of descendants that followed it (LibraBFT, Fast-HotStuff, Jolteon, HotStuff-2 and the rest), is a theorem about that one, static, constant committee.

Production chains do not want constant committees. They want freedom. They want flexibility. Validators join and leave, stake moves, keys are rotated. So, a chained BFT protocol divides its lifetime into epochs, each served by a (possibly) different committee, and at the end of an epoch performs a handoff: the current committee stops voting and a new one, determined by the state of the chain itself, takes over. In many protocols, this handoff process is a paragraph in the system's documentation, or maybe a footnote in a paper. It also turns out to be the place where safety is most fragile.

This post is about Torch, the handoff mechanism we designed for Plasma's consensus layer. Torch is, to the best of our knowledge, the first handoff for a chained BFT protocol that neither waits for the boundary to be finalized nor requires consecutive committees to share anything: members, signatures, or a leader schedule. Along the way we will try to explain why a problem that sounds simple has resisted a clean solution for so long.

What's hard about a handoff?

The safety of a BFT committee rests on one logical fact. Two quorums of 2f + 1 validators drawn from the same committee of 3f + 1 share at least f + 1 members, and since at most f are Byzantine, at least one honest validator sits in both. That honest validator voted for at most one block per view, so two conflicting blocks cannot both be certified in the same view.

The moment the committee changes, that stops being true. Take two committees of 3f + 1 that share all but one member. A quorum of the first and a quorum of the second may intersect in as few as f validators, and all f of them may be Byzantine. Replacing a single validator is enough to lose that key honest witness. Nothing then prevents the two committees from finalizing conflicting histories.

This would be manageable if everyone agreed on when the old committee stops and exactly which committee comes next. The trouble lies in that validators in a chained protocol do not, in general, know these things perfectly (or in time).

Chained protocols pipeline their rounds. A block extends a parent, receives one round of votes, and the quorum certificate (QC) that aggregates those votes becomes the justification for the next block. This does not make a block irrevocable: a block is finalized when the chain above it exhibits a fixed pattern of certificates: three QCs at consecutive views in the original HotStuff, or two in most of its descendants, such as Fast HotStuff. Finality therefore trails the certified head of the chain, and during periods of asynchrony (before the network's Global Stabilization Time, GST, in the standard partial-synchrony model) the head can keep growing, possibly along several competing branches, while finality makes no progress at all. Before the network stabilizes, no protocol can guarantee finalization; this is what the FLP impossibility result and the Dwork–Lynch–Stockmeyer model tell us. Chained protocols embrace it by letting the chain fork optimistically and sorting things out later.

Now suppose the epoch boundary is fixed at some height, and that height falls inside the gap between the last finalized block and the certified head. Every active branch crosses the boundary. Each branch reads the next committee from its own view of the chain state, but these branches may well record different validator sets. If the handoff succeeds on both, two disjoint committees are now operating independently, each certifying and finalizing its own history. There is no honest validator in common to stop them, because our adversary never meant there to be one.

Consider how little effort it takes to construct such a run on paper. Given a networking schedule with four validators, all honest, where the leaders rotate normally, with legal recoveries, the chain can fork and keep forking for as long as the scheduler wants it to. The trick is delivery order: each failed view's recovery excludes the one validator who saw the most recent QC, so the aggregate picks the other branch's parent, and the two branches alternate. No adversarial validators, just a property of partial synchrony, not malice.

Figure 1: A fork with no faulty validators

Figure 1: A fork with no faulty validators

This is not a theoretical hazard. In March 2026, a report described validator sets on BNB Smart Chain activating without waiting for finality, so that multiple committees could activate at the same height and fork the chain. The proposed fix was a finality gate, which brings us to the first of the two options people usually consider.

Two routes, both suboptimal

Handoff mechanisms for BFT protocols recover the missing agreement from one of two sources: the finality of the boundary, or an overlap between consecutive committees.

Wait for finality. Fix the boundary at a predetermined height, or wherever a reconfiguration command lands, and let the new committee act only once the boundary block is finalised. Classical SMR has done this since Lamport, where membership change is an ordinary command, subject to the usual decision rules. BFT-SMaRt and Dyno adapt the idea in the Byzantine setting, and Tendermint applies validator-set changes finalised at height H from height H + 2, which is finality-gated by construction since Tendermint doesn’t attempt pipelining.

The finality gate is reasonable in protocols that finalise every block before proposing the next. But in a chained protocol, it can be expensive because the boundary is finalised only by certifying blocks above it, and above the boundary the old committee has, by definition, no mandate to extend the ledger. So, the protocol has to fall back to a non-pipelined mode, or drain through payload-free blocks proposed and voted by the old committee for the sole purpose of finalising the boundary. LibraBFT called the handoff a stop-the-world operation and accepted the cost; Avalanche's Simplex replaces the empty blocks with a metadata-only suffix and retains the gate. Either way, throughput drops to zero at every epoch change, and if the boundary falls in an asynchronous period, then the pause lasts as long as the asynchrony does.

Overlap the committees. Alternatively, keep both committees involved. Some designs require membership to change gradually, so that consecutive committees share a quorum of (presumably) honest validators; the HotStuff-based successor of Dyno assumes exactly this. Others, such as PaLa, do not require shared members but have both committees co-sign proposals during a transition window. That’s quorum-level overlap rather than membership-level, and it tolerates disjoint committees only once the handoff has fully completed.

Overlap avoids finality, but disallows the one thing you most need in an emergency: replacing a committee wholesale before some fault threshold is crossed. In 2022, an attacker obtained the keys of five of the nine Ronin bridge validators. The bridge was paused for months while the validator set was replaced and expanded. A wholesale replacement with an honest new committee could have drastically cut down on the downtime, or avoided it entirely with regular, proactive key rotation.

Figure 2: Four ways across an epoch boundary

Figure 2: Four ways across an epoch boundary

Neither cost is easy to avoid, and the reason is the fork you see above. A handoff without either safeguard has to survive asynchrony, and under asynchrony competing branches can disagree about the committee. The conventional wisdom is that this disagreement is fatal. Komatovic, Lewis-Pye, Neu, Roughgarden and Tas, in their 2025 work compiling permissioned protocols into proof-of-stake ones, confront precisely this question and resolve it the other way: they reject deriving committees from tentatively confirmed blocks, on the grounds that conflicting tentative blocks could install conflicting committees, and gate the handoff on finality plus an extra certification round.

Our claim is that the disagreement can be rendered harmless, provided you are careful about exactly which block you read the committee from, and exactly what a validator is allowed to vote for while the question is open.

Fast-HotStuff in five minutes

Torch is an overlay on Fast-HotStuff (FHS), so we’ll briefly introduce the base protocol. The two-chain rule and the aggregate QC are the parts to remember for later.

Each block carries a payload, the view at which it was proposed, and its justification: evidence that it extends a legitimate parent. Views are the numbered rounds in which consensus proceeds, and each view has a leader. In view v the leader proposes a block justified by the QC formed at view v − 1 and broadcasts it. Validators check the proposal and, if it passes, send a vote (a signature on the block's view and digest) to the leader of view v + 1, who aggregates 2f + 1 votes into a QC and proposes on top. In the happy path, every view adds one certified block and the chain grows at network speed.

When a view fails (the leader is slow, or silent, or Byzantine), validators time out and send a NewView message to the leader of the next view, carrying the greatest QC they have seen. That leader aggregates 2f + 1 NewViews into an aggregate QC (AggQC) and proposes a block extending the block certified by the greatest QC in the aggregate. The aggregate is a proposer-side proof that the proposal extends the highest QC any quorum member holds, and it is the reason FHS finalises in two rounds where HotStuff needs three.

In most cases, the finality rule is the two-chain. A block B is directly finalised by a witness B ← B′ ⇐ B″: a QC for B carried by B′, with B′ at the very next view after B, and a QC for B′ carried by any valid child B″ (no constraint on B″'s view, and B″ need not itself be certified). Everything below a directly finalised block is finalised as well.

Figure 3: The two-chain rule

Figure 3: The two-chain rule

Under a fair leader schedule, after GST, a fixed committee eventually finalises new blocks. Under arbitrary asynchrony, finalised blocks are chain-comparable: one is an ancestor of the other. Our approach imports both, per epoch, but it must ensure that "per epoch" composes into "for the whole history of the chain".

Torch

Within an epoch, Torch runs FHS mostly unchanged. Across a boundary it does five things differently, and a validator runs one FHS instance per active epoch: usually one, briefly two. We’ll go through the five in the order that makes the design easiest to see, and for each one identify what breaks if it’s left out.

Self-locating boundaries

Torch does not fix epoch boundaries at heights. Instead every block carries two small tags: a target epoch and the start height of its epoch. A block's source epoch is defined to be its parent's target epoch; that’s the epoch the block belongs to, and hence the committee that proposes and votes for it. The target epoch is the epoch of any block extending it.

The protocol has one parameter, a minimum epoch length M ≥ 3, and the tags are determined by the parent through three local rules. The source epoch is the parent's target. The start height is the block's own height if its source epoch differs from its parent's, and the parent's start height otherwise. And the target epoch equals the source epoch plus one when two conditions hold: the block's height is at least start + M − 1, and the block extends its parent at the consecutive view. Otherwise, the target equals the source.

A block whose target exceeds its source is a transition attempt. It’s the last block of its epoch, and its votes are the handoff. Every other block is a churn block, and a churn block past the minimum length postpones the boundary by one height; we say the epoch stretches.

So an epoch has no fixed end. It ends the first time the run produces a clean consecutive-view block past the minimum length, and while the network is misbehaving and views keep failing, the epoch simply grows longer. This is the first half of "no pause". Under asynchrony, the chain keeps producing churn blocks with the old committee, rather than draining or halting.

The consecutive-view condition looks like a technicality but actually forms the heart of our design. Recall the two-chain rule: B is finalised by QCs for B and for a child at the very next view. An attempt extends its parent at the consecutive view. So the QC for the attempt, together with the QC for its parent, is exactly a finality witness for the committee election. Certifying the attempt thus finalises the block beneath it. Keep that in mind for a moment.

The tags are validated: an honest validator votes only for a block whose tags follow the three rules from its parent's. Since every valid block carries a certificate for its parent, the tags of any valid block are backed by certified ancestry all the way to genesis. They cost O(log h) bits per block for a chain of height h, and they are the only change to the wire format. Torch introduces zero new message types.

The snapshot

The committee of epoch e + 1 is derived from the chain, and from one block in particular: the snapshot block of epoch e, which sits at height s + M − 2, where s is the start height of epoch e on that branch. The snapshot is the parent of the epoch's earliest possible transition attempt.

We read this snapshot per branch, because two competing branches may carry different blocks at the snapshot height and derive different candidate committees for epoch e + 1. This behavior is valid in Torch, though it requires a guarantee that only one of those candidates is ever allowed to act.

The argument has two stages. First, every certified transition attempt from epoch e, on any branch, descends from the same ancestor: the parent of the earliest certified attempt, which sits at or above the snapshot height. So every branch that ever hosts a block of epoch e + 1 agrees on the snapshot, and therefore on the committee. Competing attempts on branches forked below the snapshot might name different candidates, but the first attempt to be certified pins its choice, and thus no attempt on a rival branch can gather a quorum afterwards. Second, the boundary witness we noted above (the QC for the attempt plus the QC for its parent, carried by the attempt's first child) directly finalises the snapshot, on every branch where anyone observes a block of the new epoch.

Combining these, you arrive at the core property of Torch: every committee's defining data is finalised before that committee certifies a block. We did not need to wait for finality; rather, the very certificate that activates the new committee serves a finality witness for the block that carried it.

We also know this is the highest snapshot that works. If we move the read one height up, to s + M − 1, both guarantees fail. After a lost boundary, that height hosts the first attempt on one branch and its churn sibling on another, so the branches genuinely differ there; and the boundary witness finalises only the attempt's parent, one height below.

Figure 4: The snapshot and the boundary witness

Figure 4: The snapshot and the boundary witness

A derivation function that consults the snapshot two or more epochs back satisfies the same definition, which supports every theorem unchanged. The tradeoff is staler committees for earlier notice to prospective members. The single-epoch lookahead is thus the minimum the mathematics requires.

Vote delivery

Votes for a churn block are routed as in FHS: to the leader of the next view, in the block's own committee. Votes for a transition attempt differ, however. Every voter sends its vote to every member of the incoming committee, as derived on the attempt's branch. Each incoming member acts as a collector: it buffers votes even before its instance for the new epoch exists, forms the QC itself on receiving 2f + 1 valid votes from members of the old committee, and processes that QC locally. The QC has source epoch e and target epoch e + 1; we call this a cross-epoch QC. Receiving it activates the member's epoch-(e + 1) instance without any proposal or NewView, and whichever incoming member leads the next view proposes the first block of the new epoch on top of it.

This is the only mechanism in Torch that crosses an epoch boundary, and unfortunately does introduce an O(n²) complexity for this specific view change. An attempt costs n_old × n_new vote transmissions instead of n_old. Ordinary blocks and NewViews keep FHS's single-recipient pattern, and a successful handoff adds no new communication rounds: the attempt's votes are the same votes FHS would have collected anyway, delivered more promiscuously.

Originally, we hoped to avoid full fanout. The obvious design sends attempt votes to one collector, the incoming leader of the next view, exactly as FHS sends every vote to one leader. Unfortunately, that design can have a dire effect on liveness. Consider two committees with aligned round-robin schedules, one Byzantine member in each, and arrange that whenever an honest old leader proposes an attempt, the incoming collector for that view is the Byzantine one. It discards the votes. The old committee times out, recovers through a churn block, produces another attempt four views later, and its votes go to the same Byzantine collector. Both leader schedules are fair round-robin, we have only one Byzantine node in each committee, and every honest message arrives within the post-GST delay bound, yet handoff never happens. Given this schedule, the adversaries can delay the handoff indefinitely.

A natural idea would be to send to f + 1 collectors, so that at least one must be honest. That fixes the schedule above, but opens another. If an honest backup collector restarts its view timer whenever it processes a greater QC (a reasonable timer policy), then a steady stream of attempt QCs from the retrying old committee would keep postponing its timeout, and it never transmits the NewView that allows the rest of the incoming committee to catch up. Full fanout closes both holes, because every honest incoming member forms the QC and activates without depending on any participant in particular. It also frees us from any need to align leader schedules across committees.

Figure 5: Where attempt votes go

Figure 5: Where attempt votes go

NewViews, by contrast, never cross a boundary. A member of committee e that times out sends its NewView to the next leader in committee e, carrying its greatest QC eligible for epoch e, which by construction is never a cross-epoch QC. An AggQC of epoch e therefore never contains an attempt's certificate. This is what allows the old committee to retry a handoff that has gone missing: if the attempt's votes reach some incoming members but the first block of the new epoch is delayed or lost, the old committee times out, aggregates, and proposes again from its own last certified block.

Every epoch everywhere, all at once

At each validator, every epoch is in one of three states: NotStarted, Active or Completed. Epoch 0 begins Active with the genesis QC; every other epoch begins NotStarted. The overlay routes every incoming event (a proposal, a newly formed QC or AggQC, the QC carried by a NewView) to the instance of the event's target epoch, which is the target epoch of the QC it carries. For a proposal, that is the epoch whose committee should vote on it, attempt included.

An event whose target epoch is NotStarted creates and activates a fresh copy of the FHS state machine with the event's QC as its highest, the view after it as the current view, and the committee and leader schedule derived on the event's branch. During a handoff, a validator runs both instances, one for the outgoing epoch and one for the incoming; each instance has its own timer and sends its own NewViews. A validator that belongs to both committees runs both and may vote multiple times for the same view, once for each epoch. This is fine, because certification is unique per view and per epoch, not per view alone.

Validators outside a committee run an instance too, so that a candidate member can start participating the moment its epoch may start; the overlay simply drops the output of any instance whose committee does not include the validator. We call this readiness, and our liveness argument leans on this behavior.

State machines are torn down by a stop condition. When the overlay sees a QC with source epoch e + 1 certifying a block at or past the second height of epoch e + 1, it marks epoch e Completed and drops every subsequent event for it. But why the second block and not the first? This is because a QC for the second block means a quorum of the new committee voted for it, and to vote for it each of them had to validate its justification, which is a source-(e + 1) QC. So at least f + 1 honest incoming members now hold an own-epoch QC and, by the locking rule below, every one of them must refuse any first-block candidate the old committee might still produce. The old committee can thus no longer affect the new epoch, and its instance can safely be forgotten. If you stop on the first QC instead, you lose exactly that guarantee: a Byzantine incoming leader can form the first block's QC, send it to every old member and to no honest incoming one, and complete the old epoch at a moment when no honest incoming member yet holds anything that would block a competing first block.

Three small changes to the instance

The state machine instance for epoch e runs the base FHS protocol with three small but critical modifications.

Order QCs by epoch first, view second. Everywhere FHS chooses a greatest QC (in the per-instance highest-QC store, and when choosing the parent out of an aggregate), Torch compares source epoch first and view second. The intuition is that a certificate produced by an older committee is staler than one produced by a newer committee, whatever the views may say. This matters most when an AggQC of the new epoch contains both an attempt's cross-epoch QC (source e) and an own-epoch QC (source e + 1). Under the plain view order, an old committee retrying attempts at ever-higher views keeps producing cross-epoch QCs that outrank the new epoch's own progress, so every recovery in the new epoch drags it back to a fresh first block and the second block is never certified.

Epoch locking. A validator votes for a block B only if the source epoch of B's justification is at least the source epoch of its highest QC in the epoch of B. In other words: once you have seen a QC formed by your own committee, you refuse every first-block candidate built on an old committee's attempt, irrespective of its view. Within an epoch this adds no restriction at all, since same-source QCs at older views remain acceptable and FHS's own rules govern. Without this, the same incoming committee can be convinced to finalise conflicting first blocks.

Truncation. A witness B ← B′ ⇐ B″ does not finalise B. It finalises the ancestor of B at height min(height(B), start(B) + M − 2): the snapshot, if B lies above it. Everything an epoch produces above its snapshot is finalised only by inheritance, once the new epoch produces a two-chain of its own. This protects the old epoch's tail while the handoff remains unresolved. During asynchrony, the old committee cannot know whether the new committee has already finalised something on another branch; without truncation, an old-committee observer could finalise a churn sibling of the attempt from a witness that needed no new votes at all, because the finality check runs before the voting check and a structurally valid carrier is enough. With truncation, both witnesses collapse to the snapshot and the conflict disappears.

Truncation also resolves a consistency question. Consider witnesses whose two QCs come from different committees. They add no new information: the tag rules force the first block of such a witness to be an attempt, so its parent's QC forms a same-source witness with the same truncated target.

Three runs

Consider this figure, which shows three executions with M = 4 and an epoch 2 starting at height 10, so the snapshot is B12. Blocks are labelled by height, with their view underneath; solid arrows are consecutive-view extensions, dashed arrows skip views.

The good case. Every proposal succeeds. B13, extending B12 at the consecutive view and sitting at height 10 + 4 − 1, is the earliest possible attempt. Its votes deliver to every member of committee 3. The leader of view 24 in the new committee forms the QC, which with B12's QC is the boundary witness, and proposes B14, the first block of epoch 3; B14's proposal finalises B12 at every observer. B15 follows. The QC for B15, carried in B16's justification, is a source-3 certificate for the second block of the epoch, and it fires the stop condition to retire epoch 2. Notice that the handoff consumed no view of its own, while the other incoming members received the same votes in parallel.

Recovery through the new committee, on a forked chain. The branches alternate, so no two-chain forms above B10; the snapshot is resolved per branch (B12 on one, B12′ on the other) and the epoch stretches through churn blocks at heights 13 and 14 whose views skip. At view 34 the attempt B15 is certified, and that settles the committee: the rival branch ends at B13′, because no block extending it can gather a quorum. The collector's first block B16′ then fails. But the cross-epoch QC is held by every honest incoming member, so the epoch does not reopen; committee 3 recovers through its own view changes, its aggregate carries the QC for B15, and B16 is certified at view 37. The old committee plays no part in this process.

Retry through the old committee. Votes for the first attempt B13 reach the next incoming proposer, but delivery to the other incoming members, and of the first block B14′, is delayed. Committee 2 times out and recovers through its own AggQC, which carries only epoch-2 QCs and so knows nothing about the attempt. The recovery proposes B13′, a churn sibling of B13 (its view skips, so it doesn’t count as an attempt), and one view later the second attempt B14 is certified one height up. Both attempts derive the new committee from B12. The delayed first crossing can arrive whenever; since both attempts extend the same parent and hand over to the same committee, it races the second one harmlessly.

Figure 6: Three runs across one boundary

Figure 6: Three runs across one boundary

What we can prove

Safety. For any two directly finalised blocks, one is an ancestor of the other. This holds under arbitrary asynchrony, across any number of epoch boundaries, with no assumption that consecutive committees share a member, and with every committee derived per branch as described.

The proof is an induction on epochs, and establishes three things at once for every epoch e. First, e is coherent: every valid block with source epoch e derives the same committee for e. Second, FHS's extension property holds within e: if B′ extends B at the consecutive view and both have source e, then every certified source-e block at a higher view must descend from B. Third, every certified attempt from e, and every valid block of epoch e + 1, descends from the parent of the earliest certified attempt. Genesis fixes a coherent epoch 0. Coherence of e puts all of e's quorums in one committee, which gives QC uniqueness per view within e, which gives the extension and agreement properties; agreement pins the snapshot, which gives coherence of e + 1. Every quorum intersection therefore happens inside a single committee.

The safety theorem and its supporting lemmas are mechanised in Lean 4, with idealised signatures and committees derived per branch.

Liveness. Every epoch that becomes operative certifies a transition attempt, finalises its snapshot on that attempt's chain, and either hands over to an operative epoch e + 1 or is superseded by one. The assumptions are the ones FHS already needs, applied per committee: the honest members of every operative committee are view-synchronised after GST, and every leader schedule is fair, selecting two consecutive honest leaders infinitely often. Boundary fanout is what allows us to require nothing across committees. An honest quorum's votes for a timely attempt activate every honest incoming member on their own, regardless of who leads the next view. We are currently using a synchronizer contract to finalize this proof, but expect to be able to remove this assumption in the future, along with mechanisation.

What we do not claim. Torch derives committees, but it does not choose them. The adversary model is static per epoch: the adversary may pick a committee's faulty members before the snapshot for that committee is finalised, and the protocol requires, rather than guarantees, that every committee proposed contains at most f Byzantine members. Adaptive corruption over the lookahead window is out of scope. Defence against long-range forgery by retired committees (weak subjectivity, checkpointing) is orthogonal and assumed to be provided at the client layer, as in other proof-of-stake systems.

Overhead

Torch is not a throughput mechanism; its cost accounting is therefore structural. It requires two epoch tags per block, O(log h) bits, with no new message types. Within an epoch, the communication pattern of FHS is unchanged. At a boundary, one attempt's votes are sent to every incoming member instead of one collector, so a handoff attempt costs n_old × n_new vote transmissions where FHS would spend n_old. A successful handoff adds no communication round and does not require a pause. The first block of the new epoch is proposed in the view after the attempt, exactly as any other block would be.

Finality latency has one asymmetry at the boundary. The blocks of epoch e above its snapshot (the attempt, and any churn blocks the epoch stretched through) are not directly finalised by the old committee; they inherit finality once the new epoch produces its first own two-chain. In the happy path that is three views after the attempt. Under asynchrony it is as long as the new epoch takes to activate; this is also just as long as any block would have waited.

What’s next?

The Plasma chain currently uses fixed-length epochs with a snapshot establishing a handoff window before the boundary; the dynamic protocol-placed boundaries described here, where an epoch stretches through asynchrony instead of the chain pausing, are forthcoming.

We believe this mechanism generalises well. The safety argument touches FHS only through its extension lemma and its one-vote-per-view property, and every chained protocol with certificate-based justification has both of these. If we define the transition attempt as a block whose certification finalises its parent, and place the snapshot at that parent, then the rest should follow directly.

The work described above is currently undergoing peer review; we will be publishing a formal whitepaper and mechanised proofs of this work later this year. We look forward to seeing what the community can accomplish using our construction in the future.