The Graph 2 构建一个基本的subgraph

这一节我们按照官方示例构建一个简单的subGraph

智能合约

// SPDX-License-Identifier: MIT
pragma solidity >0.4.0;

contract GravatarRegistry {
  event NewGravatar(uint id, address owner, string displayName, string imageUrl);
  event UpdatedGravatar(uint id, address owner, string displayName, string imageUrl);

  struct Gravatar {
    address owner;
    string displayName;
    string imageUrl;
  }

  Gravatar[] public gravatars;

  mapping (uint => address) public gravatarToOwner;
  mapping (address => uint) public ownerToGravatar;

  function createGravatar(string calldata _displayName, string calldata _imageUrl) public {
    require(ownerToGravatar[msg.sender] == 0);
    gravatars.push(Gravatar(msg.sender, _displayName, _imageUrl));
    uint id = gravatars.length - 1;

    gravatarToOwner[id] = msg.sender;
    ownerToGravatar[msg.sender] = id;

    emit NewGravatar(id, msg.sender, _displayName, _imageUrl);
  }

  function getGravatar(address owner) public view returns (string memory, string memory) {
    uint id = ownerToGravatar[owner];
    return (gravatars[id].displayName, gravatars[id].imageUrl);
  }

  function updateGravatarName(string calldata _displayName) public {
    require(ownerToGravatar[msg.sender] != 0);
    require(msg.sender == gravatars[ownerToGravatar[msg.sender]].owner);

    uint id = ownerToGravatar[msg.sender];

    gravatars[id].displayName = _displayName;
    emit UpdatedGravatar(id, msg.sender, _displayName, gravatars[id].imageUrl);
  }

  function updateGravatarImage(string calldata _imageUrl) public {
    require(ownerToGravatar[msg.sender] != 0);
    require(msg.sender == gravatars[ownerToGravatar[msg.sender]].owner);

    uint id = ownerToGravatar[msg.sender];

    gravatars[id].imageUrl =  _imageUrl;
    emit UpdatedGravatar(id, msg.sender, gravatars[id].displayName, _imageUrl);
  }

  // the gravatar at position 0 of gravatars[]
  // is fake
  // it's a mythical gravatar
  // that doesn't really exist
  // dani will invoke this function once when this contract is deployed
  // but then no more
  function setMythicalGravatar() public {
    require(msg.sender == 0xBA8B604410ca76AF86BDA9B00Eb53B65AC4c41AC);
    gravatars.push(Gravatar(address(0x0), " ", " "));
  }
}

以上是官方提供的示例,这里做了些简单的修改,主要是适配了高版本的solidity。核心的方法有三个createGravatar,updateGravatarImage,updateGravatarName。逻辑很简单,就不多加解释了!合约的部署需要读者自行完成。

需求目标

首先确定subgraph所要完成的功能,就是需要支持合约中所有Gravatar的查询,支持字段过滤。合约中并没有提供这么复杂的查询函数。接下来我们通过构建一个简单的subgraph来完成此功能

创建subGraph

首先打开subGraph studio,连结钱包,进入My Subgraphs,点击Create按钮

填写subGraph的基本信息,这里的网络我们选goerli

选择一个分类点击save,这个时候会出现deploy key

本地构建

创建空文件夹,并在文件夹中运行以下命令。

npm install -g @graphprotocol/graph-cli
yarn global add @graphprotocol/graph-cli
graph init --studio subgraph-example

--交互输出如下
√ Protocol · ethereum
√ Subgraph slug · subgraph-example
√ Directory to create the subgraph in · subgraph-example
? Ethereum network ...
? Ethereum network ...
? Ethereum network ...
√ Ethereum network · goerli
√ Contract address · 0x964F658FC863BAceFC719b85e8730fbc11c86ce4
× Failed to fetch ABI from Etherscan: request to https://api-goerli.etherscan.io/api?module=contract&action=getabi&address=0x964F658FC863BAceF
C719b85e8730fbc11c86ce4 failed, reason: connect ETIMEDOUT 69.63.178.13:443
√ ABI file (path) · ./GravatarRegistry.json //需要提前准备好abi
√ Contract Name · Gravatar
√ Index contract events as entities (Y/n) · true

构建的过程有可能会报错

fatal: unable to access 'https://hub.fastgit.org/edgeandnode/gluegun.git/': OpenSSL SSL_connect: SSL_ERROR_SYSCALL in connection to hub.fastgit.org:443

这个时候先进入subgraph-example文件夹,手动install

cd subgraph-example
yarn install

这里还有可能会出现info There appears to be trouble with your network connection. Retrying...的问题,可尝试如下方案

