Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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.

111 changes: 111 additions & 0 deletions docs/src/openapi-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1153,6 +1153,54 @@ 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 the material that is universal to constructing any
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Returns all the materials necessary to constructing any signed transactions offline."

I don't think we need any other details, and we can also get rid of the mention to /tx/artifacts.

signed transaction offline and the v14 of metadata. Replaces `/tx/artifacts`
from versions < v1.0.0.
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think JSON, and SCALE should be lower case here to be consistent

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 +1801,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
72 changes: 70 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 @@ -23,6 +23,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 +45,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 +73,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);

let api;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

if (at) {
api = await this.api.at(hash);
} else {
api = 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) === false) {
throw new Error(
`${metadataVersion} input is not of the expected 'vX' format, where 'X' represents the version number (examples: 'v14', 'v15').`,
);
}

const availableVersions = (await api.call.metadata.metadataVersions()).toHuman() as string[];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think toJSON() might be more fitting here. Generally speaking I think toHuman is good for user facing data that is meant to be displayed, such as UI's.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's awesome! Thank you so much for this one!

Changed it and added the as unknown as Vec<u32> since this reflects the actual type which is returned from await api.call.metadata.metadataVersions(). If inspected with .toRawType(), it is a Vec<u32>.

if (version && availableVersions?.includes(version.toString()) === false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (version && availableVersions?.includes(version.toString()) === false) {
if (version && !availableVersions?.includes(version.toString())) {

throw new Error(`Version ${version} of Metadata is not available.`);
}

const registry = api ? api.registry : this.api.registry;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

api will always be true here.

Copy link
Contributor Author

@Imod7 Imod7 Apr 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ofc! 😱 Changing this!

const metadata = await this.service.fetchMetadataVersioned(hash, 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, {});
};
}
58 changes: 56 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 @@ -25,6 +25,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 +63,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 +105,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);

let api;
if (at) {
api = await this.api.at(hash);
} else {
api = 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) === false) {
throw new Error(
`${metadataVersion} input is not of the expected 'vX' format, where 'X' represents the version number (examples: 'v14', 'v15').`,
);
}

const availableVersions = (await api.call.metadata.metadataVersions()).toHuman() as string[];
if (version && availableVersions?.includes(version.toString()) === false) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (version && availableVersions?.includes(version.toString()) === false) {
if (version && !availableVersions?.includes(version.toString())) {

throw new Error(`Version ${version} of Metadata is not available.`);
}

const metadataArg = this.parseMetadataArgs(metadata);

TransactionMaterialController.sanitizedSend(
res,
await this.service.fetchTransactionMaterialwithVersionedMetadata(hash, metadataArg, version),
);
};
}
47 changes: 45 additions & 2 deletions src/services/runtime/RuntimeMetadataService.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2017-2023 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,7 +15,9 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.

import { Metadata } from '@polkadot/types';
import { BlockHash } from '@polkadot/types/interfaces';
import type { Option } from '@polkadot/types/codec';
import type { BlockHash, OpaqueMetadata } from '@polkadot/types/interfaces';
import { InternalServerError } from 'http-errors';

import { AbstractService } from '../AbstractService';

Expand All @@ -32,4 +34,45 @@ export class RuntimeMetadataService extends AbstractService {

return metadata;
}

/**
* Fetch the requested version of `Metadata` in decoded JSON form.
*
* @param hash `BlockHash` to make call at
*/
async fetchMetadataVersioned(hash: BlockHash, metadataVersion: number): Promise<Metadata> {
const { api } = this;
const apiAt = await api.at(hash);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were potentially calling api.at twice, I would just pass the api from the controller down to the service and call it apiAt or something.


let metadata: Option<OpaqueMetadata> | undefined;
let metadataVersioned: Metadata | undefined;
try {
metadata = await apiAt.call.metadata.metadataAtVersion(metadataVersion);
if (metadata) {
metadataVersioned = new Metadata(apiAt.registry, metadata.unwrap());
} else {
throw new Error(`Metadata for version ${metadataVersion} is not available.`);
}
} catch {
throw new InternalServerError(
`An error occured while attempting to fetch ${metadataVersion.toString()} metadata.`,
);
}

return metadataVersioned;
}

/**
* Fetch the available `Metadata` versions.
*
* @param hash `BlockHash` to make call at
*/
async fetchMetadataVersions(hash: BlockHash): Promise<string[]> {
const { api } = this;
const apiAt = await api.at(hash);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this api.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed the previous one but for this one, in the RuntimeMetadataController > getMetadataAvailableVersions I don't call api.at. I just get the hash.


const availableVersions = (await apiAt.call.metadata.metadataVersions()).toHuman() as string[];

return availableVersions;
}
}
Loading