Больше денег: что такое Ethereum и как блокчейн меняет мир - Виталий Дмитриевич Бутерин
Стоит подчеркнуть, что «контракты» в Ethereum не следует рассматривать как нечто, что должно быть «выполнено» или «соблюдено»; скорее они больше похожи на «автономных агентов», которые живут внутри среды выполнения Ethereum, всегда выполняя определенный фрагмент кода, когда к этому «подталкивает» сообщение или транзакция. Также у них есть прямой контроль над собственным балансом и собственным хранилищем ключей/ценностей для отслеживания постоянных переменных.
СООБЩЕНИЯ И ТРАНЗАКЦИИ
Термин «транзакция» в Ethereum означает подписанный пакет данных, содержащий сообщение, которое должно быть отправлено в подконтрольный владельцу аккаунт. Транзакции включают в себя:
◊ получателя;
◊ электронную подпись отправителя;
◊ количество эфира, пересылаемого отправителем получателю.
◊ пересылаемые данные (опционально);
◊ переменную STARTGAS, означающую предел количества вычислительных шагов, возможных для осуществления транзакции;
◊ переменную GASPRICE, означающую комиссию, которую платит отправитель за каждый вычислительный шаг.
Первые три – это стандартные поля, которые есть в любой криптовалюте. Поле данных по умолчанию не имеет функции, но у виртуальной машины есть операционный код, с помощью которого контракт может получить доступ к данным. Например, возможен кейс, где, если контракт функционирует как сервис регистрации доменов внутри блокчейна, он может захотеть интерпретировать передаваемые ему данные как содержащие два «поля»; первое поле – домен для регистрации, а второе поле – IP-адрес для его регистрации. Контракт считал бы это из данных сообщения и соответственно поместил бы в хранилище.
Поля STARTGAS и GASPRICE очень важны для модели Ethereum по предотвращению атаки отказа в обслуживании. Чтобы предотвратить случайные или враждебные бесконечные циклы или другие вычислительные перегрузки в коде, каждая транзакция должна устанавливать ограничение на количество вычислительных шагов выполнения кода. Основной единицей вычисления является «газ»; обычно вычислительный шаг стоит 1 газ, но некоторые операции требуют большего количества газа, поскольку они дороже с точки зрения вычислений или увеличивают объем данных, которые должны храниться как часть состояния. Также взимается комиссия в 5 газа за каждый байт данных транзакции. Цель системы комиссий состоит в том, чтобы потребовать от злоумышленника пропорциональной оплаты за каждый потребляемый им ресурс, включая вычисления, пропускную способность и хранилище; следовательно, любая транзакция, которая приводит к тому, что сеть потребляет большее количество любого из этих ресурсов, должна иметь комиссию, примерно пропорциональную приращению.
СООБЩЕНИЯ
Контракты имеют возможность отправлять «сообщения» другим контрактам. Сообщения – это виртуальные объекты, которые никогда не сериализуются и существуют только в среде выполнения Ethereum. Сообщение содержит:
◊ отправителя сообщения (скрытого);
◊ получателя сообщения;
◊ количество эфира для передачи вместе с сообщением;
◊ пересылаемые данные (опционально).
ПЕРЕМЕННАЯ STARTGAS
По своей сути сообщение похоже на транзакцию, за исключением того, что оно создается контрактом, а не внешним актором. Когда контракт, выполняющий текущий код, выполняет опкод CALL, он создает и выполняет сообщение. Как и транзакция, сообщение ведет к аккаунту получателя, выполняющего его код. Таким образом, контракты могут иметь отношения с другими контрактами точно так же, как это могут делать внешние акторы.
Обратите внимание, что норма расхода газа, назначенная транзакцией или контрактом, применяется к общему объему газа, потребляемого этой транзакцией и всеми дополнительными исполнениями. Например, если внешний актор A отправляет транзакцию B с 1000 газа, B потребляет 600 газа перед отправкой сообщения C, а внутреннее выполнение C потребляет 300 газа перед возвратом, то B может потратить еще 100 газа, прежде чем он закончится.
ФУНКЦИЯ ИЗМЕНЕНИЯ СОСТОЯНИЯ ETHEREUM
Функция изменения состояния Ethereum, APPLY(S,TX) – > S’ работает следующим образом.
1. Проверить корректность оформления транзакции (убедиться, что все нужные значения указаны), валидность цифровой подписи и соответствие одноразового кода транзакции одноразовому коду аккаунта отправителя. Выдать ошибку, если что-то не так.
2. Вычислить комиссию за транзакцию по формуле STARTGAS × GASPRICE и определить адрес отправителя по цифровой подписи. Вычесть величину комиссии из баланса отправителя и прирастить одноразовый код отправителя. Если баланс отправителя меньше значения взимаемой комиссии, выдать ошибку.
3. Присвоить GAS = STARTGAS и взимать определенное количество газа за каждый обработанный майнером байт транзакции.
4. Переслать сумму транзакции с аккаунта отправителя на аккаунт получателя. Если аккаунта получателя не существует, создать его. Если аккаунт получателя – контракт, запустить код контракта, где контракт будет вычислен либо до конца, либо пока не закончится газ.
5. Если перевод не удался по причине того, что у отправителя не было суммы перевода или при вычислении контракта закончился газ, откатить все изменения, кроме выплаты комиссий за вычисления, и добавить комиссии к аккаунту майнера.
6. В противном случае вернуть отправителю комиссию за весь оставшийся газ и отправить майнеру комиссию, уплаченную за потраченный газ.
Для примера рассмотрим следующий код контракта:
if!self.storage[calldataload(0)]:
self.storage[calldataload(0)] = calldataload(32)
Заметим, что в реальности контракт пишется на низкоуровневом EVM-коде, но этот пример для ясности написан на Serpent, одном из наших высокоуровневых языков, который можно скомпилировать до EVM-кода. Предположим, что до транзакции хранилище нашего контракт-аккаунта было пустым, а затем посылается транзакция с 10 единицами эфира, 2000 газа, 0,001 GASPRICE и 64 байтами данных, где байты 0–31 представляют число 2, а байты 32–63 – строчку CHARLIE[114]. Здесь функция изменения состояния сработает так.
1. Убедиться, что транзакция валидна и корректно оформлена.
2. Убедиться, что у отправителя есть как минимум 2000 × 0,001 = 2 единицы эфира. После успешной проверки вычесть 2 эфира из баланса аккаунта отправителя.
3. При 2000 газа, если предположить, что транзакция занимает 170 байт и комиссия за байт равна 5, вычтем 850, и останется 1150 единиц газа.
4. Вычесть еще 10 единиц эфира из баланса аккаунта отправителя и добавить их к балансу получателя.
5. Запустить выполнение кода. В нашем примере это совсем просто: код проверяет, есть ли в хранилище контракт-аккаунта информация по адресу 2, замечает, что ее там нет, и прописывает туда значение CHARLIE. Предположим, это стоит 187 единиц газа, и газа остается 1150 – 187 = 963.
6. Добавить 963 × 0,001 = 0,963 единиц эфира в аккаунт отправителя и выдать получившееся состояние.
Если на принимающей стороне транзакции нет контракта, комиссия будет равняться GASPRICE, умноженной на длину транзакции в байтах, и никаких данных вместе с транзакцией отправлено не будет.
Отметим, что с точки зрения возврата сообщения работают эквивалентно транзакциям: если при выполнении сообщения заканчивается газ, то происходит откат к моменту вызова этого сообщения в контракте, но не к моменту начала всего контракта.