From b95f9131ea249638a62f3f5dba8b82ee6ee95c0e Mon Sep 17 00:00:00 2001 From: Tarik Gul <47201679+TarikGul@users.noreply.github.com> Date: Tue, 19 Jan 2021 23:17:31 -0500 Subject: [PATCH] feat: add finalized tag when querying blocks (#386) * Import Compact, BlockNumber Types: Create isFinalizedBlock method * Add finalized tag type * Cleanup isFinalizedBlock, add comments, add finalized tag * Run lint --fix * Parallelize rpc query * Update isFinalized to account for fork edgecase * Refactor promises, and lint * Optimize and refactor rpc calls * Update blocks controller to accomodate fetchBlock params * Refactor initial Promise.all() * Update fetchBlock in test suites to fit updated params * Add finalized tag with boolean tru * Mock data for testing queried hashs on forks * Add tests for isFinalizedBlock: (2 tests, one is a queried hash is on a fork, and another to confirm a finalized block) * export mock json data * Run lint --fix * Update grammar * Update src/services/blocks/BlocksService.spec.ts Co-authored-by: David * Update src/services/blocks/BlocksService.ts Co-authored-by: David * Update src/services/blocks/BlocksService.spec.ts Co-authored-by: David * Update src/services/blocks/BlocksService.ts Co-authored-by: David * Update src/services/blocks/BlocksService.ts Co-authored-by: David * Resolve comment formatting * Update src/controllers/blocks/BlocksController.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update params for fetchBlock to an options object * BlockService resolve merge conflicts * More merge conflicts resolved * Revert changes * Remove BlockNumber * Revert test * fix: Conflicts resolved, and up to date with master * feat: omit finalized tag when running against a PoW chain * update: update the docs * feat: add testing for omiting the finalized tag * DRY test suite * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * fix: lint * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: David * Update src/controllers/blocks/BlocksController.ts Co-authored-by: David * fix: omitFinalizeTag => omitFinalizedTag * fix: DRY finalized * fix: check for undefined finalizedHeadBlockNumber * Update docs * fix: docs, and update the finalized description * Update src/controllers/blocks/BlocksController.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * fix: DRY test code * Update src/services/blocks/BlocksService.ts Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> * fix: docs on interface * fix: lint * fix: queryFinalized should be false * fix: queryFinalized should be false fix: when querying for blockId and on a PoW chain omit finalized tag * fix: lint Co-authored-by: David Co-authored-by: Zeke Mostov <32168567+emostov@users.noreply.github.com> --- docs/src/openapi-v1.yaml | 5 + src/controllers/blocks/BlocksController.ts | 55 ++++++-- src/services/blocks/BlocksService.spec.ts | 100 ++++++++++++- src/services/blocks/BlocksService.ts | 132 +++++++++++++++++- .../mock/data/blocks789629Fork.json | 32 +++++ .../test-helpers/mock/mockBlock789629.ts | 9 ++ .../responses/blocks/blocks789629.json | 3 +- src/types/responses/Block.ts | 1 + 8 files changed, 315 insertions(+), 22 deletions(-) create mode 100644 src/services/test-helpers/mock/data/blocks789629Fork.json diff --git a/docs/src/openapi-v1.yaml b/docs/src/openapi-v1.yaml index 2b0c1afc3..2ed5e63ec 100755 --- a/docs/src/openapi-v1.yaml +++ b/docs/src/openapi-v1.yaml @@ -1094,6 +1094,11 @@ components: $ref: '#/components/schemas/Extrinsic' onFinalize: $ref: '#/components/schemas/BlockFinalize' + finalized: + type: boolean + description: >- + A boolean identifying whether the block is finalized or not. + Note: on chains that do not have deterministic finality this field is omitted. description: >- Note: Block finalization does not correspond to consensus, i.e. whether the block is in the canonical chain. It denotes the finalization of block diff --git a/src/controllers/blocks/BlocksController.ts b/src/controllers/blocks/BlocksController.ts index ca95a9ba2..cef3eded6 100644 --- a/src/controllers/blocks/BlocksController.ts +++ b/src/controllers/blocks/BlocksController.ts @@ -1,4 +1,5 @@ import { ApiPromise } from '@polkadot/api'; +import { isHex } from '@polkadot/util'; import { RequestHandler } from 'express'; import { BlocksService } from '../../services'; @@ -92,16 +93,36 @@ export default class BlocksController extends AbstractController res ) => { const eventDocsArg = eventDocs === 'true'; - const extrsinsicDocsArg = extrinsicDocs === 'true'; + const extrinsicDocsArg = extrinsicDocs === 'true'; - const hash = - finalized === 'false' || !this.options.finalizes - ? (await this.api.rpc.chain.getHeader()).hash - : await this.api.rpc.chain.getFinalizedHead(); + let hash, queryFinalizedHead, omitFinalizedTag; + + // If the network chain doesn't finalize blocks, we dont want a finalized tag. + if (!this.options.finalizes) { + omitFinalizedTag = true; + queryFinalizedHead = false; + hash = (await this.api.rpc.chain.getHeader()).hash; + } else if (finalized === 'false') { + omitFinalizedTag = false; + queryFinalizedHead = true; + hash = (await this.api.rpc.chain.getHeader()).hash; + } else { + omitFinalizedTag = false; + queryFinalizedHead = false; + hash = await this.api.rpc.chain.getFinalizedHead(); + } + + const options = { + eventDocs: eventDocsArg, + extrinsicDocs: extrinsicDocsArg, + checkFinalized: false, + queryFinalizedHead, + omitFinalizedTag, + }; BlocksController.sanitizedSend( res, - await this.service.fetchBlock(hash, eventDocsArg, extrsinsicDocsArg) + await this.service.fetchBlock(hash, options) ); }; @@ -115,18 +136,28 @@ export default class BlocksController extends AbstractController { params: { number }, query: { eventDocs, extrinsicDocs } }, res ): Promise => { + const checkFinalized = isHex(number); + const hash = await this.getHashForBlock(number); const eventDocsArg = eventDocs === 'true'; - const extrinsinsicDocsArg = extrinsicDocs === 'true'; + const extrinsicDocsArg = extrinsicDocs === 'true'; + + const queryFinalizedHead = !this.options.finalizes ? false : true; + const omitFinalizedTag = !this.options.finalizes ? true : false; + + const options = { + eventDocs: eventDocsArg, + extrinsicDocs: extrinsicDocsArg, + checkFinalized, + queryFinalizedHead, + omitFinalizedTag, + }; + // We set the last param to true because we haven't queried the finalizedHead BlocksController.sanitizedSend( res, - await this.service.fetchBlock( - hash, - eventDocsArg, - extrinsinsicDocsArg - ) + await this.service.fetchBlock(hash, options) ); }; } diff --git a/src/services/blocks/BlocksService.spec.ts b/src/services/blocks/BlocksService.spec.ts index f392c208a..a28bf1f04 100644 --- a/src/services/blocks/BlocksService.spec.ts +++ b/src/services/blocks/BlocksService.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-unsafe-call */ +import { ApiPromise } from '@polkadot/api'; import { RpcPromiseResult } from '@polkadot/api/types/rpc'; import { GenericExtrinsic } from '@polkadot/types'; import { GenericCall } from '@polkadot/types/generic'; @@ -15,6 +16,7 @@ import { getBlock, mockApi, mockBlock789629, + mockForkedBlock789629, } from '../test-helpers/mock'; import * as block789629 from '../test-helpers/mock/data/block789629.json'; import * as blocks789629Response from '../test-helpers/responses/blocks/blocks789629.json'; @@ -35,9 +37,18 @@ const blocksService = new BlocksService(mockApi); describe('BlocksService', () => { describe('fetchBlock', () => { it('works when ApiPromise works (block 789629)', async () => { + // fetchBlock options + const options = { + eventDocs: true, + extrinsicDocs: true, + checkFinalized: false, + queryFinalizedHead: false, + omitFinalizedTag: false, + }; + expect( sanitizeNumbers( - await blocksService.fetchBlock(blockHash789629, true, true) + await blocksService.fetchBlock(blockHash789629, options) ) ).toMatchObject(blocks789629Response); }); @@ -48,11 +59,22 @@ describe('BlocksService', () => { 'Block', block789629 ); + mockBlock789629BadExt.extrinsics.pop(); + mockBlock789629BadExt.extrinsics.unshift( (undefined as unknown) as GenericExtrinsic ); + // fetchBlock Options + const options = { + eventDocs: false, + extrinsicDocs: false, + checkFinalized: false, + queryFinalizedHead: false, + omitFinalizedTag: false, + }; + mockApi.rpc.chain.getBlock = (() => Promise.resolve().then(() => { return { @@ -61,7 +83,7 @@ describe('BlocksService', () => { }) as unknown) as GetBlock; await expect( - blocksService.fetchBlock(blockHash789629, false, false) + blocksService.fetchBlock(blockHash789629, options) ).rejects.toThrow( new Error( `Cannot destructure property 'method' of 'extrinsic' as it is undefined.` @@ -70,6 +92,24 @@ describe('BlocksService', () => { mockApi.rpc.chain.getBlock = (getBlock as unknown) as GetBlock; }); + + it('Returns the finalized tag as undefined when omitFinalizedTag equals true', async () => { + // fetchBlock options + const options = { + eventDocs: true, + extrinsicDocs: true, + checkFinalized: false, + queryFinalizedHead: false, + omitFinalizedTag: true, + }; + + const block = await blocksService.fetchBlock( + blockHash789629, + options + ); + + expect(block.finalized).toEqual(undefined); + }); }); describe('createCalcFee & calc_fee', () => { @@ -262,4 +302,60 @@ describe('BlocksService', () => { ); }); }); + + describe('BlockService.isFinalizedBlock', () => { + const finalizedHead = polkadotRegistry.createType( + 'BlockHash', + '0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3' + ); + + const blockNumber = polkadotRegistry.createType( + 'Compact', + 789629 + ); + + it('Returns false when queried blockId is not canonical', async () => { + const getHeader = (_hash: Hash) => + Promise.resolve().then(() => mockForkedBlock789629.header); + + const getBlockHash = (_zero: number) => + Promise.resolve().then(() => finalizedHead); + + const forkMockApi = { + rpc: { + chain: { + getHeader, + getBlockHash, + }, + }, + } as ApiPromise; + + const queriedHash = polkadotRegistry.createType( + 'BlockHash', + '0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578' + ); + + expect( + await blocksService['isFinalizedBlock']( + forkMockApi, + blockNumber, + queriedHash, + finalizedHead, + true + ) + ).toEqual(false); + }); + + it('Returns true when queried blockId is canonical', async () => { + expect( + await blocksService['isFinalizedBlock']( + mockApi, + blockNumber, + finalizedHead, + finalizedHead, + true + ) + ).toEqual(true); + }); + }); }); diff --git a/src/services/blocks/BlocksService.ts b/src/services/blocks/BlocksService.ts index bf42d44e5..ac5396ac1 100644 --- a/src/services/blocks/BlocksService.ts +++ b/src/services/blocks/BlocksService.ts @@ -1,11 +1,12 @@ import { ApiPromise } from '@polkadot/api'; import { expandMetadata } from '@polkadot/metadata/decorate'; -import { GenericCall, Struct } from '@polkadot/types'; +import { Compact, GenericCall, Struct } from '@polkadot/types'; import { AbstractInt } from '@polkadot/types/codec/AbstractInt'; import { AccountId, Block, BlockHash, + BlockNumber, BlockWeights, Digest, DispatchInfo, @@ -28,6 +29,22 @@ import { import { isPaysFee } from '../../types/util'; import { AbstractService } from '../AbstractService'; +/** + * Types for fetchBlock's options + * @field eventDocs + * @field extrinsicDocs + * @field checkFinalized Option to reduce rpc calls. Equals true when blockId is a hash. + * @field queryFinalizedHead Option to reduce rpc calls. Equals true when finalized head has not been queried. + * @field omitFinalizedTag Option to omit the finalized tag, and return it as undefined. + */ +interface FetchBlockOptions { + eventDocs: boolean; + extrinsicDocs: boolean; + checkFinalized: boolean; + queryFinalizedHead: boolean; + omitFinalizedTag: boolean; +} + /** * Event methods that we check for. */ @@ -38,28 +55,44 @@ enum Event { export class BlocksService extends AbstractService { /** - * Fetch a block enhanced with augmented and derived values. + * Fetch a block augmented with derived values. * * @param hash `BlockHash` of the block to fetch. */ async fetchBlock( hash: BlockHash, - eventDocs: boolean, - extrinsicDocs: boolean + { + eventDocs, + extrinsicDocs, + checkFinalized, + queryFinalizedHead, + omitFinalizedTag, + }: FetchBlockOptions ): Promise { const { api } = this; - let block, events, sessionValidators; + let block, events, finalizedHead, sessionValidators; if (typeof api.query.session?.validators?.at === 'function') { - [{ block }, events, sessionValidators] = await Promise.all([ + [ + { block }, + events, + sessionValidators, + finalizedHead, + ] = await Promise.all([ api.rpc.chain.getBlock(hash), this.fetchEvents(api, hash), api.query.session.validators.at(hash), + queryFinalizedHead + ? api.rpc.chain.getFinalizedHead() + : Promise.resolve(hash), ]); } else { - [{ block }, events] = await Promise.all([ + [{ block }, events, finalizedHead] = await Promise.all([ api.rpc.chain.getBlock(hash), this.fetchEvents(api, hash), + queryFinalizedHead + ? api.rpc.chain.getFinalizedHead() + : Promise.resolve(hash), ]); } @@ -92,6 +125,19 @@ export class BlocksService extends AbstractService { eventDocs ); + let finalized = undefined; + + if (!omitFinalizedTag) { + // Check if the requested block is finalized + finalized = await this.isFinalizedBlock( + api, + number, + hash, + finalizedHead, + checkFinalized + ); + } + // The genesis block is a special case with little information associated with it. if (parentHash.every((byte) => !byte)) { return { @@ -105,6 +151,7 @@ export class BlocksService extends AbstractService { onInitialize, extrinsics, onFinalize, + finalized, }; } @@ -198,6 +245,7 @@ export class BlocksService extends AbstractService { onInitialize, extrinsics, onFinalize, + finalized, }; } @@ -576,4 +624,74 @@ export class BlocksService extends AbstractService { return undefined; } + + /** + * When querying a block this will immediately inform the request whether + * or not the queried block is considered finalized at the time of querying. + * + * @param api ApiPromise to use for query + * @param blockNumber Queried block number + * @param queriedHash Hash of user queried block + * @param finalizedHead Finalized head for our chain + * @param checkFinalized If the passed in blockId is a hash + */ + private async isFinalizedBlock( + api: ApiPromise, + blockNumber: Compact, + queriedHash: BlockHash, + finalizedHead: BlockHash, + checkFinalized: boolean + ): Promise { + if (checkFinalized) { + // The blockId url param is a hash + const [finalizedHeadBlock, canonHash] = await Promise.all([ + // Returns the header of the most recently finalized block + api.rpc.chain.getHeader(finalizedHead), + // Fetch the hash of the block with equal height on the canon chain. + // N.B. We assume when we query by number <= finalized head height, + // we will always get a block on the finalized, canonical chain. + api.rpc.chain.getBlockHash(blockNumber.unwrap()), + ]); + + // If queried by hash this is the original request param + const hash = queriedHash.toHex(); + + // If this conditional is satisfied, the queried hash is on a fork, + // and is not on the canonical chain and therefore not finalized + if (canonHash.toHex() !== hash) { + return false; + } + + // Retreive the finalized head blockNumber + const finalizedHeadBlockNumber = finalizedHeadBlock?.number; + + // If the finalized head blockNumber is undefined return false + if (!finalizedHeadBlockNumber) { + return false; + } + + // Check if the user's block is less than or equal to the finalized head. + // If so, the user's block is finalized. + return blockNumber.unwrap().lte(finalizedHeadBlockNumber.unwrap()); + } else { + // The blockId url param is an integer + + // Returns the header of the most recently finalized block + const finalizedHeadBlock = await api.rpc.chain.getHeader( + finalizedHead + ); + + // Retreive the finalized head blockNumber + const finalizedHeadBlockNumber = finalizedHeadBlock?.number; + + // If the finalized head blockNumber is undefined return false + if (!finalizedHeadBlockNumber) { + return false; + } + + // Check if the user's block is less than or equal to the finalized head. + // If so, the user's block is finalized. + return blockNumber.unwrap().lte(finalizedHeadBlockNumber.unwrap()); + } + } } diff --git a/src/services/test-helpers/mock/data/blocks789629Fork.json b/src/services/test-helpers/mock/data/blocks789629Fork.json new file mode 100644 index 000000000..48c4edf2e --- /dev/null +++ b/src/services/test-helpers/mock/data/blocks789629Fork.json @@ -0,0 +1,32 @@ +{ + "header": { + "hash": "0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3", + "parentHash": "0xfbbdd075ab413168a83dbb7108f4e75516979b0acf40e691bbdcb5706f419be6", + "number": 789629, + "stateRoot": "0x2ff5d2e2e90ea7af2aca2c25ec1f79643bae368cf7e0fc1cd4414ff31cf39d19", + "extrinsicsRoot": "0x4c1d65bf6b57086f00d5df40aa0686ffbc581ef60878645613b1fc3303de5030", + "digest": { + "logs": [ + { + "PreRuntime": [ + 1161969986, + "0x036d000000f4f4d80f00000000ec9bd8e2d0368c97f3d888837f7283bbe08266869eb613159db547905026c2502a70f168b9ffcc233344005d11ebecd166769200d270a2eaa642118a00acb708a0487a440b0caf3dd5c91ab173e80ddfe5735ef8b938ea87a6105a1161612707" + ] + }, + { + "Seal": [ + 1161969986, + "0xae78514e1de84a7d32e55b9b652f9d408ab1f7b4bfdbf6b2fad9cad94a91b86b0161cabf08f5ae1d3a1aa4993e2d96d56c94b03cee0898ccb8385a546084f88b" + ] + } + ] + } + }, + "extrinsics": [ + "0x280403000bc016ed6c7301", + "0x1c040a00ea313000", + "0x1004140000", + "0x75138486bacff9e50488125f449229ffa6767a36ab06a48b04e41c70cc7e6d82359d7d015e4a0ffba337ce7ad6e334a8199e9e574355baf90253fc216ae07fecec4b2b33526fa0e338531fec0c8178fb2335e8ca0015c7ff3eb3edf42dab16665706008585034c001a007807128889bb12ffc22c93e6190aeac259184d7181bed3f0cc9938d27315f8e61c8c4a2c0000000712040fddcb4b5b6707697e2431f7330ee99372e2a55b955bf7b93f8a853d07f10f2c0000000712f4164fa5fd5aa70d2d524d72d9e17d16a56946c3b9fa97d03d2aa2a05e25cf4e2c0000000712b4f3b0258a6c76ddaf414bedb1cbfa64eaad958a0cff4f3c57085c5df38c63042c0000000712a2c71c01fae573da0ddcf3a0f10c28e5400093c72eec182b1a141c4dfb4a8a022c000000071286bacff9e50488125f449229ffa6767a36ab06a48b04e41c70cc7e6d82359d7d2c00000007128889bb12ffc22c93e6190aeac259184d7181bed3f0cc9938d27315f8e61c8c4a2d0000000712040fddcb4b5b6707697e2431f7330ee99372e2a55b955bf7b93f8a853d07f10f2d0000000712f4164fa5fd5aa70d2d524d72d9e17d16a56946c3b9fa97d03d2aa2a05e25cf4e2d0000000712b4f3b0258a6c76ddaf414bedb1cbfa64eaad958a0cff4f3c57085c5df38c63042d0000000712a2c71c01fae573da0ddcf3a0f10c28e5400093c72eec182b1a141c4dfb4a8a022d000000071286bacff9e50488125f449229ffa6767a36ab06a48b04e41c70cc7e6d82359d7d2d00000007128889bb12ffc22c93e6190aeac259184d7181bed3f0cc9938d27315f8e61c8c4a2e0000000712040fddcb4b5b6707697e2431f7330ee99372e2a55b955bf7b93f8a853d07f10f2e0000000712f4164fa5fd5aa70d2d524d72d9e17d16a56946c3b9fa97d03d2aa2a05e25cf4e2e0000000712b4f3b0258a6c76ddaf414bedb1cbfa64eaad958a0cff4f3c57085c5df38c63042e0000000712a2c71c01fae573da0ddcf3a0f10c28e5400093c72eec182b1a141c4dfb4a8a022e000000071286bacff9e50488125f449229ffa6767a36ab06a48b04e41c70cc7e6d82359d7d2e00000007128889bb12ffc22c93e6190aeac259184d7181bed3f0cc9938d27315f8e61c8c4a2f0000000712040fddcb4b5b6707697e2431f7330ee99372e2a55b955bf7b93f8a853d07f10f2f0000000712f4164fa5fd5aa70d2d524d72d9e17d16a56946c3b9fa97d03d2aa2a05e25cf4e2f0000000712b4f3b0258a6c76ddaf414bedb1cbfa64eaad958a0cff4f3c57085c5df38c63042f0000000712a2c71c01fae573da0ddcf3a0f10c28e5400093c72eec182b1a141c4dfb4a8a022f000000071286bacff9e50488125f449229ffa6767a36ab06a48b04e41c70cc7e6d82359d7d2f00000007128889bb12ffc22c93e6190aeac259184d7181bed3f0cc9938d27315f8e61c8c4a300000000712040fddcb4b5b6707697e2431f7330ee99372e2a55b955bf7b93f8a853d07f10f300000000712f4164fa5fd5aa70d2d524d72d9e17d16a56946c3b9fa97d03d2aa2a05e25cf4e300000000712b4f3b0258a6c76ddaf414bedb1cbfa64eaad958a0cff4f3c57085c5df38c6304300000000712a2c71c01fae573da0ddcf3a0f10c28e5400093c72eec182b1a141c4dfb4a8a0230000000071286bacff9e50488125f449229ffa6767a36ab06a48b04e41c70cc7e6d82359d7d30000000", + "0x510884122efdea02dda2a9b56b20809ae8108ab89c5a8692e6197d0cc8cf4903a7e81601005267278172d49fde8a23513165bcd894ba0d9b5a7ee3e2976b5202d9f2a942515021b2c175ea46139b066709dfe610ec96d019b9622f7a46ee8f2587378b8a550304001d00a569f6e4cf6bed95cafbf001e13254b28710220cd9b208c10f7154c17740099f001100305c5062779d44ea2ab0469e155b8cf3e004fce71b3b3d38263cd9fa9478f12f28f1fe9f7ba0feab9e47684d4006ed25ad6c441a1553cef62748545ea575392af5a69484f2b10ec2f1dea19394423d576f91c6b5ab2315b389f4e108bcf0aa28408e29df67564eddc299a2aa1cdacbec3c1ae061cf0835c87e5b46b920f7573a774adf51a47b72795366d52285e329229c836ea7bbfe139dbe8fa0700c4f86fc563c82ab06b794c99f14a161973be7aa6012568b1c491d45ec969ed7420bcfaa59ca5bc1915da74aba3aadd7ce7b809045d5eb5b73559259755fdcd85a40a5dc6e6af08f6bb841825b168ddf79837e70d88d75e1c5b290b74fa97cedfd668dd22c984e16482c99cfad1436111e321a86d87d0fac203bf64538f888e45d793b54133235f7d984058bb410c163fc1d7a90e5475c0917aad77deb241093a50b4f683f3ef37fce11457ebc5f00b154996471fcd30eae17efe69e5a39b12b76034dc7e4a6996ba5fce10b5419b5a9fd55998b5d7fde4922e61f93b021cb2cf6dfd5707e0f00001a93fa350e" + ] +} \ No newline at end of file diff --git a/src/services/test-helpers/mock/mockBlock789629.ts b/src/services/test-helpers/mock/mockBlock789629.ts index 8b1cbb119..69e72f2bb 100644 --- a/src/services/test-helpers/mock/mockBlock789629.ts +++ b/src/services/test-helpers/mock/mockBlock789629.ts @@ -1,5 +1,6 @@ import { polkadotRegistry } from '../../../test-helpers/registries'; import * as block789629 from './data/block789629.json'; +import * as block789629Fork from './data/blocks789629Fork.json'; /** * Mock for polkadot block #789629. @@ -16,3 +17,11 @@ export const blockHash789629 = polkadotRegistry.createType( 'BlockHash', '0x7b713de604a99857f6c25eacc115a4f28d2611a23d9ddff99ab0e4f1c17a8578' ); + +/** + * Mock for polkadot forked block #789629. + */ +export const mockForkedBlock789629 = polkadotRegistry.createType( + 'Block', + block789629Fork +); diff --git a/src/services/test-helpers/responses/blocks/blocks789629.json b/src/services/test-helpers/responses/blocks/blocks789629.json index 17d68e45f..57e74a6a7 100644 --- a/src/services/test-helpers/responses/blocks/blocks789629.json +++ b/src/services/test-helpers/responses/blocks/blocks789629.json @@ -1306,5 +1306,6 @@ ], "onFinalize": { "events": [] - } + }, + "finalized": true } diff --git a/src/types/responses/Block.ts b/src/types/responses/Block.ts index db340d545..e683a1b9f 100644 --- a/src/types/responses/Block.ts +++ b/src/types/responses/Block.ts @@ -16,6 +16,7 @@ export interface IBlock { onInitialize: IOnInitializeOrFinalize; extrinsics: IExtrinsic[]; onFinalize: IOnInitializeOrFinalize; + finalized: boolean | undefined; } interface IOnInitializeOrFinalize {