Часть I · Глава 1 из 43

Вы могли бы изобрести Ethereum

Одна страница, одно непрерывное построение. Начнём с единственной общей таблицы, покажем в анимации единственный её изъян, исправим ровно его — и повторим, пока программируемый мировой компьютер на стейке не выпадет как единственное, чем эта таблица могла стать.

Обновлено 21 июн. 2026 г. · 40 мин
Предполагается
  • любопытство

Большинство объяснений Ethereum вручают вам готовую машину и называют её детали. Это — её строит. Мы начинаем с самого тупого устройства, которое почти работает, смотрим на единственное, что его ломает, и исправляем ровно это — ничего больше. Сделайте так девять раз — и знаменитые кусочки перестают быть набором фактов для зубрёжки. Каждый из них — просто ответ на вопрос «что сломалось на прошлом шаге?»

Вот аналогия, которую мы будем держать в голове весь путь вниз. Забудьте на минуту про монеты и майнеров. Представьте одну таблицу, которую разделяет весь мир — единственный лист «кто-чем-владеет», который может читать кто угодно и в который кто угодно может предложить изменения. К концу эта таблица сможет исполнять программы, ею не будет владеть никто, а врать ей будет стоить целое состояние. Эта таблица — Ethereum.

И дело не только в анимации. В тот момент, когда шаг изобретает кусочек, вы прямо там запустите настоящий — реальный подписчик secp256k1, работающую EVM, побайтово точное дерево Меркла-Патриции, живой контроллер EIP-1559, симулятор реорганизации, который можно атаковать — и подхватите ту деталь спецификации, которую настоящему протоколу пришлось зафиксировать. Та же инкрементальная нить, интуиция прямиком до работающей машины.

1 шаг Общий лист

Одна таблица на весь мир

Выбросите банк. На его место поставьте единственную публичную таблицу: одна строка на аккаунт, один столбец под его баланс. Чтобы кому-то заплатить, вы отправляете правку — «вычесть 5 из моей строки, прибавить 5 к строке Боба» — и каждый применяет ту же правку к своему виду. Нет ни выписки, которую нужно запрашивать, ни кассира, у которого нужно спрашивать; лист и есть истина, а истина — это просто куча чисел, которые правки двигают туда-сюда.

МИРОВОЕ СОСТОЯНИЕ 0xAlice14 Ξ 0xBob9 Ξ 0xCarol2 Ξ +5 Ξ → правки двигают числа в одной общей таблице
Состояние — это лист балансов; транзакция — это правка, которая двигает числа между строками.

В этом и весь мысленный образ состояния Ethereum: не история платежей, а текущее значение в каждой строке прямо сейчас. Оно чистое, оно общее — и оно нараспашку.

→ Шаг 2: сделать так, чтобы каждую правку мог написать только её владелец.

2 шаг Подписываем правки

Печать, которую можешь поставить только ты

Дайте каждому аккаунту секрет. Не пароль, который вы показываете, чтобы войти, — а закрытый ключ Секретное 256-битное число. Его адрес выводится из него односторонне; только ключ может произвести валидную подпись, но проверить её может кто угодно. , который никогда не покидает ваших рук. Им вы припечатываете печать к каждой правке: подпись. Магия печати — в асимметрии: чтобы её произвести, нужен секрет, а чтобы её проверить, нужен лишь ваш публичный адрес. Любой может подтвердить, что печать настоящая; никто не может её подделать.

send 5 Ξ to Bob сообщение (публичное) подписать ключом подпись проверка ✓ подлинно signer 0xYou подпись доказывает, что сообщение от владельца ключа
Подпись припечатывается к публичному сообщению. Любой её проверит — но измени хоть один символ, и печать больше не совпадает.

Подделайте хоть одну цифру подписанной правки — и печать перестанет совпадать с содержимым. Подпись не приклеена рядом с сообщением; она вычисляется из него. Ваша личность на листе — это не имя, которое кто угодно может напечатать, а просто способность произвести эту печать.

Проверка: принять подлинное, отвергнуть подделанное

Проверка — зеркало подписания. Любой берёт сообщение, подпись и ваш публичный адрес и проверяет, что они согласованы, — и середины тут нет. Нетронутое сообщение проходит; то же сообщение с единственным перевёрнутым байтом — не проходит, каждый раз. Этот бинарный исход и есть вся суть: печать превращает «доверься мне» в «проверь сам».

✓ нетронутое сообщение send 5 Ξ to Bob sig проверка ВЕРНО signer 0xYou ✗ изменён один байт send 5 Ξ to Eve sig проверка НЕВЕРНО signer ≠ you
Одна подпись, два сообщения. Нетронутое проходит проверку (✓ валидно, подписант = вы); переверни один байт — проверка проваливается (✗ невалидно) — промежутка нет.

От ключа к адресу: односторонний хеш

