ethereum

(Продолжение. Ссылки на предыдущие части статьи: 1 и 2.)

Выполнение транзакций

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

im1

Для того чтобы быть выполненными, все транзакции должны отвечать определённому набору требований:

  • Транзакция должна быть правильно отформатированным префиксом рекурсивной длины (Recursive Length Prefix, RLP). RLP – это формат данных, используемый для кодирования вложенных массивов двоичных данных. В Эфириуме RLP используется для сериализации объектов.
  • Подпись транзакции должна быть действительной.
  • Параметр nonce транзакции должен быть действительным. Напомню, что nonce – это количество транзакций, отправленных с данного счёта. Значение nonce транзакции должно быть равно nonce счёта отправителя.
  • Указанный в транзакции лимит газа, должен быть больше либо равен количеству газа, использованному ею. При этом учитывается:
  1. предопределённая стоимость 21 000 газа для выполнения транзакции
  2. плата за газ для данных, отправленных с транзакцией (4 единицы газа за каждый байт данных или кода, или 0,68 единицы газа за каждый ненулевой байт данных или кода)
  3. ещё 32 000 единицы газа дополнительно, если транзакция создаёт новый контракт

im2

  • На балансе счёта отправителя должно быть достаточно эфиров, чтобы внести предоплату за газ. Предоплата за газ рассчитывается просто: Лимит газа умножается на указанную в транзакции цену газа и таким образом определяется максимальная стоимость газа. Затем максимальная стоимость газа прибавляется к общей сумме транзакции, передаваемой от отправителя получателю.

im4

Если транзакция соответствует всем вышеперечисленным требованиям, то мы можем переходить к следующему шагу.

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

im5

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

  • Self-destruct set: список счетов, которые будут автоматически ликвидированы после завершения транзакции (если таковые имеются).
  • Log series: архивированные и проиндексированные контрольные точки выполнения кода виртуальной машины.
  • Refund balance: сумма ETH, которая должна быть возвращена на счёт отправителя после завершения транзакции. Мы уже упоминали о том, что хранение данных в сети Эфириума стоит денег и что отправитель транзакции получает возмещение за сокращение количества хранимых данных. Эфириум отслеживает это с помощью специального счётчика выплаты возмещений. Перед началом выполнения транзакции значение счётчика выплаты возмещений равно нулю и увеличивается каждый раз, когда контракт удаляет какие-либо из хранимых в сети данных.

Затем производятся различные вычислительные операции, требуемые для выполнения транзакции.

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

После выплаты возмещения отправителю производятся следующие действия:

  • эфиры, уплаченные отправителем за газ, выплачиваются майнеру;
  • количество газа, использованного при выполнении данной транзакции, добавляется к общему количеству газа, потраченному на выполнение всех транзакций, включённых в блок, которое учитывается при валидации блока;
  • удаляются все счета из списка автоматической ликвидации (если есть).

Наконец, мы получаем новое состояние сети и логи, сгенерированные при выполнении транзакции.

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

Создание контракта

Напомню, что в Эфириуме есть два типа счетов: счета контрактов и счета внешних владельцев. Когда мы говорим о «транзакции создания контракта», то подразумеваем, что целью транзакции является создание нового счёта контракта.

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

  • устанавливается нулевое значение параметра nonce;
  • если отправитель включил в транзакцию некоторую сумму эфиров в качестве передаваемой ценности, то она зачисляется на баланс создаваемого счёта;
  • сумма, отправленная на адрес создаваемого счёта, вычитается с баланса счёта отправителя;
  • параметру занимаемого объёма хранилища присваивается нулевое значение;
  • значение параметра codeHash контракта устанавливается как хэш пустой строки.

После инициализации создания счёта мы можем его создать, используя «код инициализации», init code, отправленный с транзакцией (подробно описано в разделе «Транзакции и сообщения»). Происходящее во время выполнения этого кода инициализации может варьироваться. В зависимости от конструктора контракта, он может обновлять хранилище, ассоциируемое со счётом, создавать другие счета контрактов, отправлять другие сообщения и т.д.

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

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

В случае успешного выполнения кода инициализации, выплачивается конечная стоимость создания контракта. Это плата за хранение данных в сети, и её размер пропорционален размеру создаваемого кода контракта (опять же, ничто не происходит бесплатно). Если остатка газа недостаточно для того, чтобы оплатить эту конечную стоимость, то, опять же, выполнение транзакции прерывается.

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

Сообщения

Процесс выполнения сообщений схож с выполнением транзакций создания контрактов, но имеет несколько отличий.

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

Как и в случае с транзакциями создания контрактов, если выполнение сообщения прерывается по причине недостаточного количества газа или некорректных данных (например, переполнения стека, неверного адреса перехода или некорректной команды), стоимость использованного газа отправителю транзакции не возвращается. Напротив, весь неиспользованный газ также списывается, а состояние сети возвращается к состоянию, в котором она находилась непосредственно перед выполнением транзакции.

