Solidity: деактивация записи с сохранением хеша
Издатель обнаружил ошибку в материале курса 17 и хочет прекратить считать выпуск действующим. При этом полезно сохранить, какой именно выпуск отключён. Для этого базовая deactivate меняет только логический флаг. Разберём, чем такой отзыв отличается от удаления, почему повторный вызов отклоняется и что увидит сайт.
Исходное состояние — H1,true. После успешного действия должно быть H1,false. Первая часть пары остаётся той же. Это ясное правило пригодится как интерфейсу, так и независимой модели будущих проверок.
Тело операции
function deactivate(uint256 courseId) external onlyPublisher {
if (!courses[courseId].active) revert NotActive();
courses[courseId].active = false;
emit Deactivated(courseId);
}
Модификатор ограничивает действие издателем. Проверка активности отделяет допустимое отключение от попытки отключить неизвестный либо уже неактивный курс. Затем меняется ровно одно поле, и событие сообщает номер. Публикация нового отпечатка здесь не происходит.
Для отсутствующего курса mapping даёт active=false, поэтому возникает NotActive. Мы не вводим дополнительную ошибку отсутствия в базовой версии. Интерфейс может предварительно прочитать отпечаток и подробнее объяснить состояние, но окончательный отказ метода в обоих случаях общий.
Нулевой номер также приводит к неактивной стандартной записи при нашей модели, поскольку publish не создаёт ключ 0. Здесь нет отдельного InvalidCourse для этого входа. Если проверка ожидает такую ошибку, она не соответствует показанному телу. Именно поэтому сценарии следует выводить из конкретной последовательности условий.
Отзыв не удаляет байты материала
Поле активности относится к объявлению издателя. Если файл H1 уже скачан, контракт не может заставить читателя забыть его. Если файл остаётся доступен по публичному URL, деактивация не превращает этот URL в секретный. Сайт может скрыть карточку или показать предупреждение, но содержимое внешнего хранилища управляется отдельно.
Такое различие важно для заявления «курс отозван». Корректно сообщить, что издатель перестал считать выпуск активным. Некорректно обещать физическое уничтожение всех копий или полное удаление истории. Публичная сеть и открытое распространение имеют другие свойства.
Сохранённый отпечаток позволяет человеку сопоставить предупреждение со своим файлом. Если сразу стереть hash, он увидит только отсутствие текущего указателя и будет искать историю отдельно. Поэтому выбор сохранить значение имеет практический смысл, а не является случайной экономией строки кода.
Повторное действие
После первого успешного отключения active уже равен false. Второй вызов издателя встречает NotActive. Это не идемпотентный успешный метод: он не возвращает нейтральный успех на любой уже неактивной записи. В проекте можно выбрать другой договор, но требуется сказать об этом явно.
Начало: H1,true
Первый deactivate(17): H1,false + событие
Второй deactivate(17): NotActive, нового события нет
Почему такой выбор полезен? Он помогает заметить повтор редакционного действия и не создавать лишний журнал отключений. Почему он может быть неудобен? Клиент, повторивший запрос после неизвестного результата, получит отказ, даже если первое действие уже успешно выполнено. Для восстановления интерфейс должен читать состояние и искать квитанцию, а не бесконечно повторять вызов.
Это пример различия контрактного и пользовательского результата. Пользователь намеревался получить неактивный курс, и состояние может уже соответствовать этому намерению. Но новая транзакция не выполнила новый переход. Сообщение «курс уже отключён» точнее общего «ничего не получилось».
Публикация после отключения
Базовый publish(17,H2) снова записывает true. Это разрешённое развитие реестра. Отзыв не является постоянной блокировкой номера. Если требуется необратимая ликвидация курса, отдельная модель должна содержать такой статус и запрет будущего перехода.
Поэтому индексатор должен учитывать последовательность событий. Факт отключения в прошлом не означает, что курс неактивен сейчас. После исправления редактор публикует новый файл и активирует ключ. Сайт может показывать историю H1 → отзыв → H2, а текущая карточка относится к H2.
Если интерфейс продолжает отображать прежний H1 после новой публикации, возможно, кэш не обновился. Это не отменяет переход в контракте. Нужно различать момент последнего успешного чтения и актуальное состояние. Для критического действия читатель получает новую запись из выбранного источника.
Ошибка удаления вместо отключения
Рассмотрим неверную для данного требования строку:
delete courses[courseId];
Она очищает простую структуру до стандартных значений. После этого getter возвращает нулевой hash и false. Исторические события продолжают существовать, однако текущая запись потеряла указатель на отключённый выпуск. Замена не сохраняет базовую семантику и потребует другого объяснения сайта.
| Требование | deactivate | delete структуры |
|---|---|---|
| Снять активность | Да | Да через стандартное значение |
| Сохранить текущий hash | Да | Нет |
| Отличить от никогда не опубликованного | По hash возможно | По текущим полям нет |
| Удалить публичную историю | Нет | Нет |
Если будущая структура содержит вложенные mapping, очистка усложнится ещё больше. Сами ключи не перечисляются автоматически, и общая операция не гарантирует ожидаемого удаления всех вложенных данных. В нашем базовом типе таких полей нет, но слово delete всё равно нельзя понимать как универсальную очистку мира приложения.
Мы получили определённый переход от активного выпуска к неактивному с сохранением отпечатка. Следующая часть введёт отдельный журнал версий, чтобы текущий указатель и история имели собственные ясные интерфейсы. Очистка значений Solidity помогает сравнить эту операцию с нашим намеренным отзывом.
Состояние после неизвестного результата
Редактор отправил deactivate, но потерял соединение. При восстановлении getter показывает H1,false. Это совместимо с успешным отключением, однако само чтение не доказывает, что причиной была именно та транзакция. Другой разрешённый участник той же модели управления мог выполнить действие. Для связи с конкретным намерением нужна история результата.
Если же getter показывает H2,true, нельзя автоматически считать первую попытку неуспешной. Она могла отключить H1, а затем другой выпуск активировал курс. История событий объясняет последовательность, текущая запись объясняет состояние сейчас. Такое различие важно для корректного отчёта редактору.
Отзыв не должен удалять возможность просмотреть прежний выпуск с предупреждением, если это требуется библиотеке. Сайт может сохранить архивную страницу и объяснить причину отключения внешней заметкой. Это решение веб-приложения, а не автоматическая функция Deactivated.
Упражнение: после отключения H1 опубликуйте мысленно тот же H1 снова. Новый текущий статус active=true, файл не изменился, но произошло новое разрешённое объявление. События помогают понять этот переход. Их нельзя заменить сравнением только двух одинаковых отпечатков до и после всей последовательности.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.