Как работает Ethereum?
Небольшое погружение во внутренности Ethereum!
оригинал статьи вы можете найти в блоге Preethi Kasireddy.
Введение
Скорее всего, вы слышали о блокчейне Ethereum. А знаете ли вы, что это такое? В последнее время эта тема постоянно в новостях, и часто мелькает на обложках крупных журналов. Если вы не знаете что именно такое Ethereum, то содержание этих статей может выглядеть для вас как какая-то тарабарщина.
Так что же такое Ethereum?
По сути, это публичная база данных, которая ведет постоянную запись цифровых транзакций. Важно отметить, что эта база данных не требует каких-либо централизованных полномочий для ее обслуживания и защиты. Вместо этого она работает как «бездоверительная» транзакционная система — структура, в которой физические лица могут совершать одноранговые транзакции без необходимости доверять третьей стороне или друг другу.
Ну вот все еще запутанней!
Вот здесь и нужен мой пост!
Моя цель — объяснить, как Ethereum функционирует на техническом уровне, без “страшных” и сложных математических формул. Я надеюсь, что даже если вы не программист, прочитав эту статью, вы, по крайней мере поймете саму технологию. Если некоторые части будут слишком трудны технически, это абсолютно нормально! Нет необходимости понимать каждую мелочь. Я рекомендую просто сосредоточиться на понимании технологии в широком уровне.
Многие из тем, затронутых в этой статье, представляют собой разбивку концепций, обсуждаемых в Ethereum Yellow Paper. Я добавил свои собственные объяснения и диаграммы, чтобы упростить понимание технологии Ethereum. Те, кто достаточно храбр, чтобы взять на себя технический вызовы, могут также прочитать Ethereum Yellow Paper.
Давайте начнем!
Определение Blockchain.
Blockchain — это «криптографически безопасная транзакционная однопользовательская машина с общим состоянием». Да пожалуй это даже трудно произнести вслух, не так ли? Давайте взломаем это понятие.
«Криптографически безопасный» означает, что создание цифровой валюты обеспечивается сложными математическими алгоритмами, которые невозможно взломать. Эти алгоритмы делают почти невозможным обмануть систему (например, создавать поддельные транзакции, стирать транзакции и т.д.)
«Транзакционная однопользовательская машина» означает, что существует один канонический экземпляр этой машины, ответственный за все транзакции, создаваемые в системе. Другими словами, есть одна глобальная истина, в которую каждый верит.
«С общим состоянием» означает, что состояние, хранящееся на этой машине, является общим и открытым для всех.
Ethereum реализует парадигму блока.
Объяснение парадигмы алгоритма Ethereum.
Blockchain Ethereum по существу является transaction-based state machine. В информатике, состояние машины ссылается на что-то, что будет читать ряд inputs и, на основании этих inputs, будет сделан переход к новому состоянию.
В Ethereum state machine мы начинаем с «состояния генезиса». Это будет аналогично чистому листу или пустому бланку, до того пока какие-либо транзакции не произойдут в сети. Когда транзакции выполняются, “состояние генезиса” переходит в какое-то новое конечное состояние. В любой момент времени это конечное состояние представляет текущее состояние Ethereum.
Состояние Ethereum имеет миллионы транзакций. Эти транзакции сгруппированы в «блоки». Блок содержит ряд транзакций, и каждый блок соединен в цепочке со своим предыдущим блоком.
Чтобы вызвать переход из одного состояния в другое, транзакция должна быть действительной. Чтобы транзакция считалась действительной, она должна пройти процесс валидации, известный как «mining». Майнинг — это когда группа нодов (т.е компьютеров) которая расходует свои вычислительные ресурсы для создания блока с валидными транзакциями.
Любая нода в сети, объявляющий себя miner, может попытаться создать и проверить блок. Многие miner со всего мира пытаются одновременно создавать и проверять блоки. Каждый miner предоставляет математическое «доказательство» при отправке блока в блокчейн, и это доказательство действует как гарантия: если доказательство существует, блок является валидным.
Чтобы блок был добавлен в основную ветку blockchain, miner должен доказать это быстрее, чем любой другой конкурент. Процесс проверки каждого блока с помощью miner дает математическое доказательство, которое называется “proof of work”.
Miner, который валидирует новый блок, получает определенную сумму за выполнение этой работы. Что это за ценность? Blockchain Ethereum использует встроенный цифровой токен c названием «Ether». Каждый раз, когда miner доказывает блок, для него генерируются новые токены Ether.
Вы можете задаться вопросом: что же гарантирует, что каждый будет придерживается одной цепочки блоков? Как мы можем быть уверены, что не существует подмножества miners, которые решат создать свою собственную цепочку блоков?
Ранее мы определили blockchain как транзакционную однопользовательскую машину с общим состоянием. Используя это определение, мы можем понять, что правильное текущее состояние — это единственная глобальная истина, которую каждый должен принять. Наличие нескольких состояний (или цепей) разрушило бы всю систему, потому что было бы невозможно договориться о том, какое состояние было правильным. Если бы цепочки могли расходится, вы могли бы иметь 10 монет на одной цепочке, 20 на другой и 40 на третей. В этом случае не было бы способа определить, какая цепочка будет наиболее «действительной».
Всякий раз, когда генерируются несколько путей, возникает развилка « fork». Как правило мы хотим избежать развилок, потому что они нарушают систему и заставляют людей выбирать, в какую цепочку они «верят».
Чтобы определить, какой путь наиболее валиден и предотвратить множественные цепи, Ethereum использует механизм, называемый «GHOST protocol».
“GHOST” = “Greedy Heaviest Observed Subtree”
Объясняя простыми словами, протокол GHOST говорит, что мы должны выбрать тот путь, на котором было выполнено большинство вычислений. Один из способов определить этот путь — использовать номер самого последнего блока («leaf block»), который показывает общее количество блоков в текущем пути (не считая блока генезиса). Чем выше номер блока, тем длиннее путь и тем больше усилий по майнингу, которые должны были попасть в leaf block. Используя это рассуждение, мы можем договориться о канонической версии текущего состояния.
Теперь, когда вы получили начальный обзор о том, что такое blockchain, давайте погрузимся глубже в основные компоненты которые входят систему Ethereum:
- accounts
- state
- gas and fees
- transactions
- blocks
- transaction execution
- mining
- proof of wor
Одно примечание перед началом обсуждения: всякий раз, когда я говорю «хэш» X, я имею в виду KECCAK-256 hash, который использует Ethereum.
Аккаунты
Глобальное «общее состояние» Ethereum состоит из множества небольших объектов аккаунтов (учетных записей), которые могут взаимодействовать друг с другом через инфраструктуру передачи сообщений. У каждого аккаунта есть связанное с ним состояние и 20-байтовый адрес. Адрес в Ethereum — это 160-битный идентификатор, который используется для идентификации любого аккаунта.
Существует два типа аккаунтов :
- Externally owned accounts ( внешний аккаунт)- контролируются закрытыми ключами и не имеют кода, связанного с ними.
- Contract accounts( Аккаунт контракта ) — контролируются кодом контракта и имеют код ассоциированный с ним.
Внешние Аккаунт и Аккаунт Контракта.
Важно понимать фундаментальное различие между внешними аккаунтами и контракт-аккаунтами. Внешний аккаунт может отправлять сообщения другим внешним аккаунтам или другим аккаунтам контракта, создавая и подписывая транзакцию с использованием своего закрытого ключа. Сообщение между двумя внешними аккаунтами — это просто передача ценности(стоимости). Но сообщение из внешнего аккаунта на аккаунт контракта активирует его код, позволяя ему выполнять различные действия (например, переносить токены, записывать во внутреннее хранилище, эмитировать новые токены, выполнять некоторые вычисления, создавать новые смарт-контракты и т.д.).
В отличие от внешних аккаунтов, аккаунты контрактов не могут инициировать новые транзакции самостоятельно. Вместо этого контракт аккаунты могут только запускать транзакции в ответ на другие транзакции, которые они получили (из внешних аккаунтов или с другого контракт аккаунта). Мы узнаем больше о вызовах между контрактами в разделе «Транзакции и сообщения».
Таким образом, любое действие, которое происходит на blockchain Ethereum, всегда приводится в действие транзакциями, запущенными из внешние контролируемых аккаунтов
Состояние аккаунта.
Состояние аккаунта состоит из четырех компонентов, которые присутствуют независимо от типа аккаунта:
- nonce: Если аккаунт является внешним аккаунтом, это число представляет собой количество транзакций, отправленных с адреса аккаунта. Если аккаунт является аккаунтом контракта, то nonce — это количество смарт-контрактов, созданных аккаунтом контракта.
- balance: количество Wei, принадлежащих этому адресу. В Ether содержится — ¹⁰¹⁰ Wei.
- storageRoot: хэш корневой ноды дерева Merkle Patricia (позже мы объясним что такое деревья Merkle). Это дерево хэширует содержимое хранилища этого аккаунта (по умолчанию пуст).
- codeHash: хэш EVM (Ethereum Virtual Machine — подробнее об этом позже) код этого аккаунта. Для аккаунтов контракта это код, который хэшируется и сохраняется как codeHash. Для внешних аккаунтов поле codeHash является хешем пустой строки.
Глобальное состояние
Итак мы знаем, что глобальное состояние Ethereum состоит из сопоставления(mapping) между адресами аккаунтов и состояниями аккаунтов. Это сопоставление хранится в структуре данных, известной как дерево Merkle Patricia.
Дерево Merkle (или также называемое «Merkle trie») — это тип двоичного дерева, состоящего из набора узлов с:
- большое количество leaf узлов в нижней части дерева, которые содержат базовые пакеты данных
- наборов промежуточных узлов, где каждый узел является хешем из его двух дочерних узлов
- один корневой узел, также сформированный из хэша двух его дочерних узлов, представляющий вершину дерева
Данные в нижней части дерева генерируются путем разделения информации которую мы хотим сохранить на пакеты, пакеты помещаем в блок-пакеты, берутся хэши каждого блок-пакета и затем повторяем тот же процесс, пока общее количество оставшихся хешей не оставит только один: корневой хеш.
Это дерево требует ключа для каждого значения, хранящегося внутри него. Начиная с корневого узла дерева, ключ должен указать вам, какой дочерний узел должен следовать, чтобы получить соответствующее значение, которое хранится в нижних листовых узлах. В случае с Ethereum ключ/значение отображает дерево состояний между адресами и связанными с ними аккаунтами, включая balance, nonce, codeHash и storageRoot для каждой учетной записи (где storageRoot сам является деревом).
Эта же древовидная структура используется для хранения транзакций и квитанций(поступлений). Более конкретно, каждый блок имеет «заголовок», который хранит хэш корневого узла трех различных структур Merkle trie, включая:
1) State trie Дерево Состояний
2) Transactions trie Дерево транзакции
3) Receipts trie Дерево квитанции(поступлений)
Возможность эффективно хранить всю эту информацию в Merkle tries невероятно полезна в Ethereum для того, что мы называем «легкими клиентами» или «легкими нодами (light nodes)». Помните, что blockchain поддерживается сетью нодов. Вообще говоря, существуют ноды двух типов: полные(full nodes) и легкие(light nodes).
Нода с полным архивом синхронизирует blockchain, загружая всю цепочку, от блока генезиса до текущего блока, выполняя все транзакции, содержащиеся внутри. Как правило, майнеры хранят полную ноду, потому что это требуется для процесса майнинга. Также можно загрузить полную ноду без выполнения каждой транзакции. Независимо, любая полная нода содержит всю цепочку.
Но если нода не нуждается в выполнении каждой транзакции, ей не нужны исторические данные, то хранить всю цепочку блоков нет необходимости. Именно здесь приходит понятие легкого клиента. Вместо того, чтобы загружать и хранить всю цепочку и выполнять все транзакции, легкие клиенты загружают только цепочку заголовков блоков, от блока генезиса до текущего, без каких-либо транзакций или любых связанных с ними состояний. Поскольку легкие клиенты имеют доступ к заголовкам блоков, которые содержат хэши трех различных структур Merkle tries, они также легко могут генерировать и получать проверенные правдивые ответы о транзакциях, событиях, балансах и т. д.
Причина этого заключается в том, что хеши дерева Merkle распространяются вверх — если злоумышленник пытается подменить или сделать поддельную транзакцию на дне дерева Merkle, это изменение приведет к изменению хэша узла выше, что изменит хэш узла выше этого и т. д., пока он в конечном итоге не изменит корневой хэш дерева.
Любая нода, которая хочет проверить часть данных, для этого может использовать так называемое «доказательством Merkle». Доказательство Merkle состоит из:
1. Пакет данных для проверки
2. Корневой хэш дерева
3. « branch (Ветвь)» (все хеши, идущие по пути от пакета к корню)
Любой, кто может прочитать доказательством Merkle, может верифицировать, что хеши для этой ветви соответствуют на всем протяжении дерева, и, следовательно, данный пакет фактически находится в этом дереве.
Таким образом, преимущества использования дерева Merkle Patricia заключается в том, что корневой узел этой структуры криптографически зависит от данных, хранящихся в дереве, и поэтому корневой хэш может использоваться как безопасный идентификатор для этих данных. Поскольку заголовок блока включает в себя корневой хэш Дерева состояния, Дерева транзакций и Дерева квитанции(поступлений), даже легкий клиент может проверять состояния Ethereum без необходимости хранить всю историю состояний, которое может быть потенциально неограниченным по размеру.
GAS(газ) и платеж
Одна очень важная концепция в Ethereum — концепция комиссий. Каждое вычисление, которое происходит в результате транзакции в сети Ethereum, берет плату — здесь нет бесплатных обедов! Эта плата выплачивается в номинале « gas(газ)».
Gas — это единица, используемая для измерения сборов, необходимых для конкретного расчета. Gas прайс — это количество Ether, которое вы желаете потратите на каждую единицу gas , измеряется в «gwei». «Wei» — это наименьшая единица эфира, где ¹⁰¹⁸ Wei = 1 Ether, а один gwei = 1 000 000 000 Wei.
С каждой транзакцией отправитель устанавливает лимит gas и прайс gas. Произведение лимит gas на прайс gas представляет собой максимальную сумму Wei, которую отправитель готов заплатить за выполнение транзакции.
Например, давайте предположим, что отправитель устанавливает Лимит gas до 50 000 и прайс gas до 20 gwei. Это означает, что отправитель желает потратить не более 50 000 х 20 gwei = 1 000 000 000 000 000 Вей = 0,001 эфира для выполнения этой транзакции.
Помните, что лимит gas представляет собой максимальный gas, на который отправитель хочет потратить деньги. Если у него достаточно Ether на балансе своего аккаунта, чтобы покрыть этот максимум, можно действовать. По окончанию транзакции отправителю возвращается неиспользованный gas и обменивается по его оригинальному курсу.
В случае, если отправитель не предоставил необходимое количество gas для совершения транзакции, транзакция запускает «out of gas» и считается недействительной. В этом случае обработка транзакций прерывается и любые изменения состояния, которые произошли, меняются назад, Ethereum возвращается в состояние до транзакции. Кроме того, делается запись об ошибке транзакции, показывающая, какая транзакция была предпринята и где она упала. И поскольку машина уже затратила усилия на выполнение расчетов до окончания gas, логически, gas не возвращается отправителю.
Куда же именно идут деньги за gas? Все деньги, потраченные на gas отправителем, отправляются на адрес «бенифициара», который обычно является адресом майнера. Поскольку майнеры расходуют усилия на выполнение расчетов и проверку транзакций, они получают gas fee в качестве вознаграждения.
Как правило, чем выше прайс gas, который отправитель готов заплатить за транзакцию, тем выше экономическая мотивация для майнера и соответственно большая вероятность что для обработки майнерами будет выбрана именно это транзакция. Майнеры могут выбирать, какие транзакции они хотят валидировать, а какие игнорировать. Чтобы ориентировать отправителей транзакции, какой прайс gas устанавливать, у майнеров есть возможность извещать минимальный прайс gas, за который они готовы совершать транзакции.
Комиссия за хранение тоже существует.
Gas используется не только для оплаты вычислений, он также используется для оплаты за использование хранилища Ethereum. Общая комиссия за хранилище пропорциональна наименьшему числу 32 байта.
Сборы за хранение имеют некоторые нюансы. Например, поскольку увеличение хранилища увеличивает размер базы данных состояния Ethereum на всех нодах, нужно стимулировать хранения небольшого объема данных. По этой причине, если транзакция имеет шаг, который очищает запись в хранилище, плата за выполнение этой операции отменяется и происходит возврат за освобожденное место.
Какова же цель сборов?
Одним из важных аспектов работы Ethereum является то, что каждая операция, выполняемая сетью, одновременно выполняется всеми полным нодами. Однако вычисления на виртуальной машине Ethereum очень дороги. Таким образом, смарт-контракты в Ethereum лучше всего использовать для простых задач, например таких как запуск простой бизнес-логики, проверка подписей и других криптографических объектов, а не более сложные видов использования, такие как хранение файлов, электронная почта или машинное обучение, всего того что может вызвать нагрузку на сеть. Установление сборов делает экономически невыгодным пользователям перенапрягать сеть.
Ethereum — использует тьюринг-полный язык программирования (машина Тьюринга — это абстрактная вычислительная машина, которая может имитировать любой компьютерный алгоритм (для тех, кто не знаком с машинами Тьюринга.). Такой подход позволяет использовать циклы и делает Ethereum восприимчивым к проблеме остановки , мы не можем определить, будет ли программа работать бесконечно. Если не будет никаких сборов, злоумышленник может попытаться разрушить сеть, создав бесконечный цикл внутри транзакции без каких-либо последствий. Таким образом, сборы дополнительно и защищают сеть от преднамеренных атак.
Возможно, вы думаете: «Почему мы должны платить за хранение?» Точно так же, как и вычисления, хранение в сети Ethereum — это стоимость, которую вся сеть должна взять на себя.
Транзакции и сообщения
Ранее мы отмечали, что Ethereum является Транзакционная однопользовательская машина. Другими словами, транзакции, происходящие между разными аккаунтами, — это то, что меняет глобальное состояние Ethereum из одного в другое.
В базовом смысле, транзакция представляет собой криптографически подписанную часть инструкции, которая генерируется внешним аккаунтом, сериализуется и затем передается в blockchain.
Существует два типа транзакций: message calls и contract creations (т.е. транзакции, которые создают новые контракты Ethereum).
Независимо от типа все транзакции содержат следующие компоненты:
nonce: количество транзакций, отосланных этим отправителем.
gasPrice: количество Wei, которое отправитель готов заплатить за единицу gas, необходимого для совершения сделки.
gasLimit: максимальный объем gas, который отправитель готов заплатить за выполнение этой транзакции. Эта сумма устанавливается и оплачивается заранее, прежде чем какие-либо вычисления будут выполнены.
to: адрес получателя. В транзакции которая создает контракт, адреса аккаунт-контракта еще не существует, и поэтому используется пустое значение.
value: количество Wei, которое должно быть передано от отправителя получателю. В транзакции которая создает контракт, эта величина служит стартовым балансом на вновь созданном контракт-аккаунте.
v, r, s: используются для создания подписи, которая идентифицирует отправителя транзакции.
init (существует только для транзакций, создающих контракт): фрагмент кода EVM, который используется для инициализации нового аккаунт-контракта. init запускается только один раз, а затем отбрасывается. Когда init запускается впервые, он возвращает тело кода контракт-аккаунта, который является частью кода перманентно связанного с контракт-аккаунтом.
data (необязательное поле, которое существует только для вызовов сообщений): входные данные (то есть параметры) вызова сообщения. Например, если смарт-контракт служит как сервис регистрации домена, вызов этого контракта может ожидать принятие полей домен и IP-адрес.
В разделе « Аккаунты» мы узнали, что обе транзакции (message calls) и (contract creations) — всегда инициируются внешними аккаунтами и передаются blockchain. Это еще один способ подумать о том, что транзакции — это то, что соединяет внешний мир с внутренним состоянием Ethereum.
Но это не означает, что контракты не могут разговаривать с другими контрактами. Контракты, которые существуют в глобальном состоянии Ethereum, могут разговаривать с друг с другом в том же масштабе, через «сообщения» или «внутренние транзакции» между контрактами. Мы можем полагать что сообщения или внутренние транзакции как то схожи с обычными транзакциями, их большое отличите в том что они НЕ генерируются внешними аккаунтами, они создаются контрактами, это виртуальные объекты, которые, в отличие от транзакций, не сериализуются и существуют только в среде Ethereum.
Когда один контракт отправляет внутреннюю транзакцию другому контракту, соответственно код на аккаунте контракта получателя, выполняется.
Важно отметить, что внутренние транзакции или сообщения не содержат gasLimit. Это связано с тем, что gasLimit определяется внешним создателем исходной транзакции (т.е. Каким-либо внешним аккаунтом). gasLimit, который имеет внешний аккаунт должны быть достаточно высокими для осуществления транзакции, включая любые подзадачи, которые происходят в результате этой транзакции, такие как сообщения от контракту к контракту. Если в цепочке транзакций и сообщений на каком-то конкретном сообщении заканчивается gas, то выполнение этого сообщения будет возвращено вместе со всеми последующими сообщениями, вызванными родительской транзакцией.
Блоки
Все транзакции группируются в «блоки». Blockchain это ряд таких блоков, которые соединены вместе.
В Ethereum блок состоит из:
- заголовок блока
- информация о всех транзакциях, включенных в этот блок
- набор других заголовков блоков для текущих блоков ommers.
Что такое Ommers?
Что за штука такая этот «ommer?»
Ommer — это блок, чей родитель равен родительскому элементу текущего блока. Давайте быстро перейдем к тому, что используют ommers, и почему блок содержит заголовки блоков для ommers.
Из-за того что время построения блока в Ethereum, намного ниже (~ 15 секунд), чем например в биткойн (~ 10 минут). Это позволяет ускорить обработку транзакций. Однако одним из недостатков более короткого времени построения блока является то, что появляются конкурирующие блоки найдены майнерами. Эти блоки называют «orphaned blocks (осиротевшие блоки)» (т.е. такие замайненные блоки не попадают в основную цепочку).
Цель ommers — помочь вознаградить майнеров за включение этих осиротевших блоков. Ommers, которые включают майнеры, должны быть «валидными», что означает что этот блок должен быть скенерирован не раньше шестого потомка текущего блока.
После шести дочерних на устаревшие сиротские блоки больше нельзя ссылаться (так как включение старых транзакций может намного усложнить ситуацию)
За Ommer блоки майнеры получают меньшую награду, чем за полный блок. Тем не менее, все еще есть экономический стимул для майнеров, включать эти осиротевшие блоки и получать награду.
Заголовок блока
Давайте на мгновение вернемся к блокам. Мы упоминали ранее, что каждый блок имеет заголовок блока, но что это такое?
Заголовок блока представляет собой часть блока, состоящую из:
- parentHash: хэш заголовка родительского блока (это то, что соединяет блоки в «цепочку»)
- ommersHash: хэш списка текущих блоков ommers
- beneficiary: адрес аккаунта, который получает плату за майнинг этого блока
- stateRoot: хэш корневого узла состояния trie (мы же знаем, что состояние trie хранится в заголовке и облегчает для легких клиентов верификацию состоянии)
- transactionRoot: хэш корневого узла trie, который содержит все транзакции, находящиеся в этом блоке
- receiptsRoot: хэш корневого узла trie, который содержит квитанции (поступления) всех транзакций, проведенных в этом блоке
- logsBloom: фильтр Bloom (структура данных), который состоит из лога информации
- difficulty: уровень сложности этого блока
- number: счетчик текущего блока (блок генезиса имеет номер ноль, номер блока увеличивается на 1 для каждого каждого последующего блока)
- gasLimit: текущий gas лимит на блок
- gasUsed: общая сумма gas, использованного транзакциями в этом блоке
- timestamp: временная отметка создания этого блока
- extraData: дополнительные данные, относящиеся к этому блоку
- mixHash: хэш, который в сочетании с nonce доказывает, что этот блок выполнил достаточные вычисления
- nonce: хэш, который в сочетании с mixHash доказывает, что этот блок выполнил достаточное вычисление
Обратите внимание, что каждый заголовок блока содержит три структуры Merkle trie для:
Состояние — state (stateRoot)
Транзакции — transactions (transactionsRoot)
Квитанции (Поступления) — receipts (receiptsRoot)
Эти три структуры — это не что иное, как Merkle Patricia tries, которую мы обсуждали ранее.
Кроме того, есть несколько терминов из приведенного выше описания, которые заслуживают уточнения. Давайте взглянем на них:
Logs
Ethereum позволяет вести логи, чтобы отслеживать различные транзакции и сообщения. Контракт может однозначно генерировать лог, определяя «события», которые он хочет залогировать.
Запись лога содержит:
- регистратор адрес аккаунта
- серию тем, которые представляют различные события, выполненные этой транзакцией, и
- любые данные, связанные с этими событиями.
Логи хранятся в bloom filter, который эффективно хранит бесконечные данные лога.
Квитанции транзакции
Логи, хранящиеся в заголовке, поступают из информации логов, содержащейся в квитанции о транзакции. Так же, как вы получаете квитанцию, когда вы покупаете что-то в магазине, Ethereum генерирует квитанцию для каждой транзакции. Как и следовало ожидать, каждая квитанция содержит определенную информацию о транзакции. В эту квитанцию входят такие пункты, как:
- номер блока
- хеш блока
- хеш транзакции
- gas, используемый в текущей транзакции
- совокупный gas, используемый в текущем блоке после выполнения текущей транзакции
- логи, созданные при выполнении текущей транзакции
- ..и так далее
Сложность блока
«Сложность» блока используется для обеспечения согласованности времени, необходимого для проверки блоков. Блок генеза имеет сложность 131 072, и специальная формула используется для вычисления сложности каждого блока. Если определенный блок проверяется быстрее, чем предыдущий блок, протокол Ethereum увеличивает сложность этого блока.
Сложность блока влияет на nonce, который является хешем, он должен быть вычислен при майнинге блока, используя алгоритм proof-of-work.
Связь между сложностью блока и nonce математически формализована как:
где Hd — сложность
Единственный способ найти nonce, который будет соответствовать порогу сложности — использовать алгоритм proof-of-work, перебором всех возможных вариантов. Ожидаемое время для поиска решение- пропорционально сложности, чем выше сложность, тем труднее найти nonce, и тем более сложная валидация блока. Что, в свою очередь, увеличивает время, необходимое для валидации нового блока. Таким образом, регулируя сложность блока, протокол может настроить время, необходимое для его валидации.
С другой стороны, если время проверки тратится слишком много, протокол уменьшает сложность. Таким образом, время проверки самонастраивается для поддержания постоянной скорости — в среднем, по одному блоку каждые 15 секунд.
Выполнение транзакций
Мы пришли к одной из самых сложных частей протокола Ethereum: выполнение транзакции. Скажите, что произойдет когда вы отправляете транзакцию в сеть Ethereum для обработки, чтобы перевести состояние Ethereum на вашу транзакцию?
Во-первых, все транзакции должны соответствовать первоначальному набору требований для их выполнения. К ним относятся:
- Транзакция должна быть правильно отформатированной и отвечать требованиям RLP. «RLP» означает «рекурсивный префикс длины» и представляет собой формат данных, используемый для кодирования вложенных массивов двоичных данных. RLP — это формат, который Ethereum использует для сериализации объектов.
- Валидная подпись транзакции
- Валидное значение nonce в транзакции. Напомним, что nonce аккаунта — это количество транзакций, отправленных с этого акккаунта. Чтобы транзакции быть валидной, значение nonce должна быть равна nonce аккаунта с которого идет транзакция.
- Лимит gas транзакции должен быть равен или больше, используемого gas транзакцией. Используемый gas включает в себя:
- предопределенная стоимость 21 000 gas для выполнения транзакции
- gas — комиссия за данные, отправленные с транзакцией (4 gas за каждый байт данных или код, который равен нулю, и 68 gas за каждый ненулевой байт данных или код)
- если транзакция является транзакцией по созданию контракта, дополнительно 32 000 gas
- На балансе аккаунта отправителя должно быть достаточно Ether для оплаты «авансовых» расходов на gas. Расчет для первоначального количества gas прост: во-первых, лимит gas транзакции умножается на gas прайс для определения максимально нужного количества gas. Затем это количество gas добавляется к общей сумме перевода Ether от отправителя к получателю.
Если транзакция валидна т.е соответствует всем вышеперечисленным требованиям, мы переходим к следующему шагу.
Первое, мы вычитаем авансовую стоимость из баланса аккаунта отправителя и увеличиваем показатель nonce аккаунта отправителя + 1 для учета текущей транзакции. После чего мы можем рассчитать оставшийся gas от общего лимит gas для транзакции и вычитаем заданное количество gas.
Затем начинается выполнение транзакции. На протяжении всего пути транзакции Ethereum отслеживают «substate» — это способ записи информации, собранной во время транзакции, которая будет необходима сразу после завершения транзакции. В частности, она содержит:
- Self-destruct set: список аккаунтов (если есть), которые будут удалены после завершения транзакции.
- Log series: архивированные и индексируемые контрольные точки для исполнения кода виртуальной машины Ethereum (EVM).
- Refund balance: сумму, подлежащую возврату на аккаунт отправителя после транзакции. Помните, мы говорили, что хранение в Ethereum стоит денег, и что отправитель получает возврат после очистки хранилища? Ethereum отслеживает это с помощью счетчика возврата. Счетчик возврата начинается с нуля и увеличивается каждый раз, когда контракт удаляет что-то из хранилища.
Затем уже обрабатываются различные вычисления, требуемые транзакцией.
После того, как все шаги, необходимые для транзакции, будут обработаны, и нет недопустимого состояния, состояние завершается определением количества неиспользованного gas, подлежащего возврату отправителю. В дополнение к неиспользованному газу отправитель также возмещает часть gas из «баланса возврата», который мы описали выше.
После завершения транзакции и возврата отправителю происходит следующее:
- Ether за gas отправляется майнеру
- gas, используемый в транзакции, добавляется к счетчику gas (который отслеживает общий gas, используемый всеми транзакциями в блоке, и полезен при проверке блока)
- все акккаунты в списке Self-destruct set: (если таковые имеются) удаляются
И в финале мы имеем новое состояние и набор логов, созданных транзакцией.
Теперь, когда мы разобрались как выполняется транзакции, давайте рассмотрим некоторые различия между транзакциями, создающими контракты, и вызовами сообщений.
Создание контракта
Напомним, что в Ethereum существует два типа учетных записей: аккаунты контрактов и внешние аккаунты. Когда мы говорим, что транзакция «создает контракт», мы подразумеваем, что целью транзакции является создание нового аккаунта контракта.
В запросе на создание нового аккаунта контракта, мы декларируем адрес нового аккаунта, используя специальную формулу, а затем мы инициализируем создание этот аккаунта:
- Устанавливается значение ноль в поле nonce
- Если отправитель отправил некоторое количество Ether в качестве value (значения) с транзакцией, то это значение value ставится в баланс аккаунта
- Вычитается значение value добавленного на новый аккаунт с баланса отправителя
- параметр хранилища отмечаются как пустое
- Устанавливается codeHash контракта, как хэш пустой строки
После инициализации аккаунта мы по сути уже создали аккаунт, используя код инициализации, отправленный с транзакцией (см. Раздел «Транзакция и сообщения»). То, что происходит во время исполнения кода инициализации, варьируется. В зависимости от структуры контракта он может обновлять хранилище аккаунта, создавать другие адреса аккаунта, или делать другие вызовы и т.д.
Когда код инициализации контракта выполняется, он использует gas. Транзакции не разрешено использовать больше gas, чем его осталось и если запустить транзакцию с нехваткой gas, то транзакция остановиться и выйдет сообщение out-of-gas (OOG). Если транзакция остановилась из-за нехватки gas, то состояние возвращается к точке непосредственно перед транзакцией. Gas который был израсходован незаконченной транзакцией отправителю не возвращается.
Однако, если отправитель послал какое-либо количество Ether этой транзакцией, эта сумма Ether будет возвращена, даже если создание контракта завершится с ошибкой.
Если код инициализации контракта выполняется успешно, оплачивается окончательная стоимость создания контракта. Так же в данную сумму входит и стоимость хранения контракта которая пропорциональна размеру кода созданного контракта. Если запаса gas недостаточно, чтобы оплатить эту окончательную стоимость, транзакция остановиться и снова объявит “out-of-gas” (OOG)
Если же все хорошо, и мы выполнили все условия, то неиспользованный газ данной транзакции возвращается отправителю и измененное состояние Ethereum будет сохранено!
Yes!
Сообщения
Передача сообщения транзакция аналогичная исполнению контракта лишь с несколькими отличиями.
Транзакция передачи сообщения не содержит кода инициализации, поскольку новые аккаунты не создаются, однако такая транзакция может содержать какие либо входные данные и эти данные могут быть отправлены с транзакцией. Такие данные могут быть нужны получателю и в транзакции сообщения есть дополнительный компонент для их передачи.
Так же как и при транзакции создании контракта, если передача сообщения завершается из-за нехватки gas или из-за того, что транзакция недействительна (например, переполнение стека, некорректный адрес или недействительная инструкция), использованный для такой попытки gas не возвращается первоначальному абоненту и состояние сбрасывается в первоначальное положение.
До недавнего времени Ethereum не было возможности остановить или отменить выполнение транзакции без потери gas при возврате .
Например, можно представить ситуацию, когда вы являетесь инициатором создания контракта, при создании которого произошла ошибка, поскольку аккаунт не имел права выполнять какую-либо из транзакций. Так вот, в предыдущей версии Ethereum, отправитель не получил бы обратно свой gas.
Но обновление Byzantium уже включает в себя новый «обратный (revert)» код, который позволяет контракту прекратить выполнение и вернуть измененное состояния, не потребляя оставшийся gas, с возможностью вернуть неудачную транзакцию. Если транзакция завершается запросом на возврат, то неиспользованный gas возвращается отправителю.
Модель исполнения
Из рассказа выше мы знаем что происходит во время выполнения транзакции от начала до конца. Теперь мы рассмотрим, как транзакция фактически выполняется внутри виртуальной машины.
Часть протокола, которая фактически обрабатывает транзакции, является виртуальной машиной Ethereum, известной как Ethereum Virtual Machine (EVM).
EVM — полная виртуальная машина по Turing, как было определено ранее. Единственное ограничение, которое имеет EVM, заключается в том, что EVM неразрывно связана c gas. Таким образом, общая сумма вычислений, которая может быть выполнена, по существу ограничена количеством предоставленного gas.
Более того, EVM имеет stack-based архитектуру. Stack machine — это компьютер, в котором используется last-in, first-out стек для хранения временных значений. Размер каждого элемента стека в EVM составляет 256 бит, а стек имеет максимальный размер 1024.
EVM имеет память которая не является постоянной, где элементы хранятся в виде массивов байтов с обращением к словам.
EVM также имеет хранилище. В отличие от памяти, хранилище постоянно и поддерживается как часть состояния системы. EVM хранит программный код отдельно, в виртуальном ROM(ПЗУ), доступ к которому можно получить только с помощью специальных инструкций. Таким образом, EVM отличается от типичной архитектуры von Neumann , в которой программный код хранится в памяти или в хранилище.
EVM также имеет свой собственный язык: «EVM bytecode». Когда программист, пишет смарт-контракт, который работает на Ethereum, обычно он пишет код на языке более высокого уровня, таком как Solidity скомпилировав его в байт-код, код становится понятным для EVM.
ОК, теперь сделаем это.
Перед выполнением определенного вычисления процессор должен быть уверен, что следующая информация будет доступна и валидна:
- Состояние системы
- Оставшегося gas достаточно для расчета
- Адрес аккаунта, которому принадлежит исполняемый код
- Адрес отправителя транзакции, кто инициировал это исполнение
- Адрес аккаунта, который вызвал выполнение кода (может отличаться от исходного отправителя)
- Gas price на транзакцию, которая инициировала это исполнение
- Входные данные для этого выполнения
- Значение в (Wei) передано этому аккаунту как часть текущего исполнения
- Машинный код, который должен быть выполнен
- Заголовок текущего блока
- Глубина текущего сообщения или стека создания контракта
В начале выполнения память и стек пустые, а счетчик программ равен нулю.
PC: 0 STACK: [] MEM: [], STORAGE: {}
Затем EVM запускает рекурсивное выполнение транзакции, вычисляя состояние системы и состояние машины для каждого цикла. Состояние системы — это глобальное состояние Ethereum.
Состояние машины состоит из:
- доступный gas
- счетчик команд
- содержимое памяти
- активное количество слов в памяти
- содержимое стека
Элементы стека добавляются или удаляются из самой левого края фрагмента кода
Для каждого цикла из оставшегося gas отнимается его определенная часть, а счетчик программ увеличивается.
В конце каждого цикла есть три возможности:
- Машина достигает исключительного состояния (например, недостаточный gas, недействительные инструкции, недостаточные элементы стека, элементы стека превышают 1024, недействительный пункт JUMP / JUMPI и т. Д.), И поэтому возникает необходимо остановить машину.
- Последовательность действий продолжает обрабатываться в следующем цикле
- Машина достигает контролируемой остановки, операции достигают логического завершения (конец процесса выполнения)
Предполагая, что машина исполнила задачи достигнув контролируемой остановки т.е логическим завершением, а не исключительным состоянием, она генерирует состояние результата, информацию о оставшемся gas, и данные результата.
Ну наконец то! Мы прошли через одну из самых сложных частей Ethereum. Даже если вы не полностью понимаете эту часть, все в порядке. Вам действительно не нужно разбираться в деталях, если вы не работаете с Ethereum на очень глубоком уровне.
Как финализируется блок.
Наконец, давайте посмотрим, как блок с транзакциями будет финализирован.
Когда мы говорим «финализация», это может означать две разные вещи, в зависимости от того, является ли блок новым или существующим. Если это новый блок, мы имеем в виду процесс, майнинга этого блока. Если это уже существующий блок, мы говорим о процессе вылидации блока. В любом случае для блока, который должен быть «финализирован», требуется четыре требования:
1) Валидация (или, если майнинг — определение) ommers
Каждый блок ommer в заголовке блока должен быть валидным заголовком и находиться не более чем в шестом поколении настоящего блока.
2) Валидация (или, если майнинг — определение) транзакции
Количество GasUsed в блоке должен быть равен совокупному gas, используемому транзакциями, перечисленными в этом блоке. (Напомним, что при выполнении транзакции мы отслеживаем счетчик gas в блоке, который считает общий gas, используемый всеми транзакциями в этом блоке).
3) Назначение вознаграждения (только в случае майнинга)
На адрес бенефициара приходит 5 Ether за майнинг этого блока. ( В соответствии с протоколом Ethereum EIP-649 это вознаграждение в размере 5 ETH скоро будет уменьшено до 3 ETH). Кроме того, за каждого ommer бенефициару текущего блока присуждается дополнительно 1/32 от текущего вознаграждения в блоке. Наконец, бенефициар блока (ов) ommer также получает определенную сумму (есть специальная формула для того, как это рассчитывается).
4) Верифицируйте (или, в случае майнинга, вычислите валидное) состояние и nonce
Убедитесь, что все транзакции и результаты изменений состояния применились, а затем определяется новый блок как состояние после того, как вознаграждение блока было отправлено по результату состояния последней транзакции. Верификация происходит путем сравнения этого конечного состояния с состоянием trie хранящимся в заголовке блока.
Майнинг proof of work
В разделе «Блоки» мы кратко остановилась на концепции сложности блоков. Алгоритм, который дает понятие о сложности блока, называется “Proof of Work “(PoW).
В Ethereum алгоритм “proof-of-work” носит название Ethash (ранее, но назывался Dagger-Hashimoto).
Формально алгоритм выглядит как:
где m — mixHash, n — nonce, Hn — заголовок нового блока (исключая компоненты nonce и mixHash, которые должны быть вычислены), Hn — это nonce для заголовка блока, а d — DAG (Направленный ациклический граф) , который является большим набором данных.
В разделе «Блоки» мы говорили о различных элементах, которые входят в заголовок блока. Два из этих компонентов назывались mixHash и nonce. Как вы помните:
Еще раз напомню:
mixHash — это хеш, который в сочетании с nonce подтверждает, что этот блок выполнил достаточное вычисление
nonce — хэш, который в сочетании с mixHash подтверждает, что этот блок выполнил достаточные вычисления
Алгоритм PoW используется для подсчета этих двух элементов.
Как точно вычисляются mixHash и nonce с использованием функции PoW, является несколько сложной задачей, и мы не будем углубляться в подробности. Но на высоком уровне он работает следующим образом:
Для каждого блока рассчитывается «seed». Этот seed отличается для каждой «epoch», где каждая epoch составляет 30 000 блоков. Для первой epoch, seed — это хеш 32 байтов из серии нулей. Для каждой последующей epoch это хэш предыдущего seed хеша. Используя этот seed, нода может вычислить псевдослучайный «cache».
Этот «cache» невероятно полезен, поскольку он позволяет использовать концепцию «легких клиентов», о которой мы говорили ранее. Целью легких клиентов является предоставление узлам возможности верифицировать транзакции без бремени хранения полных данных blockchain. Легкий клиент может проверить достоверность транзакции, опираясь на информацию исключительно в этом «cache», поскольку «cache» может регенерировать конкретный блок, который необходимо проверить.
Используя «cache», нода может генерировать набор данных DAG, где каждый элемент в наборе данных зависит от небольшого количества элементов псевдослучайного выбора из «cache». Чтобы быть майнером, вы должны генерировать полный набор данных; все полные клиенты и майнеры хранят этот набор данных, притом набор данных растет линейно с течением времени.
Затем майнеры могут взять случайные фрагменты пакета данных, и провести их через математическую функцию, чтобы хэшировать и объединить их в «mixHash». Майнер будет повторно генерировать mixHash, пока результат не окажется ниже желаемого целевого значения. Когда условие будет соответствовать этому требованию, то значение nonce будет считается действительным и блок может быть добавлен в цепочку.
Майнинг как механизм безопасности
В общем цель алгоритма PoW заключается в том, чтобы криптографически безопасно доказать, что определенный объем вычислений был израсходован для генерации некоторого результата (т.е. nonce). Потому что нет другого способа найти варианты nonce ниже требуемого значения, кроме как перебрать все возможные варианты.
Многократное применения хеш-функции имеет равномерное распределение по времени, и поэтому мы можем быть уверены, что в время необходимое для нахождения такого nonce, будет зависеть от порога сложности. Чем выше сложность, тем больше времени требуется для решения nonce. Таким образом, алгоритм PoW дает смысл концепции сложности, которая используется для обеспечения безопасности blockchain.
Но что же значит безопасность blockchain? Это просто: мы хотим создать цепочку блоков, которой доверяет каждый. Как мы обсуждали ранее, если существовала более чем одна цепочка блоков, пользователи теряют доверие, потому что они не смогут разумно определить, какая цепочка блоков была «действительной». Чтобы все пользователи принимали базовое состояние, которое хранится в blockchain, нам нужна единственная каноническая цепочка блоков, в которую верят все пользователи.
Именно это и делает алгоритм PoW: он гарантирует, что конкретная цепочка блоков останется канонической в будущем, алгоритм делает невероятно трудным для злоумышленника создание новых блоков, которые могут перезаписать определенную часть истории (например, путем стирания транзакций или создания поддельных транзакций) или поддержать разветвление цепочки блоков. Чтобы валидировать свой блок вперед, злоумышленнику необходимо будет решать значение nonce быстрее, чем кто-либо еще в сети, так что бы сеть поверила, что их цепочка является каноническая (основанной на принципах протокола GHOST, о котором мы упоминали ранее). Сделать это невозможно, если у злоумышленника нет более чем половина мощности сети, сценарий, известный как 51% -ная атака .
Майнинг как механизм экономической мотивации.
Помимо обеспечения безопасности blockchain, PoW также является и способом экономической мотивации для тех, кто расходует свои вычисления для обеспечения этой безопасности. Напомним, что майнер получает вознаграждение за майнинг каждого блока, оно включает себя:
- вознаграждения 5 Ether за майнинг выигранного блока, (вскоре он будет уменьшен до 3 Ether)
- стоимость gas, расходуемого внутри блока, транзакциями, включенными в блок
- дополнительная награда за включение ommers как часть блока
Что бы быть уверенным что алгоритм PoW устойчиво обеспечит безопасность и экономические мотивации Ethereum старается прививать два свойства:
- Сделать алгоритм PoW доступным как можно большему количеству людей. Другими словами, что бы людям не нужно было специализированное или нестандартное оборудование для запуска алгоритма. Цель этого заключается в том, чтобы сделать модель экономические мотивации настолько открытой, насколько это возможно, чтобы каждый мог предоставить вычислительную мощность в обмен на Ether.
- Уменьшить возможности для любого отдельной ноды (или пула) делать несоразмерную прибыль. Любая нода, которая может сделать несоразмерную сумму прибыли, может оказывать большее влияние на определение канонической цепочки блоков и это является причиной для беспокойства, потому что снижает безопасность сети.
В blockchain сети Bitcoin есть одна проблема, возникающая в связи с указанными выше двумя свойствами, она заключается в том, что алгоритм PoW является хэш-функцией SHA256. Слабость этого типа хэш-функции заключается в том, что ее можно решить гораздо более эффективно с помощью специализированного оборудования, известного как ASIC.
Чтобы смягчить эту проблему, Ethereum решил сделать алгоритм PoW со своей хэш-функцией (Ethhash) с последовательной памятью. Это означает, что алгоритм сконструирован таким образом, что для вычисления nonce требуется много памяти и пропускной способности. Большие требования к памяти затрудняют использование компьютером параллельной памяти для одновременного обнаружения нескольких nonce, а высокие требования к пропускной способности затрудняют одновременное обнаружение nonce даже для супер быстрых компьютеров. Это снижает риски централизации и создает более ровное игровое поле для нодов, которые выполняют верификацию.
Следует отметить, что Ethereum переходит от консенсусного механизма PoW к тому, что называется «“proof-of-stake”». Это тема достойна отдельной статьи, и мы надеемся исследовать ее в будущем.
Вывод
Уф! Вы дошли до конца. Я надеюсь?
Я знаю, что не так легко переварить этот пост. Если вам требуется многократное чтение, чтобы полностью понять что происходит, это абсолютно нормально. Я лично много раз читал yellow paper и white paper Ethereum, и изучал различные части кода, прежде чем собирать все в голове и понять что происходит в реальности.
Тем не менее, я надеюсь, этот пост стал для вас полезным. Если вы найдете какие-либо ошибки или неточности, я бы хотел, чтобы вы написали в записку в личку или разместили ее прямо в комментариях.
И помните, я человек (да, это правда), и я делаю ошибки. Я потратил время, чтобы написать этот пост на благо сообщества, бесплатно. Поэтому, пожалуйста, будьте конструктивны в своих отзывах, без лишних стараний.