UNPKG

@substrate/api-sidecar

Version:

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

458 lines 21.4 kB
"use strict"; /* eslint-disable @typescript-eslint/no-unsafe-assignment */ /* eslint-disable @typescript-eslint/no-unsafe-call */ /* eslint-disable @typescript-eslint/no-unsafe-return */ // 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/>. Object.defineProperty(exports, "__esModule", { value: true }); exports.CoretimeService = void 0; const util_1 = require("@polkadot/util"); const AbstractService_1 = require("../AbstractService"); const util_2 = require("./util"); var ChainType; (function (ChainType) { ChainType["Relay"] = "Relay"; ChainType["Parachain"] = "Parachain"; })(ChainType || (ChainType = {})); const SCALE = new util_1.BN(10000); class CoretimeService extends AbstractService_1.AbstractService { constructor() { super(...arguments); this.getAndDecodeRegions = async (api) => { const regions = await api.query.broker.regions.entries(); const regionsInfo = regions.map((region) => { return (0, util_2.extractRegionInfo)([region[0], region[1]]); }); return regionsInfo; }; this.getAndDecodeLeases = async (api) => { const leases = await api.query.broker.leases(); return leases.map((lease) => (0, util_2.extractLeaseInfo)(lease)); }; this.getAndDecodeWorkload = async (api) => { const workloads = await api.query.broker.workload.entries(); return (0, util_2.sortByCore)(workloads.map((workload) => { return (0, util_2.extractWorkloadInfo)(workload[1], workload[0].args[0].toNumber()); })); }; this.getAndDecodeWorkplan = async (api) => { const workplans = await api.query.broker.workplan.entries(); const wplsInfo = (0, util_2.sortByCore)(workplans.map(([key, val]) => { const [timeslice, core] = key.args[0].map((a) => a.toNumber()); return (0, util_2.extractWorkplanInfo)(val, core, timeslice); })); return wplsInfo; }; this.getAndDecodeSaleInfo = async (api) => { const saleInfo = await api.query.broker.saleInfo(); return saleInfo.isSome ? (0, util_2.extractSaleInfo)(saleInfo.unwrap()) : null; }; this.getAndDecodeStatus = async (api) => { const status = await api.query.broker.status(); return (0, util_2.extractStatusInfo)(status); }; this.getAndDecodeConfiguration = async (api) => { const configuration = await api.query.broker.configuration(); return (0, util_2.extractConfigInfo)(configuration); }; this.getAndDecodePotentialRenewals = async (api) => { const potentialRenewals = await api.query.broker.potentialRenewals.entries(); const potentialRenewalsInfo = (0, util_2.sortByCore)(potentialRenewals.map((renewal) => (0, util_2.extractPotentialRenewalInfo)(renewal[1], renewal[0]))); return potentialRenewalsInfo; }; this.getAndDecodeReservations = async (api) => { const reservations = await api.query.broker.reservations(); return reservations.map((res) => (0, util_2.extractReservationInfo)(res)); }; this.getAndDecodeCoreSchedules = async (api) => { const coreSchedules = await api.query.coretimeAssignmentProvider.coreSchedules.entries(); return coreSchedules; }; this.getAndDecodeCoreDescriptors = async (api) => { const coreDescriptors = await api.query.coretimeAssignmentProvider.coreDescriptors.entries(); const descriptors = coreDescriptors; return descriptors.map((descriptor) => (0, util_2.extractCoreDescriptorInfo)(descriptor[0], descriptor[1])); }; this.getAndDecodeParachainsLifecycle = async (api) => { const parachains = await api.query.paras.paraLifecycles.entries(); return parachains.map((para) => (0, util_2.extractParachainLifecycleInfo)(para[0], para[1])); }; this.leadinFactorAt = (scaledWhen) => { const scaledHalf = SCALE.div(new util_1.BN(2)); // 0.5 scaled to 10000 if (scaledWhen.lte(scaledHalf)) { // First half of the graph, steeper slope return SCALE.mul(new util_1.BN(100)).sub(scaledWhen.mul(new util_1.BN(180))); } else { // Second half of the graph, flatter slope return SCALE.mul(new util_1.BN(19)).sub(scaledWhen.mul(new util_1.BN(18))); } }; this.getCorePriceAt = (blockNumber, saleInfo) => { const { endPrice, leadinLength, saleStart } = saleInfo; // Explicit conversion to BN const blockNowBn = new util_1.BN(blockNumber); const saleStartBn = new util_1.BN(saleStart); const leadinLengthBn = new util_1.BN(leadinLength); // Elapsed time since the start of the sale, constrained to not exceed the total lead-in period const elapsedTimeSinceSaleStart = blockNowBn.sub(saleStartBn); const cappedElapsedTime = elapsedTimeSinceSaleStart.lt(leadinLengthBn) ? elapsedTimeSinceSaleStart : leadinLengthBn; const scaledProgress = cappedElapsedTime.mul(new util_1.BN(10000)).div(leadinLengthBn); /** * Progress is a normalized value between 0 and 1, where: * * 0 means the sale just started. * 1 means the sale is at the end of the lead-in period. * * We are scaling it to avoid floating point precision issues. */ const leadinFactor = this.leadinFactorAt(scaledProgress); const scaledPrice = leadinFactor.mul(endPrice).div(SCALE); return scaledPrice; }; this.getPhaseConfiguration = (currentRegionStart, regionLength, interludeLengthTs, leadInLengthTs, lastCommittedTimeslice) => { const renewalsEndTs = currentRegionStart + interludeLengthTs; const priceDiscoveryEndTs = renewalsEndTs + leadInLengthTs; const fixedPriceLenght = regionLength - interludeLengthTs - leadInLengthTs; const fixedPriceEndTs = priceDiscoveryEndTs + fixedPriceLenght; const progress = lastCommittedTimeslice - currentRegionStart; let phaseName = 'fixedPrice'; if (progress < interludeLengthTs) { phaseName = 'renewals'; } if (progress < interludeLengthTs + leadInLengthTs) { phaseName = 'priceDiscovery'; } return { config: [ { phaseName: 'renewals', lastTimeslice: renewalsEndTs, }, { phaseName: 'priceDiscovery', lastTimeslice: priceDiscoveryEndTs, }, { phaseName: 'fixedPrice', lastTimeslice: fixedPriceEndTs, }, ], currentPhaseName: phaseName, }; }; this.getCurrentRegionStartEndTs = (saleInfo, regionLength) => { return { currentRegionEnd: saleInfo.regionBegin, currentRegionStart: saleInfo.regionBegin - regionLength, }; }; this.assertCoretimeModule = (api, chainType) => { if (chainType === ChainType.Relay) { this.assertQueryModule(api.query.onDemandAssignmentProvider, 'onDemandAssignmentProvider'); this.assertQueryModule(api.query.coretimeAssignmentProvider, 'coretimeAssignmentProvider'); return; } else if (chainType === ChainType.Parachain) { this.assertQueryModule(api.query.broker, 'broker'); return; } throw new Error('Unsupported network type.'); }; } async getCoretimeInfo(hash) { const { api } = this; const [{ specName }, { number }, historicApi] = await Promise.all([ api.rpc.state.getRuntimeVersion(hash), api.rpc.chain.getHeader(hash), api.at(hash), ]); const blockNumber = number.unwrap(); if (this.getChainType(specName.toString()) === ChainType.Relay) { this.assertCoretimeModule(historicApi, ChainType.Relay); const [brokerId, maxHistoricalRevenue, palletVersion] = await Promise.all([ historicApi.consts.coretime.brokerId, historicApi.consts.onDemandAssignmentProvider.maxHistoricalRevenue, historicApi.query.coretimeAssignmentProvider.palletVersion(), ]); return { at: { hash, height: blockNumber.toString(10), }, brokerId: brokerId, palletVersion: palletVersion, maxHistoricalRevenue: maxHistoricalRevenue, }; } else { this.assertCoretimeModule(historicApi, ChainType.Parachain); const [config, saleInfo, timeslicePeriod, status] = await Promise.all([ this.getAndDecodeConfiguration(historicApi), this.getAndDecodeSaleInfo(historicApi), historicApi.consts.broker.timeslicePeriod, this.getAndDecodeStatus(historicApi), ]); const blocksPerTimeslice = timeslicePeriod; const currentRegionStats = saleInfo && this.getCurrentRegionStartEndTs(saleInfo, config.regionLength); const phaseConfig = this.getPhaseConfiguration((currentRegionStats === null || currentRegionStats === void 0 ? void 0 : currentRegionStats.currentRegionStart) || 0, config.regionLength, config.interludeLength, (saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.leadinLength) || 0, status.lastCommittedTimeslice || 0); return { at: { hash, height: blockNumber.toString(10), }, configuration: { regionLength: config.regionLength, interludeLength: config.interludeLength, leadinLength: (saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.leadinLength) || 0, relayBlocksPerTimeslice: blocksPerTimeslice.toNumber(), }, currentRegion: { start: (currentRegionStats === null || currentRegionStats === void 0 ? void 0 : currentRegionStats.currentRegionStart) || null, end: (currentRegionStats === null || currentRegionStats === void 0 ? void 0 : currentRegionStats.currentRegionEnd) || null, }, cores: { available: Number(saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.coresOffered) - Number(saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.coresSold), sold: Number(saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.coresSold), total: Number(saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.coresOffered), currentCorePrice: this.getCorePriceAt(blockNumber.toNumber(), saleInfo), selloutPrice: saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.selloutPrice, firstCore: saleInfo === null || saleInfo === void 0 ? void 0 : saleInfo.firstCore, }, phase: { currentPhase: phaseConfig.currentPhaseName, config: phaseConfig.config.map((c) => ({ phaseName: c.phaseName, lastRelayBlock: c.lastTimeslice * blocksPerTimeslice.toNumber(), lastTimeslice: c.lastTimeslice, })), }, }; } } async getCoretimeLeases(hash) { const { api } = this; const [{ specName }, { number }, historicApi] = await Promise.all([ api.rpc.state.getRuntimeVersion(hash), api.rpc.chain.getHeader(hash), api.at(hash), ]); const blockNumber = number.unwrap(); if (this.getChainType(specName.toString()) === ChainType.Relay) { throw new Error('This endpoint is only available on coretime chains.'); } else { this.assertCoretimeModule(historicApi, ChainType.Parachain); const [leases, workload] = await Promise.all([ this.getAndDecodeLeases(historicApi), this.getAndDecodeWorkload(historicApi), ]); const leasesWithCore = leases.reduce((acc, lease) => { var _a; const core = (_a = workload.find((wl) => wl.info.task.includes(lease.task))) === null || _a === void 0 ? void 0 : _a.core; return [ ...acc, { ...lease, core: core, }, ]; }, []); return { at: { hash, height: blockNumber.toString(10), }, leases: (0, util_2.sortByCore)(leasesWithCore), }; } } async getCoretimeRegions(hash) { const { api } = this; const [{ specName }, { number }, historicApi] = await Promise.all([ api.rpc.state.getRuntimeVersion(hash), api.rpc.chain.getHeader(hash), api.at(hash), ]); const blockNumber = number.unwrap(); if (this.getChainType(specName.toString()) === ChainType.Relay) { throw new Error('This endpoint is only available on coretime chains.'); } else { this.assertCoretimeModule(historicApi, ChainType.Parachain); const regions = await this.getAndDecodeRegions(historicApi); return { at: { hash, height: blockNumber.toString(10), }, regions: (0, util_2.sortByCore)(regions), }; } } async getCoretimeReservations(hash) { const { api } = this; const [{ specName }, { number }, historicApi] = await Promise.all([ api.rpc.state.getRuntimeVersion(hash), api.rpc.chain.getHeader(hash), api.at(hash), ]); const blockNumber = number.unwrap(); if (this.getChainType(specName.toString()) === ChainType.Relay) { throw new Error('This endpoint is only available on coretime chains.'); } else { // coretime chain or parachain this.assertCoretimeModule(historicApi, ChainType.Parachain); const reservations = await this.getAndDecodeReservations(historicApi); return { at: { hash, height: blockNumber.toString(10), }, reservations, }; } } async getCoretimeRenewals(hash) { const { api } = this; const [{ specName }, { number }, historicApi] = await Promise.all([ api.rpc.state.getRuntimeVersion(hash), api.rpc.chain.getHeader(hash), api.at(hash), ]); const blockNumber = number.unwrap(); if (this.getChainType(specName.toString()) === ChainType.Relay) { throw new Error('This endpoint is only available on coretime chains.'); } else { this.assertCoretimeModule(historicApi, ChainType.Parachain); const renewals = await this.getAndDecodePotentialRenewals(historicApi); return { at: { hash, height: blockNumber.toString(10), }, renewals: (0, util_2.sortByCore)(renewals), }; } } async getCoretimeCores(hash) { const { api } = this; const [{ specName }, { number }, historicApi] = await Promise.all([ api.rpc.state.getRuntimeVersion(hash), api.rpc.chain.getHeader(hash), api.at(hash), ]); const blockNumber = number.unwrap(); if (this.getChainType(specName.toString()) === ChainType.Relay) { this.assertCoretimeModule(historicApi, ChainType.Relay); const [parachains, schedules, descriptors] = await Promise.all([ this.getAndDecodeParachainsLifecycle(historicApi), this.getAndDecodeCoreSchedules(historicApi), this.getAndDecodeCoreDescriptors(historicApi), ]); const descriptorsWithParas = parachains.reduce((acc, para) => { const core = descriptors.find((f) => { const assignments = f.info.currentWork.assignments.find((assgn) => assgn.task === para.paraId.toString()); return !!assignments; }); if (core) { acc.push({ ...para, ...core, }); } return acc; }, []); return { at: { hash, height: blockNumber.toString(10), }, cores: descriptorsWithParas, coreSchedules: schedules, }; } else { this.assertCoretimeModule(historicApi, ChainType.Parachain); const [workload, workplan, leases, reservations, regions] = await Promise.all([ this.getAndDecodeWorkload(historicApi), this.getAndDecodeWorkplan(historicApi), this.getAndDecodeLeases(historicApi), this.getAndDecodeReservations(historicApi), this.getAndDecodeRegions(historicApi), ]); const systemParas = reservations.map((el) => el.task); const cores = workload.map((wl) => { var _a, _b; const coreType = systemParas.includes(wl.info.task) ? wl.info.task === 'Pool' ? 'ondemand' : 'reservation' : leases.map((f) => f.task).includes(wl.info.task) ? 'lease' : 'bulk'; let details = undefined; if (coreType === 'reservation') { details = { mask: (_a = reservations.find((f) => f.task === wl.info.task)) === null || _a === void 0 ? void 0 : _a.mask }; } else if (coreType === 'lease') { details = { until: (_b = leases.find((f) => f.task === wl.info.task)) === null || _b === void 0 ? void 0 : _b.until }; } const coreRegions = regions.filter((region) => region.core === wl.core); return { coreId: wl.core, paraId: wl.info.task, workload: wl.info, workplan: workplan.filter((f) => f.core === wl.core), type: { condition: coreType, details }, regions: coreRegions, }; }); return { at: { hash, height: blockNumber.toString(10), }, cores, }; } } /** * Coretime pallets and modules are not available on all runtimes. This * verifies that by checking if the module exists. If it doesnt it will throw an error * * @param queryFn The QueryModuleStorage key that we want to check exists * @param mod Module we are checking */ assertQueryModule(queryFn, mod) { if (!queryFn) { throw Error(`The runtime does not include the ${mod} module at this block`); } } getChainType(specName) { const relay = ['polkadot', 'kusama', 'westend', 'paseo']; if (relay.includes(specName.toLowerCase())) { return ChainType.Relay; } else { return ChainType.Parachain; } } } exports.CoretimeService = CoretimeService; //# sourceMappingURL=CoretimeService.js.map