見出し画像

Subgraphの定義と作成方法|コールハンドラーやマッピング機能について

サブグラフは、グラフがイーサリアムからインデックスを作成するデータと、それを保存する方法を定義します。デプロイされると、ブロックチェーンデータのグローバルグラフの一部を形成します。

サブグラフを定義する


サブグラフの定義は、いくつかのファイルで構成されています。


・subgraph.yaml:サブグラフマニフェストを含むYAMLファイル

・schema.graphql:サブグラフに保存されるデータと、GraphQLを介してデータをクエリする方法を定義するGraphQLスキーマ

・AssemblyScript Mappings:Ethereumのイベントデータからスキーマで定義されたエンティティに変換するAssemblyScriptコード(mapping.tsこのチュートリアルなど)


マニフェストファイルの内容について詳しく説明する前に、グラフCLIをインストールする必要があります。これは、サブグラフを作成してデプロイする必要があります。


グラフCLIをインストールします


Graph CLIはJavaScriptで記述されており、インストールするかyarn、npm使用する必要があります。以下に糸があると仮定します。


取得したらyarn、実行してGraphCLIをインストールします


yarnでインストール:

yarn global add @graphprotocol/graph-cli

npmでインストール:

npm install -g @graphprotocol/graph-cli


サブグラフを作成する


Graph CLIを使用する前に、SubgraphStudioでサブグラフを作成する必要があります。その後、サブグラフプロジェクトをセットアップし、選択したプラットフォームにデプロイできるようになります。イーサリアムメインネットにインデックスを付けないサブグラフは、グラフネットワークに公開されないことに注意してください。


このgraph initコマンドを使用して、任意のパブリックイーサリアムネットワーク上の既存の契約から、またはサブグラフの例から、新しいサブグラフプロジェクトを設定できます。このコマンドを使用して、を渡すことにより、SubgraphStudioでサブグラフを作成できます。


graph init --product subgraph-studio.


既存のコントラクトから


すでにイーサリアムメインネットまたはテストネットの1つにスマートコントラクトがデプロイされている場合は、このコントラクトから新しいサブグラフをブートストラップすることから始めるのが良い方法です。


次のコマンドは、既存の契約のすべてのイベントにインデックスを付けるサブグラフを作成します。EtherscanからコントラクトABIをフェッチしようとし、ローカルファイルパスの要求にフォールバックします。オプションの引数のいずれかが欠落している場合は、インタラクティブなフォームが表示されます。


graph init \
 --product subgraph-studio
 --from-contract <CONTRACT_ADDRESS> \
 [--network <ETHEREUM_NETWORK>] \
 [--abi <FILE>] \
 <SUBGRAPH_SLUG> [<DIRECTORY>]


これ<SUBGRAPH_SLUG>は、Subgraph StudioのサブグラフのIDであり、サブグラフの詳細ページにあります。


サポートされているネットワーク


グラフネットワークは、メインネットイーサリアムのインデックスを作成するサブグラフをサポートしています。


mainnet


LegacyExplorerでは追加のネットワークがサポートされています。

mainnet
kovan
rinkeby
ropsten
goerli
poa-core
poa-sokol
xdai
matic
mumbai
fantom
bsc
chapel
clover
avalanche
fuji
celo
celo-alfajores
fuse
mbase
arbitrum-one
arbitrum-rinkeby
optimism
optimism-kovan

注:あなたが部分グラフを公開することができませんインデックスという点で分散型のグラフネットワークへの非mainnetネットワークサブグラフメーカー。


サブグラフの例


2番目のモードのgraph initサポートは、サブグラフの例から新しいプロジェクトを作成することです。次のコマンドはこれを行います。


graph init --studio <SUBGRAPH_SLUG>


例の部分グラフは、ユーザーのアバターと発し管理ダニ・グラントによって重力契約に基づいているNewGravatarか、UpdateGravatarアバターが作成または更新されるたびにイベントを。サブグラフは、Gravatarエンティティをグラフノードストアに書き込み、イベントに従ってこれらが更新されるようにすることで、これらのイベントを処理します。次のセクションでは、この例のサブグラフマニフェストを構成するファイルについて説明します。


サブグラフマニフェスト

サブグラフマニフェストsubgraph.yamlは、サブグラフインデックスのスマートコントラクト、これらのコントラクトからのどのイベントに注意を払うか、およびイベントデータをグラフノードが格納してクエリできるエンティティにマップする方法を定義します。サブグラフマニフェストの完全な仕様は、ここにあります。


サブグラフの例subgraph.yamlは次のとおりです。

specVersion: 0.0.1
description: Gravatar for Ethereum
repository: https://github.com/graphprotocol/example-subgraph
schema:
 file: ./schema.graphql
