Mapeo
Mapeo
Las funciones de mapeo definen cómo se transforman los datos de la cadena en las entidades optimizadas GraphQL que hemos definido previamente en el archivo schema.graphql
.
- Los mapeos se definen en el directorio
src/mappings
y se exportan como una función - Estos mapeos también son exportados en
src/index.ts
- Los archivos de mapeo son referencia en
project.yaml
bajo los manejadores de mapeo.
Hay tres clases de funciones de mapeo: Manejadores de bloques, Manejadores de eventosy Manejadores de llamadas.
Manejador de bloques
Puede utilizar manejadores de bloques para capturar información cada vez que un nuevo bloque está conectado a la cadena Substrate, por ejemplo, el número de bloque. Para lograrlo, un BlockHandler definido será llamado una vez por cada bloque.
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();
}
A SubstrateBlock is an extended interface type of signedBlock, but also includes the specVersion and timestamp.
Manejador del Evento
Puede utilizar manejadores de eventos para capturar información cuando ciertos eventos son incluidos en un nuevo bloque. Los eventos que son parte del tiempo de ejecución predeterminado de Substrate y un bloque pueden contener múltiples eventos.
Durante el procesamiento, el manejador de eventos recibirá un evento de substrate como argumento con las entradas y salidas del evento. Cualquier tipo de evento activará el mapeo, permitiendo la captura de la actividad con la fuente de datos. Debe utilizar Filtros de Mapeo en su manifiesto para filtrar eventos para reducir el tiempo que toma indexar los datos y mejorar el rendimiento de mapeo.
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();
Un SubstrateEvent es un tipo de interfaz extendida del EventRecord. Además de los datos del evento, también incluye una id
(el bloque al que pertenece este evento) y el extrínseco dentro de este bloque.
Manejador de llamada
Los manejadores de llamadas se utilizan cuando se desea capturar información sobre ciertos substrate extrínsecos.
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();
}
El SubstrateExtrinsic extiende GenericExtrinsic. Se le asigna un id
(el bloque al que pertenece este extrínseco) y proporciona una propiedad extrínseca que extiende los eventos entre este bloque. Además, registra el estado de éxito de este extrínseco.
Estados de Consulta
Nuestro objetivo es cubrir todas las fuentes de datos para los usuarios de los manejadores de mapeo (más de los tres tipos de eventos de la interfaz anterior). Por lo tanto, hemos expuesto algunas de las interfaces @polkadot/api para aumentar las capacidades.
Estas son las interfaces que actualmente soportamos:
- api.query.<module>.<method>() consultará el bloque actual.
- api.query.<module>.<method>.multi() hará múltiples consultas del mismo tipo en el bloque actual.
- api.queryMulti() hará múltiples consultas de diferentes tipos en el bloque actual.
Estas son las interfaces que actualmente no soportamos NOT:
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
Ver un ejemplo de uso de esta API en nuestro ejemplo de uso de ejemplo de validator-threshold.
Llamadas RPC
También soportamos algunos métodos RPC API que son llamadas remotas que permiten que la función de mapeo interactúe con el nodo real, la consulta y el envío. Un núcleo principal de SubQuery es que es determinista, y por lo tanto, para mantener los resultados consistentes sólo permitimos llamadas históricas RPC.
Documentos en JSON-RPC proporcionan algunos métodos que toman BlockHash
como parámetro de entrada (e.. en?: BlockHash
), que ahora están permitidos. También hemos modificado estos métodos para tomar el hash del bloque de indexación actual por defecto.
// Digamos que actualmente estamos indexando un bloque con este número hash
const blockhash = `0x844047c4cf1719ba6d54891e92c071a41e3dfe789d064871148e9d41ef086f6a`;
// El método original tiene una entrada opcional es el hash del bloque
const b1 = await api.pc.chain.getBlock(blockhash);
// Utilizará el bloque actual por defecto como
const b2 = await api.rpc.chain.getBlock();
- Para cadenas personalizadas de Substrate llamadas RPC, vea uso.
Módulos y librerías
Para mejorar las capacidades de procesamiento de datos de SubQuery, hemos permitido algunos de los módulos incorporados de NodeJS para ejecutar funciones de mapeo en el sandbox, y han permitido a los usuarios llamar a bibliotecas de terceros.
Tenga en cuenta que esta es una característica experimental y puede encontrar errores o problemas que pueden afectar negativamente a sus funciones de mapeo. Por favor, informe de cualquier error que encuentre creando un problema en GitHub.
Módulos incorporados
Actualmente, permitimos los siguientes módulos de NodeJS: assert
, buffer
, crypto
, util
, y path
.
En lugar de importar todo el módulo, recomendamos importar sólo los método(s) requeridos que usted necesita. Algunos métodos en estos módulos pueden tener dependencias que no están soportadas y fallarán al importar.
import { hashMessage } from "ethers/lib/utils"; // Good way
import { utils } from "ethers"; // Bad way
Librería de terceros
import { hashMessage } from "ethers/lib/utils"; // Good way
import { utils } from "ethers"; // Bad way
Cadenas de Substrate Personalizadas
SubQuery puede ser usado en cualquier cadena basada en Substrate, no sólo en Polkadot o Kusama.
Puede utilizar una cadena personalizada basada en Substrate y proporcionamos herramientas para importar tipos, interfaces y métodos adicionales automáticamente usando @polkadot/typegen.
En las siguientes secciones, utilizamos nuestro ejemplo de gatitos para explicar el proceso de integración.
Preparación
Crear un nuevo directorio api-interfaces
bajo la carpeta src
del proyecto para almacenar todos los archivos necesarios y generados. También creamos un directorio api-interfaces/kitties
ya que queremos añadir decoración en la API desde el módulo kitties
.
Metadatos
Necesitamos metadatos para generar los puntos finales actuales de la API. En el ejemplo del kitty utilizamos un punto final de una red de pruebas local, y proporciona tipos adicionales. Siga los pasos de configuración de metadatos PolkadotJS para recuperar los metadatos de un nodo de su punto final HTTP.
curl -H "Content-Type: application/json" -d '{"id":"1", "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' http://localhost:9933
o desde su punto final websocket con la ayuda de websocat
:
//Instalar el websocat
brew install websocat
//Obtener metadatos
echo state_getMetadata | websocat 'ws://127.0.0.1:9944' --jsonrpc
A continuación, copie y pegue la salida a un archivo JSON. In our kitty example, we have created api-interface/kitty.json
.
Tipos de definición
Asumimos que el usuario conoce los tipos específicos y el soporte RPC de la cadena, y está definido en el Manifiesto.
import { hashMessage } from "ethers/lib/utils"; // Good way
import { utils } from "ethers"; // Bad way
src/api-interfaces/definitions.ts
- esto exporta todas las definiciones de la sub-carpeta
exportar { default as kitties } desde './kitties/definitions';
src/api-interfaces/kitties/definitions.ts
- escriba definiciones para el módulo 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",
},
},
};
Paquetes
- En el paquete
package.json
, asegúrate de añadir@polkadot/typegen
como dependencia de desarrollo y@polkadot/api
como dependencia regular (idealmente la misma versión). También necesitamosts-node
como una dependencia de desarrollo para ayudarnos a ejecutar los scripts. - Añadimos scripts para ejecutar ambos tipos;
generate:defs
y metadatosgenerar:meta
generadores (en ese orden, así los metadatos pueden usar los tipos).
Aquí hay una versión simplificada de package.json
. Asegúrate de que en la sección scripts el nombre del paquete es correcto y los directorios son válidos.
{
"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"
}
}
Generación de Código
Ahora que la preparación está completa, estamos preparados para generar tipos y metadatas. Ejecutar los siguientes comandos:
# Yarn para instalar nuevas dependencias
yarn
# Genera tipos
yarn generate:defs
En cada carpeta de módulos (por ejemplo, /kitties
), ahora debería haber un manejador types.ts
que define todas las interfaces de las definiciones de estos módulos, también un archivo index.ts
que las exporta todas.
# Genera metadatos
yarn generate:meta
Este comando generará los metadatos y un nuevo complemento para las APIs. Como no queremos usar la API incorporada, necesitaremos reemplazarlos agregando una anulación explícita en nuestro tsconfig.json
. Después de las actualizaciones, las rutas en la configuración se verán así (sin los comentarios):
{
"compilerOptions": {
// este es el nombre del paquete que usamos (en la interfaz de importaciones, --package para generadors) */
"kitty-birthinfo/*": ["src/*"],
// aquí reemplazamos la mejora @polkadot/api por la nuestra, generado desde la cadena
"@polkadot/api/augment": ["src/interfaces/augment-api. s"],
// reemplazar los tipos aumentados por los nuestros, as generated from definitions
"@polkadot/types/augment": ["src/interfaces/augment-types.
Uso
Ahora en la función de mapeo, podemos mostrar cómo los metadatos y los tipos realmente decoran la API. El endpoint RPC soportará los módulos y métodos que declaramos anteriormente. Y para usar una llamada rpc personalizada, por favor vea la sección Llamadas rpc de cadena personalizadas
export async function kittyApiHandler(): Promise<void> {
// devuelve el tipo de Kitty
const nextKittyId = await api.rpc.kitties.nextKittyId();
// devuelve el tipo Kitty, los tipos de parámetros de entrada son AccountId y KittyIndex
const allKitties = await api.query.kitties.kitties("xxxxxxxxxx", 123);
logger.info(`Next kitty id ${nextKittyId}`);
//Custom rpc, establecer indefinido a blockhash
const kittyPrice = await api.rpc.kitties.getKittyPrice(
undefined,
nextKittyId
);
}
Si desea publicar este proyecto en nuestro explorador, por favor incluya los archivos generados en src/api-interfaces
.
Llamadas rpc de cadena personalizadas
Para soportar llamadas RPC personalizadas, debemos inyectar manualmente definiciones RPC para typesBundle
, permitiendo la configuración por especificación. Puede definir el typesBundle
en el project.yml
. Y por favor recuerde que sólo se soportan los tipos de llamadas isHistórico
.
...
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",
}
}
}
}
}
}