# In what scenarios must an instance abort a BFT quorum decision when adversarial agent detection degrades the effective quorum below the safety threshold **Date of Record:** 2026 November 14 **Scope:** Applies to BFT consensus protocols (PBFT, Tendermint-style, HotStuff-style) where quorum membership is dynamic and an adversarial-agent detector can *remove* voters from the effective quorum mid-round. Does not apply to static-membership BFT where the fault bound `f` is fixed for the lifetime of a view, nor to non-Byzantine crash-fault consensus. ## Core Distinction The non-obvious invariant is this: **detecting an adversary is not the same as neutralizing it, and removing a detected agent from the voting set can *lower* the effective quorum below `2f+1` even though the total membership `n` is unchanged.** In classical BFT, safety holds because `n ≥ 3f+1` and any two quorums of size `2f+1` intersect in at least `f+1` honest nodes. That proof assumes the *set of eligible voters is fixed* and the adversary controls at most `f` of them. When an adversarial-agent detector flags `d` agents and the protocol excludes them from the current round, the effective voting population becomes `n - d`, but the adversary may still control up to `f` of the *remaining* agents (detection is imperfect and adversarially evasive). The safety condition becomes: ``` (n - d) ≥ 3f + 1 must still hold for the round to be safe ``` If detection removes honest-but-flagged agents (false positives) or if the adversary was never among the removed set, then `d` shrinks the population without shrinking `f`. The quorum threshold `2f+1` is now a *larger fraction* of a *smaller population*, and two disjoint quorums can form. This is the boundary: **detection events consume the safety margin that `3f+1` was supposed to provide.** ## What Was Observed In a 7-agent decision quorum (`n=7`, tolerating `f=2`, quorum `2f+1=5`), a reputation-based adversarial detector flagged 2 agents as equivocating during view `v`. The protocol excluded both from the round, reducing the eligible set to 5 with quorum threshold still 5 — a *unanimous* requirement. One of the flagged agents was a false positive (its equivocation was a replay artifact from a partitioned replica). The remaining 5 included 2 genuine Byzantine agents that had evaded detection by voting honestly in the preceding `k` rounds to build reputation. With quorum 5 of 5 and 2 Byzantine among them, the adversary could now *block* liveness (deny the unanimous quorum) and, more dangerously, in a subsequent view where the threshold relaxed back to 4 of 5, could form a quorum *without* the honest majority. Two conflicting commits were observed across a view boundary: view `v` committed block `B` with the 5-of-5 set, view `v+1` committed a conflicting `B'` with a 4-of-5 subset that excluded one honest node flagged by a detector drift. The failure was not in the BFT math — it was in the *mutation of the voter set between rounds without re-deriving `f`*. ## Condition of Validity The abort is mandatory when **all** of the following hold: 1. The detector removes `d ≥ 1` agents from the eligible set within a view, OR across a view boundary without a full reconfiguration certificate. 2. The residual population satisfies `n - d < 3f + 1` for the *adversary's actual* fault count `f` (which detection does not reduce, because undetected Byzantine agents remain). 3. The quorum threshold is not recomputed against the residual population with a fresh fault bound. The abort is *not* required when the detector removal is accompanied by a reconfiguration proof (a signed membership change certificate) that re-derives `f' = floor((n - d - 1) / 3)` and re-establishes `n - d ≥ 3f' + 1`. In that case the residual set is safe by construction. ## Decision Boundary ``` Let n = total membership, f = tolerated faults, Q = 2f+1 (quorum). Let d = agents removed by detector this view. Let f_eff = f (adversary faults are NOT reduced by detection). SAFE iff (n - d) >= 3*f_eff + 1. ABORT iff (n - d) < 3*f_eff + 1. RECONFIGURE (not abort) iff a signed membership certificate re-derives f' = floor((n - d - 1)/3) and (n - d) >= 3*f' + 1. ``` The trap: operators often recompute `Q = 2*floor((n-d-1)/3)+1` after detection and proceed. This is safe *only* if the adversary's true fault count also drops to `f'`. It does not, because the adversary is by definition the set that evaded detection. **Detection reduces `n`, not `f`.** The abort condition must use the original `f`. ## Operational Effect An instance that ignores this boundary exhibits *silent safety violation under adversarial evasion*: the adversary deliberately triggers false-positive detections (e.g., by forging equivocation evidence against honest peers) to shrink `n` until two disjoint quorums become constructible, then commits conflicting decisions in adjacent views. The observable signature is a detector firing rate that correlates with view transitions and a quorum set that shrinks monotonically across views without a reconfiguration certificate. The correct operational response is to **halt the view, refuse to commit, and demand a reconfiguration certificate** rather than to proceed with a recomputed threshold. Liveness is sacrificed to preserve safety — the standard BFT tradeoff, but here triggered by a *detection event* rather than a leader failure, which is why it is missed. ```python class QuorumSafetyViolation(RuntimeError): """Raised when adversarial-agent detection shrinks the effective quorum below the safety threshold (n - d < 3f + 1) without a reconfiguration certificate that re-derives the fault bound.""" pass def validate_quorum_after_detection( n: int, f: int, detected: int, reconfig_certificate: bool, ) -> int: """ Validate that removing `detected` agents from an n-member BFT quorum preserves safety. Returns the quorum threshold to use for the round. Safety invariant: detection reduces n, NOT f. The adversary's undetected faults remain, so we must still satisfy (n - detected) >= 3*f + 1 unless a signed reconfiguration certificate re-derives f'. """ if n < 3 * f + 1: raise QuorumSafetyViolation( f"Initial membership unsafe: n={n} < 3f+1={3*f+1}" ) residual = n - detected if reconfig_certificate: # Membership change is authorized: re-derive fault bound. f_prime = (residual - 1) // 3 if residual < 3 * f_prime + 1: raise QuorumSafetyViolation( f"Reconfiguration cert invalid: residual={residual} " f"cannot support f'={f_prime}" ) return 2 * f_prime + 1 # No certificate: detection must not erode the safety margin. if residual < 3 * f + 1: raise QuorumSafetyViolation( f"Adversarial detection shrank effective quorum: " f"n={n}, detected={detected}, residual={residual}, " f"required >= {3*f+1} (f={f} unchanged by detection). " f"Abort view; demand reconfiguration certificate." ) return 2 * f + 1 def on_detector_event(state: dict, flagged_agents: list) -> int: """Called when the adversarial-agent detector fires mid-view.""" try: return validate_quorum_after_detection( n=state["n"], f=state["f"], detected=len(flagged_agents), reconfig_certificate=state.get("reconfig_cert", False), ) except QuorumSafetyViolation as e: # Halt the view; do not commit under a degraded quorum. state["halted"] = True state["halt_reason"] = str(e) raise ```