4 минуты
Лучшая практика субграфа 4 — увеличение скорости индексирования за счет избегания eth_calls
Краткое содержание
eth_calls
are calls that can be made from a Subgraph to an Ethereum node. These calls take a significant amount of time to return data, slowing down indexing. If possible, design smart contracts to emit all the data you need so you don’t need to use eth_calls
.
Почему избегание eth_calls
является наилучшей практикой
Subgraphs are optimized to index event data emitted from smart contracts. A Subgraph can also index the data coming from an eth_call
, however, this can significantly slow down Subgraph indexing as eth_calls
require making external calls to smart contracts. The responsiveness of these calls relies not on the Subgraph but on the connectivity and responsiveness of the Ethereum node being queried. By minimizing or eliminating eth_calls in our Subgraphs, we can significantly improve our indexing speed.
Что из себя представляет eth_call?
eth_calls
are often necessary when the data required for a Subgraph is not available through emitted events. For example, consider a scenario where a Subgraph needs to identify whether ERC20 tokens are part of a specific pool, but the contract only emits a basic Transfer
event and does not emit an event that contains the data that we need:
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 // Привязка экземпляра контракта ERC20 к указанному адресу:9 let instance = ERC20.bind(event.address)1011 // Получение информации о пуле с помощью 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}
This is functional, however is not ideal as it slows down our Subgraph’s indexing.
Как устранить eth_calls
В идеале смарт-контракт должен быть обновлён, чтобы эмитировать все необходимые данные внутри событий. Например, модификация смарт-контракта для включения информации о пуле в событие может устранить необходимость в eth_calls
:
1event TransferWithPool(address indexed from, address indexed to, uint256 value, bytes32 indexed poolInfo);
With this update, the Subgraph can directly index the required data without external calls:
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” Саймона Эмануэля Шмида, чтобы изучить различные стратегии оптимизации eth_calls
.
Сокращение времени выполнения eth_calls
Для тех eth_calls
, которые нельзя устранить, накладные расходы на их выполнение можно минимизировать, объявив их в манифесте. Когда graph-node
обрабатывает блок, все объявленные 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-node
кеширует результаты объявленных eth_calls
в памяти, а вызов из обработчика будет извлекать результат из этого кеша в памяти, вместо того чтобы выполнять фактический RPC-вызов.
Note: Declared eth_calls can only be made in Subgraphs with specVersion >= 1.2.0.
Заключение
You can significantly improve indexing performance by minimizing or eliminating eth_calls
in your Subgraphs.