Привяжите всё к одному блоку
Получите заголовок через `eth_getBlockByHash` и проверьте возвращённый хеш. Сохраните `chain ID`, номер блока и `stateRoot`. Вызовите `eth_getProof` с тем же хешем. Метка `latest` для воспроизводимой проверки не подходит: вершина цепи может измениться между запросами. Почему RPC-источники расходятся и когда блок считать устойчивым — отдельные вопросы.
Заголовок и доказательство от одного недоверенного узла могут быть согласованно ложными. Получите `stateRoot` через доверенную цепочку заголовков, лёгкий клиент или независимый источник. Здесь проверяется состояние относительно выбранного корня, а не консенсус Ethereum. `Execution APIs 1.0.0-beta.7` допускает номер, метку или хеш блока. В этой инструкции используется хеш. `EIP-1186` остаётся в статусе `Stagnant`, а не `Final`.
| Значение | Точный формат | Роль |
|---|---|---|
| `chain ID` | Целое значение сети | Не смешать одинаковый адрес в разных EVM-сетях |
| Хеш блока | 32 байта | Связать заголовок и доказательство |
| `stateRoot` | 32 байта из заголовка | Начальный корень для `accountProof` |
| Адрес | Ровно 20 байт | Построить путь аккаунта |
| Ключ хранилища | `bytesMax32`, затем 32 байта | Построить путь ячейки |
- Значение
- `chain ID`
- Точный формат
- Целое значение сети
- Роль
- Не смешать одинаковый адрес в разных EVM-сетях
- Значение
- Хеш блока
- Точный формат
- 32 байта
- Роль
- Связать заголовок и доказательство
- Значение
- `stateRoot`
- Точный формат
- 32 байта из заголовка
- Роль
- Начальный корень для `accountProof`
- Значение
- Адрес
- Точный формат
- Ровно 20 байт
- Роль
- Построить путь аккаунта
- Значение
- Ключ хранилища
- Точный формат
- `bytesMax32`, затем 32 байта
- Роль
- Построить путь ячейки
Пройдите `accountProof` от `stateRoot` до аккаунта
`accountProof` — массив узлов дерева Меркла — Патриции (`Merkle Patricia Trie`). Узлы записаны в двоичной кодировке `RLP`. Путь начинается с доверенного `stateRoot`. Его ключ — результат хеш-функции `Keccak-256` от 20 байт адреса. Хеш делится на полубайты, по которым проверяющий код проходит дерево.
Один проход по доказательству аккаунта
- 1Декодируйте входы
Адрес должен дать ровно 20 байт, корень и хеши — по 32. Не хешируйте строку `0x…` как текст UTF-8.
- 2Проверьте связи узлов
Начните с `stateRoot`. Для каждого дочернего хеша найдите соответствующий узел, декодируйте `RLP` и продолжите по нужным полубайтам.
- 3Декодируйте найденную запись
Лист аккаунта содержит `RLP`-список `[nonce, balance, storageRoot, codeHash]`. Сравните все четыре поля с ответом, включая поле ответа `storageHash`.
Путь может корректно закончиться отсутствующим дочерним узлом или несовпадающим сжатым путём. Доказательство хранит самый длинный существующий префикс. В клиенте `go-ethereum` функция `VerifyProof` тогда возвращает `nil` без ошибки. Это доказанное отсутствие аккаунта относительно `stateRoot`, а не придуманный нулевой лист.
От доказанного `storageRoot` перейдите к ячейке
Ячейка хранилища (`storage slot`) — 32-байтовая позиция в состоянии контракта. Поле `storageKeys` принимает `bytesMax32`. Декодируйте его как беззнаковое число от старшего байта к младшему (`big-endian`). Значение длиннее 32 байт отклоните, короткое дополните нулями слева. Затем вычислите `Keccak-256` от всех 32 байт.
Порядок проверки каждой записи `storageProof`
- для простой переменной возьмите номер ячейки; для отображения (`mapping`) и динамического массива сначала вычислите позицию по разметке контракта;
- начните путь от `storageRoot`, полученного из проверенного листа аккаунта, а не от `stateRoot` блока;
- сверьте поле `key` с нормализованным ключом и проверьте все `RLP`-узлы по его хешу;
- декодируйте найденный лист как `RLP`-целое без ведущих нулей и сравните с полем `value`;
- если корректный путь доказывает отсутствие листа, значение этой ячейки равно нулю в выбранном блоке.
Ключ ячейки — не calldata и не порядковый номер поля из обозревателя. Если calldata и разметка хранилища смешаны, доказательство будет проверяться по другой позиции.
Сверьте результат и сохраните причину отказа
| Что не сошлось | Частая причина | Следующий ход |
|---|---|---|
| Первый узел не соответствует `stateRoot` | Заголовок и доказательство взяты из разных блоков | Повторить запрос по точному хешу |
| Путь аккаунта расходится | Хешировали текст или расширили адрес до 32 байт | Хешировать ровно 20 декодированных байт |
| Поля аккаунта отличаются | Ответ не соответствует доказанному листу | Отклонить ответ и сохранить узлы |
| Путь ячейки расходится | Выбран неверный корень или рассчитана другая позиция | Начать от доказанного `storageRoot` и пересчитать ключ |
| Получился ноль | Смешаны нулевой лист и отсутствие листа | Сохранить конечный тип пути и результат проверяющего кода |
- Что не сошлось
- Первый узел не соответствует `stateRoot`
- Частая причина
- Заголовок и доказательство взяты из разных блоков
- Следующий ход
- Повторить запрос по точному хешу
- Что не сошлось
- Путь аккаунта расходится
- Частая причина
- Хешировали текст или расширили адрес до 32 байт
- Следующий ход
- Хешировать ровно 20 декодированных байт
- Что не сошлось
- Поля аккаунта отличаются
- Частая причина
- Ответ не соответствует доказанному листу
- Следующий ход
- Отклонить ответ и сохранить узлы
- Что не сошлось
- Путь ячейки расходится
- Частая причина
- Выбран неверный корень или рассчитана другая позиция
- Следующий ход
- Начать от доказанного `storageRoot` и пересчитать ключ
- Что не сошлось
- Получился ноль
- Частая причина
- Смешаны нулевой лист и отсутствие листа
- Следующий ход
- Сохранить конечный тип пути и результат проверяющего кода
Сохраните `chain ID`, заголовок, запрос, ответ и версии проверяющих библиотек. Повторите вычисление другой реализацией, не спрашивая правильный ответ у того же поставщика RPC. Если ячейка хранилища относится к прокси-контракту, сначала докажите разметку и точную позицию. Состояния прокси-контракта и его реализации различаются. `eth_getProof` даёт обычное доказательство дерева Меркла — Патриции. А zero-knowledge proof — доказательство с нулевым разглашением — решает другую задачу.
Ответ нужно отклонить, если
- нет точного хеша блока или доверенного `stateRoot`;
- заголовок и `eth_getProof` относятся к разным блокам либо сетям;
- `accountProof` начинают от `storageRoot`, а `storageProof` — от `stateRoot`;
- пустой массив узлов принимают за доказательство отсутствия без проверки пути;
- архивный RPC считают доказательством сам по себе, не пересчитывая узлы дерева.
Источники
Мы используем прямые ссылки и фиксируем дату проверки. Полный текст чужих материалов не перепечатывается.
- Ethereum Execution APIs 1.0.0-beta.7 — eth_getProof Ethereum Execution APIs · Проверено 25 августа 2026 г. в 00:08 GMT+5
- Execution APIs v1.0.0-beta.7 pinned state method schema Ethereum Execution APIs · Проверено 25 августа 2026 г. в 00:08 GMT+5
- EIP-1186 — RPC method to get Merkle proofs Ethereum Improvement Proposals · Проверено 25 августа 2026 г. в 00:08 GMT+5
- Ethereum Yellow Paper Ethereum · Проверено 25 августа 2026 г. в 00:08 GMT+5
- go-ethereum trie proof implementation go-ethereum · Проверено 25 августа 2026 г. в 00:08 GMT+5
