Skip to content

Commit

Permalink
feat: add decoded xcm data in blocks endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Imod7 committed Dec 6, 2023
1 parent 2a38b2e commit 394e889
Show file tree
Hide file tree
Showing 6 changed files with 204 additions and 4 deletions.
8 changes: 6 additions & 2 deletions src/controllers/blocks/BlocksController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ export default class BlocksController extends AbstractController<BlocksService>
* @param res Express Response
*/
private getBlockById: RequestHandler<INumberParam> = async (
{ params: { number }, query: { eventDocs, extrinsicDocs, finalizedKey } },
{ params: { number }, query: { eventDocs, extrinsicDocs, finalizedKey, decodedXcmMsgs, paraId } },
res,
): Promise<void> => {
const checkFinalized = isHex(number);
Expand All @@ -168,6 +168,7 @@ export default class BlocksController extends AbstractController<BlocksService>

const eventDocsArg = eventDocs === 'true';
const extrinsicDocsArg = extrinsicDocs === 'true';
const decodedXcmMsgsArg = decodedXcmMsgs === 'true';
const finalizeOverride = finalizedKey === 'false';

const queryFinalizedHead = !this.options.finalizes ? false : true;
Expand All @@ -189,7 +190,10 @@ export default class BlocksController extends AbstractController<BlocksService>
const historicApi = await this.api.at(hash);

// We set the last param to true because we haven't queried the finalizedHead
BlocksController.sanitizedSend(res, await this.service.fetchBlock(hash, historicApi, options));
BlocksController.sanitizedSend(
res,
await this.service.fetchBlock(hash, historicApi, options, decodedXcmMsgsArg, paraId as string),
);
};