dataSources:
 - kind: ethereum/contract
   name: Gravity
   network: mainnet
   source:
     address: '0x2E645469f354BB4F5c8a05B3b30A929361cf77eC'
     abi: Gravity
     startBlock: 6175244
   mapping:
     kind: ethereum/events
     apiVersion: 0.0.5
     language: wasm/assemblyscript
     entities:
       - Gravatar
     abis:
       - name: Gravity
         file: ./abis/Gravity.json
     eventHandlers:
       - event: NewGravatar(uint256,address,string,string)
         handler: handleNewGravatar
       - event: UpdatedGravatar(uint256,address,string,string)
         handler: handleUpdatedGravatar
     callHandlers:
       - function: createGravatar(string,string)
         handler: handleCreateGravatar
     blockHandlers:
       - function: handleBlock
       - function: handleBlockWithCall
         filter:
           kind: call
     file: ./src/mapping.ts

マニフェスト用に更新する重要なエントリは次のとおりです。

description:サブグラフが何であるかについての人間が読める説明。この説明は、サブグラフがホステッドサービスにデプロイされるときにグラフエクスプローラーによって表示されます。

repository:サブグラフマニフェストを見つけることができるリポジトリのURL。これは、グラフエクスプローラーでも表示されます。

dataSources.source:サブグラフソースのスマートコントラクトのアドレス、および使用するスマートコントラクトのabi。アドレスはオプションです。これを省略すると、すべてのコントラクトから一致するイベントにインデックスを付けることができます。

dataSources.source.startBlock:データソースがインデックス作成を開始するブロックのオプションの番号。ほとんどの場合、コントラクトが作成されたブロックを使用することをお勧めします。

dataSources.mapping.entities:データソースがストアに書き込むエンティティ。各エンティティのスキーマは、schema.graphqlファイルで定義されています。

dataSources.mapping.abis:ソースコントラクトおよびマッピング内から対話するその他のスマートコントラクト用の1つ以上の名前付きABIファイル。

dataSources.mapping.eventHandlers:このサブグラフが反応するスマートコントラクトイベントと、これらのイベントをストア内のエンティティに変換するマッピング内のハンドラー(例では./src/mapping.ts)を一覧表示します。

dataSources.mapping.callHandlers:このサブグラフが反応するスマートコントラクト関数と、関数呼び出しへの入力と出力をストア内のエンティティに変換するマッピング内のハンドラーを一覧表示します。

