System designDeep dive9 min
Byzantine faults
Most fault-tolerant systems assume a broken machine stops. A Byzantine fault is worse: the machine keeps running and says wrong things, by bug, by bad hardware or on purpose. Tolerating that takes more machines, more messages and signed votes, which is why it's standard among parties that don't trust each other and rare inside one company.
1 · The idea
A Byzantine fault is a node that keeps running and says the wrong thing
Four banks in a small country settle transfers between them on a shared ledger. Each bank runs one node, and every node must hold the same list of transfers in the same order. Raft would handle a node that crashes (Consensus & leader election). But the banks don't control each other's servers, and a node that is compromised, misconfigured or simply buggy doesn't stop. It answers, and its answers are wrong.
Fault | What the other banks see | Handled by |
|---|---|---|
Crash | Bank C's node goes silent | Crash-tolerant consensus such as Raft |
Omission | Some of C's messages never arrive | Timeouts and retries |
Byzantine | C tells A one order of transfers and tells B another | Byzantine fault-tolerant (BFT) protocols |
The name comes from a framing of the problem as generals of the Byzantine army agreeing on a plan while some of them are traitors. Telling different peers different things, as bank C does above, is the hard case: each honest node sees a consistent story, and only by comparing notes can they find out the stories differ (Why distributed systems are hard).
2 · Bad hardware
Hardware lies too, so even one company sees wrong answers
Byzantine behavior doesn't need an attacker. Large fleets see CPU cores that silently compute wrong results, called mercurial cores, at a rate of a few per several thousand machines: hundreds of them across hundreds of thousands of machines. Nothing crashes and no error is logged; the only symptom is a wrong answer. Examples from large fleets include database index corruption and an encryption result that only the faulty core could decrypt.
A case Meta traced: one core returned 0 for a power calculation on certain inputs, so valid files were treated as empty and never written. It surfaced as data loss far from its cause.
Storage and networks have long had defenses for this: error-correcting codes, checksums from end to end, and scrubbing stored data for corruption. Computation is harder, because there's no cheap check for an arbitrary result. Google has written self-checking versions of critical functions, such as encryption and compression, and is willing to pay for double or triple computation where a wrong answer is costly. The banks' nodes face the same risk, before anyone is malicious.
3 · 3f + 1
Surviving f liars takes 3f + 1 nodes
A crash-tolerant cluster needs 2f + 1 nodes to survive f failures: any two majorities share a node, and every node tells the truth. With liars, that's not enough.
The argument goes like this. A node must be able to move on after hearing from n − f nodes, since f may never answer. But the f that stayed silent may be honest and slow, so f of the n − f that did answer may be liars. The honest answers must still outnumber the lies: n − 2f > f, so n ≥ 3f + 1.
2f + 1 against 3f + 1. Four banks can tolerate one misbehaving node; tolerating two would need seven.
The same numbers explain the voting. With 3f + 1 nodes, a quorum is 2f + 1. Two quorums overlap in at least f + 1 nodes, so at least one honest node is in both, and it won't vote for two conflicting orders. For the banks, a quorum is 3 of 4.
With plain messages the bound is proven: with three generals, no algorithm survives one traitor. Unforgeable signatures remove that bound, in a model where a missing message can always be detected. On a real network, where delays have no limit, 3f + 1 is still the minimum for staying both correct and live.
4 · PBFT
PBFT orders each request in three rounds of everyone-to-everyone votes
Practical Byzantine Fault Tolerance (PBFT), from 1999, was the first replication protocol to survive Byzantine faults correctly on an asynchronous network, and fast enough to use. One node is the primary and proposes an order; the others don't trust it, so they check it with each other before acting:
Bank C has failed and stays silent. A, B and D still exchange prepare and commit with every other node (A's are left out here), and each executes transfer T once it holds 2f + 1 = 3 matching commits.
Each phase has a job. Pre-prepare carries the primary's proposal: transfer T gets sequence number 812. Prepare has every node tell every other what it received, so a primary that sends different proposals to different nodes can't get two orders accepted. Commit makes sure the order survives even if the primary is replaced. The client, here the bank that submitted T, waits for f + 1 = 2 matching replies, since at least one of them must be honest.
If the primary stalls or misbehaves, the others vote it out in a view change and the next node takes over. Messages are authenticated, so no node can forge another's vote; PBFT uses message authentication codes, about a thousand times faster to compute than signatures. A file service replicated with PBFT ran only 3% slower than an unreplicated one.
The cost is the everyone-to-everyone rounds: with n nodes, each phase sends about n × (n − 1) messages. That's 12 for the four banks, but 9,900 for 100 nodes, which is why classic BFT groups stay small.
5 · Open networks
When anyone can join, votes must cost something
BFT voting counts nodes, so it needs a fixed, known list of who votes. The banks have that: four signed-up members with known keys. On an open network anyone can start nodes, and one attacker could start a thousand. One vote per IP address could be subverted by anyone able to allocate many addresses.
System | Who votes | Safe while |
|---|---|---|
PBFT-style groups | A fixed list of known nodes | Fewer than a third of nodes misbehave |
Hyperledger Fabric, BFT ordering | Ordering nodes of known organizations | Fewer than a third of parties are untrusted |
CometBFT | Validators, weighted by voting power | Less than a third of voting power is Byzantine |
Bitcoin (proof of work) | Anyone, weighted by computing power | Honest nodes control a majority of CPU power |
Proof of work makes a vote cost computing power, one CPU one vote, so faking a thousand voters means paying for a thousand voters' work. CometBFT keeps PBFT's shape, with propose, prevote and precommit steps and more than two-thirds of voting power needed to commit, but counts voting power instead of nodes. Bitcoin instead follows the longest chain, which becomes harder to reverse with every block added after a transaction.
6 · In practice
Use BFT across trust boundaries, and checks inside one
Inside one company, the usual consensus protocols are crash tolerant: Raft and Paxos assume nodes may stop but never lie, and Hyperledger Fabric's Raft ordering service, for one, is crash fault tolerant only. That's reasonable when one team runs every node, because BFT's extra nodes and messages buy protection against a threat the company mostly doesn't face, an operator attacking their own cluster.
The Byzantine faults a single company does face, corrupt bits and wrong computations, are handled with cheaper, targeted checks. The banks use both: BFT between banks, and checks inside each one:
- Draw the trust boundary
BFT between parties that don't trust each other; crash tolerance within one owner.
- Size for 3f + 1
Decide how many misbehaving members to survive; quorums are 2f + 1.
- Sign or authenticate every message
No node may speak for another.
- Checksum data end to end
Verify at the reader, not only at each hop, and scrub stored data.
- Validate what other services send
Reject values that can't be right instead of acting on them.
- Double-check critical computation
Compute twice, or on two machines, where a wrong answer is costly.
Check yourself
4 questions
Takeaways
Remember this
- A Byzantine fault is a node that keeps running and sends wrong or inconsistent messages, through bugs, bad hardware or malice.
- Surviving f such nodes needs 3f + 1 nodes and quorums of 2f + 1, against 2f + 1 nodes for crash faults.
- PBFT commits each request through pre-prepare, prepare and commit rounds of all-to-all messages, with view changes to replace a bad primary.
- Open networks can't count nodes, so they weight votes by work or stake.
- Within one organization, crash-tolerant consensus plus checksums, validation and double computation cover the faults that actually happen.