Skip to content

Commit

Permalink
feat(jellyfish-api-core): add blockchain getChainTxStats RPC (#1051)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
eli-lim authored Feb 23, 2022
1 parent 48d31e0 commit 5dc9348
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 0 deletions.
21 changes: 21 additions & 0 deletions docs/node/CATEGORIES/01-blockchain.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChainTxStats>
}

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
}
```
Original file line number Diff line number Diff line change
@@ -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<string> = 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<string> = 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<string> = 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<string> = 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/)
})
})
22 changes: 22 additions & 0 deletions packages/jellyfish-api-core/src/category/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,17 @@ export class Blockchain {
async waitForBlockHeight (height: number, timeout: number = 30000): Promise<WaitBlockResult> {
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<ChainTxStats>}
*/
async getChainTxStats (nBlocks?: number, blockHash?: string): Promise<ChainTxStats> {
return await this.client.call('getchaintxstats', [nBlocks, blockHash], 'number')
}
}

/**
Expand Down Expand Up @@ -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
}

0 comments on commit 5dc9348

Please sign in to comment.