以太坊 - 如何通过Multicall优化以太坊RPC使用率 - Quicknode

  • QuickNode
  • 发布于 2024-08-14 18:51
  • 阅读 12

本文介绍了如何通过利用Viem库和Multicall3来优化以太坊的RPC使用。文章详尽地论述了Multicall的优势,实施批量合约读取的步骤,以及如何通过减少RPC请求来降低成本和提高效率。同时,文中提供了相关代码示例和环境设置步骤,适合开发人员学习和参考。

概述

本指南介绍了如何通过使用原生的 Multicall3 整合,优化你的 Ethereum RPC 使用,利用现代 Ethereum 库 Viem

我们将逐步介绍如何设置 Viem、实现 Multicall 进行批量合约读取,以及探索这种方法在减少 RPC 使用和成本方面的好处。虽然 Multicall 也可以用于状态改变调用,但本指南将重点关注读取操作。

你将进行的操作

  • 了解 Multicall3 及其优势
  • 使用 Viem 设置 TypeScript 项目
  • 使用 Multicall 实现批量合约读取,减少 RPC 请求
  • 探讨使用 Multicall 在性能和成本优化方面的好处

你将需要的条件

Multicall

Multicall是一种强大的智能合约,它允许你将多个合约调用组合成一个 JSON-RPC 请求。这可以显著优化你的 RPC 使用,通过减少发送到区块链的请求数量。Multicall 合约还可以在一次调用中处理多次状态改变交易,尽管这不太常见且需要谨慎。

Viem 拥有 原生集成 的 Multicall,使其易于实现。此外,QuickNode SDK 包含 Viem,允许你无需额外设置即可无缝利用 Multicall 功能。

多年来,Multicall 合约经历了数次改进,每次都增加了新的功能和增强。在本指南中,我们将使用最新版本的 Multicall3,它提供了更先进的特性和更好的错误处理。Multicall3 已在超过 70 条链上部署,合约地址为 0xcA11bde05977b3631167028862bE2a173976CA11。已部署链的完整列表和其他详细信息可以在 Multicall3 网站上找到。

虽然本指南不涉及如何使用 Multicall 进行状态改变调用,但开发人员应该谨慎,因为实现尚未经过审计。有关更多细节,请参见 他们的官方文档

为什么使用 Multicall?

  • 效率:减少 JSON-RPC 请求的数量,降低 RPC 使用和成本。
  • 一致性:确保所有返回值来自同一个区块。
  • 性能:减少客户端和节点之间的往返时间,提升性能。

Multicall

Multicall 是如何工作的?

Multicall3 合约具有多种用于不同用例的功能。最常用的功能是 aggregate3,它聚合调用,并确保每个调用在需要时返回成功状态。

函数

  • aggregate3: 聚合调用并确保每个调用在需要时返回成功状态。
  • aggregate3Value: 类似于 aggregate3,但也允许你在调用中发送值。
  • aggregate: 返回一个(uint256 blockNumber, bytes[] returnData)元组,如果任何调用失败则回滚。
  • blockAndAggregate: 类似于 aggregate 但还返回区块号和区块哈希。
  • tryAggregate: 传入一个布尔值,指示是否需要所有调用成功,并返回一个(bool success, bytes[] returnData)[] 元组。
  • tryBlockAndAggregate: 类似于 tryAggregate,但还返回区块号和区块哈希。

aggregate3 函数

aggregate3 函数接受一个 Call3 结构体的数组,并且是可支付的,这意味着它可以在需要时转移以太币。它返回一个 Result 结构体的数组,每个结构体包含函数调用的成功状态和返回数据。该函数迭代输入数组,调用指定的目标并存储结果。如果任何调用失败且不允许失败,函数将回滚整个交易,确保所有调用成功执行或全不执行,以维护合约状态的完整性。

你可以在 这里 查看 aggregate3 函数的代码。

如何使用 Multicall 减少 RPC 成本

QuickNode Ethereum 端点

要与 Ethereum 进行交互,你需要一个 API 端点来连接网络。你可以使用公共节点或者部署和管理自己的基础设施;然而,如果你希望更快的响应时间和安全的 RPC 通信,可以让我们来处理繁重的工作。请在 这里 注册一个免费账户。

创建账户后,点击 创建端点 按钮。然后,选择你所选择的链。在本指南中,我们使用 Ethereum 主网

接着,复制你的 HTTP Provider URL;你将在接下来的部分中使用它来访问 Ethereum 区块链。

