|
| 1 | +--- |
| 2 | +title: Subgraph Best Practice 4 - Improve Indexing Speed by Avoiding eth_calls |
| 3 | +--- |
| 4 | + |
| 5 | +## TLDR |
| 6 | + |
| 7 | +`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`. |
| 8 | + |
| 9 | +## Why Avoiding `eth_calls` Is a Best Practice |
| 10 | + |
| 11 | +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. |
| 12 | + |
| 13 | +### What Does an eth_call Look Like? |
| 14 | + |
| 15 | +`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: |
| 16 | + |
| 17 | +```yaml |
| 18 | +event Transfer(address indexed from, address indexed to, uint256 value); |
| 19 | +``` |
| 20 | + |
| 21 | +Suppose the tokens' pool membership is determined by a state variable named `getPoolInfo`. In this case, we would need to use an `eth_call` to query this data: |
| 22 | + |
| 23 | +```typescript |
| 24 | +import { Address } from '@graphprotocol/graph-ts' |
| 25 | +import { ERC20, Transfer } from '../generated/ERC20/ERC20' |
| 26 | +import { TokenTransaction } from '../generated/schema' |
| 27 | + |
| 28 | +export function handleTransfer(event: Transfer): void { |
| 29 | + let transaction = new TokenTransaction(event.transaction.hash.toHex()) |
| 30 | + |
| 31 | + // Bind the ERC20 contract instance to the given address: |
| 32 | + let instance = ERC20.bind(event.address) |
| 33 | + |
| 34 | + // Retrieve pool information via eth_call |
| 35 | + let poolInfo = instance.getPoolInfo(event.params.to) |
| 36 | + |
| 37 | + transaction.pool = poolInfo.toHexString() |
| 38 | + transaction.from = event.params.from.toHexString() |
| 39 | + transaction.to = event.params.to.toHexString() |
| 40 | + transaction.value = event.params.value |
| 41 | + |
| 42 | + transaction.save() |
| 43 | +} |
| 44 | +``` |
| 45 | + |
| 46 | +This is functional, however is not ideal as it slows down our subgraph’s indexing. |
| 47 | + |
| 48 | +## How to Eliminate `eth_calls` |
| 49 | + |
| 50 | +Ideally, the smart contract should be updated to emit all necessary data within events. For instance, modifying the smart contract to include pool information in the event could eliminate the need for `eth_calls`: |
| 51 | + |
| 52 | +``` |
| 53 | +event TransferWithPool(address indexed from, address indexed to, uint256 value, bytes32 indexed poolInfo); |
| 54 | +``` |
| 55 | + |
| 56 | +With this update, the subgraph can directly index the required data without external calls: |
| 57 | + |
| 58 | +```typescript |
| 59 | +import { Address } from '@graphprotocol/graph-ts' |
| 60 | +import { ERC20, TransferWithPool } from '../generated/ERC20/ERC20' |
| 61 | +import { TokenTransaction } from '../generated/schema' |
| 62 | + |
| 63 | +export function handleTransferWithPool(event: TransferWithPool): void { |
| 64 | + let transaction = new TokenTransaction(event.transaction.hash.toHex()) |
| 65 | + |
| 66 | + transaction.pool = event.params.poolInfo.toHexString() |
| 67 | + transaction.from = event.params.from.toHexString() |
| 68 | + transaction.to = event.params.to.toHexString() |
| 69 | + transaction.value = event.params.value |
| 70 | + |
| 71 | + transaction.save() |
| 72 | +} |
| 73 | +``` |
| 74 | + |
| 75 | +This is much more performant as it has eliminated the need for `eth_calls`. |
| 76 | + |
| 77 | +## How to Optimize `eth_calls` |
| 78 | + |
| 79 | +If modifying the smart contract is not possible and `eth_calls` are required, read “[Improve Subgraph Indexing Performance Easily: Reduce eth_calls](https://thegraph.com/blog/improve-subgraph-performance-reduce-eth-calls/)” by Simon Emanuel Schmid to learn various strategies on how to optimize `eth_calls`. |
| 80 | + |
| 81 | +## Reducing the Runtime Overhead of `eth_calls` |
| 82 | + |
| 83 | +For the `eth_calls` that can not be eliminated, the runtime overhead they introduce can be minimized by declaring them in the manifest. When `graph-node` processes a block it performs all declared `eth_calls` in parallel before handlers are run. Calls that are not declared are executed sequentially when handlers run. The runtime improvement comes from performing calls in parallel rather than sequentially - that helps reduce the total time spent in calls but does not eliminate it completely. |
| 84 | + |
| 85 | +Currently, `eth_calls` can only be declared for event handlers. In the manifest, write |
| 86 | + |
| 87 | +```yaml |
| 88 | +event: TransferWithPool(address indexed, address indexed, uint256, bytes32 indexed) |
| 89 | +handler: handleTransferWithPool |
| 90 | +calls: |
| 91 | + ERC20.poolInfo: ERC20[event.address].getPoolInfo(event.params.to) |
| 92 | +``` |
| 93 | +
|
| 94 | +The portion highlighted in yellow is the call declaration. The part before the colon is simply a text label that is only used for error messages. The part after the colon has the form `Contract[address].function(params)`. Permissible values for address and params are `event.address` and `event.params.<name>`. |
| 95 | + |
| 96 | +The handler itself accesses the result of this `eth_call` exactly as in the previous section by binding to the contract and making the call. graph-node caches the results of declared `eth_calls` in memory and the call from the handler will retrieve the result from this in memory cache instead of making an actual RPC call. |
| 97 | + |
| 98 | +Note: Declared eth_calls can only be made in subgraphs with specVersion >= 1.2.0. |
| 99 | + |
| 100 | +## Conclusion |
| 101 | + |
| 102 | +We can significantly improve indexing performance by minimizing or eliminating `eth_calls` in our subgraphs. |
0 commit comments