TheGraph 去中心化网络服务

TheGraph 新的去中心化网络是什么以及如何使用它?

之前我们翻译过TheGraph上一篇文章:使用 TheGraph 进行事件数据检索,已经过去了相当长的时间。如果你不知道TheGraph是什么,为什么TheGraph是有用的,可以读读那篇文章,在那篇文章详细解释了为什么需要TheGraph以及如何在中心化托管服务(Hosted Service)中使用它。

简短的说:区块链上的事件是一种非常有效的添加数据的方式,而不必将其存储在每个节点上(这很昂贵)。事件是通过使用bloom filter来实现的,客户端能够解析区块和交易,以快速找到其要找的数据。但仍然需要解析大量的区块,所以一个替代方案是让服务器对这些数据进行索引并将其存储在数据库中。加上把GraphQL作为一种非常方便的查询语言放在上面,这就是The Graph了,不过是作为一种中心化服务。

总之,The Graph允许以一种更有效的方式查询区块链的数据。这在构建前端和显示区块链中发生的数据时是非常重要的,而不必将数据直接存储在智能合约中。

现在,The Graph开始了一个新的去中心化的网络,也增加了更多的功能。托管服务(Hosted Service)将在2023年第一季度结束,所以现在是时候了解The Graph去中心化网络是如何工作的,如何使用它,以及作为开发者的你需要了解有哪些新功能。

向去中心化过渡

最初The Graph只有一个中心化的托管服务,但从长远来看,这当然不是我们想要的。毕竟,如果你完全依赖一个中心化的服务器来查询数据,那么Dapp的意义何在。尽管这仍然比完全中心的基础设施要好,但我们仍然可以做得更好!

为了解决这个问题,The Graph拥有自己的去中心化网络和自己的GRT ERC-20代币。

1. 协议角色

TheGraph Protocol Roles

  • 消费者(comsumer): 消费者是向索引器发送查询并为这项服务付费的人。消费者可以是直接的终端用户,例如正在使用Dapp或中间件服务的用户。
  • 索引器(Indexer): 索引器是那些实际提供运行服务器、索引事件并将其存储在数据库中的服务的人。作为回报,他们将从消费者那里获得费用。
  • 策展人(Curator):策展人将确定哪些是值得被索引的subgraph(发出索引信号),并为特定的subgraph支付自己的GRT。他们将根据联合曲线赚取一部分查询费用。策展人可能是subgraph的开发者,他想让自己的subgraph被索引。
  • 委托人(Delegator):委托人可以质押GRT到索引器,以赚取其查询费用的分成 ,例如:无法运行索引器的人可以做委托人参与生态收益。

消费者可以根据运行时间和价格等因素,自由地选择使用哪个索引器。当然,一个Dapp甚至可以使用多个索引器以获得最高的安全性。

2. GRT代币

GRT代币本身是用于质押:

  • 索引器用质押GRT的目的是为了抵制女巫攻击,以及允许对他们的不良行为进行罚款。索引器被期望其抵押GRT的比例(相对总质押)与他们为网络所做工作的比例相符。这不是强制的,而是来自于收集交易费用的设计,将质押比例和费用的函数返还给参与者,设计灵感来自于0x设计
  • 委托人将他们的GRT借给索引器,并收到部分查询费作为回报。他们不能因为索引器的不当行为而被削减他们的GRT,而且每个索引器通过委托获得的GRT是有限制的。

以下是各方参与示意图:

image-20220719095620533

3. 激励新subgraph

为了帮助引导那些还没有需求的新subgraph,GRT 质押是通货膨胀的,新铸造的代币被给予那些对查询需求非常低的subgraph进行索引的索引器。为了确保索引器确实在做索引这些低需求subgraph的工作,有一个额外的机制叫做索引证明(POI)。POI是对subgraph状态哈希的签名,索引器需要它来获得奖励。POI被乐观地接受,但以后可以被罚没。在网络的第一个版本中,通过治理设置的仲裁员将决定这些争端。

