The Graph 2 构建一个基本的subgraph

news/2024/10/19 2:24:32/

这一节我们按照官方示例构建一个简单的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 morefunction 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/contractname: Gravitynetwork: mainnetsource:address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'abi: GravitystartBlock: 6175244    #开始收集数据的区块。这里建议使用创建合约的区块。mapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities: #写入graph存储的实体。每个实体的数据结构在schema.graphql文件中定义。- Gravatarabis:- name: Gravityfile: ./abis/Gravity.jsoneventHandlers: #对智能合约事件的处理handler,示例中为./src/gravatar-registry.ts—会将这些事件转换为存储中的实体。- event: NewGravatar(uint256,address,string,string)handler: handleNewGravatar- event: UpdatedGravatar(uint256,address,string,string)handler: handleUpdatedGravatarcallHandlers: #对智能合约函数调用的处理handler,此handle可以获取函数的输入参数。- function: createGravatar(string,string)handler: handleCreateGravatarblockHandlers: #当一个新的block产生时调用的handler,如果没有filter,此handler将在每个block中运行。- handler: handleBlock- handler: handleBlockWithCallfilter:kind: callfile: ./src/gravatar-registry.ts #handler函数所在的文件位置

本例的subgraph.yaml如下

specVersion: 0.0.5
schema:file: ./schema.graphql
dataSources:- kind: ethereumname: GravatarRegistrynetwork: goerlisource:address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"abi: GravatarRegistrymapping:kind: ethereum/eventsapiVersion: 0.0.7language: wasm/assemblyscriptentities:- Gravatarabis:- name: GravatarRegistryfile: ./abis/GravatarRegistry.jsoneventHandlers:- event: NewGravatar(uint256,address,string,string)handler: handleNewGravatar- event: UpdatedGravatar(uint256,address,string,string)handler: handleUpdatedGravatarfile: ./src/gravatar-registry.ts

schema.graphql

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

通过命令生成的schema如下:

type NewGravatar @entity(immutable: true) {id: Bytes!id: BigInt! # uint256owner: Bytes! # addressdisplayName: String! # stringimageUrl: String! # stringblockNumber: BigInt!blockTimestamp: BigInt!transactionHash: Bytes!
}type UpdatedGravatar @entity(immutable: true) {id: Bytes!id: BigInt! # uint256owner: Bytes! # addressdisplayName: String! # stringimageUrl: String! # stringblockNumber: 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! # addressdisplayName: String! # stringimageUrl: String! # stringblockNumber: 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.identity.owner = event.params.ownerentity.displayName = event.params.displayNameentity.imageUrl = event.params.imageUrlentity.blockNumber = event.block.numberentity.blockTimestamp = event.block.timestampentity.transactionHash = event.transaction.hashentity.save()
}export function handleUpdatedGravatar(event: UpdatedGravatarEvent): void {let entity = new UpdatedGravatar(event.transaction.hash.concatI32(event.logIndex.toI32()))entity.id = event.params.identity.owner = event.params.ownerentity.displayName = event.params.displayNameentity.imageUrl = event.params.imageUrlentity.blockNumber = event.block.numberentity.blockTimestamp = event.block.timestampentity.transactionHash = event.transaction.hashentity.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.ownergravatar.displayName = event.params.displayNamegravatar.imageUrl = event.params.imageUrlgravatar.blockNumber = event.block.numbergravatar.blockTimestamp = event.block.timestampgravatar.transactionHash = event.transaction.hashgravatar.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.ownergravatar.displayName = event.params.displayNamegravatar.imageUrl = event.params.imageUrlgravatar.blockNumber = event.block.numbergravatar.blockTimestamp = event.block.timestampgravatar.transactionHash = event.transaction.hashgravatar.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的语法参照官方文档,功能还是很强大的,这里就不赘述了。


http://www.ppmy.cn/news/248238.html

相关文章

The Graph 3 subGraph的callHandler,blockhandler,实体关系和全文索引

上一节我们基于官方示例构建了一个具有基本功能的subgraph,这一节我们介绍其他的一些特性。 callHandler 虽然eventHandler提供了一种有效的方法来收集合约状态的相关更改,但许多合约避免生成event以优化gas成本。在这些情况下,subgraph可以订…

2022年4月3日-4月15日(方案A,ogremain源码抄写+ue4视频学习,共22小时,合计1270小时,剩8730小时)

截至2022年4月1日,ogreMain剩下4533行(含注释),纯代码2646行 周二时学完了ue第五套视频教程编辑器1,good 接下来 UE4视频教程进行到了mysql(1.1),tf1(2.1),oss(4.2),simpleThread(1.2),editor2(未开始) 清…

企业综合安防管理平台

企业综合安防管理平台 平台概述 长期以来各厂家以市场为导向,专注于具备自身特长的单一系统产品,造成目前在技防领域出现的众多分项系统各自为政的局面,如单一的视频监控系统、门禁系统、访客系统、停车管理系统、报警系统等。各厂家单一业务…

nginx+tomcat 负载均衡、动静分离集群

文章目录 一、NginxTomcat负载均衡的组合原因1.1 Nginx实现负载均衡的原理1.2 Nginx实现负载均衡的主要配置项1.3 NginxTomcat负载均衡的组合的优点1.4 NginxTomcat负载均衡的实验设计 二、动静分离部署2.1 部署TOMCAT后端服务器2.2部署nginx服务器2.3安装nginx动态服务器 一、…

关于酷派8730“移动版”手机无法将应用安装在外置SD卡的解决办法和获取ROOT权限方法

酷派8730“移动版”手机的rom版本号是“039”,具体可通过输入*#9527*#,进入工厂模式查看。 039版是无法将应用安装在外置SD卡里的,解决办法是升级至049版;即从039-->048-->049,下面是具体做法: 1.在…

Oracle Database 23c新特性之CASE语句/表达式增强

Oracle database 23c 改进了 PL/SQL 程序中的简单 CASE 语句和 CASE 表达式,支持悬空谓词(dangling predicate)和单个 WHEN 分支中的多项匹配。这个增强是为了更加符合 SQL 标准。 示例表 本文将会使用以下简单示例表: drop ta…

java中try-with-resources自动关闭io流

在传统的输入输出流处理中,我们一般使用的结构如下所示,使用try - catch - finally结构捕获相关异常,最后不管是否有异常,我们都将流进行关闭处理: try {//todo } catch (IOException e) {log.error("read xxx f…