6 分钟
子图最佳实践4-通过避免eth_calls提高索引速度
TLDR
eth_calls是可以从子图到以太坊节点进行的调用。这些调用需要大量时间来返回数据,从而减慢了索引速度。如果可能的话,设计智能合约来发出你需要的所有数据,这样你就不需要使用
eth_calls`。
为什么避免eth_calls
是一种最佳实践
子图经过优化,可以对智能合约发出的事件数据进行索引。子图也可以对来自eth_calls
的数据进行索引,但是,这会大大减慢子图索引的速度,因为eth_call
需要对智能合约进行外部调用。这些调用的响应性不依赖于子图,而是依赖于被查询的以太坊节点的连接性和响应性。通过最小化或消除子图中的eth_calls,我们可以显著提高索引速度。
Eth_call是什么样子的?
当子图所需的数据无法通过发出的事件获得时,通常需要eth_calls
。例如,考虑一个场景,其中子图需要确定ERC20代币是否是特定池的一部分,但合约只发出一个基本的转移
事件,而不发出包含我们所需数据的事件:
1event Transfer(address indexed from, address indexed to, uint256 value);
假设代币的池成员资格由名为getPoolInfo
的状态变量决定。在这种情况下,我们需要使用eth_call
来查询这些数据:
1import { Address } from '@graphprotocol/graph-ts'2import { ERC20, Transfer } from '../generated/ERC20/ERC20'3import { TokenTransaction } from '../generated/schema'45export function handleTransfer(event: Transfer): void {6 let transaction = new TokenTransaction(event.transaction.hash.toHex())78 // Bind the ERC20 contract instance to the given address:9 let instance = ERC20.bind(event.address)1011 // Retrieve pool information via eth_call12 let poolInfo = instance.getPoolInfo(event.params.to)1314 transaction.pool = poolInfo.toHexString()15 transaction.from = event.params.from.toHexString()16 transaction.to = event.params.to.toHexString()17 transaction.value = event.params.value1819 transaction.save()20}
这是功能性的,但并不理想,因为它减缓了子图的索引速度。
如何消除eth_calls
理想情况下,智能合约应该更新已在事件中发出所有必要的数据。例如,修改智能合约以在事件中包含池信息可以消除对eth_calls
的需求:
1event TransferWithPool(address indexed from, address indexed to, uint256 value, bytes32 indexed poolInfo);
通过此更新,子图可以直接索引所需的数据,而无需外部调用:
1import { Address } from '@graphprotocol/graph-ts'2import { ERC20, TransferWithPool } from '../generated/ERC20/ERC20'3import { TokenTransaction } from '../generated/schema'45export function handleTransferWithPool(event: TransferWithPool): void {6 let transaction = new TokenTransaction(event.transaction.hash.toHex())78 transaction.pool = event.params.poolInfo.toHexString()9 transaction.from = event.params.from.toHexString()10 transaction.to = event.params.to.toHexString()11 transaction.value = event.params.value1213 transaction.save()14}
这更具性能,因为它消除了对eth_calls
的需求。
如何优化eth_calls
如果无法修改智能合约并且需要eth_calls
,请阅读“轻松提高子图索引性能:减少eth_calls”由Simon Emanuel Schmid教授所作,学习如何优化eth_calls
的各种策略。
降低eth_calls
运行时的开销
对于无法消除的eth_calls
,可以通过在清单中声明来最小化引入的运行开销。当graph-节点
处理一个块时,它会在处理程序运行之前并行执行所有声明的eth_calls
。未声明的调用在处理程序运行时按顺序执行。运行时的改进来自并行而不是顺序执行调用,这有助于减少调用所花费的总时间,但并不能完全消除它。
目前,‘eth_calls’只能为事件处理程序声明。在清单中,写
1event: TransferWithPool(address indexed, address indexed, uint256, bytes32 indexed)2handler: handleTransferWithPool3calls:4 ERC20.poolInfo: ERC20[event.address].getPoolInfo(event.params.to)
黄色突出显示的部分是调用声明。冒号之前的部分只是一个仅用于错误消息的文本标签。冒号后的部分的格式为Contract[address].function(params)
。地址和参数的允许值是event.address
和event.params<name>
。
处理程序本身通过绑定到合约并进行调用来访问这个eth_call
的结果,与上一节完全相同。graph-节点将声明的eth_calls
结果缓存在内存中,处理程序的调用将从内存缓存中检索结果,而不是进行实际的RPC调用。
注意:声明的eth_calls只能在specVersion>=1.2.0的子图中创建。
结论
通过最小化或消除子图中的eth_calls
,我们可以显著提高索引性能。