subgraph配置(称为清单)通常被上传到IPFS。但是,当清单根本无法使用时,我们能做什么?那么就不可能验证这些POI了。对于这一点,有一个subgraph可用性Oracle。它将查看几个有名的IPFS端点,如果清单不可用,subgraph就不能用于索引器获取奖励。

4. 支付通道

常用的支付通道(状态通道技术)是一种很好的支持规模支付方式。像The Graph那样为每个查询付费时,我们显然需要这样的东西。在The Graph中,支付通道有一个额外的安全层:WAVE

WAVE 支付的几个部分:

  1. Work(工作):锁定小额付款,并对要进行的工作进行描述。
  2. Attestation(证明):工作+签署的证明,可以乐观地解锁小额付款。
  3. Verification(验证): 通道外的证明,用于可能的惩罚。
  4. Expiration(过期):锁定的小额支付可以过期。

查询验证

现在的问题是,你如何验证The Graph查询的正确性?最初在The Graph中,这是由链上争议解决程序处理的,通过仲裁决定。渔夫们将寻找不正确的查询,并将证明与保证金一起提交给仲裁。

在未来,与其依靠仲裁来解决查询纠纷,不如利用多项式承诺或Merkle树等技术的加密证明来保证查询的有效性。同样,即使是POI也可以使用类似于乐观 Rollup的机制来自动验证。两者结合起来,就可以完全摆脱目前网络中比较集中的对仲裁的需要。

如何部署到TheGraph 的去中心化网络

Subgraph Studio是迈向去中心化 TheGraph的第一步,但它仍然包含中心化的组件。在其目前的设计中,支付是由The Graph团队直接处理的,你创建一个API密钥来查询数据。以后一旦实现了可验证的查询,用户将直接付款。

如果你有已经在托管服务上的subgraph,可以看看这个迁移指南

  1. 进入https://thegraph.com/studio/,点击创建subgraph。
  2. $ npm install -g @graphprotocol/graph-cli
  3. 在一个现有的项目内部署一个合约,例如:$ npx hardhat run scripts/deploy.ts --network rinkeby
  4. $ graph init --studio <created-subgraph-name>
  5. 如果Etherscan合约没有被验证,你也可以直接传递ABI文件,例如:artifacts/contracts/Game.sol/Game.json
  6. 保存正确的schema.graphql和schema.ts。
  7. $ graph auth --studio <my-subgraph-id> (见Subgraph Studio UI)
  8. $ graph codegen && graph build
  9. $ graph deploy --studio <my-subgraph-id>

现在你可以在Subgraph Studio中点击 发布。要获得Rinkeby testnet GRT,请到The Graph Discord #roles,点击'T',然后到#testnet-faucet,输入!grt <自己的以太坊地址>。然后你就可以立即开始策展subgraph了。请注意,在写这篇文章的时候,还不支持通过去中心化网络对Rinkeby进行实际查询,但对于主网来说是支持的。

  • 你可以在https://testnet.thegraph.com访问testnet 浏览器。在这里你也可以找到一个subgraph,并测试其他subgraph的策展情况。
  • 索引器(和策展人+委托人)可以通过https://testnet.thegraph.com/explorer/participants/indexers,你也可以在这里测试委托功能。
  • 相应的主网的版本是https://thegraph.com/explorer和https://thegraph.com/participants/indexers。

开发者需要了解的新功能

  • 新的AssemblyScript版本:Graph已经更新了用于编写映射的AssemblyScript。如果你习惯于旧版本,你可以遵循迁移指南

  • 调试克隆(Forking):一个名为debug forking的新功能允许克隆一个已部署的subgraph并改变其对特定块的映射。因此,如果你部署了一个新的subgraph而它失败了,现在你可以从该区块进行本地调试,而不是从头开始重新同步,浪费大量的时间。

  • 新的区块链:虽然在去中心化网络上还不能使用,但The Graph已经为新公链增加了索引支持,最需要了解的是CosmosNEARArweave

  • subgraph映射单元测试: 一个名为matchstick的新单元测试功能可以测试subgraph映射,详情见下文。

