跳转到主要内容

The Graph:修复 Web3 数据查询

Solidity
智能合约
查询
the graph
React
中级
马库斯·瓦斯
2020年9月6日
12 分钟阅读

这次我们将深入探讨 The Graph,它在过去一年中基本上已成为开发去中心化应用 (dapp) 的标准技术栈的一部分。让我们首先看看传统方式是如何处理的……

如果没有 The Graph……

为了方便说明,我们来看一个简单的例子。我们都喜欢游戏,所以想象一个用户可以下注的简单游戏:

现在假设在我们的 dapp 中,我们想要显示总下注数、输赢的总局数,并在有人再次玩游戏时更新这些数据。传统的方法将是:

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

如右图所示,我们可以监听 Web3 中的事件 (opens in a new tab),但这需要处理相当多的情况。

对于我们这个简单的例子来说,这还算可以接受。但假设我们现在只想显示当前玩家输赢的下注金额。那我们就倒霉了,你最好部署一个新合约来存储这些值并获取它们。现在想象一个复杂得多的智能合约和 dapp,事情很快就会变得一团糟。

One Does Not Simply Query

你可以看出这并不是最优解:

  • 对已经部署的合约不起作用。
  • 存储这些值需要额外的 Gas 成本。
  • 需要对以太坊节点进行另一次调用来获取数据。

Thats not good enough

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

让我向你介绍 GraphQL

首先我们来谈谈 GraphQL,它最初由脸书设计和实现。你可能熟悉传统的 REST API 模型。现在想象一下,你可以准确地为你想要的数据编写查询:

GraphQL API vs. REST API

Animated demonstration of a GraphQL query in The Graph playground

这两张图片很好地捕捉了 GraphQL 的本质。通过右侧的查询,我们可以准确定义我们想要的数据,因此我们在一个请求中获得了所有内容,并且不多不少正是我们需要的。GraphQL 服务器处理所有所需数据的获取,因此前端消费者使用起来极其简单。如果你感兴趣,这里有一个很好的解释 (opens in a new tab),说明了服务器究竟是如何处理查询的。

现在有了这些知识,让我们终于进入区块链领域和 The Graph。

什么是 The Graph?

区块链是一个去中心化的数据库,但与通常情况相反,我们没有用于这个数据库的查询语言。检索数据的解决方案要么令人痛苦,要么完全不可能。The Graph 是一个用于索引和查询区块链数据的去中心化协议。你可能已经猜到了,它使用 GraphQL 作为查询语言。

The Graph

例子总是理解事物的最佳方式,所以让我们在 GameContract 示例中使用 The Graph。

如何创建子图

关于如何索引数据的定义被称为子图。它需要三个组件:

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

清单 (subgraph.yaml)

清单是我们的配置文件,它定义了:

  • 要索引哪些智能合约(地址、网络、ABI 等)
  • 要监听哪些事件
  • 要监听的其他内容,如函数调用或区块
  • 被调用的映射函数(见下文的 mapping.ts

你可以在这里定义多个合约和处理程序。一个典型的设置是在 Hardhat 项目中有一个带有自己代码库的子图文件夹。这样你就可以轻松引用 ABI。

为了方便起见,你可能还想使用像 mustache 这样的模板工具。然后你创建一个 subgraph.template.yaml 并根据最新的部署插入地址。有关更高级的示例设置,请参阅 Aave 子图代码库 (opens in a new tab)

完整的文档可以在这里 (opens in a new tab)查看。

模式 (schema.graphql)

模式是 GraphQL 数据定义。它将允许你定义存在哪些实体及其类型。The Graph 支持的类型有:

  • Bytes
  • ID
  • String
  • Boolean
  • Int
  • BigInt
  • BigDecimal

你也可以使用实体作为类型来定义关系。在我们的例子中,我们定义了从玩家到下注的一对多关系。! 表示该值不能为空。完整的文档可以在这里 (opens in a new tab)查看。

映射 (mapping.ts)

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

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

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

最后,我们可以用所有数据更新 Player 实体。数组不能直接推送,而是需要像这里显示的那样进行更新。我们使用 ID 来引用下注。并且在最后需要 .save() 来存储实体。

完整的文档可以在这里查看:https://thegraph.com/docs/en/developing/creating-a-subgraph/#writing-mappings。你也可以在映射文件中添加日志输出,请参见[这里 (opens in a new tab)](https://thegraph.com/docs/en/subgraphs/developing/creating/graph-ts/api/#api-reference)。 (opens in a new tab)

在前端中使用

使用像 Apollo Boost 这样的工具,你可以轻松地将 The Graph 集成到你的 React dapp(或 Apollo-Vue)中。特别是在使用 React hooks 和 Apollo 时,获取数据就像在组件中编写单个 GraphQL 查询一样简单。一个典型的设置可能如下所示:

现在我们可以编写例如这样的查询。这将为我们获取:

  • 当前用户赢了多少次
  • 当前用户输了多少次
  • 包含他之前所有下注的时间戳列表

所有这些都在对 GraphQL 服务器的单个请求中完成。

Magic

但我们还缺少最后一块拼图,那就是服务器。你可以自己运行它,也可以使用托管服务。

The Graph 服务器

Graph 浏览器:托管服务

最简单的方法是使用托管服务。按照这里 (opens in a new tab)的说明部署子图。对于许多项目,你实际上可以在浏览器 (opens in a new tab)中找到现有的子图。

The Graph-Explorer

运行你自己的节点

或者,你可以运行自己的节点。文档在这里 (opens in a new tab)。这样做的一个原因可能是使用了托管服务不支持的网络。当前支持的网络可以在这里找到 (opens in a new tab)

去中心化的未来

GraphQL 也支持针对新传入事件的流。这些在 The Graph 上通过目前处于公开测试阶段的 Substreams (opens in a new tab) 得到支持。

2021 (opens in a new tab) 年,The Graph 开始向去中心化索引网络过渡。你可以在这里 (opens in a new tab)阅读更多关于这个去中心化索引网络架构的信息。

两个关键方面是:

  1. 用户向索引器支付查询费用。
  2. 索引器质押 Graph 代币 (GRT)。