Блокчейн

Индексация: The Graph и субграфы

Ethereum генерирует около 1 миллиона событий в сутки: Transfer, Swap, Mint, Burn, Deposit. Каждое - запись в логах транзакции, доступная через Bloom-фильтр. Но попробуйте запросить «все свопы пользователя на Uniswap за последний год» напрямую через RPC - и вы получите таймаут, счёт на сотни долларов от Alchemy и пустую страницу вместо dApp. Проблема в том, что Ethereum-нода хранит данные в формате, оптимизированном для валидации блоков, а не для аналитических запросов. The Graph переворачивает эту модель: протокол заранее обрабатывает каждый блок, раскладывает события по сущностям и предоставляет GraphQL API, отвечающий за миллисекунды. Как именно subgraph превращает поток сырых логов в структурированную базу данных? Как обработать события от тысяч контрактов, адреса которых неизвестны заранее? И почему даже с The Graph вам всё ещё нужен кеш для RPC-вызовов?

  • **Uniswap Analytics** - dashboard Uniswap (info.uniswap.org) показывает объёмы торгов, TVL, топ-пулы в реальном времени. Все эти данные поступают из subgraph, который индексирует Swap, Mint и Burn события тысяч пулов. Без The Graph каждый посетитель dashboard генерировал бы миллионы RPC-запросов
  • **Aave и lending-протоколы** - когда вы видите процентные ставки, объёмы кредитов и историю ликвидаций на Aave, это агрегированные данные из subgraph. Block handlers обновляют ставки каждый блок, event handlers отслеживают каждый Deposit, Borrow и Liquidation
  • **OpenSea и NFT-маркетплейсы** - история продаж, рейтинги коллекций, floor price - всё индексируется из event logs контрактов Seaport. Без индексации поиск «все продажи BAYC за последний месяц» занял бы часы вместо миллисекунд

Предварительные знания

  • Transactions and Receipts in Ethereum

The Graph Protocol и субграфы

Представьте, что вам нужно показать историю всех свопов пользователя на Uniswap. Вызывать `eth_getLogs` для миллионов блоков? Это часы ожидания и терабайты трафика. **The Graph** решает эту проблему: протокол создаёт **предындексированные базы данных** из блокчейн-событий, к которым можно обращаться через GraphQL за миллисекунды.

**Subgraph** - это единица индексации в The Graph. Каждый subgraph определяет, какие контракты слушать, какие события обрабатывать и как преобразовывать данные в структурированные сущности. Subgraph состоит из трёх файлов:

Протокол The Graph работает как **децентрализованная сеть** участников, каждый из которых выполняет свою роль, а координация происходит через токен **GRT**:

Subgraph в протоколе The Graph состоит из трёх ключевых компонентов. Какой из них определяет, КАКИЕ контракты и события слушать?

GraphQL-запросы к блокчейн-данным

Когда subgraph развёрнут и индексатор обработал все блоки, данные доступны через **GraphQL API**. В отличие от REST, где каждый endpoint возвращает фиксированную структуру, GraphQL позволяет клиенту запрашивать именно те поля и связи, которые нужны. Для блокчейн-данных это критично: один и тот же subgraph может обслуживать и dashboard с агрегированной статистикой, и детальный explorer транзакций.

GraphQL в The Graph поддерживает удобные механизмы фильтрации, пагинации и работы с вложенными сущностями:

The Graph использует **cursor-based пагинацию** через поле `id`. Максимум `first: 1000` сущностей за запрос. Для получения всех данных - итерируйте с `where: { id_gt: "последний_полученный_id" }`. Offset-пагинация (`skip`) ограничена 5000 и неэффективна для больших датасетов.

Одна из уникальных возможностей The Graph - **time-travel queries**: запрос данных на определённый блок. Это позволяет получить состояние сущностей в прошлом, что критично для аналитики и аудита:

Почему GraphQL превосходит REST API для блокчейн-данных? Сравним подходы на примере задачи «показать dashboard DeFi-протокола»:

dApp хочет получить состояние ликвидности пула Uniswap V3 на момент блока #18,000,000 (три месяца назад). Какой механизм The Graph это позволяет?

Индексация событий и маппинги

Сердце subgraph - это **mapping-обработчики**: функции на AssemblyScript, которые вызываются при каждом совпадающем событии. Graph Node сканирует блоки, декодирует event logs через ABI и передаёт структурированные данные в обработчик. Задача маппинга - преобразовать «сырое» блокчейн-событие в сущности для хранения.

Помимо event handlers, The Graph поддерживает **call handlers** (вызовы функций) и **block handlers** (каждый блок). Каждый тип решает свою задачу:

Отдельная задача - индексация **динамических контрактов**. Например, Uniswap Factory создаёт новые пулы через `createPool()`. Адреса этих пулов неизвестны на момент деплоя subgraph. Для этого The Graph предоставляет **Data Source Templates**:

**Chain reorgs** (реорганизации цепочки) - серьёзная проблема для индексаторов. Если два валидатора одновременно создают блок на одной высоте, один из блоков будет отброшен (uncle/ommer). Graph Node автоматически обрабатывает reorgs: откатывает сущности до последнего стабильного блока и переиндексирует. Для критически важных данных проверяйте глубину подтверждения: на Ethereum 64 блока (~13 минут) считаются финализированными.

Uniswap V3 Factory создаёт тысячи пулов с уникальными адресами. Как subgraph индексирует события (Swap, Mint) из контрактов, адреса которых неизвестны на момент деплоя?

Кеширование RPC и альтернативные индексаторы

