Summary
Tenderlink currently treats 2f+1 nil precommits as evidence that the current round did not decide, but not as evidence that validators should stop carrying the round's prior PoW finalization candidate.
This is normal Tendermint-style behavior, but Crosslink has an extra liveness pressure: the value being finalized is a PoW checkpoint sampled from the node's current best chain. During fork recovery, the PoW best chain may move forward or switch forks while BFT is stuck around an older candidate.
The design question is whether a 2f+1 PRECOMMIT nil certificate for (height, round) should explicitly cancel the liveness momentum created by that same round's prevotes, allowing a later round to sample a fresher tip - sigma checkpoint from the PoW chain.
Current behavior
At a Tenderlink BFT height, each round runs:
Propose -> Prevote -> Precommit
Current code behavior at the pinned Tenderlink rev:
2f+1 yes prevotes set locked_value_round and valid_value_round, then precommit that value.
2f+1 nil prevotes cause a nil precommit.
2f+1 precommits of any kind only start the precommit timeout.
- Only
2f+1 yes precommits decide a value and advance the BFT height.
locked_value_round and valid_value_round reset only after a value is decided.
Relevant local areas:
zebra-crosslink/Cargo.toml pins the external tenderlink dependency.
- Tenderlink
TMState::bft_update handles the Tendermint conditions for prevote, precommit, decision, catch-up, and timeouts.
- Zebra's proposer closure samples the current PoW tip and proposes
tip - sigma only when Tenderlink does not already have a carried valid_value_round.
Why this matters
During fork recovery, the PoW best chain may move forward or switch forks while the BFT round is between prevote and precommit.
The conservative behavior is:
- If a value got
2f+1 prevotes, keep it sticky.
- If it never got
2f+1 yes precommits, keep trying to complete or repropose it in later rounds.
- After it decides, the next BFT height can jump forward to a fresher PoW checkpoint.
That preserves the familiar Tendermint safety argument, but it can make Crosslink finalization chase a stale checkpoint even after validators have effectively nil-precommitted the round.
A stronger nil-precommit rule may have better liveness: a 2f+1 PRECOMMIT nil certificate is evidence that this round is dead as a finalization attempt. If the protocol keeps carrying that round's valid_value_round or same-round lock momentum forward anyway, it can keep trying to finalize a PoW snapshot that the live network has already moved past.
Validator-set scaling intuition
The larger motivation is validator-set size.
For a globally distributed validator set, the time to reach GST-like effective synchrony grows with communication delay across the validator set, and the worst-case message pattern can be quadratic. As the validator set grows, the prevote-to-precommit window is more likely to overlap with a PoW stream change: a new longest chain appears, or the current tip - sigma candidate advances.
In the current protocol shape, that stream change can turn into a halt. Validators may have enough prevotes to carry a stale candidate, but not enough matching precommits to decide it. The later rounds then keep trying to recover around stale BFT state rather than simply sampling the current PoW stream.
With nil-precommit resampling, a stream change does not have to halt the BFT height. It can just cause the round number to increment until the network and the PoW stream are stable enough for one candidate to survive through prevote and precommit.
This matches the current Crosslink implementation model: proposals are sampled as head - sigma. The PoW stream continues progressing uninterrupted between BFT rounds, and the BFT layer should be able to re-enter that stream after an abandoned round instead of staying pinned to an obsolete sample.
Ebb-and-Flow composition
This rule is best understood as a liveness bridge between the two Crosslink pieces:
- The PoW side is the flow layer. It keeps producing availability and may reveal a fresher
tip - sigma candidate after a partition heals or a longer fork appears.
- The BFT side is the ebb/finality layer. It decides which PoW snapshot becomes finalized and must preserve monotonic finality.
- A nil-precommit certificate says the BFT layer failed to finalize this round, so the next round should be allowed to resample from the current PoW flow.
So the intended behavior is not "flow overrides finality". It is "failed ebb attempts should not force future ebb attempts to keep using stale flow input".
The safety boundary is important:
- A nil-precommit certificate should cancel same-round prevote momentum.
- It should allow later proposals to ignore the abandoned round's stale
valid_value_round.
- It may be safe to unlock validators whose lock was created by that same round, because a same-round
2f+1 PRECOMMIT nil certificate should rule out a same-round value commit certificate unless validators equivocate.
- It should not automatically cancel locks from older rounds. Older locks still need the usual Tendermint-compatible unlock proof.
Proposed rule shape
Introduce a formal nil-precommit certificate:
- A
2f+1 PRECOMMIT nil certificate for (height, round) is recorded as an abandon certificate for that round.
- Later proposals may carry this certificate.
- Validators that verify the certificate may clear round-local carried state derived from that same round.
- At minimum, this should clear the corresponding stale
valid_value_round so proposers can sample the latest PoW tip - sigma.
- Optionally, the rule may also unlock validators whose
locked_value_round was created in the same round as the nil-precommit certificate.
- The rule must not unlock validators locked in an earlier round unless the proposal carries an independently valid Tendermint-compatible unlock proof.
One crisp formulation:
For a fixed BFT height and round r, 2f+1 PRECOMMIT nil cancels the finalization attempt for round r. It can cancel state created by round r, but it cannot cancel safety locks from rounds < r.
Safety obligations
This should not be implemented as local cleanup without a protocol rule.
The analysis should prove or reject at least the following:
- For fixed
(height, round), 2f+1 PRECOMMIT nil and 2f+1 PRECOMMIT value cannot both exist unless more than f voting power equivocates or an honest validator violates the protocol.
- A same-round nil-precommit certificate is sufficient evidence that a value locked due to that round's prevotes cannot still be decided in that round.
- Unlocking only same-round locks does not allow conflicting commits across later rounds.
- Validators that do not immediately see the nil-precommit certificate remain safe, and later converge when a proposal carries the certificate.
- Clearing
valid_value_round without clearing locked_value_round is safe but may leave liveness on the table; clearing same-round locks may improve liveness further and needs the stronger proof.
- Proposal validation must define exactly which certificates can be carried and when they are accepted.
Acceptance criteria
- Document the intended behavior for
2f+1 nil precommits in Crosslink/Tenderlink.
- Decide whether nil precommit should:
- only advance the round, as in the current Tendermint-like behavior;
- clear
valid_value_round but not unlock;
- unlock only same-round locks via a formal nil-precommit certificate;
- unlock older locks only with a separate Tendermint-compatible proof; or
- be left unchanged until Crosslink proposal-validity rules are fully implemented.
- If a new rule is chosen, specify the certificate format, verification rules, and required updates to proposal validation.
- Add targeted tests covering:
- stale PoW checkpoint carried across rounds after yes prevotes;
- nil-precommit quorum with no decision;
- fork recovery where the best PoW chain changes between prevote and precommit;
- same-round nil-precommit certificate clearing stale proposal state;
- safety against conflicting commits across rounds.
Recommendation
Treat this as a protocol-design improvement, not a local bug fix.
My current bias is:
- Model
2f+1 PRECOMMIT nil as a first-class round-abandon certificate.
- Use it to clear stale
valid_value_round so Crosslink can resample from the current PoW flow after a failed finality attempt.
- Investigate same-round unlock as the liveness-improving version of the rule.
- Preserve older locks unless the protocol has a clear Tendermint-compatible unlock proof.
This version appears to compose well with Ebb-and-Flow: PoW availability keeps moving, BFT finality remains monotonic, and a failed BFT round does not keep dragging stale PoW input into future rounds.
Summary
Tenderlink currently treats
2f+1nil precommits as evidence that the current round did not decide, but not as evidence that validators should stop carrying the round's prior PoW finalization candidate.This is normal Tendermint-style behavior, but Crosslink has an extra liveness pressure: the value being finalized is a PoW checkpoint sampled from the node's current best chain. During fork recovery, the PoW best chain may move forward or switch forks while BFT is stuck around an older candidate.
The design question is whether a
2f+1 PRECOMMIT nilcertificate for(height, round)should explicitly cancel the liveness momentum created by that same round's prevotes, allowing a later round to sample a freshertip - sigmacheckpoint from the PoW chain.Current behavior
At a Tenderlink BFT height, each round runs:
Current code behavior at the pinned Tenderlink rev:
2f+1yes prevotes setlocked_value_roundandvalid_value_round, then precommit that value.2f+1nil prevotes cause a nil precommit.2f+1precommits of any kind only start the precommit timeout.2f+1yes precommits decide a value and advance the BFT height.locked_value_roundandvalid_value_roundreset only after a value is decided.Relevant local areas:
zebra-crosslink/Cargo.tomlpins the externaltenderlinkdependency.TMState::bft_updatehandles the Tendermint conditions for prevote, precommit, decision, catch-up, and timeouts.tip - sigmaonly when Tenderlink does not already have a carriedvalid_value_round.Why this matters
During fork recovery, the PoW best chain may move forward or switch forks while the BFT round is between prevote and precommit.
The conservative behavior is:
2f+1prevotes, keep it sticky.2f+1yes precommits, keep trying to complete or repropose it in later rounds.That preserves the familiar Tendermint safety argument, but it can make Crosslink finalization chase a stale checkpoint even after validators have effectively nil-precommitted the round.
A stronger nil-precommit rule may have better liveness: a
2f+1 PRECOMMIT nilcertificate is evidence that this round is dead as a finalization attempt. If the protocol keeps carrying that round'svalid_value_roundor same-round lock momentum forward anyway, it can keep trying to finalize a PoW snapshot that the live network has already moved past.Validator-set scaling intuition
The larger motivation is validator-set size.
For a globally distributed validator set, the time to reach GST-like effective synchrony grows with communication delay across the validator set, and the worst-case message pattern can be quadratic. As the validator set grows, the prevote-to-precommit window is more likely to overlap with a PoW stream change: a new longest chain appears, or the current
tip - sigmacandidate advances.In the current protocol shape, that stream change can turn into a halt. Validators may have enough prevotes to carry a stale candidate, but not enough matching precommits to decide it. The later rounds then keep trying to recover around stale BFT state rather than simply sampling the current PoW stream.
With nil-precommit resampling, a stream change does not have to halt the BFT height. It can just cause the round number to increment until the network and the PoW stream are stable enough for one candidate to survive through prevote and precommit.
This matches the current Crosslink implementation model: proposals are sampled as
head - sigma. The PoW stream continues progressing uninterrupted between BFT rounds, and the BFT layer should be able to re-enter that stream after an abandoned round instead of staying pinned to an obsolete sample.Ebb-and-Flow composition
This rule is best understood as a liveness bridge between the two Crosslink pieces:
tip - sigmacandidate after a partition heals or a longer fork appears.So the intended behavior is not "flow overrides finality". It is "failed ebb attempts should not force future ebb attempts to keep using stale flow input".
The safety boundary is important:
valid_value_round.2f+1 PRECOMMIT nilcertificate should rule out a same-round value commit certificate unless validators equivocate.Proposed rule shape
Introduce a formal nil-precommit certificate:
2f+1 PRECOMMIT nilcertificate for(height, round)is recorded as an abandon certificate for that round.valid_value_roundso proposers can sample the latest PoWtip - sigma.locked_value_roundwas created in the same round as the nil-precommit certificate.One crisp formulation:
Safety obligations
This should not be implemented as local cleanup without a protocol rule.
The analysis should prove or reject at least the following:
(height, round),2f+1 PRECOMMIT niland2f+1 PRECOMMIT valuecannot both exist unless more thanfvoting power equivocates or an honest validator violates the protocol.valid_value_roundwithout clearinglocked_value_roundis safe but may leave liveness on the table; clearing same-round locks may improve liveness further and needs the stronger proof.Acceptance criteria
2f+1nil precommits in Crosslink/Tenderlink.valid_value_roundbut not unlock;Recommendation
Treat this as a protocol-design improvement, not a local bug fix.
My current bias is:
2f+1 PRECOMMIT nilas a first-class round-abandon certificate.valid_value_roundso Crosslink can resample from the current PoW flow after a failed finality attempt.This version appears to compose well with Ebb-and-Flow: PoW availability keeps moving, BFT finality remains monotonic, and a failed BFT round does not keep dragging stale PoW input into future rounds.