Timelock в Solidity: очередь административных решений
Мультиподпись отвечает на вопрос, кто согласовал действие. Timelock добавляет другой вопрос: когда после объявления это действие можно выполнить. Для библиотеки задержка даёт время заметить неожиданный выпуск или изменение управления. Она не исправляет ошибочный договор и не выполняет действие сама.
Используем OpenZeppelin TimelockController 5.4.0 как издателя нового обычного CourseCatalog. Это самостоятельная композиция. Базовый реестр по-прежнему видит один immutable publisher, а очередь и роли находятся в timelock-контракте.
Подготовка субъектов
У TimelockController есть proposer, executor и canceller. Proposer ставит действие в очередь, executor выполняет готовое действие, canceller отменяет ожидающее. В закреплённом конструкторе первоначальные proposers получают также роль отмены. Эти назначения следует знать, а не выводить из названий кнопок панели.
Новый controller сам получает административную роль для своего управления. Дополнительный admin в конструкторе является отдельной возможностью начальной настройки. В учебном варианте ниже мы передаём ноль, поэтому не оставляем внешнего bootstrap-администратора без объяснения.
Фабрика отдельного комплекта
// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;
import {TimelockController} from
"@openzeppelin/contracts/governance/TimelockController.sol";
import {CourseCatalog} from "./CourseCatalog.sol";
contract PublicationClockFactory {
error InvalidSetup();
event Created(address indexed clock, address indexed catalog);
function create(address proposer, address executor, uint256 delay)
external returns (TimelockController clock, CourseCatalog catalog)
{
if (proposer == address(0) || executor == address(0)
|| delay == 0) revert InvalidSetup();
address[] memory proposers = new address[](1);
address[] memory executors = new address[](1);
proposers[0] = proposer;
executors[0] = executor;
clock = new TimelockController(delay, proposers, executors, address(0));
catalog = new CourseCatalog(address(clock));
emit Created(address(clock), address(catalog));
}
}
Это полный собственный исходник фабрики с соседним базовым CourseCatalog. Он не использует существующие публичные адреса. Каждый create создаёт независимый комплект, а событие помогает сохранить фактические реквизиты после самостоятельного исполнения. Никакой комплект здесь не создавался при подготовке урока.
Фабрика не навязывает конкретный срок. Переданный delay должен быть положительным по нашему правилу. Число выбирается политикой лаборатории. Его нельзя назвать достаточным сроком реакции для любой реальной команды без отдельного обоснования.
Идентификатор точного действия
Очередь связывает target, value, data, predecessor и salt. Для публикации target — новый каталог, value=0, data — кодирование publish(17,H1). Predecessor может быть нулевым, если действие не зависит от другой операции. Salt отличает нужные объявления по договору controller.
bytes memory payload = abi.encodeCall(
CourseCatalog.publish,
(uint256(17), digest)
);
Это фрагмент подготовки payload, где digest является известным ненулевым bytes32 выбранного файла. Для исполнения используется ровно тот же набор параметров, который поставлен в очередь. Подмена H1 на H2 создаёт другую операцию, а не новое содержимое прежнего разрешения.
Proposer вызывает schedule с delay не меньше текущего minDelay. Controller вычисляет идентификатор и сохраняет время готовности. После этого публикации ещё нет. Позднее executor вызывает execute, и controller проверяет состояние, возможного predecessor и точные параметры.
Ожидание и отмена
Состояния операции различаются: нет записи, ожидание, готовность и завершение. Истечение задержки переводит условие в готовность по времени, но само не запускает транзакцию. Нужен субъект, который выполняет разрешённый execute.
Отмена допустима для ожидающей либо готовой операции по правилам закреплённого кода. После неё прежнее исполнение не проходит как готовое. Повторное объявление может иметь свой выбранный идентификатор и новую процедуру. Сайт должен показывать текущее состояние, а не считать отменённое предложение навсегда ожидающим.
| Этап | Состояние курса 17 | Состояние операции |
|---|---|---|
| Payload подготовлен | Прежнее | Ещё не назначена |
| Schedule выполнен | Прежнее | Ожидает |
| Время прошло | Прежнее | Готова |
| Execute успешен | H1,true | Выполнена |
Роли и открытое исполнение
В некоторых конфигурациях EXECUTOR_ROLE назначают нулевому адресу как предусмотренный открытый режим. Это означает доступность исполнения готовых операций для всех по специальному модификатору controller, а не произвольную выдачу целей и параметров. Наш вариант выбирает конкретного ненулевого executor.
Даже с открытым исполнителем proposer остаётся важным: он определяет, какое действие поставлено в очередь. Дополнительные администраторы и право отмены также входят в карту доверия. Нельзя оценивать систему только по величине задержки.
Изменение minDelay происходит через предусмотренное действие самого timelock. У такого изменения есть свой целевой payload и очередь. Это не обычный setter с любого редакционного аккаунта. До выполнения изменения действует прежний договор времени.
Что происходит при отказе цели
Если CourseCatalog отклонит параметры, внешнее исполнение не становится успешной публикацией. Например, нулевой digest был неправильно подготовлен. Задержка и согласование не исправляют неверный вход. Для восстановления сначала разбирают отказ и состояние операции, а не меняют параметры старой попытки без нового объявления.
Порядок нескольких готовых выпусков тоже остаётся значимым. H2 может заменить H1 после двух самостоятельных разрешённых операций. Timelock даёт наблюдаемую очередь, но не гарантирует, что все участники выберут правильную редакционную последовательность без дополнительных условий.
Мы получили композицию из неизменного издателя и отложенного управления. Её параметры читаются и проверяются отдельно от состояния курсов. Исходник TimelockController 5.4.0 фиксирует роли, идентификаторы и маршрут исполнения.
Задержка и наблюдение команды
Очередь даёт время реакции только тогда, когда команда действительно её наблюдает. Если никто не читает объявленные цели и payload, неожиданное действие может спокойно дойти до готовности. Следовательно, полезность задержки связана с процедурой обнаружения и субъектом отмены, а не только с положительным числом seconds.
При отображении операции покажите целевой адрес, номер курса и полный digest после декодирования подходящим ABI. Не заменяйте эти сведения общим названием «обновление». Два действия с одним названием могут публиковать разные файлы или обращаться к разным реестрам.
Пусть proposer поставил H1 и затем H2, оба без predecessor. Их готовность не задаёт обязательный порядок. Если H2 исполняется первым, поздний H1 может стать текущим. Для требуемой зависимости используются предусмотренные параметры либо отдельная проверка ожидания в целевом контракте. Timelock сам не угадывает редакционный порядок версий.
Наконец, отмена уже завершённой публикации не является функцией cancel. Этот метод действует на pending-операцию. После успешного execute исправление курса проходит отдельным новым действием либо отзывом в подходящей модели. Задержка не превращает историю сети в редактируемую очередь старых результатов. Сохраните идентификатор каждой операции, чтобы повторный запрос состояния относился к тому же набору параметров, а не к похожему названию.
Для самостоятельного уточнения: TimelockController.sol (5.4.0).
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.