Skip to content
Learning Platform
Intermediate
40 minutes
The Graph Subgraph AssemblyScript Graph Node IPFS WASM

Prerequisites:

  • 04-subsquid-erc20-indexer

The Graph: subgraphs и AssemblyScript

Второй инструмент экосистемы

Мы провели 4 урока с Subsquid — теперь знаем, как работает индексация. The Graph — ВТОРОЙ крупнейший инструмент в экосистеме. Он отличается от Subsquid в трёх ключевых аспектах:

  1. AssemblyScript вместо TypeScript
  2. Децентрализованная сеть (GRT token, 500+ indexers)
  3. Monolithic Graph Node вместо модульных компонентов

Давайте разберёмся.

Архитектура Graph Node

Архитектура Graph Node: от блокчейна до API
EVM Node (Anvil)
Graph Node
PostgreSQL
GraphQL Endpoint
dApp / Frontend
IPFS (Kubo v0.17.0)
Subgraph manifests, схемы, WASM
↑ upload subgraph
Graph Node (внутренние компоненты):
Ethereum Adapter
Подключается к RPC
Block Ingestor
Читает блоки
Subgraph Processor
Запускает WASM маппинги
Store Interface
Пишет в PostgreSQL
РесурсоёмкостьGraph Node -- монолитный Rust binary. Он одновременно: читает блокчейн, исполняет WASM-маппинги, хранит данные, отдаёт GraphQL. Ресурсоёмкий (~512MB-1GB RAM), но самодостаточный.

Graph Node — это монолитный Rust binary. Он делает ВСЁ: читает блокчейн, исполняет WASM маппинги, хранит данные в PostgreSQL, отдаёт GraphQL. Мощный, но ресурсоёмкий.

Сравнение с Subsquid

КомпонентSubsquidThe Graph
ПроцессорEvmBatchProcessor (Node.js)Graph Node (Rust binary)
МаппингиTypeScriptAssemblyScript -> WASM
База данныхPostgreSQL (отдельный контейнер)PostgreSQL (отдельный, управляется Graph Node)
GraphQLОтдельный сервер (squid-graphql-server)Встроенный в Graph Node
ДополнительноIPFS (обязателен для хранения артефактов)
RAM (всё вместе)~740MB~1.1-1.8GB

Graph Node требует IPFS (Kubo v0.17.0) для хранения subgraph артефактов (schema, WASM, manifest). Subsquid хранит всё в файловой системе.

Subgraph: три файла

Анатомия subgraph.yaml: манифест индексатора
specVersion: 0.0.5
Версия спецификации
schema:
file: ./schema.graphql
GraphQL схема сущностей
dataSources:
Источники данных (контракты)
- kind: ethereum/contract
Тип: Ethereum контракт
network: localhost
ВАЖНО: должно совпадать с ethereum env в Graph Node
source:
address: "0x5FbDB..."
Адрес контракта
abi: SimpleToken
Имя ABI
startBlock: 1
Начальный блок для индексации
mapping:
language: wasm/assemblyscript
НЕ TypeScript! AssemblyScript -> WASM
entities:
- Transfer
eventHandlers:
Список обработчиков событий
- event: Transfer(indexed address,indexed address,uint256)
handler: handleTransfer
file: ./src/mapping.ts
Файл с обработчиками
Три файла = один subgraphsubgraph.yaml -- описание ЧТО индексировать. mapping.ts -- описание КАК обрабатывать. schema.graphql -- описание КАК хранить. Три файла определяют весь subgraph.

Каждый subgraph состоит из трёх файлов:

ФайлНазначениеАналогия с Subsquid
subgraph.yamlМанифест: ЧТО индексироватьprocessor.ts (setRpcEndpoint, addLog)
schema.graphqlСхема: КАК хранитьschema.graphql (идентично!)
src/mapping.tsМаппинги: КАК обрабатыватьmain.ts (handler)

subgraph.yaml из LAB-07

Разберём манифест нашего SimpleToken subgraph:

specVersion: 0.0.5
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum/contract
    name: SimpleToken
    network: localhost          # ДОЛЖНО совпадать с Graph Node ethereum env!
    source:
      address: '0x5FbDB2315678afecb367f032d93F642f64180aa3'
      abi: SimpleToken
      startBlock: 0
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript  # НЕ TypeScript!
      entities:
        - Transfer
      abis:
        - name: SimpleToken
          file: ./abis/SimpleToken.json
      eventHandlers:
        - event: Transfer(indexed address,indexed address,uint256)
          handler: handleTransfer
      file: ./src/mapping.ts

