From cb47760b69c6740968af19f5418a749d6c3c93c3 Mon Sep 17 00:00:00 2001 From: jouzo Date: Mon, 16 Aug 2021 12:42:03 +0200 Subject: [PATCH 1/3] remove packages/.idea --- .../category/spv/listHtlcsOutputs.test.ts | 76 +++++++++++++++++++ .../__tests__/category/spv/refundHtlc.test.ts | 2 - .../jellyfish-api-core/src/category/spv.ts | 32 ++++++++ website/docs/jellyfish/api/spv.md | 24 ++++++ 4 files changed, 132 insertions(+), 2 deletions(-) create mode 100644 packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts diff --git a/packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts b/packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts new file mode 100644 index 0000000000..a45cb7f40e --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts @@ -0,0 +1,76 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { RpcApiError } from '@defichain/jellyfish-api-core' +import { ContainerAdapterClient } from '../../container_adapter_client' +import BigNumber from 'bignumber.js' + +describe('Spv', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + await container.call('spv_fundaddress', [await container.call('spv_getnewaddress')]) // Funds 1 BTC + }) + + afterAll(async () => { + await container.stop() + }) + + it('should listHtlcOutputs', async () => { + const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) + const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) + const timeout = 10 + const seed = 'aba5f7e9aecf6ec4372c8a1e49562680d066da4655ee8b4bb01640479fffeaa8' + const seedhash = 'df95183883789f237977543885e1f82ddc045a3ba90c8f25b43a5b797a35d20e' + + const htlc = await client.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) + await container.generate(1) + const fund = await container.call('spv_sendtoaddress', [htlc.address, 0.1]) // Funds HTLC address + const claim = await client.spv.claimHtlc( + htlc.address, + await container.call('spv_getnewaddress'), + { seed } + ) + + const list = await client.spv.listHtlcOutputs() + expect(list.length).toStrictEqual(1) + expect(list[0]).toStrictEqual({ + txid: fund.txid, + vout: expect.any(Number), + amount: new BigNumber(0.1), + address: htlc.address, + confirms: 1, + spent: { + txid: claim.txid, + confirms: 1 + } + }) + }) + + it('listHtlcOutputs should return empty list when called with a new address', async () => { + const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) + const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) + const timeout = 10 + const seed = 'aba5f7e9aecf6ec4372c8a1e49562680d066da4655ee8b4bb01640479fffeaa8' + const seedhash = 'df95183883789f237977543885e1f82ddc045a3ba90c8f25b43a5b797a35d20e' + + const htlc = await client.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) + await container.generate(1) + await container.call('spv_sendtoaddress', [htlc.address, 0.1]) // Funds HTLC address + await client.spv.claimHtlc( + htlc.address, + await container.call('spv_getnewaddress'), + { seed } + ) + + const list = await client.spv.listHtlcOutputs(await container.call('spv_getnewaddress')) + expect(list.length).toStrictEqual(0) + }) + + it('should not listHtlcOutputs with invalid public address', async () => { + const promise = client.spv.listHtlcOutputs('XXXX') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Invalid address', code: -5, method: spv_listhtlcoutputs") + }) +}) diff --git a/packages/jellyfish-api-core/__tests__/category/spv/refundHtlc.test.ts b/packages/jellyfish-api-core/__tests__/category/spv/refundHtlc.test.ts index 843c5bce96..681ac56c2b 100644 --- a/packages/jellyfish-api-core/__tests__/category/spv/refundHtlc.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/spv/refundHtlc.test.ts @@ -8,8 +8,6 @@ describe('Spv', () => { beforeAll(async () => { await container.start() - await container.waitForReady() - await container.spv.fundAddress(await container.call('spv_getnewaddress')) // Funds 1 BTC }) diff --git a/packages/jellyfish-api-core/src/category/spv.ts b/packages/jellyfish-api-core/src/category/spv.ts index c2270f2aeb..da74777c50 100644 --- a/packages/jellyfish-api-core/src/category/spv.ts +++ b/packages/jellyfish-api-core/src/category/spv.ts @@ -119,6 +119,16 @@ export class Spv { async refundHtlc (scriptAddress: string, destinationAddress: string, options: SpvDefaultOptions = { feeRate: new BigNumber('10000') }): Promise { return await this.client.call('spv_refundhtlc', [scriptAddress, destinationAddress, options.feeRate], 'number') } + + /** + * List all outputs related to HTLC addresses in the wallet. + * + * @param {string | undefined} [scriptAddress] HTLC address to filter result + * @return {Promise} + */ + async listHtlcOutputs (scriptAddress?: string): Promise { + return await this.client.call('spv_listhtlcoutputs', [scriptAddress], { amount: 'bignumber' }) + } } export interface ReceivedByAddressInfo { @@ -179,3 +189,25 @@ export interface ClaimHtlcOptions { /** Fee rate in satoshis per KB */ feeRate?: BigNumber } + +export interface SpentInfo { + /** The transaction id */ + txid: string + /** Number of spent confirmations */ + confirms: number +} + +export interface ListHtlcsOutputsResult { + /** The transaction id */ + txid: string + /** Output relating to the HTLC address */ + vout: number + /** Total amount of BTC recieved by the address */ + amount: BigNumber + /** HTLC address */ + address: string + /** Number of confirmations */ + confirms: number + /** Object containing spent info */ + spent: SpentInfo +} diff --git a/website/docs/jellyfish/api/spv.md b/website/docs/jellyfish/api/spv.md index 20ab802e43..98160fd149 100644 --- a/website/docs/jellyfish/api/spv.md +++ b/website/docs/jellyfish/api/spv.md @@ -157,3 +157,27 @@ interface SendMessageResult { sendmessage: string } ``` + +## listHtlcOutputs + +List all outputs related to HTLC addresses in the wallet. + +```ts title="client.spv.listHtlcOutputs()" +interface spv { + listHtlcOutputs (scriptAddress?: string): Promise +} + +interface SpentInfo { + txid: string + confirms: number +} + +interface ListHtlcsOutputsResult { + txid: string + vout: number + amount: BigNumber + address: string + confirms: number + spent: SpentInfo +} +``` From 13647cd364f9392b048575c7c9e2924d8a0e116b Mon Sep 17 00:00:00 2001 From: jouzo Date: Mon, 16 Aug 2021 12:43:20 +0200 Subject: [PATCH 2/3] Added listHtlcOutputs RPC From 9112783cf0a5756d773352cc27443ab672249f76 Mon Sep 17 00:00:00 2001 From: jouzo Date: Mon, 16 Aug 2021 13:15:26 +0200 Subject: [PATCH 3/3] Adds test for multiple HTLC and specific HTLC address as filter + Refactor using Testing class from jellyfish-testing --- .../category/spv/listHtlcsOutputs.test.ts | 121 ++++++++++++++---- 1 file changed, 97 insertions(+), 24 deletions(-) diff --git a/packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts b/packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts index a45cb7f40e..5930011fd2 100644 --- a/packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/spv/listHtlcsOutputs.test.ts @@ -1,38 +1,37 @@ import { MasterNodeRegTestContainer } from '@defichain/testcontainers' import { RpcApiError } from '@defichain/jellyfish-api-core' -import { ContainerAdapterClient } from '../../container_adapter_client' import BigNumber from 'bignumber.js' +import { Testing } from '@defichain/jellyfish-testing' describe('Spv', () => { - const container = new MasterNodeRegTestContainer() - const client = new ContainerAdapterClient(container) + const testing = Testing.create(new MasterNodeRegTestContainer()) beforeAll(async () => { - await container.start() - await container.call('spv_fundaddress', [await container.call('spv_getnewaddress')]) // Funds 1 BTC + await testing.container.start() + await testing.container.spv.fundAddress(await testing.rpc.spv.getNewAddress()) // Funds 1 BTC }) afterAll(async () => { - await container.stop() + await testing.container.stop() }) it('should listHtlcOutputs', async () => { - const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) - const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) + const pubKeyA = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) + const pubKeyB = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) const timeout = 10 const seed = 'aba5f7e9aecf6ec4372c8a1e49562680d066da4655ee8b4bb01640479fffeaa8' const seedhash = 'df95183883789f237977543885e1f82ddc045a3ba90c8f25b43a5b797a35d20e' - const htlc = await client.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) - await container.generate(1) - const fund = await container.call('spv_sendtoaddress', [htlc.address, 0.1]) // Funds HTLC address - const claim = await client.spv.claimHtlc( + const htlc = await testing.rpc.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) + await testing.container.generate(1) + const fund = await testing.rpc.spv.sendToAddress(htlc.address, new BigNumber(0.1)) // Funds HTLC address + const claim = await testing.rpc.spv.claimHtlc( htlc.address, - await container.call('spv_getnewaddress'), + await testing.rpc.spv.getNewAddress(), { seed } ) - const list = await client.spv.listHtlcOutputs() + const list = await testing.rpc.spv.listHtlcOutputs() expect(list.length).toStrictEqual(1) expect(list[0]).toStrictEqual({ txid: fund.txid, @@ -47,30 +46,104 @@ describe('Spv', () => { }) }) - it('listHtlcOutputs should return empty list when called with a new address', async () => { - const pubKeyA = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) - const pubKeyB = await container.call('spv_getaddresspubkey', [await container.call('spv_getnewaddress')]) + it('listHtlcOutputs should return empty list when called with a non HTLC address', async () => { + const pubKeyA = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) + const pubKeyB = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) const timeout = 10 const seed = 'aba5f7e9aecf6ec4372c8a1e49562680d066da4655ee8b4bb01640479fffeaa8' const seedhash = 'df95183883789f237977543885e1f82ddc045a3ba90c8f25b43a5b797a35d20e' - const htlc = await client.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) - await container.generate(1) - await container.call('spv_sendtoaddress', [htlc.address, 0.1]) // Funds HTLC address - await client.spv.claimHtlc( + const htlc = await testing.rpc.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) + await testing.container.generate(1) + await testing.rpc.spv.sendToAddress(htlc.address, new BigNumber(0.1)) // Funds HTLC address + await testing.rpc.spv.claimHtlc( htlc.address, - await container.call('spv_getnewaddress'), + await testing.rpc.spv.getNewAddress(), { seed } ) - const list = await client.spv.listHtlcOutputs(await container.call('spv_getnewaddress')) + const list = await testing.rpc.spv.listHtlcOutputs(await testing.rpc.spv.getNewAddress()) expect(list.length).toStrictEqual(0) }) it('should not listHtlcOutputs with invalid public address', async () => { - const promise = client.spv.listHtlcOutputs('XXXX') + const promise = testing.rpc.spv.listHtlcOutputs('XXXX') await expect(promise).rejects.toThrow(RpcApiError) await expect(promise).rejects.toThrow("RpcApiError: 'Invalid address', code: -5, method: spv_listhtlcoutputs") }) }) + +describe('Spv with multiple HTLC', () => { + const testing = Testing.create(new MasterNodeRegTestContainer()) + + beforeAll(async () => { + await testing.container.start() + await testing.container.spv.fundAddress(await testing.rpc.spv.getNewAddress()) // Funds 1 BTC + }) + + afterAll(async () => { + await testing.container.stop() + }) + + it('should listHtlcOutputs with multiple HTLC', async () => { + const pubKeyA = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) + const pubKeyB = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) + const timeout = 10 + const seedhash = 'df95183883789f237977543885e1f82ddc045a3ba90c8f25b43a5b797a35d20e' + + const htlc = await testing.rpc.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) + const htlc2 = await testing.rpc.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) // Creates a second HTLC + await testing.container.generate(1) + const fund = await testing.rpc.spv.sendToAddress(htlc.address, new BigNumber(0.1)) // Funds HTLC address + const fund2 = await testing.rpc.spv.sendToAddress(htlc2.address, new BigNumber(0.1)) // Funds second HTLC address + + const list = await testing.rpc.spv.listHtlcOutputs() + expect(list.length).toStrictEqual(2) + expect(list[0]).toStrictEqual({ + txid: fund.txid, + vout: expect.any(Number), + amount: new BigNumber(0.1), + address: htlc.address, + confirms: 1 + }) + expect(list[1]).toStrictEqual({ + txid: fund2.txid, + vout: expect.any(Number), + amount: new BigNumber(0.1), + address: htlc2.address, + confirms: 1 + }) + }) + + it('should listHtlcOutputs with specific HTLC address as filter', async () => { + const pubKeyA = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) + const pubKeyB = await testing.rpc.spv.getAddressPubKey(await testing.rpc.spv.getNewAddress()) + const timeout = 10 + const seed = 'aba5f7e9aecf6ec4372c8a1e49562680d066da4655ee8b4bb01640479fffeaa8' + const seedhash = 'df95183883789f237977543885e1f82ddc045a3ba90c8f25b43a5b797a35d20e' + + const htlc = await testing.rpc.spv.createHtlc(pubKeyA, pubKeyB, { timeout: `${timeout}`, seedhash }) + await testing.container.generate(1) + const fund = await testing.rpc.spv.sendToAddress(htlc.address, new BigNumber(0.1)) // Funds HTLC address + const claim = await testing.rpc.spv.claimHtlc( + htlc.address, + await testing.rpc.spv.getNewAddress(), + { seed } + ) + + const list = await testing.rpc.spv.listHtlcOutputs(htlc.address) + expect(list.length).toStrictEqual(1) + expect(list[0]).toStrictEqual({ + txid: fund.txid, + vout: expect.any(Number), + amount: new BigNumber(0.1), + address: htlc.address, + confirms: 1, + spent: { + txid: claim.txid, + confirms: 1 + } + }) + }) +})