eth_call в Ethereum: чтение состояния без транзакции
Читателю библиотеки не нужно менять реестр, чтобы узнать текущий выпуск курса 17. Для этого подходит чтение состояния. Однако выражение «прочитать контракт» скрывает несколько важных условий: какой экземпляр выбран, какой блок используется и каким интерфейсом закодирован запрос. Разберём эти условия до написания веб-клиента.
Предположим, в реестре уже есть courses[17] = (H1, true). Мы хотим получить эту пару и показать пользователю «выпуск активен». После урока будет понятно, почему такой ответ не является вечной гарантией активности и почему неудачный запрос нельзя автоматически представить как отсутствие курса.
Вычисление без новой записи
RPC-метод eth_call предлагает узлу исполнить сообщение на выбранном состоянии, не добавляя транзакцию в цепочку. Клиент получает результат вычисления. Если вызванная функция попыталась бы изменить временно используемое состояние, такие изменения не становятся публичной записью по результату этого запроса. Для обычного getter используются функции чтения, которые не должны менять состояние в самом контракте.
В Solidity view указывает на ограничение функции относительно изменения состояния, а pure — на ещё более узкую зависимость, без чтения и изменения состояния контракта. Эти слова полезны при разработке интерфейса, но не сообщают номер блока и не выбирают RPC-провайдера. Поэтому наличие view в ABI не делает любой ответ нужным вашему сайту.
function current(uint256 courseId)
external
view
returns (bytes32 contentHash, bool active)
{
Course storage item = courses[courseId];
return (item.contentHash, item.active);
}
Это дополнительный явный getter внутри CourseCatalog, равнозначный нужной нам форме чтения публичного mapping. Структура Course и поле courses определены в том же контракте. Фрагмент не вводит новых правил публикации: он только возвращает существующие поля. По номеру 17 ожидаются H1 и true.
Если курс никогда не публиковали, стандартные значения дают нулевой отпечаток и false. Если его публиковали и отключили, отпечаток остаётся ненулевым, а активность становится false. Следовательно, флаг активности отдельно не позволяет различить отсутствие записи и выключенный выпуск. Интерфейс должен учитывать модель данных, а не только одну удобную переменную.
Состояние какого момента
Узлу можно указать определённый блок либо подходящий тег состояния. В простом интерфейсе часто используют текущую доступную вершину, но она может измениться после ответа. Для согласованной страницы полезно понимать, какие запросы относятся к одному состоянию. Если отдельно прочитать издателя и курс на разных вершинах, результат при изменяемой модели полномочий может оказаться сочетанием разных моментов.
В исходном реестре издатель неизменяемый, поэтому именно это поле не меняется. Однако курс может быть обновлён: во время загрузки страницы H1 заменяется на H2. Если браузер скачал файл для H1, а затем прочитал актуальный H2, несовпадение не обязательно означает повреждение файла. Возможно, редакция выпустила новый материал между запросами. Покажите человеку, какой выпуск проверяли, и при необходимости обновите чтение.
Блок B: current(17) → H1, true
После B: издатель отправляет publish(17, H2)
Блок B+1: current(17) → H2, true
Проверка: файл H1 совпадает с выпуском на B, но не с текущим H2
Исторический ответ относится к историческому состоянию. Не каждый RPC-узел предоставляет произвольное старое состояние; для некоторых запросов требуется соответствующая инфраструктура. Отказ узла обслужить такой запрос не доказывает, что записи в прошлом не существовало. Это ограничение доступности источника данных.
Некоторые сети поддерживают теги безопасного или финализированного состояния, но клиент должен сверить фактическую поддержку и значение этих тегов. Их нельзя автоматически приравнять к одинаковому количеству подтверждений на любой сети. В локальной учебной сети смысл ожидания отличается от публичного Ethereum.
Ответ и ошибка — разные данные
Если getter вернул корректные нули, это нормальный результат для отсутствующего ключа. Если узел недоступен, ABI не подходит или по адресу нет реестра, корректного результата могло не быть вообще. Замена любого исключения на (0, false) скроет сбой окружения под надписью «курс не опубликован».
| Случай | Что известно | Что показывать |
|---|---|---|
| Корректный ответ с нулевым hash | Ключ не содержит публикации в выбранном состоянии | Курс отсутствует |
| Корректный ненулевой hash и false | Выпуск существует, но отключён | Выпуск неактивен |
| Не удалось обратиться к RPC | Состояние неизвестно | Не удалось обновить данные |
| Не удалось декодировать ответ | Интерфейс или экземпляр не совпали | Ошибка конфигурации |
Особенно полезно хранить время последнего успешного чтения. При временном сбое сайт может показать прежние сведения с ясной отметкой, вместо того чтобы немедленно исчезнуть. Но прежний ответ нельзя назвать свежим. Кэш помогает доступности интерфейса, а не изменяет происхождение данных.
Чтение также не должно автоматически открывать кошелёк. Для публичного реестра достаточно выбранного публичного RPC-клиента. Подключение аккаунта понадобится, когда пользователь намерен выполнить действие, для которого важны его полномочия. Такое разделение позволяет человеку просматривать библиотеку без лишних запросов разрешений.
Почему чтение может быть тяжёлым
Даже без публичной транзакции узел выполняет инструкции. Большой цикл или массив может сделать getter неудобным либо превысить лимиты RPC. Кроме того, другой контракт способен вызвать view-функцию внутри своей транзакции, и тогда её работа относится к газу этой транзакции. Поэтому проектирование getter требует границ объёма.
Для курса 17 ответ состоит из двух значений фиксированного размера. Он удобен для интерфейса и не зависит от числа остальных курсов. Если позже нужен список, лучше ввести ограниченную страницу или внешнюю индексацию, чем возвращать весь реестр единственным вызовом.
Мы получили точную модель чтения: сеть и экземпляр задают источник, блок задаёт момент, ABI задаёт способ понимания байтов, а функция возвращает конкретные поля. В следующем разборе сравним это вычисление с действием, которое откатывается. JSON-RPC Ethereum описывает форму запроса, которую затем используют клиентские библиотеки.
Когда ответ лучше не объединять
Два ответа с разными источниками нельзя без пояснения собрать в один «проверенный» объект. Например, издатель прочитан из локального узла, а выпуск — из публичного тестового RPC. У значений могут быть одинаковые формы, но они принадлежат разным состояниям. Сохраняйте реквизиты рядом с результатом, особенно в диагностическом отчёте.
Для обычной карточки можно скрыть технические поля под подробностями, оставив дату актуализации и понятное предупреждение при сбое. Это помогает читателю, не заставляя его изучать ABI ради просмотра учебника. Само приложение всё равно должно удерживать сеть, экземпляр и блок как самостоятельные данные.
Оглавление курса. Листинги, расчёты и наблюдения здесь разобраны как ожидаемое поведение; компиляция, учебные программы и проверки при написании не запускались.