单元测试subgraph映射

要开始在现有的subgraph中进行单元测试。

  1. 安装matchstick:
    1. $ yarn add --dev matchstick-as
  2. 安装Postgresql:
    1. $ brew install postgresql (Mac) 或
    2. $ sudo apt install postgresql(Linux)
    3. 或对于Windows见文档
  3. /tests文件夹中创建测试文件。
  4. 运行测试:$ graph test

以下是之前的上一篇 TheGraph文章的Bet映射的单元测试例子:

import {
  assert,
  describe,
  clearStore,
  test,
  newMockEvent,
} from "matchstick-as/assembly/index";

import { BetPlaced } from "../generated/Game/Game";
import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts";
import { handleBetPlaced } from "../src/mapping";

function createBetPlacedEvent(
  player: string,
  value: BigInt,
  hasWon: boolean
): BetPlaced {
  const mockEvent = newMockEvent();
  const BetPlacedEvent = new BetPlaced(
    mockEvent.address,
    mockEvent.logIndex,
    mockEvent.transactionLogIndex,
    mockEvent.logType,
    mockEvent.block,
    mockEvent.transaction,
    mockEvent.parameters,
    null
  );
  BetPlacedEvent.parameters = new Array();
  const playerParam = new ethereum.EventParam(
    "player",
    ethereum.Value.fromAddress(Address.fromString(player))
  );
  const valueParam = new ethereum.EventParam(
    "value",
    ethereum.Value.fromUnsignedBigInt(value)
  );
  const hasWonParam = new ethereum.EventParam(
    "hasWon",
    ethereum.Value.fromBoolean(hasWon)
  );

  BetPlacedEvent.parameters.push(playerParam);
  BetPlacedEvent.parameters.push(valueParam);
  BetPlacedEvent.parameters.push(hasWonParam);

  BetPlacedEvent.transaction.from = Address.fromString(player);

  return BetPlacedEvent;
}

describe("handleBetPlaced()", () => {
  test("Should create a new Bet entity", () => {
    const player = "0x7c812f921954680af410d86ab3856f8d6565fc69";
    const hasWon = true;
    const mockedBetPlacedEvent = createBetPlacedEvent(
      player,
      BigInt.fromI32(100),
      hasWon
    );

    handleBetPlaced(mockedBetPlacedEvent);

    const betId =
      mockedBetPlacedEvent.transaction.hash.toHex() +
      "-" +
      mockedBetPlacedEvent.logIndex.toString();

    // fieldEquals(entityType: string, id: string, fieldName: string, expectedVal: string)
    assert.fieldEquals("Bet", betId, "id", betId);
    assert.fieldEquals("Bet", betId, "player", player);
    assert.fieldEquals("Bet", betId, "playerHasWon", "true");
    assert.fieldEquals(
      "Bet",
      betId,
      "time",
      mockedBetPlacedEvent.block.timestamp.toString()
    );
    clearStore();
  });
});
  1. 你可以首先使用newMockEvent创建模拟事件。

  2. 然后以你喜欢的方式修改模拟的事件。

  3. 然后从映射中调用处理事件函数。

  4. 断言新的状态符合预期。

  5. 事后清理存储。

你可以在https://github.com/soliditylabs/the-graph-studio-example 找到完整的例子。


本翻译由 Duet Protocol 赞助支持。

点赞 1
收藏 1
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
翻译小组
翻译小组
0x9e64...7c84
大家看到好的文章可以在 GitHub 提 Issue: https://github.com/lbc-team/Pioneer/issues 欢迎关注我的 Twitter: https://twitter.com/UpchainDAO