Advanced Consensus Mechanisms
Implementing advanced consensus algorithms: PBFT, Raft, Tendermint, and Byzantine Fault Tolerance.
Advanced Consensus Mechanisms
Beyond Proof of Work and Proof of Stake, advanced consensus mechanisms provide different trade-offs.
Consensus Requirements
- Safety: All honest nodes agree on the same value
- Liveness: The system eventually makes progress
- Fault Tolerance: Works despite some nodes failing or being malicious
Byzantine Fault Tolerance (BFT)
BFT systems tolerate Byzantine failures - nodes that can behave arbitrarily (lie, crash, etc.).
PBFT (Practical Byzantine Fault Tolerance)
- Assumption: At most f Byzantine nodes out of 3f+1 total
- Phases: Pre-prepare, Prepare, Commit
- Finality: Immediate (no waiting for confirmations)
- Use Cases: Permissioned blockchains, enterprise
Tendermint Consensus
- Voting Rounds: Validators vote on blocks
- Lock Mechanism: Prevents double-spending
- Finality: Blocks are final once committed
- Use Cases: Cosmos, Binance Chain
Raft Consensus
- Leader Election: One leader coordinates
- Log Replication: Leader replicates log to followers
- Crash Fault Tolerant: Doesn't handle Byzantine faults
- Use Cases: Distributed databases, private blockchains
PBFT Implementation
RUSTenum PBFTPhase { PrePrepare, Prepare, Commit, } struct PBFTMessage { phase: PBFTPhase, view: u64, sequence: u64, block_hash: String, sender: String, signature: Vec<u8>, } struct PBFTNode { id: String, view: u64, prepared: HashMap<u64, HashSet<String>>, // sequence -> set of prepare messages committed: HashSet<u64>, } impl PBFTNode { fn pre_prepare(&mut self, block_hash: String, sequence: u64) -> PBFTMessage { // Leader sends pre-prepare PBFTMessage { phase: PBFTPhase::PrePrepare, view: self.view, sequence, block_hash, sender: self.id.clone(), signature: vec![], } } fn prepare(&mut self, msg: &PBFTMessage) -> Option<PBFTMessage> { // Node sends prepare after receiving pre-prepare // Need 2f prepares to move to commit let prepares = self.prepared.entry(msg.sequence).or_insert_with(HashSet::new); prepares.insert(msg.sender.clone()); if prepares.len() >= 2 { // Have enough prepares Some(PBFTMessage { phase: PBFTPhase::Prepare, view: self.view, sequence: msg.sequence, block_hash: msg.block_hash.clone(), sender: self.id.clone(), signature: vec![], }) } else { None } } fn commit(&mut self, msg: &PBFTMessage) -> bool { // Need 2f+1 commits to finalize if msg.phase == PBFTPhase::Commit { self.committed.insert(msg.sequence); self.committed.len() >= 3 // 2f+1 for f=1 } else { false } } }
Tendermint Consensus
RUSTenum TendermintStep { Propose, Prevote, Precommit, Commit, } struct TendermintVote { step: TendermintStep, height: u64, round: u64, block_hash: Option<String>, validator: String, } struct TendermintNode { height: u64, round: u64, locked_block: Option<String>, locked_round: Option<u64>, valid_round: Option<u64>, } impl TendermintNode { fn propose(&mut self, block_hash: String) -> TendermintVote { TendermintVote { step: TendermintStep::Propose, height: self.height, round: self.round, block_hash: Some(block_hash), validator: String::from("validator1"), } } fn prevote(&mut self, block_hash: &str) -> TendermintVote { // Lock on block if valid if self.is_valid_block(block_hash) { self.locked_block = Some(block_hash.to_string()); self.locked_round = Some(self.round); } TendermintVote { step: TendermintStep::Prevote, height: self.height, round: self.round, block_hash: Some(block_hash.to_string()), validator: String::from("validator1"), } } fn is_valid_block(&self, _block_hash: &str) -> bool { // Validate block true } }
Comparison
| Mechanism | Fault Tolerance | Finality | Throughput | Use Case |
|---|---|---|---|---|
| PoW | 50% honest | Probabilistic | Low | Public, permissionless |
| PoS | 33% honest | Probabilistic | Medium | Public, permissionless |
| PBFT | 33% Byzantine | Immediate | High | Permissioned |
| Tendermint | 33% Byzantine | Immediate | High | Public, permissioned |
| Raft | 50% crash | Immediate | Very High | Private |
Choosing a Consensus
- Public, Permissionless: PoW or PoS
- High Throughput Needed: PBFT or Tendermint
- Immediate Finality: BFT-based (PBFT, Tendermint)
- Private Network: Raft or PBFT
- Energy Efficiency: PoS or BFT
Real-World Examples
- Hyperledger Fabric: Uses PBFT
- Cosmos: Uses Tendermint
- Ethereum 2.0: Uses PoS with BFT finality
- Solana: Uses Proof of History + PoS
Code Examples
Simplified PBFT
Basic PBFT consensus implementation
use std::collections::{HashMap, HashSet};
enum Phase {
PrePrepare,
Prepare,
Commit,
}
struct Message {
phase: Phase,
view: u64,
sequence: u64,
block_hash: String,
sender: String,
}
struct PBFTNode {
id: String,
view: u64,
prepares: HashMap<u64, HashSet<String>>,
commits: HashMap<u64, HashSet<String>>,
total_nodes: usize,
}
impl PBFTNode {
fn new(id: String, total_nodes: usize) -> Self {
PBFTNode {
id,
view: 0,
prepares: HashMap::new(),
commits: HashMap::new(),
total_nodes,
}
}
fn receive_prepare(&mut self, msg: &Message) -> Option<Message> {
let prepares = self.prepares.entry(msg.sequence).or_insert_with(HashSet::new);
prepares.insert(msg.sender.clone());
// Need 2f prepares (where f = (total_nodes - 1) / 3)
let f = (self.total_nodes - 1) / 3;
let required = 2 * f;
if prepares.len() >= required && !self.commits.contains_key(&msg.sequence) {
// Send commit
Some(Message {
phase: Phase::Commit,
view: self.view,
sequence: msg.sequence,
block_hash: msg.block_hash.clone(),
sender: self.id.clone(),
})
} else {
None
}
}
fn receive_commit(&mut self, msg: &Message) -> bool {
let commits = self.commits.entry(msg.sequence).or_insert_with(HashSet::new);
commits.insert(msg.sender.clone());
// Need 2f+1 commits to finalize
let f = (self.total_nodes - 1) / 3;
let required = 2 * f + 1;
commits.len() >= required
}
}
fn main() {
// 4 nodes: can tolerate 1 Byzantine fault (f=1)
let mut node1 = PBFTNode::new(String::from("node1"), 4);
// Simulate receiving prepares
let prepare_msg = Message {
phase: Phase::Prepare,
view: 0,
sequence: 1,
block_hash: String::from("block_hash_123"),
sender: String::from("node2"),
};
// Receive 2 prepares (need 2f = 2 for f=1)
node1.receive_prepare(&prepare_msg);
let mut prepare2 = prepare_msg;
prepare2.sender = String::from("node3");
if let Some(commit_msg) = node1.receive_prepare(&prepare2) {
println!("Node1 sending commit for sequence {}", commit_msg.sequence);
// Receive commits
let mut commit1 = commit_msg.clone();
commit1.sender = String::from("node2");
let mut commit2 = commit_msg.clone();
commit2.sender = String::from("node3");
if node1.receive_commit(&commit_msg) {
println!("Block finalized!");
}
if node1.receive_commit(&commit1) {
println!("Block finalized!");
}
if node1.receive_commit(&commit2) {
println!("Block finalized!");
}
}
}Explanation:
PBFT requires 3 phases: pre-prepare (leader proposes), prepare (nodes agree), and commit (nodes finalize). With 4 nodes (f=1), we need 2 prepares and 3 commits to finalize a block. This provides immediate finality unlike PoW/PoS.
Tendermint Voting
Tendermint consensus voting mechanism
enum VoteType {
Prevote,
Precommit,
}
struct Vote {
vote_type: VoteType,
height: u64,
round: u64,
block_hash: Option<String>,
validator: String,
}
struct TendermintValidator {
id: String,
height: u64,
round: u64,
prevotes: Vec<Vote>,
precommits: Vec<Vote>,
locked_block: Option<String>,
}
impl TendermintValidator {
fn new(id: String) -> Self {
TendermintValidator {
id,
height: 1,
round: 0,
prevotes: Vec::new(),
precommits: Vec::new(),
locked_block: None,
}
}
fn receive_prevote(&mut self, vote: Vote) -> bool {
if vote.height == self.height && vote.round == self.round {
self.prevotes.push(vote);
// Check if we have 2/3+ prevotes for a block
let block_votes: usize = self.prevotes
.iter()
.filter(|v| v.block_hash == vote.block_hash)
.count();
let total_validators = 4; // Simplified
let required = (2 * total_validators) / 3 + 1;
if block_votes >= required && vote.block_hash.is_some() {
// Lock on this block
self.locked_block = vote.block_hash.clone();
return true;
}
}
false
}
fn can_precommit(&self) -> bool {
self.locked_block.is_some()
}
fn create_precommit(&self) -> Option<Vote> {
if self.can_precommit() {
Some(Vote {
vote_type: VoteType::Precommit,
height: self.height,
round: self.round,
block_hash: self.locked_block.clone(),
validator: self.id.clone(),
})
} else {
None
}
}
}
fn main() {
let mut validator = TendermintValidator::new(String::from("validator1"));
// Receive prevotes
for i in 0..3 {
let vote = Vote {
vote_type: VoteType::Prevote,
height: 1,
round: 0,
block_hash: Some(String::from("block123")),
validator: format!("validator{}", i + 1),
};
validator.receive_prevote(vote);
}
if let Some(precommit) = validator.create_precommit() {
println!("Validator can precommit for block: {:?}", precommit.block_hash);
println!("Block is locked and ready to commit");
}
}Explanation:
Tendermint uses voting rounds: propose, prevote, precommit. Validators lock on a block after receiving 2/3+ prevotes. Once locked, they can precommit. With 2/3+ precommits, the block is committed. This provides immediate finality.
Exercises
Simple BFT Voting
Create a simple BFT voting system!
Starter Code:
struct Vote {
block_hash: String,
voter: String,
}
fn main() {
// Collect votes
// Check if we have 2/3+ votes
}