Tower BFT

Tower BFT is Solana's consensus mechanism—a modified version of Practical Byzantine Fault Tolerance (PBFT) that leverages Proof of History for radical efficiency improvements.

What Is Tower BFT?

Tower BFT is a voting-based consensus algorithm that achieves:

  • Byzantine fault tolerance (survives malicious actors)
  • High throughput (no waiting for network timeouts)
  • Fast finality (~400ms to confirmation, ~13s to finalization)
Text
TOWER BFT vs TRADITIONAL PBFT
═════════════════════════════

Traditional PBFT:
├── 3-phase protocol (pre-prepare, prepare, commit)
├── O() message complexity
├── Network timeout for each phase
├── ~1000 TPS maximum
└── ~3-10 seconds finality

Tower BFT:
├── 1-phase voting
├── O(n) message complexity (via gossip)
├── PoH provides timing (no timeouts needed)
├── ~65,000 TPS theoretical
└── ~400ms confirmation, ~13s finalization

The Core Mechanism

Vote Tower

Each validator maintains a "tower" of votes:

Text
VOTE TOWER VISUALIZATION
════════════════════════

                          Lockout
Slot    Vote              Period
─────────────────────────────────
500     ████              2 slots
499     ████████          4 slots
498     ████████████      8 slots
497     ██████████████████16 slots
496     ███████████████████████32 slots
...     ...               ...
(oldest) (most locked)

Key insight: Each vote DOUBLES the lockout of all previous votes.

Switching cost increases exponentially with tower height.
After 32 votes: Finalized (locked for 2³¹ slots ≈ never)

Lockout Rules

TypeScript
interface Vote {
  slot: number; // Slot being voted on
  confirmations: number; // How many newer votes confirm this
  lockout: number; // 2^confirmations slots
}

interface VoteTower {
  votes: Vote[];
  root: number; // Oldest finalized slot

  // Rule: Can only vote on slot S if:
  // For all votes V in tower:
  //   S >= V.slot + V.lockout
  // OR
  //   S is descendant of V.slot
}

// Example tower state
const tower: VoteTower = {
  votes: [
    { slot: 100, confirmations: 5, lockout: 32 }, // Locked
    { slot: 101, confirmations: 4, lockout: 16 },
    { slot: 102, confirmations: 3, lockout: 8 },
    { slot: 103, confirmations: 2, lockout: 4 },
    { slot: 104, confirmations: 1, lockout: 2 },
    { slot: 105, confirmations: 0, lockout: 1 }, // Latest
  ],
  root: 95, // Everything before 95 is finalized
};

Vote Progression

Text
VOTING EXAMPLE
══════════════

Validator sees slot 100, votes:
Tower: [100 (lockout=1)]

Slot 101 arrives (builds on 100), votes:
Tower: [100 (lockout=2), 101 (lockout=1)]
       └─── 100's lockout doubled!

Slot 102 arrives (builds on 101), votes:
Tower: [100 (lockout=4), 101 (lockout=2), 102 (lockout=1)]

... continues ...

After 32 slots building on 100:
Tower: [100 (lockout=2³¹ ≈ ∞), ...]
       └─── Slot 100 is now FINALIZED
       └─── Cannot switch to any fork not containing 100

Fork Choice Rule

When the chain forks, how does a validator choose?

Text
FORK CHOICE SCENARIO
════════════════════

                    ┌──[102A]──[103A]──[104A]15%    10%     5%
        [100]──[101]50%   40%  │
                    └──[102B]──[103B]
                         25%    20%

Numbers = percentage of total stake voted

Fork choice algorithm:
1. Start at root
2. At each fork, choose branch with most stake-weighted votes
3. Continue until tip

