@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
494 lines • 20.3 kB
JavaScript
;
// 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.ParasService = void 0;
const util_1 = require("@polkadot/util");
const http_errors_1 = require("http-errors");
const util_2 = require("../../types/util");
const AbstractService_1 = require("../AbstractService");
class ParasService extends AbstractService_1.AbstractService {
/**
* ***********************************************************
* DEPRECATION NOTE: PHASED OUT SERVICE IN FAVOR OF CORETIME
* ***********************************************************
*
* Get crowdloan information for a `paraId`.
*
* @param hash `BlockHash` to make call at
* @param paraId ID of para to get crowdloan info for
*/
async crowdloansInfo(hash, paraId) {
const { api } = this;
const historicApi = await api.at(hash);
this.assertQueryModule(historicApi.query.crowdloan, 'crowdloan');
const [fund, { number }] = await Promise.all([
historicApi.query.crowdloan.funds(paraId),
api.rpc.chain.getHeader(hash),
]);
if (!fund) {
throw new http_errors_1.InternalServerError(`Could not find funds info at para id: ${paraId}`);
}
let fundInfo, leasePeriods;
if (fund.isSome) {
fundInfo = fund.unwrap();
const firstSlot = fundInfo.firstPeriod.toNumber();
// number of lease periods this crowdloan covers
const leasePeriodCount = fundInfo.lastPeriod.toNumber() - firstSlot + 1;
leasePeriods = Array(leasePeriodCount)
.fill(0)
.map((_, i) => i + firstSlot);
}
else {
fundInfo = null;
}
const at = {
hash,
height: number.unwrap().toString(10),
};
return {
at,
fundInfo,
leasePeriods,
};
}
/**
* ***********************************************************
* DEPRECATION NOTE: PHASED OUT SERVICE IN FAVOR OF CORETIME
* ***********************************************************
*
* List all available crowdloans.
*
* @param hash `BlockHash` to make call at
* @param includeFundInfo wether or not to include `FundInfo` for every crowdloan
*/
async crowdloans(hash) {
const { api } = this;
const historicApi = await api.at(hash);
this.assertQueryModule(historicApi.query.crowdloan, 'crowdloan');
const [{ number }, funds] = await Promise.all([
api.rpc.chain.getHeader(hash),
historicApi.query.crowdloan.funds.entries(),
]);
const fundsByParaId = funds.map(([keys, fundInfo]) => {
return {
paraId: keys.args[0],
fundInfo,
};
});
return {
at: {
hash,
height: number.unwrap().toString(10),
},
funds: fundsByParaId,
};
}
/**
* ***********************************************************
* DEPRECATION NOTE: PHASED OUT SERVICE IN FAVOR OF CORETIME
* ***********************************************************
*
* Get current and future lease info + lifecycle stage for a given `paraId`.
*
* @param hash Get lease info at this `BlockHash`
* @param paraId ID of para to get lease info of
*/
async leaseInfo(hash, paraId) {
const { api } = this;
const historicApi = await api.at(hash);
this.assertQueryModule(historicApi.query.paras, 'paras');
const [leases, { number }, paraLifecycleOpt] = await Promise.all([
historicApi.query.slots.leases(paraId),
api.rpc.chain.getHeader(hash),
historicApi.query.paras.paraLifecycles(paraId),
]);
const blockNumber = number.unwrap();
const at = {
hash,
height: blockNumber.toString(10),
};
let leasesFormatted;
if (leases.length) {
const currentLeasePeriodIndex = this.leasePeriodIndexAt(historicApi, blockNumber);
leasesFormatted = leases.reduce((acc, curLeaseOpt, idx) => {
if (curLeaseOpt.isSome) {
const leasePeriodIndex = currentLeasePeriodIndex ? currentLeasePeriodIndex.toNumber() + idx : null;
const lease = curLeaseOpt.unwrap();
acc.push({
leasePeriodIndex,
account: lease[0],
deposit: lease[1],
});
}
return acc;
}, []);
}
else {
leasesFormatted = null;
}
let onboardingAs;
if (paraLifecycleOpt.isSome && paraLifecycleOpt.unwrap().isOnboarding) {
const paraGenesisArgs = await historicApi.query.paras.upcomingParasGenesis(paraId);
if (paraGenesisArgs.isSome) {
onboardingAs = paraGenesisArgs.unwrap().parachain.isTrue ? 'parachain' : 'parathread';
}
}
return {
at,
paraLifecycle: paraLifecycleOpt,
onboardingAs,
leases: leasesFormatted,
};
}
/**
* ***********************************************************
* DEPRECATION NOTE: PHASED OUT SERVICE IN FAVOR OF CORETIME
* ***********************************************************
*
* Get the status of the current auction.
*
* Note: most fields will be null if there is no ongoing auction.
*
* @param hash `BlockHash` to make call at
*/
async auctionsCurrent(hash) {
var _a;
const { api } = this;
const historicApi = await api.at(hash);
this.assertQueryModule(historicApi.query.auctions, 'auctions');
const [auctionInfoOpt, { number }, auctionCounter] = await Promise.all([
historicApi.query.auctions.auctionInfo(),
api.rpc.chain.getHeader(hash),
historicApi.query.auctions.auctionCounter(),
]);
const blockNumber = number.unwrap();
const endingPeriod = historicApi.consts.auctions.endingPeriod;
let leasePeriodIndex, beginEnd, finishEnd, phase, winning;
if (auctionInfoOpt.isSome) {
/**
* If `AuctionInfo:::<T>:get()` is `Some`, it returns a tuple where the first item is the
* lease period index that the first of the four contiguous lease periods
* of an auction is for. The second is the block number when the auction will
* 'being to end', i.e. the first block of the Ending Period of the auction
*/
[leasePeriodIndex, beginEnd] = auctionInfoOpt.unwrap();
/**
* End of current auctions endPeriod.
*/
finishEnd = beginEnd.add(endingPeriod);
/**
* We determine what our phase is so we can decide how to calculate our
* ending offset.
*/
if (finishEnd.lte(blockNumber)) {
phase = 'vrfDelay';
}
else {
phase = beginEnd.gt(blockNumber) ? 'startPeriod' : 'endPeriod';
}
/**
* The endingOffset according to polkadot has two potential phases
* where this will be a valid block number. Both `startPeriod`, and `endPeriod`
* have valid offsets.
*/
const endingOffset = this.endingOffset(blockNumber, beginEnd, phase, historicApi);
if (endingOffset) {
const ranges = this.enumerateLeaseSets(historicApi, leasePeriodIndex);
const winningOpt = await historicApi.query.auctions.winning(endingOffset);
// zip the winning bids together with their enumerated `SlotRange` (aka `leaseSet`)
winning = winningOpt.unwrap().map((bid, idx) => {
const leaseSet = ranges[idx];
let result;
if (bid.isSome) {
const [accountId, paraId, amount] = bid.unwrap();
result = { bid: { accountId, paraId, amount }, leaseSet };
}
else {
result = { bid: null, leaseSet };
}
return result;
});
}
else {
winning = null;
}
}
else {
leasePeriodIndex = null;
beginEnd = null;
finishEnd = null;
phase = null;
winning = null;
}
const leasePeriodsPerSlot = (_a = historicApi.consts.auctions.leasePeriodsPerSlot) === null || _a === void 0 ? void 0 : _a.toNumber();
const leasePeriods = (0, util_2.isSome)(leasePeriodIndex)
? Array(leasePeriodsPerSlot)
.fill(0)
.map((_, i) => i + leasePeriodIndex.toNumber())
: null;
return {
at: {
hash,
height: blockNumber.toString(10),
},
beginEnd,
finishEnd,
phase,
// If there is no current auction, this will be the index of the previous auction
auctionIndex: auctionCounter,
leasePeriods,
winning,
};
}
/**
* ***********************************************************
* DEPRECATION NOTE: PHASED OUT SERVICE IN FAVOR OF CORETIME
* ***********************************************************
*
* Get general information about the current lease period.
*
* @param hash `BlockHash` to make call at
* @param includeCurrentLeaseHolders wether or not to include the paraIds of
* all the curent lease holders. Not including is likely faster and reduces
* response size.
*/
async leasesCurrent(hash, includeCurrentLeaseHolders) {
const { api } = this;
const historicApi = await api.at(hash);
let blockNumber, currentLeaseHolders;
if (!includeCurrentLeaseHolders) {
const { number } = await api.rpc.chain.getHeader(hash);
blockNumber = number.unwrap();
}
else {
const [{ number }, leaseEntries] = await Promise.all([
api.rpc.chain.getHeader(hash),
historicApi.query.slots.leases.entries(),
]);
blockNumber = number.unwrap();
currentLeaseHolders = leaseEntries.filter(([_k, leases]) => { var _a; return (_a = leases[0]) === null || _a === void 0 ? void 0 : _a.isSome; }).map(([key, _l]) => key.args[0]);
}
const leasePeriod = historicApi.consts.slots.leasePeriod;
const leasePeriodIndex = this.leasePeriodIndexAt(historicApi, blockNumber);
const leaseOffset = historicApi.consts.slots.leaseOffset || util_1.BN_ZERO;
const endOfLeasePeriod = leasePeriodIndex
? leasePeriodIndex.mul(leasePeriod).add(leasePeriod).add(leaseOffset)
: null;
return {
at: {
hash,
height: blockNumber.toString(10),
},
leasePeriodIndex: leasePeriodIndex ? leasePeriodIndex : util_1.BN_ZERO,
endOfLeasePeriod,
currentLeaseHolders,
};
}
/**
* ***********************************************************
* DEPRECATION NOTE: PHASED OUT SERVICE IN FAVOR OF CORETIME
* ***********************************************************
*
* List all registered paras (parathreads & parachains).
*
* @param hash `BlockHash` to make call at
* @returns all the current registered paraIds and their lifecycle status
*/
async paras(hash) {
const { api } = this;
const historicApi = await api.at(hash);
this.assertQueryModule(historicApi.query.paras, 'paras');
const [{ number }, paraLifecycles] = await Promise.all([
api.rpc.chain.getHeader(hash),
historicApi.query.paras.paraLifecycles.entries(),
]);
const parasPromises = paraLifecycles.map(async ([k, paraLifecycle]) => {
const paraId = k.args[0];
let onboardingAs;
if (paraLifecycle.isOnboarding) {
const paraGenesisArgs = await historicApi.query.paras.paraGenesisArgs(paraId);
onboardingAs = paraGenesisArgs.parachain.isTrue ? 'parachain' : 'parathread';
}
return {
paraId,
paraLifecycle,
onboardingAs,
};
});
return {
at: {
hash,
height: number.unwrap().toString(10),
},
paras: await Promise.all(parasPromises),
};
}
/**
* ***********************************************************
* DEPRECATION NOTE: PHASED OUT ENDPOINT IN FAVOR OF CORETIME
* ***********************************************************
*
* Get the heads of the included (backed or considered available) parachain candidates
* at the specified block height or at the most recent finalized head otherwise.
*
* @param hash `BlockHash` to make call at
*/
async parasHead(hash, method) {
const { api } = this;
const historicApi = await api.at(hash);
const [{ number }, events] = await Promise.all([api.rpc.chain.getHeader(hash), historicApi.query.system.events()]);
const paraInclusion = events.filter((record) => {
return record.event.section === 'paraInclusion' && record.event.method === method;
});
const paraHeaders = {};
paraInclusion.forEach(({ event }) => {
const { data } = event;
const paraData = data[0];
const headerData = data[1];
const { paraHead, paraId } = paraData.descriptor;
const header = api.createType('Header', headerData);
const { parentHash, number, stateRoot, extrinsicsRoot, digest } = header;
paraHeaders[paraId.toString()] = Object.assign({}, { hash: paraHead }, { parentHash, number, stateRoot, extrinsicsRoot, digest });
});
return {
at: {
hash,
height: number.unwrap().toString(10),
},
...paraHeaders,
};
}
/**
* Calculate the current lease period index.
* Ref: https://github.com/paritytech/polkadot/pull/3980
*
* @param historicApi
* @param now Current block number
*/
leasePeriodIndexAt(historicApi, now) {
const leasePeriod = historicApi.consts.slots.leasePeriod;
const offset = historicApi.consts.slots.leaseOffset || util_1.BN_ZERO;
// Edge case, see https://github.com/paritytech/polkadot/commit/3668966dc02ac793c799d8c8667e8c396d891734
if (now.toNumber() - offset.toNumber() < 0) {
return null;
}
return now.sub(offset).div(leasePeriod);
}
/**
* The offset into the ending samples of the auction. When we are not in the
* ending phase of the auction we can use 0 as the offset, but we do not return
* that here in order to closely mimic `Auctioneer::is_ending` impl in
* polkadot's `runtime::common::auctions`.
*
* @param now current block number
* @param beginEnd block number of the start of the auction's ending period
* @param phase Current phase the auction is in
* @param historicApi api specific to the queried blocks runtime
*/
endingOffset(now, beginEnd, phase, historicApi) {
if ((0, util_2.isNull)(beginEnd)) {
return null;
}
const afterEarlyEnd = now.sub(beginEnd);
if (afterEarlyEnd.lten(0)) {
return null;
}
/**
* The length of each sample to take during the ending period.
*
* A sample can be represented by `afterEarlyEnd` / `sampleLength`.
* When we are in the endingPeriod, the offset is represented by:
* `sample`.
*
* If the current phase is in `vrfDelay`, and you are interested in
* querying the winners of the auction that just finished, it is advised
* to query the last block of the `endingPeriod`.
*/
const sampleLength = historicApi.consts.auctions.sampleLength;
switch (phase) {
case 'startPeriod':
return util_1.BN_ZERO;
case 'endPeriod':
return afterEarlyEnd.div(sampleLength);
default:
return null;
}
}
/**
* Enumerate in order all the lease sets (SlotRange expressed as a set of
* lease periods) that an `auctions::winning` array covers.
*
* Below is an example of function behavior with
* input:
* `leasePeriodsPerSlot`: 3, `leasePeriodIndexNumber`: 0.
* output:
* `[[0], [0, 1], [0, 1, 2], [1], [1, 2], [2]]`
*
* The third inner loop builds the sub arrays that represent the SlotRange variant.
* You can think of the outer two loops as creating the start and the end of the range,
* then the inner most loop iterates through that start and end to build it for us.
* If we have 3 lease periods per slot (`lPPS`), the outer loop start at 0 and the 2nd
* inner loop builds increasing ranges starting from 0:
* `[0], [0, 1], [0, 1, 2]`
*
* Once we have built our 0 starting ranges we then increment the outermost loop and
* start building our 1 starting ranges:
* `[1], [1, 2]`
*
* And finally we increment the outer most loop to 2, building our 2 starting ranges
* `[2]`
*
* Put all those ranges together in the order they where produced and we get:
* `[[0], [0, 1], [0, 1, 2], [1], [1, 2], [2]]`
*
* So now we have an array, where each index corresponds to the same `SlotRange` that
* would be at that index in the `auctions::winning` array.
*
* @param historicApi
* @param leasePeriodIndex
*/
enumerateLeaseSets(historicApi, leasePeriodIndex) {
var _a;
const leasePeriodIndexNumber = leasePeriodIndex.toNumber();
const lPPS = (_a = historicApi.consts.auctions.leasePeriodsPerSlot) === null || _a === void 0 ? void 0 : _a.toNumber();
const ranges = [];
for (let start = 0; start < lPPS; start += 1) {
for (let end = start; end < lPPS; end += 1) {
const slotRange = [];
for (let i = start; i <= end; i += 1) {
slotRange.push(i + leasePeriodIndexNumber);
}
ranges.push(slotRange);
}
}
return ranges;
}
/**
* Parachains 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`);
}
}
}
exports.ParasService = ParasService;
//# sourceMappingURL=ParasService.js.map