Использование The Graph на Moonbeam

Moonbeam in Russian
8 min readMay 3, 2021

--

Вступление

Протоколы индексирования структурируют информацию таким образом, чтобы приложения могли получить к ней доступ наиболее эффективным способом. Например, 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.

--

--

Moonbeam in Russian
Moonbeam in Russian

Written by Moonbeam in Russian

Moonbeam — это совместимая с Ethereum платформа смарт-контрактов на Polkadot

No responses yet