From ecdc3b53a2549a98135cf8e4ab90c5758a3f96a1 Mon Sep 17 00:00:00 2001 From: Shoham Chakraborty Date: Mon, 5 Dec 2022 12:51:48 +0800 Subject: [PATCH 1/4] Add invalidateBlock and tests --- .../blockchain/invalidateBlock.test.ts | 65 +++++++++++++++++++ .../src/category/blockchain.ts | 10 +++ 2 files changed, 75 insertions(+) create mode 100644 packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts diff --git a/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts b/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts new file mode 100644 index 0000000000..ef199e410c --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts @@ -0,0 +1,65 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { Testing } from '@defichain/jellyfish-testing' +import { RpcApiError } from '@defichain/jellyfish-api-core' + +describe('Block', () => { + const testing = Testing.create(new MasterNodeRegTestContainer()) + + beforeAll(async () => { + await testing.container.start() + }) + + afterAll(async () => { + await testing.container.stop() + }) + + it('should throw error if no blockhash provided', async () => { + const promise = testing.rpc.blockchain.invalidateBlock('') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -8, + message: 'blockhash must be of length 64 (not 0, for \'\')', + method: 'invalidateblock' + } + }) + }) + + it('should throw error if invalid blockhash provided - invalid length', async () => { + const promise = testing.rpc.blockchain.invalidateBlock('invalidblockhash') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -8, + message: 'blockhash must be of length 64 (not 16, for \'invalidblockhash\')', + method: 'invalidateblock' + } + }) + }) + + it('should throw error if invalid blockhash provided - invalid hex string', async () => { + const promise = testing.rpc.blockchain.invalidateBlock('d744db74fb70ed42767aj028a129365fb4d7de54ba1b6575fb047490554f8a7b') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -8, + message: 'blockhash must be hexadecimal string (not \'d744db74fb70ed42767aj028a129365fb4d7de54ba1b6575fb047490554f8a7b\')', + method: 'invalidateblock' + } + }) + }) + + it('should invalidate block', async () => { + await testing.generate(1) // generate some blocks to have a chain history + const blockToInvalidate = await testing.rpc.blockchain.getBestBlockHash() + + await testing.generate(2) // generate new blocks + await expect(testing.rpc.blockchain.getBestBlockHash()).not.toStrictEqual(blockToInvalidate) // confirm that new blocks were generated + const promise = testing.rpc.blockchain.invalidateBlock(blockToInvalidate) + + await expect(promise).toBeTruthy() + }) +}) diff --git a/packages/jellyfish-api-core/src/category/blockchain.ts b/packages/jellyfish-api-core/src/category/blockchain.ts index d954b2d841..b2bcd25bee 100644 --- a/packages/jellyfish-api-core/src/category/blockchain.ts +++ b/packages/jellyfish-api-core/src/category/blockchain.ts @@ -287,6 +287,16 @@ export class Blockchain { async getChainTxStats (nBlocks?: number, blockHash?: string): Promise { return await this.client.call('getchaintxstats', [nBlocks, blockHash], 'number') } + + /** + * Permanently marks a block as invalid, as if it violated a consensus rule. + * + * @param {string} blockHash the hash of the block to invalidate + * @return {Promise} + */ + async invalidateBlock (blockHash: string): Promise { + return await this.client.call('invalidateblock', [blockHash], 'number') + } } /** From f4c1e80284c3e75d60041d4c5e2a53c9c98f1a52 Mon Sep 17 00:00:00 2001 From: Shoham Chakraborty Date: Mon, 5 Dec 2022 13:02:19 +0800 Subject: [PATCH 2/4] Add reconsiderBlock and tests --- .../blockchain/invalidateBlock.test.ts | 2 +- .../blockchain/reconsiderBlock.test.ts | 68 +++++++++++++++++++ .../src/category/blockchain.ts | 11 +++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts diff --git a/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts b/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts index ef199e410c..ddf2266b27 100644 --- a/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts @@ -2,7 +2,7 @@ import { MasterNodeRegTestContainer } from '@defichain/testcontainers' import { Testing } from '@defichain/jellyfish-testing' import { RpcApiError } from '@defichain/jellyfish-api-core' -describe('Block', () => { +describe('Invalidate block', () => { const testing = Testing.create(new MasterNodeRegTestContainer()) beforeAll(async () => { diff --git a/packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts b/packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts new file mode 100644 index 0000000000..9dc45ebdf0 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts @@ -0,0 +1,68 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { Testing } from '@defichain/jellyfish-testing' +import { RpcApiError } from '@defichain/jellyfish-api-core' + +describe('Reconsider block', () => { + const testing = Testing.create(new MasterNodeRegTestContainer()) + + beforeAll(async () => { + await testing.container.start() + }) + + afterAll(async () => { + await testing.container.stop() + }) + + it('should throw error if no blockhash provided', async () => { + const promise = testing.rpc.blockchain.reconsiderBlock('') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -8, + message: 'blockhash must be of length 64 (not 0, for \'\')', + method: 'reconsiderblock' + } + }) + }) + + it('should throw error if invalid blockhash provided - invalid length', async () => { + const promise = testing.rpc.blockchain.reconsiderBlock('invalidblockhash') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -8, + message: 'blockhash must be of length 64 (not 16, for \'invalidblockhash\')', + method: 'reconsiderblock' + } + }) + }) + + it('should throw error if invalid blockhash provided - invalid hex string', async () => { + const promise = testing.rpc.blockchain.reconsiderBlock('d744db74fb70ed42767aj028a129365fb4d7de54ba1b6575fb047490554f8a7b') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -8, + message: 'blockhash must be hexadecimal string (not \'d744db74fb70ed42767aj028a129365fb4d7de54ba1b6575fb047490554f8a7b\')', + method: 'reconsiderblock' + } + }) + }) + + it('should reconsider block', async () => { + await testing.generate(1) // generate some blocks to have a chain history + const blockToInvalidate = await testing.rpc.blockchain.getBestBlockHash() + + await testing.generate(2) // generate new blocks + const bestBlockHash = await testing.rpc.blockchain.getBestBlockHash() + + await expect(bestBlockHash).not.toStrictEqual(blockToInvalidate) // confirm that new blocks were generated + await testing.rpc.blockchain.invalidateBlock(blockToInvalidate) + + const promise = testing.rpc.blockchain.reconsiderBlock(blockToInvalidate) + await expect(promise).toBeTruthy() + }) +}) diff --git a/packages/jellyfish-api-core/src/category/blockchain.ts b/packages/jellyfish-api-core/src/category/blockchain.ts index b2bcd25bee..48657e4243 100644 --- a/packages/jellyfish-api-core/src/category/blockchain.ts +++ b/packages/jellyfish-api-core/src/category/blockchain.ts @@ -297,6 +297,17 @@ export class Blockchain { async invalidateBlock (blockHash: string): Promise { return await this.client.call('invalidateblock', [blockHash], 'number') } + + /** + * Removes invalidity status of a block, its ancestors and its descendants, reconsider them for activation. + * This can be used to undo the effects of invalidateBlock. + * + * @param {string} blockHash the hash of the block to invalidate + * @return {Promise} + */ + async reconsiderBlock (blockHash: string): Promise { + return await this.client.call('reconsiderblock', [blockHash], 'number') + } } /** From ac3652df052d6d3dc61a0c1204cf8cc69d8f73c0 Mon Sep 17 00:00:00 2001 From: Shoham Chakraborty Date: Mon, 5 Dec 2022 13:54:21 +0800 Subject: [PATCH 3/4] Update docs --- docs/node/CATEGORIES/01-blockchain.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/node/CATEGORIES/01-blockchain.md b/docs/node/CATEGORIES/01-blockchain.md index 399afa4f55..b5541ae14c 100644 --- a/docs/node/CATEGORIES/01-blockchain.md +++ b/docs/node/CATEGORIES/01-blockchain.md @@ -299,7 +299,7 @@ Get all in-mempool ancestors if a transaction id is in mempool as string[] if ve interface blockchain { getMempoolAncestors (txId: string, verbose?: false): Promise getMempoolAncestors (txId: string, verbose?: true): Promise - getMempoolAncestors (txId: string, verbose?: boolean: Promise + getMempoolAncestors (txId: string, verbose?: boolean): Promise } interface MempoolTx { @@ -482,3 +482,23 @@ interface ChainTxStats { txrate: number } ``` + +## invalidateBlock + +Permanently marks a block as invalid, as if it violated a consensus rule. + +```ts title="client.blockchain.invalidateBlock()" +interface blockchain { + invalidateBlock (blockHash: string): Promise +} +``` + +## reconsiderBlock + +Removes invalidity status of a block, its ancestors and its descendants, reconsider them for activation. This can be used to undo the effects of invalidateBlock. + +```ts title="client.blockchain.reconsiderBlock()" +interface blockchain { + reconsiderBlock (blockHash: string): Promise +} +``` From a48c41e91be43871c4560f8e3fd4ac1ca994008e Mon Sep 17 00:00:00 2001 From: Shoham Chakraborty Date: Mon, 5 Dec 2022 13:57:07 +0800 Subject: [PATCH 4/4] Add test for 'block not found' error --- .../category/blockchain/invalidateBlock.test.ts | 13 +++++++++++++ .../category/blockchain/reconsiderBlock.test.ts | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts b/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts index ddf2266b27..63f230cfe8 100644 --- a/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/blockchain/invalidateBlock.test.ts @@ -52,6 +52,19 @@ describe('Invalidate block', () => { }) }) + it('should throw error if block is not found', async () => { + const promise = testing.rpc.blockchain.reconsiderBlock('a9b5faec3324361a842df4302ce987f5fe1c5ce9ba53650f3a54397a36fea3b2') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -5, + message: 'Block not found', + method: 'reconsiderblock' + } + }) + }) + it('should invalidate block', async () => { await testing.generate(1) // generate some blocks to have a chain history const blockToInvalidate = await testing.rpc.blockchain.getBestBlockHash() diff --git a/packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts b/packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts index 9dc45ebdf0..eca841fd07 100644 --- a/packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/blockchain/reconsiderBlock.test.ts @@ -52,6 +52,19 @@ describe('Reconsider block', () => { }) }) + it('should throw error if block is not found', async () => { + const promise = testing.rpc.blockchain.reconsiderBlock('a9b5faec3324361a842df4302ce987f5fe1c5ce9ba53650f3a54397a36fea3b2') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toMatchObject({ + payload: { + code: -5, + message: 'Block not found', + method: 'reconsiderblock' + } + }) + }) + it('should reconsider block', async () => { await testing.generate(1) // generate some blocks to have a chain history const blockToInvalidate = await testing.rpc.blockchain.getBestBlockHash()