4 minutes
Doporučený postup pro podgraf 4 - Zlepšení rychlosti indexování vyhnutím se 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
.
Proč je dobré se vyhnout 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.
Jak vypadá 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);
Předpokládejme, že příslušnost tokenů k poolu je určena stavovou proměnnou s názvem getPoolInfo
. V takovém případě bychom k dotazu na tato data potřebovali použít příkaz 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}
This is functional, however is not ideal as it slows down our Subgraph’s indexing.
Jak odstranit eth_calls
V ideálním případě by měl být inteligentní kontrakt aktualizován tak, aby v rámci událostí vysílal všechna potřebná data. Například úprava inteligentního kontraktu tak, aby v události obsahoval informace o bazénu, by mohla odstranit potřebu 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}
To je mnohem výkonnější, protože to odstranilo potřebu eth_calls
.
Jak optimalizovat eth_calls
Pokud úprava inteligentního kontraktu není možná a eth_calls
jsou nutné, přečtěte si článek “Improve Subgraph Indexing Performance Easily: Reduce eth_calls” od Simon Emanuel Schmid, kde se dozvíte různé strategie, jak optimalizovat eth_calls
.
Snížení běhové režie eth_calls
U eth_calls
, které nelze odstranit, lze jejich režii za běhu minimalizovat jejich deklarací v manifestu. Když graph-node
zpracovává blok, provede všechny deklarované eth_calls
paralelně před spuštěním obslužných programů. Volání, která nejsou deklarována, se při běhu obslužných programů provádějí postupně. Zlepšení běhu je způsobeno tím, že se volání provádějí paralelně, nikoliv sekvenčně - to pomáhá zkrátit celkový čas strávený voláním, ale zcela ho neeliminuje.
V současné době lze eth_calls
deklarovat pouze pro obsluhy událostí. V manifestu napište
1event: TransferWithPool(address indexed, address indexed, uint256, bytes32 indexed)2handler: handleTransferWithPool3calls:4 ERC20.poolInfo: ERC20[event.address].getPoolInfo(event.params.to)
Žlutě zvýrazněná část je deklarace volání. Část před dvojtečkou je pouze textový popisek, který se používá pouze pro chybová hlášení. Část za dvojtečkou má tvar Contract[address].function(params)
. Přípustné hodnoty pro adresu a params jsou event.address
a event.params.<name>
.
Samotná obslužná rutina přistupuje k výsledku tohoto eth_call
přesně tak, jak je uvedeno v předchozí části, a to navázáním na smlouvu a provedením volání. graph-node cachuje výsledky deklarovaných eth_call
v paměti a volání obslužné rutiny získá výsledek z této paměťové cache místo skutečného volání RPC.
Note: Declared eth_calls can only be made in Subgraphs with specVersion >= 1.2.0.
Závěr
You can significantly improve indexing performance by minimizing or eliminating eth_calls
in your Subgraphs.