Skip to content

feat: add metadata versions endpoints #1424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 57 additions & 57 deletions docs/dist/app.bundle.js

Large diffs are not rendered by default.

110 changes: 110 additions & 0 deletions docs/src/openapi-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,53 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/transaction/material/{metadataVersion}:
get:
tags:
- transaction
summary: Get all the network information needed to construct a transaction offline and
the version of metadata specified in `metadataVersion`.
description: Returns all the materials necessary for constructing any signed transactions
offline.
operationId: getTransactionMaterialwithVersionedMetadata
parameters:
- name: metadataVersion
in: path
description: The version of metadata. The input is expected in a `vX` format, where `X`
represents the version number (e.g. `v14`, `v15`). By default, metadata is outputted
in 'json' format, unless the `metadata` query parameter is provided, in which case it
can be either in 'json' or 'scale' format.
required: true
schema:
type: string
- name: at
in: query
description: Block at which to retrieve the transaction construction
material.
required: false
schema:
type: string
description: Block identifier, as the block height or block hash.
format: unsignedInteger or $hex
- name: metadata
in: query
description: Specifies the format of the metadata to be returned. Accepted values are
'json', and 'scale'. 'json' being the decoded metadata, and 'scale' being the SCALE encoded metadata.
schema:
type: string
responses:
"200":
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionMaterial'
"400":
description: invalid blockId supplied for at query param
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/pallets/assets/{assetId}/asset-info:
get:
tags:
Expand Down Expand Up @@ -1753,6 +1800,69 @@ paths:
schema:
type: object
description: Response is dependent on the runtime metadata contents.
/runtime/metadata/{metadataVersion}:
get:
tags:
- runtime
summary: Get the requested version of runtime metadata in decoded, JSON form.
description: >-
Returns the requested version of runtime metadata as a JSON object.
Substrate Reference:
- FRAME Support: https://crates.parity.io/frame_support/metadata/index.html
- Knowledge Base: https://substrate.dev/docs/en/knowledgebase/runtime/metadata
parameters:
- name: metadataVersion
in: path
description: The version of metadata. The input is expected in a `vX` format, where `X`
represents the version number (e.g. `v14`, `v15`).
required: true
schema:
type: string
- name: at
in: query
description: Block at which to retrieve the metadata at.
required: false
schema:
type: string
description: Block identifier, as the block height or block hash.
format: unsignedInteger or $hex
responses:
"200":
description: successful operation
content:
application/json:
schema:
type: object
description: Response is dependent on the runtime metadata contents.
/runtime/metadata/versions:
get:
tags:
- runtime
summary: Get the available versions of runtime metadata.
description: >-
Returns the available versions of runtime metadata.
Substrate Reference:
- FRAME Support: https://crates.parity.io/frame_support/metadata/index.html
- Knowledge Base: https://substrate.dev/docs/en/knowledgebase/runtime/metadata
parameters:
- name: at
in: query
description: Block at which to retrieve the metadata versions at.
required: false
schema:
type: string
description: Block identifier, as the block height or block hash.
format: unsignedInteger or $hex
responses:
"200":
description: successful operation
content:
application/json:
schema:
type: array
items:
type: string
description: An array with the available metadata versions.
/runtime/code:
get:
tags:
Expand Down
73 changes: 71 additions & 2 deletions src/controllers/runtime/RuntimeMetadataController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// Copyright 2017-2024 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 All @@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import { ApiPromise } from '@polkadot/api';
import { u32, Vec } from '@polkadot/types';
import { RequestHandler } from 'express';