КРИТИЧНО: network: localhost ДОЛЖНО совпадать с именем в Graph Node env: ethereum: 'localhost:http://anvil:8545'. Если написать network: mainnet, получите ошибку “Ethereum network not supported by registrar: mainnet”. Имя до двоеточия в env == значение network в manifest.

Обратите внимание на language: wasm/assemblyscript. Файл src/mapping.ts имеет расширение .ts, но это НЕ TypeScript. Graph Node компилирует его в WebAssembly.

AssemblyScript: НЕ TypeScript!

AssemblyScript vs TypeScript: ключевые отличия
Аспект
TypeScript (Subsquid)
AssemblyScript (The Graph)
СинтаксисПолный TypeScriptПодмножество TypeScript
Среда выполненияNode.jsWebAssembly (WASM)
async/awaitДаНет
Array.filter/mapДаНет (for loop)
ClosuresДаОграничено
Nullable typesT | nullT | null (stricter)
String operationsПолныеОграничены (no regex)
JSON parsingJSON.parse()Нет встроенного
Entity APITypeORM (store.insert)entity.save()
Типы данныхBigInt, ethers.js typesBigInt, Bytes, Address
Subsquid: обычный TypeScript
// Subsquid: обычный TypeScript
const transfers = events
  .filter(e => e.topic0 === TRANSFER_TOPIC)
  .map(e => new Transfer({ ... }))
await ctx.store.insert(transfers)
Graph: AssemblyScript (НЕ TypeScript!)
// Graph: AssemblyScript (НЕ TypeScript!)
// Нет .filter(), нет .map(), нет await
export function handleTransfer(
  event: TransferEvent
): void {
  let entity = new Transfer(...)
  entity.from = event.params.from
  entity.save()  // Не async!
}
Главное отличиеAssemblyScript ВЫГЛЯДИТ как TypeScript, но это другой язык. Компилируется в WASM для безопасного исполнения в Graph Node. Отсутствие async/await и стандартных Array-методов -- главный источник путаницы.

Это самый важный раздел урока. AssemblyScript ВЫГЛЯДИТ как TypeScript. Файлы имеют расширение .ts. Но это ДРУГОЙ ЯЗЫК, который компилируется в WebAssembly. Многие привычные TypeScript-паттерны НЕ РАБОТАЮТ.

Что НЕ работает в AssemblyScript

Фича TypeScriptAssemblyScriptАльтернатива
async / awaitНетВсе операции синхронные. entity.save() — синхронный
Array.filter()НетИспользуйте for loop с if
Array.map()НетИспользуйте for loop с push
Array.reduce()НетИспользуйте for loop с аккумулятором
Closures (замыкания)ОграниченоИзбегайте захвата переменных в arrow functions
JSON.parse()НетНет встроенного JSON парсинга
console.log()Нетlog.info() из @graphprotocol/graph-ts
try / catchОграниченоОшибки часто вызывают abort() всего WASM модуля
Template literalsОграниченоИспользуйте string concatenation (+)
Object spread (...)НетПрисваивайте поля по одному
DestructuringОграниченоИспользуйте event.params.from напрямую

Маппинг код из LAB-07

// src/mapping.ts -- это AssemblyScript, НЕ TypeScript!
import { Transfer as TransferEvent } from '../generated/SimpleToken/SimpleToken'
import { Transfer } from '../generated/schema'

export function handleTransfer(event: TransferEvent): void {
  // Создание entity с уникальным ID
  let entity = new Transfer(
    event.transaction.hash.concatI32(event.logIndex.toI32())
  )

  // Присвоение полей (НЕ object spread, НЕ destructuring)
  entity.from = event.params.from
  entity.to = event.params.to
  entity.value = event.params.value
  entity.blockNumber = event.block.number
  entity.blockTimestamp = event.block.timestamp
  entity.transactionHash = event.transaction.hash

  // Синхронное сохранение (НЕ await)
  entity.save()
}

Сравнение с Subsquid эквивалентом

// Subsquid: обычный TypeScript
const transfers = logs
  .filter(l => l.topics[0] === TRANSFER_TOPIC)  // .filter() работает!
  .map(l => new Transfer({                        // .map() работает!
    id: l.id,
    from: '0x' + l.topics[1].slice(26),
    to: '0x' + l.topics[2].slice(26),
    value: BigInt(l.data),
    timestamp: new Date(block.header.timestamp),
    blockNumber: block.header.height,
    txHash: l.transactionHash,
  }))
await ctx.store.insert(transfers)  // await работает! Batch insert!

Subsquid: 5 строк с .filter().map() и batch insert. The Graph: 10+ строк с manual field assignment и .save() per entity.

