Web3: форма публикации и аргументы контракта
Оглавление курса «Web3-интерфейс: реестр в браузере» · Урок 16 из 30.
Форма публикации связывает намерение издателя с двумя аргументами publish: номером курса и ненулевым отпечатком выпуска. Она не должна отправлять транзакцию сразу после ввода. В этом уроке подготовим проверенное намерение и отдельный экран подтверждения параметров. Результат — неверный номер или неправильная длина отпечатка обнаруживаются до обращения к кошельку.
Предполагаются карточка реестра, подключённый аккаунт и актуальный сетевой контекст. Для курса 17 используем SHA-256 байтов манифеста, как определено ранее. Контракт лишь хранит bytes32; алгоритм является соглашением приложения. Примеры не запускались, форма и публикация в сети не выполнялись.
Проверка строки и смысла
Номер вводится строкой и преобразуется в bigint функцией parseCourseId. Отпечаток должен иметь префикс 0x и ровно 64 шестнадцатеричных символа. Нулевое значение запрещено базовым контрактом. Нельзя сокращать отпечаток, автоматически дополнять неизвестные байты нулями или превращать название файла в аргумент вместо хеша.
Проверка формы решает две задачи. Первая — совместимость с ABI: типы и размеры аргументов правильны. Вторая — понятность намерения человеку: какой курс изменяется, какой выпуск заменяется, куда направляется вызов. Даже идеально закодированный запрос может означать неправильное пользовательское действие, если выбран чужой экземпляр или ошибочно указан номер курса.
Новый вызов publish заменяет существующую запись полностью и устанавливает active = true. Это не добавление второй записи истории в mapping. История появится из события, а текущий getter покажет новый отпечаток. Экран подтверждения должен объяснить замену выпуска, особенно если прежний курс был деактивирован и публикация снова его активирует.
Модуль намерения
Файл publication-intent.js принимает текст формы и нормализованный контекст. Возвращаемый объект не является подписанной транзакцией и не содержит секретных данных.
import { parseCourseId } from './numbers.js';
const ZERO = '0x' + '00'.repeat(32);
export function publicationIntent({ idText, digestText, context, snapshot }) {
const courseId = parseCourseId(idText);
const contentHash = digestText.trim().toLowerCase();
if (!/^0x[0-9a-f]{64}$/.test(contentHash) || contentHash === ZERO) {
throw new Error('Нужен ненулевой отпечаток из 32 байтов');
}
if (context.chainId !== 31337 || !context.account) {
throw new Error('Нужен актуальный аккаунт локальной сети');
}
return Object.freeze({
functionName: 'publish',
args: [courseId, contentHash],
account: context.account,
chainId: context.chainId,
address: context.catalogAddress,
revision: context.revision,
observedBlockHash: snapshot.blockHash,
previousContentHash: snapshot.contentHash,
});
}
Здесь context.catalogAddress получен из проверенной конфигурации, а не из скрытого поля формы. revision относится к совокупному поколению аккаунта, сети и экземпляра. Если любая часть контекста изменилась, намерение сохраняется как черновик, но теряет статус готового к подписи.
Снимок должен относиться к тому же courseId, что и введённая форма. Для интерфейса только курса 17 поле номера можно сделать фиксированным, оставив проверку на программной границе. Если разрешён произвольный номер, сначала читают соответствующий курс и затем создают намерение. Нельзя подставлять старый снимок курса 17 в экран изменения курса 18.
Экран предварительного просмотра
Человек видит действие «Опубликовать выпуск», номер курса, прежний и новый отпечатки, сеть, экземпляр и аккаунт. Отправляемое ETH в базовом вызове равно нулю: метод не является payable и не описывает покупку материала. Комиссия оплачивает исполнение сети, но не превращается в цену курса.
Сравнение прежнего и нового отпечатков помогает заметить повтор. Базовый контракт допускает публикацию тех же байтов и снова создаёт событие. Интерфейс может предупредить, что содержание не меняется, но не объявляет такой вызов запрещённым правилом контракта. Это локальная политика удобства, которая должна быть явно отделена от семантики EVM.
Перед моделированием проверяем, что аккаунт совпадает с издателем. Эта проверка даёт быстрое сообщение, однако моделирование и само исполнение всё равно необходимы. Значение publisher в нашем базовом контракте неизменяемо, но сетевой контекст и адрес выбранного экземпляра могут измениться в браузере.
Отпечаток не создаётся из текста поля
Если издатель выбрал файл, браузер хеширует точные байты файла по объявленному правилу. Если он вручную вставил отпечаток, приложение не может утверждать, что проверило соответствующий манифест. Поле принимает корректные байты, но происхождение остаётся пользовательским. Полезно показывать, каким способом подготовлено значение: «получено из файла» или «введено вручную».
Перечень материалов внутри манифеста также проверяется до выпуска по редакционной схеме. Контракт не знает, что все Markdown-файлы существуют и ссылки работают. Он сохранит отпечаток даже для пустого или ошибочного документа, если значение ненулевое. Поэтому выпуск библиотеки остаётся сочетанием редакционной подготовки и сетевого объявления.
Не надо загружать огромный файл целиком только ради формы, если архитектура допускает проверенный небольшой манифест. Размер и допустимый тип входа относятся к ограничениям веб-приложения. При ошибке загрузки текст формы и выбор курса можно сохранить, не создавая ложного готового отпечатка.
Смена выпуска во время подготовки
Пока издатель редактировал форму, другой разрешённый процесс мог опубликовать новый выпуск. Сравнение свежего снимка с previousContentHash позволяет предупредить о смене текущей записи. Но базовый publish не имеет условного аргумента «замени только если прежний хеш A». Поэтому предупреждение интерфейса не обеспечивает атомарную защиту от конкуренции.
Если бизнес-процесс требует такой защиты, контракт расширяют отдельным методом с ожидаемым состоянием. Здесь не меняем исходник молча. Пользователь перед отправкой видит актуальные сведения, понимает возможную замену и принимает новое решение. Ограничение важно именно потому, что текущий пример намеренно простой.
Для упражнения рассмотрите короткий отпечаток, нулевой отпечаток, новый номер без подходящего снимка и повтор прежнего выпуска. В первых трёх случаях подготовка останавливается или требует новое чтение; в последнем интерфейс объясняет повтор, не придумывая откат. Следующий урок выполнит предварительное моделирование этого намерения. Ограничения типов описаны в документации Solidity.
Сохранение формы и версия правила
Если черновик восстанавливается после обновления приложения, он содержит версию правила хеширования и схему намерения. Новая страница не должна считать старое значение автоматически соответствующим текущему манифесту. Она показывает исходные данные как черновик и выполняет новую проверку. Особенно важно это при переходе от хеша одного файла к хешу манифеста всего курса.
Сохранённый аккаунт также является только прошлым выбором, а не актуальным разрешением. Даже если строка совпадает с издателем, необходимо прочитать текущий провайдер и сеть. Не следует сохранять объект request симуляции как универсальный документ для поздней подписи: он имеет конкретное состояние, профиль библиотеки и контекст подготовки.