Перейти к содержанию

Events в Solidity: события реестра публикаций

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

В уроке определим Published и Deactivated, разберём индексированный номер и отделим факт события от состояния на последнем выбранном блоке. Это подготовит реестр к браузерному клиенту и отдельному индексатору сайта.

Объявление журнала

В базовом контракте используются два события:

event Published(uint256 indexed courseId, bytes32 contentHash);
event Deactivated(uint256 indexed courseId);

Published сообщает номер и объявленный отпечаток. Deactivated сообщает номер отключённой записи. Ключевой номер отмечен indexed, чтобы внешнее чтение могло фильтровать журнал по предусмотренным темам. Это помогает искать события курса 17, не декодируя подряд весь объём чужих номеров.

Событие не выполняется от простого объявления. Его добавляет инструкция emit в успешно исполняемой функции. В публикации она находится после присваивания, поэтому журнал отражает выполненное действие с переданным отпечатком. При откате успешного лога этой операции не остаётся.

courses[courseId] = Course(contentHash, true);
emit Published(courseId, contentHash);

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

Один курс, несколько переходов

Рассмотрим последовательность:

Блок B:   Published(17,H1)
Блок C:   Deactivated(17)
Блок D:   Published(17,H2)
Текущая запись после D: H2,true

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

Аналогично первое Published не гарантирует, что курс активен сейчас. Оно объясняет прошлый переход. Для читателя полезно показать текущий выпуск и, отдельно, историю действий. Это разные экраны и разные вопросы к данным.

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

Indexed и данные

У событий есть тема сигнатуры и предусмотренные индексированные аргументы. Для нашего целочисленного courseId фильтрация удобна: известный номер можно выразить в ожидаемом формате. Неиндексированный contentHash декодируется из данных события по ABI.

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

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

Внешнее чтение по диапазонам

Индексатор спрашивает логи выбранного адреса на ограниченном диапазоне блоков. Это помогает соблюдать ограничения RPC и возобновлять работу после перерыва. Номер блока размещения реестра из манифеста позволяет не начинать с истории, где реестра ещё не существовало.

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

Реорганизация также может изменить принятую историю. Индексатор должен понимать, к какому блоку относятся его записи, и уметь откатить неподтверждённую ветвь. Само присутствие лога в ответе на один запрос не создаёт абсолютную финальность. Политика ожидания зависит от сети и требований приложения.

Данные Подходящий вопрос Ограничение
courses(17) Какой выпуск выбран сейчас Не перечисляет историю
Published Что издатель объявил в переходе Не гарантирует текущую активность
Deactivated Когда произошёл отзыв Позже возможна новая публикация
Индекс сайта Удобный список действий Может отставать от сети

Событие и самостоятельная проверка

Если сайт увидел Published(17,H2), он может предложить скачать соответствующий выпуск. Но само событие не проверяет полученные байты и не гарантирует их доступность. Сравнение отпечатка остаётся отдельной операцией, описанной ранее. Таким образом, журнал соединяется с доставкой файла, но не заменяет её.

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

Мы получили два слоя: текущее состояние для быстрых ответов и логи для истории. Следующая страница объяснит ABI, благодаря которому клиент правильно кодирует вызовы и понимает как поля ответа, так и события. События Solidity описывают сигнатуры, индексирование и ограничения.

Изменение ABI журнала

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

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

Также дублированная доставка одного лога не означает два действия. Индексатор должен распознавать известную запись по устойчивым реквизитам и не увеличивать счётчик публикаций от повторного запроса. А два разных успешных publish с одинаковым hash — действительно два события. Эти случаи нельзя различить только по содержимому hash.

В качестве упражнения составьте историю с H1, затем H2, затем отзывом и повторной публикацией H2. Текущая запись активна, а журнал содержит несколько объявлений одинакового файла. Этот результат соответствует базовому контракту и показывает, почему история действий не равна списку уникальных учебников.

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

Оглавление серии