Melhores Práticas de Subgraph Parte 4 - Como Melhorar a Velocidade da Indexação ao Evitar eth_calls
Reading time: 4 min
eth_calls
são chamadas feitas de um subgraph a um node no Ethereum. Estas chamadas levam um bom tempo para retornar dados, o que retarda a indexação. Se possível, construa contratos inteligentes para emitir todos os dados necessários, para que não seja necessário usar eth_calls
.
Subgraphs são otimizados para indexar dados de eventos emitidos de contratos inteligentes. Um subgraph também pode indexar os dados que vêm de uma eth_call
, mas isto pode atrasar muito a indexação de um subgraph, já que eth_calls
exigem a realização de chamadas externas para contratos inteligentes. A capacidade de respostas destas chamadas depende não apenas do subgraph, mas também da conectividade e das respostas do node do Ethereum a ser consultado. Ao minimizar ou eliminar eth_calls
nos nossos subgraphs, podemos melhorar muito a nossa velocidade de indexação.
eth_calls
tendem a ser necessárias quando os dados requeridos por um subgraph não estão disponíveis via eventos emitidos. Por exemplo, vamos supor que um subgraph precisa identificar se tokens ERC20 são parte de um pool específico, mas o contrato só emite um evento Transfer
básico e não emite um evento que contém os dados que precisamos:
event 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:
import { Address } from '@graphprotocol/graph-ts'import { ERC20, Transfer } from '../generated/ERC20/ERC20'import { TokenTransaction } from '../generated/schema'export function handleTransfer(event: Transfer): void {let transaction = new TokenTransaction(event.transaction.hash.toHex())// Atrele a instância de contrato ERC20 ao endereço dado:let instance = ERC20.bind(event.address)// Retire a informação do pool via eth_calllet poolInfo = instance.getPoolInfo(event.params.to)transaction.pool = poolInfo.toHexString()transaction.from = event.params.from.toHexString()transaction.to = event.params.to.toHexString()transaction.value = event.params.valuetransaction.save()}
Isto é funcional, mas não ideal, já que ele atrasa a indexação do nosso subgraph.
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
:
event TransferWithPool(address indexed from, address indexed to, uint256 value, bytes32 indexed poolInfo);
Com esta atualização, o subgraph pode indexar directamente os dados exigidos sem chamadas externas:
import { Address } from '@graphprotocol/graph-ts'import { ERC20, TransferWithPool } from '../generated/ERC20/ERC20'import { TokenTransaction } from '../generated/schema'export function handleTransferWithPool(event: TransferWithPool): void {let transaction = new TokenTransaction(event.transaction.hash.toHex())transaction.pool = event.params.poolInfo.toHexString()transaction.from = event.params.from.toHexString()transaction.to = event.params.to.toHexString()transaction.value = event.params.valuetransaction.save()}
Assim, o desempenho melhora muito por eliminar a necessidade de eth_calls
.
Se não for possível modificar o contrato inteligente e se eth_calls
forem necessários, leia “” para aprender várias estratégias sobre como otimizar 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
event: TransferWithPool(address indexed, address indexed, uint256, bytes32 indexed)handler: handleTransferWithPoolcalls: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.
Nota: eth_calls
declaradas só podem ser feitas em subgraphs com specVersion maior que 1.2.0.
Podemos melhorar muito o desempenho da indexação ao minimizar ou eliminar eth_calls
nos nossos subgraphs.