Here: 100101102B103B
(102B has 25% vs 102A's 15%)

Implementation

TypeScript
// Simplified fork choice
function chooseFork(
  bankForks: Map<number, { parent: number; votes: number }>,
  root: number,
): number {
  let current = root;

  while (true) {
    // Find children of current slot
    const children = Array.from(bankForks.entries())
      .filter(([_, data]) => data.parent === current)
      .map(([slot, data]) => ({ slot, votes: data.votes }));

    if (children.length === 0) {
      // Reached tip
      return current;
    }

    // Choose child with most votes
    children.sort((a, b) => b.votes - a.votes);
    current = children[0].slot;
  }
}

Optimistic Confirmation

Solana provides confirmation before finalization:

Text
CONFIRMATION LEVELS
═══════════════════

PROCESSED (~400ms):
├── Block exists in leader's output
├── Not yet voted on
└── Can be rolled back

CONFIRMED (~400-800ms):
├── Supermajority (>66%) of stake voted
├── Very unlikely to roll back
├── Would require >33% malicious stake
└── Safe for most applications

FINALIZED (~12-13 seconds):
├── Maximum lockout reached
├── Cannot be rolled back
├── Would require coordinated attack
└── Required for exchanges/high-value

Confirmation Math

Text
WHY 66% SUPERMAJORITY?
══════════════════════

Byzantine Fault Tolerance requires:
├── n > 3f (n nodes, f byzantine)
├── Need 2f+1 honest votes (>66%)
└── At most f can be dishonest (<33%)

With 66% voted:
├── At most 33% didn't vote
├── Even if ALL non-voters are malicious
├── Plus some voters switch (impossible due to lockout)
├── Still have honest majority
└── Fork cannot succeed

Safety: 66% voted means CERTAIN confirmation
         (assuming <33% byzantine)

Vote Transactions

Votes are submitted as on-chain transactions:

TypeScript
import { VoteProgram, PublicKey, Transaction } from "@solana/web3.js";

// Vote account structure (on-chain)
interface VoteState {
  nodePubkey: PublicKey; // Validator identity
  authorizedVoter: PublicKey; // Who can vote
  authorizedWithdrawer: PublicKey;
  commission: number; // Reward commission %

  // Tower state
  votes: Lockout[]; // Recent votes
  rootSlot: number; // Finalized slot

  // Reward tracking
  priorVoters: PriorVoter[];
  epochCredits: EpochCredit[]; // For rewards
}

// Creating a vote instruction
function createVoteInstruction(
  votePubkey: PublicKey,
  authorizedVoterPubkey: PublicKey,
  vote: {
    slots: number[];
    hash: string;
    timestamp: number;
  },
): Transaction {
  return VoteProgram.vote({
    votePubkey,
    authorizedVoterPubkey,
    vote: {
      slots: vote.slots,
      hash: Buffer.from(vote.hash, "hex"),
      timestamp: vote.timestamp,
    },
  });
}

Vote Account Queries

TypeScript
import { Connection, VoteAccountStatus } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com");

// Get all vote accounts
const voteAccounts: VoteAccountStatus = await connection.getVoteAccounts();

console.log("Current validators:", voteAccounts.current.length);
console.log("Delinquent validators:", voteAccounts.delinquent.length);

// Analyze a vote account
for (const va of voteAccounts.current.slice(0, 3)) {
  console.log({
    validator: va.nodePubkey,
    voteAccount: va.votePubkey,
    stake: va.activatedStake / 1e9, // Convert lamports to SOL
    commission: va.commission,
    lastVote: va.lastVote,
    rootSlot: va.rootSlot,
    epochCredits: va.epochCredits.slice(-3), // Last 3 epochs
  });
}

// Get specific vote account
const voteAccount = await connection.getAccountInfo(votePubkey);
// Parse vote state from account data...

Optimistic Concurrency Control

Tower BFT enables speculative execution:

Text
OPTIMISTIC EXECUTION
════════════════════

Traditional approach:
1. Wait for consensus
2. Then execute
3. Slow but safe

Solana approach:
1. Execute speculatively
2. Continue building
3. Roll back if fork switch needed

Timeline:
─────────────────────────────────────────────────────
Slot 100     Slot 101     Slot 102     Slot 103
   │            │            │            │
   ▼            ▼            ▼            ▼
 Execute     Execute     Execute     Execute
 Update      Update      Update      Update
 state       state       state       state
   │            │            │            │
   │            │            │            │
   └────────────┴────────────┴────────────┘
                     │
                     ▼
              If fork switch:
              Replay from last
              common ancestor
─────────────────────────────────────────────────────

Benefits:
├── No waiting for votes
├── Continuous progress
├── Pipelining throughput
└── Fork switches are rare

Consensus Safety

Slashing Conditions

Currently, Solana doesn't implement slashing, but violations are detectable:

TypeScript
// Theoretically slashable conditions
interface SlashableViolation {
  // Double voting: Voting for two different blocks at same slot
  doubleVote: {
    slot: number;
    vote1Hash: string;
    vote2Hash: string;
    signature1: string;
    signature2: string;
  };

  // Surround voting: Vote A surrounds vote B
  // (A.source < B.source && A.target > B.target)
  surroundVote: {
    voteA: { source: number; target: number };
    voteB: { source: number; target: number };
  };

  // Tower violation: Voting against locked slot
  lockoutViolation: {
    lockedSlot: number;
    lockoutRemaining: number;
    votedSlot: number;
    notDescendant: boolean;
  };
}

// Detection (would be verified by network)
function detectDoubleVote(vote1: SignedVote, vote2: SignedVote): boolean {
  return (
    vote1.slot === vote2.slot &&
    vote1.hash !== vote2.hash &&
    vote1.validator === vote2.validator &&
    verifySignature(vote1) &&
    verifySignature(vote2)
  );
}

Performance Characteristics

Vote Timing

Text
VOTE PROPAGATION TIMELINE
═════════════════════════

T+0ms:     Leader produces block
T+50ms:    Block reaches validators via Turbine
T+100ms:   Validators process block
T+150ms:   Validators create vote transactions
T+200ms:   Votes propagate via gossip
T+300ms:   Leader of voting slot sees votes
T+400ms:   Vote transactions in next block

Full confirmation cycle: ~400-800ms

                │
    ┌───────────┼───────────┐
    │           │           │
    ▼           ▼           ▼
 Block 100   Block 101   Block 102
    │           │           │
    │     Votes for 100     │
    │           │           │
    └───────────┼───────────┘
                │
                ▼
          100 confirmed
         after 101-102

Stake Distribution Impact

Text
STAKE CONCENTRATION EFFECTS
═══════════════════════════

More concentrated stake = faster confirmation
(fewer votes needed for supermajority)

Example scenarios:
─────────────────────────────────────────────────
Distribution          | Votes for 66%  | Speed
─────────────────────────────────────────────────
3 validators @ 33%    |      2 votes   | Fastest
10 validators @ 10%   |      7 votes   | Fast
100 validators @ 1%   |     67 votes   | Moderate
1700 validators mixed |    ~50 votes   | Current
─────────────────────────────────────────────────

Solana reality (~1700 validators):
├── Top ~20 validators have ~33% stake
├── Top ~100 have ~66%
├── Confirmation needs votes from ~50-100 validators
└── Fast confirmation despite decentralization

Handling Network Partitions

Text
PARTITION SCENARIO
══════════════════

Network splits into two groups:

Partition A (40% stake)         Partition B (60% stake)
[V1, V2, V3, ...]               [V4, V5, V6, ...]
        │                               │
        ▼                               ▼
   Fork A grows                   Fork B grows
   No confirmation                Confirms at 60%
   (< 66%)                        (< 66%, no finalization)

When partition heals:
├── Fork B has more stake
├── All validators switch to B
├── Fork A transactions replayed or dropped
└── Network converges

Tower BFT ensures:
├── A validators were locked on A
├── Must wait for lockout expiry
├── Then can switch to B
└── No safety violation

Practical Monitoring

TypeScript
// Monitor consensus health
async function monitorConsensus(connection: Connection) {
  // 1. Check slot progression
  const slot = await connection.getSlot("confirmed");
  const finalizedSlot = await connection.getSlot("finalized");
  const confirmationLag = slot - finalizedSlot;

  console.log(`Confirmed: ${slot}, Finalized: ${finalizedSlot}`);
  console.log(`Confirmation lag: ${confirmationLag} slots`);

  // 2. Check validator participation
  const voteAccounts = await connection.getVoteAccounts();
  const activeStake = voteAccounts.current.reduce(
    (sum, v) => sum + v.activatedStake,
    0,
  );
  const delinquentStake = voteAccounts.delinquent.reduce(
    (sum, v) => sum + v.activatedStake,
    0,
  );
  const totalStake = activeStake + delinquentStake;

  console.log(
    `Active stake: ${((activeStake / totalStake) * 100).toFixed(1)}%`,
  );
  console.log(
    `Delinquent: ${((delinquentStake / totalStake) * 100).toFixed(1)}%`,
  );

  // 3. Healthy if > 66% active (can reach consensus)
  const isHealthy = activeStake / totalStake > 0.66;
  console.log(`Consensus healthy: ${isHealthy}`);

  return {
    slot,
    finalizedSlot,
    confirmationLag,
    activeStakePercent: (activeStake / totalStake) * 100,
    isHealthy,
  };
}

Key Takeaways

  1. Tower BFT combines PBFT safety with PoH efficiency
  2. Exponential lockouts create economic finality
  3. 66% supermajority provides Byzantine fault tolerance
  4. Optimistic confirmation (~400ms) is safe for most uses
  5. Finalization (~13s) guarantees irreversibility

Next: Account Structure - Deep dive into Solana's account model.