このセクションでは、公式の例に従って単純なサブグラフを構築します。
スマートコントラクト
// 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的语法参照官方文档,功能还是很强大的,这里就不赘述了。