dataSources.mapping.blockHandlers:このサブグラフが反応するブロックと、ブロックがチェーンに追加されたときに実行されるマッピング内のハンドラーを一覧表示します。フィルタがないと、ブロックハンドラはすべてのブロックで実行されます。オプションのフィルターには、次の種類を指定できます。. Aブロックにデータソースコントラクトへの呼び出しが少なくとも1つ含まれている場合、callcall`フィルターはハンドラーを実行します。

1つのサブグラフで、複数のスマートコントラクトからのデータにインデックスを付けることができます。データをdataSources配列にインデックス付けする必要があるコントラクトごとにエントリを追加します。

ブロック内のデータソースのトリガーは、次のプロセスを使用して順序付けられます。

イベントトリガーと呼び出しトリガーは、最初にブロック内のトランザクションインデックス順に並べられます。
同じトランザクション内のイベントトリガーと呼び出しトリガーは、規則を使用して順序付けられます。最初にイベントトリガー、次に呼び出しトリガー。各タイプは、マニフェストで定義されている順序を尊重します。
ブロックトリガーは、マニフェストで定義されている順序で、イベントトリガーと呼び出しトリガーの後に実行されます。
これらの注文規則は変更される場合があります。

ABIの取得#
ABIファイルは契約と一致する必要があります。ABIファイルを取得する方法はいくつかあります。

独自のプロジェクトを構築している場合は、最新のABIにアクセスできる可能性があります。
公開プロジェクトのサブグラフを作成している場合は、そのプロジェクトをコンピューターにダウンロードし、truffle compilesolcを使用または使用してコンパイルすることでABIを取得できます。
また、EtherscanでABIを見つけることもできますが、アップロードされたABIが古くなっている可能性があるため、これが常に信頼できるとは限りません。適切なABIがあることを確認してください。そうでない場合、サブグラフの実行は失敗します。
GraphQLスキーマ#
サブグラフのスキーマはファイルにありますschema.graphql。GraphQLスキーマは、GraphQLインターフェース定義言語を使用して定義されます。GraphQLスキーマを作成したことがない場合は、GraphQL型システムでこの入門書を確認することをお勧めします。GraphQLスキーマのリファレンスドキュメントは、GraphQLAPIセクションにあります。

エンティティの定義#
エンティティを定義する前に、一歩下がって、データがどのように構造化およびリンクされているかを考えることが重要です。すべてのクエリは、サブグラフスキーマで定義されたデータモデルと、サブグラフによってインデックス付けされたエンティティに対して行われます。このため、dAppのニーズに一致する方法でサブグラフスキーマを定義することをお勧めします。エンティティを、イベントや関数としてではなく、「データを含むオブジェクト」として想像すると便利な場合があります。

グラフを使用すると、でエンティティタイプを定義するだけでschema.graphql、グラフノードはそのエンティティタイプの単一のインスタンスとコレクションをクエリするためのトップレベルのフィールドを生成します。エンティティである必要がある各タイプには、@entityディレクティブで注釈を付ける必要があります。

良い例#
Gravatar以下のエンティティはGravatarオブジェクトを中心に構成されており、エンティティを定義する方法の良い例です。

type Gravatar @entity {
 id: ID!
 owner: Bytes
 displayName: String
 imageUrl: String
 accepted: Boolean
}

悪い例#
以下の例GravatarAcceptedとGravatarDeclinedエンティティは、イベントに基づいています。イベントまたは関数呼び出しをエンティティに1:1でマップすることはお勧めしません。

type GravatarAccepted @entity {
 id: ID!
 owner: Bytes
 displayName: String
 imageUrl: String
}

type GravatarDeclined @entity {
 id: ID!
 owner: Bytes
 displayName: String
 imageUrl: String
}


オプションおよび必須フィールド


エンティティフィールドは、必須またはオプションとして定義できます。必須フィールドは!、スキーマ内でで示されます。マッピングに必須フィールドが設定されていない場合、フィールドをクエリすると次のエラーが発生します。

Null value resolved for non-null field 'name'


各エンティティにidは、タイプID!(文字列)のフィールドが必要です。idフィールドが主キーとして機能し、同じタイプのすべてのエンティティの中で一意である必要があります。

組み込みのスカラー型#
GraphQLでサポートされているスカラー#
GraphQLAPIでは次のスカラーをサポートしています。



Bytes:16進文字列として表されるバイト配列。イーサリアムのハッシュとアドレスに一般的に使用されます。


ID:stringとして保存されます。


String:string値のスカラー。ヌル文字はサポートされておらず、自動的に削除されます。


Boolean:boolean値のスカラー。


Int:GraphQL仕様ではInt、サイズが32バイトであると定義されています。


BitInt:大きな整数。イーサリアムのために使用されるuint32、int64、uint64、...、uint256のタイプ。注:以下のすべてuint32のようなint32、uint24またはint8として表されますi32。


BigDecimal:BigDecimal仮数と指数として表される高精度の小数。指数の範囲は-6143から+6144です。有効数字34桁に丸められます。


列挙型


スキーマ内に列挙型を作成することもできます。列挙型の構文は次のとおりです。

enum TokenStatus {
 OriginalOwner
 SecondOwner
 ThirdOwner
}

​スキーマで列挙型が定義されると、列挙型値の文字列表現を使用して、エンティティに列挙型フィールドを設定できます。たとえば、最初にエンティティを定義し、次にフィールドをで設定することで、tokenStatusをに設定できSecondOwnerますentity.tokenStatus = "SecondOwner。以下の例は、トークンエンティティが列挙型フィールドでどのように見えるかを示しています。

列挙型の記述の詳細については、GraphQLのドキュメントを参照してください。


実体関連


エンティティは、スキーマ内の他の1つ以上のエンティティと関係がある場合があります。これらの関係は、クエリでトラバースされる可能性があります。グラフの関係は一方向です。関係のいずれかの「端」で単方向の関係を定義することにより、双方向の関係をシミュレートすることができます。


関係は、指定されたタイプが別のエンティティのタイプであることを除いて、他のフィールドと同じようにエンティティで定義されます。


1対1の関係

Transactionエンティティタイプとのオプションの1対1の関係を使用して、エンティティタイプを定義しますTransactionReceipt。

type Transaction @entity {
 id: ID!
 transactionReceipt: TransactionReceipt
}

type TransactionReceipt @entity {
 id: ID!
 transaction: Transaction
}

1対多の関係


TokenBalanceトークンエンティティタイプとの必要な1対多の関係を持つエンティティタイプを定義します。

type Token @entity {
 id: ID!
}

type TokenBalance @entity {
 id: ID!
 amount: Int!
 token: Token!
}

逆引き参照


逆引き参照は、@derivedFromフィールドを介してエンティティで定義できます。これにより、エンティティに仮想フィールドが作成されます。この仮想フィールドは、クエリを実行できますが、マッピングAPIを介して手動で設定することはできません。むしろ、他のエンティティで定義された関係から派生します。このような関係の場合、関係の両側を格納することはほとんど意味がありません。一方のみを格納し、もう一方を派生させると、インデックス作成とクエリのパフォーマンスの両方が向上します。


1対多の関係の場合、関係は常に「1」側に格納され、「多」側は常に導出される必要があります。エンティティの配列を「多」側に格納するのではなく、この方法で関係を格納すると、サブグラフのインデックス作成とクエリの両方のパフォーマンスが劇的に向上します。一般に、エンティティの配列を格納することは、実用的である限り避ける必要があります。



tokenBalancesフィールドを導出することにより、トークンからトークンの残高にアクセスできるようにすることができます。

type Token @entity {
 id: ID!
 tokenBalances: [TokenBalance!]! @derivedFrom(field: "token")
}

type TokenBalance @entity {
 id: ID!
 amount: Int!
 token: Token!
}

多対多の関係


それぞれが任意の数の組織に属している可能性があるユーザーなど、多対多の関係の場合、関係をモデル化する最も簡単ですが、一般的に最もパフォーマンスの高い方法は、関係する2つのエンティティのそれぞれの配列としてです。関係が対称である場合、関係の一方の側だけを保存する必要があり、もう一方の側を導出できます。


Userエンティティタイプからエンティティタイプへの逆引き参照を定義しOrganizationます。以下の例では、これはエンティティmembers内から属性を検索することによって実現されOrganizationます。クエリでは、上のorganizationsフィールドは、ユーザーのIDを含むUserすべてのOrganizationエンティティを見つけることによって解決されます。

type Organization @entity {
 id: ID!
 name: String!
 members: [User!]!
}

type User @entity {
 id: ID!
 name: String!
 organizations: [Organization!]! @derivedFrom(field: "members")
}

この関係を保存するためのよりパフォーマンスの高い方法は、次のようなスキーマを持つUser/Organizationペアごとに1つのエントリを持つマッピングテーブルを使用することです。

type Organization @entity {
 id: ID!
 name: String!
 members: [UserOrganization]! @derivedFrom(field: "user")
}

type User @entity {
 id: ID!
 name: String!
 organizations: [UserOrganization!] @derivedFrom(field: "organization")
}

type UserOrganization @entity {
 id: ID! # Set to `${user.id}-${organization.id}`
 user: User!
 organization: Organization!
}

このアプローチでは、たとえば、ユーザーの組織を取得するために、クエリが1つの追加レベルに下がる必要があります。

query usersWithOrganizations {
 users {
   organizations {
     # this is a UserOrganization entity
     organization {
       name
     }
   }
 }
}

多対多の関係を格納するこのより複雑な方法では、サブグラフに格納されるデータが少なくなるため、サブグラフのインデックス作成とクエリが大幅に高速化されます。

スキーマへのコメントの追加


GraphQL仕様に従って、二重引用符" "を使用してスキーマエンティティ属性の上にコメントを追加できます。これを以下の例に示します。

type MyFirstEntity @entity {
 "unique identifier and primary key of the entity"
 id: ID!
 address: Bytes!
}

全文検索フィールドの定義


全文検索クエリは、テキスト検索入力に基づいてエンティティをフィルタリングおよびランク付けします。フルテキストクエリは、インデックス付きテキストデータと比較する前に、ステムに入力されたクエリテキストを処理することにより、類似した単語の一致を返すことができます。


フルテキストクエリ定義には、クエリ名、テキストフィールドの処理に使用される言語辞書、結果の順序付けに使用されるランキングアルゴリズム、および検索に含まれるフィールドが含まれます。各フルテキストクエリは複数のフィールドにまたがることができますが、含まれるすべてのフィールドは単一のエンティティタイプからのものである必要があります。


フルテキストクエリを追加するに_Schema_は、GraphQLスキーマにフルテキストディレクティブを持つタイプを含めます。


type _Schema_
 @fulltext(
   name: "bandSearch"
   language: en
   algorithm: rank
   include: [{ entity: "Band", fields: [{ name: "name" }, { name: "description" }, { name: "bio" }] }]
 )

type Band @entity {
 id: ID!
 name: String!
 description: String!
 bio: String
 wallet: Address
 labels: [Label!]!
 discography: [Album!]!
 members: [Musician!]!
}


例のbandSearchフィールドは、name、descriptionおよびbioフィールドのテキスト文書に基づいたBandエンティティにフィルターされたクエリで使用することができます。


GraphQLAPIにジャンプします-全文検索APIの説明とその他の使用例についてのクエリ。

query {
 bandSearch(text: "breaks & electro & detroit") {
   id
   name
   description
   wallet
 }
}

サポートされている言語


別の言語を選択すると、全文検索APIに決定的な影響がありますが、微妙な場合もあります。全文クエリフィールドの対象となるフィールドは、選択した言語のコンテキストで検査されるため、分析クエリと検索クエリによって生成される語彙素は言語ごとに異なります。例:サポートされているトルコ語辞書を使用する場合、「token」は「toke」にステム処理されますが、もちろん、英語辞書は「token」にステム処理されます。


サポートされている言語辞書:


コード 辞書


単純 全般的

da デンマーク語
nl オランダの
en 英語
fi フィンランド語
NS フランス語
de ドイツ人
胡 ハンガリー語
それ イタリアの
番号 ノルウェー語
pt ポルトガル語
ro ルーマニア語
ru ロシア
es スペイン語
sv スウェーデンの
tr トルコ語


ランキングアルゴリズム


結果を注文するためにサポートされているアルゴリズム:


ランク:フルテキストクエリの一致品質(0-1)を使用して、結果を並べ替えます。


近接ランク:ランクに似ていますが、試合の近さも含まれます。

マッピングの作成

マッピングは、マッピングがソースしているイーサリアムデータをスキーマで定義されたエンティティに変換します。マッピングは、WASM(WebAssembly)にコンパイルできるAssemblyScriptと呼ばれるTypeScriptのサブセットで記述されます。AssemblyScriptは通常のTypeScriptよりも厳密ですが、使い慣れた構文を提供します。


各イベントハンドラのsubgraph.yaml下にmapping.eventHandlersで定義されている、同じ名前のエクスポートされた関数を作成します。各ハンドラーeventは、処理されているイベントの名前に対応するタイプで呼び出される単一のパラメーターを受け入れる必要があります。


サブグラフの例でsrc/mapping.tsは、NewGravatarおよび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.toHex());
 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.toHex();
 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();
}


最初のハンドラーはNewGravatarイベントを受け取り、対応するイベントパラメーターを使用してエンティティフィールドにデータを入力する新しいGravatarエンティティを作成しnew Gravatar(event.params.id.toHex())ます。このエンティティインスタンスはgravatar、ID値がevent.params.id.toHex()。の変数で表されます。

2番目のハンドラーはGravatar、グラフノードストアから既存のものを読み込もうとします。まだ存在しない場合は、オンデマンドで作成されます。次に、エンティティは、を使用してストアに保存される前に、新しいイベントパラメータに一致するように更新されますgravatar.save()。


新しいエンティティを作成するための推奨ID 


idすべてのエンティティには、同じタイプのすべてのエンティティ間で一意のが必要です。エンティティのid値は、エンティティの作成時に設定されます。以下は、id新しいエンティティを作成するときに考慮すべきいくつかの推奨値です。注:の値はであるid必要がありstringます。

event.params.id.toHex()
event.transaction.from.toHex()
event.transaction.hash.toHex() + "-" + event.logIndex.toString()
グラフノードストアと対話するためのユーティリティと、スマートコントラクトデータおよびエンティティを処理するための便利な機能を含むグラフタイプスクリプトライブラリを提供します。あなたは、インポートすることで、あなたのマッピングでは、このライブラリを使用することができます@graphprotocol/graph-tsでmapping.ts。


コード生成


スマートコントラクト、イベント、エンティティの作業を簡単かつタイプセーフにするために、Graph CLIは、サブグラフのGraphQLスキーマとデータソースに含まれるコントラクトABIからAssemblyScriptタイプを生成できます。

これはで行われます

graph codegen [--output-dir <OUTPUT_DIR>] [<MANIFEST>]

ただし、ほとんどの場合、サブグラフはすでに事前構成されておりpackage.json、次のいずれかを実行するだけで同じことができます。

# Yarn
yarn codegen

# NPM
npm run codegen

これにより、subgraph.yamlで説明したABIファイル内のすべてのスマートコントラクトに対してAssemblyScriptクラスが生成され、これらのコントラクトをマッピング内の特定のアドレスにバインドし、処理中のブロックに対して読み取り専用コントラクトメソッドを呼び出すことができます。また、すべてのコントラクトイベントのクラスを生成して、イベントパラメータ、およびイベントの発生元のブロックとトランザクションに簡単にアクセスできるようにします。

これらのタイプは<OUTPUT_DIR>/<DATA_SOURCE_NAME>/<ABI_NAME>.tsすべてに書き込まれます。例のサブグラフでは、これは次のようになりgenerated/Gravity/Gravity.ts、マッピングでこれらのタイプをインポートできるようになります。

import {
 // The contract class:
 Gravity,
 // The events classes:
 NewGravatar,
 UpdatedGravatar
} from '../generated/Gravity/Gravity';

これに加えて、サブグラフのGraphQLスキーマのエンティティタイプごとに1つのクラスが生成されます。これらのクラスは、タイプセーフなエンティティのロード、エンティティフィールドへの読み取りおよび書き込みアクセス、およびsave()格納するエンティティを書き込むメソッドを提供します。すべてのエンティティクラスはに書き込まれ<OUTPUT_DIR>/schema.ts、マッピングでそれらをインポートできます。

import { Gravatar } from '../generated/schema';

注:コード生成は、マニフェストに含まれるGraphQLスキーマまたはABIに変更を加えるたびに再度実行する必要があります。また、サブグラフを作成または展開する前に、少なくとも1回実行する必要があります。


コード生成では、src/mapping.tsのマッピングコードはチェックされません。サブグラフをグラフエクスプローラーにデプロイする前にそれを確認したいyarn build場合は、TypeScriptコンパイラーが検出する可能性のある構文エラーを実行して修正できます。


データソーステンプレート


イーサリアムスマートコントラクトの一般的なパターンは、レジストリまたはファクトリコントラクトの使用です。このコントラクトでは、それぞれが独自の状態とイベントを持つ任意の数の他のコントラクトを作成、管理、または参照します。これらの下請け契約の住所は事前にわかっている場合とわからない場合があり、これらの契約の多くは時間の経過とともに作成および/または追加される場合があります。このような場合、単一のデータソースまたは固定数のデータソースを定義することは不可能であり、より動的なアプローチであるデータソーステンプレートが必要になるのはこのためです。


主コントラクトのデータソース


最初に、メインコントラクトの通常のデータソースを定義します。以下のスニペットは、Uniswap 交換ファクトリ契約の簡略化されたサンプルデータソースを示しています。NewExchange(address,address)イベントハンドラーに注意してください。これは、factory contractによってチェーン上に新しいexchange contractが作成されたときに発行されます。

dataSources:
 - kind: ethereum/contract
   name: Factory
   network: mainnet
   source:
     address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
     abi: Factory
   mapping:
     kind: ethereum/events
     apiVersion: 0.0.5
     language: wasm/assemblyscript
     file: ./src/mappings/factory.ts
     entities:
       - Directory
     abis:
       - name: Factory
         file: ./abis/factory.json
     eventHandlers:
       - event: NewExchange(address,address)
         handler: handleNewExchange

動的に作成されたコントラクトのデータソーステンプレート


次に、データソーステンプレートをマニフェストに追加します。これらは通常のデータソースと同じですが、sourceの下に事前定義された契約アドレスがない点が異なります。通常、parent contractによって管理または参照されるsub-contractのタイプごとに1つのテンプレートを定義します。

dataSources:
 - kind: ethereum/contract
   name: Factory
   # ... other source fields for the main contract ...
templates:
 - name: Exchange
   kind: ethereum/contract
   network: mainnet
   source:
     abi: Exchange
   mapping:
     kind: ethereum/events
     apiVersion: 0.0.5
     language: wasm/assemblyscript
     file: ./src/mappings/exchange.ts
     entities:
       - Exchange
     abis:
       - name: Exchange
         file: ./abis/exchange.json
     eventHandlers:
       - 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

データソーステンプレートのインスタンス化


最後のステップでは、メインコントラクトマッピングを更新して、テンプレートの1つから動的データソースインスタンスを作成します。この例では、メインコントラクトマッピングを変更してExchangeテンプレートをインポートし、そのExchange.create(address)メソッドを呼び出して、新しいエクスチェンジコントラクトのインデックス作成を開始します。

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 contract
 Exchange.create(event.params.exchange);
}

注:新しいデータソースは、それが作成されたブロックとそれに続くすべてのブロックの呼び出しとイベントのみを処理し、履歴データ、つまり前のブロックに含まれているデータは処理しません。

以前のブロックに新しいデータソースに関連するデータが含まれている場合は、契約の現在の状態を読み取り、新しいデータソースの作成時にその状態を表すエンティティを作成して、そのデータにインデックスを付けるのが最適です。


データソースコンテキスト


データソースコンテキストを使用すると、テンプレートをインスタンス化するときに追加の構成を渡すことができます。この例では、取引所がNewExchangeイベントに含まれている特定の取引ペアに関連付けられているとしましょう。その情報は、次のようにインスタンス化されたデータソースに渡すことができます。

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);
}

Exchangeテンプレートのマッピング内で、コンテキストにアクセスできます。

import { dataSource } from '@graphprotocol/graph-ts';

let context = dataSource.context();
let tradingPair = context.getString('tradingPair');

すべての値のタイプのためのsetStringとgetStringなどのセッターとゲッターがあります。


開始ブロック


startBlockはオプションの設定であり、チェーン内のどのブロックからデータソースがインデックス作成を開始するかを定義できます。開始ブロックを設定すると、データソースは無関係な数百万のブロックをスキップできる可能性があります。通常、サブグラフ開発者はstartBlock、データソースのスマートコントラクトが作成されたブロックに設定します。

dataSources:
 - kind: ethereum/contract
   name: ExampleSource
   network: mainnet
   source:
     address: '0xc0a47dFe034B400B47bDaD5FecDa2621de6c4d95'
     abi: ExampleContract
     startBlock: 6627917
   mapping:
     kind: ethereum/events
     apiVersion: 0.0.5
     language: wasm/assemblyscript
     file: ./src/mappings/factory.ts
     entities:
       - User
     abis:
       - name: ExampleContract
         file: ./abis/ExampleContract.json
     eventHandlers:
       - event: NewEvent(address,address)
         handler: handleNewEvent

注:契約作成ブロックは、Etherscanですばやく検索できます。

検索バーに住所を入力して、コントラクトを検索します。


Contract Creatorセクションの作成トランザクションハッシュをクリックします。その契約の開始ブロックがあるトランザクションの詳細ページをロードします。


ハンドラーの呼び出し


イベントは、契約の状態に関連する変更を収集する効果的な方法を提供しますが、多くの契約では、ガスコストを最適化するためのログの生成を回避しています。このような場合、サブグラフはデータソースコントラクトに対して行われた呼び出しをサブスクライブできます。これは、関数シグネチャを参照する呼び出しハンドラーと、この関数への呼び出しを処理するマッピングハンドラーを定義することで実現されます。これらの呼び出しを処理するために、マッピングハンドラーはethereum.Call、呼び出しへの入力と呼び出しからの出力を入力した引数としてを受け取ります。トランザクションのコールチェーンの任意の深さで行われた呼び出しはマッピングをトリガーし、プロキシコントラクトを介したデータソースコントラクトでのアクティビティをキャプチャできるようにします。


コールハンドラは、指定された関数がコントラクト自体以外のアカウントによって呼び出された場合、またはSolidityで外部としてマークされ、同じコントラクト内の別の関数の一部として呼び出された場合にのみトリガーされます。


注:コールハンドラーは、Rinkeby、Goerli、またはGanacheではサポートされていません。コールハンドラは現在、パリティトレースAPIに依存しており、これらのネットワークはそれをサポートしていません。


コールハンドラーの定義


マニフェストでコールハンドラーを定義するには、callHandlersサブスクライブするデータソースの下に配列を追加するだけです。

dataSources:
 - kind: ethereum/contract
   name: Gravity
   network: mainnet
   source:
     address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
     abi: Gravity
   mapping:
     kind: ethereum/events
     apiVersion: 0.0.5
     language: wasm/assemblyscript
     entities:
       - Gravatar
       - Transaction
     abis:
       - name: Gravity
         file: ./abis/Gravity.json
     callHandlers:
       - function: createGravatar(string,string)
         handler: handleCreateGravatar

これfunctionは、呼び出しをフィルタリングするための正規化された関数シグネチャです。 handlerプロパティを使用すると、ターゲット関数は、データソース契約で呼び出されたときに実行したいと思い、あなたのマッピングで関数の名前です。

マッピング機能


各呼び出しハンドラーは、呼び出された関数の名前に対応するタイプを持つ単一のパラメーターを取ります。上記のサブグラフの例では、マッピングには、createGravatar関数が呼び出さCreateGravatarCallれ、引数としてパラメーターを受け取る ときのハンドラーが含まれています。

import { CreateGravatarCall } from '../generated/Gravity/Gravity';
import { Transaction } from '../generated/schema';

export function handleCreateGravatar(call: CreateGravatarCall): void {
 let id = call.transaction.hash.toHex();
 let transaction = new Transaction(id);
 transaction.displayName = call.inputs._displayName;
 transaction.imageUrl = call.inputs._imageUrl;
 transaction.save();
}

この関数は、handleCreateGravatarによって提供されるのCreateGravatarCallサブクラスであるnewを受け取ります。これには、呼び出しの型指定された入力と出力が含まれます。あなたが実行したときのタイプはあなたのために生成されます。ethereum.Call@graphprotocol/graph-tsCreateGravatarCallgraph codegen

ブロックハンドラー


コントラクトイベントまたは関数呼び出しのサブスクライブに加えて、サブグラフは、新しいブロックがチェーンに追加されたときにデータを更新したい場合があります。これを実現するために、サブグラフは、すべてのブロックの後、または事前定義されたフィルターに一致するブロックの後に関数を実行できます。

サポートされているフィルター

filter:
 kind: call

定義されたハンドラーは、ハンドラーが定義されているコントラクト(データソース)への呼び出しを含むブロックごとに1回呼び出されます。


ブロックハンドラーのフィルターがないため、ハンドラーはすべてのブロックで呼び出されます。データソースには、フィルタータイプごとに1つのブロックハンドラーのみを含めることができます。

dataSources:
 - kind: ethereum/contract
   name: Gravity
   network: dev
   source:
     address: '0x731a10897d267e19b34503ad902d0a29173ba4b1'
     abi: Gravity
   mapping:
     kind: ethereum/events
     apiVersion: 0.0.5
     language: wasm/assemblyscript
     entities:
       - Gravatar
       - Transaction
     abis:
       - name: Gravity
         file: ./abis/Gravity.json
     blockHandlers:
       - handler: handleBlock
       - handler: handleBlockWithCallToContract
         filter:
           kind: call

マッピング機能


マッピング関数はethereum.Block、唯一の引数としてを受け取ります。イベントのマッピング関数と同様に、この関数はストア内の既存のサブグラフエンティティにアクセスし、スマートコントラクトを呼び出し、エンティティを作成または更新できます。

import { ethereum } from '@graphprotocol/graph-ts';

export function handleBlock(block: ethereum.Block): void {
 let id = block.hash.toHex();
 let entity = new Block(id);
 entity.save();
}

匿名イベント


Solidityで匿名イベントを処理する必要がある場合は、次の例のように、イベントのトピック0を指定することで実現できます。

eventHandlers:
 - event: LogNote(bytes4,address,bytes32,bytes32,uint256,bytes)
   topic0: '0xbaa8529c00000000000000000000000000000000000000000000000000000000'
   handler: handleGive

イベントは、署名とトピック0の両方が一致した場合にのみトリガーされます。デフォルトでtopic0は、はイベント署名のハッシュと同じです。

実験的特徴


IPFSピン留め


IPFSをEthereumと組み合わせる一般的な使用例は、チェーン上で維持するにはコストがかかりすぎるIPFSにデータを格納し、Ethereum契約でIPFSハッシュを参照することです。


このようIPFSハッシュを考えると、部分グラフが使用してIPFSから対応するファイルを読み込むことができますipfs.catし、ipfs.map。ただし、これを確実に行うには、これらのファイルが、サブグラフのインデックスを作成するグラフノードが接続するIPFSノードに固定されている必要があります。ホストされているサービスの場合、これは https://api.thegraph.com/ipfs/です。


注:グラフのネットワークはまだサポートされていないipfs.catとipfs.map、と開発者がメーカーを介してネットワークにその機能を使用して部分グラフを展開してはなりません。


サブグラフ開発者がこれを簡単に行えるようにするために、グラフチームは、あるIPFSノードから別のIPFSノードにファイルを転送するためのipfs-syncというツールを作成しました 。

致命的でないエラー


すでに同期されているサブグラフのインデックスエラーにより、デフォルトでサブグラフが失敗し、同期が停止します。あるいは、エラーを引き起こしたハンドラーによって行われた変更を無視することにより、エラーが存在する場合でも同期を継続するようにサブグラフを構成することもできます。


これにより、サブグラフの作成者は、クエリが最新のブロックに対して引き続き提供されている間、サブグラフを修正する時間が与えられますが、エラーの原因となったバグのために結果に一貫性がなくなる可能性があります。一部のエラーは依然として常に致命的であることに注意してください。致命的でないためには、エラーが決定論的であることがわかっている必要があります。


注:グラフネットワークは致命的でないエラーをまだサポートしていないため、開発者はその機能を使用してサブグラフをStudio経由でネットワークにデプロイしないでください。


致命的でないエラーを有効にするには、サブグラフマニフェストに次の機能フラグを設定する必要があります。

features:
 - nonFatalErrors

クエリは、subgraphError引数を介して潜在的な不整合のあるデータのクエリにもオプトインする必要があります 。次_metaの例のように、サブグラフがエラーをスキップしたかどうかを確認するためにクエリを実行することもお勧めします。

foos(first: 100, subgraphError: allow) {
 id
}

_meta {
 hasIndexingErrors
}

サブグラフでエラーが発生した場合、次の"indexing_error"応答例のように、クエリはデータとgraphqlエラーの両方をメッセージとともに返します。

"data": {
   "foos": [
       {
         "id": "fooId"
       }
   ],
   "_meta": {
       "hasIndexingErrors": true
   }
},
"errors": [
   {
       "message": "indexing_error"
   }
]

既存のサブグラフへの移植


サブグラフが最初にデプロイstartBlockされると、対応するチェーンのジェネシスブロック(または各データソースで定義された)でイベントのインデックス作成が開始されます。状況によっては、既存のサブグラフのデータを再利用して、多くの場所でインデックス作成を開始すると便利です。後でブロックします。このインデックス作成モードは、グラフトと呼ばれます。グラフトは、たとえば、開発中にマッピングの単純なエラーをすばやく回避したり、既存のサブグラフが失敗した後に一時的に再び機能させたりするのに役立ちます。


注:グラフトするには、インデクサーがベースサブグラフにインデックスを付けている必要があります。現時点では、グラフネットワークでは推奨されていません。開発者は、その機能を使用してサブグラフをStudio経由でネットワークに展開しないでください。


のサブグラフマニフェストにトップレベルのブロックがsubgraph.yaml含まれている場合、サブグラフはベースサブグラフに接ぎ木され ますgraft。

description: ...
graft:
 base: Qm... # Subgraph ID of base subgraph
 block: 7345624 # Block number

マニフェストにgraftブロックが含まれているサブグラフが展開されると、グラフノードはbaseサブグラフのデータを指定されたものまでコピーし、block そのブロックから新しいサブグラフのインデックスを作成し続けます。ベースサブグラフは、ターゲットグラフノードインスタンスに存在する必要があり、少なくとも指定されたブロックまでインデックスが付けられている必要があります。この制限があるため、グラフトは、同等の非グラフトサブグラフの作成を高速化するために、開発中または緊急時にのみ使用する必要があります。


ベースデータにインデックスを付けるのではなくコピーを移植するため、最初からインデックスを作成するよりも、サブグラフを目的のブロックに移動する方がはるかに高速です。ただし、非常に大きなサブグラフの場合、最初のデータコピーには数時間かかることがあります。グラフトされたサブグラフが初期化されている間、グラフノードはすでにコピーされたエンティティタイプに関する情報をログに記録します。


移植されたサブグラフは、ベースサブグラフのスキーマと同一ではなく、単に互換性のあるGraphQLスキーマを使用できます。それ自体が有効なサブグラフスキーマである必要がありますが、次の方法でベースサブグラフのスキーマから逸脱する可能性があります。


・エンティティタイプを追加または削除します
・エンティティタイプから属性を削除します
・null許容属性をエンティティタイプに追加します
・null許容でない属性をnull許容属性に変換します
・列挙型に値を追加します
・インターフェイスを追加または削除します
・インターフェイスが実装されているエンティティタイプを変更します



この記事が気に入ったらサポートをしてみませんか?