17 minutos
Funções Avançadas de Subgraph
Visão geral
Add and implement advanced Subgraph features to enhanced your Subgraph’s built.
Starting from specVersion
0.0.4
, Subgraph features must be explicitly declared in the features
section at the top level of the manifest file, using their camelCase
name, as listed in the table below:
Função | Nome |
---|---|
Erros não fatais | nonFatalErrors |
Busca em full-text | fullTextSearch |
Enxertos | grafting |
For instance, if a Subgraph uses the Full-Text Search and the Non-fatal Errors features, the features
field in the manifest should be:
1specVersion: 1.3.02description: Gravatar for Ethereum3features:4 - fullTextSearch5 - nonFatalErrors6dataSources: ...
Note that using a feature without declaring it will incur a validation error during Subgraph deployment, but no errors will occur if a feature is declared but not used.
Séries de Tempo e Agregações
Pré-requisitos:
- O specVersion do subgraph deve ser maior que 1.1.0.
Timeseries and aggregations enable your Subgraph to track statistics like daily average price, hourly total transfers, and more.
This feature introduces two new types of Subgraph entity. Timeseries entities record data points with timestamps. Aggregation entities perform pre-declared calculations on the timeseries data points on an hourly or daily basis, then store the results for easy access via GraphQL.
Exemplo de Schema
1type Data @entity(timeseries: true) {2 id: Int8!3 timestamp: Timestamp!4 price: BigDecimal!5}67type Stats @aggregation(intervals: ["hour", "day"], source: "Data") {8 id: Int8!9 timestamp: Timestamp!10 sum: BigDecimal! @aggregate(fn: "sum", arg: "price")11}
Como Definir Séries Temporais e Agregações
Entidades de séries temporais são definidas com @entity(timeseries: true)
no schema da GraphQL. Toda entidade deste tipo deve:
- ter uma ID exclusiva do tipo int8
- ter um registro de data e hora do tipo Timestamp
- incluir dados a serem usados para cálculo pelas entidades de agregação.
Estas entidades de Série Temporal podem ser guardadas em handlers regulares de gatilho, e atuam como “dados brutos” para as entidades de agregação.
As entidades de agregação são definidas com @aggregation
no schema da GraphQL. Toda entidade deste tipo define a fonte de onde retirará dados (que deve ser uma entidade de Série Temporal), determina os intervalos (por ex., hora, dia) e especifica a função de agregação que usará (por ex., soma, contagem, min, max, primeiro, último).
As entidades de agregação são calculadas automaticamente com base na fonte especificada no final do intervalo necessário.
Intervalos de Agregação Disponíveis
hour
: configura o período de série de tempo para cada hora, em ponto.day
: configura o período de série de tempo para cada dia, a começar e terminar à meia-noite.
Funções de Agregação Disponíveis
sum
: Total de todos os valores.count
: Número de valores.min
: Valor mínimo.max
: Valor máximo.first
: Primeiro valor no período.last
: Último valor no período.
Exemplo de Query de Agregações
1{2 stats(interval: "hour", where: { timestamp_gt: 1704085200 }) {3 id4 timestamp5 sum6 }7}
Leia mais sobre Séries Temporais e Agregações.
Erros não-fatais
Indexing errors on already synced Subgraphs will, by default, cause the Subgraph to fail and stop syncing. Subgraphs can alternatively be configured to continue syncing in the presence of errors, by ignoring the changes made by the handler which provoked the error. This gives Subgraph authors time to correct their Subgraphs while queries continue to be served against the latest block, though the results might be inconsistent due to the bug that caused the error. Note that some errors are still always fatal. To be non-fatal, the error must be known to be deterministic.
Note: The Graph Network does not yet support non-fatal errors, and developers should not deploy Subgraphs using that functionality to the network via the Studio.
Enabling non-fatal errors requires setting the following feature flag on the Subgraph manifest:
1specVersion: 1.3.02description: Gravatar for Ethereum3features:4 - nonFatalErrors5 ...
The query must also opt-in to querying data with potential inconsistencies through the subgraphError
argument. It is also recommended to query _meta
to check if the Subgraph has skipped over errors, as in the example:
1foos(first: 100, subgraphError: allow) {2 id3}45_meta {6 hasIndexingErrors7}
If the Subgraph encounters an error, that query will return both the data and a graphql error with the message "indexing_error"
, as in this example response:
1"data": {2 "foos": [3 {4 "id": "0xdead"5 }6 ],7 "_meta": {8 "hasIndexingErrors": true9 }10},11"errors": [12 {13 "message": "indexing_error"14 }15]
Fontes de Dados de Arquivos em IPFS/Arweave
File data sources are a new Subgraph functionality for accessing off-chain data during indexing in a robust, extendable way. File data sources support fetching files from IPFS and from Arweave.
Isto também abre as portas para indexar dados off-chain de forma determinística, além de potencialmente introduzir dados arbitrários com fonte em HTTP.
Visão geral
Em vez de buscar arquivos “em fila” durante a execução do handler, isto introduz modelos que podem ser colocados como novas fontes de dados para um identificador de arquivos. Estas novas fontes de dados pegam os arquivos e tentam novamente caso não obtenham êxito; quando o arquivo é encontrado, executam um handler dedicado.
Isso é semelhante aos modelos existentes de fonte de dados, usados para criar dinamicamente novas fontes de dados baseados em chain.
Isto substitui a API ipfs.cat
existente
Guia de atualização
Atualização de graph-ts
e graph-cli
O recurso de fontes de dados de arquivos exige o graph-ts na versão acima de 0.29.0 e o graph-cli acima de 0.33.1
Adicionar um novo tipo de entidade que será atualizado quando os arquivos forem encontrados
Fontes de dados de arquivos não podem acessar ou atualizar entidades baseadas em chain, mas devem atualizar entidades específicas a arquivos.
Isto pode implicar separar campos de entidades existentes em entidades separadas, ligadas juntas.
Entidade combinada original:
1type Token @entity {2 id: ID!3 tokenID: BigInt!4 tokenURI: String!5 externalURL: String!6 ipfsURI: String!7 image: String!8 name: String!9 description: String!10 type: String!11 updatedAtTimestamp: BigInt12 owner: User!13}
Entidade nova, separada:
1type Token @entity {2 id: ID!3 tokenID: BigInt!4 tokenURI: String!5 ipfsURI: TokenMetadata6 updatedAtTimestamp: BigInt7 owner: String!8}910type TokenMetadata @entity {11 id: ID!12 image: String!13 externalURL: String!14 name: String!15 description: String!16}
Se o relacionamento for perfeitamente proporcional entre a entidade parente e a entidade de fontes de dados de arquivos resultante, é mais simples ligar a entidade parente a uma entidade de arquivos resultante, com a CID IPFS como o assunto de busca. Se tiver dificuldades em modelar suas novas entidades baseadas em arquivos, pergunte no Discord!
É possível usar filtros aninhados para filtrar entidades parentes, com base nestas entidades aninhadas.
Adicione um novo modelo de fonte de dados com kind: file/ipfs
ou kind: file/arweave
Esta é a fonte de dados que será gerada quando um arquivo de interesse for identificado.
1templates:2 - name: TokenMetadata3 kind: file/ipfs4 mapping:5 apiVersion: 0.0.96 language: wasm/assemblyscript7 file: ./src/mapping.ts8 handler: handleMetadata9 entities:10 - TokenMetadata11 abis:12 - name: Token13 file: ./abis/Token.json
Atualmente é obrigatório usar abis
, mas não é possível chamar contratos de dentro de fontes de dados de arquivos
A fonte de dados de arquivos deve mencionar, especificamente, todos os tipos de entidades com os quais ela interagirá sob entities
. Veja as limitações para mais detalhes.
Criar um novo handler para processar arquivos
Este handler deve aceitar um parâmetro Bytes
, que consistirá dos conteúdos do arquivo; quando encontrado, este poderá ser acessado. Isto costuma ser um arquivo JSON, que pode ser processado com helpers graph-ts
(documentação).
A CID do arquivo como um string legível pode ser acessada através do dataSource
a seguir:
1const cid = dataSource.stringParam()
Exemplo de handler:
1import { json, Bytes, dataSource } from '@graphprotocol/graph-ts'2import { TokenMetadata } from '../generated/schema'34export function handleMetadata(content: Bytes): void {5 let tokenMetadata = new TokenMetadata(dataSource.stringParam())6 const value = json.fromBytes(content).toObject()7 if (value) {8 const image = value.get('image')9 const name = value.get('name')10 const description = value.get('description')11 const externalURL = value.get('external_url')1213 if (name && image && description && externalURL) {14 tokenMetadata.name = name.toString()15 tokenMetadata.image = image.toString()16 tokenMetadata.externalURL = externalURL.toString()17 tokenMetadata.description = description.toString()18 }1920 tokenMetadata.save()21 }22}
Gerar fontes de dados de arquivos quando for obrigatório
Agora pode criar fontes de dados de arquivos durante a execução de handlers baseados em chain:
- Importe o modelo do
templates
gerado automaticamente - chame o
TemplateName.create(cid: string)
de dentro de um mapeamento, onde o cid é um identificador de conteúdo válido para IPFS ou Arweave
Para o IPFS, o Graph Node apoia identificadores de conteúdo v0 e v1 e identificadores com diretórios (por ex. bafyreighykzv2we26wfrbzkcdw37sbrby4upq7ae3aqobbq7i4er3tnxci/metadata.json
).
Para o Arweave, a partir da versão 0.33.0, o Graph Node pode buscar arquivos armazenados no Arweave com base no seu ID de transação de um gateway Arweave (exemplo de arquivo). O Arweave apoia transações enviadas via Irys (antigo Bundlr), e o Graph Node também pode solicitar arquivos com base em manifests do Irys.
Exemplo:
1import { TokenMetadata as TokenMetadataTemplate } from '../generated/templates'23const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'4//This example code is for a Crypto coven Subgraph. The above ipfs hash is a directory with token metadata for all crypto coven NFTs.56export function handleTransfer(event: TransferEvent): void {7 let token = Token.load(event.params.tokenId.toString())8 if (!token) {9 token = new Token(event.params.tokenId.toString())10 token.tokenID = event.params.tokenId1112 token.tokenURI = '/' + event.params.tokenId.toString() + '.json'13 const tokenIpfsHash = ipfshash + token.tokenURI14 //This creates a path to the metadata for a single Crypto coven NFT. It concats the directory with "/" + filename + ".json"1516 token.ipfsURI = tokenIpfsHash1718 TokenMetadataTemplate.create(tokenIpfsHash)19 }2021 token.updatedAtTimestamp = event.block.timestamp22 token.owner = event.params.to.toHexString()23 token.save()24}
Isto criará uma fonte de dados de arquivos, que avaliará o endpoint de IPFS ou Arweave configurado do Graph Node, e tentará novamente caso não achá-lo. Com o arquivo localizado, o handler da fonte de dados de arquivos será executado.
Este exemplo usa a CID como a consulta entre a entidade parente Token
e a entidade TokenMetadata
resultante.
Previously, this is the point at which a Subgraph developer would have called ipfs.cat(CID)
to fetch the file
Parabéns, você está a usar fontes de dados de arquivos!
Deploying your Subgraphs
You can now build
and deploy
your Subgraph to any Graph Node >=v0.30.0-rc.0.
Limitações
File data source handlers and entities are isolated from other Subgraph entities, ensuring that they are deterministic when executed, and ensuring no contamination of chain-based data sources. To be specific:
- Entidades criadas por Fontes de Dados de Arquivos são imutáveis, e não podem ser atualizadas
- Handlers de Fontes de Dados de Arquivos não podem acessar entidades de outras fontes de dados de arquivos
- Entidades associadas com Fontes de Dados de Arquivos não podem ser acessadas por handlers baseados em chain
While this constraint should not be problematic for most use-cases, it may introduce complexity for some. Please get in touch via Discord if you are having issues modelling your file-based data in a Subgraph!
Além disto, não é possível criar fontes de dados de uma fonte de dado de arquivos, seja uma on-chain ou outra fonte de dados de arquivos. Esta restrição poderá ser retirada no futuro.
Boas práticas
Caso ligue metadados de NFTs a tokens correspondentes, use o hash IPFS destes para referenciar uma entidade de Metadados da entidade do Token. Salve a entidade de Metadados a usar o hash IPFS como ID.
Você pode usar o contexto de DataSource ao criar Fontes de Dados de Arquivo (FDS), para passar informações extras que estarão disponíveis para o handler de FDS.
Caso tenha entidades a ser atualizadas várias vezes, crie entidades únicas baseadas em arquivos utilizando o hash IPFS e o ID da entidade, e as referencie com um campo derivado na entidade baseada na chain.
Estamos a melhorar a recomendação acima, para que os queries retornem apenas a versão “mais recente”
Problemas conhecidos
Fontes de dados de arquivo atualmente requerem ABIs, mesmo que estas não sejam usadas (problema). Por enquanto, vale a pena adicionar qualquer ABI como alternativa.
Handlers para Fontes de Dados de Arquivos não podem estar em arquivos que importam ligações de contrato eth_call
, o que causa falhas com “unknown import: ethereum::ethereum.call
has not been defined” (problema no GitHub). A solução atual é criar handlers de fontes de dados de arquivos num arquivo dedicado.
Exemplos
Migração de Subgraph do Crypto Coven
Referências
Fontes de Dados de Arquivos GIP
Filtros de Argumentos Indexados / Filtros de Tópicos
Obrigatório: SpecVersion >= 1.2.0
Topic filters, also known as indexed argument filters, are a powerful feature in Subgraphs that allow users to precisely filter blockchain events based on the values of their indexed arguments.
-
These filters help isolate specific events of interest from the vast stream of events on the blockchain, allowing Subgraphs to operate more efficiently by focusing only on relevant data.
-
This is useful for creating personal Subgraphs that track specific addresses and their interactions with various smart contracts on the blockchain.
Como Filtros de Tópicos Funcionam
When a smart contract emits an event, any arguments that are marked as indexed can be used as filters in a Subgraph’s manifest. This allows the Subgraph to listen selectively for events that match these indexed arguments.
- O primeiro argumento indexado do evento corresponde ao
topic1
, o segundo aotopic2
, e por aí vai até otopic3
, já que a Máquina Virtual de Ethereum (EVM) só permite até três argumentos indexados por evento.
1// SPDX-License-Identifier: MIT2pragma solidity ^0.8.0;34contract Token {5 // Declaração de evento com parâmetros indexados para endereços6 event Transfer(address indexed from, address indexed to, uint256 value);78 // Função para simular a transferência de tokens9 function transfer(address to, uint256 value) public {10 // Emitting the Transfer event with from, to, and value11 emit Transfer(msg.sender, to, value);12 }13}
Neste exemplo:
- O evento
Transfer
é usado para gravar transações de tokens entre endereços. - Os parâmetros
from
eto
são indexados, o que permite que ouvidores de eventos filtrem e monitorizem transferências que envolvem endereços específicos. - A função
transfer
é uma representação simples de uma ação de transferência de token, e emite o evento Transfer sempre que é chamada.
Configuração em Subgraphs
Topic filters are defined directly within the event handler configuration in the Subgraph manifest. Here is how they are configured:
1eventHandlers:2 - event: SomeEvent(indexed uint256, indexed address, indexed uint256)3 handler: handleSomeEvent4 topic1: ['0xValue1', '0xValue2']5 topic2: ['0xAddress1', '0xAddress2']6 topic3: ['0xValue3']
Neste cenário:
topic1
corresponde ao primeiro argumento indexado do evento,topic2
ao segundo, etopic3
ao terceiro.- Cada tópico pode ter um ou mais valores, e um evento só é processado se corresponder a um dos valores em cada tópico especificado.
Lógica de Filtro
- Dentro de um Tópico Único: A lógica funciona como uma condição OR. O evento será processado se corresponder a qualquer dos valores listados num tópico.
- Entre Tópicos Diferentes: A lógica funciona como uma condição AND. Um evento deve atender a todas as condições especificadas em vários tópicos para acionar o handler associado.
Exemplo 1: Como Rastrear Transferências Diretas do Endereço A ao Endereço B
1eventHandlers:2 - event: Transfer(indexed address,indexed address,uint256)3 handler: handleDirectedTransfer4 topic1: ['0xAddressA'] # Sender Address5 topic2: ['0xAddressB'] # Receiver Address
Nesta configuração:
topic1
é configurado para filtrar eventosTransfer
onde0xAddressA
é o remetente.topic2
é configurado para filtrar eventosTransfer
onde0xAddressB
é o destinatário.- The Subgraph will only index transactions that occur directly from
0xAddressA
to0xAddressB
.
Exemplo 2: Como Rastrear Transações em Qualquer Direção Entre Dois ou Mais Endereços
1eventHandlers:2 - event: Transfer(indexed address,indexed address,uint256)3 handler: handleTransferToOrFrom4 topic1: ['0xAddressA', '0xAddressB', '0xAddressC'] # Endereço do Remetente5 topic2: ['0xAddressB', '0xAddressC'] # Endereço do Destinatário
Nesta configuração:
- O
topic1
é configurado para filtrar eventosTransfer
onde0xAddressA
,0xAddressB
,0xAddressC
é o remetente. topic2
é configurado para filtrar eventosTransfer
onde0xAddressB
e0xAddressC
é o destinatário.- The Subgraph will index transactions that occur in either direction between multiple addresses allowing for comprehensive monitoring of interactions involving all addresses.
eth_call declarada
Nota: Esta é uma função experimental que atualmente não está disponível numa versão estável do Graph Node, e só pode ser usada no Subgraph Studio ou no seu node auto-hospedado.
Declarative eth_calls
are a valuable Subgraph feature that allows eth_calls
to be executed ahead of time, enabling graph-node
to execute them in parallel.
Esta ferramenta faz o seguinte:
- Significantly improves the performance of fetching data from the Ethereum blockchain by reducing the total time for multiple calls and optimizing the Subgraph’s overall efficiency.
- Permite retiros de dados mais rápidos, o que resulta em respostas de query aceleradas e uma experiência de utilizador melhorada.
- Reduz tempos de espera para aplicativos que precisam agregar dados de várias chamadas no Ethereum, o que aumenta a eficácia do processo de retiro de dados.
Conceitos Importantes
eth_calls
declarativas: Chamadas no Ethereum definidas para serem executadas em paralelo, e não em sequência.- Execução Paralela: Ao invés de esperar o término de uma chamada para começar a próxima, várias chamadas podem ser iniciadas simultaneamente.
- Eficácia de Tempo: O total de tempo levado para todas as chamadas muda da soma dos tempos de chamadas individuais (sequencial) para o tempo levado para a chamada mais longa (paralelo).
Cenário sem eth_calls
Declarativas
Imagine you have a Subgraph that needs to make three Ethereum calls to fetch data about a user’s transactions, balance, and token holdings.
Tradicionalmente, estas chamadas podem ser realizadas em sequência:
- Chamada 1 (Transações): Leva 3 segundos
- Chamada 2 (Saldo): Leva 2 segundos
- Chamada 3 (Posses de Token): Leva 4 segundos
Total de tempo: 3 + 2 + 4 = 9 segundos
Cenário com eth_calls
Declarativas
Com esta ferramenta, é possível declarar que estas chamadas sejam executadas em paralelo:
- Chamada 1 (Transações): Leva 3 segundos
- Chamada 2 (Saldo): Leva 2 segundos
- Chamada 3 (Posses de Token): Leva 4 segundos
Como estas chamadas são executadas em paralelo, o total de tempo é igual ao tempo gasto pela chamada mais longa.
Total de tempo = max (3, 2, 4) = 4 segundos
Como Funciona
- Declarative Definition: In the Subgraph manifest, you declare the Ethereum calls in a way that indicates they can be executed in parallel.
- Motor de Execução Paralela: O motor de execução do Graph Node reconhece estas declarações e executa as chamadas simultaneamente.
- Result Aggregation: Once all calls are complete, the results are aggregated and used by the Subgraph for further processing.
Exemplo de Configuração no Manifest do Subgraph
eth_calls
declaradas podem acessar o event.address
do evento subjacente, assim como todos os event.params
.
subgraph.yaml
using event.address
:
1eventHandlers:2event: Swap(indexed address,indexed address,int256,int256,uint160,uint128,int24)3handler: handleSwap4calls:5 global0X128: Pool[event.address].feeGrowthGlobal0X128()6 global1X128: Pool[event.address].feeGrowthGlobal1X128()
Detalhes para o exemplo acima:
global0X128
é aeth_call
declarada.- O texto antes dos dois pontos (
global0X128
) é o rótulo para estaeth_call
que é usado ao registar erros. - O texto (
Pool[event.address].feeGrowthGlobal0X128()
) é aeth_call
a ser executada, que está na forma doContract[address].function(arguments)
- O
address
e oarguments
podem ser substituídos por variáveis a serem disponibilizadas quando o handler for executado.
subgraph.yaml
using event.params
1calls:2 - ERC20DecimalsToken0: ERC20[event.params.token0].decimals()
Como Enxertar em Subgraphs Existentes
Observação: não é recomendado usar enxertos quando começar a atualização para a The Graph Network. Aprenda mais aqui.
When a Subgraph is first deployed, it starts indexing events at the genesis block of the corresponding chain (or at the startBlock
defined with each data source) In some circumstances; it is beneficial to reuse the data from an existing Subgraph and start indexing at a much later block. This mode of indexing is called Grafting. Grafting is, for example, useful during development to get past simple errors in the mappings quickly or to temporarily get an existing Subgraph working again after it has failed.
A Subgraph is grafted onto a base Subgraph when the Subgraph manifest in subgraph.yaml
contains a graft
block at the top-level:
1description: ...2graft:3 base: Qm... # Subgraph ID of base Subgraph4 block: 7345624 # Block number
When a Subgraph whose manifest contains a graft
block is deployed, Graph Node will copy the data of the base
Subgraph up to and including the given block
and then continue indexing the new Subgraph from that block on. The base Subgraph must exist on the target Graph Node instance and must have indexed up to at least the given block. Because of this restriction, grafting should only be used during development or during an emergency to speed up producing an equivalent non-grafted Subgraph.
Because grafting copies rather than indexes base data, it is much quicker to get the Subgraph to the desired block than indexing from scratch, though the initial data copy can still take several hours for very large Subgraphs. While the grafted Subgraph is being initialized, the Graph Node will log information about the entity types that have already been copied.
The grafted Subgraph can use a GraphQL schema that is not identical to the one of the base Subgraph, but merely compatible with it. It has to be a valid Subgraph schema in its own right, but may deviate from the base Subgraph’s schema in the following ways:
- Ele adiciona ou remove tipos de entidade
- Ele retira atributos de tipos de entidade
- Ele adiciona atributos anuláveis a tipos de entidade
- Ele transforma atributos não anuláveis em atributos anuláveis
- Ele adiciona valores a enums
- Ele adiciona ou remove interfaces
- Ele muda os tipos de entidades para qual implementar uma interface
Feature Management: grafting
must be declared under features
in the Subgraph manifest.