Откуда берётся этот «публичный адрес»? Вы прогоняете свой открытый ключ через хеш — функцию-отпечаток в одну сторону, первый из фундаментальных инструментов, на которые мы будем опираться снова и снова. Подайте ей любой вход — она вернёт фиксированные 32 байта; измените один символ — почти каждый байт вывода перевернётся; и обратить её невозможно. Ваш адрес — это просто последние 20 байт хеша вашего открытого ключа. Познакомьтесь с машиной здесь — вы увидите ровно ту же лавину, когда мы будем снимать отпечаток со всего состояния и связывать блоки вместе.

Интерактив

keccak256 — складывающая машина

Любой вход, сколь угодно длинный, становится ровно 32 байтами. Измени один символ — и почти каждый байт вывода перевернётся. Эта чувствительность — и есть вся причина, по которой отпечаток способен обнаружить изменение.

Input17 bytes in · any length
keccak256256-bit sponge · always 32 bytes out
Digest · 32 bytes
d0e28d17a11f073ece738166449587d268272a4400680e4ae2577f2f98ab07ac
0xd0e28d17a11f073ece738166449587d268272a4400680e4ae2577f2f98ab07ac
One tiny change in → a totally different fingerprint out. That's the avalanche, and it's why a single edited balance rewrites the whole path of hashes up to the state root.

Настоящий keccak256 (тот же хеш, что использует Ethereum), вычисляемый в вашем браузере.

Настоящая печать: secp256k1

Теперь весь конвейер разом, по-настоящему. Печать — это ECDSA-подпись над кривой secp256k1, ровно та схема, которую использует Ethereum. Сгенерируйте ключ и проследите private key → public key → address (последняя стрелка — это хеш выше), затем подпишите сообщение. Вы заметите, что подписанта можно восстановить прямо из подписи — отложите это; в этом весь фокус шага 3.

Интерактив

Лаборатория ключей и подписей

Закрытый ключ выводит открытый ключ, который выводит адрес — только в одну сторону. Подпишите сообщение, затем восстановите по подписи, кто его подписал. Подделайте сообщение, чтобы сломать всё.

private key
secret · 256-bit random number · never share
public key0x044e3b81af…dd277956dea point on secp256k1 · shareable
address0x2c7536e3…96a65c23last 20 bytes · this is "you"

→ One-way only. Public key and address are derived from the private key; you can't run the arrows backward.

This is a signature, not encryption. The message stays public — anyone can read it; the signature only proves who wrote itand that it wasn't altered. Encryption (hiding the contents) is the other use of the same keypair math, and Ethereum doesn't do it to transactions. Real secp256k1 + EIP-191, computed in your browser.

Настоящий ECDSA secp256k1 + EIP-191; адрес, подпись и восстановление выверены побайтово против viem.

Формула
private key 1 public key 2 address 3
  1. 1 случайный 256-битный секрет — единственное, что вы храните
  2. 2 secp256k1 умножает его на точку кривой (в одну сторону)
  3. 3 последние 20 байт keccak256(публичного ключа)
Каждая стрелка ведёт только вправо, никогда влево. На этой асимметрии и держится подпись.

Одно уточнение, потому что оно следует прямо из «сообщение публично» и почти все понимают его наоборот: подписание — это не шифрование. Та же пара ключей могла бы шифровать, но с поменянными ролями — а Ethereum L1 вообще не прячет ваши правки.

Подписание (то, что мы только что построили)Шифрование (то, чем оно не является)
Цельподлинность + целостностьконфиденциальность
Закрытый ключподписываетрасшифровывает
Открытый ключпроверяет / восстанавливаетшифрует
Сообщениепубличноскрыто

Ваша правка — открытый текст на листе навсегда; печать доказывает кто, и что не изменено — но никогда не скрывает.

И ещё одну тонкую дыру настоящему протоколу пришлось заткнуть. Сырая подпись — это просто подпись над байтами, так что dapp мог бы подсунуть вам безобидно выглядящее «сообщение», которое втайне является валидной транзакцией, — и ваша печать его авторизует. Лекарство — никогда не печатать сырые байты, а байты внутри помеченного контекста:

→ Шаг 3: прочитать автора прямо из печати.

3 шаг Читаем автора

Подпись уже говорит, кто вы

Вот фокус, который позволяет Ethereum полностью отказаться от поля «from». Из подписи и сообщения, которое она покрывает, можно прогнать математику в обратную сторону и восстановить ровно тот адрес, который должен был её произвести. Автор — не заявление, прикреплённое к правке, а нечто, что вы вычисляете из самой печати. Соврите о том, чью строку трогать, — и восстановленный адрес окажется чужаком, у которого нет денег для движения.

только подпись ecrecover кто подписал? 0xBob…a3f1 восстановленный подписант поле «from» не нужно; подпись раскрывает отправителя
Восстановление: адрес подписанта выводится из одной только подписи. Правке вообще не нужно заявлять, кто её отправил.