До самого последнего обновления Эфириума не существовало возможности остановить или отменить выполнение транзакции иначе как дождаться, пока система не израсходует весь предусмотренный для транзакции газ. Предположим, что вы создали контракт, который выдаёт ошибку, если у счёта отправителя нет прав на выполнение той или иной транзакции. В предыдущих версиях Эфириума остаток газа полностью потреблялся сетью и никакая его часть отправителю не возвращалась. Но обновление Byzantium включает новый код «отмены», позволяющий остановить выполнение контракта и отменить изменения состояния сети без расходования всего остатка газа и с возможностью уведомления о причине неудавшейся транзакции. Если выполнение транзакции отменяется пользователем, то неиспользованный газ возвращается отправителю.

Модель выполнения транзакций

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

Часть протокола, которая выполняет обработку транзакций, называется виртуальной машиной Эфириума (EVM).

Как мы уже упоминали ранее, EVM представляет собой полную по Тьюрингу виртуальную машину. Единственное отличие EVM от обычной Тьюринг-полной машины заключается в том, что в EVM используется газ. Таким образом, общая сумма вычислений, которые могут быть ею произведены, ограничивается количеством предоставленного газа.

im6

Кроме того, EVM имеет стековую архитектуру. Стековая машина – это компьютер, использующий для хранения временных значений стек магазинного типа.

Размер каждого элемента стека в EVM составляет 256 бит, а максимальный размер стека – 1024 бит.

EVM имеет память, в которой элементы хранятся в виде байтовых массивов с обращением к словам. Память EVM не является долговременной.

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

im8

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

Ну что ж, перейдём непосредственно к выполнению транзакций.

Перед началом выполнения вычислительной операции процессор убеждается в доступности и действительности следующей информации:

  • состояние системы;
  • остаток газа для вычислительной операции;
  • адрес счёта, которому принадлежит выполняемый код;
  • адрес отправителя транзакции, инициировавшей выполнение данного действия;
  • адрес счёта, инициировавшего выполнение кода (может отличаться от исходного отправителя транзакции);
  • цена газа, указанная в транзакции, инициировавшей выполнение данного действия;
  • входные данные для выполнения этого действия;
  • количество средств, которое должно быть отправлено на данный счёт как часть выполнения данного действия (в Wei);
  • машинный код, который должен быть выполнен;
  • заголовок текущего блока;
  • глубина стека данного сообщения или транзакции создания контракта;

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

im9

Затем EVM выполняет транзакцию рекурсивно, рассчитывая состояние системы и состояние машины для каждого цикла. Состояние системы – это просто глобальное состояние сети Эфириума. Состояние машины включает в себя:

  • доступный газ;
  • счётчик программы;
  • содержимое памяти;
  • активное количество слов в памяти;
  • содержимое стека.

Элементы стека добавляются или удаляются в левой крайней части ряда.

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

В конце каждого цикла есть три возможности:

  1. машина достигает исключительного состояния (например, недостаточное количество газа, недействительные инструкции, недостаточное количество элементов стека, объём элементов стека в результате выполнения действия превысит 1024 бит, неверный адрес JUMP/JUMPI перехода и т.д.) и должна быть остановлена, а все изменения отменены;
  2. последовательность продолжает обрабатываться в следующем цикле;
  3. машина достигает момента управляемой остановки, завершив выполнение процесса.

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

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

Как блоки финализируются

Наконец, давайте посмотрим, как финализируются блоки из множества транзакций.

Под словом «финализируются» мы можем подразумевать два разных процесса в зависимости от того, является ли блок вновь созданным или уже существующим. Если речь идёт о новом блоке, то имеется в виду процесс, необходимый для майнинга этого блока. Говоря же о существующем блоке, мы имеем в виду процесс валидации блока. В обоих случаях, для того, чтобы «финализировать» блок, необходимо выполнить четыре требования:

1) Валидировать (или, если речь идёт о майнинге, определить) оммеры
Каждый оммер в заголовке блока должен быть допустимым заголовком и относиться к не более чем шестому поколению от текущего блока.

2) Валидировать (или, если речь идёт о майнинге, определить) транзакции
Значение gasUsed для блока должно быть равно общему количеству газа, совокупно использованному транзакциями, включёнными в данный блок. (Напомню, что при выполнении транзакций увеличивается значение счётчика газа блока, который отслеживает общее количество газа, использованное всеми включёнными в блок транзакциями).

3) Выплатить вознаграждение (только в случае майнинга)
На адрес счёта бенефициара перечисляется 5 ETH за майнинг блока. (Согласно принятому предложению EIP-649, эта сумма в скором времени будет уменьшена до 3 ETH). Кроме того, выгодоприобретатель создания блока получает дополнительное вознаграждение за каждого оммера данного блока в размере 1/32 от вознаграждения за текущий блок. Наконец, выгодоприобретатели формирования блоков-оммеров тоже получают определённое вознаграждение, рассчитываемое по специальной формуле.

4) Верифицировать (или, в случае майнинга, правильно рассчитать) состояние и значение nonce
Необходимо убедиться, что все транзакции и результирующие состояния сети применены, а затем определить новый блок как состояние после выплаты награды за майнинг, применённое к финальному состоянию сети после выполнения всех включённых в блок транзакций. Процесс верификации происходит путём сравнения этого финального состояния с состоянием trie-дерева в заголовке блока.