import { RuntimeMetadataService } from '../../services';
Expand All @@ -23,6 +24,10 @@ import AbstractController from '../AbstractController';
/**
* GET the chain's metadata.
*
* Path params:
* - (Optional) `metadataVersion`: The specific version of the Metadata to query.
* The input must conform to the `vX` format, where `X` represents the version number (examples: 'v14', 'v15').
*
* Query:
* - (Optional) `at`: Block hash or height at which to query. If not provided, queries
* finalized head.
Expand All @@ -41,7 +46,11 @@ export default class RuntimeMetadataController extends AbstractController<Runtim
}

protected initRoutes(): void {
this.safeMountAsyncGetHandlers([['', this.getMetadata]]);
this.safeMountAsyncGetHandlers([
['/', this.getMetadata],
['/versions', this.getMetadataAvailableVersions],
['/:metadataVersion', this.getMetadataVersioned],
]);
}

/**
Expand All @@ -65,4 +74,64 @@ export default class RuntimeMetadataController extends AbstractController<Runtim
metadataOpts: { registry, version: metadata.version },
});
};

/**
* Get the chain's metadata at a specific version in a decoded, JSON format.
*
* @param _req Express Request
* @param res Express Response
*/
private getMetadataVersioned: RequestHandler = async (
{ params: { metadataVersion }, query: { at } },
res,
): Promise<void> => {
const hash = await this.getHashFromAt(at);

const api = at ? await this.api.at(hash) : this.api;

// Validation of the `metadataVersion` path parameter.
const metadataV = metadataVersion.slice(1);
const version = this.parseNumberOrThrow(
metadataV,
`Version ${metadataV.toString()} of metadata provided is not a number.`,
);

const regExPattern = new RegExp('^[vV][0-9]+$');
if (!regExPattern.test(metadataVersion)) {
throw new Error(
`${metadataVersion} input is not of the expected 'vX' format, where 'X' represents the version number (examples: 'v14', 'v15').`,
);
}

let availableVersions = [];
try {
availableVersions = (await api.call.metadata.metadataVersions()).toJSON() as unknown as Vec<u32>;
} catch {
throw new Error(`Function 'api.call.metadata.metadataVersions()' is not available at this block height.`);
}
if (version && !availableVersions?.includes(version as unknown as u32)) {
throw new Error(`Version ${version} of Metadata is not available.`);
}

const registry = api.registry;
const metadata = await this.service.fetchMetadataVersioned(api, version);

RuntimeMetadataController.sanitizedSend(res, metadata, {
metadataOpts: { registry, version },
});
};

/**
* Get the available versions of chain's metadata.
*
* @param _req Express Request
* @param res Express Response
*/
private getMetadataAvailableVersions: RequestHandler = async ({ query: { at } }, res): Promise<void> => {
const hash = await this.getHashFromAt(at);

const metadataVersions = await this.service.fetchMetadataVersions(hash);

RuntimeMetadataController.sanitizedSend(res, metadataVersions, {});
};
}
59 changes: 57 additions & 2 deletions src/controllers/transaction/TransactionMaterialController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// Copyright 2017-2024 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 All @@ -15,6 +15,7 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import { ApiPromise } from '@polkadot/api';
import { u32, Vec } from '@polkadot/types';
import { RequestHandler } from 'express';

import { TransactionMaterialService } from '../../services';
Expand All @@ -25,6 +26,10 @@ export type MetadataOpts = 'json' | 'scale';
/**
* GET all the network information needed to construct a transaction offline.
*
* Path params:
* - (Optional) `metadataVersion`: The specific version of the Metadata to query.
* The input must conform to the `vX` format, where `X` represents the version number (examples: 'v14', 'v15').
*
* Query
* - (Optional) `metadata`: It accepts `json`, or `scale` values. If it is not present,
* the metadata field will not be included.
Expand Down Expand Up @@ -59,7 +64,10 @@ export default class TransactionMaterialController extends AbstractController<Tr
}

protected initRoutes(): void {
this.safeMountAsyncGetHandlers([['', this.getTransactionMaterial]]);
this.safeMountAsyncGetHandlers([
['/', this.getTransactionMaterial],
['/:metadataVersion', this.getTransactionMaterialwithVersionedMetadata],
]);
}

/**
Expand Down Expand Up @@ -98,4 +106,51 @@ export default class TransactionMaterialController extends AbstractController<Tr

return false;
}

/**
* Get the chain's metadata at the requested version in JSON or scale format
* depending on the `metadata` query param.
*
* @param _req Express Request
* @param res Express Response
*/
private getTransactionMaterialwithVersionedMetadata: RequestHandler = async (
{ params: { metadataVersion }, query: { at, metadata } },
res,
): Promise<void> => {
const hash = await this.getHashFromAt(at);

const api = at ? await this.api.at(hash) : this.api;

// Validation of the `metadataVersion` path parameter.
const metadataV = metadataVersion.slice(1);
const version = this.parseNumberOrThrow(
metadataV,
`Version ${metadataV.toString()} of metadata provided is not a number.`,
);

const regExPattern = new RegExp('^[vV][0-9]+$');
if (!regExPattern.test(metadataVersion)) {
throw new Error(
`${metadataVersion} input is not of the expected 'vX' format, where 'X' represents the version number (examples: 'v14', 'v15').`,
);
}

let availableVersions = [];
try {
availableVersions = (await api.call.metadata.metadataVersions()).toJSON() as unknown as Vec<u32>;
} catch {
throw new Error(`Function 'api.call.metadata.metadataVersions()' is not available at this block height.`);
}
if (version && !availableVersions?.includes(version as unknown as u32)) {
throw new Error(`Version ${version} of Metadata is not available.`);
}

const metadataArg = this.parseMetadataArgs(metadata);

TransactionMaterialController.sanitizedSend(
res,
await this.service.fetchTransactionMaterialwithVersionedMetadata(api, hash, metadataArg, version),
);
};
}
34 changes: 9 additions & 25 deletions src/services/node/NodeTransactionPoolService.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017-2022 Parity Technologies (UK) Ltd.
// Copyright 2017-2024 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 All @@ -18,29 +18,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers';
import { polkadotRegistryV9300 } from '../../test-helpers/registries';
import {
// blockHash789629,
defaultMockApi,
pendingExtrinsics,
} from '../test-helpers/mock';
import { defaultMockApi, pendingExtrinsics } from '../test-helpers/mock';
import transactionPoolResponse from '../test-helpers/responses/node/transactionPool.json';
import transactionPoolWithTipResponse from '../test-helpers/responses/node/transactionPoolWithTip.json';
import transactionPoolWithTipOperationalResponse from '../test-helpers/responses/node/transactionPoolWithTipOperational.json';
import { NodeTransactionPoolService } from '.';