Так что у Ethereum-транзакции и вправду нет поля «from». Сеть берёт вашу подписанную правку, восстанавливает адрес — и это единственная строка, которую ей разрешено списать. Авторство и полномочие схлопываются в одну неподделываемую печать — но одно авторство ещё не безопасность.

Подпись переиспользуема — отсюда nonce

Вот изъян, который печать не чинит: она никогда не истекает, а сообщение, которое она покрывает, публично. В тот момент, когда вы вручаете Бобу подписанное «вычесть 5 у меня, прибавить 5 Бобу», Боб держит совершенно валидную правку, которую он может транслировать снова — и снова. Каждая копия проходит проверку; каждая копия двигает ещё 5. Один платёж становится медленной утечкой, которую вы никогда не санкционировали.

без nonce отпр. 5 Ξ → Bob (no nonce) ✗ снова повтор 9 → 4 → -1 Ξ одна подписанная правка применяется снова и снова — баланс утекает с nonce отпр. 5 Ξ → Bob nonce 7 · spent 9 → 4 Ξ копия отклоняется — nonce 7 уже потрачен, правка применяется один раз
Подписанное сообщение публично и валидно навсегда, поэтому любая копия воспроизводится. Nonce делает каждую одноразовой.

Лекарство — один счётчик, вложенный в подписанное сообщение: nonce, «это моя 7-я транзакция». Сеть помнит, сколько каждый аккаунт потратил, и применяет только следующий по очереди номер. Ваша правка с nonce-7 проходит ровно один раз; каждая воспроизведённая копия теперь называет 7, который аккаунт уже миновал, поэтому отскакивает. Упорядочивание получается бесплатно — правки аккаунта могут вступать в силу только в порядке nonce, так что никто не может их и переставить.

Воспроизводима и между мирами — отсюда chainId

Остаётся одна более тонкая копия. То же ПО работает в тестнетах и других EVM-цепочках, которые держат те же самые адреса. Подписанный перевод с nonce-7 на тестовой сети прошёл бы проверку точно так же, будучи воспроизведённым на настоящей. Поэтому подписанное сообщение также фиксирует chainId — делая правку валидной ровно на одной цепочке и инертной на каждой другой.

Голый перевод наконец-то непробиваем, и взгляните, почему там есть каждое поле: получатель и сумма, чтобы сказать, что делать, nonce, чтобы её нельзя было воспроизвести здесь, chainId, чтобы её нельзя было воспроизвести в другом месте, и подпись, которая и есть отправитель. Вытащите любое одно — и конкретная дверь распахивается.

Построить настоящую транзакцию

Этот список — отправитель-через-восстановление, nonce, chainId, получатель, сумма — и есть Ethereum-транзакция. Постройте и подпишите её по-настоящему: отредактируйте поля, подпишите и посмотрите, как отправитель восстанавливается из подписи, не появляясь в байтах нигде. Подделайте поле после подписания — и восстановление схлопнется в чужака.

Интерактив

Подписчик транзакций

Редактируйте, подписывайте, восстанавливайте. Хеш для подписи обновляется вживую; подписание производит (r, s, yParity); отправитель восстанавливается из этой подписи. Подделайте поле после подписания, чтобы сломать всё.

Your wallet0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266private key 0xac09…ff80 · a public throwaway demo key
The transaction
  • chainId1
  • maxFeePerGas30 gwei
  • gasLimit21000
  • no from field →
Signing hash · keccak256(0x02 ‖ rlp(tx))0x35935b2459…1917d94a

Real secp256k1 ECDSA + EIP-1559 RLP, computed in your browser. Edit any field after signing: the signature only authorizes the exact bytes it signed, so the recovered sender turns into a stranger — that's the whole security model.

Настоящий ECDSA secp256k1 + RLP EIP-1559; хеш для подписи, хеш транзакции и восстановление выверены побайтово против viem.

Восстановление — приём, который позволяет нам отказаться от поля «from», — это просто математика подписания, прогнанная в обратную сторону:

А байты разложены как типизированный конверт — байт типа, затем RLP-полезная нагрузка, — чтобы протокол мог добавлять новые формы транзакций без неоднозначности:

Этот один байт превратил «формат транзакции» в версионированное семейство — то же ядро (подписана, упорядочена по nonce, замерена по газу), новые поля прикручиваются по мере появления нужд:

  • 0x00 Legacy — изначальный, до-типизированный формат.
  • 0x01 EIP-2930 — добавляет список доступа (предобъявленное хранилище, ради газа).
  • 0x02 EIP-1559 — сегодняшний дефолт: базовая комиссия + приоритетные чаевые (поля газа, до которых мы дойдём на шаге 9).
  • 0x03 EIP-4844 — транзакции, несущие blob, сердце доступности данных L2.
  • 0x04 EIP-7702 — позволяет EOA временно действовать как аккаунт-смарт-контракт.

→ Шаг 4: пусть никто не держит мастер-копию.

4 шаг Копия у каждого

Никакой мастер-копии — лист крутит каждый

