跳转至主要内容

帮助更新此页面

🌏

本页面有新版本,但现在只有英文版。请帮助我们翻译最新版本。

翻译页面
查看英文

没有错误!🐛

此页面未翻译,因此特意以英文显示。

The Graph:修复Web3数据查询

solidity
智能合约
查询
The Graph
create-eth-app
react
中级
✍Markus Waas
📚soliditydeveloper.com
📆2020年9月6日
⏱️9 分钟阅读

这一次,我们将更仔细地看一下 The Graph,它在去年基本上成为了开发去中心化应用的标准堆栈的一部分。 让我们先看看我们会如何用传统的方式做事…

没有 The Graph...

为了说明起见,让我们举一个简单的例子。 我们都喜欢游戏,所以想象一个用户下注的简单游戏:

1pragma solidity 0.7.1;
2
3contract Game {
4 uint256 totalGamesPlayerWon = 0;
5 uint256 totalGamesPlayerLost = 0;
6 event BetPlaced(address player, uint256 value, bool hasWon);
7
8 function placeBet() external payable {
9 bool hasWon = evaluateBetForPlayer(msg.sender);
10
11 if (hasWon) {
12 (bool success, ) = msg.sender.call{ value: msg.value * 2 }('');
13 require(success, "Transfer failed");
14 totalGamesPlayerWon++;
15 } else {
16 totalGamesPlayerLost++;
17 }
18
19 emit BetPlaced(msg.sender, msg.value, hasWon);
20 }
21}
22
显示全部
📋 复制

现在假设在我们的去中心化应用中,我们想要显示总输/赢游戏数,并在有人再次玩游戏时对其进行更新。 该方法将是:

  1. 获取totalGamesPlayerWon
  2. 获取totalGamesPlayerLost
  3. 订阅BetPlaced事件。

我们可以侦听如右侧所示的Web3 中的事件,但它需要处理相当多的情况。

1GameContract.events.BetPlaced({
2 fromBlock: 0
3}, function(error, event) { console.log(event); })
4.on('data', function(event) {
5 // event fired
6})
7.on('changed', function(event) {
8 // event was removed again
9})
10.on('error', function(error, receipt) {
11 // tx rejected
12});
13
显示全部
📋 复制

现在,对于我们的简单示例来说,这在某种程度上还是不错的。 但是假设我们现在只想显示当前玩家输/赢的赌注金额。 嗯,我们运气不好,您最好部署一份新的合约来存储这些值,并将它们提取出来。 现在想象一下一个更复杂的智能合约和去中心化应用,事情可能很快就会变得一团糟。

一个不简单的查询

您可以看到这并不是最优的:

  • 不适用于已经部署的合约。
  • 存储这些值需要额外的 gas 成本。
  • 需要另一个调用来获取以太坊节点的数据。

是不够好的

现在让我们看看更好的解决方案。

让我向您介绍一下 GraphQL

首先我们来谈谈 GraphQL,它最初是由 Facebook 设计和实现的。 您可能熟悉传统的 Rest API 模型。 现在设想一下,您可以编写一个查询来精确查找您想要的数据:

GraphQL API与REST API

这两张图片基本上抓住了 GraphQL 的精髓。 通过右边的查询,我们可以精确地定义我们想要的数据,这样我们就可以在一个请求中得到所有的东西,而不仅仅是我们需要的东西。 GraphQL 服务器处理所有所需数据的获取,因此前端用户端使用起来非常简单。 如果您感兴趣,这是一个很好的解释,说明服务器是如何处理查询的。

现在有了这些知识,让我们最终进入区块链空间和 The Graph。

什么是 The Graph?

The Graph

如何创建子图

  1. 清单(subgraph.yaml)
  2. 模式(schema.graphql)
  3. 映射(mapping.ts)

清单(subgraph.yaml)

  • 要为哪些智能合约建立索引(地址、网络、ABI...)
  • 侦听哪些事件
  • 其他要侦听的东西,如函数调用或区块
  • 所调用的映射函数(参见下面的 mapping.ts)

为方便起见,您可能还想使用像 mustache 这样的模板工具。 然后您可以创建一个 subgraph.template.yaml,并根据最新的部署插入相应的地址。 有关更高级的示例设置,请参阅这个 Aave subgraph repo 示例。

1specVersion: 0.0.1
2description: Placing Bets on Ethereum
3repository: - GitHub link -
4schema:
5 file: ./schema.graphql
6dataSources:
7 - kind: ethereum/contract
8 name: GameContract
9 network: mainnet
10 source:
11 address: '0x2E6454...cf77eC'
12 abi: GameContract
13 startBlock: 6175244
14 mapping:
15 kind: ethereum/events
16 apiVersion: 0.0.1
17 language: wasm/assemblyscript
18 entities:
19 - GameContract
20 abis:
21 - name: GameContract
22 file: ../build/contracts/GameContract.json
23 eventHandlers:
24 - event: PlacedBet(address,uint256,bool)
25 handler: handleNewBet
26 file: ./src/mapping.ts
27
显示全部

模式(schema.graphql)

