Skip to content

Commit

Permalink
feat: add finalized tag when querying blocks (#386)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: David <[email protected]>

* Update src/services/blocks/BlocksService.spec.ts

Co-authored-by: David <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: David <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: David <[email protected]>

* Resolve comment formatting

* Update src/controllers/blocks/BlocksController.ts

Co-authored-by: Zeke Mostov <[email protected]>

* 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 <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* fix: lint

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: David <[email protected]>

* Update src/controllers/blocks/BlocksController.ts

Co-authored-by: David <[email protected]>

* 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 <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* fix: DRY test code

* Update src/services/blocks/BlocksService.ts

Co-authored-by: Zeke Mostov <[email protected]>

* 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 <[email protected]>
Co-authored-by: Zeke Mostov <[email protected]>
  • Loading branch information
3 people committed Jan 20, 2021
1 parent 8c621b0 commit b95f913
Show file tree
Hide file tree
Showing 8 changed files with 315 additions and 22 deletions.
5 changes: 5 additions & 0 deletions docs/src/openapi-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
55 changes: 43 additions & 12 deletions src/controllers/blocks/BlocksController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ApiPromise } from '@polkadot/api';
import { isHex } from '@polkadot/util';
import { RequestHandler } from 'express';

import { BlocksService } from '../../services';
Expand Down Expand Up @@ -92,16 +93,36 @@ export default class BlocksController extends AbstractController<BlocksService>
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)
);
};

Expand All @@ -115,18 +136,28 @@ export default class BlocksController extends AbstractController<BlocksService>
{ params: { number }, query: { eventDocs, extrinsicDocs } },
res
): Promise<void> => {
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)
);
};
}
100 changes: 98 additions & 2 deletions src/services/blocks/BlocksService.spec.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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);
});
Expand All @@ -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 {
Expand All @@ -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.`
Expand All @@ -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', () => {
Expand Down Expand Up @@ -262,4 +302,60 @@ describe('BlocksService', () => {
);
});
});

describe('BlockService.isFinalizedBlock', () => {
const finalizedHead = polkadotRegistry.createType(
'BlockHash',
'0x91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3'
);

const blockNumber = polkadotRegistry.createType(
'Compact<BlockNumber>',
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);
});
});
});
Loading

0 comments on commit b95f913

Please sign in to comment.