Итак, таблицу не хостит никто. Её хостят все. Каждый участник держит полный лист и правила его обновления и независимо перепроверяет каждую подписанную правку перед применением. Нет привилегированного сервера, который можно вызвать в суд, подкупить или выключить. Вы не доверяете сети — вы держите собственную копию и проверяете её с самой первой строки.

ваша правка ✗ отброшено один сервер одна копия один хост может тихо отбросить или переписать вашу правку ваша правка каждый узел перепроверяет; ни один хост не заглушит
Лист реплицирован повсюду. Каждый узел заново выводит то же состояние из тех же правок — не доверяй никому, проверяй всё.

Это и есть настоящий скачок от «базы данных» к «блокчейну»: данные перестают жить где-то и начинают жить везде, а правила вшиты в каждую копию.

→ Шаг 5: свернуть весь мир в одно число.

5 шаг Один отпечаток

Один отпечаток для всего мира

Грубое лекарство использует хеш из шага 2: прогнать весь лист через складывающую машину и сравнить единственное число, которое из неё выпадает. Оно наполовину работает и ломается двумя способами: изменение одного баланса заставляет перехешировать весь лист, а чтобы доказать кому-то свой баланс, вам пришлось бы вручить ему весь лист, чтобы он мог его перехешировать.

Исправьте оба изъяна с помощью дерева хешей. Хешируйте аккаунты группами, затем хешируйте эти хеши, уровень за уровнем, до единственного корня. Теперь правка одного аккаунта перехеширует лишь короткий путь от его листа до корня — а не весь лист, — и вы можете доказать один аккаунт тому, кто держит только корень, послав лишь хеши-братья вдоль этого пути. Это дерево Меркла.

Но состояние — не аккуратный массив фиксированного размера; это разреженная, постоянно меняющаяся карта с ключами из 20-байтовых адресов. Простое дерево не кодирует, где живёт аккаунт. Так сделайте ключ самим путём: прочитайте адрес как строку шестнадцатеричных цифр — полубайтов (nibbles) — и пройдите по ним вниз по дереву, чтобы найти аккаунт.

адрес a 7 3 f c branch root node — take slot a branch next node — take slot 7 extension shared nibbles 3 f c leaf value = 9 Ξ re-hash правим лист, перехешируем путь вверх, новый корень
Адрес — это путь из полубайтов. Пройдите по нему вниз по дереву к аккаунту; три типа узлов делают этот проход эффективным.

Эффективный проход по этому пути принуждает ровно три вида узлов, и каждый заслуживает своё место. Узел-ветка (branch) — это 16-стороннее разветвление (по слоту на каждый возможный следующий полубайт), используемое везде, где пути расходятся. Лист (leaf) держит финальный отрезок пути и значение аккаунта. А узел-расширение (extension) сжимает длинный пробег полубайтов, который всё, что ниже, оказывается общим, — чтобы дерево не растрачивало целую 16-стороннюю ветку на отрезок, где ничего не ветвится. Хешируйте каждый узел из его детей — keccak от его кодировки — до единственного корня, и у вас есть дерево Меркла-Патриции Ethereum: часть «Патриция» делает обновления по ключу дешёвыми, часть «Меркл» делает всё это одним проверяемым отпечатком.

0xA1… 14 Ξ 0xB2… 9 Ξ 0xC3… 2 Ξ trie ЗАГОЛОВОК БЛОКА # 9f2e a2e0 правим один аккаунт → выпадает новый state root
Отредактируйте аккаунт, перехешируйте вверх по его пути — и выпадет совершенно новый 32-байтовый корень состояния (state root) — единственное число, которое снимает отпечаток со всего мира.

Разберём настоящее дерево на части

Это самое что ни на есть подлинное — те же RLP, теги узлов с hex-префиксом и keccak256, что использует живой клиент; stateRoot, который он печатает, побайтово совпадает с тем, что вычислили бы go-ethereum или reth. Вставьте аккаунт и проследите, как путь перестраивается и пульсирует к новому корню; кликните по любому узлу, чтобы прочитать его кодировку.

Интерактив

Визуализатор дерева Меркла-Патриции

Вставьте или отредактируйте аккаунт. Узлы скользят на новые позиции, а изменённый путь пульсирует вверх до совершенно нового корня состояния. Кликните по узлу, чтобы изучить его RLP и хеш.

abcd19BRANCH2 children0xfe1d55…dfaaEXTENSIONshares: 70xd4c332…c9dfLEAF · inlineDave · 0.4 ETHpath …014BRANCH2 children0x74362b…7f04BRANCH2 children0x3bfd19…99b6LEAF · inlineCarol · 88.1 ETHpath …4LEAF · inlineAlice · 12.0 ETHpath …LEAF · inlineBob · 3.4 ETHpath …
Branch (16-way)Extension (shared prefix)Leaf (value)Rewritten by last edit
Block headerwhere the root lands
  • parentHash0x…
  • stateRoot0xfe1d55aa34af
  • transactionsRoot0x…
  • receiptsRoot0x…
  • number0x…
  • timestamp0x…
