@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
JavaScript
"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