Advance Subgraph Features
Reading time: 17 min
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:
For instance, if a subgraph uses the Full-Text Search and the Non-fatal Errors features, the features
field in the manifest should be:
specVersion: 0.0.4description: Gravatar for Ethereumfeatures:- fullTextSearch- nonFatalErrorsdataSources: ...
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.
Les séries chronologiques et les agrégations permettent à votre subgraph de suivre des statistiques telles que le prix moyen journalier, le total des transferts par heure, etc.
Cette fonctionnalité introduit deux nouveaux types d'entités de subgraph. Les entités de séries chronologiques enregistrent des points de données avec des horodatages. Les entités d'agrégation effectuent des calculs pré-déclarés sur les points de données des séries chronologiques sur une base horaire ou quotidienne, puis stockent les résultats pour un accès facile via GraphQL.
type Data @entity(timeseries: true) {id: Int8!timestamp: Timestamp!price: BigDecimal!}type Stats @aggregation(intervals: ["hour", "day"], source: "Data") {id: Int8!timestamp: Timestamp!sum: BigDecimal! @aggregate(fn: "sum", arg: "price")}
Timeseries entities are defined with @entity(timeseries: true)
in schema.graphql. Every timeseries entity must have a unique ID of the int8 type, a timestamp of the Timestamp type, and include data that will be used for calculation by aggregation entities. These Timeseries entities can be saved in regular trigger handlers, and act as the “raw data” for the Aggregation entities.
Aggregation entities are defined with @aggregation
in schema.graphql. Every aggregation entity defines the source from which it will gather data (which must be a Timeseries entity), sets the intervals (e.g., hour, day), and specifies the aggregation function it will use (e.g., sum, count, min, max, first, last). Aggregation entities are automatically calculated on the basis of the specified source at the end of the required interval.
hour
: sets the timeseries period every hour, on the hour.day
: sets the timeseries period every day, starting and ending at 00:00.
sum
: Total of all values.count
: Number of values.min
: Minimum value.max
: Maximum value.first
: First value in the period.last
: Last value in the period.
{stats(interval: "hour", where: { timestamp_gt: 1704085200 }) {idtimestampsum}}
Remarque:
Pour utiliser les Séries Chronologiques et les Agrégations, un subgraph doit avoir une spec Version ≥1.1.0. Notez que cette fonctionnalité pourrait subir des changements significatifs affectant la compatibilité rétroactive.
about Timeseries and Aggregations.
Les erreurs d'indexation sur les subgraphs déjà synchronisés entraîneront, par défaut, l'échec du subgraph et l'arrêt de la synchronisation. Les subgraphs peuvent également être configurés pour continuer la synchronisation en présence d'erreurs, en ignorant les modifications apportées par le gestionnaire qui a provoqué l'erreur. Cela donne aux auteurs de subgraphs le temps de corriger leurs subgraphs pendant que les requêtes continuent d'être traitées sur le dernier bloc, bien que les résultats puissent être incohérents en raison du bogue à l'origine de l'erreur. Notez que certaines erreurs sont toujours fatales. Pour être non fatale, l'erreur doit être connue pour être déterministe.
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.
L'activation des erreurs non fatales nécessite la définition de l'indicateur de fonctionnalité suivant sur le manifeste du subgraph :
specVersion: 0.0.4description: Gravatar for Ethereumfeatures:- nonFatalErrors...
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:
foos(first: 100, subgraphError: allow) {id}_meta {hasIndexingErrors}
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:
"data": {"foos": [{"id": "0xdead"}],"_meta": {"hasIndexingErrors": true}},"errors": [{"message": "indexing_error"}]
Les sources de données de fichiers sont une nouvelle fonctionnalité de subgraph permettant d'accéder aux données hors chaîne pendant l'indexation de manière robuste et extensible. Les sources de données de fichiers prennent en charge la récupération de fichiers depuis IPFS et Arweave.
Cela jette également les bases d’une indexation déterministe des données hors chaîne, ainsi que de l’introduction potentielle de données arbitraires provenant de HTTP.
Plutôt que de récupérer les fichiers "ligne par ligne" pendant l'exécution du gestionnaire, ceci introduit des modèles qui peuvent être générés comme nouvelles sources de données pour un identifiant de fichier donné. Ces nouvelles sources de données récupèrent les fichiers, réessayant en cas d'échec, et exécutant un gestionnaire dédié lorsque le fichier est trouvé.
This is similar to the , which are used to dynamically create new chain-based data sources.
This replaces the existing ipfs.cat
API
File data sources requires graph-ts >=0.29.0 and graph-cli >=0.33.1
Les sources de données de fichier ne peuvent pas accéder ni mettre à jour les entités basées sur une chaîne, mais doivent mettre à jour les entités spécifiques au fichier.
Cela peut impliquer de diviser les champs des entités existantes en entités distinctes, liées entre elles.
Entité combinée d'origine :
type Token @entity {id: ID!tokenID: BigInt!tokenURI: String!externalURL: String!ipfsURI: String!image: String!name: String!description: String!type: String!updatedAtTimestamp: BigIntowner: User!}
Nouvelle entité scindée :
type Token @entity {id: ID!tokenID: BigInt!tokenURI: String!ipfsURI: TokenMetadataupdatedAtTimestamp: BigIntowner: String!}type TokenMetadata @entity {id: ID!image: String!externalURL: String!name: String!description: String!}
Si la relation est 1:1 entre l'entité parent et l'entité de source de données de fichier résultante, le modèle le plus simple consiste à lier l'entité parent à une entité de fichier résultante en utilisant le CID IPFS comme recherche. Contactez Discord si vous rencontrez des difficultés pour modéliser vos nouvelles entités basées sur des fichiers !
Il s'agit de la source de données qui sera générée lorsqu'un fichier d'intérêt est identifié.
templates:- name: TokenMetadatakind: file/ipfsmapping:apiVersion: 0.0.7language: wasm/assemblyscriptfile: ./src/mapping.tshandler: handleMetadataentities:- TokenMetadataabis:- name: Tokenfile: ./abis/Token.json
Currently abis
are required, though it is not possible to call contracts from within file data sources
The file data source must specifically mention all the entity types which it will interact with under entities
. See for more details.
This handler should accept one Bytes
parameter, which will be the contents of the file, when it is found, which can then be processed. This will often be a JSON file, which can be processed with graph-ts
helpers ().
The CID of the file as a readable string can be accessed via the dataSource
as follows:
const cid = dataSource.stringParam()
Exemple de gestionnaire :
import { json, Bytes, dataSource } from '@graphprotocol/graph-ts'import { TokenMetadata } from '../generated/schema'export function handleMetadata(content: Bytes): void {let tokenMetadata = new TokenMetadata(dataSource.stringParam())const value = json.fromBytes(content).toObject()if (value) {const image = value.get('image')const name = value.get('name')const description = value.get('description')const externalURL = value.get('external_url')if (name && image && description && externalURL) {tokenMetadata.name = name.toString()tokenMetadata.image = image.toString()tokenMetadata.externalURL = externalURL.toString()tokenMetadata.description = description.toString()}tokenMetadata.save()}}
Vous pouvez désormais créer des sources de données de fichiers lors de l'exécution de gestionnaires basés sur une chaîne :
- Import the template from the auto-generated
templates
- call
TemplateName.create(cid: string)
from within a mapping, where the cid is a valid content identifier for IPFS or Arweave
For IPFS, Graph Node supports , and content identifers with directories (e.g. bafyreighykzv2we26wfrbzkcdw37sbrby4upq7ae3aqobbq7i4er3tnxci/metadata.json
).
For Arweave, as of version 0.33.0 Graph Node can fetch files stored on Arweave based on their from an Arweave gateway (). Arweave supports transactions uploaded via Irys (previously Bundlr), and Graph Node can also fetch files based on .
L'exemple:
import { TokenMetadata as TokenMetadataTemplate } from '../generated/templates'const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'//Cet exemple de code concerne un sous-graphe de Crypto coven. Le hachage ipfs ci-dessus est un répertoire contenant les métadonnées des jetons pour toutes les NFT de l'alliance cryptographique.export function handleTransfer(event: TransferEvent): void {let token = Token.load(event.params.tokenId.toString())if (!token) {token = new Token(event.params.tokenId.toString())token.tokenID = event.params.tokenIdtoken.tokenURI = '/' + event.params.tokenId.toString() + '.json'const tokenIpfsHash = ipfshash + token.tokenURI//Ceci crée un chemin vers les métadonnées pour un seul Crypto coven NFT. Il concatène le répertoire avec "/" + nom de fichier + ".json"token.ipfsURI = tokenIpfsHashTokenMetadataTemplate.create(tokenIpfsHash)}token.updatedAtTimestamp = event.block.timestamptoken.owner = event.params.to.toHexString()token.save()}
Cela créera une nouvelle source de données de fichier, qui interrogera le point d'extrémité IPFS ou Arweave configuré du nœud de graphique, en réessayant si elle n'est pas trouvée. Lorsque le fichier est trouvé, le gestionnaire de la source de données de fichier est exécuté.
This example is using the CID as the lookup between the parent Token
entity and the resulting TokenMetadata
entity.
Previously, this is the point at which a subgraph developer would have called ipfs.cat(CID)
to fetch the file
Félicitations, vous utilisez des sources de données de fichiers !
You can now build
and deploy
your subgraph to any Graph Node >=v0.30.0-rc.0.
Les entités et les gestionnaires de sources de données de fichiers sont isolés des autres entités du subgraph, ce qui garantit que leur exécution est déterministe et qu'il n'y a pas de contamination des sources de données basées sur des chaînes. Pour être plus précis :
- Les entités créées par les sources de données de fichiers sont immuables et ne peuvent pas être mises à jour
- Les gestionnaires de sources de données de fichiers ne peuvent pas accéder à des entités provenant d'autres sources de données de fichiers
- Les entités associées aux sources de données de fichiers ne sont pas accessibles aux gestionnaires basés sur des chaînes
Cette contrainte ne devrait pas poser de problème pour la plupart des cas d'utilisation, mais elle peut en compliquer certains. N'hésitez pas à nous contacter via Discord si vous rencontrez des problèmes pour modéliser vos données basées sur des fichiers dans un subgraph !
En outre, il n'est pas possible de créer des sources de données à partir d'une source de données de fichier, qu'il s'agisse d'une source de données onchain ou d'une autre source de données de fichier. Cette restriction pourrait être levée à l'avenir.
Si vous liez des métadonnées NFT aux jetons correspondants, utilisez le hachage IPFS des métadonnées pour référencer une entité Metadata à partir de l'entité Token. Enregistrez l'entité Metadata en utilisant le hachage IPFS comme identifiant.
You can use when creating File Data Sources to pass extra information which will be available to the File Data Source handler.
If you have entities which are refreshed multiple times, create unique file-based entities using the IPFS hash & the entity ID, and reference them using a derived field in the chain-based entity.
Nous travaillons à l'amélioration de la recommandation ci-dessus, afin que les requêtes ne renvoient que la version "la plus récente"
File data sources currently require ABIs, even though ABIs are not used (). Workaround is to add any ABI.
Handlers for File Data Sources cannot be in files which import eth_call
contract bindings, failing with "unknown import: ethereum::ethereum.call
has not been defined" (). Workaround is to create file data source handlers in a dedicated file.
Les filtres de topics, également connus sous le nom de filtres d'arguments indexés, sont une fonctionnalité puissante dans les subgraphs qui permettent aux utilisateurs de filtrer précisément les événements de la blockchain en fonction des valeurs de leurs arguments indexés.
-
Ces filtres aident à isoler des événements spécifiques intéressants parmi le vaste flux d'événements sur la blockchain, permettant aux subgraphs de fonctionner plus efficacement en se concentrant uniquement sur les données pertinentes.
-
Ceci est utile pour créer des subgraphs personnels qui suivent des adresses spécifiques et leurs interactions avec divers contrats intelligents sur la blockchain.
Lorsqu'un contrat intelligent émet un événement, tous les arguments marqués comme indexés peuvent être utilisés comme filtres dans le manifeste d'un subgraph. Ceci permet au subgraph d'écouter de façon sélective les événements qui correspondent à ces arguments indexés.
- The event's first indexed argument corresponds to
topic1
, the second totopic2
, and so on, up totopic3
, since the Ethereum Virtual Machine (EVM) allows up to three indexed arguments per event.
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract Token {// Déclaration de l'événement avec des paramètres indexés pour les adressesevent Transfer(address indexed from, address indexed to, uint256 value);// Fonction pour simuler le transfert de tokensfunction transfer(address to, uint256 value) public {// Emission de l'événement Transfer avec from, to, et valueemit Transfer(msg.sender, to, value);}}
Dans cet exemple:
- The
Transfer
event is used to log transactions of tokens between addresses. - The
from
andto
parameters are indexed, allowing event listeners to filter and monitor transfers involving specific addresses. - The
transfer
function is a simple representation of a token transfer action, emitting the Transfer event whenever it is called.
Les filtres de topics sont définis directement dans la configuration du gestionnaire d'évènement situé dans le manifeste du subgraph. Voici comment ils sont configurés :
eventHandlers:- event: SomeEvent(indexed uint256, indexed address, indexed uint256)handler: handleSomeEventtopic1: ['0xValue1', '0xValue2']topic2: ['0xAddress1', '0xAddress2']topic3: ['0xValue3']
Dans cette configuration :
topic1
corresponds to the first indexed argument of the event,topic2
to the second, andtopic3
to the third.- Chaque topic peut avoir une ou plusieurs valeurs, et un événement n'est traité que s'il correspond à l'une des valeurs de chaque rubrique spécifiée.
- Au sein d'une même Topic: La logique fonctionne comme une condition OR. L'événement sera traité s'il correspond à l'une des valeurs listées dans une rubrique donnée.
- Entre différents Topics: La logique fonctionne comme une condition AND. Un événement doit satisfaire toutes les conditions spécifiées à travers les différents topics pour déclencher le gestionnaire associé.
eventHandlers:- event: Transfer(indexed address,indexed address,uint256)handler: handleDirectedTransfertopic1: ['0xAddressA'] # Sender Addresstopic2: ['0xAddressB'] # Receiver Address
Dans cette configuration:
topic1
is configured to filterTransfer
events where0xAddressA
is the sender.topic2
is configured to filterTransfer
events where0xAddressB
is the receiver.- The subgraph will only index transactions that occur directly from
0xAddressA
to0xAddressB
.
eventHandlers:- event: Transfer(indexed address,indexed address,uint256)handler: handleTransferToOrFromtopic1: ['0xAddressA', '0xAddressB', '0xAddressC'] # Sender Addresstopic2: ['0xAddressB', '0xAddressC'] # Receiver Address
Dans cette configuration:
topic1
is configured to filterTransfer
events where0xAddressA
,0xAddressB
,0xAddressC
is the sender.topic2
is configured to filterTransfer
events where0xAddressB
and0xAddressC
is the receiver.- Le subgraph indexera les transactions qui se produisent dans les deux sens entre plusieurs adresses, permettant une surveillance complète des interactions impliquant toutes les adresses.
Remarque : Il s'agit d'une fonctionnalité expérimentale qui n'est pas encore disponible dans une version stable de Graph Node. Vous ne pouvez l'utiliser que dans Subgraph Studio ou sur votre nœud auto-hébergé.
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.
Cette fonctionnalité permet de :
- Améliorer de manière significative les performances de la récupération des données de la blockchain Ethereum en réduisant le temps total pour plusieurs appels et en optimisant l'efficacité globale du subgraph.
- Permet une récupération plus rapide des données, entraînant des réponses de requête plus rapides et une meilleure expérience utilisateur.
- Réduire les temps d'attente pour les applications qui doivent réunir des données de plusieurs appels Ethereum, rendant le processus de récupération des données plus efficace.
- Declarative
eth_calls
: Ethereum calls that are defined to be executed in parallel rather than sequentially. - Exécution en parallèle : Au lieu d'attendre la fin d'un appel avant de commencer le suivant, plusieurs appels peuvent être initiés simultanément.
- Efficacité temporelle : Le temps total nécessaire pour tous les appels passe de la somme des temps d'appel individuels (séquentiels) au temps pris par l'appel le plus long (parallèle).
Imaginez que vous ayez un subgraph qui doit effectuer trois appels Ethereum pour récupérer des données sur les transactions, le solde et les avoirs en jetons d'un utilisateur.
Traditionnellement, ces appels pourraient être effectués de manière séquentielle :
- Appel 1 (Transactions) : Prend 3 secondes
- Appel 2 (Solde) : Prend 2 secondes
- Appel 3 (Avoirs en jetons) : Prend 4 secondes
Temps total pris = 3 + 2 + 4 = 9 secondes
Avec cette fonctionnalité, vous pouvez déclarer que ces appels soient exécutés en parallèle :
- Appel 1 (Transactions) : Prend 3 secondes
- Appel 2 (Solde) : Prend 2 secondes
- Appel 3 (Avoirs en jetons) : Prend 4 secondes
Puisque ces appels sont exécutés en parallèle, le temps total pris est égal au temps pris par l'appel le plus long.
Temps total pris = max (3, 2, 4) = 4 secondes
- Définition déclarative : Dans le manifeste du subgraph, vous déclarez les appels Ethereum d'une manière indiquant qu'ils peuvent être exécutés en parallèle.
- Moteur d'exécution parallèle : Le moteur d'exécution de Graph Node reconnaît ces déclarations et exécute les appels simultanément.
- Agrégation des résultats : Une fois que tous les appels sont terminés, les résultats sont réunis et utilisés par le subgraph pour un traitement ultérieur.
Declared eth_calls
can access the event.address
of the underlying event as well as all the event.params
.
Subgraph.yaml
using event.address
:
eventHandlers:event: Swap(indexed address,indexed address,int256,int256,uint160,uint128,int24)handler: handleSwapcalls:global0X128: Pool[event.address].feeGrowthGlobal0X128()global1X128: Pool[event.address].feeGrowthGlobal1X128()
Détails pour l'exemple ci-dessus :
global0X128
is the declaredeth_call
.- The text (
global0X128
) is the label for thiseth_call
which is used when logging errors. - The text (
Pool[event.address].feeGrowthGlobal0X128()
) is the actualeth_call
that will be executed, which is in the form ofContract[address].function(arguments)
- The
address
andarguments
can be replaced with variables that will be available when the handler is executed.
Subgraph.yaml
using event.params
calls:- ERC20DecimalsToken0: ERC20[event.params.token0].decimals()
Note: it is not recommended to use grafting when initially upgrading to The Graph Network. Learn more .
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:
description: ...graft:base: Qm... # Subgraph ID of base subgraphblock: 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.
Étant donné que le greffage copie plutôt que l'indexation des données de base, il est beaucoup plus rapide d'amener le susgraph dans le bloc souhaité que l'indexation à partir de zéro, bien que la copie initiale des données puisse encore prendre plusieurs heures pour de très gros subgraphs. Pendant l'initialisation du subgraph greffé, le nœud graphique enregistrera des informations sur les types d'entités qui ont déjà été copiés.
Le subgraph greffé peut utiliser un schéma GraphQL qui n'est pas identique à celui du subgraph de base, mais simplement compatible avec celui-ci. Il doit s'agir d'un schéma de subgraph valide à part entière, mais il peut s'écarter du schéma du subgraph de base des manières suivantes :
- Il ajoute ou supprime des types d'entités
- Il supprime les attributs des types d'entités
- Il ajoute des attributs nullables aux types d'entités
- Il transforme les attributs non nullables en attributs nullables
- Il ajoute des valeurs aux énumérations
- Il ajoute ou supprime des interfaces
- Cela change pour quels types d'entités une interface est implémentée
Comment fonctionnent les filtres de Topics
#Lien vers cette section