The most surprising thing about Byzantine Fault Tolerance (BFT) is that it’s not about preventing malicious nodes from misbehaving, but about ensuring the system can still function correctly even when a certain number of them are actively trying to break it.

Imagine a group of chefs trying to agree on the exact time to serve a critical dish. Each chef has a clock, but some might be deliberately lying about the time, or even sending different times to different chefs. BFT is the protocol that lets the honest chefs agree on the real time, despite the liars.

Here’s how it might look in a simplified scenario:

Let’s say we have 4 nodes (N=4) and we want to tolerate 1 malicious node (F=1). This means we need at least 2F+1 = 3 nodes to agree for a decision to be considered valid.

Phase 1: Propose Node 1 (the leader) proposes a value, say "Serve at 12:00 PM". It sends this proposal to all other nodes.

Node 1 -> Node 2: PROPOSE(value="12:00 PM")
Node 1 -> Node 3: PROPOSE(value="12:00 PM")
Node 1 -> Node 4: PROPOSE(value="12:00 PM")

Phase 2: Pre-Prepare Each node that receives the proposal (in this case, Nodes 2, 3, and 4) sends a "pre-prepare" message to all other nodes, indicating they’ve received the proposal from Node 1.

Node 2 -> Node 1: PRE-PREPARE(proposal_id=X, value="12:00 PM")
Node 2 -> Node 3: PRE-PREPARE(proposal_id=X, value="12:00 PM")
Node 2 -> Node 4: PRE-PREPARE(proposal_id=X, value="12:00 PM")

Node 3 -> Node 1: PRE-PREPARE(proposal_id=X, value="12:00 PM")
Node 3 -> Node 2: PRE-PREPARE(proposal_id=X, value="12:00 PM")
Node 3 -> Node 4: PRE-PREPARE(proposal_id=X, value="12:00 PM")

Node 4 -> Node 1: PRE-PREPARE(proposal_id=X, value="12:00 PM")
Node 4 -> Node 2: PRE-PREPARE(proposal_id=X, value="12:00 PM")
Node 4 -> Node 3: PRE-PREPARE(proposal_id=X, value="12:00 PM")

(Note: In some BFT algorithms, the leader itself sends a "prepare" message, and other nodes send "pre-prepare". The naming can vary, but the concept of agreeing on the order and content of messages is key.)

Phase 3: Prepare Upon receiving enough PRE-PREPARE messages (say, from 3 nodes, including themselves), each node sends a "prepare" message to all other nodes. This confirms they are ready to commit to this specific proposal.

Node 2 -> Node 1: PREPARE(proposal_id=X, value="12:00 PM")
Node 2 -> Node 3: PREPARE(proposal_id=X, value="12:00 PM")
Node 2 -> Node 4: PREPARE(proposal_id=X, value="12:00 PM")

Node 3 -> Node 1: PREPARE(proposal_id=X, value="12:00 PM")
Node 3 -> Node 2: PREPARE(proposal_id=X, value="12:00 PM")
Node 3 -> Node 4: PREPARE(proposal_id=X, value="12:00 PM")

Node 4 -> Node 1: PREPARE(proposal_id=X, value="12:00 PM")
Node 4 -> Node 2: PREPARE(proposal_id=X, value="12:00 PM")
Node 4 -> Node 3: PREPARE(proposal_id=X, value="12:00 PM")

Phase 4: Commit Once a node receives 2F (in this case, 2) PREPARE messages for the same proposal_id and value (including its own), it sends a "commit" message.

Node 2 -> Node 1: COMMIT(proposal_id=X, value="12:00 PM")
Node 2 -> Node 3: COMMIT(proposal_id=X, value="12:00 PM")
Node 2 -> Node 4: COMMIT(proposal_id=X, value="12:00 PM")

Node 3 -> Node 1: COMMIT(proposal_id=X, value="12:00 PM")
Node 3 -> Node 2: COMMIT(proposal_id=X, value="12:00 PM")
Node 3 -> Node 4: COMMIT(proposal_id=X, value="12:00 PM")

Node 4 -> Node 1: COMMIT(proposal_id=X, value="12:00 PM")
Node 4 -> Node 2: COMMIT(proposal_id=X, value="12:00 PM")
Node 4 -> Node 3: COMMIT(proposal_id=X, value="12:00 PM")

Phase 5: Reply/Decide When a node receives 2F+1 (in this case, 3) COMMIT messages for the same proposal_id and value, it considers the value "committed" and will act on it.

If Node 1 was malicious and sent "11:00 AM" to Node 4, but "12:00 PM" to Nodes 2 and 3, the honest nodes (2 and 3) would eventually agree on "12:00 PM" because they receive and prepare the same value from each other, and eventually commit to it. Node 4, seeing conflicting PREPARE messages or not enough commits for "11:00 AM", would eventually align with the majority.

The core problem BFT solves is achieving consensus in an environment where some participants might be arbitrarily faulty or malicious. This is crucial for systems like blockchains, distributed databases, and critical control systems where a single rogue element cannot be allowed to derail the entire operation. The magic number is 3F+1 total nodes required to tolerate F faulty nodes. This ensures that even if F nodes send one message and the remaining 2F+1 nodes send another, the honest majority can still outvote the faulty minority.

The specific levers you control in BFT are primarily the number of nodes in the network (N) and the maximum number of faulty nodes you want to tolerate (F). Increasing F requires a proportional increase in N (specifically, N >= 3F + 1). This is a direct trade-off between resilience and overhead, as more nodes mean more communication and computation.

What most people don’t realize is that BFT algorithms don’t just tolerate faults; they enforce a strict ordering of operations. When a node commits a value, it’s not just agreeing on the value itself, but also on the sequence in which it was proposed and prepared. This deterministic ordering is what prevents diverging states and ensures all honest nodes eventually reach the same conclusion.

The next concept you’ll run into is how BFT handles leader failures and view changes, where the system must elect a new leader and potentially re-synchronize nodes that might have missed messages.

Want structured learning?

Take the full Distributed Systems course →