Использование The Graph на Moonbeam
Вступление
Протоколы индексирования структурируют информацию таким образом, чтобы приложения могли получить к ней доступ наиболее эффективным способом. Например, Google выполняет индексацию всей сети Интернет, чтобы быстро предоставлять информацию, когда Вы что-то ищете.
The Graph — это децентрализованный протокол индексации с открытым исходным кодом для выполнения запросов к таким сетям, как Ethereum.
Простыми словами, он обеспечивает способ эффективного хранения данных, генерируемых событиями из смарт-контрактов, чтобы другие проекты или приложения могли легко получить к ним доступ.
Кроме того, разработчики могут создавать API, именуемые как “subgraph(ы)”.
Пользователи или другие разработчики могут использовать “subgraph(ы)” для запроса данных, относящихся к набору смарт-контрактов. Данные извлекаются с помощью стандартных API запросов с использованием — GraphQL. Вы можете посетить страницу с официальной документацией, чтобы узнать больше о протоколе The Graph.
С внедрением модулей трассировки Ethereum в Moonbase Alpha v7, The Graph может индексировать данные блокчейна в Moonbeam.
В этом руководстве мы рассмотрим процесс создания простого “subgraph(a)” для контракта “Lottery” на Moonbase Alpha.
Предварительная проверка
У вас есть два способа, чтобы использовать The Graph на Moonbase Alpha:
- Запустить Graph узел c Moonbase Alpha настроив свой “subgraph” ссылкой на него.
- Привязать свой “subgraph” к Graph API через веб-сайт Graph Explorer. Для этого вам необходимо создать учетную запись и получить ключ доступа.
Lottery контракт
В качестве примера мы рассмотрим использование простого Lottery контракта. Вы можете найти файл Solidity используя эту ссылку.
В контракте проводится лотерея, где игроки могут купить билеты для себя или подарить их другому пользователю. По прошествию 1-го часа, если набралось 10 участников, следующий игрок, который присоединяется к лотерее, выполнит функцию, которая выберет победителя. Все средства, хранящиеся в контракте, будут отправлены победителю, после чего начинается новый раунд.
Основные функции контракта:
joinLottery — нет вхождений. Функция для входа в текущий раунд лотереи, значение (количество токенов), отправленное на контракт, должно быть равной цене билета.
giftTicket — одно вхождение: адрес получателя билета. Аналогично joinLottery, но для владельца билета можно указать другой адрес.
enterLottery — одно вхождение: адрес владельца билета. Внутренняя функция, которая обрабатывает логику лотерейных билетов. Если прошел час и есть не менее 10 участников, вызывается функция pickWinner.
pickWinner — нет вхождений. Внутренняя функция, которая выбирает победителя лотереи с помощью генератора псевдослучайных чисел (небезопасно, только для демонстрационных целей). Он обрабатывает логику перевода средств и сброса переменной для следующего раунда лотереи.
События лотерейного контракта
The Graph использует события, генерируемые контрактом, для индексации данных. Контракт лотереи включает в себя только два события:
PlayerJoined — вызывается в функции enterLottery. Он предоставляет информацию, которая относится к последней лотерее, такую как адрес игрока, текущий раунд лотереи, был ли подарен билет и сумма приза в текущем раунде.
LotteryResult — вызывается в функции pickWinner. Он предоставляет информацию о розыгрыше текущего раунда, такую как адрес победителя, текущий раунд лотереи, был ли выигрышный билет подарком, сумму приза и отметку времени розыгрыша.
Создание Subgraph
В этом разделе рассматривается процесс создания subgraph(a). Для subgraph лотереи был подготовлен репозиторий GitHub со всем необходимым, чтобы помочь Вам начать работу. Репозиторий включает контракт лотереи, а также файл конфигурации Hardhat и сценарий развертывания. Если Вы не знакомы с ним, Вы можете ознакомиться с нашим руководством по интеграции Hardhat.
Для начала клонируйте репозиторий и установите необходимые зависимости:
git clone https://github.com/PureStake/moonlotto-subgraph \
&& cd moonlotto-subgraph && yarn
Теперь Вы можете создать типы TypeScript для The Graph, выполнив:
npx graph codegen --output-dir src/types/
Примечание: Для создания типов необходимо, чтобы файлы ABI были определены в файле subgraph.yaml
. В этом экземпляре репозитория уже присутствует файл, но обычно он создается после компиляции контракта. Дополнительную информацию можно найти в репозитории Moonlotto.Вы можете выполнить команду codegen
с помощью yarn codegen
.
В этом примере контракт был развернут на 0x44ddD2EC5BE2A7f3e4A465C21600bE8df644093f
. Вы можете найти дополнительную информацию о том, как развернуть контракт с Hardhat, в нашем руководстве по интеграции. Кроме того, файл «README» в репозитории Moonloto содержит шаги, необходимые для компиляции и развертывания контракта, если это потребуется.
Основная структура Subgraphs
В общих чертах, subgraphs определяют данные, которые The Graph будет индексировать из цепочки блоков, и способ их хранения. Subgraphs, как правило, содержат следующие файлы:
- subgraph.yaml — это YAML файл, который содержит манифест subgraph(а), т.е информацию, относящуюся к смарт-контрактам, индексируемым subgraph(ом)
- schema.graphql — это файл схемы GraphQL, который определяет хранилище данных для создаваемого subgraph и его структуру. Он написан с использованием схемы определения интерфейса GraphQL.
- AssemblyScript mappings — код в TypeScript (скомпилированный в AssemblyScript), который используется для преобразования данных событий из контракта к субъектам, определенным в схеме.
При изменении файлов для создания subgraph нет определенного порядка.
Schema.graphql
Перед изменением schema.graphql
. важно подчеркнуть, какие данные необходимо извлечь из событий контракта. Схемы необходимо определять с учетом требований самого dApp. В этом примере, хотя приложение dApp не связано с лотереей, определены четыре объекта:
- Round — относится к розыгрышу лотереи. В нем хранится индекс раунда, присужденный приз, отметка времени начала раунда, отметка времени, когда был выбран победитель, и информация об участвующих билетах, полученная от объекта
Ticket
. - Player — относится к игроку, который участвовал хотя бы в одном раунде. Он хранит свой адрес и информацию обо всех участвующих билетах, полученную от объекта
Ticket
. - Ticket — относится к билетам для участия в розыгрыше лотереи. Он хранит информацию о том, был ли билет подарен, адрес владельца, раунд, с которого билет действителен, и был ли это выигрышный билет.
Одним словом, schema.graphql
должна выглядеть следующим образом:
type Round @entity {
id: ID!
index: BigInt!
prize: BigInt!
timestampInit: BigInt!
timestampEnd: BigInt
tickets: [Ticket!] @derivedFrom(field: "round")
}
type Player @entity {
id: ID!
address: Bytes!
tickets: [Ticket!] @derivedFrom(field: "player")
}
type Ticket @entity {
id: ID!
isGifted: Boolean!
player: Player!
round: Round!
isWinner: Boolean!
}
Subgraph Манифест
Файл subgraph.yaml
или манифест Subgraph содержит информацию, относящуюся к индексируемому смарт-контракту, включая события, которые содержат данные, необходимые для сопоставления. Эти данные затем сохраняются на Graph узлах, что позволяет приложениям запрашивать их.
Некоторые из наиболее важных параметров в файле subgraph.yaml
:
- repository — относится к репозиторию Github subgraph
- schema/file — ссылается на расположение файла
schema.graphql
- dataSources/name — относится к имени subgraph
- network — относится к имени сети. Это значение должно быть установлено в
mbase
для любого subgraph , развертываемого в Moonbase Alpha. - dataSources/source/address — относится к адресу интересующего договора
- dataSources/source/abi — указывает, где хранится интерфейс контракта внутри папки
types
, созданной с помощью командыcodegen
- dataSources/source/startBlock — относится к начальному блоку, с которого начнется индексация. В идеале это значение должно быть как можно ближе к блоку, в котором был создан контракт. Вы можете использовать Blockscout, чтобы получить эту информацию, указав адрес контракта. В этом примере контракт был создан на блоке
132605
. - dataSources/mapping/file — указывает на расположение файла сопоставления
- dataSources/mapping/entity — относится к определениям сущностей в файле
schema.graphql
- dataSources/abis/name — указывает, где интерфейс контракта хранится внутри
types/dataSources/name
- dataSources/abis/file — относится к месту, где хранится файл
.json
с ABI контракта. - dataSources/eventHandlers — здесь нет необходимости определять значение, но этот раздел относится ко всем событиям, которые Graph будет индексировать.
- dataSources/eventHandlers/event — относится к структуре события, которое будет отслеживаться внутри контракта. Вам необходимо указать название события и тип его переменных.
- dataSources/eventHandlers/handler — относится к имени функции внутри файла
mapping.ts
, который обрабатывает данные события.
Вкратце, subgraph.yaml
должен выглядеть как следующий фрагмент:
specVersion: 0.0.2
description: Moonbeam lottery subgraph tutorial
repository: https://github.com/PureStake/moonlotto-subgraph
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: MoonLotto
network: mbase
source:
address: '0x44ddD2EC5BE2A7f3e4A465C21600bE8df644093f'
abi: MoonLotto
startBlock: 132605
mapping:
kind: ethereum/events
apiVersion: 0.0.4
language: wasm/assemblyscript
file: ./src/mapping.ts
entities:
- Player
- Round
- Ticket
- Winner
abis:
- name: MoonLotto
file: ./artifacts/contracts/MoonLotto.sol/MoonLotto.json
eventHandlers:
- event: PlayerJoined(uint256,address,uint256,bool,uint256)
handler: handlePlayerJoined
- event: LotteryResult(uint256,address,uint256,bool,uint256,uint256)
handler: handleLotteryResult
Сопоставления
Файлы сопоставлений — это то, что преобразует данные цепочки блоков в объекты, которые определены в файле схемы. Каждый обработчик событий внутри файла subgraph.yaml
должен содержать вспомогательные функции сопоставления.
Файл сопоставления, используемый для примера Lottery, можно найти по этой ссылке.
В целом, стратегия каждой функции-обработчика состоит в том, чтобы загрузить данные о событии, проверить, существует ли уже запись, разместить данные по желанию и сохранить запись. Например, функция-обработчик для события PlayerJoined
выглядит следующим образом:
export function handlePlayerJoined(event: PlayerJoined): void {
// ID for the round:
// round number
let roundId = event.params.round.toString();
// try to load Round from a previous player
let round = Round.load(roundId);
// if round doesn't exists, it's the first player in the round -> create round
if (round == null) {
round = new Round(roundId);
round.timestampInit = event.block.timestamp;
}
round.index = event.params.round;
round.prize = event.params.prizeAmount;
round.save();
// ID for the player:
// issuer address
let playerId = event.params.player.toHex();
// try to load Player from previous rounds
let player = Player.load(playerId);
// if player doesn't exists, create it
if (player == null) {
player = new Player(playerId);
}
player.address = event.params.player;
player.save();
// ID for the ticket (round - player_address - ticket_index_round):
// "round_number" + "-" + "player_address" + "-" + "ticket_index_per_round"
let nextTicketIndex = event.params.ticketIndex.toString();
let ticketId = roundId + "-" + playerId + "-" + nextTicketIndex;
let ticket = new Ticket(ticketId);
ticket.round = roundId;
ticket.player = playerId;
ticket.isGifted = event.params.isGifted;
ticket.isWinner = false;
ticket.save();
}
Развертывание Subgraph
Если Вы собираетесь использовать Graph API (размещенный сервис), вам необходимо:
- Создать учетную запись Graph Explorer, для этого Вам понадобится учетная запись Github
- Зайдите в личный кабинет для получения “access token”
- Создайте свой subgraph с помощью кнопки «Add Subgraph» на сайте Graph Explorer. Запишите название Subgraph
Примечаение: Все шаги можно найти по этой ссылке.
Если Вы используете локальный узел Graph, Вы можете создать свой subgraph, выполнив следующую команду:
npx graph create <username>/<subgraphName> --node <graph-node>
Где:
- username — относится к имени пользователя, относящемуся к создаваемому subgraph(y)
- subgraphName — относится к имени subgraph(a)
- graph-node — ссылка на URL-адрес для управления сервисом. Обычно для локального графического узла используется адрес is
http://127.0.0.1:8020
.
После создания Вы можете развернуть свой subgraph, выполнив следующую команду с теми же параметрами, что и раньше:
npx graph deploy <username>/<subgraphName> \
--ipfs <ipfs-url> \
--node <graph-node> \
--access-token <access-token>
Где:
username — относится к имени пользователя, используемому при создании subgraph
subraphName — относится к имени subgraph, определенному при создании subgraph
ifps-url — относится к URL-адресу IFPS. Если Вы используете Graph API, Вы можете использовать https://api.thegraph.com/ipfs/
. Для вашего локального узла Graph значение по умолчанию — http://localhost:5001
.
graph-node — относится к URL-адресу размещенной службы для использования. Если Вы используете Graph API, Вы можете использовать https://api.thegraph.com/deploy/
. Для вашего локального узла Graph значение по умолчанию — http://localhost:8020
.
access-token — относится к токену доступа для использования API Graph. Если Вы используете локальный Graph Node, этот параметр не нужен.
Журнал из предыдущей команды должны выглядеть примерно так:
DApps теперь могут использовать конечные точки Subgraph для извлечения данных, проиндексированных протоколом The Graph.
Мы хотим услышать Ваше мнение
Если у Вас есть какие-либо отзывы о создании subgraph(a) в Moonbeam или любой другой теме, связанной с Moonbeam, не стесняйтесь обращаться через наш официальный канал разработки в Discord.
Подготовлено при участии: AntonM, Lyn.