BRANCH
children
a b
rlp
0xf84480808080808080808080a0d4c33224293ce06be86e1378a345a9bb844acc66d204ef8b6b18edde43cdc9dfd38230148f4461766520c2b720302e34204554488080808080
rlp length
70 bytes
state root
0xfe1d55aa34af3c72deffdd4d1ff21f7ea4a835852e1a6a77f1ca753f86c2dfaa

Настоящий движок MPT, выверенный побайтово против @ethereumjs/mpt на 100 случайных тестах.

Два правила кодирования заставляют каждого клиента согласиться до байта. RLP сериализует узел перед хешированием; hex-префиксное кодирование упаковывает путь из полубайтов узла плюс флаг (лист против расширения, нечётный против чётного) в байты. И одно правило подставляет подножку каждому самописному дереву:

То, что лежит в листе, — не просто баланс, а четыре поля аккаунта, и два из них оживают лишь тогда, когда аккаунты могут держать код (шаг 8):

Этот storage_root — выдаёт всё: как только появляются контракты, каждый из них держит ещё одно MPT для своего хранилища, чей корень живёт здесь, в листе аккаунта, — так что одна запись в хранилище прокатывает новый корень хранилища в лист, который прокатывает новый корень состояния, та же волна, вложенная на уровень глубже. (Одна деталь из реального мира: ключи на самом деле — keccak256(address), а не сырой адрес — «защищённое дерево» (secure trie), — что рассеивает аккаунты так, что префиксы редко сталкиваются.)

Почему форма всё ещё меняется

У шестнадцатеричного дерева есть счёт к оплате: чтобы доказать один аккаунт тому, кто держит только корень, — то, что нужно клиенту без состояния (stateless client) Узел, который проверяет блоки, используя свидетельства (доказательства), поставляемые вместе с блоком, вместо хранения полного состояния у себя. , — вы должны раскрыть на каждом уровне хеши-братья, в которые вы не спускались. 16-сторонняя ветка означает до 15 братьев на уровень; умножьте на глубину — и свидетельства раздуваются до мегабайтов. Это и есть препятствие для бессостоятельности, и именно поэтому обязательство (commitment) перепроектируется.

Интерактив

Шестнадцатеричное MPT → Verkle → Двоичное

Один и тот же аккаунт, доказанный тремя способами. Посмотрите, как арность узла и размер свидетельства торгуются друг с другом — и почему дорожная карта сдвинулась.

16 slots/level · many siblings to reveal
Node arity16-way
Witness sizelarge

A branch node has 16 slots. To prove one account you must reveal up to 15 sibling hashes at every level — and the secure-trie keys make the tree deep-ish and wide.

whyMerkle proof cost ≈ (arity − 1) × depth hashes. With arity 16, siblings dominate the witness.

Diagrams and bars are illustrative, order-of-magnitude — not a live cryptographic benchmark. The takeaway is structural: the commitment scheme dictates the tree shape, and the tree shape dictates how big a stateless witness has to be.

Годами лекарством были деревья Verkle (векторные обязательства: одно короткое доказательство на все 256 детей, братья перестают иметь значение). На протяжении 2024–2025 годов дорожная карта развернулась к двоичному дереву Меркла (EIP-7864): арность 2, обычное хеширование, без доверенной настройки и дружелюбное к доказывателям SNARK (SNARK provers) Системы, которые производят сжатые доказательства того, что вычисление было выполнено корректно. Простые деревья на основе хешей доказывать внутри SNARK гораздо дешевле, чем схемы векторных обязательств. , которые в итоге будут проверять переходы состояния Ethereum.

→ Шаг 6: заморозить историю в цепочку.

6 шаг Блокчейн

Сцепи пачки, чтобы прошлое не могло сдвинуться

Перестаньте обращаться с правками как с разрозненными бумажками. Соберите их в пронумерованный блок и сведите его к небольшому заголовку (header): его номер, parentHash (хеш блока перед ним), txsRoot, снимающий отпечаток с правок внутри, и stateRoot, который мы только что построили, — отпечаток всего листа после этого блока. Собственный hash блока — это хеш всего этого заголовка. Поскольку складывающая машина из шага 2 превращает любое однобитовое изменение в совершенно другой вывод, заголовок привязывает блок ко всей истории до него и к точному состоянию, которое он произвёл.