Ethereum Mainnet Node Endpoint

开发环境设置

如果你还没有安装 TypeScript,可以运行以下命令全局安装 TypeScriptts-node 以便在所有项目中都可以使用 TypeScript。

npm install -g typescript ts-node

步骤 1:创建项目目录

mkdir viem-multicall
cd viem-multicall

步骤 2:初始化你的项目

npm init -y
tsc -init --resolveJsonModule true

步骤 3:安装依赖

为确保你的项目具备所需的一切,运行以下命令。这将添加 viem 以与 Ethereum 进行交互,并添加 dotenv 以管理环境变量。此外,我们还包括 @types/node 以支持 Node.js 项目中的 TypeScript 类型检查,增强开发体验和类型检查。

npm install viem
npm install --save-dev dotenv @types/node

步骤 4:环境设置

创建一个 .env 文件,用于存储敏感信息,如你的钱包私钥和 QuickNode 端点。

echo > .env

然后,使用你的代码编辑器修改它,用下面的内容替换 YOUR_QUICKNODE_ENDPOINT_URL 字段。

QUICKNODE_ENDPOINT = "YOUR_QUICKNODE_ENDPOINT_URL"

步骤 5:创建必要的文件

创建 index.tscontract.ts 文件,它们将包含你的主要代码和合约相关数据,如 ABI 和合约地址。

echo > index.ts
echo > contract.ts

实现 Multicall

现在,我们将一次请求从 USDT 智能合约 中检索多个信息。这包括获取代币的名称、符号、总供应量、小数位数,以及 Vitalik Buterin 的 USDT 余额,演示从智能合约高效批量读取的能力。

我们将调用以下函数:

  • name: 检索代币的名称。
  • symbol: 检索代币的符号。
  • totalSupply: 检索代币的总供应量。
  • decimals: 检索代币的小数位数。
  • balanceOf(address owner): 检索特定地址的 USDT 余额,在这里是 Vitalik Buterin 的地址。

步骤 1:创建合约配置

在你的 contract.ts 文件中,包含 USDT 合约的地址和 ABI。以下代码中仅包含与之相关的函数,以保持文件简洁。

打开 contract.ts 文件并添加如下代码:

contract.ts

export const usdtContract = {
  address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
  abi: [\
    {\
      constant: true,\
      inputs: [],\
      name: "name",\
      outputs: [{ name: "", type: "string" }],\
      payable: false,\
      stateMutability: "view",\
      type: "function",\
    },\
    {\
      constant: true,\
      inputs: [],\
      name: "totalSupply",\
      outputs: [{ name: "", type: "uint256" }],\
      payable: false,\
      stateMutability: "view",\
      type: "function",\
    },\
    {\
      constant: true,\
      inputs: [],\
      name: "decimals",\
      outputs: [{ name: "", type: "uint256" }],\
      payable: false,\
      stateMutability: "view",\
      type: "function",\
    },\
    {\
      constant: true,\
      inputs: [{ name: "who", type: "address" }],\
      name: "balanceOf",\
      outputs: [{ name: "", type: "uint256" }],\
      payable: false,\
      stateMutability: "view",\
      type: "function",\
    },\
    {\
      constant: true,\
      inputs: [],\
      name: "symbol",\
      outputs: [{ name: "", type: "string" }],\
      payable: false,\
      stateMutability: "view",\
      type: "function",\
    },\
  ],
} as const;

步骤 2:设置客户端

在你的 index.ts 文件中,首先导入必要的模块,并设置客户端以与 Ethereum 主网交互。

打开 index.ts 文件并添加如下代码:

index.ts

// 导入必要的函数和类型
import { http, createPublicClient } from "viem";
// 从 viem chains 导入主网配置
import { mainnet } from "viem/chains";
// 导入 USDT 合约配置
import { usdtContract } from "./contract";

// 从环境变量中获取 QuickNode 端点
const QUICKNODE_ENDPOINT = process.env.QUICKNODE_ENDPOINT as string;

// 创建用于与 Ethereum 主网交互的公共客户端
const client = createPublicClient({
  chain: mainnet,
  transport: http(QUICKNODE_ENDPOINT),
});

// Vitalik Buterin 的 Ethereum 地址
const vitalikAddress = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";

步骤 3:定义 Multicall 参数

定义 Multicall 的参数,指定你想在 USDT 合约上调用的函数。

