Как получать данные блокчейна в реальном времени используя сабграфы
Кирилл Балахонов долгое время работал в сфере обработки данных, руководил разными проектами, связанными с машинным обучением, ИИ, в СБЕРе и еще в нескольких больших компаниях, несколько лет занимался data science, обработкой данных, после чего перешел на web3. Сейчас занимается развитием продуктов в компании Chainstack.
О компании Chainstack
Довольно большая, по меркам web3, компания — в ней работает больше 100 человек из 35 стран мира. Основная сфера услуг — запуск блокчейн-нод для клиентов разных блокчейнов. Клиенты компании — в основном разработчики децентрализованных приложений. В отличие от прямых конкурентов, компания представлена за пределами США и поддерживает пять облачных провайдеров и десять локаций по всему миру. Работая с девелоперами, компания старается расширять продуктовую линейку в сторону создания новых инструментов для web3-разработки. Об одном из таких инструментов, о блокчейн-индексаторе, — этот доклад.
Проблема web3-разработки
Представьте, что вы разрабатываете web3 dApp и вам нужны данные прямо из блокчейна. Эти данные, при этом, должны быть актуальными, но доступ должен быть и к историческим данным. Кроме того, приложениям удобно, чтобы доступ был по API.
Как правило, разработчики добывают данные следующим образом: берется архивная нода, к ней подключается индексатор, который пробегает по всему блокчейну и собирает нужные для dApp данные. Сначала индексатор проводит подготовительный анализ всего блокчейна, сохраняет данные в базу данных (базы данных могут быть разные, разные системы хранилища). И далее эти данные становятся доступными по API для использования в приложении. Помимо подготовительного анализа, индексатор должен поддерживать свои функции онлайн/риалтайм: поддерживать актуальность данных, обновляя информацию после каждого нового блока в блокчейне.

Проблема данных в смарт-контрактах
Смарт-контракты представлены в виде байт-кода, поэтому, чтобы вытащить от туда данные необходим ключ для их расшифровки. Это называется ABI — application binary interface. ABI включает названия методов смарт-контракта, входные и выходные переменные и так далее. Без ABI данные выглядят просто как байт-код.
Кроме этого, необходимо решить проблему: как понять, где именно нужные данные лежать в блокчейне? Необходимо проверить весь блокчейн от первого блока до последнего, чтобы найти транзакции с упоминанием вашего смарт-контракта.
Также необходимо иметь архивные ноды. Можно использовать два типа нод: фул ноды, в которых хранится состояние блокчейна, но не хранятся предыдущие транзакции; архивные хранят все, что есть в блокчейне. Логично, что объем хранилища для архивной ноды — на порядки выше чем для фул ноды, что гораздо дороже.
Тут есть помощники:
- готовые решения: NFT API, Token API и т.д. — они могут помочь, если в вашем приложении нужно работать со стандартизированными данными (ERC20, 721 и т.д.).
- Есть также платформы с готовыми данными: Dune, Nansen, Footprint. У этих сервисов есть серьезные недостатки: нет удобного интерфейса, данные приходят с задержкой.
- Еще одним помощником являются custom indexing solutions: The Graph, Subsquid, Nakji, Kenshi и т.д.
Разработчики могут создать свое решение, но нужно понимать, что подобные сервисы требуют достаточно рутинного поддержания их в строе, чтобы всегда были актуальные данные.

Про The Graph и Hosted
Так вышло, что самым популярным решением для индексации данных в блокчейнах оказался централизованный сервис Hosted. У этого проекта есть цель создать децентрализованную сеть The Graph, когда вы платите за запрос в токенах на публичной permissionless DeFi-платформе. Но пока они ее разрабатывают, решили запустить полностью централизованный сервис, доступный для всех под открытой лицензией бесплатно. Децентрализованная версия будет стоить токенов, которые нужно будет правильным образом застейкать и потратить на привлечение индексаторов и оплату каждого отдельного запроса. К тому же, у децентрализованной версии есть ограничение — она работает только на Ethereum. После запуска децентрализованной сети, централизованную бесплатную версию хотят отключить.

Практическая часть: как сделать свой индексатор при помощи The Graph
На сайте Hosted можно использовать комьюнити-сабграфы.
Сабграф — индексатор данных, который пробегает по блокчейну с первого по последний блок, отправляя данные в API для dApp.
На странице сабграфов можно найти информацию: когда он был создан, количество строчек в базе данных, когда он был обновлен, на какой сети развернут и т.д. Также из UI можно сделать запрос на языке Graph QL и посмотреть результат.
Также есть эндпойнт, который можно использовать в коде своего dApp.
Как создать свой граф: nstal, init, deploy
Нужно залогиниться через Github, перейти на сайте в My Dashboards со списком сабграфов, где можно создать новый. При создании нужно указать имя. После этого открывается страничка сабграфа, которого еще не существует — код еще не отправлен. Появилась удобная подсказка следующих шагов.
-
Установить пакет для управления сабграфом, интерфейсы.
-
Дальше можно запустить команду graph unit, перейти в командную строку и запустить команду.
-
Утилита спросит, под какую сеть мы создаем сабграф. (Я выбираю Goerli — тестнет Ethereum, в котором можно бесплатно получить для тестирования нативные токены, чтобы не тратиться на комиссии).
-
Имя и директорию не меняем.
-
Дальше пишем адрес смарт-контракта, который мы будем использовать в dApp. (Для примера я использую смарт-контракт — форк ERC20-токена в Goerli. Этот контракт можно увидеть в кошельке Metamask вместе с токенами, которые к нему относятся. Адрес смарт-контракта находится во вкладке details. Адреса Goerli смарт-контрактов проверяются при помощи Goerli Etherscan).