block #1 stateRoot a2e0 txsRoot c109 prev hash 3a07 block #2 stateRoot 71c4 txsRoot c119 prev hash 88b2 block #3 stateRoot 0db8 txsRoot c129 prev hash f0c5 новый корень → block.stateRoot ветвь root 2d7a a f ветвь a· 8a41 ветвь f· 77c2 7 9 2 5 a7 · Alice бал. 9 Ξ h 8a47 a9 · Bob bal 6 Ξ h 55de f2 · Carol bal 20 Ξ h 9e10 f5 · Dave bal 3 Ξ h b204
Полное дерево состояния из четырёх аккаунтов под заголовком блока. Отредактируйте баланс Алисы (14 → 9) и проследите реальный пересчёт: хеш её листа меняется (3c1a → 8a47), его ветка перехешируется (3c08 → 8a41), корень перехешируется (91f3 → 2d7a) в новый stateRoot (9f12 → a2e0), хеш блока меняется, и каждый последующий блок ломается — а нетронутые поддеревья Боба, Кэрол и Дэйва сохраняют свои хеши.

Теперь посмотрите, что делает один подделанный баланс. Измените Alice 14 → 9 глубоко в состоянии блока #1 — и эффект не остаётся локальным. Лист её аккаунта меняется, что перехешируется вверх по дереву в новый stateRoot; stateRoot сидит в заголовке блока #1, так что заголовок меняется; hash заголовка меняется; и теперь parentHash блока #2 указывает на хеш, которого больше не существует, — так что блок #2 невалиден, что делает невалидным блок #3, и так далее по всей линии. Одно отредактированное число — и вся цепочка после него разваливается. Чтобы переписать один прошлый баланс, вам пришлось бы втихую перестроить каждый блок с тех пор. Прошлое заморожено не стражником, а самим весом всего, что навалено сверху. Эта связанная, упакованная, обязующая состояние история — блокчейн.

→ Шаг 7: согласиться на одну цепочку и сделать ложь саморазрушительной.

7 шаг Соглашаемся на одну цепочку

Согласись на одну историю — и назначь цену лжи

Мы не можем назначить судью, поэтому сети приходится сходиться к одной цепочке самостоятельно. Очевидная идея — пусть все голосуют, один узел один голос — умирает мгновенно. Личности бесплатны: я поднимаю миллион фальшивых узлов (атака Сивиллы) и перевешиваю весь честный мир ещё до завтрака.

личности бесплатны 1 участник +1 +1 +1 +1 +1 бесплатно → затопить голосование личность стоит стейка 1 участник stake 32 Ξ +1 армию не потянуть
Голоса, подсчитанные по личности, проваливаются — личности можно подделывать бесплатно. Голос должен стоить чего-то настоящего, что нельзя подделать.

Так что голос должен стоить чего-то неподделываемого. Есть две знаменитые цены. Proof of work взимает электричество: чтобы продлить цепочку, вы сжигаете реальные вычисления, и побеждает цепочка с наибольшей работой за спиной, потому что подделать её — значит пережечь всю честную сеть. Proof of stake — выбор Ethereum — взимает залоговые деньги: валидаторы блокируют депозит, чтобы предлагать блоки и аттестовать их. Это дешевле и зеленее, но настоящий приз — то, что работа предложить не может: поскольку каждый голосующий — известный залоговый депозит, лжеца можно наказать, а не просто пересоревновать.

На этой основе цепочку устаканивают два механизма. Выбор форка (fork choice) удерживает сеть на одной истории от мгновения к мгновению: следуй за форком с наибольшим аттестующим стейком за спиной, чтобы меньшинство никогда не смогло сделать свою ветку самой тяжёлой. Финальность (finality) делает старую историю постоянной: периодически валидаторы голосуют за финализацию контрольной точки (checkpoint), и как только за неё проголосовало две трети всего стейка, обращение её вспять потребовало бы, чтобы треть всего стейка подписала две противоречащие контрольные точки — доказуемое преступление, которое протокол ловит и наказывает слэшингом, уничтожая их депозиты.

время → 30 31 32 33 больше всего голосов 32' ✗ перевес против выигрывает самая поддержанная история; финал. блоки заперты
Цепочка следует за форком с наибольшим стейком; финализированный блок нельзя обратить вспять без того, чтобы валидаторы сожгли треть всего стейка.

Так что сдерживающий фактор — собственный депозит атакующего. Двойную трату останавливает не стражник у двери — её останавливает то, что провернуть её в масштабе означает поджечь состояние, которое вы внесли заранее, в то время как честные валидаторы просто держат самую тяжёлую, финализированную цепочку.

Сыграй за атакующего

Не принимайте это на веру — попробуйте сломать. Вы контролируете часть валидаторов: заплатите продавцу, заберите товар, затем раскройте секретный форк, где платежа никогда не было, и посмотрите, как выбор форка и финальность решат вашу судьбу (и счёт).

Интерактив

Симулятор реорганизации / двойной траты

Задайте, сколько стейка контролирует атакующий, затем раскройте секретный форк. Выбор форка выбирает более тяжёлую цепочку; финальность запирает блоки; вердикт показывает, срабатывает ли двойная трата — и во что бы она обошлась.

honest chain
#1💰 your payment
#2
#3
#4
280 votes · 70 validators × 4 slots
forks here ↓ (just before your payment)

