|
| 1 | +## Graph 介绍 |
| 2 | + |
| 3 | +编写智能合约时,通常状态的变化是通过触发一个事件来表达,The Graph 则是捕捉区块链事件并提供一个查询事件的 GraphQL 接口,让我们可以方便的跟踪数据的变化。实际上很多 defi 协议都是 The Graph 来基于查询数据。 |
| 4 | + |
| 5 | +## 流程概述 |
| 6 | + |
| 7 | +- 在 Rinkeby 部署一个合约,并调用触发事件。 |
| 8 | +- 创建定义数据索引的 Subgraph。 |
| 9 | +- 部署 Subgraph 到 TheGraph,实现数据索引。 |
| 10 | +- 在前端 DApp 中查询索引数据。 |
| 11 | + |
| 12 | +如果你有自己的私有链,这可以克隆 Graph 节点代码 https://github.com/graphprotocol/graph-node/ 自己运行 Graph 节点来完成数据的索引。 |
| 13 | + |
| 14 | +TheGraph 中定义如何为数据建立索引,称为 Subgraph,它包含三个组件: |
| 15 | + |
| 16 | +- Manifest 清单(subgraph.yaml) - 定义配置项 |
| 17 | +- Schema 模式(schema.graphql) - 定义数据 , 参考文档 https://graphql.cn/learn/ |
| 18 | +- Mapping 映射(mapping.ts) - 定义事件到数据的转换 |
| 19 | + |
| 20 | +## 操作步骤 |
| 21 | + |
| 22 | +1. 安装相关依赖 |
| 23 | + |
| 24 | + ```bash |
| 25 | + yarn install |
| 26 | + ``` |
| 27 | + |
| 28 | +2. 配置私钥 |
| 29 | + |
| 30 | + 为方便获取,在 .env 中放入的私钥,格式为 "PRIVATE_KEY=xxxx", 然后代码自动从中读取<br> |
| 31 | + 另外需要设置你的 infura 节点 id,在 .env 中放入的私钥,格式为 "INFURA_ID=xxxx" |
| 32 | + |
| 33 | +3. 部署合约(用于测试 graph 的简单合约) |
| 34 | + |
| 35 | + ```bash |
| 36 | + npx hardhat run ./scripts/deploy.js --network rinkeby |
| 37 | + ``` |
| 38 | + |
| 39 | + 输出信息类似如下: |
| 40 | + |
| 41 | + ```bash |
| 42 | + Deploying contracts with the account: xxxxxxxxxxxxxx |
| 43 | + Account balance: 10000000000000000000000 |
| 44 | + Token address: 0x5FbDB2315678afecb367f032d93F642f64180aa3 |
| 45 | + Transfer 50 to receiver 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 |
| 46 | + Account balance of receiver is: 50 |
| 47 | + ``` |
| 48 | + |
| 49 | +4. TheGraph 创建一个 Subgraph 空间 |
| 50 | + |
| 51 | + 因为需要借助 TheGraph 的节点来完成数据的索引,因此我们需要在 [TheGraph Studio](https://thegraph.com/studio/) 上创建一个 Subgraph。 |
| 52 | + |
| 53 | + 如果没有 The Graph 的账户,可以直接连接钱包注册,账户名即为钱包地址,以下称之为 `<THEGRAPH_USERNAME>`。 |
| 54 | + |
| 55 | + 批准钱包签名之后,会跳转到 `My Subgraphs` 面板,点击 `Create a Subgraph` 按钮。 |
| 56 | + <center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/08-hardhat-graph/create_subgraph_btn.png?raw=true" /></center> |
| 57 | + |
| 58 | + 输入你的项目名称(例如 TEST01),以下称之为 `<SUBGRAPH_NAME>`,点击 continue 按钮,之后会跳转到 subgraph 的项目主页 |
| 59 | + |
| 60 | + 注:最新版的 Graph CLI 仅支持在 mainnet 和 rinkeby 上部署,若要在其他网络上使用,需要使用 Github 账户登录后在 Hosted Service 上创建和部署 |
| 61 | + |
| 62 | +5. 开发和部署 subgraph |
| 63 | + |
| 64 | + 先使用 yarn 在全局安装 Graph CLI |
| 65 | + |
| 66 | + ```bash |
| 67 | + yarn global add @graphprotocol/graph-cli |
| 68 | + ``` |
| 69 | + |
| 70 | +6. 初始化配置: |
| 71 | + |
| 72 | + ```bash |
| 73 | + graph init --studio <SUBGRAPH_NAME> |
| 74 | + ``` |
| 75 | + |
| 76 | + 若使用 Hosted Service,则初始化命令如下: |
| 77 | + |
| 78 | + ```bash |
| 79 | + graph init --product hosted-service <GITHUB_USER>/<SUBGRAPH NAME> |
| 80 | + ``` |
| 81 | + |
| 82 | + - 在 "Subgraph name" 和 "Directory to create the subgraph" 直接回车即可 |
| 83 | + - Ethereum network 这里选择 rinkeby |
| 84 | + - "Contract address" 这里输入在步骤 3 中部署合约时生成的合约地址 |
| 85 | + - 上面执行到 "fetch ABI from Etherscan" 时会报执行失败,然后出现 "ABI file (path)" 字样,提示输入本机中 abi 的文件路径,这里我们输入 SimpleToken.json 所在的路径即可(`./abis/SimpleToken.json`) |
| 86 | + - 如果 yarn install 失败(例如网络错误),可以进入新生成的项目目录,手动安装 npm 依赖 |
| 87 | + |
| 88 | +7. 修改定义模式 |
| 89 | + |
| 90 | + - 两个文件的修改范例在 `./scripts/schema.graphql` 和 `./scripts/mapping.ts` |
| 91 | + |
| 92 | + - `<SUBGRAPH_NAME>/schema.graphql` 修改文件内容如下 |
| 93 | + |
| 94 | + ```graphql |
| 95 | + type TransferEntity @entity { |
| 96 | + id: ID! |
| 97 | + from: Bytes! # address |
| 98 | + to: Bytes! # address |
| 99 | + value: BigInt! |
| 100 | + } |
| 101 | + |
| 102 | + type ApprovalEntity @entity { |
| 103 | + id: ID! |
| 104 | + owner: Bytes! # address |
| 105 | + spender: Bytes! # address |
| 106 | + value: BigInt! |
| 107 | + } |
| 108 | + ``` |
| 109 | + |
| 110 | + - `<SUBGRAPH_NAME>/src/mapping.ts` 修改文件内容如下 |
| 111 | + |
| 112 | + ```ts |
| 113 | + import { BigInt } from '@graphprotocol/graph-ts'; |
| 114 | + import { SimpleToken, Transfer, Approval } from '../generated/SimpleToken/SimpleToken'; |
| 115 | + import { TransferEntity, ApprovalEntity } from '../generated/schema'; |
| 116 | + |
| 117 | + export function handleTransfer(event: Transfer): void { |
| 118 | + // Entities can be loaded from the store using a string ID; this ID |
| 119 | + // needs to be unique across all entities of the same type |
| 120 | + let entity = TransferEntity.load(event.transaction.from.toHex()); |
| 121 | + |
| 122 | + // Entities only exist after they have been saved to the store; |
| 123 | + // `null` checks allow to create entities on demand |
| 124 | + if (entity == null) { |
| 125 | + entity = new TransferEntity(event.transaction.from.toHex()); |
| 126 | + } |
| 127 | + |
| 128 | + // BigInt and BigDecimal math are supported |
| 129 | + entity.value = event.params.value; |
| 130 | + |
| 131 | + // Entity fields can be set based on event parameters |
| 132 | + entity.from = event.params.from; |
| 133 | + entity.to = event.params.to; |
| 134 | + |
| 135 | + // Entities can be written to the store with `.save()` |
| 136 | + entity.save(); |
| 137 | + |
| 138 | + // Note: If a handler doesn't require existing field values, it is faster |
| 139 | + // _not_ to load the entity from the store. Instead, create it fresh with |
| 140 | + // `new Entity(...)`, set the fields that should be updated and save the |
| 141 | + // entity back to the store. Fields that were not set or unset remain |
| 142 | + // unchanged, allowing for partial updates to be applied. |
| 143 | + |
| 144 | + // It is also possible to access smart contracts from mappings. For |
| 145 | + // example, the contract that has emitted the event can be connected to |
| 146 | + // with: |
| 147 | + // |
| 148 | + // let contract = Contract.bind(event.address) |
| 149 | + // |
| 150 | + // The following functions can then be called on this contract to access |
| 151 | + // state variables and other data: |
| 152 | + // |
| 153 | + // - contract.approve(...) |
| 154 | + // - contract.totalSupply(...) |
| 155 | + // - contract.transferFrom(...) |
| 156 | + // - contract.increaseAllowance(...) |
| 157 | + // - contract.balanceOf(...) |
| 158 | + // - contract.decreaseAllowance(...) |
| 159 | + // - contract.transfer(...) |
| 160 | + // - contract.allowance(...) |
| 161 | + } |
| 162 | + |
| 163 | + export function handleApproval(event: Approval): void { |
| 164 | + // Entities can be loaded from the store using a string ID; this ID |
| 165 | + // needs to be unique across all entities of the same type |
| 166 | + let entity = ApprovalEntity.load(event.transaction.from.toHex()); |
| 167 | + |
| 168 | + // Entities only exist after they have been saved to the store; |
| 169 | + // `null` checks allow to create entities on demand |
| 170 | + if (entity == null) { |
| 171 | + entity = new ApprovalEntity(event.transaction.from.toHex()); |
| 172 | + } |
| 173 | + |
| 174 | + // BigInt and BigDecimal math are supported |
| 175 | + entity.value = event.params.value; |
| 176 | + |
| 177 | + // Entity fields can be set based on event parameters |
| 178 | + entity.owner = event.params.owner; |
| 179 | + entity.spender = event.params.spender; |
| 180 | + |
| 181 | + // Entities can be written to the store with `.save()` |
| 182 | + entity.save(); |
| 183 | + } |
| 184 | + ``` |
| 185 | + |
| 186 | +8. 修改实体名字 |
| 187 | + |
| 188 | + - 进入 graphtest 目录 |
| 189 | + - 修改 subgraph.yaml 中 entities 定义如下 |
| 190 | + |
| 191 | + ```yaml |
| 192 | + --- |
| 193 | + entities: |
| 194 | + - TransferEntity |
| 195 | + - ApprovalEntity |
| 196 | + ``` |
| 197 | + |
| 198 | +9. 授权和部署 Subgraph |
| 199 | + |
| 200 | + 首先获取你的 `<DEPLOY KEY>`,在你的 subgraph 项目主页可以找到: |
| 201 | + <center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/08-hardhat-graph/auth_deploy_key.png?raw=true" /></center> |
| 202 | + |
| 203 | + - 授权 |
| 204 | + |
| 205 | + ```bash |
| 206 | + graph auth --studio <DEPLOY KEY> |
| 207 | + ``` |
| 208 | + |
| 209 | + 若使用 Hosted Service,则初始化命令如下: |
| 210 | + |
| 211 | + ```bash |
| 212 | + graph auth --product hosted-service <ACCESS_TOKEN> |
| 213 | + ``` |
| 214 | + |
| 215 | + - 进入 subgraph 的本地目录 |
| 216 | + |
| 217 | + ```bash |
| 218 | + cd ./<SUBGRAPH_NAME> |
| 219 | + ``` |
| 220 | + |
| 221 | + - BUILD SUBGRAPH |
| 222 | + |
| 223 | + ```bash |
| 224 | + graph codegen && graph build |
| 225 | + ``` |
| 226 | + |
| 227 | + - DEPLOY SUBGRAPH |
| 228 | + |
| 229 | + ```bash |
| 230 | + graph deploy --studio <SUBGRAPH_NAME> |
| 231 | + ``` |
| 232 | + |
| 233 | + 若使用 Hosted Service,则初始化命令如下: |
| 234 | + |
| 235 | + ```bash |
| 236 | + graph deploy --product hosted-service <GITHUB_USER>/<SUBGRAPH NAME> |
| 237 | + ``` |
| 238 | + |
| 239 | + - 这里必须输入 `Version Label` , 比如`0.0.1`, 否则会报错提示 `You must provide a version label.` |
| 240 | + |
| 241 | +## 检验 subgraph 是否部署成功 |
| 242 | + |
| 243 | +从 subgraphs 面板进入你的 subgraph 项目主页, 查看索引进度,当进度 100%可以开始调用。 |
| 244 | + |
| 245 | +这里已经预生成了一个示例请求,点击播放按钮即可请求数据。至此 subgraph 部署成功 |
| 246 | + |
| 247 | +<center><img src="https://github.com/Dapp-Learning-DAO/Dapp-Learning-Arsenal/blob/main/images/basic/08-hardhat-graph/query_subgraph.png?raw=true" /></center> |
| 248 | + |
| 249 | +## Graph Node 本地搭建 |
| 250 | + |
| 251 | +1. 搭建 graph-node |
| 252 | + 出于便捷的考虑,我们使用官方提供的 docker compose 来进行节点、数据库、IPFS 的部署。 |
| 253 | + |
| 254 | +- 克隆 graph node( https://github.com/graphprotocol/graph-node/ )代码 |
| 255 | +- 进入 docker 目录 |
| 256 | +- 将 docker-compose.yml 中 ethereum 字段的值改为需要连接链的节点连接信息。 |
| 257 | + |
| 258 | +注意:如果是最新的 mac(big sur 系统),在安装 docker 的时候,不能使用 brew cask install docker 命令,具体原因参考链接:https://www.jianshu.com/p/50037be9c00d |
| 259 | + |
| 260 | +```yaml |
| 261 | +graph-node: |
| 262 | + image: graphprotocol/graph-node |
| 263 | + ports: |
| 264 | + - '8000:8000' |
| 265 | + - '8001:8001' |
| 266 | + - '8020:8020' |
| 267 | + - '8030:8030' |
| 268 | + - '8040:8040' |
| 269 | + depends_on: |
| 270 | + - ipfs |
| 271 | + - postgres |
| 272 | + environment: |
| 273 | + postgres_host: postgres |
| 274 | + postgres_user: graph-node |
| 275 | + postgres_pass: let-me-in |
| 276 | + postgres_db: graph-node |
| 277 | + ipfs: 'ipfs:5001' |
| 278 | + ethereum: 'mainnet:http://127.0.0.1:8545' #此处的mainnet需要和subgraph.yml里network对应上 |
| 279 | + # ethereum: 'dev:https://rinkeby.infura.io/v3/INFURA_ID' # 也可以连测试网络 |
| 280 | + RUST_LOG: info |
| 281 | +``` |
| 282 | + |
| 283 | +> 注意: graph-node 连接的节点需要开启 archive 模式(启动节点时,添加 flag --syncmode full --gcmode archive)。 |
| 284 | + |
| 285 | +2. graph-node 启动 |
| 286 | + |
| 287 | +直接使用 docker compose 来进行启动 |
| 288 | + |
| 289 | +```bash |
| 290 | +docker-compose -f docker-compose.yml up -d |
| 291 | +``` |
| 292 | + |
| 293 | +3. 编译 subgraph |
| 294 | + 进入 subgraph 的本地目录运行下列命令 |
| 295 | + |
| 296 | + 由于在前一步骤执行过命令 npx hardhat run ./scripts/deploy.js --network rinkeby |
| 297 | + |
| 298 | + 因此,此处修改 subgraph.yaml,修改内容如下: |
| 299 | + |
| 300 | +```bash |
| 301 | +dataSources: |
| 302 | + - kind: ethereum/contract |
| 303 | + name: SimpleToken |
| 304 | + network: rinkeby |
| 305 | +
|
| 306 | +``` |
| 307 | + |
| 308 | +```bash |
| 309 | +graph codegen --output-dir src/types/ |
| 310 | +graph build |
| 311 | +``` |
| 312 | + |
| 313 | +4. 部署 subgraph |
| 314 | + |
| 315 | +```bash |
| 316 | +graph create davekaj/SimpleToken --node http://127.0.0.1:8020 |
| 317 | +
|
| 318 | +graph deploy davekaj/SimpleToken --debug --ipfs http://localhost:5001 --node http://127.0.0.1:8020 |
| 319 | +``` |
| 320 | + |
| 321 | +5. 可以使用 GraphQL 来进行查询数据。 |
| 322 | + |
| 323 | +## subgraph |
| 324 | + |
| 325 | +subgraph 定义了你希望通过 GraphQL API 提供的数据、数据源和数据访问模式。开发者可以选择直接使用别人已经部署[17]的 subgraph,或者自己定义并部署 subgraph。 |
| 326 | + |
| 327 | +1. GraphQL Schema |
| 328 | + GraphQL Schema 定义了你想保存和查询的数据类型/实体。也可定义如关系或全文搜索的配置项。 |
| 329 | +2. subgraph 清单( yaml 配置) |
| 330 | + manifest 定义了 subgraph 索引的智能合约、合约的 ABI、关注这些合约的事件,以及如何将事件数据映射到 Graph 节点存储并允许查询。 |
| 331 | +3. AssemblyScript 映射 |
| 332 | + AssemblyScript 映射允许您使用 schema 中定义的实体类型保存要索引的数据。Graph CLI 还使用 schema 与智能合约的 ABI 的组合生成 AssemblyScript 类型。 |
| 333 | +4. 通过@derivedFrom 建立关系 |
| 334 | + 通过@derivedFrom 字段在实体上定义反向查询,这样就在实体上创建了一个虚拟字段,使它可以被查询,但不能通过映射 API 手动设置。实际上,这是从另一个实体上定义的关系中衍生出来的。这样的关系,对存储关系的两者意义不大,如果只存储一方而派生另一方,则索引和查询性能都会更好。 |
| 335 | + |
| 336 | +## 参考文档 |
| 337 | + |
| 338 | +官方文档: |
| 339 | + |
| 340 | +- https://thegraph.com/docs/developer/quick-start |
| 341 | + |
| 342 | +本项目参考文档: |
| 343 | + |
| 344 | +- https://mp.weixin.qq.com/s/DlC5jAS_CzXuOZFmmveNXA |
| 345 | +- https://mp.weixin.qq.com/s/LhdAREmhXSHxIaVfhcJQ_g |
| 346 | +- https://dev.to/dabit3/building-graphql-apis-on-ethereum-4poa |
| 347 | +- https://learnblockchain.cn/article/2566 |
| 348 | +- https://blog.openzeppelin.com/subgraphs-announcement |
| 349 | + OpenZeppelin subgraphs 库: 为常用的 OpenZepplin 合约建立 subgraphs |
| 350 | +- https://github.com/graphprotocol/agora |
| 351 | + 成本模型 |
| 352 | +- Subgraph 选择指南(分析节点成本,收益以及应该索引哪些 Subgraph): |
| 353 | + <https://wavefive.notion.site/The-Graph-Protocol-Indexer-Subgraph-Selection-Guide-725f6e575f6e4024ad7e50f2f4e9bbad> |
| 354 | + |
| 355 | +其他相关参考文档: |
| 356 | + |
| 357 | +- https://thegraph.com/ |
| 358 | +- https://graphql.cn/learn/ |
| 359 | +- https://gql-guide.vercel.app/ |
| 360 | +- https://thegraph.com/docs/graphql-api |
| 361 | + GraphGen——命令行工具,用于快速生成子图,由一些有 GraphGen 命令注释的 Solidity 接口文件组成。 |
| 362 | +- https://medium.com/protean-labs/introducing-graphgen-a-subgraph-generator-for-the-graph-network-836fe0385336 |
| 363 | + |
| 364 | +- Matchstick ——是 Limechain 做一个开发的单元测试框架,一个 graph 模拟节点,用于在沙盒环境中测试子图部署的映射逻辑 |
| 365 | + 相关教程:https://limechain.tech/blog/matchstick-what-it-is-and-how-to-use-it/ |
0 commit comments