https://www.cnblogs.com/fmixue/p/16375938.html

这里生成了几个关键的文件

subgraph.yaml

subgraph.yaml文件是上一篇概述中提到的subgraph manifest,是subgraph的起点文件,定义了subgraph索引的智能合约,这些合约中需要关注的事件,以及如何将事件数据映射到 Graph 节点存储的实体。具体如下(关键部分做了注释)

specVersion: 0.0.4
description: Gravatar for Ethereum #subgraph的描述。当subgraph部署到托管服务时,Graph Explorer 会显示此描述。
repository: https://github.com/graphprotocol/example-subgraph #subgraph的代码仓库。Graph Explorer会显示
schema:
  file: ./schema.graphql #entities定义所在的文件
dataSources:
  - kind: ethereum/contract
    name: Gravity
    network: mainnet
    source:
      address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'
      abi: Gravity
      startBlock: 6175244    #开始收集数据的区块。这里建议使用创建合约的区块。
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.6
      language: wasm/assemblyscript
      entities: #写入graph存储的实体。每个实体的数据结构在schema.graphql文件中定义。
        - Gravatar
      abis:
        - name: Gravity
          file: ./abis/Gravity.json
      eventHandlers: #对智能合约事件的处理handler,示例中为./src/gravatar-registry.ts—会将这些事件转换为存储中的实体。
        - event: NewGravatar(uint256,address,string,string)
          handler: handleNewGravatar
        - event: UpdatedGravatar(uint256,address,string,string)
          handler: handleUpdatedGravatar
      callHandlers: #对智能合约函数调用的处理handler,此handle可以获取函数的输入参数。
        - function: createGravatar(string,string)
          handler: handleCreateGravatar
      blockHandlers: #当一个新的block产生时调用的handler,如果没有filter,此handler将在每个block中运行。
        - handler: handleBlock
        - handler: handleBlockWithCall
          filter:
            kind: call
      file: ./src/gravatar-registry.ts #handler函数所在的文件位置

本例的subgraph.yaml如下

specVersion: 0.0.5
schema:
  file: ./schema.graphql
dataSources:
  - kind: ethereum
    name: GravatarRegistry
    network: goerli
    source:
      address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"
      abi: GravatarRegistry
    mapping:
      kind: ethereum/events
      apiVersion: 0.0.7
      language: wasm/assemblyscript
      entities:
        - Gravatar
      abis:
        - name: GravatarRegistry
          file: ./abis/GravatarRegistry.json
      eventHandlers:
        - event: NewGravatar(uint256,address,string,string)
          handler: handleNewGravatar
        - event: UpdatedGravatar(uint256,address,string,string)
          handler: handleUpdatedGravatar
      file: ./src/gravatar-registry.ts

schema.graphql

实体的定义文件。在定义实体之前,重要的是要退后一步,思考数据的结构和链接方式。 所有查询都将针对schema.graphql中定义的数据模型和subgraph的索引的实体进行。 因此,最好以符合 dapp 需求的方式定义子图模式。 将实体想象为“包含数据的对象”,而不是事件或函数。

通过命令生成的schema如下:

type NewGravatar @entity(immutable: true) {
  id: Bytes!
  id: BigInt! # uint256
  owner: Bytes! # address
  displayName: String! # string
  imageUrl: String! # string
  blockNumber: BigInt!
  blockTimestamp: BigInt!
  transactionHash: Bytes!
}

type UpdatedGravatar @entity(immutable: true) {
  id: Bytes!
  id: BigInt! # uint256
  owner: Bytes! # address
  displayName: String! # string
  imageUrl: String! # string
  blockNumber: BigInt!
  blockTimestamp: BigInt!
  transactionHash: Bytes!
}

这里的entity是完全按照abi当中定义的事件来生成的,我们需要做一些修改。

按照上述,数据模型应该根据dapp最终展示的结果来定义,而不是完全照搬event,而我们的需求是展示合约中存储的Gravatar,所以并不需要按照create和updated分成两个entity。

还有一个是字段重复的问题,我们观察到自动生成的实体当中包含了两个id字段,原因是每一个实体都要包含一个默认的id字段,而我们的event中也定义了一个id字段,所以重复了,引用官方文档的一段话:

Each entity must have an id field, which must be of type Bytes! or String!. It is generally recommended to use Bytes!, unless the id contains human-readable text, since entities with Bytes! id's will be faster to write and query as those with a String! id. The id field serves as the primary key, and needs to be unique among all entities of the same type. For historical reasons, the type ID! is also accepted and is a synonym for String!.

