UNPKG

@substrate/api-sidecar

Version:

REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.

263 lines 13.9 kB
"use strict"; // Copyright 2017-2025 Parity Technologies (UK) Ltd. // This file is part of Substrate API Sidecar. // // Substrate API Sidecar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const util_1 = require("@polkadot/util"); const http_errors_1 = require("http-errors"); const apiRegistry_1 = require("../../../apiRegistry"); const validate_1 = require("../../../middleware/validate"); const services_1 = require("../../../services"); const PromiseQueue_1 = require("../../../util/PromiseQueue"); const AbstractController_1 = __importDefault(require("../../AbstractController")); class RcBlocksController extends AbstractController_1.default { constructor(_api, options) { var _a; const rcApiSpecName = (_a = apiRegistry_1.ApiPromiseRegistry.getSpecNameByType('relay')) === null || _a === void 0 ? void 0 : _a.values(); const rcSpecName = rcApiSpecName ? Array.from(rcApiSpecName)[0] : undefined; if (!rcSpecName) { throw new Error('Relay chain API spec name is not defined.'); } super(rcSpecName, '/rc/blocks', new services_1.BlocksService(rcSpecName, options.minCalcFeeRuntime, options.hasQueryFeeApi)); this.options = options; this.emitExtrinsicMetrics = (totExtrinsics, totBlocks, method, path, res) => { if (res.locals.metrics) { const seconds = res.locals.metrics.timer(); const extrinsics_in_request = res.locals.metrics.registry['sas_extrinsics_in_request']; extrinsics_in_request.labels({ method: method, route: path, status_code: res.statusCode }).observe(totExtrinsics); const extrinsics_per_second = res.locals.metrics.registry['sas_extrinsics_per_second']; extrinsics_per_second .labels({ method: method, route: path, status_code: res.statusCode }) .observe(totExtrinsics / seconds); const extrinsicsPerBlockMetrics = res.locals.metrics.registry['sas_extrinsics_per_block']; extrinsicsPerBlockMetrics .labels({ method: 'GET', route: path, status_code: res.statusCode }) .observe(totExtrinsics / totBlocks); const seconds_per_block = res.locals.metrics.registry['sas_seconds_per_block']; seconds_per_block .labels({ method: method, route: path, status_code: res.statusCode }) .observe(seconds / totBlocks); if (totBlocks > 1) { const seconds_per_block = res.locals.metrics.registry['sas_seconds_per_block']; seconds_per_block .labels({ method: method, route: path, status_code: res.statusCode }) .observe(seconds / totBlocks); } } }; this.getLatestBlock = async ({ query: { eventDocs, extrinsicDocs, finalized, noFees, decodedXcmMsgs, paraId }, method, route }, res) => { var _a; const rcApi = (_a = apiRegistry_1.ApiPromiseRegistry.getApiByType('relay')[0]) === null || _a === void 0 ? void 0 : _a.api; if (!rcApi) { throw new Error('Relay chain API not found, please use SAS_SUBSTRATE_MULTI_CHAIN_URL env variable'); } const eventDocsArg = eventDocs === 'true'; const extrinsicDocsArg = extrinsicDocs === 'true'; let hash, queryFinalizedHead, omitFinalizedTag; if (!this.options.finalizes) { omitFinalizedTag = true; queryFinalizedHead = false; hash = (await rcApi.rpc.chain.getHeader()).hash; } else if (finalized === 'false') { omitFinalizedTag = false; queryFinalizedHead = true; hash = (await rcApi.rpc.chain.getHeader()).hash; } else { omitFinalizedTag = false; queryFinalizedHead = false; hash = await rcApi.rpc.chain.getFinalizedHead(); } const noFeesArg = noFees === 'true'; const decodedXcmMsgsArg = decodedXcmMsgs === 'true'; const paraIdArg = paraId ? this.parseNumberOrThrow(paraId, 'paraId must be an integer') : undefined; const options = { eventDocs: eventDocsArg, extrinsicDocs: extrinsicDocsArg, checkFinalized: false, queryFinalizedHead, omitFinalizedTag, noFees: noFeesArg, checkDecodedXcm: decodedXcmMsgsArg, paraId: paraIdArg, }; const cacheKey = hash.toString() + Number(options.eventDocs) + Number(options.extrinsicDocs) + Number(options.checkFinalized) + Number(options.noFees) + Number(options.checkDecodedXcm) + Number(options.paraId); const isBlockCached = this.blockStore.get(cacheKey); if (isBlockCached) { RcBlocksController.sanitizedSend(res, isBlockCached); return; } const historicApi = await rcApi.at(hash); const block = await this.service.fetchBlock(hash, historicApi, options); this.blockStore.set(cacheKey, block); RcBlocksController.sanitizedSend(res, block); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const path = route.path; if (res.locals.metrics) { this.emitExtrinsicMetrics(block.extrinsics.length, 1, method, path, res); } }; this.getBlockById = async ({ params: { number }, query: { eventDocs, extrinsicDocs, noFees, finalizedKey, decodedXcmMsgs, paraId }, method, route, }, res) => { var _a; const rcApi = (_a = apiRegistry_1.ApiPromiseRegistry.getApiByType('relay')[0]) === null || _a === void 0 ? void 0 : _a.api; if (!rcApi) { throw new Error('Relay chain API not found, please use SAS_SUBSTRATE_MULTI_CHAIN_URL env variable'); } const checkFinalized = (0, util_1.isHex)(number); const hash = await this.getHashFromAt(number, { api: rcApi }); const eventDocsArg = eventDocs === 'true'; const extrinsicDocsArg = extrinsicDocs === 'true'; const finalizeOverride = finalizedKey === 'false'; const queryFinalizedHead = !this.options.finalizes ? false : true; const noFeesArg = noFees === 'true'; let omitFinalizedTag = !this.options.finalizes ? true : false; if (finalizeOverride) { omitFinalizedTag = true; } const decodedXcmMsgsArg = decodedXcmMsgs === 'true'; const paraIdArg = paraId ? this.parseNumberOrThrow(paraId, 'paraId must be an integer') : undefined; const options = { eventDocs: eventDocsArg, extrinsicDocs: extrinsicDocsArg, checkFinalized, queryFinalizedHead, omitFinalizedTag, noFees: noFeesArg, checkDecodedXcm: decodedXcmMsgsArg, paraId: paraIdArg, }; const cacheKey = hash.toString() + Number(options.eventDocs) + Number(options.extrinsicDocs) + Number(options.checkFinalized) + Number(options.noFees) + Number(options.checkDecodedXcm) + Number(options.paraId); const isBlockCached = this.blockStore.get(cacheKey); if (isBlockCached) { RcBlocksController.sanitizedSend(res, isBlockCached); return; } const historicApi = await rcApi.at(hash); const block = await this.service.fetchBlock(hash, historicApi, options); this.blockStore.set(cacheKey, block); RcBlocksController.sanitizedSend(res, block); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const path = route.path; if (res.locals.metrics) { this.emitExtrinsicMetrics(block.extrinsics.length, 1, method, path, res); } }; this.getBlockHeaderById = async ({ params: { number } }, res) => { var _a; const rcApi = (_a = apiRegistry_1.ApiPromiseRegistry.getApiByType('relay')[0]) === null || _a === void 0 ? void 0 : _a.api; if (!rcApi) { throw new Error('Relay chain API not found, please use SAS_SUBSTRATE_MULTI_CHAIN_URL env variable'); } const hash = await this.getHashFromAt(number, { api: rcApi }); const headerResult = await this.service.fetchBlockHeader(hash); RcBlocksController.sanitizedSend(res, headerResult); }; this.getLatestBlockHeader = async ({ query: { finalized } }, res) => { var _a; const rcApi = (_a = apiRegistry_1.ApiPromiseRegistry.getApiByType('relay')[0]) === null || _a === void 0 ? void 0 : _a.api; if (!rcApi) { throw new Error('Relay chain API not found, please use SAS_SUBSTRATE_MULTI_CHAIN_URL env variable'); } const paramFinalized = finalized !== 'false'; const hash = paramFinalized ? await rcApi.rpc.chain.getFinalizedHead() : undefined; const headerResult = await this.service.fetchBlockHeader(hash); RcBlocksController.sanitizedSend(res, headerResult); }; this.getBlocks = async ({ query: { range, eventDocs, extrinsicDocs, noFees }, method, route }, res) => { var _a; if (!range) throw new http_errors_1.BadRequest('range query parameter must be inputted.'); const rcApi = (_a = apiRegistry_1.ApiPromiseRegistry.getApiByType('relay')[0]) === null || _a === void 0 ? void 0 : _a.api; if (!rcApi) { throw new Error('Relay chain API not found, please use SAS_SUBSTRATE_MULTI_CHAIN_URL env variable'); } const rangeOfNums = this.parseRangeOfNumbersOrThrow(range, 500); const eventDocsArg = eventDocs === 'true'; const extrinsicDocsArg = extrinsicDocs === 'true'; const queryFinalizedHead = !this.options.finalizes ? false : true; const omitFinalizedTag = !this.options.finalizes ? true : false; const noFeesArg = noFees === 'true'; const options = { eventDocs: eventDocsArg, extrinsicDocs: extrinsicDocsArg, checkFinalized: false, queryFinalizedHead, omitFinalizedTag, noFees: noFeesArg, checkDecodedXcm: false, paraId: undefined, }; const pQueue = new PromiseQueue_1.PromiseQueue(4); const blocksPromise = []; for (let i = 0; i < rangeOfNums.length; i++) { const result = pQueue.run(async () => { const hash = await this.getHashFromAt(rangeOfNums[i].toString(), { api: rcApi }); const historicApi = await rcApi.at(hash); return await this.service.fetchBlock(hash, historicApi, options); }); blocksPromise.push(result); } const allBlocks = await Promise.all(blocksPromise); blocksPromise.length = 0; const blocks = allBlocks; blocks.sort((a, b) => a.number.toNumber() - b.number.toNumber()); RcBlocksController.sanitizedSend(res, blocks); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const path = route.path; if (res.locals.metrics) { const totExtrinsics = blocks.reduce((current, block) => { const extrinsics = block.extrinsics; if (Array.isArray(extrinsics)) { return current + extrinsics.length; } return current; }, 0); this.emitExtrinsicMetrics(totExtrinsics, blocks.length, method, path, res); } }; this.initRoutes(); this.blockStore = options.blockStore; } initRoutes() { this.router.use(this.path, (0, validate_1.validateBoolean)(['eventDocs', 'extrinsicDocs', 'finalized'])); this.safeMountAsyncGetHandlers([ ['/', this.getBlocks], ['/head', this.getLatestBlock], ['/:number', this.getBlockById], ['/head/header', this.getLatestBlockHeader], ['/:number/header', this.getBlockHeaderById], ]); } } RcBlocksController.controllerName = 'RcBlocks'; RcBlocksController.requiredPallets = [['System', 'Session']]; exports.default = RcBlocksController; //# sourceMappingURL=RcBlocksController.js.map