Skip to content
ApiHandler.ts 3.88 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';

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

export default class ApiHandler {
	private api: ApiPromise;

	constructor(api: ApiPromise) {
		this.api = api;
	}

	async fetchBlock(hash: BlockHash) {
		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((extrinsic) => {
			if (extrinsic.isSigned) {
				return api.rpc.payment.queryInfo(extrinsic.toHex(), hash);
			} else {
				return Promise.resolve({});
			}
		}));
		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) {
		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.number,
		};

		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.';
		}
	}
Maciej Hirsz's avatar
Maciej Hirsz committed
}