Skapa en Subgraph
Reading time: 43 min
En subgraph extraherar data från en blockchain, bearbetar den och lagrar den så att den kan frågas enkelt via GraphQL.
Subgraph-definitionen består av några filer:
-
subgraph.yaml
: en YAML-fil som innehåller subgraph-manifestet -
schema.graphql
: ett GraphQL-schema som definierar vilka data som lagras för din subgraph och hur man frågar efter det via GraphQL -
AssemblyScript Mappings
: kod som översätter från händelsedata till de enheter som är definierade i ditt schema (t.ex.mapping.ts
i den här handledningen)
In order to use your subgraph on The Graph's decentralized network, you will need to . It is recommended that you to your subgraph with at least .
Before you go into detail about the contents of the manifest file, you need to install the which you will need to build and deploy a subgraph.
Graph CLI är skrivet i JavaScript, och du måste installera antingen yarn
eller npm
för att använda det; det antas att du har yarn i det följande.
När du har yarn
, installera Graph CLI genom att köra
Installera med yarn:
yarn global add @graphprotocol/graph-cli
Installera med npm:
npm install -g @graphprotocol/graph-cli
Once installed, the graph init
command can be used to set up a new subgraph project, either from an existing contract or from an example subgraph. This command can be used to create a subgraph in Subgraph Studio by passing in graph init --product subgraph-studio
. If you already have a smart contract deployed to your preferred network, bootstrapping a new subgraph from that contract can be a good way to get started.
Följande kommando skapar en subgraf som indexerar alla händelser i ett befintligt kontrakt. Det försöker hämta kontraktets ABI från Etherscan och faller tillbaka till att begära en lokal filsökväg. Om något av de valfria argumenten saknas tar det dig genom ett interaktivt formulär.
graph init \--product subgraph-studio--from-contract <CONTRACT_ADDRESS> \[--network <ETHEREUM_NETWORK>] \[--abi <FILE>] \<SUBGRAPH_SLUG> [<DIRECTORY>]
<SUBGRAPH_SLUG>
är ID för din subgraf i Subgraf Studio, det kan hittas på din subgraf detaljsida.
Det andra läget som graph init
stöder är att skapa ett nytt projekt från ett exempel på en undergraf. Följande kommando gör detta:
graph init --studio <SUBGRAPH_SLUG>
The is based on the Gravity contract by Dani Grant that manages user avatars and emits NewGravatar
or UpdateGravatar
events whenever avatars are created or updated. The subgraph handles these events by writing Gravatar
entities to the Graph Node store and ensuring these are updated according to the events. The following sections will go over the files that make up the subgraph manifest for this example.
Från och med v0.31.0
stöder graph-cli
att lägga till nya datakällor i en befintlig subgraf genom kommandot graph add
.
graph add <address> [<subgraph-manifest default: "./subgraph.yaml">]Options:--abi <path> Sökväg till kontraktets ABI (standard: nedladdning från Etherscan)--contract-name Kontraktets namn (standard: Kontrakt)--merge-entities Om enheter med samma namn ska slås samman (standard: false)--network-file <path> Sökväg till konfigurationsfil för nätverk (standard: "./networks.json")
Kommandot add
hämtar ABI: en från Etherscan (om inte en ABI-sökväg anges med alternativet --abi
) och skapar en ny dataSource
på samma sätt som kommandot graph init
skapar en dataSource
--from-contract
, och uppdaterar schemat och mappningarna därefter.
Alternativet --merge-entities
identifierar hur utvecklaren vill hantera konflikter med entity
- och event
-namn:
- Om
true
: den nyadataSource
ska använda befintligaeventHandlers
&entities
. - Om
false
: en ny entitet och händelsehanterare ska skapas med${dataSourceName}{EventName}
.
Kontraktsadressen kommer att skrivas till networks.json
för den relevanta nätverket.
Obs: När du använder det interaktiva kommandoraden, efter att ha kört graph init
framgångsrikt, kommer du att bli ombedd att lägga till en ny dataSource
.
Subgrafens manifest subgraph.yaml
definierar de smarta kontrakten som din subgraf indexerar, vilka händelser från dessa kontrakt som ska uppmärksammas och hur man kartlägger händelsedata till entiteter som Graph Node lagrar och tillåter att fråga. Den fullständiga specifikationen för subgrafens manifest finns .
För exempelsubgrafen är subgraph.yaml
:
specVersion: 0.0.4description: Gravatar for Ethereumrepository: https://github.com/graphprotocol/graph-toolingschema:file: ./schema.graphqlindexerHints:prune: autodataSources:- kind: ethereum/contractname: Gravitynetwork: mainnetsource:address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'abi: GravitystartBlock: 6175244endBlock: 7175245context:foo:type: Booldata: truebar:type: Stringdata: 'bar'mapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities:- Gravatarabis:- name: Gravityfile: ./abis/Gravity.jsoneventHandlers:- event: NewGravatar(uint256,address,string,string)handler: handleNewGravatar- event: UpdatedGravatar(uint256,address,string,string)handler: handleUpdatedGravatarcallHandlers:- function: createGravatar(string,string)handler: handleCreateGravatarblockHandlers:- handler: handleBlock- handler: handleBlockWithCallfilter:kind: callfile: ./src/mapping.ts
De viktiga posterna att uppdatera för manifestet är:
-
specVersion
: a semver version that identifies the supported manifest structure and functionality for the subgraph. The latest version is1.2.0
. See section to see more details on features & releases. -
description
: a human-readable description of what the subgraph is. This description is displayed in Graph Explorer when the subgraph is deployed to Subgraph Studio. -
repository
: the URL of the repository where the subgraph manifest can be found. This is also displayed in Graph Explorer. -
indexerHints.prune
: Defines the retention of historical block data for a subgraph. See in section. -
dataSources.source
: adressen till det smarta kontraktet som subgrafen hämtar data från, och ABI för det smarta kontraktet att använda. Adressen är valfri; att utelämna den gör det möjligt att indexera matchande händelser från alla kontrakt. -
dataSources.source.startBlock
: det valfria blocknummer som datakällan börjar indexera från. I de flesta fall föreslår vi att du använder det block där kontraktet skapades. -
dataSources.source.endBlock
: The optional number of the block that the data source stops indexing at, including that block. Minimum spec version required:0.0.9
. -
dataSources.context
: key-value pairs that can be used within subgraph mappings. Supports various data types likeBool
,String
,Int
,Int8
,BigDecimal
,Bytes
,List
, andBigInt
. Each variable needs to specify itstype
anddata
. These context variables are then accessible in the mapping files, offering more configurable options for subgraph development. -
dataSources.mapping.entities
: de entiteter som datakällan skriver till lagringsplatsen. Schemat för varje entitet definieras i filen schema.graphql. -
dataSources.mapping.abis
: en eller flera namngivna ABI-filer för källkontraktet samt eventuella andra smarta kontrakt som du interagerar med från inom mappningarna. -
dataSources.mapping.eventHandlers
: listar de smarta kontraktshändelser som denna subgraf reagerar på och hanterare i mappningen—./src/mapping.ts i exemplet - som omvandlar dessa händelser till entiteter i lagringsplatsen. -
dataSources.mapping.callHandlers
: listar de smarta kontraktsfunktioner som denna subgraf reagerar på och hanterare i mappningen som omvandlar in- och utdata till funktionsanrop till entiteter i lagringsplatsen. -
dataSources.mapping.blockHandlers
: listar de block som denna subgraf reagerar på och hanterare i mappningen som körs när ett block läggs till i kedjan. Utan ett filter körs blockhanteraren varje block. En valfri anropsfiltrering kan tillhandahållas genom att lägga till enfilter
-fält medkind: call
till hanteraren. Detta körs bara om blocket innehåller minst ett anrop till datakällan.
En enskild subgraf kan indexera data från flera smarta kontrakt. Lägg till en post för varje kontrakt från vilket data behöver indexeras i dataSources
-matrisen.
Utlösarna för en datakälla inom ett block ordnas med hjälp av följande process:
- Händelse- och anropsutlösare ordnas först efter transaktionsindex inom blocket.
- Händelse- och anropsutlösare inom samma transaktion ordnas med hjälp av en konvention: händelseutlösare först, sedan anropsutlösare, varje typ respekterar ordningen de definieras i manifestet.
- Blockutlösare körs efter händelse- och anropsutlösare, i den ordning de definieras i manifestet.
Dessa ordningsregler kan komma att ändras.
Note: When new are created, the handlers defined for dynamic data sources will only start processing after all existing data source handlers are processed, and will repeat in the same sequence whenever triggered.
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.
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.
- 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 {// Event declaration with indexed parameters for addressesevent Transfer(address indexed from, address indexed to, uint256 value);// Function to simulate transferring tokensfunction transfer(address to, uint256 value) public {// Emitting the Transfer event with from, to, and valueemit Transfer(msg.sender, to, value);}}
In this example:
- 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.
Topic filters are defined directly within the event handler configuration in the subgraph manifest. Here is how they are configured:
eventHandlers:- event: SomeEvent(indexed uint256, indexed address, indexed uint256)handler: handleSomeEventtopic1: ['0xValue1', '0xValue2']topic2: ['0xAddress1', '0xAddress2']topic3: ['0xValue3']
In this setup:
topic1
corresponds to the first indexed argument of the event,topic2
to the second, andtopic3
to the third.- Each topic can have one or more values, and an event is only processed if it matches one of the values in each specified topic.
- Within a Single Topic: The logic functions as an OR condition. The event will be processed if it matches any one of the listed values in a given topic.
- Between Different Topics: The logic functions as an AND condition. An event must satisfy all specified conditions across different topics to trigger the associated handler.
eventHandlers:- event: Transfer(indexed address,indexed address,uint256)handler: handleDirectedTransfertopic1: ['0xAddressA'] # Sender Addresstopic2: ['0xAddressB'] # Receiver Address
In this 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
In this configuration:
topic1
is configured to filterTransfer
events where0xAddressA
,0xAddressB
,0xAddressC
is the sender.topic2
is configured to filterTransfer
events where0xAddressB
and0xAddressC
is the receiver.- The subgraph will index transactions that occur in either direction between multiple addresses allowing for comprehensive monitoring of interactions involving all addresses.
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.
This feature does the following:
- 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.
- Allows faster data fetching, resulting in quicker query responses and a better user experience.
- Reduces wait times for applications that need to aggregate data from multiple Ethereum calls, making the data retrieval process more efficient.
- Declarative
eth_calls
: Ethereum calls that are defined to be executed in parallel rather than sequentially. - Parallel Execution: Instead of waiting for one call to finish before starting the next, multiple calls can be initiated simultaneously.
- Time Efficiency: The total time taken for all the calls changes from the sum of the individual call times (sequential) to the time taken by the longest call (parallel).
Imagine you have a subgraph that needs to make three Ethereum calls to fetch data about a user's transactions, balance, and token holdings.
Traditionally, these calls might be made sequentially:
- Call 1 (Transactions): Takes 3 seconds
- Call 2 (Balance): Takes 2 seconds
- Call 3 (Token Holdings): Takes 4 seconds
Total time taken = 3 + 2 + 4 = 9 seconds
With this feature, you can declare these calls to be executed in parallel:
- Call 1 (Transactions): Takes 3 seconds
- Call 2 (Balance): Takes 2 seconds
- Call 3 (Token Holdings): Takes 4 seconds
Since these calls are executed in parallel, the total time taken is equal to the time taken by the longest call.
Total time taken = max (3, 2, 4) = 4 seconds
- Declarative Definition: In the subgraph manifest, you declare the Ethereum calls in a way that indicates they can be executed in parallel.
- Parallel Execution Engine: The Graph Node's execution engine recognizes these declarations and runs the calls simultaneously.
- Result Aggregation: Once all calls are complete, the results are aggregated and used by the subgraph for further processing.
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()
Details for the example above:
global0X128
is the declaredeth_call
.- The text before colon(
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()
ABI-filerna måste matcha ditt/dina kontrakt. Det finns några olika sätt att få ABI-filer:
- Om du bygger ditt eget projekt har du förmodligen tillgång till dina senaste ABIs.
- Om du bygger en subgraf för ett offentligt projekt kan du ladda ner det projektet till din dator och få ABI:n genom att använda eller använda solc för att kompilera.
- Du kan också hitta ABI:n på , men detta är inte alltid pålitligt, eftersom ABI:n som laddas upp där kan vara föråldrad. Se till att du har rätt ABI, annars kommer din subgraf att misslyckas när den körs.
Schemat för din subgraf finns i filen schema.graphql
. GraphQL-scheman definieras med hjälp av gränssnittsdefinitionsspråket för GraphQL. Om du aldrig har skrivit ett GraphQL-schema rekommenderas det att du kollar in denna introduktion till GraphQL-typsystemet. Referensdokumentation för GraphQL-scheman finns i avsnittet .
Innan du definierar entiteter är det viktigt att ta ett steg tillbaka och tänka på hur din data är strukturerad och länkad. Alla frågor kommer att göras mot datamodellen som definieras i subgrafens schema och de entiteter som indexerats av subgraf. Därför är det bra att definiera subgrafens schema på ett sätt som matchar din dapp's behov. Det kan vara användbart att tänka på entiteter som "objekt som innehåller data", snarare än som händelser eller funktioner.
Med The Graph definierar du helt enkelt entitetstyper i schema.graphql
, och Graph Node kommer att generera toppnivåfält för att fråga enskilda instanser och samlingar av den entitetstypen. Varje typ som ska vara en entitet måste vara annoterad med en @entity
-direktiv. Som standard är entiteter muterbara, vilket innebär att mappningar kan ladda befintliga entiteter, ändra dem och lagra en ny version av den entiteten. Mutabilitet har ett pris, och för entitetstyper där det är känt att de aldrig kommer att ändras, till exempel eftersom de helt enkelt innehåller data som extraherats ordagrant från kedjan, rekommenderas att markera dem som omutbara med @entity(immutable: true)
. Mappningar kan göra ändringar i omutbara entiteter så länge dessa ändringar sker i samma block som entiteten skapades. Omutebara entiteter är mycket snabbare att skriva och att fråga, och bör därför användas när det är möjligt.
Entiteten Gravatar
nedan är strukturerad kring ett Gravatar-objekt och är ett bra exempel på hur en entitet kan definieras.
type Gravatar @entity(immutable: true) {id: Bytes!owner: BytesdisplayName: StringimageUrl: Stringaccepted: Boolean}
Exemplen GravatarAccepted
och GravatarDeclined
nedan är baserade på händelser. Det rekommenderas inte att mappa händelser eller funktionsanrop till entiteter 1:1.
type GravatarAccepted @entity {id: Bytes!owner: BytesdisplayName: StringimageUrl: String}type GravatarDeclined @entity {id: Bytes!owner: BytesdisplayName: StringimageUrl: String}
Entitetsfält kan definieras som obligatoriska eller valfria. Obligatoriska fält anges med !
i schemat. Om ett obligatoriskt fält inte har angetts i mappningen får du det här felmeddelandet när du frågar efter fältet:
Null value resolved for non-null field 'name'
Varje entitet måste ha ett id
-fält, som måste vara av typen Bytes!
eller String!
. Det rekommenderas generellt att använda Bytes!
, om inte id
innehåller läsbar text, eftersom entiteter med Bytes!
-id kommer att vara snabbare att skriva och fråga än de med ett String!
id
. id
-fältet fungerar som primärnyckel och måste vara unikt bland alla entiteter av samma typ. Av historiska skäl accepteras också typen ID!
och är en synonym för String!
.
För vissa entitetstyper konstrueras id
från id:erna hos två andra entiteter; det är möjligt med concat
, t.ex. let id = left.id.concat(right.id)
för att bilda id från id:erna hos left
och right
. På liknande sätt kan för att konstruera ett id från id:et hos en befintlig entitet och en räknare count
användas let id = left.id.concatI32(count)
. Konkatineringen garanterar att producera unika id:er så länge längden av left
är densamma för alla sådana entiteter, till exempel eftersom left.id
är en Address
.
Vi stödjer följande skalartyper i vår GraphQL API:
Typ | Beskrivning |
---|---|
Bytes | Bytematris, representerad som en hexadecimal sträng. Vanligt används för Ethereum-hashar och adresser. |
String | Skalär för string -värden. Nolltecken stöds inte och tas automatiskt bort. |
Boolean | Skalär för boolean -värden. |
Int | The GraphQL spec defines Int to be a signed 32-bit integer. |
Int8 | An 8-byte signed integer, also known as a 64-bit signed integer, can store values in the range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. Prefer using this to represent i64 from ethereum. |
BigInt | Stora heltal. Används för Ethereum's uint32 , int64 , uint64 , ..., uint256 typer. Observera: Allt under uint32 , som int32 , uint24 eller int8 representeras som i32 . |
BigDecimal | BigDecimal Högprecisionsdecimaler representerade som en signifikant och en exponent. Exponentområdet är från −6143 till +6144. Avrundat till 34 signifikanta siffror. |
Timestamp | It is an i64 value in microseconds. Commonly used for timestamp fields for timeseries and aggregations. |
Du kan också skapa enums inom ett schema. Enums har följande syntax:
enum TokenStatus {OriginalOwnerSecondOwnerThirdOwner}
När enumet är definierat i schemat kan du använda enumvärdenas strängrepresentation för att ställa in ett enumfält på en entitet. Till exempel kan du ställa in tokenStatus
till SecondOwner
genom att först definiera din entitet och sedan ställa in fältet med entity.tokenStatus = "SecondOwner"
. Exemplet nedan visar hur Token-entiteten skulle se ut med ett enumfält:
Mer detaljer om att skriva enums finns i .
En entitet kan ha en relation till en eller flera andra entiteter i ditt schema. Dessa relationer kan traverseras i dina frågor. Relationer i The Graph är enriktade. Det är möjligt att simulera dubbelriktade relationer genom att definiera en enriktad relation på antingen den ena "änden" av relationen.
Relationer definieras på entiteter precis som vilket annat fält som helst, förutom att den specificerade typen är en annan entitet.
Definiera en entitetstyp Transaction
med en valfri en-till-en-relation till en entitetstyp TransactionReceipt
:
type Transaction @entity(immutable: true) {id: Bytes!transactionReceipt: TransactionReceipt}type TransactionReceipt @entity(immutable: true) {id: Bytes!transaction: Transaction}
Definiera en entitetstyp TokenBalance
med ett obligatoriskt en-till-many förhållande med en entitetstyp Token:
type Token @entity(immutable: true) {id: Bytes!}type TokenBalance @entity {id: Bytes!amount: Int!token: Token!}
Omvända sökningar kan definieras på en entitet genom fältet @derivedFrom
. Det skapar ett virtuellt fält på entiteten som kan frågas, men som inte kan ställas in manuellt via mappings API. Istället härleds det från den relation som är definierad på den andra entiteten. För sådana relationer är det sällan meningsfullt att lagra båda sidor av relationen, och både indexering och frågeprestanda blir bättre när bara en sida lagras och den andra härleds.
För en-till-många-relationer bör relationen alltid lagras på 'en'-sidan, och 'många'-sidan bör alltid härledas. Att lagra relationen på detta sätt, istället för att lagra en array av entiteter på 'många'-sidan, kommer att resultera i dramatiskt bättre prestanda både för indexering och för frågning av subgraphen. Generellt sett bör lagring av arrayer av entiteter undvikas så mycket som är praktiskt möjligt.
Vi kan göra balanserna för en token åtkomliga från token genom att härleda ett fält tokenBalances
:
type Token @entity(immutable: true) {id: Bytes!tokenBalances: [TokenBalance!]! @derivedFrom(field: "token")}type TokenBalance @entity {id: Bytes!amount: Int!token: Token!}
För många-till-många-relationer, som till exempel användare som var och en kan tillhöra ett antal organisationer, är det mest raka, men generellt sett inte den mest prestanda-optimerade, sättet att modellera relationen som en array i vardera av de två entiteter som är involverade. Om relationen är symmetrisk behöver bara ena sidan av relationen lagras och den andra sidan kan härledas.
Definiera en omvänd sökning från en entitet av typen Användare
till en entitet av typen Organisation
. I exemplet nedan uppnås detta genom att söka upp attributet medlemmar
inom entiteten Organisation
. I frågor kommer fältet organisationer
på Användare
att lösas genom att hitta alla Organisations
-entiteter som inkluderar användarens ID.
type Organization @entity {id: Bytes!name: String!members: [User!]!}type User @entity {id: Bytes!name: String!organizations: [Organization!]! @derivedFrom(field: "members")}
Ett mer effektivt sätt att lagra denna relation är genom en mappningstabell som har en post för varje User
/ Organization
-par med ett schema som
type Organization @entity {id: Bytes!name: String!members: [UserOrganization!]! @derivedFrom(field: "organization")}type User @entity {id: Bytes!name: String!organizations: [UserOrganization!] @derivedFrom(field: "user")}type UserOrganization @entity {id: Bytes! # Set to `user.id.concat(organization.id)`user: User!organization: Organization!}
Detta tillvägagångssätt kräver att frågorna går ner till ytterligare en nivå för att hämta t. ex. organisationer för användare:
query usersWithOrganizations {users {organizations {# this is a UserOrganization entityorganization {name}}}}
Detta mer avancerade sätt att lagra många-till-många-relationer kommer att leda till att mindre data lagras för subgrafen, och därför till en subgraf som ofta är dramatiskt snabbare att indexera och att fråga.
As per GraphQL spec, comments can be added above schema entity attributes using the hash symble #
. This is illustrated in the example below:
type MyFirstEntity @entity {# unique identifier and primary key of the entityid: Bytes!address: Bytes!}
Fulltextsökningar filtrerar och rangordnar entiteter baserat på en textinmatning för sökning. Fulltextförfrågningar kan returnera träffar för liknande ord genom att bearbeta söktexten till stammar innan de jämförs med den indexerade textdata.
En fulltextförfrågningsdefinition inkluderar förfrågningsnamnet, ordboken som används för att bearbeta textfälten, rangordningsalgoritmen som används för att ordna resultaten och fälten som ingår i sökningen. Varje fulltextförfrågan kan omfatta flera fält, men alla inkluderade fält måste vara från en enda entitetstyp.
För att lägga till en fulltextförfrågan inkludera en typ _Schema_
med en fulltextdirektiv i GraphQL-schemat.
type _Schema_@fulltext(name: "bandSearch"language: enalgorithm: rankinclude: [{ entity: "Band", fields: [{ name: "name" }, { name: "description" }, { name: "bio" }] }])type Band @entity {id: Bytes!name: String!description: String!bio: Stringwallet: Addresslabels: [Label!]!discography: [Album!]!members: [Musician!]!}
Exempelfältet bandSearch
kan användas i frågor för att filtrera Band
-entiteter baserat på textdokumenten i fälten name
, description
och bio
. Gå till för en beskrivning av API:et för fulltextsökning och fler exempel på användning.
query {bandSearch(text: "breaks & electro & detroit") {idnamedescriptionwallet}}
: Från specVersion
0.0.4
och framåt måste fullTextSearch
deklareras under avsnittet features
i subgraph-manifestet.
Att välja ett annat språk kommer att ha en definitiv, om än ibland subtil, effekt på fulltext-sök-API:en. Fält som omfattas av en fulltextförfrågningsfunktion granskas i kontexten av det valda språket, så lexem som produceras av analys och sökfrågor varierar från språk till språk. Till exempel: när det används det stödda turkiska ordboken "token" så avstamsas det till "toke", medan engelska ordboken självklart avstammar det till "token".
Stödda språkordböcker:
Kod | Ordbok |
---|---|
enkel | Allmän |
da | Danska |
nl | Holländska |
en | Engelska |
fi | Finska |
fr | Franska |
de | Tyska |
hu | Ungerska |
it | Italienska |
no | Norska |
pt | Portugisiska |
ro | Rumänska |
ru | Ryska |
es | Spanska |
sv | Svenska |
tr | Turkiska |
Stödda algoritmer för att ordna resultat:
Algoritm | Beskrivning |
---|---|
rank | Använd matchningskvaliteten (0-1) från fulltextförfrågan för att ordna resultaten. |
proximityRank | Liknande rank, men inkluderar också närheten av träffarna. |
Mappningar tar data från en specifik källa och omvandlar den till entiteter som är definierade i din schema. Mappningar skrivs i en delmängd av som kallas som kan kompileras till WASM (). AssemblyScript är strängare än vanlig TypeScript, men erbjuder en bekant syntax.
För varje händelsehanterare som är definierad i subgraph.yaml
under mapping.eventHandlers
, skapa en exporterad funktion med samma namn. Varje hanterare måste acceptera en enda parameter med namnet event
med en typ som motsvarar namnet på händelsen som hanteras.
I det här exempelsubgraphet innehåller src/mapping.ts
hanterare för händelserna NewGravatar
och UpdatedGravatar
:
import { NewGravatar, UpdatedGravatar } from "../generated/Gravity/Gravity";import { Gravatar } from "../generated/schema";export function handleNewGravatar(event: NewGravatar): void {let gravatar = new Gravatar(event.params.id);gravatar.owner = event.params.owner;gravatar.displayName = event.params.displayName;gravatar.imageUrl = event.params.imageUrl;gravatar.save();}export function handleUpdatedGravatar(event: UpdatedGravatar): void {let id = event.params.id;let gravatar = Gravatar.load(id);if (gravatar == null) {gravatar = new Gravatar(id);}gravatar.owner = event.params.owner;gravatar.displayName = event.params.displayName;gravatar.imageUrl = event.params.imageUrl;gravatar.save();}
Den första hanteraren tar en NewGravatar
-händelse och skapar en ny Gravatar
-entitet med new Gravatar(event.params.id.toHex())
, fyller i entitetsfälten med hjälp av motsvarande händelseparametrar. Denna entitetsinstans representeras av variabeln gravatar
, med ett id-värde av event.params.id.toHex()
.
Den andra hanteraren försöker ladda den befintliga Gravatar
från Graph Node-lagringen. Om den inte finns ännu skapas den på begäran. Entiteten uppdateras sedan för att matcha de nya händelseparametrarna innan den sparas tillbaka till lagringen med gravatar.save()
.
It is highly recommended to use Bytes
as the type for id
fields, and only use String
for attributes that truly contain human-readable text, like the name of a token. Below are some recommended id
values to consider when creating new entities.
-
transfer.id = event.transaction.hash
-
let id = event.transaction.hash.concatI32(event.logIndex.toI32())
-
For entities that store aggregated data, for e.g, daily trade volumes, the
id
usually contains the day number. Here, using aBytes
as theid
is beneficial. Determining theid
would look like
let dayID = event.block.timestamp.toI32() / 86400let id = Bytes.fromI32(dayID)
- Convert constant addresses to
Bytes
.
const id = Bytes.fromHexString('0xdead...beef')
There is a which contains utilities for interacting with the Graph Node store and conveniences for handling smart contract data and entities. It can be imported into mapping.ts
from @graphprotocol/graph-ts
.
When creating and saving a new entity, if an entity with the same ID already exists, the properties of the new entity are always preferred during the merge process. This means that the existing entity will be updated with the values from the new entity.
If a null value is intentionally set for a field in the new entity with the same ID, the existing entity will be updated with the null value.
If no value is set for a field in the new entity with the same ID, the field will result in null as well.
För att göra det enkelt och typsäkert att arbeta med smarta kontrakt, händelser och entiteter kan Graph CLI generera AssemblyScript-typer från subgrafens GraphQL-schema och kontrakts-ABIn som ingår i datakällorna.
Detta görs med
graph codegen [--output-dir <OUTPUT_DIR>] [<MANIFEST>]
men i de flesta fall är undergrafer redan förkonfigurerade via package.json
så att du helt enkelt kan köra en av följande för att uppnå samma sak:
# Yarnyarn codegen# NPMnpm run codegen
Detta genererar en AssemblyScript-klass för varje smart kontrakt i ABI-filerna som nämns i subgraph.yaml
, så att du kan binda dessa kontrakt till specifika adresser i mappningarna och anropa skrivskyddade kontraktsmetoder mot det block som bearbetas. Den kommer också att generera en klass för varje kontraktshändelse för att ge enkel åtkomst till händelseparametrar, samt blocket och transaktionen som händelsen härstammar från. Alla dessa typer skrivs till <OUTPUT_DIR>/<DATA_SOURCE_NAME>/<ABI_NAME>.ts
. I undergrafen i exemplet skulle detta vara generated/Gravity/Gravity.ts
, vilket gör att mappningar kan importera dessa typer med.
import {// The contract class:Gravity,// The events classes:NewGravatar,UpdatedGravatar,} from '../generated/Gravity/Gravity'
Utöver detta genereras en klass för varje entitetstyp i subgrafens GraphQL-schema. Dessa klasser tillhandahåller typsäker entitetsladdning, läs- och skrivåtkomst till entitetsfält samt en save()
-metod för att skriva entiteter till lagret. Alla entitetsklasser skrivs till <OUTPUT_DIR>/schema.ts
, vilket gör att mappningar kan importera dem med
import { Gravatar } from '../generated/schema'
Observera: Kodgenerering måste utföras igen efter varje ändring av GraphQL-schemat eller ABIn som ingår i manifestet. Det måste också utföras minst en gång innan du bygger eller distribuerar subgrafet.
Code generation does not check your mapping code in src/mapping.ts
. If you want to check that before trying to deploy your subgraph to Graph Explorer, you can run yarn build
and fix any syntax errors that the TypeScript compiler might find.
En vanlig mönster i EVM-kompatibla smarta kontrakt är användningen av register- eller fabrikskontrakt, där ett kontrakt skapar, hanterar eller hänvisar till ett godtyckligt antal andra kontrakt som var och en har sin egen stat och händelser.
Adresserna till dessa underkontrakt kan eller kanske inte vara kända på förhand, och många av dessa kontrakt kan skapas och/eller läggas till över tid. Det är därför, i sådana fall, som det är omöjligt att definiera en enda datakälla eller ett fast antal datakällor och en mer dynamisk metod behövs: datakällmallar.
Först definierar du en vanlig datakälla för huvudkontraktet. Snutten nedan visar ett förenklat exempel på en datakälla för utbytesfabrikskontrakt. Observera NewExchange(address,address)
händelsehanteraren. Denna händelse emitteras när en ny utbyteskontrakt skapas på kedjan av fabrikskontraktet.
dataSources:- kind: ethereum/contractname: Factorynetwork: mainnetsource:address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'abi: Factorymapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptfile: ./src/mappings/factory.tsentities:- Directoryabis:- name: Factoryfile: ./abis/factory.jsoneventHandlers:- event: NewExchange(address,address)handler: handleNewExchange
Sedan lägger du till datakällmallar i manifestet. Dessa är identiska med vanliga datakällor, förutom att de saknar en fördefinierad avtalsadress under source
. Vanligtvis definierar du en mall för varje typ av underkontrakt som hanteras eller refereras till av det överordnade kontraktet.
dataSources:- kind: ethereum/contractname: Factory# ... other source fields for the main contract ...templates:- name: Exchangekind: ethereum/contractnetwork: mainnetsource:abi: Exchangemapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptfile: ./src/mappings/exchange.tsentities:- Exchangeabis:- name: Exchangefile: ./abis/exchange.jsoneventHandlers:- event: TokenPurchase(address,uint256,uint256)handler: handleTokenPurchase- event: EthPurchase(address,uint256,uint256)handler: handleEthPurchase- event: AddLiquidity(address,uint256,uint256)handler: handleAddLiquidity- event: RemoveLiquidity(address,uint256,uint256)handler: handleRemoveLiquidity
I det sista steget uppdaterar du mappningen av huvudkontraktet för att skapa en dynamisk datakällinstans från en av mallarna. I det här exemplet ändrar du mappningen av huvudkontraktet för att importera mallen Exchange
och anropar metoden Exchange.create(address)
för att börja indexera det nya växlingskontraktet.
import { Exchange } from '../generated/templates'export function handleNewExchange(event: NewExchange): void {// Start indexing the exchange; `event.params.exchange` is the// address of the new exchange contractExchange.create(event.params.exchange)}
** Notera:** En ny datakälla bearbetar endast anrop och händelser för det block där den skapades och alla efterföljande block, men bearbetar inte historiska data, dvs. data som finns i tidigare block.
Om tidigare block innehåller data som är relevanta för den nya datakällan, är det bäst att indexera dessa data genom att läsa kontraktets aktuella status och skapa enheter som representerar denna status vid den tidpunkt då den nya datakällan skapas.
Datakällans kontext gör det möjligt att skicka extra konfiguration när en mall instansieras. I vårt exempel kan vi säga att börser är associerade med ett visst handelspar, vilket ingår i händelsen NewExchange
. Den informationen kan skickas till den instansierade datakällan, så här:
import { Exchange } from '../generated/templates'export function handleNewExchange(event: NewExchange): void {let context = new DataSourceContext()context.setString('tradingPair', event.params.tradingPair)Exchange.createWithContext(event.params.exchange, context)}
Inuti en mappning av mallen Exchange
kan kontexten sedan nås:
import { dataSource } from '@graphprotocol/graph-ts'let context = dataSource.context()let tradingPair = context.getString('tradingPair')
Det finns sättare och hämtare som setString
och getString
för alla värdestyper.
startBlock
är en valfri inställning som låter dig definiera från vilken block i kedjan datakällan ska börja indexera. Genom att ställa in startblocket kan datakällan hoppa över potentiellt miljontals block som är irrelevanta. Vanligtvis kommer en subgrafutvecklare att ställa in startBlock
till blocket där datakällans smarta kontrakt skapades.
dataSources:- kind: ethereum/contractname: ExampleSourcenetwork: mainnetsource:address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'abi: ExampleContractstartBlock: 6627917mapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptfile: ./src/mappings/factory.tsentities:- Userabis:- name: ExampleContractfile: ./abis/ExampleContract.jsoneventHandlers:- event: NewEvent(address,address)handler: handleNewEvent
Observera: Blocket där kontraktet skapades kan snabbt sökas upp på Etherscan:
- Sök efter kontraktet genom att ange dess adress i sökfältet.
- Klicka på transaktionshashen för skapandet i avsnittet
Kontraktsskapare
. - Ladda sidan med transaktionsdetaljer där du hittar startblocket för det kontraktet.
The indexerHints
setting in a subgraph's manifest provides directives for indexers on processing and managing a subgraph. It influences operational decisions across data handling, indexing strategies, and optimizations. Presently, it features the prune
option for managing historical data retention or pruning.
This feature is available from specVersion: 1.0.0
indexerHints.prune
: Defines the retention of historical block data for a subgraph. Options include:
"never"
: No pruning of historical data; retains the entire history."auto"
: Retains the minimum necessary history as set by the indexer, optimizing query performance.- A specific number: Sets a custom limit on the number of historical blocks to retain.
indexerHints:prune: auto
The term "history" in this context of subgraphs is about storing data that reflects the old states of mutable entities.
History as of a given block is required for:
- , which enable querying the past states of these entities at specific blocks throughout the subgraph's history
- Using the subgraph as a in another subgraph, at that block
- Rewinding the subgraph back to that block
If historical data as of the block has been pruned, the above capabilities will not be available.
Using "auto"
is generally recommended as it maximizes query performance and is sufficient for most users who do not require access to extensive historical data.
For subgraphs leveraging , it's advisable to either set a specific number of blocks for historical data retention or use prune: never
to keep all historical entity states. Below are examples of how to configure both options in your subgraph's settings:
To retain a specific amount of historical data:
indexerHints:prune: 1000 # Replace 1000 with the desired number of blocks to retain
To preserve the complete history of entity states:
indexerHints:prune: never
You can check the earliest block (with historical state) for a given subgraph by querying the :
{indexingStatuses(subgraphs: ["Qm..."]) {subgraphsyncedhealthchains {earliestBlock {number}latestBlock {number}chainHeadBlock { number }}}}
Note that the earliestBlock
is the earliest block with historical data, which will be more recent than the startBlock
specified in the manifest, if the subgraph has been pruned.
Event handlers in a subgraph react to specific events emitted by smart contracts on the blockchain and trigger handlers defined in the subgraph's manifest. This enables subgraphs to process and store event data according to defined logic.
An event handler is declared within a data source in the subgraph's YAML configuration. It specifies which events to listen for and the corresponding function to execute when those events are detected.
dataSources:- kind: ethereum/contractname: Gravitynetwork: devsource:address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'abi: Gravitymapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities:- Gravatar- Transactionabis:- name: Gravityfile: ./abis/Gravity.jsoneventHandlers:- event: Approval(address,address,uint256)handler: handleApproval- event: Transfer(address,address,uint256)handler: handleTransfertopic1: ['0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045', '0xc8dA6BF26964aF9D7eEd9e03E53415D37aA96325'] # Optional topic filter which filters only events with the specified topic.
Medan händelser ger ett effektivt sätt att samla in relevanta ändringar av ett kontrakts tillstånd, undviker många kontrakt att generera loggar för att optimera gasavgifterna. I dessa fall kan en subgraf prenumerera på anrop som görs till datakällans kontrakt. Detta uppnås genom att definiera anropsbehandlare som refererar till funktions signaturen och hanteraren som kommer att bearbeta anrop till denna funktion. För att bearbeta dessa anrop kommer hanteraren att ta emot ett ethereum.Call
som ett argument med de typade in- och utdata från anropet. Anrop som görs på vilken djupnivå som helst i en transaktions anropskedja kommer att utlösa kartläggningen, vilket gör det möjligt att fånga aktivitet med datakällan genom proxykontrakt.
Anropsbehandlare utlöses endast i ett av två fall: när den specificerade funktionen anropas av ett konto som inte är kontraktet självt eller när den är markerad som extern i Solidity och anropas som en del av en annan funktion i samma kontrakt.
Observera: Anropsbehandlare är för närvarande beroende av Paritys spårnings-API. Vissa nätverk, som BNB-kedjan och Arbitrum, stöder inte denna API. Om en subgraf som indexerar ett av dessa nätverk innehåller en eller flera anropsbehandlare kommer den inte att börja synkroniseras. Subgrafutvecklare bör istället använda händelsehanterare. Dessa är mycket mer prestandaoptimerade än anropsbehandlare och stöds på alla evm-nätverk.
För att definiera en anropsbehandlare i din manifest, lägg helt enkelt till en callHandlers
-array under den datakälla du vill prenumerera på.
dataSources:- kind: ethereum/contractname: Gravitynetwork: mainnetsource:address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'abi: Gravitymapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities:- Gravatar- Transactionabis:- name: Gravityfile: ./abis/Gravity.jsoncallHandlers:- function: createGravatar(string,string)handler: handleCreateGravatar
function
är den normaliserade funktions signaturen för att filtrera anrop efter. Egenskapen handler
är namnet på funktionen i din kartläggning som du vill utföra när målfunktionen anropas i datakällans kontrakt.
Varje anropsbehandlare tar en enda parameter med en typ som motsvarar namnet på den kallade funktionen. I det ovanstående exempelsubgrafet innehåller kartläggningen en hanterare för när funktionen createGravatar
anropas och tar emot en CreateGravatarCall
-parameter som ett argument:
import { CreateGravatarCall } from '../generated/Gravity/Gravity'import { Transaction } from '../generated/schema'export function handleCreateGravatar(call: CreateGravatarCall): void {let id = call.transaction.hashlet transaction = new Transaction(id)transaction.displayName = call.inputs._displayNametransaction.imageUrl = call.inputs._imageUrltransaction.save()}
Funktionen handleCreateGravatar
tar emot ett nytt CreateGravatarCall
, som är en underklass av ethereum.Call
, tillhandahållen av @graphprotocol/graph-ts
, som inkluderar de typade in- och utmatningarna från anropet. Typen CreateGravatarCall
genereras för dig när du kör graph codegen
.
Förutom att prenumerera på kontrakts händelser eller funktionsanrop kan en subgraf vilja uppdatera sina data när nya block läggs till i kedjan. För att uppnå detta kan en subgraf köra en funktion efter varje block eller efter block som matchar en fördefinierad filter.
filter:kind: call
Den definierade hanteraren kommer att anropas en gång för varje block som innehåller ett anrop till det kontrakt (datakälla) som hanteraren är definierad under.
Observera: call
-filtret är för närvarande beroende av Parity-tracing-API: et. Vissa nätverk, som BNB-kedjan och Arbitrum, stöder inte detta API. Om en subgraf som indexerar ett av dessa nätverk innehåller en eller flera blockhanterare med ett call
-filter, kommer den inte att börja synkronisera.
Avsaknaden av ett filter för en blockhanterare kommer att säkerställa att hanteraren kallas för varje block. En datakälla kan endast innehålla en blockhanterare för varje filttyp.
dataSources:- kind: ethereum/contractname: Gravitynetwork: devsource:address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'abi: Gravitymapping:kind: ethereum/eventsapiVersion: 0.0.6language: wasm/assemblyscriptentities:- Gravatar- Transactionabis:- name: Gravityfile: ./abis/Gravity.jsonblockHandlers:- handler: handleBlock- handler: handleBlockWithCallToContractfilter:kind: call
Requires specVersion
>= 0.0.8
Observera: Undersökningsfilter är endast tillgängliga på datakällor av typen kind: ethereum
.
blockHandlers:- handler: handleBlockfilter:kind: pollingevery: 10
Den definierade hanteraren kommer att kallas en gång för varje n
block, där n
är värdet som anges i fältet every
. Denna konfiguration möjliggör för delgrafer att utföra specifika operationer med regelbundna blockintervall.
Requires specVersion
>= 0.0.8
Observera: En gång-filtrar är endast tillgängliga på datakällor av typen kind: ethereum
.
blockHandlers:- handler: handleOncefilter:kind: once
Den definierade hanteraren med filtret once kommer att anropas endast en gång innan alla andra hanterare körs. Denna konfiguration gör det möjligt för subgrafen att använda hanteraren som en initialiseringshanterare, som utför specifika uppgifter i början av indexeringen.
export function handleOnce(block: ethereum.Block): void {let data = new InitialData(Bytes.fromUTF8('initial'))data.data = 'Setup data here'data.save()}
Mappningsfunktionen tar emot ett ethereum.Block
som sitt enda argument. Liksom mappningsfunktioner för händelser kan denna funktion komma åt befintliga subgrafiska enheter i lagret, anropa smarta kontrakt och skapa eller uppdatera enheter.
import { ethereum } from '@graphprotocol/graph-ts'export function handleBlock(block: ethereum.Block): void {let id = block.hashlet entity = new Block(id)entity.save()}
Om du behöver behandla anonyma händelser i Solidity kan du göra det genom att ange händelsens ämne 0, som i exemplet:
eventHandlers:- event: LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)topic0: '0x644843f351d3fba4abcd60109eaff9f54bac8fb8ccf0bab941009c21df21cf31'handler: handleGive
En händelse utlöses endast när både signaturen och topic0 matchar. Som standard är topic0
lika med hashtillståndet för händelsesignaturen.
Från och med specVersion
0.0.5
och apiVersion
0.0.7
kan händelsehanterare få tillgång till kvittot för den transaktion som emitterade dem.
För att göra detta måste händelsehanterare deklareras i delgrafmanifestet med den nya nyckeln receipt: true
, vilket är valfritt och som standard är falskt.
eventHandlers:- event: NewGravatar(uint256,address,string,string)handler: handleNewGravatarreceipt: true
Inuti hanterarfunktionen kan kvittot nås i fältet Event.receipt
. När nyckeln receipt
är inställd som false
eller utelämnad i manifestet, kommer istället ett null
-värde att returneras.
Från och med specVersion
0.0.4
måste delgrafsfunktioner deklareras explicit i avsnittet features
högst upp i manifestfilen, med deras camelCase
-namn, som listas i tabellen nedan:
Till exempel, om en delgraf använder funktionerna Fulltextssökning och Icke dödliga fel, ska fältet features
i manifestet vara:
specVersion: 0.0.4description: Gravatar for Ethereumfeatures:- fullTextSearch- nonFatalErrorsdataSources: ...
Observera att att använda en funktion utan att deklarera den kommer att resultera i en valideringsfel under delgrafens distribution, men inga fel uppstår om en funktion deklareras men inte används.
Timeseries and aggregations enable your subgraph to track statistics like daily average price, hourly total transfers, etc.
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.
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}}
Note:
To use Timeseries and Aggregations, a subgraph must have a spec version ≥1.1.0. Note that this feature might undergo significant changes that could affect backward compatibility.
about Timeseries and Aggregations.
Indexeringsfel på redan synkroniserade delgrafer kommer, som standard, att få delgrafen att misslyckas och sluta synkronisera. Delgrafer kan istället konfigureras för att fortsätta synkroniseringen i närvaro av fel, genom att ignorera ändringarna som orsakades av hanteraren som provocerade felet. Det ger delgrafsförfattare tid att korrigera sina delgrafer medan förfrågningar fortsätter att behandlas mot det senaste blocket, även om resultaten kan vara inkonsekventa på grund av felet som orsakade felet. Observera att vissa fel alltid är dödliga. För att vara icke-dödliga måste felet vara känt för att vara deterministiskt.
Observera: The Graph Nätverk stöder ännu inte icke-dödliga fel, och utvecklare bör inte distribuera delgrafer med den funktionaliteten till nätverket via Studio.
Aktivering av icke-dödliga fel kräver att följande funktionsflagga sätts i delgrafens manifest:
specVersion: 0.0.4description: Gravatar for Ethereumfeatures:- nonFatalErrors...
Frågan måste också välja att fråga efter data med potentiella inkonsekvenser genom argumentet subgraphError
. Det rekommenderas också att fråga _meta
för att kontrollera om subgrafen har hoppat över fel, som i exemplet:
foos(first: 100, subgraphError: allow) {id}_meta {hasIndexingErrors}
Om subgrafen stöter på ett fel returnerar frågan både data och ett graphql-fel med meddelandet "indexing_error"
, som i detta exempelsvar:
"data": {"foos": [{"id": "0xdead"}],"_meta": {"hasIndexingErrors": true}},"errors": [{"message": "indexing_error"}]
Observera: Det rekommenderas inte att använda ympning vid initial uppgradering till The Graph Nätverk. Läs mer .
När en delgraf först distribueras börjar den indexera händelser från genesisblocket på den motsvarande kedjan (eller på startBlock
som är definierat för varje datakälla). I vissa situationer kan det vara fördelaktigt att återanvända data från en befintlig delgraf och börja indexera vid en mycket senare block. Denna indexeringsläge kallas Ympning. Ympning är exempelvis användbart under utvecklingen för att snabbt komma förbi enkla fel i mappningarna eller tillfälligt få en befintlig delgraf att fungera igen efter att den har misslyckats.
En delgraf ympas på en grunddelgraf när delgrafmanifestet i subgraph.yaml
innehåller en graft
-block högst upp:
description: ...graft:base: Qm... # Subgraph ID of base subgraphblock: 7345624 # Block number
När en delgraf vars manifest innehåller en graft
-sektion distribueras kommer Graph Node att kopiera data från den base
delgrafen upp till och inklusive det angivna block
och sedan fortsätta indexera den nya delgrafen från det blocket. Basdelgrafen måste finnas på målnoden Graph Node och måste ha indexerats upp till minst det angivna blocket. På grund av denna begränsning bör ympning endast användas under utveckling eller i en nödsituation för att snabba upp produktionen av en motsvarande icke-ympad delgraf.
Eftersom ympning kopierar data istället för att indexera basdata går det mycket snabbare att få delgrafen till det önskade blocket än att indexera från början, även om den initiala datorkopieringen fortfarande kan ta flera timmar för mycket stora delgrafer. Medan den ympade delgrafen initialiseras kommer Graph Node att logga information om de entitetstyper som redan har kopierats.
Den ympade delgrafen kan använda ett GraphQL-schema som inte är identiskt med basdelgrafens, men bara kompatibelt med den. Det måste vara ett giltigt delgrafschema i sig själv, men kan avvika från basdelgrafens schema på följande sätt:
- Den lägger till eller tar bort entitetstyper
- Den tar bort attribut från entitetstyper
- Den lägger till nollställbara attribut till entitetstyper
- Den gör icke-nollställbara attribut till nollställbara attribut
- Den lägger till värden till enum
- Den lägger till eller tar bort gränssnitt
- Den ändrar vilka entitetstyper som ett gränssnitt är implementerat för
Filbaserade datakällor är en ny delgrafsfunktion för att få tillgång till data utanför kedjan under indexering på ett robust, utökat sätt. Filbaserade datakällor stödjer hämtning av filer från IPFS och från Arweave.
Detta lägger också grunden för deterministisk indexering av data utanför kedjan, samt möjligheten att introducera godtycklig data som hämtas via HTTP.
Rather than fetching files "in line" during handler execution, this introduces templates which can be spawned as new data sources for a given file identifier. These new data sources fetch the files, retrying if they are unsuccessful, running a dedicated handler when the file is found.
This is similar to the , which are used to dynamically create new chain-based data sources.
Detta ersätter den befintliga ipfs.cat
API
Filbaserade datakällor kräver graph-ts >=0.29.0 och graph-cli >=0.33.1
Filbaserade datakällor kan inte komma åt eller uppdatera kedjebaserade entiteter, utan måste uppdatera filspecifika entiteter.
Detta kan innebära att fält från befintliga entiteter separeras i separata entiteter som är kopplade ihop.
Ursprunglig kombinerad entitet:
type Token @entity {id: ID!tokenID: BigInt!tokenURI: String!externalURL: String!ipfsURI: String!image: String!name: String!description: String!type: String!updatedAtTimestamp: BigIntowner: User!}
Ny, delad enhet:
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!}
Om relationen är 1:1 mellan föräldraentiteten och den resulterande filbaserade datakälla entiteten är det enklaste mönstret att länka föräldraentiteten till en resulterande filbaserad entitet genom att använda IPFS CID som söknyckel. Kontakta oss på Discord om du har svårt att modellera dina nya filbaserade entiteter!
Detta är datakällan som skapas när en intressant fil identifieras.
templates:- name: TokenMetadatakind: file/ipfsmapping:apiVersion: 0.0.7language: wasm/assemblyscriptfile: ./src/mapping.tshandler: handleMetadataentities:- TokenMetadataabis:- name: Tokenfile: ./abis/Token.json
För närvarande krävs abis
, även om det inte är möjligt att anropa kontrakt från filbaserade datakällor
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 ().
CID för filen som en läsbar sträng kan nås via dataSource
enligt följande:
const cid = dataSource.stringParam()
Exempel på hanterare:
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()}}
Nu kan du skapa filbaserade datakällor under utförandet av kedjebaserade hanterare:
- Importera mallen från den automatiskt genererade
templates
- anropa
TemplateName.create(cid: string)
från en mappning, där cid är en giltig innehållsidentifierare för IPFS eller Arweave
För IPFS stöder Graph Node , och innehållsidentifierare med kataloger (t.ex. 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 .
Exempel:
import { TokenMetadata as TokenMetadataTemplate } from '../generated/templates'const ipfshash = 'QmaXzZhcYnsisuue5WRdQDH6FDvqkLQX1NckLqBYeYYEfm'//Denna exempelkod är för en undergraf för kryptosamverkan. Ovanstående ipfs-hash är en katalog med tokenmetadata för alla kryptosamverkande NFT:er.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//Detta skapar en sökväg till metadata för en enskild Crypto coven NFT. Den konkaterar katalogen med "/" + filnamn + ".json"token.ipfsURI = tokenIpfsHashTokenMetadataTemplate.create(tokenIpfsHash)}token.updatedAtTimestamp = event.block.timestamptoken.owner = event.params.to.toHexString()token.save()}
Detta kommer att skapa en ny filbaserad datakälla som kommer att övervaka Graph Nodes konfigurerade IPFS- eller Arweave-slutpunkt och försöka igen om den inte hittas. När filen hittas kommer filbaserad datakälla hanteraren att köras.
I det här exemplet används CID som koppling mellan förälderentiteten Token
och den resulterande entiteten TokenMetadata
.
Tidigare är detta det punkt där en delgrafutvecklare skulle ha anropat ipfs.cat(CID)
för att hämta filen
Grattis, du använder filbaserade datakällor!
Du kan nu bygga
och distribuera
dina delgrafer till en Graph Node >=v0.30.0-rc.0.
Filbaserade datakällahanterare och entiteter är isolerade från andra delgrafentiteter, vilket säkerställer att de är deterministiska när de körs och att ingen förorening av kedjebaserade datakällor sker. För att vara specifik:
- Entiteter skapade av Filbaserade datakällor är oföränderliga och kan inte uppdateras
- Filbaserade datakällahanterare kan inte komma åt entiteter från andra filbaserade datakällor
- Entiteter associerade med filbaserade datakällor kan inte nås av kedjebaserade hanterare
Även om denna begränsning inte bör vara problematisk för de flesta användningsfall kan den införa komplexitet för vissa. Var god kontakta oss via Discord om du har problem med att modellera din data baserad på fil i en delgraf!
Dessutom är det inte möjligt att skapa datakällor från en filbaserad datakälla, vare sig det är en datakälla på kedjan eller en annan filbaserad datakälla. Denna begränsning kan komma att hävas i framtiden.
Om du länkar NFT-metadata till motsvarande token, använd metadata IPFS-hash för att referera till en Metadata-entitet från Token-entiteten. Spara Metadata-entiteten med IPFS-hash som ID.
You can use when creating File Data Sources to pass extra information which will be available to the File Data Source handler.
Om du har entiteter som uppdateras flera gånger, skapa unika filbaserade entiteter med IPFS-hash & entitets-ID, och referera till dem med hjälp av ett härlett fält i kedjebaserade entiteten.
Vi arbetar med att förbättra rekommendationen ovan så att förfrågningar endast returnerar den "senaste" versionen
Filbaserade datakällor kräver för närvarande ABIs, även om ABIs inte används (). Ett arbetsområde är att lägga till en 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.