# Waves 1.5: легкая нода
# Обзор
Протокол Waves версии 1.5 предоставляет механизм, позволяющий генератору блока создавать и распространять снапшоты состояния блокчейна в результате применения транзакций блока. Другие ноды могут полагаться на эти снапшоты для ускорения обработки транзакций при большой нагрузке. Другие ноды могут оспаривать результаты вычисления снапшотов в случаях, когда недобросовестный генератор распространяет невалидные снапшоты в сети блокчейна.
# Снапшот состояния для транзакции
Состояние блокчейна можно описать как набор соответствий key => value
, который необходим для проверки последующих транзакций, включая конечный баланс WAVES, ассетов и лизингов для аккаунта, записи данных, статусы исполнения ордеров и т. д. Каждая транзакция, в свою очередь, каким-то образом изменяет состояние, создавая, изменяя или удаляя некоторые значения из состояния. Снапшот состояния для транзакции (transaction state snapshot, TSS) — это набор всех пар (key, value)
, затронутых транзакцией. На сетевом уровне TSS кодируются как protobuf-сообщение TransactionStateSnapshot
.
Генератор создает снапшот состояния для каждой транзакции, которую он включает в блок. Вместо валидации транзакций ноды могут последовательно применять снапшоты состояния для транзакции к своему локальному состоянию, заменяя локальные значения для данного ключа новыми. Применение снапшотов вместо выполнения полной валидации транзакции может значительно сократить время обработки блока. Состояние блокчейна, полученное в результате последовательного применения снапшотов для транзакций, идентично состоянию, полученному путем выполнения всех транзакций.
# Хеш состояния для блока
Генератор вычисляет хеш снапшота состояния для транзакции (transaction hash, TH) путем построения списка бинарных строк si, представляющих изменения состояния. Эти бинарные строки сортируются в лексикографическом порядке и подаются на вход хеш-функции Blake2b. Для целей сортировки байты считаются беззнаковыми. Полученный хеш и является хешем состояния для транзакции.
Для снапшота формируются следующие бинарные строки (||
— оператор конкатенации строк):
- Балансы WAVES:
address || balance
- Балансы ассетов:
address || asset_id || balance
- Записи данных:
address || key || data_entry
- Скрипт аккаунта:
sender_public_key || script || verifier_complexity)
. В случае удаления скрипта хешируется толькоsender_public_key
. - Скрипт ассета:
asset_id || script
- Баланс лизинга:
address || lease_in || lease_out
- Параметры лизинга:
lease_id || amount || sender_public_key || recipient
- Статус лизинга:
lease_id || is_active
- Спонсорство:
asset_id || min_sponsored_fee
- Псевдоним:
address || alias
- Выполненное количество и комиссия ордера:
order_id || filled_volume || filled_fee
- Статические данные ассета:
asset_id || issuer_public_key || decimals || is_nft
.decimals
занимает 1 байт. - Возможность довыпуска ассета:
asset_id || is_reissuable || total_quantity
- Название и описание ассета:
asset_id || name || description || change_height
- Статус выполнения неуспешной транзакции:
tx_id || application_status
. Статус выполнения представляет собой 1 байт: либо 0x01 (неудачное выполнение скрипта), либо 0x02 (транзакция пропущена; см. ниже).
Логические флаги (is_active
, is_nft
, is_reissuable
) кодируются как один байт, где 0x01 означает true
, а 0x00 — false
. Все числовые поля представляют собой 8-байтовые значения с прямым порядком байтов (big-endian), если не указано иное.
Определим H0 как хеш пустой строки, а Hi — как хеш предыдущего хеша Hi-1, объединенного с THi. Hi — это хеш состояния после применения i-й транзакции:
- TH = Blake2b(s1||s2||...||sm),
- H0 = Blake2b('')
- Hn = Blake2b(Hn–1 || THn)
Генератор не включает снапшоты состояния в блок. Чтобы удостоверить подлинность TSS, генератор включает в заголовок блока финальный хеш Hn, где n — порядковый номер последней транзакции в блоке. Hn называется хешем состояния для блока.
Этот алгоритм хорошо работает для Waves-NG: при добавлении нового микроблока к состоянию не требуется пересчитывать хеши. Как и другие изменяемые поля «жидкого» блока (например, корневой хеш транзакций — merkle root), хеш состояния блока изменяется при добавлении новых микроблоков.
Ноды, работающие в режиме применения снапшотов состояния, при получении снапшота по сети вычисляют на его основе хеш состояния для блока и проверяют, совпадает ли вычисленный хеш с хешем из заголовка блока. Если хеши не совпадают, нода считает снапшот невалидным и игнорирует его.
Хотя ноды в этом режиме выполняют проверку консенсуса блоков, они, в конечном итоге, доверяют производителям снапшотов состояния, поскольку не проверяют, действительно ли снапшот соответствует содержимому блока.
# Оспаривание
Когда нода получает блок от генератора, она валидирует все транзакции блока и вычисляет собственный хеш состояния. Если собственный хеш не совпадает с хешем, вычисленным исходным генератором, нода может оспорить результаты, если в настройках ноды включена генерация блоков, а генерирующие аккаунты имеют достаточный генерирующий баланс.
- Генерирующий баланс оспаривающей ноды объединяется с генерирующим балансом исходного генератора, а затем параметры консенсуса блока пересчитываются. Если на ноде настроено несколько генерирующих аккаунтов, консенсусные параметры (включая временную метку блока) вычисляются для всех аккаунтов. Блок создается от имени аккаунта, для которого временная метка блока оказалась наименьшей.
- Оспаривающая нода создает новый блок с транзакциями из исходного блока и той же ссылкой.
- Заголовок оспариваемого блока включается в заголовок нового блока.
- Блок подписывается и передается другим нодам сети. Поскольку временная метка блока также пересчитывается, ноде, возможно, придется подождать, прежде чем распространять блок.
Этот механизм работает как для обычных блоков, так и для микроблоков: если в одном из микроблоков обнаружено несоответствие хеша состояния, нода перестает принимать последующие микроблоки и немедленно начинает оспаривание.
Когда какая-либо нода получает блок с оспариванием, она валидирует как оспариваемый блок, так и консенсус по оспариванию. Если оспаривание валидно, эффективный баланс вредоносного генератора (и, следовательно, его генерирующий баланс для следующих 1000 блоков) считается равным 0. Нода, успешно оспорившая блок, получает вознаграждение вместо первоначального генератора.
Уменьшение генерирующего баланса вредоносного генератора помогает предотвратить повторную попытку атаки. Сброс эффективного баланса до 0 в основной цепочке приведет к эффективному отключению майнера на следующие 1000 блоков.
# Противодействие недобросовестным оспариваниям
Недобросовестные генераторы могут распространять в сети невалидные оспаривающие блоки несколькими способами: транслируя альтернативный блок наверху блокчейна с намерением заменить валидный «жидкий» блок или предоставляя блок в ответ на запрос GetBlock
во время синхронизации. В обоих случаях такие невалидные блоки не будут оспариваться генератором и вместо этого будут отброшены. Оспорить можно только исходный блок (не являющийся оспаривающим).
# Пропущенные транзакции
Поскольку консенсусные параметры оспаривающего блока отличаются от параметров исходного блока, некоторые транзакции из исходного блока могут стать недействительными.
- Предположим, что регулярный баланс аккаунта генератора исходного блока равен 0 (то есть вся генерирующая мощность получена за счет входящего лизинга). Первая транзакция исходного блока переводит вознаграждение за блок со счета генератора в «сейф». Поскольку генератор оспаривающего блока меняется, эта транзакция больше не будет валидной.
- Временная метка блока и адрес генератора доступны из скриптов Ride. Изменения в этих полях могут изменить результаты выполнения скрипта и либо сделать неуспешной транзакцию вызова скрипта, либо сделать транзакцию невалидной из-за того, что верификатор аккаунта-отправителя вернет
false
.
Чтобы проверить корневой хеш транзакций (merkle root) исходного блока, оспаривающий блок должен включать все транзакции из исходного блока, поэтому никакие транзакции не могут быть отброшены, даже если они стали невалидными в оспаривающем блоке. Такие транзакции становятся пропущенными (elided): они остаются в блоке, но порождают пустое изменение состояния. Пропущенные транзакции обладают следующими свойствами:
- могут появиться только в оспаривающем блоке (транзакции в обычных блоках никогда не могут быть пропущенными),
- уникальность идентификатора транзакции по-прежнему требуется,
- поле
applicationStatus
содержит значениеelided
, - метод
/transactions/merkleProof
API ноды предоставляет доказательства присутствия в блоке для пропущенных транзакций, - встроенные функции Ride
transactionHeightById()
,transferTransactionById()
иtransactionById()
возвращаютunit
для пропущенных транзакций.
# VRF
VRF играет ключевую роль как на уровне консенсуса, так и на прикладном уровне. Поскольку VRF пересчитывается в оспаривающем блоке, важно убедиться, что оспаривающий генератор не сможет злоупотреблять механизмом оспаривания. Как упоминалось выше, только аккаунту с генерирующим балансом не менее 1000 WAVES разрешается оспаривать недопустимые хеши состояний. Это требование делает невозможным «добыть» конкретное значение VRF, чтобы переиграть dApp.
# Изменения схемы
В схему заголовка блока добавлены два новых поля:
bytes state_hash = 11;
ChallengedHeader challenged_header = 12;
Поле state_hash
содержит хеш состояния для текущего блока.
Сообщение ChallengedHeader
содержит поля заголовка блока, который был оспорен:
message ChallengedHeader {
int64 base_target = 1;
bytes generation_signature = 2;
repeated uint32 feature_votes = 3;
int64 timestamp = 4;
int32 version = 5;
bytes generator = 6;
int64 reward_vote = 7;
bytes header_signature = 8;
}
Цель состоит в том, чтобы включить все поля, необходимые для восстановления исходного заголовка, чтобы можно было проверить подпись и установить подлинность заголовка.
# Активация
После активации фичи № 22 “Light Node” схема блоков изменилась. Старые ноды не могут проверить подписи новых блоков, содержащих хеши состояния, поэтому не могут их добавить к своей цепочке. Ноды версии 1.4 проверяют активацию функции после того, как блок был добавлен. Поскольку новые блоки не добавлены, ноды не знают, что фича уже была активирована, и не могут отключиться из-за активации неподдерживаемой функции. Эта очевидная проблема привела бы к тихому отсоединению нод 1.4 от основной цепочки.
Чтобы гарантировать, что ноды 1.4 остановятся после активации фичи, изменение структуры блоков было отложено на 1000 блоков: генераторы начали добавлять в блок хеши состояний на высоте активации + 1000. 1000 превышает максимальную глубина отката (100), поэтому ноды, работающие в режиме синхронизации, могут добавить все блоки и увидеть, что была активирована неподдерживаемая фича.