-
Утилита проверяет ABI смарт-контракта на Etherscan. Эту строку можно вставить вручную.

-
Утилита спрашивает: индексировать ли ивенты, которые происходили в смарт-контракте в качестве таблиц данных. Эта функция очень удобная — позволяет запустить свой сабграф первый раз вообще не написав ни строчки кода.
-
Сгенерированный сабграф можно отправлять на сервис Hosted. Все основные события смарт-контракта (вроде transfer, approve) будут сохранены.
-
Утилита спрашивает: нужно ли добавить еще один смарт-контракт. При ответе «нет» приводятся следующие шаги: run graph auth to authenticate with your deploy key / type cd tempsubgraphs321 to enter the subgraph /run yarn deploy to deploy the subgraph.
-
Проверяем, что мы сделали. Открываем снова редактор кода, ищем папку с сабграфом. В этой папке важны всего три файла:
-
subgraph.yami — манифест сабграфа, где хранятся его параметры, а также информация, в какой сети он запущен, адрес смарт-контракта, расположение ABI, события, код dai-token, описывающий логику извлечения данных, где находится схема, описывающая логику хранения файлов.
-
schema.graphql — тут описаны таблички, в которых будут храниться индексируемые данные. Таблички называются также, как и ивенты. Если в байт-коде показаны обычно всего три параметра (owner, spender, amount), то в табличке показано больше: +blockNumber, blockcTimestamp, transactionHash и уникальный id.
-
Еще один важный файл находится в папке src — код на type script, который описывает, как добывать данные из блокчейна. Тут мы видим две функции, которые относятся к событиям, которые мы уже встречали (transfer, approve). Тут можно дописать код, чтобы приспособить API под различные задачи.

-
Сабграф создан (напомню, что для этого мы запустили всего одну команду и больше ничего). Теперь возвращаемся на страницу с подсказками и смотрим, что делать дальше.

-
Команды: graph codegen и graph build
Копируем код из подсказки, вставляем и смотрим, что вышло. Эти команды нужны, если мы вносили изменения в сабграф на этом этапе.
-
Последний шаг перед полноценным запуском сабграфа — deploy.

-
Перед этим нужно пройти аутентификацию, чтобы платформа знала, кто ей отправляет сабграф.
-
Копируем и вставляем код команды graph deploy. Граф отправляется на платформу.
-
Можно указать индексатору, с какого блока смотреть блокчейн (например, начиная с блока, в котором мы создали контракт). Чтобы индексатор не делал лишнюю работу просчитывая все исторические данные блокчейна с момента его запуска. Номер нужного блока смотрим в Etherscan и вставляем в манифест сабграфа: startblock: и после двоеточия добавляем нужное значение блока.
-
Проверяем параметр «last updated», чтобы убедиться, что мы задеплоили сабграф и он находится на стадии синхронизации — наполнения данными. По мере синхронизации, данные будут загружаться и будут сразу доступны по API.
Как индексировать балансы адресов
С развитием вашего приложения, скорее всего вы поймете, что данных от такого индексатора вам недостаточно. Например, мы хотим посмотреть баланс какого-то адреса. Это, на самом деле, очень затратная операция по времени и другим ресурсам. Выход — хранить эти данные сразу в «правильном» формате: сохранять то, что нужно, а не то, что мы можем по дефолту.
Модифицируем код, чтобы индексатор собирал балансы:
-
Создаем еще одну табличку «Balance» для хранения балансов. Поля: id — адрес, value — стоимость транзакции.

-
Переходим в файл с обработкой ивентов и создаем там переменную «balance». Баланс может обновляться, поэтому и логика будет тут сложнее. При каждом обновлении добавляется строчка с новым параметром value.
-
Нужно отметить, что баланс будет обновляться и у отправителя, и у получателя. Поэтому код разделен визуально на две части. Для создания баланса отправителя создаем объекта из класса «balance», где в качестве id указан отправитель.
-
Проверяем, был ли уже такой отправитель в базе данных. Если не был, то нужно создать нового. Тут value уменьшается (так как он отправил токены, а не получил). То есть мы ищем его value в системе и уменьшаем его на то value, которое он отправил.
-
Для получателя происходит все то же самое, только наоборот: value прибавляется.
-
Далее заходим в subgraph.yami и дописываем там Balance.
-
Еще раз деплоим сабграф на Hosted. Чекаем изменения в интерфейсе на сайте: можно посмотреть балансы и изменения в блокчейне в риалтайм.

-
Кстати, смарт-контракты EVM-подобных сетей легко адаптировать к другим EVM-подобным сетям. Например, чтобы перевести наш индексатор из Goerli в Ethereum, надо поменять значение
networkнаmainnet. Этого достаточно, чтобы переключиться на другую сеть.
