From bd28ca9b8ded227d83d3141bf3b9f884c6b7c07c Mon Sep 17 00:00:00 2001 From: Dilshan Madushanka Date: Tue, 10 Jan 2023 08:28:26 +0800 Subject: [PATCH] feat(jellyfish-api-core): add blockchain `getMempoolDescendants` RPC (#1796) What this PR does / why we need it: /kind feature Which issue(s) does this PR fixes?: Fixes part of https://github.com/JellyfishSDK/jellyfish/issues/48 - Implements `getmempooldescendants` type for RPC. --- docs/node/CATEGORIES/01-blockchain.md | 43 +++++++ .../blockchain/getMempoolDescendants.test.ts | 111 ++++++++++++++++++ .../src/category/blockchain.ts | 30 +++++ 3 files changed, 184 insertions(+) create mode 100644 packages/jellyfish-api-core/__tests__/category/blockchain/getMempoolDescendants.test.ts diff --git a/docs/node/CATEGORIES/01-blockchain.md b/docs/node/CATEGORIES/01-blockchain.md index 399afa4f55..7df0ee1419 100644 --- a/docs/node/CATEGORIES/01-blockchain.md +++ b/docs/node/CATEGORIES/01-blockchain.md @@ -334,6 +334,49 @@ interface MempoolTx { } ``` +## getMempoolDescendants + +Get all in-mempool descendants if a transaction id is in mempool as string[] if verbose is false else as json object + +```ts title="client.blockchain.getMempoolDescendants()" +interface blockchain { + getMempoolDescendants (txId: string, verbose?: false): Promise + getMempoolDescendants (txId: string, verbose?: true): Promise + getMempoolDescendants (txId: string, verbose?: boolean: Promise +} + +interface MempoolTx { + [key: string]: { + vsize: BigNumber + /** + * @deprecated same as vsize. Only returned if defid is started with -deprecatedrpc=size + */ + size: BigNumber + weight: BigNumber + fee: BigNumber + modifiedfee: BigNumber + time: BigNumber + height: BigNumber + descendantcount: BigNumber + descendantsize: BigNumber + descendantfees: BigNumber + ancestorcount: BigNumber + ancestorsize: BigNumber + ancestorfees: BigNumber + wtxid: string + fees: { + base: BigNumber + modified: BigNumber + ancestor: BigNumber + descendant: BigNumber + } + depends: string[] + spentby: string[] + 'bip125-replaceable': boolean + } +} +``` + ## getMempoolEntry Get transaction details in the memory pool using a transaction ID. diff --git a/packages/jellyfish-api-core/__tests__/category/blockchain/getMempoolDescendants.test.ts b/packages/jellyfish-api-core/__tests__/category/blockchain/getMempoolDescendants.test.ts new file mode 100644 index 0000000000..198de126c1 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/blockchain/getMempoolDescendants.test.ts @@ -0,0 +1,111 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { Testing } from '@defichain/jellyfish-testing' +import BigNumber from 'bignumber.js' + +describe('Transactions without descendants', () => { + const container = new MasterNodeRegTestContainer() + const testing = Testing.create(container) + + beforeAll(async () => { + await testing.container.start() + await testing.container.waitForWalletCoinbaseMaturity() + }) + + afterAll(async () => { + await testing.container.stop() + }) + + it('should return empty array for transaction id without descendants', async () => { + const txId = await testing.rpc.wallet.sendToAddress('mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU', 0.003) + const mempoolDescendants = await testing.rpc.blockchain.getMempoolDescendants(txId) + + expect(mempoolDescendants.length).toStrictEqual(0) + }) + + it('should return error if transaction is not in mempool', async () => { + const nonExistingTxId = 'a'.repeat(64) + const promise = testing.rpc.blockchain.getMempoolDescendants(nonExistingTxId) + + await expect(promise) + .rejects + .toThrowError('RpcApiError: \'Transaction not in mempool\', code: -5, method: getmempooldescendants') + }) + + it('should return error if transaction id has an invalid length', async () => { + const invalidLengthTxid = 'a'.repeat(5) + const promise = testing.rpc.blockchain.getMempoolDescendants(invalidLengthTxid) + + await expect(promise) + .rejects + .toThrow(`RpcApiError: 'parameter 1 must be of length 64 (not ${invalidLengthTxid.length}, for '${invalidLengthTxid}')', code: -8, method: getmempooldescendants`) + }) +}) + +describe('Transactions with descendants', () => { + const container = new MasterNodeRegTestContainer() + const testing = Testing.create(container) + + beforeAll(async () => { + await testing.container.start() + await testing.container.waitForWalletCoinbaseMaturity() + }) + + async function getTxIdWithDescendants (): Promise { + const txId = await testing.rpc.wallet.sendToAddress('mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU', 0.003) + for (let i = 0; i < 10; i++) { + await testing.rpc.wallet.sendToAddress('mwsZw8nF7pKxWH8eoKL9tPxTpaFkz7QeLU', 0.003) + } + return txId + } + + afterAll(async () => { + await testing.container.stop() + }) + + it('should return JSON object if verbose is true', async () => { + const txIdWithDescendants = await getTxIdWithDescendants() + const mempoolDescendants = await testing.rpc.blockchain.getMempoolDescendants(txIdWithDescendants, true) + + const keys = Object.keys(mempoolDescendants) + expect(keys.length).toBeGreaterThan(0) + for (const key of keys) { + expect(mempoolDescendants[key]).toStrictEqual({ + fees: expect.any(Object), + vsize: expect.any(BigNumber), + weight: expect.any(BigNumber), + fee: expect.any(BigNumber), + modifiedfee: expect.any(BigNumber), + time: expect.any(BigNumber), + height: expect.any(BigNumber), + descendantcount: expect.any(BigNumber), + descendantsize: expect.any(BigNumber), + descendantfees: expect.any(BigNumber), + ancestorcount: expect.any(BigNumber), + ancestorsize: expect.any(BigNumber), + ancestorfees: expect.any(BigNumber), + wtxid: expect.any(String), + depends: expect.any(Array), + spentby: expect.any(Array), + 'bip125-replaceable': expect.any(Boolean) + }) + } + }) + + it('should return array of transaction ids if verbose is false', async () => { + const txIdWithDescendants = await getTxIdWithDescendants() + const mempoolDescendants = await testing.rpc.blockchain.getMempoolDescendants(txIdWithDescendants, false) + expect(mempoolDescendants.length).toBeGreaterThan(0) + for (const descendantId of mempoolDescendants) { + expect(descendantId).toStrictEqual(expect.stringMatching(/^[0-9a-f]{64}$/)) + } + }) + + it('should return array of transaction ids if verbose is undefined', async () => { + const txIdWithDescendants = await getTxIdWithDescendants() + const mempoolDescendants = await testing.rpc.blockchain.getMempoolDescendants(txIdWithDescendants) + expect(mempoolDescendants.length).toBeGreaterThan(0) + for (const descendantId of mempoolDescendants) { + expect(descendantId).toStrictEqual(expect.stringMatching(/^[0-9a-f]{64}$/)) + } + }) +}) diff --git a/packages/jellyfish-api-core/src/category/blockchain.ts b/packages/jellyfish-api-core/src/category/blockchain.ts index d954b2d841..66d1762253 100644 --- a/packages/jellyfish-api-core/src/category/blockchain.ts +++ b/packages/jellyfish-api-core/src/category/blockchain.ts @@ -206,6 +206,36 @@ export class Blockchain { return await this.client.call('getmempoolancestors', [txId, verbose], 'bignumber') } + /** + * Get all in-mempool descendants for a given transaction as string[] + * + * @param {string} txId the transaction id + * @param {boolean} verbose false + * @return {Promise} + */ + getMempoolDescendants (txId: string, verbose?: false): Promise + + /** + * Get all in-mempool descendants for a given transaction as json object + * + * @param {string} txId the transaction id + * @param {boolean} verbose true + * @return {Promise} + */ + getMempoolDescendants (txId: string, verbose?: true): Promise + + /** + * Get all in-mempool descendants if txId is in mempool as string[] if verbose is false + * else as json object + * + * @param {string} txId the transaction id + * @param {boolean} verbose default = false, true for json object, false for array of transaction ids + * @return {Promise} + */ + async getMempoolDescendants (txId: string, verbose: boolean = false): Promise { + return await this.client.call('getmempooldescendants', [txId, verbose], 'bignumber') + } + /** * Get mempool data for the given transaction * @param {string} txId the transaction id