Skip to content

Commit

Permalink
feat: add /blocks/:number/header, and /blocks/head/header (#615)
Browse files Browse the repository at this point in the history
  • Loading branch information
TarikGul committed Jul 27, 2021
1 parent 5a78903 commit b7c2818
Show file tree
Hide file tree
Showing 5 changed files with 229 additions and 16 deletions.
2 changes: 1 addition & 1 deletion docs/dist/app.bundle.js

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions docs/src/openapi-v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,35 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Error'
/blocks/{blockId}/header:
get:
tags:
- blocks
summary: Get a block's header by its height or hash.
description: Returns a single block's header. BlockId can either be a block hash or a
block height.
operationId: getBlockHeaderById
parameters:
- name: blockId
in: path
description: Block identifier, as the block height or block hash.
required: true
schema:
pattern: 'a-km-zA-HJ-NP-Z1-9{8,64}'
type: string
responses:
"200":
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/BlockHeader'
"400":
description: invalid Block identifier supplied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/blocks/{blockId}/extrinsics/{extrinsicIndex}:
get:
tags:
Expand Down Expand Up @@ -461,6 +490,36 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/Block'
/blocks/head/header:
get:
tags:
- blocks
summary: Get information about the header of the most recent finalized block.
description: Returns the most recently finalized block's header.
operationId: getLatestBlockHeader
parameters:
- name: finalized
in: query
description: Boolean representing whether or not to get the finalized head.
If it is not set the value defaults to true. When set to false it will attempt
to get the newest known block, which may not be finalized.
required: false
schema:
type: boolean
default: true
responses:
"200":
description: successful operation
content:
application/json:
schema:
$ref: '#/components/schemas/BlockHeader'
"400":
description: invalid Block identifier supplied
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
/node/network:
get:
tags:
Expand Down Expand Up @@ -2606,6 +2665,33 @@ components:
type: array
items:
$ref: '#/components/schemas/Operation'
BlockHeader:
type: object
properties:
parentHash:
type: string
description: The hash of the parent block.
format: hex
number:
type: string
description: The block's height.
format: unsignedInteger
stateRoot:
type: string
description: The state root after executing this block.
format: hex
extrinsicRoot:
type: string
description: The Merkle root of the extrinsics.
format: hex
digest:
type: object
properties:
logs:
type: array
items:
$ref: '#/components/schemas/DigestItem'
description: Array of `DigestItem`s associated with the block.
requestBodies:
Transaction:
content:
Expand Down
100 changes: 85 additions & 15 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 { BlockHash } from '@polkadot/types/interfaces';
import { isHex } from '@polkadot/util';
import { RequestHandler } from 'express';

Expand All @@ -12,6 +13,12 @@ interface ControllerOptions {
blockWeightStore: {};
}

interface IFinalizationOpts {
hash: BlockHash;
omitFinalizedTag: boolean;
queryFinalizedHead: boolean;
}

/**
* GET a block.
*
Expand Down Expand Up @@ -89,6 +96,8 @@ export default class BlocksController extends AbstractController<BlocksService>
this.safeMountAsyncGetHandlers([
['/head', this.getLatestBlock],
['/:number', this.getBlockById],
['/head/header', this.getLatestBlockHeader],
['/:number/header', this.getBlockHeaderById],
]);
}

Expand All @@ -105,21 +114,9 @@ export default class BlocksController extends AbstractController<BlocksService>
const eventDocsArg = eventDocs === 'true';
const extrinsicDocsArg = extrinsicDocs === 'true';

let hash, queryFinalizedHead, omitFinalizedTag;
if (!this.options.finalizes) {
// If the network chain doesn't finalize blocks, we dont want a finalized tag.
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 paramFinalized = finalized === 'true' ? true : false;
const { hash, queryFinalizedHead, omitFinalizedTag } =
await this.parseFinalizationOpts(this.options.finalizes, paramFinalized);

const options = {
eventDocs: eventDocsArg,
Expand Down Expand Up @@ -169,4 +166,77 @@ export default class BlocksController extends AbstractController<BlocksService>
await this.service.fetchBlock(hash, options)
);
};

/**
* Return the Header of the identified block.
*
* @param req Express Request
* @param res Express Response
*/
private getBlockHeaderById: RequestHandler<INumberParam> = async (
{ params: { number } },
res
): Promise<void> => {
const hash = await this.getHashForBlock(number);

BlocksController.sanitizedSend(
res,
await this.service.fetchBlockHeader(hash)
);
};

/**
* Return the header of the latest block
*
* @param req Express Request
* @param res Express Response
*/
private getLatestBlockHeader: RequestHandler = async (
{ query: { finalized } },
res
): Promise<void> => {
const paramFinalized = finalized !== 'false';

const hash = paramFinalized
? await this.api.rpc.chain.getFinalizedHead()
: undefined;

BlocksController.sanitizedSend(
res,
await this.service.fetchBlockHeader(hash)
);
};

/**
* This also returns the hash for the block to query.
*
* @param optFinalizes
* @param paramFinalized
*/
private parseFinalizationOpts = async (
optFinalizes: boolean,
paramFinalized: boolean
): Promise<IFinalizationOpts> => {
let hash, queryFinalizedHead, omitFinalizedTag;
if (!optFinalizes) {
// If the network chain doesn't finalize blocks, we dont want a finalized tag.
omitFinalizedTag = true;
queryFinalizedHead = false;
hash = (await this.api.rpc.chain.getHeader()).hash;
} else if (!paramFinalized) {
omitFinalizedTag = false;
queryFinalizedHead = true;
hash = (await this.api.rpc.chain.getHeader()).hash;
} else {
omitFinalizedTag = false;
queryFinalizedHead = false;
hash = await this.api.rpc.chain.getFinalizedHead();
}

return {
hash,
omitFinalizedTag,
queryFinalizedHead,
};
};
}
41 changes: 41 additions & 0 deletions src/services/blocks/BlocksService.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -507,4 +507,45 @@ describe('BlocksService', () => {
);
});
});

