マッピング
マッピング
マッピング関数は、 schema.graphql
ファイルで定義したチェーンデータを最適化した GraphQL エンティティに変換する方法を定義します。
- マッピングは
src/mappings
ディレクトリに定義され、関数としてエクスポートされます。 - これらのマッピングは
src/index.ts
にもエクスポートされます。 - マッピングファイルはマッピングハンドラの下の
project.yaml
内で参照されます。
マッピング関数には次の 3 つのクラスがあります。 ブロックハンドラ, イベントハンドラ, 呼び出しハンドラ
ブロックハンドラ
新しいブロックが Substrate チェーンに接続されるたびに、ブロックハンドラを使用して情報を取得できます。 これを実行するために、定義されたブロックハンドラが各ブロックに対して 1 回呼び出されます。
import { SubstrateBlock } from "@subql/types";
export async function handleBlock(block: SubstrateBlock): Promise<void> {
// Create a new StarterEntity with the block hash as it's ID
const record = new starterEntity(block.block.header.hash.toString());
record.field1 = block.block.header.number.toNumber();
await record.save();
}
SubstrateBlockはsignedBlockの拡張インターフェースタイプで、specVersion
とtimestamp
も含まれています。
イベント ハンドラ
特定のイベントが新しいブロックに含まれる場合、イベントハンドラを使用して情報を取得できます。 デフォルトの Substrate ランタイムとブロックの一部であるイベントには、複数のイベントが含まれます。
処理中、イベントハンドラは substrate イベントを受け取り、イベントの型付けされた入出力を持つ引数として受け取ります。 どのようなタイプのイベントでもマッピングのトリガーとなり、データソースでのアクティビティをキャプチャすることができます。 イベントをフィルタリングするには、マニフェストで マッピングフィルタ を使用し、データのインデックス化とマッピングのパフォーマンスを向上させる必要があります。
import {SubstrateEvent} from "@subql/types";
export async function handleEvent(event: SubstrateEvent): Promise<void> {
const {event: {data: [account, balance]}} = event;
// Retrieve the record by its ID
const record = new starterEntity(event.extrinsic.block.block.header.hash.toString());
record.field2 = account.toString();
record.field3 = (balance as Balance).toBigInt();
await record.save();
SubstrateEvent は EventRecord の拡張インターフェイス型です。 イベントデータのほかに、 id
(このイベントが属するブロック) と、このブロックの内部にある外部データも含まれています。
呼び出しハンドラ
呼び出しハンドラは、特定の Substrate 外部関数の情報をキャプチャするときに使用されます。
export async function handleCall(extrinsic: SubstrateExtrinsic): Promise<void> {
const record = new starterEntity(
extrinsic.block.block.header.hash.toString()
);
record.field4 = extrinsic.block.timestamp;
await record.save();
}
SubstrateExtinsic は GenericExtrinsic を拡張します。 このブロックには、 id
(この外部が属するブロック) が割り当てられており、このブロックの中でイベントを拡張する外部プロパティを提供します。 さらに、この外部関数のステータスを記録します。
クエリの状態
私たちの目標は、マッピングハンドラ(上記の 3 つのインターフェースイベントタイプだけでなく)のために、ユーザーのすべてのデータソースをカバーすることです。 したがって、私たちは @polkadot/api インタフェースのいくつかを公開しています。
これらは現在サポートされているインターフェースです:
- api.query.<module>.<method>() は 現在の ブロックを問い合わせます。
- api.query.<module>.<method>.multi() は、現在のブロックで 同じ 型の複数のクエリを実行します。
- api.queryMulti() は、現在のブロックで 異なる 型の複数のクエリを実行します。
これらは現在サポート されていない インターフェイスです:
api.tx.*api.derive.*api.query.<module>.<method>.atapi.query.<module>.<method>.entriesAtapi.query.<module>.<method>.entriesPagedapi.query.<module>.<method>.hashapi.query.<module>.<method>.keysAtapi.query.<module>.<method>.keysPagedapi.query.<module>.<method>.rangeapi.query.<module>.<method>.sizeAt
validator-threshold のユースケースでこの API を使用する例をご覧ください。
RPC コール
また、マッピング関数が実際のノード、クエリー、および送信を行うことを可能にするリモートコールである API RPC 関数もサポートしています。 SubQuery は決定論的であることを前提としているため、結果の一貫性を保つために、過去の RPC コールのみを許可しています。
JSON-RPCのドキュメントでは、BlockHash
を入力パラメータとして受け取るいくつかのメソッド(例:at?: BlockHash
)がありますが、これが許可されるようになりました。 また、これらの関数は、現在のインデックスブロックハッシュをデフォルトで受け取るように変更しました。
// Let's say we are currently indexing a block with this hash number
const blockhash = `0x844047c4cf1719ba6d54891e92c071a41e3dfe789d064871148e9d41ef086f6a`;
// Original method has an optional input is block hash
const b1 = await api.rpc.chain.getBlock(blockhash);
// It will use the current block has by default like so
const b2 = await api.rpc.chain.getBlock();
- カスタム Substrate チェーン RPC コールについては、 使用法 を参照してください。
モジュールとライブラリ
SubQuery のデータ処理能力を向上させるには sandboxでマッピング関数を実行するための NodeJS の組み込みモジュールの一部を許可しました。
これは 実験的機能 であり、マッピング関数に悪影響を与えるバグや問題が発生する可能性があります。 Issue を GitHub で作成することで、バグを報告してください。
組み込みモジュール
現在、次の NodeJS モジュールを許可しています:assert
, buffer
, crypto
, util
, path
モジュール全体をインポートするのではなく、必要なメソッドだけをインポートすることをお勧めします。 これらのモジュールのいくつかのメソッドにはサポートされていない依存関係があり、インポート時に失敗する可能性があります。
import { hashMessage } from "ethers/lib/utils"; // Good way
import { utils } from "ethers"; // Bad way
サードパーティライブラリ
サンドボックス内の仮想マシンが制限されているため、現在、 CommonJS によって書かれたサードパーティ製ライブラリのみをサポートしています。
hybrid ライブラリ(例えば @polkadot/*
)もサポートしており、ESM をデフォルトとして使用しています。 しかし、他のライブラリがESM形式のモジュールに依存している場合、仮想マシンはコンパイルされず、エラーを返します。
カスタム Substrate チェーン
SubQuery は、Polkadot や Kusama だけではなく、Substrate-based chain 上で使用できます。
Substrate ベースのカスタムチェーンを使用することができ、@polkadot/typegenを使用してタイプ、インターフェイス、追加関数を自動的にインポートするツールを提供しています。
以下のセクションでは、 キティの例 を使用して統合プロセスを説明します。
準備
プロジェクトの src
フォルダの下に新しいディレクトリ api-interfaces
を作成し、必要なファイルと生成されたファイルをすべて保存します。 kitties
モジュールから API にデコレーションを追加するため、 api-interfaces/kitties
ディレクトリを作成します。
メタデータ
実際の API エンドポイントを生成するにはメタデータが必要です。 キティの例では、ローカルのテストネットからのエンドポイントを使用し、追加の型を提供します。 PolkadotJS metadata setup の手順に従い、 HTTP endpoint からノードのメタデータを取得します。
curl -H "Content-Type: application/json" -d '{"id":"1", "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' http://localhost:9933
または、websocketエンドポイントからwebsocat
の助けを借ります。
//Install the websocat
brew install websocat
//Get metadata
echo state_getMetadata | websocat 'ws://127.0.0.1:9944' --jsonrpc
次に、JSON ファイルに出力結果をコピーし、貼り付けます。 キティの例では、 api-interface/kitty.json
を作成しました。
型の定義
ここでは、ユーザーがチェーンから特定のタイプと RPC サポートを知っており、それがManifestで定義されていることを想定しています。
types setupに続いて作成します
src/api-interfaces/definitions.ts
- 全てのサブフォルダ定義をエクスポートします
export { default as kitties } from "./kitties/definitions";
src/api-interfaces/kities/definitions.ts
- kitties モジュールの型定義
export default {
// custom types
types: {
Address: "AccountId",
LookupSource: "AccountId",
KittyIndex: "u32",
Kitty: "[u8; 16]",
},
// custom rpc : api.rpc.kitties.getKittyPrice
rpc: {
getKittyPrice: {
description: "Get Kitty price",
params: [
{
name: "at",
type: "BlockHash",
isHistoric: true,
isOptional: false,
},
{
name: "kittyIndex",
type: "KittyIndex",
isOptional: false,
},
],
type: "Balance",
},
},
};
パッケージ
package.json
ファイルでは、@polkadot/typegen
を開発用の依存関係に、@polkadot/api
を通常の依存関係(理想的には同じバージョン)に追加してください。 また、スクリプトを実行するために、開発の依存性としてts-node
も必要です。- 両方の型を実行するスクリプトを追加します;
generate:defs
と メタデータジェネレータgenerate:metadata
(この順序で、メタデータが型を使用できるようにします)
以下は package.json
の簡略化されたバージョンです。 スクリプト セクションでパッケージ名が正しく、ディレクトリが有効であることを確認します。
{
"name": "kitty-birthinfo",
"scripts": {
"generate:defs": "ts-node --skip-project node_modules/.bin/polkadot-types-from-defs --package kitty-birthinfo/api-interfaces --input ./src/api-interfaces",
"generate:meta": "ts-node --skip-project node_modules/.bin/polkadot-types-from-chain --package kitty-birthinfo/api-interfaces --endpoint ./src/api-interfaces/kitty.json --output ./src/api-interfaces --strict"
},
"dependencies": {
"@polkadot/api": "^4.9.2"
},
"devDependencies": {
"typescript": "^4.1.3",
"@polkadot/typegen": "^4.9.2",
"ts-node": "^8.6.2"
}
}
型の生成
これで準備が完了し、型とメタデータを生成する準備が整いました。 以下のコマンドを実行します。
# Yarn to install new dependencies
yarn
# Generate types
yarn generate:defs
各モジュールのフォルダ(例:/kitties
)には、このモジュールの定義からすべてのインターフェイスを定義したtypes.ts
と、それらすべてをエクスポートするindex.ts
が生成されているはずです。
# Generate metadata
yarn generate:meta
このコマンドは、API のメタデータと新しい拡張機能を生成します。 組み込みの API を使用したくないので、tsconfig.json
に明示的なオーバーライドを追加して置き換える必要があります。 更新後、設定内のパスは次のようになります(コメントなし):
{
"compilerOptions": {
// this is the package name we use (in the interface imports, --package for generators) */
"kitty-birthinfo/*": ["src/*"],
// here we replace the @polkadot/api augmentation with our own, generated from chain
"@polkadot/api/augment": ["src/interfaces/augment-api.ts"],
// replace the augmented types with our own, as generated from definitions
"@polkadot/types/augment": ["src/interfaces/augment-types.ts"]
}
}
使用法
マッピング関数では、メタデータと型が実際に API をどのようにデコレートするかを示すことができます。 RPC エンドポイントは、上記で宣言したモジュールと関数をサポートします。 カスタム RPC 呼び出しを使用するには、カスタムチェーン RPC 呼び出しのセクションを参照してください。
export async function kittyApiHandler(): Promise<void> {
//return the KittyIndex type
const nextKittyId = await api.query.kitties.nextKittyId();
// return the Kitty type, input parameters types are AccountId and KittyIndex
const allKitties = await api.query.kitties.kitties("xxxxxxxxx", 123);
logger.info(`Next kitty id ${nextKittyId}`);
//Custom rpc, set undefined to blockhash
const kittyPrice = await api.rpc.kitties.getKittyPrice(
undefined,
nextKittyId
);
}
このプロジェクトをエクスプローラに公開したい場合は、生成されたファイルを src/api-interface
に含めてください。
カスタムチェーン RPC コール
カスタマイズされたチェーン RPC 呼び出しをサポートするには、仕様ごとの設定を可能にする typesBundle
に手動で RPC 定義を挿入する必要があります。 project.yml
で typesBundle
を定義できます。 そして、 isHistoric
タイプの呼び出しのみがサポートされていることを覚えておいてください。
...
types: {
"KittyIndex": "u32",
"Kitty": "[u8; 16]",
}
typesBundle: {
spec: {
chainname: {
rpc: {
kitties: {
getKittyPrice:{
description: string,
params: [
{
name: 'at',
type: 'BlockHash',
isHistoric: true,
isOptional: false
},
{
name: 'kittyIndex',
type: 'KittyIndex',
isOptional: false
}
],
type: "Balance",
}
}
}
}
}
}