グラフ 2 は基本的なサブグラフを構築します

このセクションでは、公式の例に従って単純なサブグラフを構築します。

スマートコントラクト

// 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 の 3 つがあります。ロジックは非常に簡単なので、詳しくは説明しません。コントラクトの展開は読者が行う必要があります。

需要目標

まず、サブグラフによって完了する関数を決定します。つまり、コントラクト内のすべての Gravatar のクエリをサポートし、フィールド フィルタリングをサポートする必要があります。この契約では、そのような複雑なクエリ機能は提供されていません。次に、単純なサブグラフを構築することでこれを実現します。

サブグラフの作成

まずsubGraph スタジオを開き、ウォレットをリンクし、「My Subgraphs」と入力して、「作成」ボタンをクリックします。

subGraph の基本情報を入力します。ここではネットワークとして goerli を選択します。

カテゴリを選択して [保存] をクリックすると、この時点でデプロイ キーが表示されます。

ローカルで構築する

空のフォルダーを作成し、そのフォルダー内で次のコマンドを実行します。

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 フォルダーに入り、手動でインストールします。

cd subgraph-example
yarn install

情報の問題も考えられます。ネットワーク接続に問題があるようです。再試行中...、次の解決策をお試しください。

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

ここでいくつかのキーファイルが生成されます

サブグラフ.yaml

subgraph.yaml ファイルは、前の概要で説明したサブグラフ マニフェストであり、サブグラフの開始点ファイルであり、サブグラフ インデックスのスマート コントラクト、これらのコントラクトで注意する必要があるイベント、およびマッピング方法を定義します。イベント データをグラフ ノードに格納されているエンティティに送信します。詳細は次のとおりです(重要な部分に注釈を付けています)

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

スキーマ.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的语法参照官方文档,功能还是很强大的,这里就不赘述了。

おすすめ

転載: blog.csdn.net/gambool/article/details/128673435