@substrate/api-sidecar
Version:
REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.
170 lines • 8.78 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/>.
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getInclusionBlockNumber = exports.searchForInclusionBlock = exports.hasRelayParentData = exports.getRelayParentNumberRaw = exports.getRelayParentNumber = void 0;
const bn_js_1 = __importDefault(require("bn.js"));
/** Default search depth for inclusion search */
const DEFAULT_SEARCH_DEPTH = 10;
/** Batch size for parallel searches */
const BATCH_SIZE = 5;
/**
* Extract the relay chain parent block number from a parachain block.
*
* Parachain blocks contain a `parachainSystem.setValidationData` extrinsic that
* includes the relay chain block number that was the parent when the parachain
* block was created.
*
* Note: This is the relay PARENT, not the inclusion block. For the actual
* inclusion block, use `getInclusionBlockNumber` or `searchForInclusionBlock`.
*
* @param api - The ApiPromise instance connected to the parachain
* @param blockHash - The hash of the parachain block to extract relay parent from
* @returns The relay chain parent block number as a BN
* @throws Error if the block doesn't contain setValidationData extrinsic
*/
const getRelayParentNumber = async (api, blockHash) => {
const [apiAt, { block }] = await Promise.all([api.at(blockHash), api.rpc.chain.getBlock(blockHash)]);
const setValidationData = block.extrinsics.find((ext) => {
return ext.method.method.toString() === 'setValidationData';
});
if (!setValidationData) {
throw new Error('Block does not contain setValidationData extrinsic. Cannot determine relay parent number.');
}
const callArgs = apiAt.registry.createType('Call', setValidationData.method);
const { relayParentNumber } = callArgs.toJSON().args.data.validationData;
return new bn_js_1.default(relayParentNumber);
};
exports.getRelayParentNumber = getRelayParentNumber;
/**
* Extract relay parent number as a plain number (for use with search functions).
*
* @param api - The ApiPromise instance connected to the parachain
* @param blockHash - The hash of the parachain block
* @returns The relay chain parent block number as a number
*/
const getRelayParentNumberRaw = async (api, blockHash) => {
const [apiAt, { block }] = await Promise.all([api.at(blockHash), api.rpc.chain.getBlock(blockHash)]);
const setValidationData = block.extrinsics.find((ext) => {
return ext.method.method.toString() === 'setValidationData';
});
if (!setValidationData) {
throw new Error('Block does not contain setValidationData extrinsic. Cannot determine relay parent number.');
}
const callArgs = apiAt.registry.createType('Call', setValidationData.method);
const { relayParentNumber } = callArgs.toJSON().args.data.validationData;
return relayParentNumber;
};
exports.getRelayParentNumberRaw = getRelayParentNumberRaw;
/**
* Check if a block contains the setValidationData extrinsic.
*
* This can be used to determine if a block is from a parachain (has validation data)
* or a relay chain (does not have validation data).
*
* @param api - The ApiPromise instance
* @param blockHash - The hash of the block to check
* @returns true if the block contains setValidationData, false otherwise
*/
const hasRelayParentData = async (api, blockHash) => {
const { block } = await api.rpc.chain.getBlock(blockHash);
return block.extrinsics.some((ext) => ext.method.method.toString() === 'setValidationData');
};
exports.hasRelayParentData = hasRelayParentData;
/**
* Search relay chain blocks for the inclusion of a specific parachain block.
*
* This searches relay chain blocks starting from `relayParentNumber + 1` looking
* for a `paraInclusion.CandidateIncluded` event that matches the given parachain
* block number and paraId.
*
* @param rcApi - The ApiPromise instance connected to the relay chain
* @param paraId - The parachain ID (e.g., 1000 for Asset Hub)
* @param parachainBlockNumber - The parachain block number to search for
* @param relayParentNumber - The relay parent number from setValidationData
* @param maxDepth - Maximum number of relay blocks to search (default: 10)
* @returns The relay chain block number where inclusion was found, or null if not found
*/
const searchForInclusionBlock = async (rcApi, paraId, parachainBlockNumber, relayParentNumber, maxDepth = DEFAULT_SEARCH_DEPTH) => {
// Search in batches for optimal performance
// Most inclusions happen within 2-4 blocks of relayParentNumber
for (let offset = 0; offset < maxDepth; offset += BATCH_SIZE) {
const batchSize = Math.min(BATCH_SIZE, maxDepth - offset);
const searchBlocks = Array.from({ length: batchSize }, (_, i) => relayParentNumber + offset + i + 1);
const searchPromises = searchBlocks.map(async (blockNum) => {
try {
const relayBlockHash = await rcApi.rpc.chain.getBlockHash(blockNum);
const rcApiAt = await rcApi.at(relayBlockHash);
const events = await rcApiAt.query.system.events();
const foundInclusion = events.find((record) => {
if (record.event.section === 'paraInclusion' && record.event.method === 'CandidateIncluded') {
const eventData = record.event.data[0].toJSON();
if (eventData.descriptor.paraId === paraId) {
const header = rcApiAt.registry.createType('Header', record.event.data[1]);
return header.number.toString() === parachainBlockNumber;
}
}
return false;
});
return foundInclusion ? blockNum : null;
}
catch {
return null;
}
});
const results = await Promise.all(searchPromises);
const found = results.find((result) => result !== null);
if (found) {
return found; // Early termination when found in this batch
}
}
return null; // Not found within search depth
};
exports.searchForInclusionBlock = searchForInclusionBlock;
/**
* Get the relay chain block number where a parachain block was included.
*
* This is the main function for finding the accurate relay chain block number
* corresponding to a parachain block. It:
* 1. Extracts the relayParentNumber from the parachain block's setValidationData
* 2. Searches relay chain blocks for the actual inclusion event
*
* @param parachainApi - The ApiPromise instance connected to the parachain
* @param rcApi - The ApiPromise instance connected to the relay chain
* @param parachainBlockHash - The hash of the parachain block
* @param paraId - The parachain ID (e.g., 1000 for Asset Hub)
* @param maxDepth - Maximum number of relay blocks to search (default: 10)
* @returns The inclusion search result with inclusionBlockNumber, relayParentNumber, and found status
*/
const getInclusionBlockNumber = async (parachainApi, rcApi, parachainBlockHash, paraId, maxDepth = DEFAULT_SEARCH_DEPTH) => {
// Get the parachain block header to get the block number
const header = await parachainApi.rpc.chain.getHeader(parachainBlockHash);
const parachainBlockNumber = header.number.unwrap().toString();
// Extract relay parent number from the parachain block
const relayParentNumber = await (0, exports.getRelayParentNumberRaw)(parachainApi, parachainBlockHash);
// Search for the actual inclusion block
const inclusionBlockNumber = await (0, exports.searchForInclusionBlock)(rcApi, paraId, parachainBlockNumber, relayParentNumber, maxDepth);
return {
inclusionBlockNumber,
relayParentNumber,
found: inclusionBlockNumber !== null,
};
};
exports.getInclusionBlockNumber = getInclusionBlockNumber;
//# sourceMappingURL=getRelayParentNumber.js.map