Three layers stop a double-spend: a nonce blocks re-spending from one account; fork choice (LMD-GHOST) makes the heaviest-attested chain canonical so a minority fork loses; and finality + slashing (Casper-FFG) make reversing a finalized block cost a third of all staked ETH. Security is economic — and the numbers are enormous.

Выбор головы LMD-GHOST + модель финальности Casper-FFG, покрытые юнит-тестами. Цифры стейка/стоимости даны в реальном порядке величины.

У двух механизмов, которые мы только что изобрели, есть настоящие имена; вместе их называют Gasper:

И тот же расчёт назначает цену всему семейству, к которому принадлежит двойная трата: 51% (стоимость стейка + слэшинг), дальнобойные (long-range) переписывания древней истории (защищены слабой субъективностью (weak subjectivity) Новый или долго бывший офлайн узел доверяет недавней финализированной контрольной точке как своей отправной точке, а не самой длинной цепочке от генезиса, — нейтрализуя переписывание далёкой истории. ), сама Сивилла (влияние пропорционально застейканному ETH, а не числу личностей), затмение (eclipse) (разнообразный выбор пиров на сетевом уровне) и цензура (смягчается разделением предлагающего и билдера и списками включения). Шаблон никогда не меняется: найди, что атакующий должен потратить, и сделай так, чтобы это стоило больше, чем атака может заработать.

→ Шаг 8: пусть ячейки держат программы.

8 шаг Ячейки, которые вычисляют

Ячейки, которые исполняют программы

Улучшите лист ещё раз: пусть строка держит не просто баланс, а код. Теперь правка может вызвать этот код, который исполняется поверх общего состояния — читая строки, записывая строки, отказываясь, если его правила не выполнены. Поскольку каждый узел исполняет идентичную программу над идентичным состоянием, все они приходят к идентичному результату. Таблица тихо стала мировым компьютером, а маленькие программы, живущие в её ячейках, — это смарт-контракты. (Это code_hash и storage_root, которые мы видели ждущими в листе аккаунта ещё на шаге 5, наконец пущены в дело.)

PUSH 2PUSH 3ADDMUL код выполняется одинаково везде стек 2 3 5 ADD складывает 2+3
Ячейка держит крошечную программу. Виртуальная машина шагает по её инструкциям поверх стека — те же шаги, тот же результат, на каждом узле на Земле.

Это тот скачок, который делает Ethereum Ethereum, а не более быстрым банком. Контракт — это просто аккаунт, чьё поведение зафиксировано в коде, который кто угодно может прочитать и никто не может втайне переопределить, — эскроу, который высвобождается при выполнении условий, токен, чьи правила эмиссии публичны, голосование, которое никакой клерк не может неправильно подсчитать.

И это навязывает транзакции ещё одно поле. Чтобы вызвать контракт, правка должна сказать, какую функцию и с какими аргументами, — эта полезная нагрузка и есть поле data. Простой перевод оставляет его пустым; вызов кода заполняет его закодированным вызовом. (Если у транзакции вообще нет получателя, поле data читается как код совершенно нового контракта — именно так контракты и разворачиваются.)

Прошагай настоящую машину

Вот эта виртуальная машина по-настоящему — стек Список 256-битных слов по принципу «последним вошёл — первым вышел», максимальная глубина 1024. Почти каждый опкод снимает свои входы с верха и кладёт свой результат обратно. для операндов, черновая память, постоянное хранилище и счётчик. Загрузите программу и шагайте по ней по одной инструкции за раз; смотрите, как значения кладутся и снимаются, а счётчик тикает вниз (мы как следует назначим цену этому счётчику на следующем шаге).

Интерактив

Пошаговщик EVM

Шагайте или проиграйте байткод. Подсвеченная инструкция — это счётчик команд; стек показан верхом вверх; газ тикает вниз на каждой операции. Хранилище сохраняется; память — черновик.

1 / 6
Program
  1. 00PUSH10x02
  2. 02PUSH10x03
  3. 04ADD
  4. 05PUSH10x04
  5. 07MUL
gas3 used · −3 this op
Stack (top first)
  • 00x22
Storage

— empty —

Memory

— empty —

A real (faithful-subset) EVM, executed in your browser. Every instruction pops and pushes the 256-bit stack, can touch memory (cheap, transient) or storage(expensive, persistent), and burns gas until it halts or runs out. Edit the bytecode or pick a program and step through it.

Достоверное подмножество EVM (арифметика, стек, память, хранилище, переходы, газ), исполняемое в вашем браузере, — 16 юнит-тестов, покрывающих опкоды и газ.

Каждое слово на этом стеке — 256 бит — размером, чтобы вместить хеш keccak256 или адрес, с арифметикой по модулю 2²⁵⁶ (попробуйте 0 - 1 для максимального uint256). И Solidity, который вы могли бы написать, компилируется прямо вниз до этого: mapping — это адрес хранилища keccak256(key . slot); вызов функции диспетчеризуется по первым 4 байтам calldata ( селектор функции (function selector) Первые 4 байта keccak256 от сигнатуры функции. Диспетчер контракта сравнивает первые 4 байта calldata с ними, чтобы выбрать, какая функция запускается. ); revert откатывает состояние и возвращает оставшийся газ.