describe('fetchBlockSummary', () => {
const expectedResponse = {
parentHash:
'0x205da5dba43bbecae52b44912249480aa9f751630872b6b6ba1a9d2aeabf0177',
number: '789629',
stateRoot:
'0x023b5bb1bc10a1a91a9ef683f46a8bb09666c50476d5592bd6575a73777eb173',
extrinsicsRoot:
'0x4c1d65bf6b57086f00d5df40aa0686ffbc581ef60878645613b1fc3303de5030',
digest: {
logs: [
{
preRuntime: [
'0x45424142',
'0x036d000000f4f4d80f00000000ec9bd8e2d0368c97f3d888837f7283bbe08266869eb613159db547905026c2502a70f168b9ffcc233344005d11ebecd166769200d270a2eaa642118a00acb708a0487a440b0caf3dd5c91ab173e80ddfe5735ef8b938ea87a6105a1161612707',
],
},
{
seal: [
'0x45424142',
'0xae78514e1de84a7d32e55b9b652f9d408ab1f7b4bfdbf6b2fad9cad94a91b86b0161cabf08f5ae1d3a1aa4993e2d96d56c94b03cee0898ccb8385a546084f88b',
],
},
],
},
};
it('Returns the correct summary for the latest block', async () => {
const blockSummary = await blocksService.fetchBlockHeader(
blockHash789629
);

expect(sanitizeNumbers(blockSummary)).toStrictEqual(expectedResponse);
});

it('Returns the correct summary for the given block number', async () => {
const blockSummary = await blocksService.fetchBlockHeader();

expect(sanitizeNumbers(blockSummary)).toStrictEqual(expectedResponse);
});
});
});
16 changes: 16 additions & 0 deletions src/services/blocks/BlocksService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
DispatchInfo,
EventRecord,
Hash,
Header,
} from '@polkadot/types/interfaces';
import { AnyJson, Codec, Registry } from '@polkadot/types/types';
import { u8aToHex } from '@polkadot/util';
Expand Down Expand Up @@ -283,6 +284,21 @@ export class BlocksService extends AbstractService {
};
}

/**
* Return the header of a block
*
* @param hash When no hash is inputted the header of the chain will be queried.
*/
async fetchBlockHeader(hash?: BlockHash): Promise<Header> {
const { api } = this;

const header = hash
? await api.rpc.chain.getHeader(hash)
: await api.rpc.chain.getHeader();

return header;
}

/**
*
* @param block Takes in a block which is the result of `BlocksService.fetchBlock`
Expand Down

0 comments on commit b7c2818

Please sign in to comment.