4 minutos
Melhores Práticas de Subgraph Parte 4 - Como Melhorar a Velocidade da Indexação ao Evitar eth_calls
TLDR
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
.
Por que Evitar eth_calls
É uma Boa Prática
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.
Como É Um 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);
Suponhamos que a filiação de pool dos tokens seja determinada por um variável de estado chamado getPoolInfo
. Neste caso, precisaríamos usar uma eth_call
para consultar estes dados:
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 // Atrele a instância de contrato ERC20 ao endereço dado:9 let instance = ERC20.bind(event.address)1011 // Retire a informação do pool 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}
This is functional, however is not ideal as it slows down our Subgraph’s indexing.
Como Eliminar eth_calls
Idealmente, o contrato inteligente deve ser atualizado para emitir todos os dados necessários dentro de eventos. Por exemplo, modificar o contrato inteligente para incluir informações de pools no evento pode eliminar a necessidade de 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}
Assim, o desempenho melhora muito por eliminar a necessidade de eth_calls
.
Como Otimizar eth_calls
Se não for possível modificar o contrato inteligente e se eth_calls
forem necessários, leia “Improve Subgraph Indexing Performance Easily: Reduce eth_calls” para aprender várias estratégias sobre como otimizar eth_calls
.
Como Reduzir o Overhead de Runtime de eth_calls
Para eth_calls
que não podem ser eliminadas, o overhead de runtime que elas introduzem pode ser minimizado ao declará-las no manifest. Quando ograph-node
processa um bloco, ele realiza todas as eth_calls
em paralelo antes da execução dos handlers. Chamadas não declaradas são executadas em sequência quando os handlers são executados. A melhoria do runtime vem da execução das chamadas em paralelo, e não em sequência - isto ajuda a reduzir o tempo total gasto em chamadas, mas não o elimina por completo.
Atualmente, eth_calls
só podem ser declaradas para handlers de evento. No manifest, escreva
1event: TransferWithPool(address indexed, address indexed, uint256, bytes32 indexed)2handler: handleTransferWithPool3calls:4 ERC20.poolInfo: ERC20[event.address].getPoolInfo(event.params.to)
A porção destacada em amarelo é a declaração de chamada. A parte antes dos dois pontos é apenas um rótulo de texto usado só para mensagens de erro. A parte após os dois pontos tem a forma Contract[address].function(params)
. Valores permissíveis para address e params são event.address
e event.params.<name>
.
O próprio handler acessa o resultado desta eth_call
exatamente como na secção anterior ao atrelar ao contrato e fazer a chamada. o graph-node coloca em cache os resultados de eth_calls
na memória e a chamada do handler terirará o resultado disto no cache de memória em vez de fazer uma chamada de RPC real.
Note: Declared eth_calls can only be made in Subgraphs with specVersion >= 1.2.0.
Conclusão
You can significantly improve indexing performance by minimizing or eliminating eth_calls
in your Subgraphs.