/**
Expand Down
9 changes: 7 additions & 2 deletions src/services/blocks/BlocksService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// Copyright 2017-2023 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
Expand Down Expand Up @@ -56,6 +56,7 @@ import {
import { IOption } from '../../types/util';
import { isPaysFee } from '../../types/util';
import { AbstractService } from '../AbstractService';
import { XcmDecoder } from './XCMDecoder';

/**
* Types for fetchBlock's options
Expand Down Expand Up @@ -102,6 +103,8 @@ export class BlocksService extends AbstractService {
hash: BlockHash,
historicApi: ApiDecoration<'promise'>,
{ eventDocs, extrinsicDocs, checkFinalized, queryFinalizedHead, omitFinalizedTag }: FetchBlockOptions,
decodedXcmMsgsArg?: boolean,
paraId?: string,
): Promise<IBlock> {
const { api } = this;

Expand Down Expand Up @@ -287,7 +290,8 @@ export class BlocksService extends AbstractService {
kind: dispatchFeeType,
};
}

const decodedMsgs = decodedXcmMsgsArg ? new XcmDecoder(api, specName.toString(), extrinsics, paraId) : undefined;
const decodedXcmMsgs = decodedMsgs?.messages;
const response = {
number,
hash,
Expand All @@ -300,6 +304,7 @@ export class BlocksService extends AbstractService {
extrinsics,
onFinalize,
finalized,
decodedXcmMsgs,
};

// Store the block in the cache
Expand Down
151 changes: 151 additions & 0 deletions src/services/blocks/XCMDecoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2017-2023 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/>.

import '@polkadot/api-augment';

import { ApiPromise } from '@polkadot/api';
import { Bytes } from '@polkadot/types';

import {
IDownwardMessage,
IExtrinsic,
IFrameMethod,
IHorizontalMessage,
IMessages,
IUpwardMessage,
} from '../../types/responses';

enum ChainType {
Relay = 'Relay',
Parachain = 'Parachain',
}

export class XcmDecoder {
readonly messages: IMessages[];
readonly api: ApiPromise;
static curChainType: ChainType;

constructor(api: ApiPromise, specName: string, extrinsics: IExtrinsic[], paraId?: string) {
this.api = api;
XcmDecoder.curChainType = XcmDecoder.getCurChainType(specName);
this.messages = XcmDecoder.getMessages(api, extrinsics, paraId);
}

static getCurChainType(specName: string): ChainType {
const relay = ['polkadot', 'kusama', 'westend', 'rococo'];
if (relay.includes(specName)) {
return ChainType.Relay;
} else {
return ChainType.Parachain;
}
}

static getMessages(api: ApiPromise, extrinsics: IExtrinsic[], paraId?: string): IMessages[] {
const xcmMessages: IMessages[] = [];
if (XcmDecoder.curChainType === ChainType.Relay) {
extrinsics.forEach((extrinsic) => {
const frame = extrinsic.method as IFrameMethod;
if (frame.pallet === 'paraInherent' && frame.method === 'enter') {
const data: any = extrinsic.args.data;

Check failure on line 62 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const upwardMessage: IUpwardMessage[] = [];
if (paraId !== undefined) {
data.backedCandidates.forEach((candidate: any) => {

Check failure on line 65 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe call of an `any` typed value

Check failure on line 65 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .backedCandidates on an `any` value

Check failure on line 65 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
if (candidate.candidate.descriptor.paraId.toString() === paraId) {

Check failure on line 66 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe call of an `any` typed value

Check failure on line 66 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .candidate on an `any` value
const msg_decoded = XcmDecoder.checkUpwardMsg(api, candidate);
if (msg_decoded != undefined && Object.keys(msg_decoded).length > 0) {
upwardMessage.push(msg_decoded);
}
}
});
} else {
data.backedCandidates.forEach((candidate: any) => {

Check failure on line 74 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe call of an `any` typed value

Check failure on line 74 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unsafe member access .backedCandidates on an `any` value

Check failure on line 74 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
const msg_decoded = XcmDecoder.checkUpwardMsg(api, candidate);
if (msg_decoded != undefined && Object.keys(msg_decoded).length > 0) {
upwardMessage.push(msg_decoded);
}
});
}
xcmMessages.push({
upwardMessages: upwardMessage,
});
}
});
} else if (XcmDecoder.curChainType === ChainType.Parachain) {
extrinsics.forEach((extrinsic) => {
const frame: IFrameMethod = extrinsic.method as IFrameMethod;
if (frame.pallet === 'parachainSystem' && frame.method === 'setValidationData') {
const data: any = extrinsic.args.data;

Check failure on line 90 in src/services/blocks/XCMDecoder.ts

View workflow job for this annotation

GitHub Actions / lint

Unexpected any. Specify a different type
data.downwardMessages.forEach((msg: any) => {
if (msg.msg.length > 0) {
const downwardMessage: IDownwardMessage[] = [];
const xcmMessageDecoded = this.decodeMsg(api, msg.msg);
downwardMessage.push({
sentAt: msg.sentAt,
data: xcmMessageDecoded,
});
xcmMessages.push({
downwardMessages: downwardMessage,
});
}
});
data.horizontalMessages.forEach((msg: [], index: string) => {
msg.forEach((msg: any) => {
const horizontalMessage: IHorizontalMessage[] = [];
const xcmMessageDecoded = this.decodeMsg(api, msg.data.slice(1));
horizontalMessage.push({
sentAt: msg.sentAt,
paraId: index,
data: xcmMessageDecoded,
});
xcmMessages.push({
horizontalMessages: horizontalMessage,
});
});
});
}
});
}
return xcmMessages;
}

static checkUpwardMsg(api: ApiPromise, candidate: any): IUpwardMessage | undefined {
if (candidate.candidate.commitments.upwardMessages.length > 0) {
const xcmMessage: string = candidate.candidate.commitments.upwardMessages;
const paraId: string = candidate.candidate.descriptor.paraId.toString();
const xcmMessageDecoded: string = this.decodeMsg(api, xcmMessage[0]);
const upwardMessage = {
paraId: paraId,
data: xcmMessageDecoded[0],
};
return upwardMessage;
} else {
return undefined;
}
}

static decodeMsg(api: ApiPromise, message: string): string {
const instructions = [];
let xcmMessage: string = message;
let instructionLength = 0;
while (xcmMessage.length != 0) {
const xcmInstructions: Bytes = api.createType('XcmVersionedXcm', xcmMessage);
instructions.push(xcmInstructions);
instructionLength = xcmInstructions.toU8a().length;
xcmMessage = xcmMessage.slice(instructionLength);
}
return instructions as unknown as string;
}
}
2 changes: 2 additions & 0 deletions src/types/responses/Block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { AccountId } from '@polkadot/types/interfaces/runtime';
import { Codec } from '@polkadot/types/types';

import { IExtrinsic, ISanitizedEvent } from '.';
import { IMessages } from './BlockXCMMessages';

export interface IBlock {
number: Compact<BlockNumber>;
Expand All @@ -33,6 +34,7 @@ export interface IBlock {
extrinsics: IExtrinsic[];
onFinalize: IOnInitializeOrFinalize;
finalized: boolean | undefined;
decodedXcmMsgs?: IMessages[] | undefined;
}

interface IOnInitializeOrFinalize {
Expand Down
37 changes: 37 additions & 0 deletions src/types/responses/BlockXCMMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2017-2023 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/>.

export interface IUpwardMessage {
paraId: string;
data: string;
}

export interface IDownwardMessage {
sentAt: string;
data: string;
}

export interface IHorizontalMessage {
sentAt: string;
paraId: string;
data: string;
}

export interface IMessages {
horizontalMessages?: IHorizontalMessage[];
downwardMessages?: IDownwardMessage[];
upwardMessages?: IUpwardMessage[];
}
1 change: 1 addition & 0 deletions src/types/responses/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './Assets';
export * from './At';
export * from './Block';
export * from './BlockRaw';
export * from './BlockXCMMessages';
export * from './EraPayouts';
export * from './Extrinsic';
export * from './ForeignAssets';
Expand Down

0 comments on commit 394e889

Please sign in to comment.