Skip to content
ApiHandler.ts 4.85 KiB
Newer Older
Maciej Hirsz's avatar
Maciej Hirsz committed
// Copyright 2017-2020 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/>.

Maciej Hirsz's avatar
Maciej Hirsz committed
import { ApiPromise } from '@polkadot/api';
import { BlockHash } from '@polkadot/types/interfaces/rpc';
Maciej Hirsz's avatar
Maciej Hirsz committed
import { Event, EventRecord } from '@polkadot/types/interfaces/system';
import { EventData } from '@polkadot/types/primitive/Generic/Event';
Maciej Hirsz's avatar
Maciej Hirsz committed
import { blake2AsU8a } from '@polkadot/util-crypto';
import { u8aToHex } from '@polkadot/util';
import { getChainTypes } from '@polkadot/types/known';
import { u32 } from '@polkadot/types/primitive';
Maciej Hirsz's avatar
Maciej Hirsz committed

interface SantiziedEvent {
	method: string;
	data: EventData;
Maciej Hirsz's avatar
Maciej Hirsz committed

export default class ApiHandler {
	private api: ApiPromise;
Maciej Hirsz's avatar
Maciej Hirsz committed

	constructor(api: ApiPromise) {
		this.api = api;
		this.specVersion = api.createType('u32', -1);
Maciej Hirsz's avatar
Maciej Hirsz committed
	}

	async fetchBlock(hash: BlockHash) {
Maciej Hirsz's avatar
Maciej Hirsz committed
		const { api } = this;
Maciej Hirsz's avatar
Maciej Hirsz committed
		const [{ block }, events] = await Promise.all([
			api.rpc.chain.getBlock(hash),
			this.fetchEvents(hash),
		]);

Maciej Hirsz's avatar
Maciej Hirsz committed
		const { parentHash, number, stateRoot, extrinsicsRoot } = block.header;

		const logs = block.header.digest.logs.map((log) => {
			const { type, index, value } = log;

			return { type, index, value };
		});
Maciej Hirsz's avatar
Maciej Hirsz committed

		const defaultSuccess = typeof events === 'string' ? events : false;
		const queryInfo = await Promise.all(block.extrinsics.map(async (extrinsic) => {
			if (extrinsic.isSigned && extrinsic.method.sectionName !== 'sudo') {
				try {
					return await api.rpc.payment.queryInfo(extrinsic.toHex(), hash);
				} catch (err) {
					console.error(err);

					return {
						error: 'Unable to fetch fee info',
					}
				}
		}));
		const extrinsics = block.extrinsics.map((extrinsic, idx) => {
Maciej Hirsz's avatar
Maciej Hirsz committed
			const { method, nonce, signature, signer, isSigned, tip, args } = extrinsic;
			const hash = u8aToHex(blake2AsU8a(extrinsic.toU8a(), 256));
			const info = queryInfo[idx];
Maciej Hirsz's avatar
Maciej Hirsz committed

			return {
				method: `${method.sectionName}.${method.methodName}`,
				signature: isSigned ? { signature, signer } : null,
				nonce,
				args,
Maciej Hirsz's avatar
Maciej Hirsz committed
				tip,
				hash,
Maciej Hirsz's avatar
Maciej Hirsz committed
				events: [] as SantiziedEvent[],
				success: defaultSuccess,
Maciej Hirsz's avatar
Maciej Hirsz committed
		if (Array.isArray(events)) {
			for (const record of events) {
				const { event, phase } = record;

				if (phase.isApplyExtrinsic) {
					const extrinsicIdx = phase.asApplyExtrinsic.toNumber();
					const extrinsic = extrinsics[extrinsicIdx];

					if (!extrinsic) {
						throw new Error(`Missing extrinsic ${extrinsicIdx} in block ${hash}`);
					}

					const method = `${event.section}.${event.method}`;

					if (method === 'system.ExtrinsicSuccess') {
						extrinsic.success = true;
					}

					extrinsic.events.push({
						method: `${event.section}.${event.method}`,
						data: event.data,
					});
				}
			}
		}

Maciej Hirsz's avatar
Maciej Hirsz committed
		return {
			number,
			hash,
			parentHash,
			stateRoot,
			extrinsicsRoot,
			logs,
			extrinsics,
		};
	}

	async fetchBalance(hash: BlockHash, address: string) {
Maciej Hirsz's avatar
Maciej Hirsz committed
		const { api } = this;

		const [header, free, reserved, locks, nonce] = await Promise.all([
Maciej Hirsz's avatar
Maciej Hirsz committed
			api.rpc.chain.getHeader(hash),
			api.query.balances.freeBalance.at(hash, address),
			api.query.balances.reservedBalance.at(hash, address),
			api.query.balances.locks.at(hash, address),
			api.query.system.accountNonce.at(hash, address),
Maciej Hirsz's avatar
Maciej Hirsz committed
		]);

		const at = {
			hash,
			height: (header as any).number, // TODO: fix this nasty obvious type erasure
		return { at, nonce, free, reserved, locks };
Maciej Hirsz's avatar
Maciej Hirsz committed
	}
Maciej Hirsz's avatar
Maciej Hirsz committed

	async fetchEvents(hash: BlockHash): Promise<EventRecord[] | string> {
		try {
			return await await this.api.query.system.events.at(hash);
		} catch (_) {
			return 'Unable to fetch Events, cannot confirm extrinsic status. Check pruning settings on the node.';
		}
	}

	async ensureMeta(hash: BlockHash) {
		const { api } = this;

		const runtimeVersion = await api.rpc.state.getRuntimeVersion(hash);
		const blockSpecVersion = runtimeVersion.specVersion;

	    // swap metadata if spec version is different
	    if (!this.specVersion.eq(blockSpecVersion)) {
	    	this.specVersion = blockSpecVersion;
			const meta = await api.rpc.state.getMetadata(hash);
			const chain = await api.rpc.system.chain();

			api.registry.register(getChainTypes(chain, runtimeVersion));
			api.registry.setMetadata(meta);
		}
	}
Maciej Hirsz's avatar
Maciej Hirsz committed
}