Web3索引终极指南
并不是每个开发人员都能轻松确定索引在 Web3 背景下的含义。 我想定义有关此主题的一些细节,并讨论 The Graph,它已成为 DApp 构建者访问区块链数据的事实上的行业标准。
一键发币: SOL | BNB | ETH | BASE | Blast | ARB | OP | POLYGON | AVAX | FTM | OK
很难说数据工程文化在 Web3 开发者社区中根深蒂固。 并不是每个开发人员都能轻松确定索引在 Web3 上下文中的含义。 我想定义有关此主题的一些细节,并讨论 The Graph,它已成为 DApp 构建者访问区块链数据的事实上的行业标准。
1、从索引开始
让我们从索引(indexing)开始。
数据库中的索引是创建数据结构的过程,该数据结构以可以有效执行搜索查询的方式对数据库中的数据进行排序和组织。 通过在数据库表上创建索引,数据库服务器可以更快地搜索和检索与查询中指定的条件相匹配的数据。 这有助于提高数据库的性能并减少检索信息所需的时间。
但是区块链中的索引又如何呢? 最流行的区块链架构是EVM(以太坊虚拟机)。
以太坊虚拟机(EVM)是在以太坊区块链上执行智能合约的运行时环境。 它是一个在以太坊网络上的每个节点中运行的计算机程序。 它负责执行智能合约的代码,还提供沙箱和gas使用控制等安全功能。 EVM 确保以太坊网络上的所有参与者都能以一致且安全的方式执行智能合约。
你可能知道,区块链上的数据存储为块,其中包含交易。 此外,你可能知道有两种类型的帐户:
- 外部拥有账户——由任何普通钱包地址描述。
- 合约账户——由任何部署的智能合约地址描述。
如果你将一些以太币从你的帐户发送到任何其他外部所有者帐户 - 幕后没有任何事情。 但是,如果你将一些以太币发送到带有任何有效负载的智能合约地址,那么你实际上在智能合约上运行了某种方法,该方法实际上创建了一些“内部”交易。
好吧,如果可以在区块链上找到任何交易,为什么不将所有数据转换为一个可以以类似 SQL 格式查询的不断更新的大型数据库呢?
问题是,只有拥有破译智能合约的“密钥”,你才能访问它。 如果没有这个“密钥”,区块链上智能合约的数据实际上就是一团乱麻。 该密钥称为 ABI(应用程序二进制接口)。
ABI(应用程序二进制接口)是定义智能合约与外界(包括其他智能合约和用户界面)通信方式的标准。 它定义了智能合约的数据结构、函数签名和参数类型,以实现合约与其用户之间正确有效的通信。
任何链上智能合约都有 ABI。 问题是,你可能没有感兴趣的智能合约的 ABI。有时,你可以找到一个 ABI 文件(实际上是一个 JSON 文件,其中包含智能合约的函数和变量的名称 - 就像接口一样) 与之沟通:
- 在 Etherscan(如果智能合约已被验证)
- 在 GitHub 上(如果开发人员开源了该项目)
- 或者如果智能合约涉及 ERC-20、ERC-721 等任何标准类型。
当然,如果你是智能合约的开发者,你就有ABI,因为它是在编译时生成的。
2、开发者视角
但我们不要停留在 ABI 的概念上。 如果我们从智能合约开发者的角度来看这个话题呢? 什么是智能合约? 答案比你想象的要容易得多。 对于熟悉面向对象编程的人来说,这是一个简单的解释:
开发人员代码中的智能合约是一个具有字段和方法的类(对于 EVM 兼容的链,智能合约通常是用 Solidity 编写的)。 而已经部署在链上的智能合约就成为该类的一个对象。 因此,它的生命周期允许用户调用其方法并更改其内部字段。
值得强调的是,任何伴随智能合约状态变化的方法调用都意味着一次交易,通常后面跟着开发人员从代码中发出的事件。 让我们演示一下 ERC-721(BoredApeYachtClub 等不可替代代币集合的常用标准)智能合约的函数调用,该智能合约在转移 NFT 所有权时发出事件:
/**
* @dev Transfers `tokenId` from `from` to `to`.
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(address from, address to, uint256 tokenId) internal virtual {
address owner = ownerOf(tokenId);
if (owner != from) {
revert ERC721IncorrectOwner(from, tokenId, owner);
}
if (to == address(0)) {
revert ERC721InvalidReceiver(address(0));
}
_beforeTokenTransfer(from, to, tokenId, 1);
// Check that tokenId was not transferred by `_beforeTokenTransfer` hook
owner = ownerOf(tokenId);
if (owner != from) {
revert ERC721IncorrectOwner(from, tokenId, owner);
}
// Clear approvals from the previous owner
delete _tokenApprovals[tokenId];
// Decrease balance with checked arithmetic, because an `ownerOf` override may
// invalidate the assumption that `_balances[from] >= 1`.
_balances[from] -= 1;
unchecked {
// `_balances[to]` could overflow in the conditions described in `_mint`. That would require
// all 2**256 token ids to be minted, which in practice is impossible.
_balances[to] += 1;
}
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId, 1);
}
我们在这里可以看到什么。 要将 NFT 从你的地址转移到任何其他地址,需要调用函数 _transfer
并传递这两个地址的值和该 NFT 的 ID。 在代码中,你可以看到将进行一些检查,然后用户的余额将被更改。 但这里重要的是,在函数代码的最后,有一行:
emit Transfer(from, to, tokenId);
这意味着这三个值将被“发送”到外部,并且可以在区块链的日志中找到。 以这种方式保存所需的历史数据会更有效,因为将数据直接存储在区块链上的成本太高。
3、定义区块链的索引
考虑到任何智能合约(作为某个类的对象)在其生命周期中不断被用户(和其他智能合约)调用并改变状态(同时发出事件),我们可以将索引定义为一个过程 在智能合约的生命周期内收集智能合约数据(其中的任何内部变量,而不仅仅是显式发出的变量),并将这些数据与交易 ID(哈希)和区块号一起保存,以便能够在 未来。
值得注意的是,智能合约不显式存储这些数据(因为我们知道它非常昂贵),我们也无法访问这些数据,例如钱包A的代币B交易或合约C上的最大交易。
这就是为什么我们需要索引。 我们在 SQL 数据库中可以做的简单事情在区块链中却变得不可能了。 没有索引。
换句话说,这里的“索引”是智能合约数据收集的同义词,因为在 Web3 中没有索引就意味着没有数据访问。
3.1 如何建立区块链索引?
在过去,开发人员需要从头开始做:
- 使用一些快速编程语言(如 Go、Rust 等)编写高性能代码。
- 建立一个数据库来存储数据。
- 建立了一个 API 来使数据可以从应用程序访问。
- 启动了一个归档区块链节点。
- 在第一阶段,检查整个区块链,查找与特定智能合约相关的所有交易。
- 通过存储新实体并刷新数据库中的现有实体来处理这些事务。
- 当到达链头时,需要切换到更复杂的模式来处理新交易,因为每个新块(甚至是块链)都可能由于链重组而被拒绝。
- 如果链已经重组,需要返回分叉块并重新计算新链头的所有内容。
正如你所注意到的,不仅开发困难,而且实时维护也不容易,因为每个节点故障可能需要一些步骤才能再次实现数据一致性。 这实际上就是 The Graph 出现的原因。 这是一个简单的想法,即开发人员和最终用户需要轻松访问智能合约数据,而无需遇到所有这些麻烦。
3.2 The Graph的方式
The Graph 项目定义了一个名为“subgraph”的范式,要从区块链中提取智能合约数据,你需要描述 3 件事:
- 通用参数,例如要使用的区块链、要索引的智能合约地址、要处理的事件以及从哪个起始块开始。 这些变量在所谓的manifest文件中定义。
- 如何存储数据。 应在数据库中创建哪些表来保留智能合约中的数据? 答案可以在schema文件中找到。
- 如何收集数据。 应该从事件中保存哪些变量,还应该收集哪些伴随数据(例如交易哈希、块号、其他方法调用的结果等),以及如何将它们放入我们定义的模式中?
这三件事可以在以下三个文件中优雅地定义:
- subgraph.yaml — 清单文件
- schema.graphql — 模式描述
- mapping.ts — AssemblyScript 文件
由于这个标准,按照这些教程中的任何一个来描述整个索引是非常容易的:
那么它看起来是这样:
正如你在这里看到的,the graph负责索引工作。 但你仍然需要运行graph-node(这是 The Graph 的开源软件), 这是另一个范式转变。
由于过去开发人员一直在运行自己的区块链节点,因此不再这样做,因此区块链节点提供商承担了这一麻烦。 该图显示了另一种架构简化。 Graph 托管服务通过以下方式寻找开发人员(此处为“用户”):
在这种情况下,用户(或开发人员)不需要运行自己的indexer或graph-node,但仍然可以控制所有算法,甚至不会陷入供应商锁定,因为不同的提供者使用相同的图描述格式(Chainstack 是 与 The Graph 子图托管完全兼容,但值得与你的 web3 基础设施提供商检查此声明)。 这是一件大事,因为它可以帮助开发人员加快开发过程并降低运营维护成本。
但这个范例的另一个很酷的地方是,任何时候开发人员想要使他们的应用程序真正去中心化,他们都可以使用相同的子图无缝迁移到 The Graph 去中心化网络。
我在前面的叙述中错过了什么?
- 你可能会注意到,The Graph 使用 GraphQL 而不是 REST API。 它允许用户灵活地查询他们创建的任何表,组合它们并轻松过滤。 这是一个关于如何掌握它的好视频。
- The Graph有自己的托管服务,其中包含许多现成的子图。 它是免费的,但不幸的是不符合任何生产要求(可靠性、SLA、支持),并且同步比付费解决方案慢得多,但仍然可以用于开发。 有关如何通过 Python 使用这些现成子图的教程可以在此处找到。
原文链接:Web3 Indexing: The Ultimate Guide (No Prior Knowledge Required)
DefiPlot翻译整理,转载请标明出处