Skip to main content

Мапинг

SubQuery TeamAbout 6 min

Мапинг

Мапинг функциите дефинират как данните от веригата се трансформират в оптимизираните GraphQL обекти, които по-рано сме дефинирали във файла schema.graphql.

  • Мапингите се дефинират в директорията src/mappings и се експортират като функция
  • Мапингите също се експортират src/index.ts
  • Мапинг файловете са препратки в project.yaml под манипулаторите за мапинг.

Има три класа функции за мапинг; Манипулатори на блокове, Манипулатори на събития и Манипулатори на изпълнение.

Манипулатор на блокове

Можете да използвате манипулатори на блокове, за да улавяте информация всеки път, когато нов блок е прикачен към веригата на 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();
}

SubstrateBlockopen in new window е разширен тип интерфейс на signedBlockopen in new window, но също така включва 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();

SubstrateEventopen in new window е разширен тип интерфейс на EventRecordopen in new window. Освен данните за събитието, той включва и 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();
}

SubstrateExtrinsicopen in new window разширява GenericExtrinsicopen in new window. Прилага му се id (блокът, към който принадлежи този външен елемент) и предоставя външен елемент, който разширява събитията между този блок. Освен това, той записва статуса на успех на този външен елемент.

Състояния на заявка

Нашата цел е да покрием всички източници на данни за потребители за манипулатори на мапинг (повече от трите типа събития на интерфейса по-горе). Ето защо ние разкрихме някои от @polkadot/api интерфейсите, за да увеличим възможностите.

Това са интерфейсите, които в момента поддържаме:

Това са интерфейсите, които **НЕ ** поддържаме в момента:

  • 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 в нашия пример за използване на validator-thresholdopen in new window.

RPC повиквания

Ние поддържаме някои API RPC методи, които са отдалечени повиквания, които позволяват на функцията за мапинг да взаимодейства с действителния нод, заявка и подаване. Основната предпоставка на SubQuery е, че е детерминистична и следователно, за да поддържаме резултатите последователни, позволяваме само исторически RPC повиквания.

Документите в JSON-RPCopen in new window предоставят някои методи, които приема BlockHash като входен параметър (например at?: BlockHash), който вече е разрешен. Ние също така променихме тези методи, за да приемем хеш на текущия индексиращ блок по подразбиране.

// Да приемем, че в момента индексираме блок с този хеш номер
const blockhash = `0x844047c4cf1719ba6d54891e92c071a41e3dfe789d064871148e9d41ef086f6a`;

// Оригиналният метод има избираем вход в блок хеш
const b1 = await api.rpc.chain.getBlock(blockhash);

// Ще използва текущия блок по подразбиране, по този начин:
const b2 = await api.rpc.chain.getBlock();

Модули и библиотеки

За да подобрим възможностите на SubQuery за обработка на данни, ние разрешихме някои от вградените модули на NodeJS за изпълнение на функции за мапинг в sandbox, и позволихме на потребителите да изпълняват библиотеки на трети страни.

Моля, имайте предвид, че това е експериментална функция и може да срещнете грешки или проблеми, които могат да повлияят негативно на функциите ви за мапинг. Моля, докладвайте всички грешки, които откриете, като създадете казус в GitHubopen in new window.

Вградени модули

Понастоящем позволяваме следните модули на NodeJS: assert, buffer, crypto, util и path.

Вместо да импортирате целия модул, препоръчваме да импортирате само необходимия(те) метод(и), от който се нуждаете. Някои методи в тези модули може да имат зависимости, които не се поддържат и няма да се импортират.

импортирайте  {hashMessage} от "ethers/lib/utils"; //Добър начин
импортирайте  {utils} от "ethers" //Лош начин

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

Библиотеки на трети страни

Поради ограниченията на виртуалната машина в нашия sandbox, в момента поддържаме само библиотеки на трети страни, написани на CommonJS.

Ние поддържаме хибридна библиотека като @polkadot/* която използва ESM по подразбиране. Въпреки това, ако други библиотеки зависят от модули във формат ESM виртуалната машина НЯМА да компилира и да върне грешка.

Персонализирани вериги за Substrate

SubQuery може да се използва във всяка верига, базирана на Substrate, не само Polkadot или Kusama.

Можете да използвате персонализирана верига, базирана на Substrate, и ние предоставяме инструменти за автоматично импортиране на типове, интерфейси и допълнителни методи, използвайки @polkadot/typegenopen in new window.

В следващите раздели използваме нашия пример kittyopen in new window, за да обясним процеса на интеграция.

Подготовка

Създайте нова директория api-interfaces под папката src на проекта, за да съхранявате всички необходими и генерирани файлове. Също създаваме директория api-interfaces/kitties тъй като искаме да добавим декорация в API от модула kitties.

Метаданни

Нуждаем се от метаданни, за да генерираме действителните крайни точки на API. В примера с котките, ние използваме крайна точка от локална тестова мрежа и тя предоставя допълнителни типове. Следвайте стъпките в настройката на метаданните на PolkadotJS metadata setupopen in new window за да извлечете метаданните на нода от неговата HTTP крайна точка.

curl -H "Content-Type: application/json" -d '{"id":"1", "jsonrpc":"2.0", "method": "state_getMetadata", "params":[]}' http://localhost:9933

или от неговата websocket крайна точка с помощта на websocatopen in new window:

//Install the websocat
brew install websocat

//Get metadata
echo state_getMetadata | websocat 'ws://127.0.0.1:9944' --jsonrpc

След това копирайте и поставете резултата в JSON файл. В нашия пример kittyopen in new window, създадохме api-interface/kitty.json.

Дефиниции на типове

Предполагаме, че потребителят познава специфичните типове и RPC поддръжка от веригата, и това е дефинирано в Манифеста.

Следвайки видовете сетъпиopen in new window, създаваме :

  • src/api-interfaces/definitions.ts - това експортира всички дефиниции на подпапки
export { default as kitties } from "./kitties/definitions";
  • src/api-interfaces/kitties/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:meta генератори (в този ред, така че метаданните могат да използват типовете).

Ето опростена версия на package.json. Уверете се, че в секцията scripts името на пакета е правилно и директориите са валидни.

{
  "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-augment за 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-interfaces.

Rpc повиквания в персонализирана верига

За да поддържаме персонализирани верижни RPC преобразувания, трябва ръчно да вкараме RPC дефиниции за typesBundle, позволявайки конфигурация по спецификация. Можете да дефинирате typesBundle в project.yml. И моля, не забравяйте, че се поддържат само повиквания тип 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",
            }
          }
        }
      }
    }
  }