查看原文
其他

SubQuery开发者指南丨映射(Mapping)

区块链数据提供者 SubQuery中文站 2022-03-30


映射函数定义了如何将链数据转换为我们之前在 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();}

 

SubstrateExtrinsic(substrate 外来上链信息), 扩展 GenericExtrinsic(泛型外来上链信息) 它被分配一个 id(该外来上链信息所属的块),提供外部属性扩展这个块之间的事件。 此外,它还记录了外来上链信息的成功状态。
 

查询状态

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>.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

在我们的验证器阈值示例中查看使用此 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



为了提高 SubQuery 的数据处理能力,我们允许 NodeJS 的一些内置模块在沙箱中运行映射函数,并允许用户调用第三方库。
 
请注意,这是一项实验性功能,你可能会遇到可能对您的映射功能产生负面影响的错误或问题。 

请通过在 GitHub 中创建问题来报告发现的任何错误。https://github.com/subquery/subql 

 


内置模块

Built-in modules



目前,我们允许使用以下 NodeJS 模块:assert、buffer、crypto、util 和 path。

 

我们建议只导入您需要的必需方法,而不是导入整个模块。 这些模块中的某些方法可能具有不受支持的依赖项,并且会在导入时失败。

 

import {hashMessage} from "ethers/lib/utils"; //Good wayimport {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 websocatbrew install websocat

//Get metadataecho 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 dependenciesyarn

# Generate typesyarn generate:defs

每个模块文件夹(eg /kitties),应有已生成的types.ts 可以定义按照该模块定义的所有接口,且文件 index.ts可以全部将他们导出。

 

# Generate metadatayarn 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丨开发者快速入门指南》


《SubQuery丨Hello world 入门简介》


《SubQuery开发者指南丨Hello World Explained》


《SubQuery开发者指南丨Hello World (由SubQuery 托管)》


《SubQuery 开发者指南丨安装 SubQuery》


《SubQuery开发者指南丨创建一个SubQuery项目》

《SubQuery开发者指南丨清单文件(Manifest File)》




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存