Вы могли бы изобрести Ethereum
Одна страница, одно непрерывное построение. Начнём с единственной общей таблицы, покажем в анимации единственный её изъян, исправим ровно его — и повторим, пока программируемый мировой компьютер на стейке не выпадет как единственное, чем эта таблица могла стать.
- любопытство
Большинство объяснений Ethereum вручают вам готовую машину и называют её детали. Это — её строит. Мы начинаем с самого тупого устройства, которое почти работает, смотрим на единственное, что его ломает, и исправляем ровно это — ничего больше. Сделайте так девять раз — и знаменитые кусочки перестают быть набором фактов для зубрёжки. Каждый из них — просто ответ на вопрос «что сломалось на прошлом шаге?»
Вот аналогия, которую мы будем держать в голове весь путь вниз. Забудьте на минуту про монеты и майнеров. Представьте одну таблицу, которую разделяет весь мир — единственный лист «кто-чем-владеет», который может читать кто угодно и в который кто угодно может предложить изменения. К концу эта таблица сможет исполнять программы, ею не будет владеть никто, а врать ей будет стоить целое состояние. Эта таблица — Ethereum.
И дело не только в анимации. В тот момент, когда шаг изобретает кусочек, вы прямо там запустите настоящий — реальный подписчик secp256k1, работающую EVM, побайтово точное дерево Меркла-Патриции, живой контроллер EIP-1559, симулятор реорганизации, который можно атаковать — и подхватите ту деталь спецификации, которую настоящему протоколу пришлось зафиксировать. Та же инкрементальная нить, интуиция прямиком до работающей машины.
Одна таблица на весь мир
Выбросите банк. На его место поставьте единственную публичную таблицу: одна строка на аккаунт, один столбец под его баланс. Чтобы кому-то заплатить, вы отправляете правку — «вычесть 5 из моей строки, прибавить 5 к строке Боба» — и каждый применяет ту же правку к своему виду. Нет ни выписки, которую нужно запрашивать, ни кассира, у которого нужно спрашивать; лист и есть истина, а истина — это просто куча чисел, которые правки двигают туда-сюда.
В этом и весь мысленный образ состояния Ethereum: не история платежей, а текущее значение в каждой строке прямо сейчас. Оно чистое, оно общее — и оно нараспашку.
→ Шаг 2: сделать так, чтобы каждую правку мог написать только её владелец.
Печать, которую можешь поставить только ты
Дайте каждому аккаунту секрет. Не пароль, который вы показываете, чтобы войти, — а закрытый ключ Секретное 256-битное число. Его адрес выводится из него односторонне; только ключ может произвести валидную подпись, но проверить её может кто угодно. , который никогда не покидает ваших рук. Им вы припечатываете печать к каждой правке: подпись. Магия печати — в асимметрии: чтобы её произвести, нужен секрет, а чтобы её проверить, нужен лишь ваш публичный адрес. Любой может подтвердить, что печать настоящая; никто не может её подделать.
Подделайте хоть одну цифру подписанной правки — и печать перестанет совпадать с содержимым. Подпись не приклеена рядом с сообщением; она вычисляется из него. Ваша личность на листе — это не имя, которое кто угодно может напечатать, а просто способность произвести эту печать.
Проверка: принять подлинное, отвергнуть подделанное
Проверка — зеркало подписания. Любой берёт сообщение, подпись и ваш публичный адрес и проверяет, что они согласованы, — и середины тут нет. Нетронутое сообщение проходит; то же сообщение с единственным перевёрнутым байтом — не проходит, каждый раз. Этот бинарный исход и есть вся суть: печать превращает «доверься мне» в «проверь сам».
От ключа к адресу: односторонний хеш
Откуда берётся этот «публичный адрес»? Вы прогоняете свой открытый ключ через хеш — функцию-отпечаток в одну сторону, первый из фундаментальных инструментов, на которые мы будем опираться снова и снова. Подайте ей любой вход — она вернёт фиксированные 32 байта; измените один символ — почти каждый байт вывода перевернётся; и обратить её невозможно. Ваш адрес — это просто последние 20 байт хеша вашего открытого ключа. Познакомьтесь с машиной здесь — вы увидите ровно ту же лавину, когда мы будем снимать отпечаток со всего состояния и связывать блоки вместе.
keccak256 — складывающая машина
Настоящая печать: secp256k1
Теперь весь конвейер разом, по-настоящему. Печать — это ECDSA-подпись над кривой secp256k1, ровно та схема, которую использует Ethereum. Сгенерируйте ключ и проследите private key → public key → address (последняя стрелка — это хеш выше), затем подпишите сообщение. Вы заметите, что подписанта можно восстановить прямо из подписи — отложите это; в этом весь фокус шага 3.
Лаборатория ключей и подписей
- 1 случайный 256-битный секрет — единственное, что вы храните
- 2 secp256k1 умножает его на точку кривой (в одну сторону)
- 3 последние 20 байт keccak256(публичного ключа)
Одно уточнение, потому что оно следует прямо из «сообщение публично» и почти все понимают его наоборот: подписание — это не шифрование. Та же пара ключей могла бы шифровать, но с поменянными ролями — а Ethereum L1 вообще не прячет ваши правки.
| Подписание (то, что мы только что построили) | Шифрование (то, чем оно не является) | |
|---|---|---|
| Цель | подлинность + целостность | конфиденциальность |
| Закрытый ключ | подписывает | расшифровывает |
| Открытый ключ | проверяет / восстанавливает | шифрует |
| Сообщение | публично | скрыто |
Ваша правка — открытый текст на листе навсегда; печать доказывает кто, и что не изменено — но никогда не скрывает.
И ещё одну тонкую дыру настоящему протоколу пришлось заткнуть. Сырая подпись — это просто подпись над байтами, так что dapp мог бы подсунуть вам безобидно выглядящее «сообщение», которое втайне является валидной транзакцией, — и ваша печать его авторизует. Лекарство — никогда не печатать сырые байты, а байты внутри помеченного контекста:
→ Шаг 3: прочитать автора прямо из печати.
Подпись уже говорит, кто вы
Вот фокус, который позволяет Ethereum полностью отказаться от поля «from». Из подписи и сообщения, которое она покрывает, можно прогнать математику в обратную сторону и восстановить ровно тот адрес, который должен был её произвести. Автор — не заявление, прикреплённое к правке, а нечто, что вы вычисляете из самой печати. Соврите о том, чью строку трогать, — и восстановленный адрес окажется чужаком, у которого нет денег для движения.
Так что у Ethereum-транзакции и вправду нет поля «from». Сеть берёт вашу подписанную правку, восстанавливает адрес — и это единственная строка, которую ей разрешено списать. Авторство и полномочие схлопываются в одну неподделываемую печать — но одно авторство ещё не безопасность.
Подпись переиспользуема — отсюда nonce
Вот изъян, который печать не чинит: она никогда не истекает, а сообщение, которое она покрывает, публично. В тот момент, когда вы вручаете Бобу подписанное «вычесть 5 у меня, прибавить 5 Бобу», Боб держит совершенно валидную правку, которую он может транслировать снова — и снова. Каждая копия проходит проверку; каждая копия двигает ещё 5. Один платёж становится медленной утечкой, которую вы никогда не санкционировали.
Лекарство — один счётчик, вложенный в подписанное сообщение: nonce, «это моя 7-я транзакция». Сеть помнит, сколько каждый аккаунт потратил, и применяет только следующий по очереди номер. Ваша правка с nonce-7 проходит ровно один раз; каждая воспроизведённая копия теперь называет 7, который аккаунт уже миновал, поэтому отскакивает. Упорядочивание получается бесплатно — правки аккаунта могут вступать в силу только в порядке nonce, так что никто не может их и переставить.
Воспроизводима и между мирами — отсюда chainId
Остаётся одна более тонкая копия. То же ПО работает в тестнетах и других EVM-цепочках, которые держат те же самые адреса. Подписанный перевод с nonce-7 на тестовой сети прошёл бы проверку точно так же, будучи воспроизведённым на настоящей. Поэтому подписанное сообщение также фиксирует chainId — делая правку валидной ровно на одной цепочке и инертной на каждой другой.
Голый перевод наконец-то непробиваем, и взгляните, почему там есть каждое поле: получатель и сумма, чтобы сказать, что делать, nonce, чтобы её нельзя было воспроизвести здесь, chainId, чтобы её нельзя было воспроизвести в другом месте, и подпись, которая и есть отправитель. Вытащите любое одно — и конкретная дверь распахивается.
Построить настоящую транзакцию
Этот список — отправитель-через-восстановление, nonce, chainId, получатель, сумма — и есть Ethereum-транзакция. Постройте и подпишите её по-настоящему: отредактируйте поля, подпишите и посмотрите, как отправитель восстанавливается из подписи, не появляясь в байтах нигде. Подделайте поле после подписания — и восстановление схлопнется в чужака.
Подписчик транзакций
Восстановление — приём, который позволяет нам отказаться от поля «from», — это просто математика подписания, прогнанная в обратную сторону:
А байты разложены как типизированный конверт — байт типа, затем RLP-полезная нагрузка, — чтобы протокол мог добавлять новые формы транзакций без неоднозначности:
Этот один байт превратил «формат транзакции» в версионированное семейство — то же ядро (подписана, упорядочена по nonce, замерена по газу), новые поля прикручиваются по мере появления нужд:
0x00Legacy — изначальный, до-типизированный формат.0x01EIP-2930 — добавляет список доступа (предобъявленное хранилище, ради газа).0x02EIP-1559 — сегодняшний дефолт: базовая комиссия + приоритетные чаевые (поля газа, до которых мы дойдём на шаге 9).0x03EIP-4844 — транзакции, несущие blob, сердце доступности данных L2.0x04EIP-7702 — позволяет EOA временно действовать как аккаунт-смарт-контракт.
→ Шаг 4: пусть никто не держит мастер-копию.
Никакой мастер-копии — лист крутит каждый
Итак, таблицу не хостит никто. Её хостят все. Каждый участник держит полный лист и правила его обновления и независимо перепроверяет каждую подписанную правку перед применением. Нет привилегированного сервера, который можно вызвать в суд, подкупить или выключить. Вы не доверяете сети — вы держите собственную копию и проверяете её с самой первой строки.
Это и есть настоящий скачок от «базы данных» к «блокчейну»: данные перестают жить где-то и начинают жить везде, а правила вшиты в каждую копию.
→ Шаг 5: свернуть весь мир в одно число.
Один отпечаток для всего мира
Грубое лекарство использует хеш из шага 2: прогнать весь лист через складывающую машину и сравнить единственное число, которое из неё выпадает. Оно наполовину работает и ломается двумя способами: изменение одного баланса заставляет перехешировать весь лист, а чтобы доказать кому-то свой баланс, вам пришлось бы вручить ему весь лист, чтобы он мог его перехешировать.
Исправьте оба изъяна с помощью дерева хешей. Хешируйте аккаунты группами, затем хешируйте эти хеши, уровень за уровнем, до единственного корня. Теперь правка одного аккаунта перехеширует лишь короткий путь от его листа до корня — а не весь лист, — и вы можете доказать один аккаунт тому, кто держит только корень, послав лишь хеши-братья вдоль этого пути. Это дерево Меркла.
Но состояние — не аккуратный массив фиксированного размера; это разреженная, постоянно меняющаяся карта с ключами из 20-байтовых адресов. Простое дерево не кодирует, где живёт аккаунт. Так сделайте ключ самим путём: прочитайте адрес как строку шестнадцатеричных цифр — полубайтов (nibbles) — и пройдите по ним вниз по дереву, чтобы найти аккаунт.
Эффективный проход по этому пути принуждает ровно три вида узлов, и каждый заслуживает своё место. Узел-ветка (branch) — это 16-стороннее разветвление (по слоту на каждый возможный следующий полубайт), используемое везде, где пути расходятся. Лист (leaf) держит финальный отрезок пути и значение аккаунта. А узел-расширение (extension) сжимает длинный пробег полубайтов, который всё, что ниже, оказывается общим, — чтобы дерево не растрачивало целую 16-стороннюю ветку на отрезок, где ничего не ветвится. Хешируйте каждый узел из его детей — keccak от его кодировки — до единственного корня, и у вас есть дерево Меркла-Патриции Ethereum: часть «Патриция» делает обновления по ключу дешёвыми, часть «Меркл» делает всё это одним проверяемым отпечатком.
Разберём настоящее дерево на части
Это самое что ни на есть подлинное — те же RLP, теги узлов с hex-префиксом и keccak256, что использует живой клиент; stateRoot, который он печатает, побайтово совпадает с тем, что вычислили бы go-ethereum или reth. Вставьте аккаунт и проследите, как путь перестраивается и пульсирует к новому корню; кликните по любому узлу, чтобы прочитать его кодировку.
Визуализатор дерева Меркла-Патриции
Два правила кодирования заставляют каждого клиента согласиться до байта. RLP сериализует узел перед хешированием; hex-префиксное кодирование упаковывает путь из полубайтов узла плюс флаг (лист против расширения, нечётный против чётного) в байты. И одно правило подставляет подножку каждому самописному дереву:
То, что лежит в листе, — не просто баланс, а четыре поля аккаунта, и два из них оживают лишь тогда, когда аккаунты могут держать код (шаг 8):
Этот storage_root — выдаёт всё: как только появляются контракты, каждый из них держит ещё одно MPT для своего хранилища, чей корень живёт здесь, в листе аккаунта, — так что одна запись в хранилище прокатывает новый корень хранилища в лист, который прокатывает новый корень состояния, та же волна, вложенная на уровень глубже. (Одна деталь из реального мира: ключи на самом деле — keccak256(address), а не сырой адрес — «защищённое дерево» (secure trie), — что рассеивает аккаунты так, что префиксы редко сталкиваются.)
Почему форма всё ещё меняется
У шестнадцатеричного дерева есть счёт к оплате: чтобы доказать один аккаунт тому, кто держит только корень, — то, что нужно клиенту без состояния (stateless client) Узел, который проверяет блоки, используя свидетельства (доказательства), поставляемые вместе с блоком, вместо хранения полного состояния у себя. , — вы должны раскрыть на каждом уровне хеши-братья, в которые вы не спускались. 16-сторонняя ветка означает до 15 братьев на уровень; умножьте на глубину — и свидетельства раздуваются до мегабайтов. Это и есть препятствие для бессостоятельности, и именно поэтому обязательство (commitment) перепроектируется.
Шестнадцатеричное MPT → Verkle → Двоичное
Годами лекарством были деревья Verkle (векторные обязательства: одно короткое доказательство на все 256 детей, братья перестают иметь значение). На протяжении 2024–2025 годов дорожная карта развернулась к двоичному дереву Меркла (EIP-7864): арность 2, обычное хеширование, без доверенной настройки и дружелюбное к доказывателям SNARK (SNARK provers) Системы, которые производят сжатые доказательства того, что вычисление было выполнено корректно. Простые деревья на основе хешей доказывать внутри SNARK гораздо дешевле, чем схемы векторных обязательств. , которые в итоге будут проверять переходы состояния Ethereum.
→ Шаг 6: заморозить историю в цепочку.
Сцепи пачки, чтобы прошлое не могло сдвинуться
Перестаньте обращаться с правками как с разрозненными бумажками. Соберите их в пронумерованный блок и сведите его к небольшому заголовку (header): его номер, parentHash (хеш блока перед ним), txsRoot, снимающий отпечаток с правок внутри, и stateRoot, который мы только что построили, — отпечаток всего листа после этого блока. Собственный hash блока — это хеш всего этого заголовка. Поскольку складывающая машина из шага 2 превращает любое однобитовое изменение в совершенно другой вывод, заголовок привязывает блок ко всей истории до него и к точному состоянию, которое он произвёл.
Теперь посмотрите, что делает один подделанный баланс. Измените Alice 14 → 9 глубоко в состоянии блока #1 — и эффект не остаётся локальным. Лист её аккаунта меняется, что перехешируется вверх по дереву в новый stateRoot; stateRoot сидит в заголовке блока #1, так что заголовок меняется; hash заголовка меняется; и теперь parentHash блока #2 указывает на хеш, которого больше не существует, — так что блок #2 невалиден, что делает невалидным блок #3, и так далее по всей линии. Одно отредактированное число — и вся цепочка после него разваливается. Чтобы переписать один прошлый баланс, вам пришлось бы втихую перестроить каждый блок с тех пор. Прошлое заморожено не стражником, а самим весом всего, что навалено сверху. Эта связанная, упакованная, обязующая состояние история — блокчейн.
→ Шаг 7: согласиться на одну цепочку и сделать ложь саморазрушительной.
Согласись на одну историю — и назначь цену лжи
Мы не можем назначить судью, поэтому сети приходится сходиться к одной цепочке самостоятельно. Очевидная идея — пусть все голосуют, один узел один голос — умирает мгновенно. Личности бесплатны: я поднимаю миллион фальшивых узлов (атака Сивиллы) и перевешиваю весь честный мир ещё до завтрака.
Так что голос должен стоить чего-то неподделываемого. Есть две знаменитые цены. Proof of work взимает электричество: чтобы продлить цепочку, вы сжигаете реальные вычисления, и побеждает цепочка с наибольшей работой за спиной, потому что подделать её — значит пережечь всю честную сеть. Proof of stake — выбор Ethereum — взимает залоговые деньги: валидаторы блокируют депозит, чтобы предлагать блоки и аттестовать их. Это дешевле и зеленее, но настоящий приз — то, что работа предложить не может: поскольку каждый голосующий — известный залоговый депозит, лжеца можно наказать, а не просто пересоревновать.
На этой основе цепочку устаканивают два механизма. Выбор форка (fork choice) удерживает сеть на одной истории от мгновения к мгновению: следуй за форком с наибольшим аттестующим стейком за спиной, чтобы меньшинство никогда не смогло сделать свою ветку самой тяжёлой. Финальность (finality) делает старую историю постоянной: периодически валидаторы голосуют за финализацию контрольной точки (checkpoint), и как только за неё проголосовало две трети всего стейка, обращение её вспять потребовало бы, чтобы треть всего стейка подписала две противоречащие контрольные точки — доказуемое преступление, которое протокол ловит и наказывает слэшингом, уничтожая их депозиты.
Так что сдерживающий фактор — собственный депозит атакующего. Двойную трату останавливает не стражник у двери — её останавливает то, что провернуть её в масштабе означает поджечь состояние, которое вы внесли заранее, в то время как честные валидаторы просто держат самую тяжёлую, финализированную цепочку.
Сыграй за атакующего
Не принимайте это на веру — попробуйте сломать. Вы контролируете часть валидаторов: заплатите продавцу, заберите товар, затем раскройте секретный форк, где платежа никогда не было, и посмотрите, как выбор форка и финальность решат вашу судьбу (и счёт).
Симулятор реорганизации / двойной траты
У двух механизмов, которые мы только что изобрели, есть настоящие имена; вместе их называют Gasper:
И тот же расчёт назначает цену всему семейству, к которому принадлежит двойная трата: 51% (стоимость стейка + слэшинг), дальнобойные (long-range) переписывания древней истории (защищены слабой субъективностью (weak subjectivity) Новый или долго бывший офлайн узел доверяет недавней финализированной контрольной точке как своей отправной точке, а не самой длинной цепочке от генезиса, — нейтрализуя переписывание далёкой истории. ), сама Сивилла (влияние пропорционально застейканному ETH, а не числу личностей), затмение (eclipse) (разнообразный выбор пиров на сетевом уровне) и цензура (смягчается разделением предлагающего и билдера и списками включения). Шаблон никогда не меняется: найди, что атакующий должен потратить, и сделай так, чтобы это стоило больше, чем атака может заработать.
→ Шаг 8: пусть ячейки держат программы.
Ячейки, которые исполняют программы
Улучшите лист ещё раз: пусть строка держит не просто баланс, а код. Теперь правка может вызвать этот код, который исполняется поверх общего состояния — читая строки, записывая строки, отказываясь, если его правила не выполнены. Поскольку каждый узел исполняет идентичную программу над идентичным состоянием, все они приходят к идентичному результату. Таблица тихо стала мировым компьютером, а маленькие программы, живущие в её ячейках, — это смарт-контракты. (Это code_hash и storage_root, которые мы видели ждущими в листе аккаунта ещё на шаге 5, наконец пущены в дело.)
Это тот скачок, который делает Ethereum Ethereum, а не более быстрым банком. Контракт — это просто аккаунт, чьё поведение зафиксировано в коде, который кто угодно может прочитать и никто не может втайне переопределить, — эскроу, который высвобождается при выполнении условий, токен, чьи правила эмиссии публичны, голосование, которое никакой клерк не может неправильно подсчитать.
И это навязывает транзакции ещё одно поле. Чтобы вызвать контракт, правка должна сказать, какую функцию и с какими аргументами, — эта полезная нагрузка и есть поле data. Простой перевод оставляет его пустым; вызов кода заполняет его закодированным вызовом. (Если у транзакции вообще нет получателя, поле data читается как код совершенно нового контракта — именно так контракты и разворачиваются.)
Прошагай настоящую машину
Вот эта виртуальная машина по-настоящему — стек Список 256-битных слов по принципу «последним вошёл — первым вышел», максимальная глубина 1024. Почти каждый опкод снимает свои входы с верха и кладёт свой результат обратно. для операндов, черновая память, постоянное хранилище и счётчик. Загрузите программу и шагайте по ней по одной инструкции за раз; смотрите, как значения кладутся и снимаются, а счётчик тикает вниз (мы как следует назначим цену этому счётчику на следующем шаге).
Пошаговщик EVM
Каждое слово на этом стеке — 256 бит — размером, чтобы вместить хеш keccak256 или адрес, с арифметикой по модулю 2²⁵⁶ (попробуйте 0 - 1 для максимального uint256). И Solidity, который вы могли бы написать, компилируется прямо вниз до этого: mapping — это адрес хранилища keccak256(key . slot); вызов функции диспетчеризуется по первым 4 байтам calldata ( селектор функции (function selector) Первые 4 байта keccak256 от сигнатуры функции. Диспетчер контракта сравнивает первые 4 байта calldata с ними, чтобы выбрать, какая функция запускается. ); revert откатывает состояние и возвращает оставшийся газ.
→ Шаг 9: поставить счётчик на каждую инструкцию.
Счётчик, чтобы ничто не работало вечно
Берите плату за вычисление, по одной инструкции за раз. У каждой операции есть цена в газе; каждая транзакция предоплачивает бюджет газа; счётчик тикает вниз по мере исполнения программы, и в тот миг, когда он достигает нуля, исполнение останавливается, а изменения откатываются. Бесконечный цикл больше не замораживает мир — он просто остаётся без топлива и умирает, а атакующий заплатил за каждый шаг. Назначение цены и есть решение проблемы остановки.
Этот предоплаченный бюджет — сам по себе поле транзакции, gasLimit, — и он тихо делает вторую работу: поскольку исполнение никогда не может сжечь больше газа, чем лимит, он ограничивает ваш худший случай потерь, когда вы вызываете контракт, чьему поведению вы не полностью доверяете. Цене, которую вы платите, тоже нужны поля. От блока к блоку базовая комиссия колеблется, поэтому вы называете не точную цену, а потолок — maxFeePerGas (максимум, который вы стерпите), — и maxPriorityFeePerGas — чаевые поверх сожжённой базовой комиссии, чтобы дать предлагающему повод вас включить. Сама базовая комиссия — не слепой аукцион; это контроллер, который дрейфует вверх, когда блоки полны, и вниз, когда они пусты, целясь в наполовину заполненные блоки, и она сжигается, а не кладётся в карман.
Порули контроллером
Прежде чем приземлиться в блок, подписанная транзакция ждёт в мемпуле (mempool) — слабо разделяемой, поузловой комнате ожидания, разносимой сплетнями от пира к пиру, — оценённая ровно этим контроллером. Задайте, насколько полон каждый блок, и смотрите, как базовая комиссия гонится за полу-полной целью: вверх не более чем на ⅛ за блок, когда спрос накаляется, вниз, когда остывает.
Симулятор базовой комиссии EIP-1559
Весь контроллер — это несколько строк целочисленной математики над родительским блоком:
Транзакция затем платит min(maxFeePerGas, baseFee + maxPriorityFeePerGas) за газ; если базовая комиссия когда-либо взбирается выше вашего maxFeePerGas, она просто ждёт, а не переплачивает. И одно следствие, которое стоит назвать: базовая комиссия покупает включение, но чаевые покупают позицию — а позиция имеет ценность (ликвидация, которую можно урвать, арбитраж, который можно опередить). Эта ценность — MEV Maximal Extractable Value — прибыль, которую билдер может извлечь, выбирая, какие транзакции включить и в каком порядке. Это превратило сборку блоков в конкурентный рынок. , и при разделении предлагающего и билдера (proposer-builder separation) специализированные билдеры теперь собирают самый ценный блок, какой могут, и торгуются, чтобы предлагающий его опубликовал.
На этом транзакция завершена — а с ней и машина. Разложите транзакцию, и ни одно поле не является украшением; каждое — ответ на конкретное «что ломается без него?»