The Graph решает задачу индексации событий, но dApp-ы обращаются к блокчейну не только за историческими данными. Вызовы `eth_call` (чтение state контракта), `eth_getBalance`, `eth_blockNumber` - всё это **RPC-запросы** к ноде, каждый из которых стоит денег и добавляет задержку. Грамотное **кеширование** этих запросов - разница между dApp, который загружается за 200ms, и тем, который висит 5 секунд.

Ключевая оптимизация - **Multicall** (батчинг нескольких `eth_call` в один RPC-запрос). Вместо 50 отдельных вызовов `balanceOf()` для 50 токенов, dApp отправляет один вызов контракта Multicall3, который исполняет все 50 вызовов внутри EVM и возвращает результаты одной транзакцией:

The Graph - не единственное решение для индексации. Экосистема предлагает инструменты, оптимизированные под разные задачи:

**CDN для статических данных**: метаданные токенов (name, symbol, decimals), ABI контрактов, списки токенов (token lists) - всё это неизменяемые данные, которые стоит раздавать через CDN (Cloudflare, Fastly). Один `eth_call` для `name()` стоит ~0.0001 на Alchemy, а CDN-ответ - бесплатен. Для крупных dApp-ов экономия достигает тысяч долларов в месяц.

The Graph полностью заменяет RPC-вызовы к ноде - если у dApp есть subgraph, прямые обращения к ноде не нужны

The Graph индексирует только event logs (и опционально call/block данные) - он не заменяет eth_call для чтения текущего state контракта. Для получения актуального баланса, allowance или результата view-функции по-прежнему нужен RPC-вызов к ноде. Subgraph и RPC дополняют друг друга: subgraph - для исторических и агрегированных данных, RPC - для текущего state

Это заблуждение возникает из-за смешения двух типов данных: event-driven (исторические события, агрегаты) и state-driven (текущее состояние контракта). The Graph обрабатывает первое. Когда пользователь хочет увидеть свой текущий баланс USDC - это eth_call к контракту, а не запрос к subgraph. Когда нужна история всех его трансферов за год - это GraphQL-запрос к subgraph, а не сканирование блоков через RPC.

dApp вызывает eth_call для чтения balanceOf(Alice) на конкретном блоке #18,000,000 (не latest). Какой TTL корректен для кеширования этого результата?

Итоги

  • **Subgraph = schema + manifest + mappings**: schema.graphql описывает сущности, subgraph.yaml указывает контракты и события, AssemblyScript-маппинги преобразуют сырые event logs в структурированные данные. Децентрализованная сеть The Graph координирует индексаторов через токен GRT
  • **GraphQL вместо REST** для блокчейн-данных: один запрос с точными полями, вложенными сущностями и фильтрами заменяет десятки REST-вызовов. Time-travel queries позволяют запрашивать состояние на конкретном блоке - уникальная возможность для аналитики и аудита
  • **Три типа обработчиков**: event handlers (90% случаев - реагируют на event logs), call handlers (перехватывают вызовы функций через trace), block handlers (срабатывают на каждый блок для регулярных снапшотов). Data Source Templates решают проблему динамических контрактов (Factory pattern)
  • **Кеширование - многослойное**: immutable данные (конкретный блок) кешируются навсегда, latest-данные - на 12 секунд (один блок). Multicall батчит десятки eth_call в один RPC-запрос. Альтернативные индексаторы (Ponder, Envio, Goldsky) предлагают TypeScript, скорость и SQL-запросы
  • В начале мы спросили: как показать историю свопов за год, не положив RPC-ноду? Ответ - предварительная индексация. Subgraph обрабатывает каждый блок один раз, превращая поток событий в базу данных с GraphQL API. RPC - для текущего state, subgraph - для исторических и агрегированных данных. Вместе они покрывают все потребности dApp

Связанные темы

Индексация блокчейн-данных связывает механику транзакций, ноды, стандарты токенов и DeFi-протоколы:

  • Транзакции и receipts — Event logs из transaction receipts - сырые данные, которые индексирует The Graph. Bloom-фильтры позволяют быстро отсеивать блоки без нужных событий, а receipt trie гарантирует их целостность
  • Ноды: full, light, archive — Graph Node подключается к archive-ноде для доступа к историческим данным. Full node хранит state только текущего блока, archive - всех блоков. Time-travel queries требуют archive-ноду
  • Стандарты токенов (ERC-20, ERC-721) — Стандартизированные события Transfer и Approval из ERC-20/ERC-721 - самые индексируемые в экосистеме Ethereum. Subgraphs Uniswap, OpenSea, Aave строятся вокруг этих событий
  • AMM: автоматический маркет-мейкинг — Subgraphs DeFi-протоколов индексируют Swap, Mint, Burn события AMM-пулов. Агрегированные данные (TVL, объёмы, цены) вычисляются в маппингах и доступны через GraphQL

Вопросы для размышления

  • The Graph Network требует стейкинг GRT от индексаторов. Как это влияет на надёжность данных? Что произойдёт, если индексатор вернёт некорректные результаты на GraphQL-запрос?
  • Почему The Graph выбрал AssemblyScript (компилируемый в WASM) для маппингов, а не JavaScript или Python? Какие преимущества и ограничения это создаёт? Как Ponder и Envio обходят это решение?
  • Вы проектируете dApp, которому нужен текущий баланс токена (eth_call), история трансферов за год (event logs) и цена токена в реальном времени (обновляется каждый блок). Какую комбинацию инструментов (subgraph, RPC, кеш, multicall) вы выберете для каждой задачи и почему?

Связанные уроки

  • db-09-indexes-btree
Индексация: The Graph и субграфы

0

1

Войти