每个实体必须有一个id字段,它的类型必须是Bytes!或String!通常建议使用Bytes!,除非id包含可读文本。Bytes类型的id会比String类型的id拥有更快的读写速度。id字段作为主键,在相同类型的所有实体中必须是唯一的。由于历史原因,类型ID!也是可以接受的,并且是String!的同义词。

合约里面存储的id是递增的数字,这里先使用String

immutable: true 的意思是当前的实体是不可变的。在本例中,我们需要在处理UpdatedGravatar事件的时候根据id更新存储在graph node上的实体。所以应当设为可变。

修改后的schema.graphql如下:

type Gravatar @entity(immutable: false) {
  id: String!
  owner: Bytes! # address
  displayName: String! # string
  imageUrl: String! # string
  blockNumber: BigInt!
  blockTimestamp: BigInt!
  transactionHash: Bytes!
}

字段类型后面跟!代表非空。

gravatar-registry.ts

import {
  NewGravatar as NewGravatarEvent,
  UpdatedGravatar as UpdatedGravatarEvent
} from "../generated/GravatarRegistry/GravatarRegistry"
import { NewGravatar, UpdatedGravatar } from "../generated/schema"

export function handleNewGravatar(event: NewGravatarEvent): void {
  let entity = new NewGravatar(
    event.transaction.hash.concatI32(event.logIndex.toI32())
  )
  entity.id = event.params.id
  entity.owner = event.params.owner
  entity.displayName = event.params.displayName
  entity.imageUrl = event.params.imageUrl

  entity.blockNumber = event.block.number
  entity.blockTimestamp = event.block.timestamp
  entity.transactionHash = event.transaction.hash

  entity.save()
}

export function handleUpdatedGravatar(event: UpdatedGravatarEvent): void {
  let entity = new UpdatedGravatar(
    event.transaction.hash.concatI32(event.logIndex.toI32())
  )
  entity.id = event.params.id
  entity.owner = event.params.owner
  entity.displayName = event.params.displayName
  entity.imageUrl = event.params.imageUrl

  entity.blockNumber = event.block.number
  entity.blockTimestamp = event.block.timestamp
  entity.transactionHash = event.transaction.hash

  entity.save()
}

由于我们对schema.graphql做了修改,这里的handler需要和schema.graphql里面定义的entity配套使用,所以也要做相应的改动。需要注意的是对id的处理,event里面的id定义为uint,而Gravatar实体里定义的id是String,不可以直接赋值,需要做一下转换,我们编写handler方法时用到的api可以参考AssemblyScript API,修改完成后的代码如下。

import {
  NewGravatar as NewGravatarEvent,
  UpdatedGravatar as UpdatedGravatarEvent
} from "../generated/GravatarRegistry/GravatarRegistry"
import { Gravatar} from "../generated/schema"

export function handleNewGravatar(event: NewGravatarEvent): void {
  let gravatar = new Gravatar(event.params.id.toString());
  gravatar.owner = event.params.owner
  gravatar.displayName = event.params.displayName
  gravatar.imageUrl = event.params.imageUrl

  gravatar.blockNumber = event.block.number
  gravatar.blockTimestamp = event.block.timestamp
  gravatar.transactionHash = event.transaction.hash

  gravatar.save()
}

export function handleUpdatedGravatar(event: UpdatedGravatarEvent): void {
  let id = event.params.id.toString();
  let gravatar = Gravatar.load(id)
  if (gravatar == null) {
    gravatar = new Gravatar(id)
  }
  gravatar.owner = event.params.owner
  gravatar.displayName = event.params.displayName
  gravatar.imageUrl = event.params.imageUrl

  gravatar.blockNumber = event.block.number
  gravatar.blockTimestamp = event.block.timestamp
  gravatar.transactionHash = event.transaction.hash

  gravatar.save()
}

这个时候开始还剩三步,代码生成,编译,和发布,命令如下

graph auth --studio {deploy key}
graph codegen 
graph build

deploy key可以在这里获取

codegen 和build会分别生成各自的文件夹,schema.graphql,subgraph.yaml,gravatar-registry.ts,这几个文件有任何改动都需要运行这两个命令,重新生成代码和编译。

如果上述的命令都成功运行,最后执行yarn deploy命令发布subgraph

验证

最后我们回到subgraph studio刷新一下页面,这个时候会多出来两个table页,选择Playground

进度条会显示同步区块的进度,等区块数据同步完成就可以开始查询了。

也可以做一些过滤查询

graphql的语法参照官方文档,功能还是很强大的,这里就不赘述了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值