Для простых случаев разница невелика. Для сложных с десятками entities — Subsquid значительно удобнее благодаря полному TypeScript.

Code generation в The Graph

Аналогично Subsquid, The Graph генерирует типы из ABI и schema:

# Генерация типов из ABI и schema
npx graph codegen
# Генерирует:
# - generated/SimpleToken/SimpleToken.ts (event types из ABI)
# - generated/schema.ts (entity types из schema.graphql)

# Сборка WASM
npx graph build
# Компилирует AssemblyScript -> WebAssembly (.wasm файл)

В The Graph codegen объединяет ABI и schema типы в одну команду. В Subsquid — две отдельные команды (squid-typeorm-codegen + squid-evm-typegen).

generated/ директория содержит AssemblyScript-типы. Файлы выглядят как TypeScript, но используют типы из @graphprotocol/graph-ts (Bytes, BigInt, Address — все специфичные для WASM).

Graph Node: запуск через Docker

LAB-07 включает Graph Node стек в профиле graph:

# Запуск Graph Node стека (4 сервиса)
cd labs/LAB-07
docker compose --profile graph up -d

# Проверка: anvil + graph-db + ipfs + graph-node
docker compose --profile graph ps
СервисПортНазначение
anvil8545Локальная Ethereum сеть
graph-db5434PostgreSQL для Graph Node
ipfs5001IPFS Kubo для subgraph артефактов
graph-node8000 (HTTP), 8001 (WS), 8020 (Admin)Graph Node

Ожидание готовности

Graph Node может стартовать 30-60 секунд (должен подключиться к PostgreSQL, IPFS и Ethereum node):

# Проверить логи Graph Node
docker logs graph-node --tail 20

# Искать строку:
# "Starting JSON-RPC server at: http://localhost:8020"
# Это значит -- Graph Node готов принимать subgraph deployments

IPFS: Graph Node требует IPFS (Kubo v0.17.0) для хранения subgraph artifacts. При graph deploy манифест, schema и WASM загружаются в IPFS, а Graph Node скачивает их оттуда. Для локальной разработки IPFS — просто локальное хранилище, без подключения к публичной сети.

Deployment subgraph

Полный процесс deployment описан в INDEX-08. Краткий обзор:

cd subgraph

# 1. Установить зависимости
npm install

# 2. Генерация типов
npx graph codegen

# 3. Сборка WASM
npx graph build

# 4. Создание subgraph в Graph Node
npx graph create --node http://localhost:8020 simple-token

# 5. Deploy
npx graph deploy --node http://localhost:8020 \
  --ipfs http://localhost:5001 simple-token
# Version label: v0.0.1

После deploy Graph Node начинает индексировать блоки с startBlock: 0 и обрабатывать Transfer события.

Алгоритмический уровень

Модель обработки в Graph Node:

GraphNode:
  on new block:
    for each registered subgraph:
      for each event matching subgraph eventHandlers:
        wasm_instance.call(handler_name, event)
        // Handler runs in WASM sandbox
        // entity.save() -> SQL INSERT/UPDATE
    // One event at a time (NOT batched like Subsquid)

ProcessingModel:
  Subsquid: BATCH
    Receive 100 blocks -> process ALL events -> batch INSERT
    Throughput: 1,000-50,000 blocks/sec

  The Graph: SEQUENTIAL
    Receive 1 event -> process -> save -> next event
    Throughput: 100-150 blocks/sec

Ключевое отличие: The Graph обрабатывает события ПОСЛЕДОВАТЕЛЬНО (одно за другим). Subsquid — БАТЧАМИ (сотни за раз). Это главная причина разницы в скорости (50-300x).

Для локальной разработки с десятками блоков разница незаметна. Для mainnet с миллионами блоков — принципиальна.

Итоги

КонцепцияОписание
Graph NodeМонолитный Rust binary (blockchain + WASM + PostgreSQL + GraphQL)
SubgraphТри файла: subgraph.yaml (manifest), schema.graphql, mapping.ts
AssemblyScriptНЕ TypeScript: нет async/await, filter, map, closures
IPFSОбязателен для хранения subgraph артефактов
network nameДОЛЖНО совпадать между manifest и Graph Node env
Sequential processingОдно событие за раз (vs batch в Subsquid)

Что дальше: В INDEX-08 мы задеплоим subgraph на локальный Graph Node, увидим данные через GraphQL, а затем сравним три инструмента (Subsquid, The Graph, SubQuery) по всем ключевым параметрам.

Finished the lesson?

Mark it as complete to track your progress