diff --git a/CHANGELOG.md b/CHANGELOG.md index 4852288c6e..83ca60031c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Changes: - Add `derive.chain.subscribeFinalized{Heads, Blocks}` which returns all finalized, no skips +- Support Moonbeam author extraction on `derive.chain.*` (w/ shared logic extraction) ## 8.12.2 Jul 10, 2022 diff --git a/packages/api-derive/src/chain/getBlock.ts b/packages/api-derive/src/chain/getBlock.ts index cf4e1fa104..92731cc2d5 100644 --- a/packages/api-derive/src/chain/getBlock.ts +++ b/packages/api-derive/src/chain/getBlock.ts @@ -9,6 +9,7 @@ import { combineLatest, map, of, switchMap } from 'rxjs'; import { createSignedBlockExtended } from '../type'; import { memo } from '../util'; +import { getAuthorDetails } from './util'; /** * @name getBlock @@ -27,19 +28,17 @@ export function getBlock (instanceId: string, api: DeriveApi): (hash: Uint8Array return memo(instanceId, (blockHash: Uint8Array | string): Observable => combineLatest([ api.rpc.chain.getBlock(blockHash), - api.queryAt(blockHash).pipe( - switchMap((queryAt) => - combineLatest([ - queryAt.system.events(), - queryAt.session - ? queryAt.session.validators() - : of([]) - ]) - ) - ) + api.queryAt(blockHash) ]).pipe( - map(([signedBlock, [events, validators]]) => - createSignedBlockExtended(events.registry, signedBlock, events, validators) + switchMap(([signedBlock, queryAt]) => + combineLatest([ + of(signedBlock), + queryAt.system.events(), + getAuthorDetails(signedBlock.block.header, queryAt) + ]) + ), + map(([signedBlock, events, [, validators, author]]) => + createSignedBlockExtended(events.registry, signedBlock, events, validators, author) ) ) ); diff --git a/packages/api-derive/src/chain/getHeader.ts b/packages/api-derive/src/chain/getHeader.ts index 41ee4f3129..24079d2044 100644 --- a/packages/api-derive/src/chain/getHeader.ts +++ b/packages/api-derive/src/chain/getHeader.ts @@ -2,14 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import type { Observable } from 'rxjs'; -import type { AccountId } from '@polkadot/types/interfaces'; import type { HeaderExtended } from '../type/types'; import type { DeriveApi } from '../types'; -import { combineLatest, map, of, switchMap } from 'rxjs'; +import { combineLatest, map, switchMap } from 'rxjs'; import { createHeaderExtended } from '../type'; import { memo } from '../util'; +import { getAuthorDetails } from './util'; /** * @name getHeader @@ -29,16 +29,14 @@ export function getHeader (instanceId: string, api: DeriveApi): (blockHash: Uint return memo(instanceId, (blockHash: Uint8Array | string): Observable => combineLatest([ api.rpc.chain.getHeader(blockHash), - api.queryAt(blockHash).pipe( - switchMap((queryAt) => - queryAt.session - ? queryAt.session.validators() - : of([] as AccountId[]) - ) - ) + api.queryAt(blockHash) ]).pipe( - map(([header, validators]) => - createHeaderExtended(header.registry, header, validators) + switchMap(([header, queryAt]) => + getAuthorDetails(header, queryAt) + ), + map(([header, validators, author]) => + createHeaderExtended((validators || header).registry, header, validators, author) ) - )); + ) + ); } diff --git a/packages/api-derive/src/chain/subscribeFinalizedHeads.ts b/packages/api-derive/src/chain/subscribeFinalizedHeads.ts index 0a3c81b8c9..e712af7b98 100644 --- a/packages/api-derive/src/chain/subscribeFinalizedHeads.ts +++ b/packages/api-derive/src/chain/subscribeFinalizedHeads.ts @@ -41,8 +41,7 @@ export function subscribeFinalizedHeads (instanceId: string, api: DeriveApi): () const endHash = prevHash; const startHash = header.parentHash; - prevHash = header.hash; - header.createdAtHash = prevHash; + prevHash = header.createdAtHash = header.hash; return endHash === null || startHash.eq(endHash) ? of(header) diff --git a/packages/api-derive/src/chain/subscribeNewBlocks.ts b/packages/api-derive/src/chain/subscribeNewBlocks.ts index dc429483f0..8da5ad9b66 100644 --- a/packages/api-derive/src/chain/subscribeNewBlocks.ts +++ b/packages/api-derive/src/chain/subscribeNewBlocks.ts @@ -5,9 +5,8 @@ import type { Observable } from 'rxjs'; import type { SignedBlockExtended } from '../type/types'; import type { DeriveApi } from '../types'; -import { combineLatest, map, of, switchMap } from 'rxjs'; +import { switchMap } from 'rxjs'; -import { createSignedBlockExtended } from '../type'; import { memo } from '../util'; /** @@ -17,22 +16,8 @@ import { memo } from '../util'; export function subscribeNewBlocks (instanceId: string, api: DeriveApi): () => Observable { return memo(instanceId, (): Observable => api.derive.chain.subscribeNewHeads().pipe( - switchMap((header) => { - const blockHash = header.createdAtHash || header.hash; - - // we get the block first, setting up the registry - return combineLatest([ - of(header), - api.rpc.chain.getBlock(blockHash), - api.queryAt(blockHash).pipe( - switchMap((queryAt) => - queryAt.system.events() - ) - ) - ]); - }), - map(([header, block, events]) => - createSignedBlockExtended(events.registry, block, events, header.validators) + switchMap((header) => + api.derive.chain.getBlock(header.createdAtHash || header.hash) ) ) ); diff --git a/packages/api-derive/src/chain/subscribeNewHeads.ts b/packages/api-derive/src/chain/subscribeNewHeads.ts index 4adca91d95..afdfbc9352 100644 --- a/packages/api-derive/src/chain/subscribeNewHeads.ts +++ b/packages/api-derive/src/chain/subscribeNewHeads.ts @@ -5,10 +5,11 @@ import type { Observable } from 'rxjs'; import type { HeaderExtended } from '../type/types'; import type { DeriveApi } from '../types'; -import { combineLatest, map, of } from 'rxjs'; +import { combineLatest, map, of, switchMap } from 'rxjs'; import { createHeaderExtended } from '../type'; import { memo } from '../util'; +import { getAuthorDetails } from './util'; /** * @name subscribeNewHeads @@ -25,16 +26,20 @@ import { memo } from '../util'; */ export function subscribeNewHeads (instanceId: string, api: DeriveApi): () => Observable { return memo(instanceId, (): Observable => - combineLatest([ - api.rpc.chain.subscribeNewHeads(), - api.query.session - ? api.query.session.validators() - : of(undefined) - ]).pipe( - map(([header, validators]): HeaderExtended => { + api.rpc.chain.subscribeNewHeads().pipe( + switchMap((header) => + combineLatest([ + of(header), + api.queryAt(header.hash) + ]) + ), + switchMap(([header, queryAt]) => + getAuthorDetails(header, queryAt) + ), + map(([header, validators, author]): HeaderExtended => { header.createdAtHash = header.hash; - return createHeaderExtended(header.registry, header, validators); + return createHeaderExtended(header.registry, header, validators, author); }) ) ); diff --git a/packages/api-derive/src/chain/util.ts b/packages/api-derive/src/chain/util.ts index ad380e1b79..0e011c4fbb 100644 --- a/packages/api-derive/src/chain/util.ts +++ b/packages/api-derive/src/chain/util.ts @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import type { Observable } from 'rxjs'; -import type { Compact } from '@polkadot/types'; -import type { BlockNumber } from '@polkadot/types/interfaces'; +import type { QueryableStorage } from '@polkadot/api-base/types'; +import type { Compact, Vec } from '@polkadot/types'; +import type { AccountId, BlockNumber, Header } from '@polkadot/types/interfaces'; +import type { Codec, IOption } from '@polkadot/types/types'; import type { DeriveApi } from '../types'; -import { map } from 'rxjs'; +import { combineLatest, map, of } from 'rxjs'; import { memo } from '../util'; @@ -20,3 +22,36 @@ export function unwrapBlockNumber }> ( map((r) => r.number.unwrap()) )); } + +export function getAuthorDetails (header: Header, queryAt: QueryableStorage<'rxjs'>): Observable<[Header, Vec | null, AccountId | null]> { + // this is Moonbeam specific + if (queryAt.authorMapping && queryAt.authorMapping.mappingWithDeposit) { + const mapId = header.digest.logs[0] && ( + (header.digest.logs[0].isConsensus && header.digest.logs[0].asConsensus[1]) || + (header.digest.logs[0].isPreRuntime && header.digest.logs[0].asPreRuntime[1]) + ); + + if (mapId) { + return combineLatest([ + of(header), + queryAt.session + ? queryAt.session.validators() + : of(null), + queryAt.authorMapping.mappingWithDeposit>(mapId).pipe( + map((opt) => + opt.unwrapOr({ account: null }).account + ) + ) + ]); + } + } + + // normal operation, non-mapping + return combineLatest([ + of(header), + queryAt.session + ? queryAt.session.validators() + : of(null), + of(null) + ]); +} diff --git a/packages/api-derive/src/type/HeaderExtended.ts b/packages/api-derive/src/type/HeaderExtended.ts index d2d9c41e70..83a8bf4624 100644 --- a/packages/api-derive/src/type/HeaderExtended.ts +++ b/packages/api-derive/src/type/HeaderExtended.ts @@ -7,19 +7,17 @@ import type { HeaderExtended } from './types'; import { extractAuthor } from './util'; -export function createHeaderExtended (registry: Registry, header?: Header, validators?: AccountId[]): HeaderExtended { +export function createHeaderExtended (registry: Registry, header?: Header, validators?: AccountId[] | null, author?: AccountId | null): HeaderExtended { // an instance of the base extrinsic for us to extend const HeaderBase = registry.createClass('Header'); class Implementation extends HeaderBase implements HeaderExtended { readonly #author?: AccountId; - readonly #validators?: AccountId[]; - constructor (registry: Registry, header?: Header, validators?: AccountId[]) { + constructor (registry: Registry, header?: Header, validators?: AccountId[] | null, author?: AccountId | null) { super(registry, header); - this.#author = extractAuthor(this.digest, validators); - this.#validators = validators; + this.#author = author || extractAuthor(this.digest, validators || []); this.createdAtHash = header?.createdAtHash; } @@ -29,14 +27,7 @@ export function createHeaderExtended (registry: Registry, header?: Header, valid public get author (): AccountId | undefined { return this.#author; } - - /** - * @description Convenience method, returns the validators for the block - */ - public get validators (): AccountId[] | undefined { - return this.#validators; - } } - return new Implementation(registry, header, validators); + return new Implementation(registry, header, validators, author); } diff --git a/packages/api-derive/src/type/SignedBlockExtended.ts b/packages/api-derive/src/type/SignedBlockExtended.ts index 8ab3f2047a..878a891392 100644 --- a/packages/api-derive/src/type/SignedBlockExtended.ts +++ b/packages/api-derive/src/type/SignedBlockExtended.ts @@ -31,7 +31,7 @@ function mapExtrinsics (extrinsics: Extrinsic[], records: EventRecord[]): TxWith }); } -export function createSignedBlockExtended (registry: Registry, block?: SignedBlock, events?: EventRecord[], validators?: AccountId[]): SignedBlockExtended { +export function createSignedBlockExtended (registry: Registry, block?: SignedBlock, events?: EventRecord[], validators?: AccountId[] | null, author?: AccountId | null): SignedBlockExtended { // an instance of the base extrinsic for us to extend const SignedBlockBase = registry.createClass('SignedBlock'); @@ -40,10 +40,10 @@ export function createSignedBlockExtended (registry: Registry, block?: SignedBlo readonly #events: EventRecord[]; readonly #extrinsics: TxWithEvent[]; - constructor (registry: Registry, block?: SignedBlock, events?: EventRecord[], validators?: AccountId[]) { + constructor (registry: Registry, block?: SignedBlock, events?: EventRecord[], validators?: AccountId[] | null, author?: AccountId | null) { super(registry, block); - this.#author = extractAuthor(this.block.header.digest, validators); + this.#author = author || extractAuthor(this.block.header.digest, validators || []); this.#events = events || ([] as EventRecord[]); this.#extrinsics = mapExtrinsics(this.block.extrinsics, this.#events); this.createdAtHash = block?.createdAtHash; @@ -71,5 +71,5 @@ export function createSignedBlockExtended (registry: Registry, block?: SignedBlo } } - return new Implementation(registry, block, events, validators); + return new Implementation(registry, block, events, validators, author); } diff --git a/packages/api-derive/src/type/types.ts b/packages/api-derive/src/type/types.ts index 03b8a277d7..3e6460ec81 100644 --- a/packages/api-derive/src/type/types.ts +++ b/packages/api-derive/src/type/types.ts @@ -5,7 +5,6 @@ import type { AccountId, DispatchError, DispatchInfo, Event, EventRecord, Extrin export interface HeaderExtended extends Header { readonly author: AccountId | undefined; - readonly validators: AccountId[] | undefined; } export interface SignedBlockExtended extends SignedBlock {