const nodeTranstionPoolService = new NodeTransactionPoolService(defaultMockApi);
const nodeTransactionPoolService = new NodeTransactionPoolService(defaultMockApi);

describe('NodeTransactionPoolService', () => {
describe('fetchTransactionPool', () => {
it('works when ApiPromiseWorks (no txs)', async () => {
expect(
sanitizeNumbers(
await nodeTranstionPoolService.fetchTransactionPool(
// blockHash789629
false,
),
),
).toStrictEqual({ pool: [] });
expect(sanitizeNumbers(await nodeTransactionPoolService.fetchTransactionPool(false))).toStrictEqual({ pool: [] });
});

it('works when ApiPromiseWorks (1 tx)', async () => {
Expand All @@ -52,14 +41,9 @@ describe('NodeTransactionPoolService', () => {
const pool = defaultMockApi.createType('Vec<Extrinsic>', [ext]);
(defaultMockApi.rpc.author as any).pendingExtrinsics = () => Promise.resolve().then(() => pool);

expect(
sanitizeNumbers(
await nodeTranstionPoolService.fetchTransactionPool(
// blockHash789629
false,
),
),
).toStrictEqual(transactionPoolResponse);
expect(sanitizeNumbers(await nodeTransactionPoolService.fetchTransactionPool(false))).toStrictEqual(
transactionPoolResponse,
);

(defaultMockApi.rpc.author as any).pendingExtrinsics = pendingExtrinsics;
});
Expand All @@ -74,7 +58,7 @@ describe('NodeTransactionPoolService', () => {
const pool = polkadotRegistryV9300.createType('Vec<Extrinsic>', [normalExt]);
(defaultMockApi.rpc.author as any).pendingExtrinsics = () => Promise.resolve().then(() => pool);

expect(sanitizeNumbers(await nodeTranstionPoolService.fetchTransactionPool(true))).toStrictEqual(
expect(sanitizeNumbers(await nodeTransactionPoolService.fetchTransactionPool(true))).toStrictEqual(
transactionPoolWithTipResponse,
);

Expand All @@ -89,7 +73,7 @@ describe('NodeTransactionPoolService', () => {
const pool = polkadotRegistryV9300.createType('Vec<Extrinsic>', [operationalExt]);
(defaultMockApi.rpc.author as any).pendingExtrinsics = () => Promise.resolve().then(() => pool);

expect(sanitizeNumbers(await nodeTranstionPoolService.fetchTransactionPool(true))).toStrictEqual(
expect(sanitizeNumbers(await nodeTransactionPoolService.fetchTransactionPool(true))).toStrictEqual(
transactionPoolWithTipOperationalResponse,
);

Expand Down
Loading
Loading