From 9ba1f58ad5c0123204ec44f65be9c7448eb4ee07 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sat, 22 Aug 2020 00:42:02 -0700 Subject: [PATCH 1/5] Implement `runtime/code` & `runtime/spec` --- openapi/openapi-proposal.yaml | 11 ++-- .../runtime/RuntimeCodeController.ts | 51 +++++++++++++++++ .../runtime/RuntimeSpecController.ts | 55 +++++++++++++++++++ src/controllers/runtime/index.ts | 2 + src/main.ts | 4 ++ .../accounts/AccountsStakingInfoService.ts | 2 +- .../runtime/RuntimeCodeService.spec.ts | 18 ++++++ src/services/runtime/RuntimeCodeService.ts | 32 +++++++++++ .../runtime/RuntimeSpecService.spec.ts | 18 ++++++ src/services/runtime/RuntimeSpecService.ts | 33 +++++++++++ src/services/runtime/index.ts | 2 + src/services/test-helpers/mock/mockApi.ts | 26 +++++++++ .../responses/runtime/code789629.json | 7 +++ .../test-helpers/responses/runtime/spec.json | 15 +++++ src/types/responses/MetadataCode.ts | 8 +++ src/types/responses/RuntimeSpec.ts | 12 ++++ src/types/responses/index.ts | 2 + 17 files changed, 292 insertions(+), 6 deletions(-) create mode 100644 src/controllers/runtime/RuntimeCodeController.ts create mode 100644 src/controllers/runtime/RuntimeSpecController.ts create mode 100644 src/services/runtime/RuntimeCodeService.spec.ts create mode 100644 src/services/runtime/RuntimeCodeService.ts create mode 100644 src/services/runtime/RuntimeSpecService.spec.ts create mode 100644 src/services/runtime/RuntimeSpecService.ts create mode 100644 src/services/test-helpers/responses/runtime/code789629.json create mode 100644 src/services/test-helpers/responses/runtime/spec.json create mode 100644 src/types/responses/MetadataCode.ts create mode 100644 src/types/responses/RuntimeSpec.ts diff --git a/openapi/openapi-proposal.yaml b/openapi/openapi-proposal.yaml index 889ce02a6..2f052f41b 100755 --- a/openapi/openapi-proposal.yaml +++ b/openapi/openapi-proposal.yaml @@ -979,7 +979,7 @@ paths: get: tags: - runtime - summary: Get version information of the runtime. + summary: Get version information of the Substrate runtime. description: Returns version information related to the runtime. parameters: - name: at @@ -1599,16 +1599,17 @@ components: - Live implVersion: type: string - description: Version of the implementation of the specification. Non-consensus-breaking + description: Version of the implementation specification. Non-consensus-breaking optimizations are about the only changes that could be made which would - result in only the `impl_version` changing. + result in only the `impl_version` changing. The `impl_version` is set to 0 + when `spec_version` is incremented. specName: type: string description: Identifies the different Substrate runtimes. specVersion: type: string - description: version of the runtime specification - txVersion: + description: Version of the runtime specification. + transactionVersion: type: string description: All existing dispatches are fully compatible when this number doesn't change. This number must change when an existing dispatchable diff --git a/src/controllers/runtime/RuntimeCodeController.ts b/src/controllers/runtime/RuntimeCodeController.ts new file mode 100644 index 000000000..d94d0ff34 --- /dev/null +++ b/src/controllers/runtime/RuntimeCodeController.ts @@ -0,0 +1,51 @@ +import { ApiPromise } from '@polkadot/api'; +import { RequestHandler } from 'express'; + +import { RuntimeCodeService } from '../../services'; +import AbstractController from '../AbstractController'; + +/** + * Get version information of the Substrate runtime. + * + * Query: + * - (Optional)`at`: Block at which to retrieve runtime version information at. Block + * identifier, as the block height or block hash. Defaults to most recent block. + * + * Returns: + * - `at`: Block number and hash at which the call was made. + * - `code`: Runtime code wasm blob. + */ +export default class RuntimeCodeController extends AbstractController< + RuntimeCodeService +> { + constructor(api: ApiPromise) { + super(api, '/runtime/code', new RuntimeCodeService(api)); + this.initRoutes(); + } + + protected initRoutes(): void { + this.safeMountAsyncGetHandlers([['', this.getCodeAtBlock]]); + } + + /** + * Get the chain's latest metadata in a decoded, JSON format. + * + * @param _req Express Request + * @param res Express Response + */ + + private getCodeAtBlock: RequestHandler = async ( + { query: { at } }, + res + ): Promise => { + const hash = + typeof at === 'string' + ? await this.getHashForBlock(at) + : await this.api.rpc.chain.getFinalizedHead(); + + RuntimeCodeController.sanitizedSend( + res, + await this.service.fetchCode(hash) + ); + }; +} diff --git a/src/controllers/runtime/RuntimeSpecController.ts b/src/controllers/runtime/RuntimeSpecController.ts new file mode 100644 index 000000000..ae511ce46 --- /dev/null +++ b/src/controllers/runtime/RuntimeSpecController.ts @@ -0,0 +1,55 @@ +import { ApiPromise } from '@polkadot/api'; +import { RequestHandler } from 'express'; + +import { RuntimeSpecService } from '../../services'; +import AbstractController from '../AbstractController'; + +/** + * Get version information of the Substrate runtime. + * + * Query: + * - (Optional)`at`: Block at which to retrieve runtime version information at. Block + * identifier, as the block height or block hash. Defaults to most recent block. + * + * Returns: + * - `at`: Block number and hash at which the call was made. + * - `authoringVersion`: The version of the authorship interface. An authoring node + * will not attempt to author blocks unless this is equal to its native runtime. + * - `chainType`: Type of the chain. + * - `implVersion`: Version of the implementation specification. Non-consensus-breaking + * optimizations are about the only changes that could be made which would + * result in only the `impl_version` changing. The `impl_version` is set to 0 + * when `spec_version` is incremented. + * - `specName`: Identifies different Substrate runtimes. + * - `specVersion`: Version of the runtime specification. + * - `transactionVersion`: All existing dispatches are fully compatible when this + * number doesn't change. This number must change when an existing dispatchable + * (module ID, dispatch ID) is changed, either through an alteration in its + * user-level semantics, a parameter added/removed/changed, a dispatchable + * its index. + * - `properties`: Arbitrary properties defined in the chain spec. + */ +export default class RuntimeSpecController extends AbstractController< + RuntimeSpecService +> { + constructor(api: ApiPromise) { + super(api, '/runtime/spec', new RuntimeSpecService(api)); + this.initRoutes(); + } + + protected initRoutes(): void { + this.safeMountAsyncGetHandlers([['', this.getSpec]]); + } + + private getSpec: RequestHandler = async ({ query: { at } }, res) => { + const hash = + typeof at === 'string' + ? await this.getHashForBlock(at) + : await this.api.rpc.chain.getFinalizedHead(); + + RuntimeSpecController.sanitizedSend( + res, + await this.service.fetchSpec(hash) + ); + }; +} diff --git a/src/controllers/runtime/index.ts b/src/controllers/runtime/index.ts index de4d63830..91694536f 100644 --- a/src/controllers/runtime/index.ts +++ b/src/controllers/runtime/index.ts @@ -1 +1,3 @@ export { default as Metadata } from './RuntimeMetadataController'; +export { default as RuntimeCode } from './RuntimeCodeController'; +export { default as RuntimeSpec } from './RuntimeSpecController'; diff --git a/src/main.ts b/src/main.ts index a0833297d..aaa32adb8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -66,6 +66,8 @@ async function main() { const stakingController = new controllers.PalletsStakingProgress(api); const vestingController = new controllers.AccountsVestingInfo(api); const metadataController = new controllers.Metadata(api); + const runtimeCodeController = new controllers.RuntimeCode(api); + const runtimeSpecController = new controllers.RuntimeSpec(api); const claimsController = new controllers.Claims(api); const txArtifactsController = new controllers.TransactionMaterial(api); const txFeeEstimateController = new controllers.TransactionFeeEstimate(api); @@ -81,6 +83,8 @@ async function main() { stakingController, vestingController, metadataController, + runtimeCodeController, + runtimeSpecController, claimsController, txArtifactsController, txFeeEstimateController, diff --git a/src/services/accounts/AccountsStakingInfoService.ts b/src/services/accounts/AccountsStakingInfoService.ts index bce591dda..fc39f45b8 100644 --- a/src/services/accounts/AccountsStakingInfoService.ts +++ b/src/services/accounts/AccountsStakingInfoService.ts @@ -23,7 +23,7 @@ export class AccountsStakingInfoService extends AbstractService { const at = { hash, - height: header.number.toNumber().toString(10), + height: header.number.unwrap().toString(10), }; if (controllerOption.isNone) { diff --git a/src/services/runtime/RuntimeCodeService.spec.ts b/src/services/runtime/RuntimeCodeService.spec.ts new file mode 100644 index 000000000..54bfe3ff3 --- /dev/null +++ b/src/services/runtime/RuntimeCodeService.spec.ts @@ -0,0 +1,18 @@ +import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers'; +import { blockHash789629, mockApi } from '../test-helpers/mock'; +import * as codeResponse from '../test-helpers/responses/runtime/code789629.json'; +import { RuntimeCodeService } from './RuntimeCodeService'; + +const runtimeCodeService = new RuntimeCodeService(mockApi); + +describe('RuntimeCodeService', () => { + describe('fetchCode', () => { + it('works when ApiPromise works', async () => { + expect( + sanitizeNumbers( + await runtimeCodeService.fetchCode(blockHash789629) + ) + ).toStrictEqual(codeResponse); + }); + }); +}); diff --git a/src/services/runtime/RuntimeCodeService.ts b/src/services/runtime/RuntimeCodeService.ts new file mode 100644 index 000000000..fca90773f --- /dev/null +++ b/src/services/runtime/RuntimeCodeService.ts @@ -0,0 +1,32 @@ +import { Option, Raw } from '@polkadot/types'; +import { BlockHash } from '@polkadot/types/interfaces'; +import { IMetadataCode } from 'src/types/responses'; + +import { AbstractService } from '../AbstractService'; + +// https://github.com/shawntabrizi/substrate-graph-benchmarks/blob/master/js/extensions/known-keys.js#L21 +export const CODE_KEY = '0x3a636f6465'; + +export class RuntimeCodeService extends AbstractService { + /** + * Fetch `Metadata` in decoded JSON form. + * + * @param hash `BlockHash` to make call at + */ + async fetchCode(hash: BlockHash): Promise { + const api = await this.ensureMeta(hash); + + const [code, { number }] = await Promise.all([ + api.rpc.state.getStorage(CODE_KEY, hash), + api.rpc.chain.getHeader(hash), + ]); + + return { + at: { + hash, + height: number.unwrap().toString(10), + }, + code: code as Option, + }; + } +} diff --git a/src/services/runtime/RuntimeSpecService.spec.ts b/src/services/runtime/RuntimeSpecService.spec.ts new file mode 100644 index 000000000..7aa5e7852 --- /dev/null +++ b/src/services/runtime/RuntimeSpecService.spec.ts @@ -0,0 +1,18 @@ +import { sanitizeNumbers } from '../../sanitize/sanitizeNumbers'; +import { blockHash789629, mockApi } from '../test-helpers/mock'; +import * as response from '../test-helpers/responses/runtime/spec.json'; +import { RuntimeSpecService } from './RuntimeSpecService'; + +const runtimeSpecService = new RuntimeSpecService(mockApi); + +describe('RuntimeSpecService', () => { + describe('fetchSpec', () => { + it('works when ApiPromise works', async () => { + expect( + sanitizeNumbers( + await runtimeSpecService.fetchSpec(blockHash789629) + ) + ).toStrictEqual(response); + }); + }); +}); diff --git a/src/services/runtime/RuntimeSpecService.ts b/src/services/runtime/RuntimeSpecService.ts new file mode 100644 index 000000000..ffae506d8 --- /dev/null +++ b/src/services/runtime/RuntimeSpecService.ts @@ -0,0 +1,33 @@ +import { BlockHash } from '@polkadot/types/interfaces'; +import { IRuntimeSpec } from 'src/types/responses'; + +import { AbstractService } from '../AbstractService'; +export class RuntimeSpecService extends AbstractService { + async fetchSpec(hash: BlockHash): Promise { + const [ + { + authoringVersion, + specName, + specVersion, + transactionVersion, + implVersion, + }, + chainType, + properties, + ] = await Promise.all([ + this.api.rpc.state.getRuntimeVersion(hash), + this.api.rpc.system.chainType(), + this.api.rpc.system.properties(), + ]); + + return { + authoringVersion, + transactionVersion, + implVersion, + specName, + specVersion, + chainType, + properties, + }; + } +} diff --git a/src/services/runtime/index.ts b/src/services/runtime/index.ts index 015841544..8354625a9 100644 --- a/src/services/runtime/index.ts +++ b/src/services/runtime/index.ts @@ -1 +1,3 @@ export * from './RuntimeMetadataService'; +export * from './RuntimeCodeService'; +export * from './RuntimeSpecService'; diff --git a/src/services/test-helpers/mock/mockApi.ts b/src/services/test-helpers/mock/mockApi.ts index fc4f53ea8..a59f7c191 100644 --- a/src/services/test-helpers/mock/mockApi.ts +++ b/src/services/test-helpers/mock/mockApi.ts @@ -47,6 +47,8 @@ const getRuntimeVersion = () => specName: polkadotRegistry.createType('Text', 'polkadot'), specVersion: polkadotRegistry.createType('u32', 16), transactionVersion: polkadotRegistry.createType('u32', 2), + authoringVersion: polkadotRegistry.createType('u32', 0), + implVersion: polkadotRegistry.createType('u32', 0), }; }); @@ -193,6 +195,27 @@ export const queryInfoBalancesTransfer = ( export const submitExtrinsic = (_extrinsic: string): Promise => Promise.resolve().then(() => polkadotRegistry.createType('Hash')); +const getStorage = () => + Promise.resolve().then(() => + polkadotRegistry.createType('Option', '0x') + ); + +const chainType = () => + Promise.resolve().then(() => + polkadotRegistry.createType('ChainType', { + Live: null, + }) + ); + +const properties = () => + Promise.resolve().then(() => + polkadotRegistry.createType('ChainProperties', { + ss58Format: '0', + tokenDecimals: '12', + tokenSymbol: 'DOT', + }) + ); + const getFinalizedHead = () => Promise.resolve().then(() => blockHash789629); export const tx = (): Extrinsic => @@ -275,9 +298,12 @@ export const mockApi = ({ state: { getRuntimeVersion, getMetadata, + getStorage, }, system: { chain, + chainType, + properties, }, payment: { queryInfo: queryInfoBalancesTransfer, diff --git a/src/services/test-helpers/responses/runtime/code789629.json b/src/services/test-helpers/responses/runtime/code789629.json new file mode 100644 index 000000000..63079a159 --- /dev/null +++ b/src/services/test-helpers/responses/runtime/code789629.json @@ -0,0 +1,7 @@ +{ + "at": { + "hash": "0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578", + "height": "789629" + }, + "code": "0x" +} \ No newline at end of file diff --git a/src/services/test-helpers/responses/runtime/spec.json b/src/services/test-helpers/responses/runtime/spec.json new file mode 100644 index 000000000..caf8920d3 --- /dev/null +++ b/src/services/test-helpers/responses/runtime/spec.json @@ -0,0 +1,15 @@ +{ + "authoringVersion": "0", + "transactionVersion": "2", + "implVersion": "0", + "specName": "polkadot", + "specVersion": "16", + "chainType": { + "Live": null + }, + "properties": { + "ss58Format": "0", + "tokenDecimals": "12", + "tokenSymbol": "DOT" + } +} \ No newline at end of file diff --git a/src/types/responses/MetadataCode.ts b/src/types/responses/MetadataCode.ts new file mode 100644 index 000000000..2e2472f2c --- /dev/null +++ b/src/types/responses/MetadataCode.ts @@ -0,0 +1,8 @@ +import { Option, Raw } from '@polkadot/types'; + +import { IAt } from '.'; + +export interface IMetadataCode { + at: IAt; + code: Option; +} diff --git a/src/types/responses/RuntimeSpec.ts b/src/types/responses/RuntimeSpec.ts new file mode 100644 index 000000000..cbd0968e7 --- /dev/null +++ b/src/types/responses/RuntimeSpec.ts @@ -0,0 +1,12 @@ +import { ChainProperties, ChainType } from '@polkadot/types/interfaces'; +import { Text, u32 } from '@polkadot/types/primitive'; + +export interface IRuntimeSpec { + authoringVersion: u32; + transactionVersion: u32; + implVersion: u32; + specName: Text; + specVersion: u32; + chainType: ChainType; + properties: ChainProperties; +} diff --git a/src/types/responses/index.ts b/src/types/responses/index.ts index 23dc008c2..8b84def95 100644 --- a/src/types/responses/index.ts +++ b/src/types/responses/index.ts @@ -9,3 +9,5 @@ export * from './AccountStakingInfo'; export * from './AccountVestingInfo'; export * from './TransactionMaterial'; export * from './Extrinsic'; +export * from './MetadataCode'; +export * from './RuntimeSpec'; From cf0ac4aef905940a42c01e7102ed41b622a8a7b3 Mon Sep 17 00:00:00 2001 From: Zeke Mostov <32168567+emostov@users.noreply.github.com> Date: Sat, 22 Aug 2020 12:22:24 -0700 Subject: [PATCH 2/5] Apply suggestions Joe from code review Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- src/controllers/runtime/RuntimeCodeController.ts | 2 +- src/controllers/runtime/RuntimeSpecController.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controllers/runtime/RuntimeCodeController.ts b/src/controllers/runtime/RuntimeCodeController.ts index d94d0ff34..02450c88b 100644 --- a/src/controllers/runtime/RuntimeCodeController.ts +++ b/src/controllers/runtime/RuntimeCodeController.ts @@ -13,7 +13,7 @@ import AbstractController from '../AbstractController'; * * Returns: * - `at`: Block number and hash at which the call was made. - * - `code`: Runtime code wasm blob. + * - `code`: Runtime code Wasm blob. */ export default class RuntimeCodeController extends AbstractController< RuntimeCodeService diff --git a/src/controllers/runtime/RuntimeSpecController.ts b/src/controllers/runtime/RuntimeSpecController.ts index ae511ce46..288241a72 100644 --- a/src/controllers/runtime/RuntimeSpecController.ts +++ b/src/controllers/runtime/RuntimeSpecController.ts @@ -8,7 +8,7 @@ import AbstractController from '../AbstractController'; * Get version information of the Substrate runtime. * * Query: - * - (Optional)`at`: Block at which to retrieve runtime version information at. Block + * - (Optional)`at`: Block at which to retrieve runtime version information. Block * identifier, as the block height or block hash. Defaults to most recent block. * * Returns: @@ -20,7 +20,7 @@ import AbstractController from '../AbstractController'; * optimizations are about the only changes that could be made which would * result in only the `impl_version` changing. The `impl_version` is set to 0 * when `spec_version` is incremented. - * - `specName`: Identifies different Substrate runtimes. + * - `specName`: Identifies the spec name for the current runtime. * - `specVersion`: Version of the runtime specification. * - `transactionVersion`: All existing dispatches are fully compatible when this * number doesn't change. This number must change when an existing dispatchable From fdd64629ef1198340ec5dc7b6763888c9f9292d4 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sat, 22 Aug 2020 12:42:05 -0700 Subject: [PATCH 3/5] Add eof --- src/services/test-helpers/responses/runtime/code789629.json | 2 +- src/services/test-helpers/responses/runtime/spec.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/test-helpers/responses/runtime/code789629.json b/src/services/test-helpers/responses/runtime/code789629.json index 63079a159..ed529e2a4 100644 --- a/src/services/test-helpers/responses/runtime/code789629.json +++ b/src/services/test-helpers/responses/runtime/code789629.json @@ -4,4 +4,4 @@ "height": "789629" }, "code": "0x" -} \ No newline at end of file +} diff --git a/src/services/test-helpers/responses/runtime/spec.json b/src/services/test-helpers/responses/runtime/spec.json index caf8920d3..e51551858 100644 --- a/src/services/test-helpers/responses/runtime/spec.json +++ b/src/services/test-helpers/responses/runtime/spec.json @@ -12,4 +12,4 @@ "tokenDecimals": "12", "tokenSymbol": "DOT" } -} \ No newline at end of file +} From 6349ff8a12ce1f807e0ea2f31e60b8f4c1a61fc4 Mon Sep 17 00:00:00 2001 From: Zeke Mostov Date: Sun, 23 Aug 2020 18:20:46 -0700 Subject: [PATCH 4/5] Update readme links --- README.md | 4 ++++ src/controllers/runtime/RuntimeCodeController.ts | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b867449ac..19251e05e 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,10 @@ a 32-byte hex string (`0x` followed by 64 hexadecimal digits) that denotes the b - [`/metadata/NUMBER` fetch chain metadata at the block identified by 'NUMBER`.](src/controllers/runtime/RuntimeMetadataController.ts) +- [`/runtime/code` fetch the Wasm code blob of the Substrate runtime.](src/controllers/runtime/RuntimeCodeController.ts) + +- [`/runtime/spec` version information of the Substrate runtime.](src/controllers/runtime/RuntimeSpecController.ts) + - [`/claims/ADDRESS` fetch claims data for an Ethereum `ADDRESS`.](src/controllers/claims/ClaimsController.ts) - [`/claims/ADDRESS/NUMBER` fetch claims data for an Ethereum `ADDRESS` at the block identified by 'NUMBER`.](src/controllers/claims/ClaimsController.ts) diff --git a/src/controllers/runtime/RuntimeCodeController.ts b/src/controllers/runtime/RuntimeCodeController.ts index 02450c88b..c5dfd9065 100644 --- a/src/controllers/runtime/RuntimeCodeController.ts +++ b/src/controllers/runtime/RuntimeCodeController.ts @@ -5,7 +5,7 @@ import { RuntimeCodeService } from '../../services'; import AbstractController from '../AbstractController'; /** - * Get version information of the Substrate runtime. + * Get the Wasm code blob of the Substrate runtime. * * Query: * - (Optional)`at`: Block at which to retrieve runtime version information at. Block From 31b8bebc14bbffa1433fa7479196d9b23816b05a Mon Sep 17 00:00:00 2001 From: Zeke Mostov <32168567+emostov@users.noreply.github.com> Date: Sun, 23 Aug 2020 23:27:23 -0700 Subject: [PATCH 5/5] Update src/services/runtime/RuntimeCodeService.ts Co-authored-by: joe petrowski <25483142+joepetrowski@users.noreply.github.com> --- src/services/runtime/RuntimeCodeService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/runtime/RuntimeCodeService.ts b/src/services/runtime/RuntimeCodeService.ts index fca90773f..e4bc60a53 100644 --- a/src/services/runtime/RuntimeCodeService.ts +++ b/src/services/runtime/RuntimeCodeService.ts @@ -4,7 +4,7 @@ import { IMetadataCode } from 'src/types/responses'; import { AbstractService } from '../AbstractService'; -// https://github.com/shawntabrizi/substrate-graph-benchmarks/blob/master/js/extensions/known-keys.js#L21 +// https://github.com/shawntabrizi/substrate-graph-benchmarks/blob/ae9b82f/js/extensions/known-keys.js#L21 export const CODE_KEY = '0x3a636f6465'; export class RuntimeCodeService extends AbstractService {