Web3: деактивация публикации из интерфейса
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 23 из 30.
Деактивация курса не удаляет его отпечаток: она меняет флаг текущей записи на false. Для интерфейса это отдельное намерение, а не разновидность публикации с нулевым хешем. В этом уроке подготовим маршрут deactivate(17) с теми же этапами проверки, подписи и наблюдения. Результат — издатель понимает, какую текущую запись собирается сделать неактивной.
Предполагается уже реализованный маршрут публикации и точный ABI базового контракта. Не добавляем новую функцию или право. Учебные вызовы не запускались, кошелёк не использовался. Ожидаемые состояния определяются исходником CourseCatalog из обзора Solidity.
Семантика действия
Если курс активен, deactivate сохраняет прежний contentHash, записывает active = false и испускает событие Deactivated. Если запись уже неактивна, ожидается NotActive. Если отправитель не издатель, проверка полномочий выполняется первой и ожидается Unauthorized. Порядок имеет значение при объяснении ошибки читателю.
Нулевой отпечаток не является сигналом деактивации. Метод publish отвергает такое значение. Даже если бы интерфейс искусственно показал нули вместо сохранённого хеша, это не изменило бы состояние контракта. Карточка после деактивации показывает прежний отпечаток и отдельный статус неактивности.
Деактивация не уничтожает файл манифеста и не отзывает уже скачанные материалы. Она сообщает о состоянии реестра. Если сайт перестаёт предлагать ссылку на файл, это отдельная политика интерфейса. Пользователь, у которого сохранились байты, всё ещё способен сравнить их с отпечатком; флаг активности описывает редакционное состояние, а не физическое наличие файла.
Подготовка конкретного намерения
На экране подтверждения показываем номер курса и текущий отпечаток. Базовый метод не принимает ожидаемую редакцию, поэтому сравнение перед подписью важно для понимания человека, но не создаёт атомарную защиту от параллельного обновления.
import { publicClient } from './client.js';
import { catalogAbi } from './catalog-abi.js';
export async function prepareDeactivation({ address, account, courseId, revision }) {
const [contentHash, active] = await publicClient.readContract({
address, abi: catalogAbi, functionName: 'courses', args: [courseId],
});
if (!active) throw new Error('Курс уже неактивен');
const { request } = await publicClient.simulateContract({
address, abi: catalogAbi, functionName: 'deactivate',
args: [courseId], account,
});
return {
request,
preparedFor: { address, account, chainId: 31337, revision },
observedContentHash: contentHash,
courseId,
};
}
Это самостоятельный модуль подготовки, который использует уже определённые клиенты. Его request можно передать модулю отправки после актуальной сверки контекста. Название прежней функции sendPublication лучше заменить нейтральным sendCatalogWrite, если она теперь обслуживает оба метода. Изменение имени объясняет расширение ответственности, а не меняет алгоритм подписи.
Предварительное чтение и симуляция могут относиться к разным последним блокам. Если необходим согласованный снимок, передаём один blockNumber, как в уроке о чтении. Даже тогда состояние способно измениться перед включением транзакции. В исходном контракте нет сравнения observedContentHash, поэтому оно остаётся информацией экрана подтверждения.
Гонка с новой публикацией
Представим, что издатель увидел выпуск A и подготовил его деактивацию. До подписи другой процесс того же издателя опубликовал B. Вызов deactivate(17) теперь сделает неактивным B. Это не ошибка EVM: так определён метод. Интерфейс должен повторно прочитать запись и предупредить, если отпечаток изменился.
Если продукт требует деактивировать строго A, вводят метод вроде deactivateIfHash с проверкой ожидаемого значения в контракте. Его нельзя объявить существующим только на клиентской стороне. Такой вариант относится к расширению модели реестра и требует собственного исходника, ABI и описания ошибок.
Повторное подтверждение помогает человеку избежать обычной гонки, но не гарантирует отсутствие изменений в последнюю секунду. Поэтому предел простого контракта остаётся видимым. Для редакционного процесса можно также ограничить число параллельных рабочих сессий издателя, однако это внешняя организационная мера.
Receipt и новое чтение
После подписи журнал сохраняет метод deactivate, номер курса и наблюдаемый отпечаток. Полученный хеш не переводит карточку в неактивное состояние автоматически. После успешного receipt выполняется новое чтение. Если другая публикация уже активировала курс, интерфейс показывает факт успешной деактивации и текущую активную запись как разные наблюдения.
При откате журнал сохраняет результат, а карточка обновляется отдельно. NotActive может означать, что желаемое состояние уже установлено другим вызовом, но не следует выдавать откатившуюся операцию за успешную только потому, что итоговый флаг удобен. История показывает, что именно произошло с конкретной транзакцией.
Если ожидание истекло, сохраняется неизвестность и хеш. Повторная деактивация может откатиться после позднего исполнения первой. Это ещё одна причина сначала продолжить наблюдение, а не отправлять повтор автоматически. Один пользовательский замысел и несколько сетевых попыток должны оставаться различимыми.
Поведение для читателя
Обычный читатель видит объяснение неактивности и сохранённый отпечаток. Он не получает кнопку административного действия только из-за подключения кошелька. Внешняя ссылка на манифест может быть скрыта по политике сайта, но старые URL уроков ProfessorWeb не меняются от флага учебного контракта: это отдельный демонстрационный реестр.
Для упражнения разберите активную запись, уже неактивную запись и смену выпуска A на B перед подписью. Запишите, какой результат ожидается от контракта и какие дополнительные сведения должен показать интерфейс. Итог урока — деактивация является конкретным переходом состояния со своими ограничениями. Определения ошибок и функций приведены в обзоре Solidity.
Деактивация и редакционная история
Кнопка может запросить текст причины для внешнего журнала сайта, но базовый контракт эту причину не сохраняет. Приложение должно прямо разделить сетевой переход и редакционную заметку. Если запись заметки на сервере не удалась после успешного receipt, курс всё равно деактивирован. Повторная транзакция ради сохранения комментария не решит проблему внешнего сервиса.
Для связности истории заметка использует идентификатор операции и хеш receipt, а не только номер курса. Позже новая публикация может снова активировать курс17, и прежняя причина останется относящейся к конкретной деактивации. Текущее описание карточки не должно переносить старую причину на новый выпуск без отдельного редакционного решения.
Если продукт не имеет серверного журнала, интерфейс показывает только факты, доступные в цепочке. Отсутствие объяснения не разрешает ему придумывать причины. Это важная граница между удобным содержанием страницы и проверяемым состоянием реестра.
При повторной активации через publish сохраняется новый отпечаток и создаётся отдельное событие. Не следует удалять прежнюю деактивацию из истории: она относится к своему блоку и остаётся частью последовательности выпуска.