Требуемые знания:
- 05-solana/02-proof-of-history
Tower BFT
Зачем это блокчейну?
В предыдущем уроке мы разобрали PoH — криптографические часы, которые доказывают порядок событий. Но часы сами по себе не решают главную задачу: как валидаторы соглашаются на одну версию истории?
Ethereum использует Casper FFG + LMD GHOST — два протокола, работающих вместе. Финализация занимает ~12.8 минут (2 эпохи по 32 слота). Tower BFT — это вариант PBFT (Practical Byzantine Fault Tolerance), оптимизированный для использования с PoH. Он финализирует блоки за ~13 секунд.
Ключевая идея: PoH заменяет обмен сообщениями для синхронизации времени. Благодаря этому Tower BFT требует один раунд голосования вместо нескольких. Это фундаментальное отличие от классического PBFT, где нужны фазы pre-prepare, prepare и commit.
# Классический PBFT (3 фазы, O(n^2) сообщений):
# 1. Pre-prepare: лидер предлагает блок
# 2. Prepare: валидаторы подтверждают получение
# 3. Commit: валидаторы подтверждают готовность зафиксировать
# Итого: 3 раунда сообщений, ~3 * network_latency
# Tower BFT (1 фаза, O(n) сообщений):
# 1. Лидер публикует блок с PoH-меткой
# 2. Валидаторы голосуют (один раунд)
# PoH заменяет фазы prepare/commit -- "часы" уже синхронизированы
# Итого: 1 раунд сообщений, ~1 * network_latency
Интуитивное объяснение: аналогия с ставками
Представьте покерный турнир, где каждый игрок делает ставки на “правильную” руку:
- Первая ставка: 2 фишки. “Я верю, что рука #100 выиграет.”
- Вторая ставка: ваша первая ставка удваивается до 4 фишек, плюс новая ставка в 2 фишки.
- Третья ставка: первая = 8, вторая = 4, третья = 2.
- После 32 ставок: первая ставка заблокирована на 2^32 = 4 миллиарда раундов.
Отменить глубокий голос становится экспоненциально дороже. Это и есть Tower BFT.
Башня голосований: пошагово
Каждый валидатор поддерживает “башню” (tower) своих голосов. При каждом новом голосе lockout всех предыдущих голосов удваивается.
Алгоритмический уровень
class VoteTower:
"""Башня голосований валидатора в Tower BFT"""
def __init__(self):
self.tower = [] # Стек: [(slot, lockout, confirmations)]
def vote(self, slot: int):
"""Проголосовать за слот"""
# Удалить голоса, чей lockout истек
self.tower = [
(s, lo, conf)
for (s, lo, conf) in self.tower
if s + lo > slot # lockout еще не истек
]
# Каждый существующий голос удваивает свой lockout
self.tower = [
(s, lo * 2, conf + 1)
for (s, lo, conf) in self.tower
]
# Добавить новый голос на вершину
self.tower.append((slot, 2, 1)) # lockout=2, confirmations=1
# Проверить rooted (32+ confirmations)
rooted = [
(s, lo, conf)
for (s, lo, conf) in self.tower
if conf >= 32
]
for r in rooted:
self.tower.remove(r)
print(f" ROOTED (finalized): slot {r[0]}")
def can_switch_fork(self, new_slot: int) -> bool:
"""Можно ли переключиться на другой форк?"""
for (s, lo, conf) in self.tower:
if s + lo > new_slot:
return False # Lockout не истек -- нельзя!
return True
def display(self):
print(" Tower (снизу вверх):")
for s, lo, conf in self.tower:
print(f" Slot {s}: lockout={lo}, confirmations={conf}")
# Демонстрация:
tower = VoteTower()
for slot in [100, 101, 102, 103]:
print(f"\nГолос за слот {slot}:")
tower.vote(slot)
tower.display()
# Вывод:
# Голос за слот 100: lockout=2
# Голос за слот 101: slot 100 lockout=4, slot 101 lockout=2
# Голос за слот 102: slot 100 lockout=8, slot 101 lockout=4, slot 102 lockout=2
# Голос за слот 103: slot 100 lockout=16, slot 101 lockout=8, ...
Почему экспоненциальный lockout?
# Lockout растет экспоненциально с каждым подтверждением:
# confirmations: 1 2 3 4 5 ... 32
# lockout: 2 4 8 16 32 ... 2^32 = 4,294,967,296
# Это значит:
# - Отменить ПОСЛЕДНИЙ голос: нужно подождать 2 слота (~0.8s)
# - Отменить 4-й голос снизу: нужно подождать 16 слотов (~6.4s)
# - Отменить 10-й голос: нужно подождать 1,024 слота (~6.8 минут)
# - Отменить 32-й голос: нужно подождать 2^32 слотов (~54 ЛЕТ)
# Экономический эффект: если валидатор голосует нечестно
# и потом хочет переключиться на правильный форк,
# его старые голоса "заблокированы" -- он теряет вознаграждение
# за все слоты, пока lockout не истечет
Математический уровень
Определение (Vote Tower):
Стек голосов V = {(s_1, l_1, c_1), ..., (s_n, l_n, c_n)} где:
s_i -- номер слота
l_i = 2^{c_i} -- lockout (экспоненциальный)
c_i -- количество подтверждений
При голосовании за слот s_{n+1}:
1. Для всех i: c_i := c_i + 1, l_i := 2^{c_i}
2. Добавить (s_{n+1}, 2, 1) на вершину
3. Если c_i >= 32: голос s_i финализирован (rooted)
Безопасность:
Валидатор не может проголосовать за форк F', если
существует голос (s, l, c) такой что s + l > current_slot
и s не в форке F'.
=> Чем глубже голос, тем длиннее lockout,
тем дороже обман (потеря вознаграждений за l слотов).
Три уровня подтверждения
Solana предоставляет три уровня подтверждения транзакций:
| Уровень | Условие | Время | Гарантия | Аналог в Ethereum |
|---|---|---|---|---|
| Processed | Лидер обработал | ~400ms | Может быть отменено | Tx в mempool |
| Confirmed | 2/3 валидаторов проголосовали | ~6.4s | Очень вероятно финально | 1 подтверждение блока |
| Finalized | 32+ confirmations (rooted) | ~13s | Необратимо | 2 эпохи (~12.8 мин) |
# Выбор уровня подтверждения при запросе:
from solana.rpc.api import Client
client = Client("http://localhost:8899")
# Самый быстрый -- для UI (может быть отменено):
result = client.get_transaction(sig, commitment="processed")
# Оптимальный баланс -- для большинства dApp:
result = client.get_transaction(sig, commitment="confirmed")
# Максимальная безопасность -- для бирж, крупных переводов:
result = client.get_transaction(sig, commitment="finalized")
# Сравнение с Ethereum:
# Ethereum "finalized" = 2 эпохи = ~12.8 минут
# Solana "finalized" = 32 confirmations = ~13 секунд
# Разница: ~60x быстрее!
Когда использовать какой уровень
| Сценарий | Уровень | Почему |
|---|---|---|
| Отображение баланса в кошельке | confirmed | Быстрый UI, вероятность отката минимальна |
| Получение депозита на бирже | finalized | Нужна абсолютная уверенность |
| Игровая транзакция | processed | Скорость важнее, можно повторить |
| DeFi свап | confirmed | Баланс скорости и безопасности |
| NFT минт | confirmed | Пользователь ждет подтверждения |
Расписание лидеров и Gulf Stream
Расписание лидеров
В Solana один валидатор выбирается лидером для последовательности из 4 слотов (~1.6 секунды). Расписание определяется псевдослучайно, с весом пропорциональным stake.
Алгоритмический уровень: расписание
# Упрощенная модель расписания лидеров
# Epoch = 432,000 слотов (~2-3 дня)
# Каждый лидер получает 4 последовательных слота
SLOTS_PER_EPOCH = 432_000
SLOTS_PER_LEADER = 4
# Расписание определяется в начале эпохи:
# 1. Берем stake-weight каждого валидатора
# 2. Используем seed (хеш предыдущей эпохи) для pseudo-random shuffle
# 3. Каждый валидатор получает слоты пропорционально stake
# Пример: 3 валидатора
validators = {"A": 1000, "B": 2000, "C": 3000} # SOL staked
total_stake = sum(validators.values())
# Вероятность быть выбранным лидером:
for v, stake in validators.items():
prob = stake / total_stake
expected_slots = SLOTS_PER_EPOCH * prob
expected_rotations = expected_slots / SLOTS_PER_LEADER
print(f" {v}: stake={stake}, P={prob:.1%}, ~{expected_rotations:.0f} ротаций за эпоху")
# A: 16.7%, ~18,000 ротаций
# B: 33.3%, ~36,000 ротаций
# C: 50.0%, ~54,000 ротаций
Gulf Stream: транзакции без mempool
Gulf Stream — одна из 8 инноваций Solana, тесно связанная с расписанием лидеров:
# Ethereum: транзакция ждет в mempool
# 1. Клиент -> mempool
# 2. Mempool хранит тысячи транзакций
# 3. Пропозер выбирает транзакции из mempool (MEV!)
# 4. Время ожидания: от 12 секунд до минут
# Solana: Gulf Stream -- нет mempool
# 1. Клиент отправляет tx текущему лидеру
# 2. Текущий лидер пересылает tx СЛЕДУЮЩЕМУ лидеру
# 3. Следующий лидер уже имеет tx до начала своих слотов
# 4. Время ожидания: практически 0 (forwarded ahead)
# Преимущества:
# - Нет mempool = нет "зависших" транзакций
# - Следующий лидер начинает обработку заранее
# - Уменьшает latency подтверждения
# - Снижает давление памяти на валидаторы
# Ограничения:
# - Если следующий лидер офлайн, tx теряется
# - Клиент должен знать расписание лидеров
# - Пересылка добавляет сетевой hop
Tower BFT vs Casper FFG (Ethereum)
| Аспект | Tower BFT (Solana) | Casper FFG (Ethereum) |
|---|---|---|
| Тип | PoH-optimized PBFT | PoS FFG + LMD GHOST |
| Раунды голосования | 1 (PoH = часы) | Несколько (2 эпохи) |
| Финализация | ~13 секунд | ~12.8 минут |
| Lockout механизм | Экспоненциальный (2^n) | Slashing conditions |
| Переключение форка | Ограничено lockout | Ограничено slashing |
| Наказание нечестных | Потеря вознаграждений + lockout | Slashing (потеря stake) |
| Fork choice | Heaviest tower | LMD GHOST (heaviest subtree) |
# Стоимость атаки (упрощенно):
# Ethereum: чтобы отменить финализированный блок,
# нужно 1/3 stake (slashing condition + fork)
# При total stake = 34M ETH, это ~11.3M ETH (~$35B)
# Solana: чтобы отменить rooted блок,
# нужно ждать 2^32 слотов (~54 года)
# Экономически: потеря вознаграждений за это время
# При ~400 SOL/slot reward: потеря ~1.7 триллиона SOL (невозможно)
# Обе системы достигают практической безопасности,
# но разными механизмами:
# Ethereum: "заберем ваш deposit" (slashing)
# Solana: "вы не сможете голосовать" (lockout)
Alpenglow: планируемая замена
Alpenglow заменит PoH + Tower BFT новым протоколом с двумя компонентами:
- Votor: Быстрое голосование за блоки (без PoH)
- Rotor: Ротация лидеров с улучшенной устойчивостью к сбоям
Целевая финальность: 100-150ms (vs 13s у Tower BFT).
Важно для разработчиков: Alpenglow не затрагивает модель аккаунтов, программы, инструкции, PDA или Anchor. Весь код, написанный для текущей Solana, продолжит работать.
Формальная модель безопасности
Теорема (Tower BFT Safety):
Если f < n/3 Byzantine валидаторов (из n общих),
то два конфликтующих блока не могут оба стать rooted.
Доказательство (идея):
1. Rooted = 32+ confirmations = lockout >= 2^32 слотов
2. Для конфликтующего форка нужны голоса 2/3 валидаторов
3. Но голоса заблокированы lockout -- нельзя переголосовать
4. Если 2/3 проголосовали за блок A, и A получил 32 confirmations,
то 1/3 из этих 2/3 должны были бы нарушить lockout для блока B
5. Но lockout = 2^32 слотов -- нарушение невозможно в разумное время
6. Если нарушат -- теряют все вознаграждения (экономическая безопасность)
Связь с предыдущими уроками
| Концепция | Откуда | Как используется в Tower BFT |
|---|---|---|
| PoH (часы) | SOL-02 | Tower BFT использует PoH для синхронизации голосований |
| Хеш-функции | CRYPTO-05 | SHA-256 в основе PoH-цепочки |
| Proof of Stake | ETH-10 | Tower BFT — альтернатива Casper FFG |
| Финализация | ETH-10 | Сравнение: 13s vs 12.8 мин |
| Byzantine Fault Tolerance | ETH-01 | Tower BFT — оптимизированный PBFT |
Практика
# Упражнение: симуляция Tower BFT
# 1. Реализуйте VoteTower из псевдокода выше
# 2. Проголосуйте за 40 последовательных слотов
# 3. Наблюдайте: когда первый голос станет rooted?
# 4. Попробуйте переключить форк после 10 голосов
# 5. Сравните lockout с Ethereum slashing conditions
# Ожидаемый результат:
# - Первый голос станет rooted после 32 последующих голосов
# - Переключение форка после 10 голосов потребует ожидания
# 2^10 = 1024 слотов (~6.8 минут)
Что дальше?
В следующем уроке мы перейдем к модели аккаунтов Solana — самому важному отличию от Ethereum для разработчиков. Вы узнаете, почему программы в Solana stateless, как данные хранятся в отдельных аккаунтах, и как работают Program Derived Addresses (PDA).
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс