From 5dc9348cebe61d0821e5916cdd705094ec1a7d7a Mon Sep 17 00:00:00 2001 From: Eli <31790206+eli-lim@users.noreply.github.com> Date: Wed, 23 Feb 2022 14:49:29 +0800 Subject: [PATCH] feat(jellyfish-api-core): add blockchain getChainTxStats RPC (#1051) * feat(jellyfish-api-core): add blockchain getChainTxStats RPC * fix(jellyfish-api-core): fix type typo in doc * chore(jellyfish-api-core): improve tests and add negative test case - remove redundant type checking - change to test window_final_block_hash against block hash * chore(jellyfish-api-core): add rpc default value descriptions to doc * chore(jellyfish-api-core): check time field against blocks, add more negative cases --- docs/node/CATEGORIES/01-blockchain.md | 21 ++++ .../blockchain/getChainTxStats.test.ts | 117 ++++++++++++++++++ .../src/category/blockchain.ts | 22 ++++ 3 files changed, 160 insertions(+) create mode 100644 packages/jellyfish-api-core/__tests__/category/blockchain/getChainTxStats.test.ts diff --git a/docs/node/CATEGORIES/01-blockchain.md b/docs/node/CATEGORIES/01-blockchain.md index 8eea1cfe4a..609ae1640b 100644 --- a/docs/node/CATEGORIES/01-blockchain.md +++ b/docs/node/CATEGORIES/01-blockchain.md @@ -371,3 +371,24 @@ interface WaitBlockResult { height: number } ``` + +## getChainTxStats + +Get statistics about the total number and rate of transactions in the chain. + +```ts title="client.blockchain.getChainTxStats()" +interface blockchain { + getChainTxStats (nBlocks?: number, blockHash?: string): Promise +} + +interface ChainTxStats { + time: number + txcount: number + window_final_block_hash: string + window_final_block_height: number + window_block_count: number + window_tx_count: number + window_interval: number + txrate: number +} +``` \ No newline at end of file diff --git a/packages/jellyfish-api-core/__tests__/category/blockchain/getChainTxStats.test.ts b/packages/jellyfish-api-core/__tests__/category/blockchain/getChainTxStats.test.ts new file mode 100644 index 0000000000..f941b17e11 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/blockchain/getChainTxStats.test.ts @@ -0,0 +1,117 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { Testing } from '@defichain/jellyfish-testing' +import { Block } from '../../../src/category/blockchain' + +describe('ChainTxStats', () => { + const testing = Testing.create(new MasterNodeRegTestContainer()) + + beforeAll(async () => { + await testing.container.start() + }) + + afterAll(async () => { + await testing.container.stop() + }) + + it('should getChainTxStats for selected interval', async () => { + const blockHash = await testing.misc.waitForBlockHash(3) + const stats = await testing.rpc.blockchain.getChainTxStats(2, blockHash) + + expect(stats.txcount).toStrictEqual(12) + expect(stats.window_final_block_hash).toStrictEqual(blockHash) + expect(stats.window_final_block_height).toStrictEqual(3) + expect(stats.window_block_count).toStrictEqual(2) + expect(stats.window_tx_count).toStrictEqual(2) + expect(stats.window_interval).toStrictEqual(2) + expect(stats.txrate).toStrictEqual(1) + + const block: Block = await testing.rpc.blockchain.getBlock(blockHash, 1) + expect(stats.time).toStrictEqual(block.time) + }) + + it('should getChainTxStats for all blocks excl. genesis when called with default arguments', async () => { + await testing.misc.waitForBlockHash(4) + const bestHash = await testing.rpc.blockchain.getBestBlockHash() + + const stats = await testing.rpc.blockchain.getChainTxStats() // all 4 blocks excl genesis + const statsFromBestHash = await testing.rpc.blockchain.getChainTxStats(4, bestHash) // equivalent + expect(stats).toStrictEqual(statsFromBestHash) + + expect(stats.txcount).toStrictEqual(14) + expect(stats.window_final_block_hash).toStrictEqual(bestHash) + expect(stats.window_final_block_height).toStrictEqual(5) + expect(stats.window_block_count).toStrictEqual(4) + expect(stats.window_tx_count).toStrictEqual(4) + expect(stats.window_interval).toStrictEqual(2) + expect(stats.txrate).toStrictEqual(2) + + const bestBlock: Block = await testing.rpc.blockchain.getBlock(bestHash, 1) + expect(stats.time).toStrictEqual(bestBlock.time) + }) + + it('should getChainTxStats for blocks referencing chain tip when called with nBlocks', async () => { + await testing.misc.waitForBlockHash(4) + const bestHash = await testing.rpc.blockchain.getBestBlockHash() + + const stats = await testing.rpc.blockchain.getChainTxStats(3) + const statsFromBestHash = await testing.rpc.blockchain.getChainTxStats(3, bestHash) // equivalent + expect(stats).toStrictEqual(statsFromBestHash) + + expect(stats.txcount).toStrictEqual(14) + expect(stats.window_final_block_hash).toStrictEqual(bestHash) + expect(stats.window_final_block_height).toStrictEqual(5) + expect(stats.window_block_count).toStrictEqual(3) + expect(stats.window_tx_count).toStrictEqual(3) + expect(stats.window_interval).toStrictEqual(2) + expect(stats.txrate).toStrictEqual(1.5) + + const bestBlock: Block = await testing.rpc.blockchain.getBlock(bestHash, 1) + expect(stats.time).toStrictEqual(bestBlock.time) + }) + + it('should getChainTxStats for block and all ancestor blocks when called with blockHash', async () => { + const blockHash = await testing.misc.waitForBlockHash(4) + + const stats = await testing.rpc.blockchain.getChainTxStats(undefined, blockHash) + const statsFromNBlocks = await testing.rpc.blockchain.getChainTxStats(3, blockHash) // equivalent + expect(stats).toStrictEqual(statsFromNBlocks) + + expect(stats.txcount).toStrictEqual(13) + expect(stats.window_final_block_hash).toStrictEqual(blockHash) + expect(stats.window_final_block_height).toStrictEqual(4) + expect(stats.window_block_count).toStrictEqual(3) + expect(stats.window_tx_count).toStrictEqual(3) + expect(stats.window_interval).toStrictEqual(2) + expect(stats.txrate).toStrictEqual(1.5) + + const block: Block = await testing.rpc.blockchain.getBlock(blockHash, 1) + expect(stats.time).toStrictEqual(block.time) + }) + + it('should not getChainTxStats if block count < 0 or >= block\'s height - 1', async () => { + const blockHash = await testing.misc.waitForBlockHash(3) + + const expectedError = /RpcApiError: 'Invalid block count: should be between 0 and the block's height - 1', code: -8, method: getchaintxstats/ + await expect(testing.rpc.blockchain.getChainTxStats(-1, blockHash)).rejects.toThrowError(expectedError) + await expect(testing.rpc.blockchain.getChainTxStats(3, blockHash)).rejects.toThrowError(expectedError) + await expect(testing.rpc.blockchain.getChainTxStats(4, blockHash)).rejects.toThrowError(expectedError) + + await expect(testing.rpc.blockchain.getChainTxStats(0, blockHash)).resolves.toBeTruthy() + await expect(testing.rpc.blockchain.getChainTxStats(1, blockHash)).resolves.toBeTruthy() + await expect(testing.rpc.blockchain.getChainTxStats(2, blockHash)).resolves.toBeTruthy() + }) + + it('should not getChainTxStats if blockhash is not a valid hash', async () => { + await testing.misc.waitForBlockHash(3) + await expect(testing.rpc.blockchain.getChainTxStats(1, '0')) + .rejects + .toThrowError("RpcApiError: 'blockhash must be of length 64 (not 1, for '0')', code: -8, method: getchaintxstats") + }) + + it('should not getChainTxStats if blockhash doesnt exist', async () => { + await testing.misc.waitForBlockHash(3) + await expect(testing.rpc.blockchain.getChainTxStats(1, '0'.repeat(64))) + .rejects + .toThrowError(/RpcApiError: 'Block not found', code: -5, method: getchaintxstats/) + }) +}) diff --git a/packages/jellyfish-api-core/src/category/blockchain.ts b/packages/jellyfish-api-core/src/category/blockchain.ts index 428301414c..e549e2346e 100644 --- a/packages/jellyfish-api-core/src/category/blockchain.ts +++ b/packages/jellyfish-api-core/src/category/blockchain.ts @@ -214,6 +214,17 @@ export class Blockchain { async waitForBlockHeight (height: number, timeout: number = 30000): Promise { return await this.client.call('waitforblockheight', [height, timeout], 'number') } + + /** + * Get statistics about the total number and rate of transactions in the chain. + * + * @param {number} [nBlocks] size of the window in number of blocks. Defaults to 1 month (~86,400) blocks. + * @param {string} [blockHash] the hash of the block that ends the window. Defaults to the chain tip. + * @return {Promise} + */ + async getChainTxStats (nBlocks?: number, blockHash?: string): Promise { + return await this.client.call('getchaintxstats', [nBlocks, blockHash], 'number') + } } /** @@ -415,3 +426,14 @@ export interface WaitBlockResult { hash: string height: number } + +export interface ChainTxStats { + time: number + txcount: number + window_final_block_hash: string + window_final_block_height: number + window_block_count: number + window_tx_count: number + window_interval: number + txrate: number +}