index.ts 中的上述代码之后添加以下代码。

async function main() {
  // 在一次请求中进行多次只读调用到 USDT 合约
  const results = await client.multicall({
    contracts: [\
      { ...usdtContract, functionName: "name" },\
      { ...usdtContract, functionName: "symbol" },\
      { ...usdtContract, functionName: "totalSupply" },\
      { ...usdtContract, functionName: "decimals" },\
      { ...usdtContract, functionName: "balanceOf", args: [vitalikAddress] },\
    ],
  });

  console.log(results);
}

// 执行主函数并记录任何错误
main().catch(console.error);

步骤 4:运行文件

使用以下命令运行 index.ts 文件:

ts-node index.ts

此设置将允许你使用 Viem 和 Multicall 在一次请求中执行多次只读合约调用,有效地优化 Ethereum RPC 使用。

检查结果

运行代码后,输出结果应该与以下类似。可以看到,每个合约调用的结果与指示成功或失败的状态一起返回。

[\
  { result: 'Tether USD', status: 'success' },\
  { result: 'USDT', status: 'success' },\
  { result: 51991636685165571n, status: 'success' },\
  { result: 6n, status: 'success' },\
  { result: 914883658n, status: 'success' }\
]

验证单一 eth_call 请求

为了验证只发出一个 eth_call 请求,你可以修改传输配置以记录请求。更新现有代码片段如下所示。

现有代码片段

// 从环境变量中获取 QuickNode 端点
const QUICKNODE_ENDPOINT = process.env.QUICKNODE_ENDPOINT as string;

// 创建用于与 Ethereum 主网交互的公共客户端
const client = createPublicClient({
  chain: mainnet,
  transport: http(QUICKNODE_ENDPOINT),
});

新代码片段

// 从环境变量中获取 QuickNode 端点
const QUICKNODE_ENDPOINT = process.env.QUICKNODE_ENDPOINT as string;

const transport = http(QUICKNODE_ENDPOINT, {
  onFetchRequest(request) {
    console.log(request);
  },
});

// 创建用于与 Ethereum 主网交互的公共客户端
const client = createPublicClient({
  chain: mainnet,
  transport: transport,
});

通过添加此日志记录,你将看到 eth_call 请求的详细信息,确认为 multicall 操作发出了单一请求。

请求

{
  "method": "eth_call",
  "params": [\
    {\
      "data": "0x82ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000500000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002800000000000000000000000000000000000000000000000000000000000000320000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000406fdde0300000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000495d89b4100000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000418160ddd00000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000004313ce56700000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002470a08231000000000000000000000000d8da6bf26964af9d7eed9e03e53415d37aa9604500000000000000000000000000000000000000000000000000000000",\
      "to": "0xca11bde05977b3631167028862be2a173976ca11"\
    },\
    "latest"\
  ]
}

在记录的请求数据中,以下信息是重要的:

  • 方法eth_call 表示这是一个以太坊调用请求。
  • 到地址:0xca11bde05977b3631167028862be2a173976ca11Multicall3 合约的地址。
  • 数据:数据字段包含编码的函数调用。函数签名 "82ad56cb" 表示正在调用 aggregate3 函数。

比较成本:多个 eth_call 与 Multicall

使用 Multicall 将多个合约调用聚合成一个请求可以显著减少发送给区块链的请求数量,从而减少所需的计算资源并降低成本。

考虑一下发出五个单独的 eth_call 请求与使用 QuickNode 定价的一个 Multicall 请求(每次调用 20 积分)的成本比较:

方法 调用次数 总使用积分
单独的 eth_call 请求 5 100
Multicall 1 20

结论

在本指南中,我们探讨了如何通过 Multicall 和 Viem 优化 Ethereum RPC 使用。通过高效地聚合多个合约读取操作,你可以减少成本,并提高基于 Ethereum 的应用程序的性能。请查看其他资源以获取更深入的信息,并继续优化你的区块链交互。

订阅我们的 新闻通讯,获取更多关于 Web3 和区块链的文章和指南。如果你有任何问题,请查看 QuickNode 论坛 寻求帮助。通过关注我们的 Twitter (@QuickNode) 或 Discord 来保持最新消息。

额外资源

我们 ❤️ 反馈!

告诉我们如果你有任何反馈或对新主题的请求。我们很想听取你的意见。

  • 原文链接: quicknode.com/guides/eth...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
QuickNode
QuickNode
江湖只有他的大名,没有他的介绍。