Механизмы консенсуса: PoW, PoS, финальность, форки, атака 51%
В предыдущих статьях трека мы разобрали, как устроен блок и хеш-цепочка, и как криптография даёт подписи и адреса. Но у этих двух кирпичей есть общая дыра, которую они не закрывают.
Подпись доказывает авторство: «эту транзакцию действительно составил владелец ключа». Хеш-цепочка доказывает целостность: «эту историю не переписали задним числом незаметно». Ни то, ни другое не отвечает на главный вопрос платёжной системы: какая из двух подписанных транзакций была первой?
Алиса подписывает перевод всех своих монет Бобу и одновременно — перевод тех же монет себе на второй адрес. Обе транзакции валидны по подписи. Обе валидны по формату. Обе не нарушают целостность цепи. Проблема двойной траты — это не проблема криптографии, это проблема порядка. И решается она не математикой, а протоколом согласия — консенсусом.
Что именно нужно согласовать
Формально блокчейн — это реплицированный автомат состояний (replicated state machine). У каждого узла есть копия состояния (кто чем владеет) и детерминированная функция перехода apply(state, tx) -> state'. Если все узлы применят один и тот же список транзакций в одном и том же порядке, они получат идентичное состояние. Значит, задача сводится к одному: договориться о едином, растущем, неизменяемом журнале операций.
От протокола консенсуса мы хотим три свойства:
- Согласованность (safety, agreement). Два честных узла никогда не финализируют разные значения на одной позиции журнала. Практически: если ваш узел считает блок 20 000 000 финальным, ничей другой честный узел не считает финальным другой блок на этой высоте.
- Валидность (validity). Финализируется только то, что предложил кто-то и что проходит правила протокола. Нельзя «согласовать» транзакцию, которую никто не подписывал.
- Живучесть (liveness). Система продолжает добавлять новые блоки. Журнал не застревает навсегда.
Плохая новость из теории: теорема FLP (Fischer, Lynch, Paterson, 1985) доказывает, что в полностью асинхронной сети детерминированного протокола, который гарантирует и safety, и liveness при хотя бы одном отказе, не существует. Все реальные системы обходят FLP одним из двух способов: добавляют случайность (Nakamoto consensus, Algorand) или принимают предположение о частичной синхронности (PBFT, Tendermint) — «сеть иногда тормозит, но не бесконечно».
Вторая сложность — модель отказов. В корпоративном кластере узлы обычно просто падают (crash faults), и хватает Raft/Paxos. В открытой сети узлы лгут целенаправленно: подписывают конфликтующие сообщения, скрывают блоки, притворяются множеством участников. Это византийские отказы (Lamport, Shostak, Pease, 1982). Классический результат: против византийских отказов нужно $n \ge 3f + 1$ узлов, чтобы пережить $f$ предателей, то есть честными должны быть более двух третей.
И тут третья, убийственная проблема: атака Сивиллы. Правило «более 2/3 узлов» работает, только если известно, сколько всего узлов. В открытой сети я запускаю десять тысяч процессов на одном ноутбуке — и у меня «большинство». Голосование по головам в интернете бессмысленно.
Прорыв Накамото в 2008 году состоял не в изобретении хеш-цепочки (она была у Haber и Stornetta в 1991) и не в цифровых подписях. Он состоял в идее: привязать право голоса к дефицитному, внешнему по отношению к системе ресурсу. Не «один узел — один голос», а «одна единица работы — один голос». Подделать себе тысячу личностей дёшево. Подделать себе тысячу мегаваттных ферм — нет.
Proof of Work изнутри
Задача, которую решает майнер
В Bitcoin блок состоит из заголовка (ровно 80 байт) и списка транзакций. Согласуется именно заголовок: он содержит хеш родителя, корень дерева Меркла (обязательство на весь набор транзакций), время, компактную запись цели и nonce.
Правило: блок валиден, если SHA-256(SHA-256(header)), прочитанный как 256-битное число, не превышает целевое значение target. Хеш непредсказуем, поэтому единственная стратегия — перебирать nonce и пересчитывать. Это чистая лотерея без памяти: каждая попытка независима.
Ожидаемое число хешей до успеха:
$$\mathbb{E}\lbrack H \rbrack = \frac{2^{256}}{T + 1}$$
где $T$ — текущий target. «Сложность» (difficulty) — это просто удобная нормировка: $D = T_{\max} / T$, где $T_{\max}$ — target первого блока Bitcoin. При сложности $D$ нужно примерно $D \cdot 2^{32}$ хешей.
Реализация проверки и перебора — почти дословно из спецификации:
import hashlib
import struct
MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
def double_sha256(b: bytes) -> bytes:
"""Bitcoin хеширует дважды — исторически, как защита от length-extension."""
return hashlib.sha256(hashlib.sha256(b).digest()).digest()
def bits_to_target(bits: int) -> int:
"""Компактная 4-байтовая запись nBits разворачивается в 256-битный target."""
exponent = bits >> 24 # старший байт — показатель степени
mantissa = bits & 0x00FFFFFF # три младших байта — мантисса
return mantissa * 256 ** (exponent - 3)
def mine(version, prev_hash, merkle_root, timestamp, bits, max_nonce=1 << 32):
"""Заголовок — 80 байт: 4 + 32 + 32 + 4 + 4 + 4, little-endian, хеши в обратном порядке байт."""
target = bits_to_target(bits)
prefix = (struct.pack("<I", version) + prev_hash[::-1] + merkle_root[::-1]
+ struct.pack("<II", timestamp, bits))
for nonce in range(max_nonce):
h = double_sha256(prefix + struct.pack("<I", nonce))
# хеш интерпретируется как little-endian целое
if int.from_bytes(h, "little") <= target:
return nonce, h[::-1].hex() # в UI хеш показывают big-endian
# 2^32 вариантов nonce исчерпаны за доли секунды на современном ASIC,
# поэтому майнер меняет extranonce в coinbase → меняется merkle_root → новое пространство
raise RuntimeError("nonce исчерпан, нужен новый merkle_root")
Сложность. По времени — $O(2^{256}/T)$ хешей в среднем, геометрическое распределение; по памяти — $O(1)$, буквально 80 байт плюс контекст SHA-256. Именно константная память сделала возможными ASIC: чип с гигабайтами памяти не нужен, нужен только конвейер сжимающей функции. Некоторые сети (Ethereum до Merge с Ethash, Monero с RandomX) сознательно делали функцию memory-hard, чтобы уравнять GPU и ASIC — с переменным успехом.
Проверка асимметрично дешева. Найти блок — миллиарды хешей; проверить — два хеша. Это и есть суть «доказательства работы»: дорого создать, дёшево верифицировать.
Регулировка сложности
Хешрейт сети меняется на порядки. Чтобы интервал между блоками оставался стабильным, target пересчитывается. Bitcoin делает это каждые 2016 блоков (≈ 2 недели):
TWO_WEEKS = 14 * 24 * 3600
def next_target(old_target: int, actual_seconds: int) -> int:
"""Ретаргет Bitcoin: шаг коррекции ограничен четырёхкратным в обе стороны."""
actual = max(TWO_WEEKS // 4, min(actual_seconds, TWO_WEEKS * 4))
return min(old_target * actual // TWO_WEEKS, MAX_TARGET)
Два нюанса, которые любят на собеседованиях. Первый: в Bitcoin здесь исторический off-by-one — берётся разница времён между блоками с индексами, отстоящими на 2015, а не 2016, из-за чего средний интервал чуть меньше 10 минут. Баг не чинят, потому что починка — хардфорк ради нуля пользы. Второй: timestamp в заголовке не доверенный — майнер задаёт его сам. Протокол лишь требует, чтобы время было больше медианы одиннадцати предыдущих блоков и не более чем на 2 часа впереди сетевого времени. Отсюда прямое следствие для смарт-контрактов: время блока — это диапазон, а не точка.
Почему 10 минут, а не 10 секунд
Интервал блока — компромисс с задержкой распространения. Пока блок расходится по p2p-сети, другие майнеры продолжают работать над старым родителем. Если время распространения $t_p$ сравнимо с интервалом $t_b$, доля «осиротевших» блоков растёт как $\approx t_p / t_b$, а вместе с ней растёт и эффективное преимущество крупных пулов (они узнают о своих блоках мгновенно). Быстрые цепи вынуждены компенсировать это на уровне протокола: Ethereum до Merge платил за дядюшек (ommers) по правилу GHOST (Sompolinsky, Zohar, 2013), учитывая работу боковых ветвей в весе цепи.
Честно о цене PoW
PoW работает — Bitcoin не был успешно атакован на уровне консенсуса за пятнадцать с лишним лет. Но платить приходится честно:
- Энергия. Безопасность буквально пропорциональна сожжённому электричеству. Это не «баг», это механизм: атака дорога ровно потому, что защита дорога.
- Централизация железа. Производство ASIC — олигополия. Дешёвая энергия — география. В итоге хешрейт концентрируется.
- Пулы. Индивидуальный майнер с одной картой получает блок раз в тысячелетия, поэтому все идут в пулы. Несколько крупнейших пулов регулярно суммарно контролируют больше половины хешрейта. Юридически это разные независимые майнеры, но координация технически возможна — и это реальный, а не гипотетический риск концентрации.
Форки: почему цепь — это дерево
Ключевая вещь, которую пропускают почти все популярные объяснения: блокчейн локально — это дерево, а не цепь. Узел хранит все известные валидные блоки, а «цепь» — это путь от генезиса до головы, выбранный правилом выбора форка (fork choice rule).
В Bitcoin правило — не «самая длинная цепь», а цепь с наибольшей накопленной работой. Разница существенна: цепь из 100 блоков высокой сложности тяжелее цепи из 120 блоков низкой.
Пока обе ветки существуют, узлы разделены. Как только одна становится тяжелее, все честные узлы делают реорганизацию (reorg): откатывают блоки своей ветки, возвращают их транзакции в мемпул и применяют новую ветку. Транзакция, которая была «подтверждена» три блока назад, может вернуться в статус неподтверждённой. Это нормальная работа протокола, а не сбой.
Полный цикл обработки входящего блока:
Форк консенсуса против форка правил
Слово «форк» перегружено, и путаница тут стоит денег. Различают:
| Тип | Что происходит | Пример |
|---|---|---|
| Временный форк | Два валидных блока на одной высоте, разрешается сам за 1–2 блока | Стандартная работа сети |
| Soft fork | Правила ужесточаются: новые блоки валидны для старых узлов, но не наоборот | SegWit, Taproot в Bitcoin |
| Hard fork | Правила ослабляются или меняются: старые узлы отвергают новые блоки | The Merge, каждый апгрейд Ethereum |
| Chain split | Хардфорк без консенсуса сообщества — две монеты навсегда | Ethereum / Ethereum Classic (2016), BTC / BCH (2017) |
Софтфорк совместим «сверху вниз»: старый узел видит новые блоки валидными, просто не понимает дополнительных ограничений. Хардфорк требует, чтобы обновились все — иначе сеть расколется. Именно поэтому хардфорки координируются месяцами и активируются по номеру блока или по времени.
Финальность: когда «подтверждено» значит подтверждено
Вероятностная финальность
В Nakamoto consensus строгой финальности нет. Есть экспоненциально убывающая вероятность отката. Оценка из раздела 11 white paper Bitcoin: если атакующий контролирует долю $q$ хешрейта ($p = 1 - q$ у честных), вероятность того, что он догонит цепь после $z$ подтверждений:
$$P_{\text{atk}} = 1 - \sum_{k=0}^{z} \frac{\lambda^{k} e^{-\lambda}}{k!}\left(1 - \left(\frac{q}{p}\right)^{z-k}\right), \qquad \lambda = z \frac{q}{p}$$
Числа (по расчётам Накамото и уточнению Rosenfeld, 2014):
| Доля хешрейта $q$ | $z=1$ | $z=3$ | $z=6$ | $z=10$ |
|---|---|---|---|---|
| 10 % | 0,2045 | 0,0132 | 0,00024 | ~2·10⁻⁷ |
| 25 % | 0,4557 | 0,1316 | 0,0248 | 0,0021 |
| 40 % | 0,7043 | 0,4285 | 0,1980 | 0,0655 |
| 50 %+ | 1,0 | 1,0 | 1,0 | 1,0 |
Отсюда фольклорное «шесть подтверждений»: при разумном предположении $q \le 0{,}1$ шесть блоков дают вероятность отката около 0,02 %. Обратите внимание на последнюю строку: при $q > 0{,}5$ никакое число подтверждений не помогает. Это не «становится очень трудно» — это математически неизбежный успех при достаточном терпении.
Практический вывод: число подтверждений — это функция от суммы. Биржи требуют больше подтверждений для крупных выводов и меньше для мелких, и это ровно риск-менеджмент по формуле выше, а не суеверие.
Детерминированная финальность
BFT-семейство протоколов даёт другое обещание: блок финализирован сразу, и отменить его невозможно без нарушения предположения о том, что более 2/3 участников честны. Цена — фиксированный, известный набор валидаторов и остановка сети, если больше трети выпали.
Proof of Stake
Идея и её честные проблемы
Замените дефицитный внешний ресурс (энергию) на дефицитный внутренний (заблокированную монету). Право предлагать блоки и голосовать пропорционально стейку. Атака требует купить огромную долю предложения — а это дорого и, в отличие от ASIC, обесценивается вместе с атакуемой сетью.
Красиво, но наивный PoS ломается двумя способами:
Nothing at stake. В PoW майнер физически не может работать над двумя ветками одновременно — хешрейт делится. В PoS подписать блоки на всех ветках сразу ничего не стоит. Значит, форки не разрешаются экономически. Решение — слэшинг: протокол определяет объективно детектируемые нарушения (две подписи на одной высоте, «окружающий» голос), и любой может опубликовать доказательство, после чего у нарушителя сжигается часть стейка. Наказание должно быть внутрипротокольным и объективным — вот главное отличие рабочего PoS от бумажного.
Long-range attack. Валидаторы, вышедшие из системы, забрали свой стейк и больше ничем не рискуют. Они могут задним числом собрать альтернативную историю от старого чекпоинта. Криптографически такая цепь безупречна. Решение — weak subjectivity (Buterin, 2014): новый или долго отключённый узел обязан получить недавний доверенный чекпоинт из внешнего источника. Это признанное ослабление модели доверия по сравнению с PoW, где достаточно генезис-блока и правила «самая тяжёлая цепь». Не верьте маркетингу, который это замалчивает.
Gasper: как это работает в Ethereum
Ethereum использует Gasper (Buterin et al., 2020) — гибрид: правило выбора форка LMD-GHOST плюс наложенный слой финальности Casper FFG (Buterin, Griffith, 2017).
Структура времени: слот — 12 секунд, эпоха — 32 слота (6,4 минуты). В каждом слоте детерминированно (через RANDAO) выбирается один предлагающий и набор комитетов аттестаторов; каждый валидатор аттестует ровно раз за эпоху. Порог входа — 32 ETH; после апгрейда Pectra (2025) один валидатор может консолидировать до 2048 ETH эффективного баланса, что сократило раздувание числа ключей.
Аттестация несёт три голоса разом: голову цепи (для LMD-GHOST), source-чекпоинт и target-чекпоинт (для FFG). Подписи BLS агрегируются, поэтому миллион голосов помещается в разумный объём блока.
Финальность строится на чекпоинтах — первых блоках эпох:
Правило простое: ссылка (source → target), собравшая более 2/3 суммарного стейка, делает target обоснованным (justified). Когда обоснованы два чекпоинта подряд, нижний становится финализированным. От попадания транзакции в блок до финальности проходят две эпохи — около 12,8 минуты.
Почему откатить финализированное нельзя без огромных потерь: чтобы финализировать конфликтующий чекпоинт, нужно, чтобы более 1/3 стейка подписали противоречащие голоса. Такие голоса объективно детектируемы, и весь этот стейк сжигается. Это называется экономической финальностью: откат не невозможен физически, он стоит миллиарды и оставляет доказательство вины на цепи.
Отдельный механизм — inactivity leak. Если сеть не финализирует четыре эпохи подряд (например, треть валидаторов отключилась), балансы неактивных начинают квадратично утекать, пока активные снова не наберут 2/3. Система жертвует балансами отсутствующих ради восстановления живучести. Практический смысл: сеть переживает массовое отключение, но участники этого отключения теряют деньги.
Жизненный цикл валидатора со всеми штрафными путями:
Важная деталь слэшинга — корреляционный штраф. Если вас слэшнули в одиночку, потери невелики. Если одновременно слэшнули 30 % валидаторов, штраф каждого приближается ко всему стейку. Это делает экономически невыгодным запуск одного ключа на двух серверах «для надёжности» и наказывает системные ошибки провайдеров сильнее, чем частные.
Чистый LMD-GHOST уязвим к ex-ante реоргу: злонамеренный предлагающий придерживает блок и публикует его так, чтобы перебить следующего честного. Ethereum добавил proposer boost — блок, пришедший вовремя в свой слот, временно получает дополнительный вес, равный 40 % веса комитета слота. Это делает «догоняющие» атаки дороже. Обратная сторона: сама по себе эвристика, а не доказанное свойство, и исследования одиночных реоргов продолжаются.
BFT-финальность за один раунд: Tendermint
Альтернативная школа — классический BFT с известным набором валидаторов. CometBFT (бывший Tendermint), на котором работает экосистема Cosmos, даёт финальность в том же блоке.
Два раунда голосования (prevote, precommit) с порогом 2/3 дают классическую BFT-гарантию: два конфликтующих блока на одной высоте потребовали бы, чтобы более 1/3 валидаторов подписали противоречие — и это доказуемо, за это слэшат. Если предлагающий молчит, срабатывает таймаут и раунд увеличивается, предлагающий меняется.
Цена — приоритет safety над liveness: если офлайн больше трети стейка, сеть просто останавливается и не производит блоков, пока валидаторы не вернутся. Это происходило в реальности не раз в цепях Cosmos. Nakamoto consensus в такой ситуации продолжил бы работать, просто медленнее.
Сравнение честно
| Свойство | PoW (Bitcoin) | Gasper PoS (Ethereum) | BFT PoS (CometBFT) |
|---|---|---|---|
| Ресурс безопасности | Энергия и железо | Заблокированный стейк | Заблокированный стейк |
| Финальность | Вероятностная, ~60 мин | Экономическая, ~13 мин | Немедленная, ~1–6 с |
| Число валидаторов | Не ограничено | Сотни тысяч и больше | Обычно 100–200 |
| Ведёт себя при отказе 1/3 | Работает медленнее | Inactivity leak, восстановление | Полная остановка |
| Модель доверия при старте | Только генезис | Нужен недавний чекпоинт | Нужен недавний чекпоинт |
| Наказание за атаку | Потраченная энергия | Сожжённый стейк | Сожжённый стейк |
| Стоимость участия | Капитальные затраты на ASIC | 32 ETH и надёжный узел | Отбор в набор валидаторов |
Ни один вариант не «лучше». Это разные точки на кривой между открытостью, скоростью финальности и поведением при частичном отказе.
Атака 51 %
Что атакующий может и чего не может
Самое частое заблуждение: «51 % позволяет украсть все монеты». Нет. Контроль большинства ресурса даёт власть над порядком, а не над правилами и не над подписями.
Можно:
- Провести двойную трату: отправить монеты бирже, дождаться зачисления и вывода в другом активе, затем опубликовать более тяжёлую ветку, где отправки не было.
- Цензурировать: не включать чьи-то транзакции в свои блоки (полностью — только при контроле, стремящемся к 100 %).
- Осиротить чужие блоки, вытеснив остальных майнеров из вознаграждений.
Нельзя:
- Потратить чужие монеты — нужна чужая приватная подпись, а её большинство хешрейта не даёт.
- Создать монеты сверх эмиссии — узлы отвергнут блок как невалидный по правилам.
- Изменить финализированную историю в Gasper без сжигания трети стейка.
Механика двойной траты по шагам: атакующий начинает тайно майнить ветку от блока $N$, одновременно публикуя в публичной ветке платёж бирже. Биржа ждёт свои шесть подтверждений и отдаёт средства. Атакующий публикует накопленную тайную ветку, в которой платежа нет; она тяжелее, сеть реорганизуется, монеты возвращаются атакующему, а выведенное с биржи остаётся у него.
Это не теория
Реальные случаи, все проверяемые:
- Ethereum Classic, январь 2019: реорги глубиной более 100 блоков, двойные траты примерно на 1,1 млн USD. В августе 2020 — три атаки подряд, одна с реоргом глубиной свыше 7000 блоков. Coinbase публично описала первую атаку и подняла требование до 5000+ подтверждений.
- Bitcoin Gold, май 2018: около 18 млн USD двойных трат; повторно в январе 2020.
- Verge, Vertcoin, Feathercoin и десятки мелких PoW-монет — атаки арендованным хешрейтом.
Общий знаменатель: малая доля от общего хешрейта своего алгоритма. Если сеть использует SHA-256, но владеет одним процентом мирового SHA-256-хешрейта, её безопасность равна цене аренды этого одного процента на несколько часов. Отсюда практическое правило инженера, а не инвестора: безопасность цепи определяется не её собственным хешрейтом, а стоимостью аренды превосходящего его хешрейта.
Selfish mining: атака дешевле 51 %
Eyal и Sirer (2013) показали, что майнер, придерживающий найденные блоки и публикующий их стратегически, получает долю вознаграждения выше своей доли хешрейта уже примерно с 25–33 %. Это не двойная трата, а искажение стимулов: рациональные майнеры начинают присоединяться к эгоистичному пулу, ускоряя концентрацию. Порог «51 %» — верхняя граница проблемы, а не нижняя.
Атака на PoS
В Gasper прямого аналога 51 % нет — есть три разных порога:
- ~34 % стейка — можно помешать финальности (не набрать 2/3), запустив inactivity leak. Сеть выживет, атакующий потеряет часть баланса.
- ~51 % стейка — контроль над выбором головы: цензура, реорги нефинализированных блоков.
- ~67 % стейка — можно финализировать конфликтующие чекпоинты, но за это сжигается весь стейк атакующего.
Дополнительный, неформальный уровень защиты — social slashing: сообщество договаривается о хардфорке, обнуляющем стейк атакующего. Это выход за пределы протокола, и относиться к нему надо трезво: он работает, но означает, что в предельном сценарии решение принимают люди, а не код. Утверждение «код — это закон» на этом уровне перестаёт быть точным.
Что из этого следует для вашего кода
Консенсус — не абстракция «где-то в сети». Он протекает в приложение через три конкретные вещи.
1. Реорг-безопасная индексация
Наивный индексатор, который просто читает блоки подряд и пишет события в БД, сломается на первом же реорге: в базе останутся события транзакций, которых больше нет в цепи. Правильный подход — хранить хеш блока и уметь откатываться:
from dataclasses import dataclass
@dataclass
class Head:
number: int
hash: str
parent_hash: str
class ReorgAwareIndexer:
"""Индексатор, переживающий реорганизации цепи."""
def __init__(self, rpc, db, confirmations: int = 12):
self.rpc, self.db = rpc, db
self.confirmations = confirmations # глубина, ниже которой не откатываемся
def step(self) -> None:
next_number = self.db.last_indexed_number() + 1
block = self.rpc.get_block(next_number)
if block is None:
return # ждём новый блок
expected_parent = self.db.hash_of(next_number - 1)
if expected_parent is not None and block.parent_hash != expected_parent:
# родитель не тот, что мы записали — произошёл реорг
self.rollback_to_common_ancestor(next_number - 1)
return
self.db.apply_block(block)
def rollback_to_common_ancestor(self, number: int) -> None:
"""Идём вниз, пока локальный хеш не совпадёт с каноническим."""
while number > 0:
canonical = self.rpc.get_block(number)
if canonical is not None and canonical.hash == self.db.hash_of(number):
break
self.db.revert_block(number) # откатываем все производные записи
number -= 1
def finalized_view(self) -> int:
return self.rpc.get_block_by_tag("finalized").number # то, что уже нельзя откатить
В Ethereum после Merge у RPC есть теги latest, safe и finalized. finalized — это гарантия Casper FFG, а не эвристика. Для критичных операций (зачисление средств, выдача товара) читайте finalized; для отзывчивого UI показывайте latest, но помечайте как «ожидает подтверждения».
2. Данные блока — не источник случайности
Прямое следствие того, что предлагающий блок влияет на его содержимое:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract Lottery {
// ПЛОХО: всё внутри keccak известно или управляемо предлагающим блока
function badRandom() external view returns (uint256) {
return uint256(
keccak256(abi.encodePacked(block.timestamp, block.prevrandao, msg.sender))
) % 100;
}
// ПЛОХО ПО-ДРУГОМУ: blockhash текущего блока всегда 0,
// а история доступна лишь на 256 блоков назад
function alsoBad() external view returns (bytes32) {
return blockhash(block.number); // всегда 0x00...0
}
}
Почему это ломается. block.timestamp задаёт предлагающий с точностью до допустимого протоколом окна — в PoW это часы, в PoS предлагающий выбирает, публиковать блок или пропустить слот. block.prevrandao — это RANDAO-микс, и последний предлагающий эпохи имеет один бит влияния: опубликовать блок или промолчать, изменив итоговое значение. Одного бита достаточно, чтобы сдвинуть исход лотереи в свою пользу, если ставка окупает потерянную награду за блок. Наконец, любой контракт видит результат в той же транзакции — атакующий просто откатывает вызов через require, если исход неудачный.
Рабочие варианты: коммит-раскрытие в двух транзакциях с залогом, внешний VRF (например, Chainlink VRF), либо схемы вроде RANDAO с задержкой раскрытия. Подробнее об этом классе уязвимостей — в статье о безопасности контрактов.
3. Финальность как продуктовое решение
Сколько подтверждений ждать — это не техническая, а риск-константа. Отталкивайтесь от суммы: стоимость атаки должна превышать выигрыш. Для чашки кофе за 5 USD ноль подтверждений на L1 разумны. Для перевода на 10 млн USD — ждите финальности, а на PoW-цепях смотрите на долю сети в общем хешрейте алгоритма. Отдельная тема — финальность на L2, где к этому добавляется задержка публикации данных на L1; разберём в статье о масштабировании.
Типичные ошибки
- «Транзакция в мемпуле — значит, прошла». Нет. Она не в цепи вообще. Её могут не включить никогда.
- «Одно подтверждение — достаточно». Одно подтверждение при доле атакующего $q = 0{,}1$ даёт 20 % вероятность отката по формуле выше.
- Индексация без хеша блока. Единственный способ обнаружить реорг — сравнивать
parent_hash, а не номера. - Случайность из
block.timestamp/prevrandao/blockhash. Классика потерянных денег. - Строгая арифметика по времени в контрактах.
block.timestamp— величина с погрешностью в секунды-минуты, а не часы. - Вера в «новый супербыстрый консенсус» без объяснения, какое предположение он ослабил. Быстрая финальность всегда чем-то куплена: фиксированным набором валидаторов, доверием к чекпоинту, остановкой при отказах. Если в описании нет раздела о том, что ломается, — это маркетинг, и часто прикрытие для схемы отъёма денег.
- Стейкинг «под ключ» без понимания рисков. Делегирование в кастодиальный сервис добавляет и риск слэшинга по чужой ошибке, и обычный контрагентский риск. Ключ вывода — это ваш ключ; его потеря означает потерю стейка так же окончательно, как потеря любого приватного ключа.
Мини-итог
- Консенсус решает задачу порядка, а не подлинности: подписи и хеши её не закрывают.
- В открытой сети голосование по головам ломается атакой Сивиллы, поэтому право голоса привязывают к дефицитному ресурсу: работе (PoW) или стейку (PoS).
- PoW — лотерея с проверкой
hash ≤ target, константной памятью и вероятностной финальностью; правило выбора форка — наибольшая накопленная работа, а не длина. - Цепь локально всегда дерево; реорганизации — штатное поведение, и код обязан их переживать.
- PoS требует объективного слэшинга против nothing-at-stake и внешнего чекпоинта против long-range атак — это реальное, а не выдуманное ослабление модели доверия.
- Gasper даёт экономическую финальность за две эпохи; BFT-протоколы вроде CometBFT — мгновенную, ценой остановки сети при отказе трети валидаторов.
- Атака 51 % даёт власть над порядком, а не над чужими ключами; на малых цепях она реальна, дёшева и происходила многократно.
- В коде это проявляется как реорг-безопасная индексация, чтение тега
finalizedи полный запрет на использование данных блока как источника случайности.
Источники
- Bitcoin: A Peer-to-Peer Electronic Cash System — Satoshi Nakamoto, 2008. Раздел 11 — расчёт вероятности отката.
- Impossibility of Distributed Consensus with One Faulty Process — Fischer, Lynch, Paterson, 1985, и The Byzantine Generals Problem — Lamport, Shostak, Pease, 1982.
- Practical Byzantine Fault Tolerance — Castro, Liskov, 1999. Основа семейства Tendermint.
- Majority is not Enough: Bitcoin Mining is Vulnerable — Eyal, Sirer, 2013, и Analysis of hashrate-based double spending — Rosenfeld, 2014.
- Secure High-Rate Transaction Processing in Bitcoin (GHOST) — Sompolinsky, Zohar, 2013.
- Casper the Friendly Finality Gadget — Buterin, Griffith, 2017, и Combining GHOST and Casper — Buterin et al., 2020.
- Proof of Stake: How I Learned to Love Weak Subjectivity и Why Proof of Stake — Buterin.
- Ethereum consensus specs — исполняемая спецификация beacon-цепи; eth2book Ben Edgington — подробный разбор Gasper.
- CometBFT documentation — спецификация BFT-консенсуса Tendermint.
- Bitcoin developer reference: block chain — точные форматы полей заголовка.
Что дальше
Мы разобрали, как сеть договаривается о порядке. Теперь посмотрим, что именно она этим порядком исполняет: аккаунты, транзакции, газ и модель состояния самой распространённой программируемой цепи.