diff --git a/CHANGELOG.md b/CHANGELOG.md index b59aa5e3c1..0f03888002 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## 1.32.0-beta.x + +Changes: + +- Added `derive.chain.getBlock(hash)` to retrieve a `SignedBlock` extended with an `.author` (same as `derive.chain.getHeader(...)`) + + ## 1.31.1 Aug 31, 2020 Upgrade priority: Medium, especially for staking users on Polkadot, Kusama & Westend (type updates applied for v23 onwards) or if transparent `.at(hash)` queries are required/used diff --git a/packages/api-derive/src/chain/getBlock.ts b/packages/api-derive/src/chain/getBlock.ts new file mode 100644 index 0000000000..156124d4e0 --- /dev/null +++ b/packages/api-derive/src/chain/getBlock.ts @@ -0,0 +1,44 @@ +// Copyright 2017-2020 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { ApiInterfaceRx } from '@polkadot/api/types'; + +import { Observable, combineLatest, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; + +import { SignedBlockExtended } from '../type'; +import { memo } from '../util'; + +/** + * @name getBlock + * @param {( Uint8Array | string )} hash - A block hash as U8 array or string. + * @description Get a specific block (e.g. rpc.chain.getBlock) and extend it with the author + * @example + *
+ * + * ```javascript + * const { author, number } = await api.derive.chain.getHeader('0x123...456'); + * + * console.log(`block #${number} was authored by ${author}`); + * ``` + */ +export function getHeader (api: ApiInterfaceRx): (hash: Uint8Array | string) => Observable { + return memo((hash: Uint8Array | string): Observable => + combineLatest([ + api.rpc.chain.getBlock(hash), + api.query.session + ? api.query.session.validators.at(hash) + : of([]) + ]).pipe( + map(([signedBlock, validators]): SignedBlockExtended => + new SignedBlockExtended(api.registry, signedBlock, validators) + ), + catchError((): Observable => + // where rpc.chain.getHeader throws, we will land here - it can happen that + // we supplied an invalid hash. (Due to defaults, storeage will have an + // empty value, so only the RPC is affected). So return undefined + of() + ) + )); +} diff --git a/packages/api-derive/src/type/HeaderExtended.ts b/packages/api-derive/src/type/HeaderExtended.ts index 7b0b8869cc..06c7980986 100644 --- a/packages/api-derive/src/type/HeaderExtended.ts +++ b/packages/api-derive/src/type/HeaderExtended.ts @@ -8,6 +8,8 @@ import { AnyJson, Constructor, Registry } from '@polkadot/types/types'; import runtimeTypes from '@polkadot/types/interfaces/runtime/definitions'; import { Struct } from '@polkadot/types'; +import { extractAuthor } from './util'; + // We can ignore the properties, added via Struct.with const _Header = Struct.with(runtimeTypes.types.Header as any) as Constructor
; @@ -22,29 +24,7 @@ export default class HeaderExtended extends _Header { constructor (registry: Registry, header?: Header, sessionValidators?: AccountId[]) { super(registry, header); - this.#author = this._extractAuthor(sessionValidators); - } - - private _extractAuthor (sessionValidators: AccountId[] = []): AccountId | undefined { - const [pitem] = this.digest.logs.filter(({ type }) => type === 'PreRuntime'); - - // extract from the substrate 2.0 PreRuntime digest - if (pitem) { - const [engine, data] = pitem.asPreRuntime; - - return engine.extractAuthor(data, sessionValidators); - } else { - const [citem] = this.digest.logs.filter(({ type }) => type === 'Consensus'); - - // extract author from the consensus (substrate 1.0, digest) - if (citem) { - const [engine, data] = citem.asConsensus; - - return engine.extractAuthor(data, sessionValidators); - } - } - - return undefined; + this.#author = extractAuthor(this.digest, sessionValidators); } /** diff --git a/packages/api-derive/src/type/SignedBlockExtended.ts b/packages/api-derive/src/type/SignedBlockExtended.ts new file mode 100644 index 0000000000..42bb850837 --- /dev/null +++ b/packages/api-derive/src/type/SignedBlockExtended.ts @@ -0,0 +1,60 @@ +// Copyright 2017-2020 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { AccountId, SignedBlock } from '@polkadot/types/interfaces'; +import { AnyJson, Constructor, Registry } from '@polkadot/types/types'; + +import runtimeTypes from '@polkadot/types/interfaces/runtime/definitions'; +import { Struct } from '@polkadot/types'; + +import { extractAuthor } from './util'; + +// We can ignore the properties, added via Struct.with +const _SignedBlock = Struct.with(runtimeTypes.types.SignedBlock as any) as Constructor; + +/** + * @name SignedBlockExtended + * @description + * A [[Block]] header with an additional `author` field that indicates the block author + */ +export default class BlockExtended extends _SignedBlock { + readonly #author?: AccountId; + + constructor (registry: Registry, block?: SignedBlock, sessionValidators?: AccountId[]) { + super(registry, block); + + this.#author = extractAuthor(this.block.header.digest, sessionValidators); + } + + /** + * @description Convenience method, returns the author for the block + */ + public get author (): AccountId | undefined { + return this.#author; + } + + /** + * @description Creates a human-friendly JSON representation + */ + public toHuman (isExtended?: boolean): AnyJson { + return { + ...super.toHuman(isExtended) as { [index: string]: AnyJson }, + author: this.author + ? this.author.toHuman() + : undefined + }; + } + + /** + * @description Creates the JSON representation + */ + public toJSON (): AnyJson { + return { + ...super.toJSON() as { [index: string]: AnyJson }, + author: this.author + ? this.author.toJSON() + : undefined + }; + } +} diff --git a/packages/api-derive/src/type/index.ts b/packages/api-derive/src/type/index.ts index 0040bfc0e3..bc4c675114 100644 --- a/packages/api-derive/src/type/index.ts +++ b/packages/api-derive/src/type/index.ts @@ -3,3 +3,4 @@ // of the Apache-2.0 license. See the LICENSE file for details. export { default as HeaderExtended } from './HeaderExtended'; +export { default as SignedBlockExtended } from './SignedBlockExtended'; diff --git a/packages/api-derive/src/type/util.ts b/packages/api-derive/src/type/util.ts new file mode 100644 index 0000000000..68db9e19e0 --- /dev/null +++ b/packages/api-derive/src/type/util.ts @@ -0,0 +1,27 @@ +// Copyright 2017-2020 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { AccountId, Digest } from '@polkadot/types/interfaces'; + +export function extractAuthor (digest: Digest, sessionValidators: AccountId[] = []): AccountId | undefined { + const [pitem] = digest.logs.filter(({ type }) => type === 'PreRuntime'); + + // extract from the substrate 2.0 PreRuntime digest + if (pitem) { + const [engine, data] = pitem.asPreRuntime; + + return engine.extractAuthor(data, sessionValidators); + } else { + const [citem] = digest.logs.filter(({ type }) => type === 'Consensus'); + + // extract author from the consensus (substrate 1.0, digest) + if (citem) { + const [engine, data] = citem.asConsensus; + + return engine.extractAuthor(data, sessionValidators); + } + } + + return undefined; +}