您还可以使用实体作为类型来定义关系。 在我们的示例中,我们定义了从玩家到投注的一对多关系。 感叹号(!)

  • 字节
  • ID
  • 字符串
  • 布尔值
  • 整数
  • 大整数
  • 大十进制数

The Graph 中的映射文件定义了将传入事件转换为实体的函数。 它是用 AssemblyScript(TypeScript 的子集)编写的。 这意味着它可以编译成 WASM (WebAssembly),以更高效和可移植的方式执行映射。

1type Bet @entity {
2 id: ID!
3 player: Player!
4 playerHasWon: Boolean!
5 time: Int!
6}
7
8type Player @entity {
9 id: ID!
10 totalPlayedCount: Int
11 hasWonCount: Int
12 hasLostCount: Int
13 bets: [Bet]!
14}
15
显示全部

映射(mapping.ts)

您需要定义 subgraph.yaml 文件中命名的每个函数,因此在我们的例子中,我们只需要一个:handleNewBet。 我们首先尝试将发送者地址中的 Player 实体加载为 id。 如果它不存在,我们创建一个新实体并用起始值填充它。

然后我们创建一个新的 Bet 实体。 它的 id 将是 event.transaction.hash.toHex()+“-”+event.logIndex.toString(),确保值始终唯一。 仅使用哈希是不够的,因为有人可能会通过智能合约在一笔交易中多次调用 placeBet 函数。

最后,我们可以更新 Player 实体的所有数据。 数组不能直接推送,需要按如下所示进行更新。 我们使用 id 来引用投注。

完整的文档可以在这里看到:https://thegraph.com/docs/define-a-subgraph#writing-mappings。 您还可以将日志输出添加到映射文件,请参阅此处

使用 Apollo Boost 之类的工具,您可以轻松地将 The Graph 集成到您的 React 去中心化应用(或 Apollo-Vue)中。 特别是当使用 React hooks 和 Apollo 这样的工具时,获取数据与在组件中写入单个 GraphQl 查询一样简单。

1import { Bet, Player } from "../generated/schema"
2import { PlacedBet } from "../generated/GameContract/GameContract"
3
4export function handleNewBet(event: PlacedBet): void {
5 let player = Player.load(event.transaction.from.toHex())
6
7 if (player == null) {
8 // create if doesn't exist yet
9 player = new Player(event.transaction.from.toHex())
10 player.bets = new Array<string>(0)
11 player.totalPlayedCount = 0
12 player.hasWonCount = 0
13 player.hasLostCount = 0
14 }
15
16 let bet = new Bet(
17 event.transaction.hash.toHex() + "-" + event.logIndex.toString()
18 )
19 bet.player = player.id
20 bet.playerHasWon = event.params.hasWon
21 bet.time = event.block.timestamp
22 bet.save()
23
24 player.totalPlayedCount++
25 if (event.params.hasWon) {
26 player.hasWonCount++
27 } else {
28 player.hasLostCount++
29 }
30
31 // update array like this
32 let bets = player.bets
33 bets.push(bet.id)
34 player.bets = bets
35
36 player.save()
37}
38
显示全部

在前端使用它

现在我们可以编写一个像这样的查询。 这个查询将帮助我们获取如下数据:

1// See all subgraphs: https://thegraph.com/explorer/
2const client = new ApolloClient({
3 uri: "{{ subgraphUrl }}",
4})
5
6ReactDOM.render(
7 <ApolloProvider client={client}>
8 <App />
9 </ApolloProvider>,
10 document.getElementById("root")
11)
12
显示全部
  • 当前用户已经赢得游戏多少次
  • 当前用户已经输了游戏多少次
  • 他之前所有赌注的时间戳清单
1const myGraphQlQuery = gql`
2 players(where: { id: $currentUser }) {
3 totalPlayedCount
4 hasWonCount
5 hasLostCount
6 bets {
7 time
8 }
9 }
10`
11
12const { loading, error, data } = useQuery(myGraphQlQuery)
13
14React.useEffect(() => {
15 if (!loading && !error && data) {
16 console.log({ data })
17 }
18}, [loading, error, data])
19
显示全部

Magic

最简单的方法是使用托管服务。 按照此处的说明来部署子图。

The Graph 服务器

Graph Explorer:托管服务

The Graph-Explorer

运行您自己的节点

GraphQL 也支持新传入事件的流。 The Graph 还没有完全支持这一点,但很快就会发布。

去中心化的未来

然而,一个缺失的方面仍然是去中心化。 The Graph 计划最终成为一个完全去中心化的协议。

  1. 用户将为查询支持索引器费用。
  2. 索引器将权益质押图形通证(GRT)。
上次编辑: , Invalid DateTime
编辑页面

本页面对您有帮助吗?

网站最后更新: 2022年11月29日

使用以太坊

  • 查找钱包
  • 获取 ETH
  • 去中心化应用 (dapps)
  • 第二层
  • 运行一个节点
  • 稳定币
  • 质押以太币

生态系统

  • 社区中心
  • 以太坊基金会
  • 以太坊基金会博客
  • 生态系统支持方案
  • 以太坊漏洞悬赏计划
  • 生态系统资助计划
  • 以太坊品牌资产
  • Devcon