SubQuery开发者指南丨映射(Mapping)
映射函数定义了如何将链数据转换为我们之前在 schema.graphql 文件中定义的优化 GraphQL 实体。
映射是用称为AssemblyScript的TypeScript子集编写的,它可以编译为WASM (WebAssembly)。
映射在 src/mappings 目录中定义,作为为函数导出
这些映射也被导出到 src/index.ts
映射文件在映射处理程序下的 project.yaml 中引用
映射函数分为三类:块处理程序、事件处理程序和调用处理程序。
块处理程序
Block Handler
每次将新块附加到 Substrate 链时,你都可以使用块处理程序来捕获信息,例如块编号。 为了实现这一点,每个块调用一次定义过的 BlockHandler 。
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。
事件处理程序
Event Handler
当一些事件包含在一个新块中时,你可以使用事件处理程序来捕获信息。 该事件是默认 Substrate 运行时间组成部分,一个块可能包含多个事件。
处理过程中,事件处理程序将接收一个 Substrate 事件作为参数,带有事件已键入的输入和输出。 任何类型的事件都会触发映射,导致产生捕获数据源的活动。
你应该在清单中使用映射过滤器Mapping Filters:
https://doc.subquery.network/create/manifest.html#mapping-filters来过滤事件,减少索引数据所需的时间,并提高映射性能。
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 (这个事件所属的块)和这个块的外来内部信息。
调用处理程序
Call Handler
当你想要捕获一些 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();
}
查询状态
Query States
我们的目标是覆盖用户用于映射处理程序的所有数据源(不仅仅是上述三种接口事件类型)。因此,我们公开了一些@polkadot/api 接口以增加功能。
这些是我们目前支持的接口:
api.query.<module>.<method>() (打开新窗口/opens new window)
will query the current block./将查询当前块。
api.query.<module>.<method>() 将查询当前块。
api.query.<module>.<method>.multi() (opens new window/打开新窗口/)
will make multiple queries of the same type at the current block.
api.query.<module>.<method>.multi() 将在当前块进行多个相同类型的查询。
api.queryMulti() (opens new window/打开新窗口)
will make multiple queries of different types at the current block.
api.queryMulti() 将在当前块进行不同类型的多个查询。
这些是我们目前不支持的接口:
api.tx.*
api.derive.*
api.query.<module>.<method>.at
api.query.<module>.<method>.entriesAt
api.query.<module>.<method>.entriesPaged
api.query.<module>.<method>.hash
api.query.<module>.<method>.keysAt
api.query.<module>.<method>.keysPaged
api.query.<module>.<method>.range
api.query.<module>.<method>.sizeAt
在我们的验证器阈值示例中查看使用此 API 的示例
https://github.com/subquery/tutorials-validator-threshold 示例适用案例
RPC调用
RPC Calls
我们还支持一些 API RPC 方法,这些方法是远程调用,允许映射函数与实际节点、查询和提交进行交互。SubQuery 的一个核心前提是它是确定性的,因此,为了保持结果一致,我们只允许历史 RPC 调用。
JSON-RPC 中的文档:
https://polkadot.js.org/docs/substrate/rpc/#rpc,提供一些将 BlockHash 作为输入参数的方法(例如 at?: BlockHash),这些方法现在是允许的。 我们还修改了这些方法以默认采用当前索引块哈希。
对于自定义 Substrate链 RPC 调用,请参阅用法https://doc.subquery.network/create/mapping/#usage。
模块和库
Modules and Libraries
内置模块
Built-in modules
目前,我们允许使用以下 NodeJS 模块:assert、buffer、crypto、util 和 path。
我们建议只导入您需要的必需方法,而不是导入整个模块。 这些模块中的某些方法可能具有不受支持的依赖项,并且会在导入时失败。
import {hashMessage} from "ethers/lib/utils"; //Good way
import {utils} from "ethers" //Bad way
export async function handleCall(extrinsic: SubstrateExtrinsic): Promise<void> {
const record = new starterEntity(extrinsic.block.block.header.hash.toString());
record.field1 = hashMessage('Hello');
await record.save();
}
第三方库
Third-party Libraries
由于我们沙箱中虚拟机的限制,目前我们只支持由 CommonJS 编写的第三方库。
我们还支持像 @polkadot/* 这样的混合库,它默认使用 ESM。 但是,如果任何其他库依赖于 ESM 格式的任何模块,虚拟机将不会编译并显示错误。
自定义Substrate链
Custom Substrate Chains
SubQuery 可用于任何基于 Substrate 的链,而不仅仅是 Polkadot 或 Kusama。
您可以使用自定义的基于 Substrate 的链,我们提供了使用 @polkadot/typegen 自动导入类型、接口和其他方法的工具
在以下部分中,我们使用我们的 kitty,示例解释集成过程:
https://github.com/subquery/tutorials-kitty-chain
准备
Preparation
在项目 src 文件夹下创建一个新目录 api-interfaces 以存储所有必需和生成的文件。 我们还创建了 api-interfaces/kitties 目录,因为我们想从 kitties 模块在 API 中添加修饰。
元数据
Metadata
我们需要元数据来生成实际的 API 端点。 在 kitty 示例中,我们使用了来自本地测试网的端点,它提供了其他类型的端点。 按照 PolkadotJS 元数据设置中的步骤操作,来从其 HTTP 端点检索一个节点的元数据。
curl -H "Content-Type: application/json" -d '{"id":"1", "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' http://localhost:9933
或利用websocat形成websocket端
//Install the websocat
brew install websocat
//Get metadata
echo state_getMetadata | websocat 'ws://127.0.0.1:9944' --jsonrpc
然后,复制黏贴输出到 JSON 文件,在我们 kitty 示例中,我们创建了 api-interface/kitty.json.
键入定义
Type definitions
我们假定用户知道具体类型和来自区块链的RPC支持,并在清单中定义。
使用类型设置,我们创建:
src/api-interfaces/definitions.ts - this exports all the sub-folder definitions
export { default as kitties } from './kitties/definitions';
src/api-interfaces/kitties/definitions.ts - type definitions for the kitties module
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'
}
}
}
包
Packages
在package.json文件中,确保加上 @polkadot/typegen 作为开发依赖项,加上 @polkadot/api 作为常规依赖项 (最好是相同版本)。我们还需要 ts-node 作为开发依赖项帮助我们运行脚本。
我们加上 脚本运行2种类型:generate:defs 和metadata generate:meta 生成器 (此命令下,元数据可使用这些类型)。
下面是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"
}
}
键入生成
Type generation
现在已完成准备,可以生成类型和元数据。运行以下命令:
# Yarn to install new dependencies
yarn
# Generate types
yarn generate:defs
每个模块文件夹(eg /kitties),应有已生成的types.ts 可以定义按照该模块定义的所有接口,且文件 index.ts可以全部将他们导出。
# Generate metadata
yarn generate:meta
该命令将生成元数据和新的针对 API 的 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"]
}
}
用法
Usage
现在,在映射函数中,我们可以展示元数据和类型是如何装饰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-interfaces 中该包含生成文件。
自定义链RPC调用
Custom chain rpc calls
为了支持自定义链 RPC 调用,我们必须手动为 typesBundle 注入 RPC 定义,允许按规格进行配置。你可以在 project.yml 中定义 typesBundle。请记住,只支持历史类型的调用。
...
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",
}
}
}
}
}
}
Website:
https://www.subquery.network/
Discord中文群:
https://discord.gg/SZsgE3M7
Telegram中文群:
t.me/subquerychina
Mixin中文群:
https://subquery.mixinbots.com/join
往期精彩
《SubQuery开发者指南丨GraphQL 架构(GraphQL Schema)》
《SubQuery开发者指南丨Hello World Explained》
《SubQuery开发者指南丨Hello World (由SubQuery 托管)》
《SubQuery开发者指南丨创建一个SubQuery项目》
《SubQuery开发者指南丨清单文件(Manifest File)》