背景介绍
有时,从区块链获取数据的成本可能会非常高,不管是从请求花费的时间还是从发送的请求数量上来说,都是这样。如果我们想同时获取大量数据,用来在仪表板上显示或进行分析,我们必须调用合约的不同函数或者用不同参数调用相同函数, 这些都可能会导致查询时间很长。另外,当我们使用像Infura[4]这样的节点提供商,也很容易达到发送请求数量的限额。
什么是 Multicall?
Multicall[5]是一个 npm 软件包,可将多个 HTTP 调用分为一个组。用这个方式,之前想从n个不同的请求中获取的数据,现在可以在发送 HTTP 请求之前对它们进行分组,然后进发送一个请求,从而缩短了请求响应时间,并降低了 eth_call 调用的次数。
用测试了解运作方式
为了了解这种机制的工作原理以及相对于传统方法是否确实有所改进,我们将通过一个对比测试来验证。分别在不使用 Multicall 和使用 Multicall 的情况下,对每个函数调用n次, 然后分析结果。为此,我们通过调用函数 getAccountLiquidity 来查询 Compound 协议。我们将使用 1,000 个不同的地址来获取所有地址的信息。
创建项目
安装依赖
为了进行测试,先创建一个 Node 项目,并将安装依赖项:ethers.js[6] 用于与区块链交互、money-legos[7]则用来以更简单的方式引用 ABI 和合约,以及 Multicall 软件包。
使用以下命令创建项目:
代码语言:javascript复制npm init -y
然后,安装了上述提到的依赖项:
代码语言:javascript复制npm install -S @studydefi/money-legos ethers ethers-multicall
导入依赖
对比测试的两种情况,我们都必须使用引入公共依赖项进行实例化并以此与区块链连接。引入方式如下(import.js):
代码语言:javascript复制const { ethers } = require("ethers");
const { ALCHEMY_URL } = require('./config')
const compound = require("@ studydefi/money-legos/compound");
const { accounts } = require("./accounts");
const { Contract, Provider } = require('ethers-multicall');
const provider = new ethers.providers.JsonRpcProvider(ALCHEMY_URL);
并创建一个用来显示结果和执行时间的函数,如下所示(calculatetime.js):
代码语言:javascript复制const calculateTime = async () => {
const startDate = new Date();
const result = await getLiquidity()
const endDate = new Date();
const milliseconds = (endDate.getTime() - startDate.getTime());
console.log(`Time to process in milliseconds: $ {milliseconds}`)
console.log(`Time to process in seconds: $ {milliseconds / 1000}`)
const callsCount = Object.keys(result).length;
console.log(`Number of entries in the result: $ {callsCount}`);
}
calculatetime.js
调用合约
常规循环调用
先使用传统方法进行测试,我们将遍历 1,000 个的地址数组(在map
循环中),逐个获取每个查询的结果,执行方法如下:
const getLiquidity = () => {
const compoundContract = new ethers.Contract(
compound.comptroller.address,
compound.comptroller.abi,
provider
)
return Promise.all(accounts.map(account => {
let data
try {
data = compoundContract.getAccountLiquidity(account.id)
} catch (error) {
console.log(`Error getting the data $ {error}`)
}
return data
}))
}
上面实例化 compound comptroller 合约,并在每个地址上调用流动性函数。
使用 Multicall 调用
使用 Multicall 调用时,调用函数必须稍作更改,形式如下:
代码语言:javascript复制const getLiquidity = async () => {
const ethcallProvider = new Provider(provider);
await ethcallProvider.init();
const compoundContract = new Contract(
compound.comptroller.address,
compound.comptroller.abi,
)
const contractCalls = accounts.map(account => compoundContract.getAccountLiquidity(account.id))
const results = await ethcallProvider.all(contractCalls);
return results
}
利用 Multicall 包中的Provider
和Contract
类。首先,初始化 provider,并传递web3
、合约地址及其合约 ABI。
创建完成后,执行则和之前类似。在map
里,调用帐户流动性函数。但是现在它不会发送到网络,而是将它们分组到一个数组中。创建此数组后,将调用创建好的 Multicall Provider
的 all
函数,并进行网络调用。
对比分析结果
要查看是否确实有重大改进,只需要对比两个调用消耗的时间。
传统循环方法消耗的时间:
代码语言:javascript复制Time to process in milliseconds: 124653
Time to process in seconds: 124.653
Number of entries in the result: 1000
使用 Multicall 调用
代码语言:javascript复制Time to process in milliseconds: 9591
Time to process in seconds: 9.591
Number of entries in the result: 1000
结论
通过结果对比,发现使用 Multicall 调用时间的减少是非常可观的,从 124 秒减少到 9.5,花费的时间减少大约十倍。
另外,如果比较eth_call
RPC 调用的数量,同样是非常明显的减少,从一千个减少到只有一个。
因此,如果我们依赖第三方的节点提供商,而在该提供商中对 API 的调用是有限额,则这一点也同样重要。
本翻译由 Cell Network[8] 赞助支持。
来源:https://medium.com/better-programming/speed-up-your-defi-queries-using-multicall-d4cf652d8ab6
参考资料
[1]
登链翻译计划: https://github.com/lbc-team/Pioneer
[2]
翻译小组: https://learnblockchain.cn/people/412
[3]
Tiny 熊: https://learnblockchain.cn/people/15
[4]
Infura: https://infura.io/
[5]
Multicall: https://github.com/cavanmflynn/ethers-multicall#readme
[6]
ethers.js: https://docs.ethers.io/v5/
[7]
money-legos: https://money-legos.studydefi.com/#/
[8]
Cell Network: https://www.cellnetwork.io/?utm_souce=learnblockchain