→ Шаг 9: поставить счётчик на каждую инструкцию.

9 шаг Замеряем работу

Счётчик, чтобы ничто не работало вечно

Берите плату за вычисление, по одной инструкции за раз. У каждой операции есть цена в газе; каждая транзакция предоплачивает бюджет газа; счётчик тикает вниз по мере исполнения программы, и в тот миг, когда он достигает нуля, исполнение останавливается, а изменения откатываются. Бесконечный цикл больше не замораживает мир — он просто остаётся без топлива и умирает, а атакующий заплатил за каждый шаг. Назначение цены и есть решение проблемы остановки.

каждый шаг стоит газ; бюджет только падает потрачено остаток PUSH -3 ADD -3 SSTORE -20000 газ закончился → revert (все изменения отменены) платим за шаг и ограничиваем — любая программа завершается
Каждая инструкция тратит газ из предоплаченного бюджета. Закончится — и исполнение останавливается, так что никакая программа не может работать вечно.

Этот предоплаченный бюджет — сам по себе поле транзакции, gasLimit, — и он тихо делает вторую работу: поскольку исполнение никогда не может сжечь больше газа, чем лимит, он ограничивает ваш худший случай потерь, когда вы вызываете контракт, чьему поведению вы не полностью доверяете. Цене, которую вы платите, тоже нужны поля. От блока к блоку базовая комиссия колеблется, поэтому вы называете не точную цену, а потолок — maxFeePerGas (максимум, который вы стерпите), — и maxPriorityFeePerGas — чаевые поверх сожжённой базовой комиссии, чтобы дать предлагающему повод вас включить. Сама базовая комиссия — не слепой аукцион; это контроллер, который дрейфует вверх, когда блоки полны, и вниз, когда они пусты, целясь в наполовину заполненные блоки, и она сжигается, а не кладётся в карман.

Порули контроллером

Прежде чем приземлиться в блок, подписанная транзакция ждёт в мемпуле (mempool) — слабо разделяемой, поузловой комнате ожидания, разносимой сплетнями от пира к пиру, — оценённая ровно этим контроллером. Задайте, насколько полон каждый блок, и смотрите, как базовая комиссия гонится за полу-полной целью: вверх не более чем на ⅛ за блок, когда спрос накаляется, вниз, когда остывает.

Интерактив

Симулятор базовой комиссии EIP-1559

Кликните по блоку, чтобы изменить, насколько он полон (или выберите шаблон спроса). Базовая комиссия бежит вперёд блок за блоком — вверх над целью, вниз под ней, ±12,5% максимум. Базовая комиссия сжигается; чаевые идут предлагающему.

demand— or click any block to change how full it is
5127gweitarget · 50%
base fee now44.87 gwei
burned / gas44.87 gwei
tip → proposer / gas2.00 gwei

The base fee isn't an auction — it's a controller. Each block, it moves ±12.5% at most, up when the previous block was over half full and down when under, steering the chain toward 50% full. And it's burned, not paid to anyone. Senders add a priority tip on top to compete for ordering.

Точная формула базовой комиссии EIP-1559, в целочисленной арифметике, — покрыта юнит-тестами против спецификации.

Весь контроллер — это несколько строк целочисленной математики над родительским блоком:

Транзакция затем платит min(maxFeePerGas, baseFee + maxPriorityFeePerGas) за газ; если базовая комиссия когда-либо взбирается выше вашего maxFeePerGas, она просто ждёт, а не переплачивает. И одно следствие, которое стоит назвать: базовая комиссия покупает включение, но чаевые покупают позицию — а позиция имеет ценность (ликвидация, которую можно урвать, арбитраж, который можно опередить). Эта ценность — MEV Maximal Extractable Value — прибыль, которую билдер может извлечь, выбирая, какие транзакции включить и в каком порядке. Это превратило сборку блоков в конкурентный рынок. , и при разделении предлагающего и билдера (proposer-builder separation) специализированные билдеры теперь собирают самый ценный блок, какой могут, и торгуются, чтобы предлагающий его опубликовал.

На этом транзакция завершена — а с ней и машина. Разложите транзакцию, и ни одно поле не является украшением; каждое — ответ на конкретное «что ломается без него?»

ТРАНЗАКЦИЯ что сломается без него chainId replay onto another chain nonce replay on this chain to no recipient (or new contract) value how much ether moves data which code + args to run gasLimit a loop drains your balance maxFeePerGas overpaying a volatile fee maxPriorityFee no tip, proposer skips you sig r,s,yParity anyone could forge the sender
Вся транзакция, поле за полем. Каждая строка вынуждена атакой или отказом, который она предотвращает, — ничего произвольного.
04 Глубже Куда двигаться дальше