From a2c61fb52e354f5f57103b3a9c023d48ba26c35c Mon Sep 17 00:00:00 2001 From: shancht Date: Fri, 13 Jan 2023 11:12:14 +0800 Subject: [PATCH 1/6] add to wallet.md and wallet.ts --- docs/node/CATEGORIES/05-wallet.md | 23 ++++++++++++++++++ .../jellyfish-api-core/src/category/wallet.ts | 24 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/docs/node/CATEGORIES/05-wallet.md b/docs/node/CATEGORIES/05-wallet.md index 6301aabb02..dc0ff3963f 100644 --- a/docs/node/CATEGORIES/05-wallet.md +++ b/docs/node/CATEGORIES/05-wallet.md @@ -407,3 +407,26 @@ interface wallet { signMessage (address: string, message: string): Promise } ``` +## encryptWallet + +Encrypts the wallet for the first time using a custom ‘passphrase’. Transactions related to private keys will thereafter require a passphrase before execution. + +To unlock the wallet, use [walletPassphrase](#walletPassphrase) + +```ts title="client.wallet.encryptWallet()" +interface wallet { + encryptWallet (passphrase: string): Promise +} +``` + +## walletPassphrase + +Stores the wallet decryption key in memory for ‘timeout’ seconds. Calling `walletPassphrase` when wallet is unlocked will set a new unlock time that overrides the old setting. + +To encrypt the wallet for the first time, use use [encryptWallet](#encryptWallet) + +```ts title="client.wallet.walletPassphrase()" +interface wallet { + walletPassphrase (passphrase: string, timeout: number): Promise +} +``` \ No newline at end of file diff --git a/packages/jellyfish-api-core/src/category/wallet.ts b/packages/jellyfish-api-core/src/category/wallet.ts index 8785b1796d..60c442880e 100644 --- a/packages/jellyfish-api-core/src/category/wallet.ts +++ b/packages/jellyfish-api-core/src/category/wallet.ts @@ -336,6 +336,30 @@ export class Wallet { async signMessage (address: string, message: string): Promise { return await this.client.call('signmessage', [address, message], 'number') } + + /** + * Encrypts the wallet for the first time using a custom ‘passphrase’. + * Transactions related to private keys will thereafter require a passphrase before execution. + * To unlock wallet, use 'walletpassphrase' + * + * @param {string} passphrase The wallet passphrase. Must be at least 1 character, but should be long. + * @return {Promise} + */ + async encryptWallet (passphrase: string): Promise { + return await this.client.call('encryptwallet', [passphrase], 'number') + } + + /** + * Stores the wallet decryption key in memory for ‘timeout’ seconds. + * Calling 'walletpassphrase' when wallet is unlocked will set a new unlock time that overrides the old setting. + * + * @param {string} passphrase The wallet passphrase. Must be at least 1 character, but should be long. + * @param {number} timeout The time to keep the decryption key in seconds; capped at 100000000 (~3 years). + * @return {Promise<>} + */ + async walletPassphrase (passphrase: string, timeout: number): Promise { + return await this.client.call('walletpassphrase', [passphrase, timeout], 'number') + } } export interface UTXO { From 50a2561bfe9222ca84146a09586d02498f9aaa18 Mon Sep 17 00:00:00 2001 From: shana Date: Mon, 6 Feb 2023 13:45:54 +0800 Subject: [PATCH 2/6] added tests to encryptWallet.test.ts --- .../category/wallet/encryptWallet.test.ts | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts new file mode 100644 index 0000000000..2ee9805e89 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts @@ -0,0 +1,60 @@ +import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers' +import { ContainerAdapterClient } from '../../container_adapter_client' + +describe('Wallet without masternode', () => { + const container = new RegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if passphrase for encryptWallet is empty', async () => { + return await expect(client.wallet.encryptWallet('')) + .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") + }) + + it('should encryptWallet with a given passphrase', async () => { + const promise = await client.wallet.encryptWallet('yourpassphrase') + + expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') + }) + + it('should throw error when encryptWallet is called again after encryption', async () => { + return await expect(client.wallet.encryptWallet('yourpassphrase')) + .rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") + }) +}) + +describe('Wallet on masternode', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if passphrase for encryptWallet is empty', async () => { + return await expect(client.wallet.encryptWallet('')) + .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") + }) + + it('should encryptWallet with a given passphrase', async () => { + const promise = await client.wallet.encryptWallet('yourpassphrase') + + expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') + }) + + it('should throw error when encryptWallet is called again after encryption', async () => { + return await expect(client.wallet.encryptWallet('yourpassphrase')) + .rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") + }) +}) From 85165ac350bf294887fdeb9bab47a20d3538eba1 Mon Sep 17 00:00:00 2001 From: shana Date: Mon, 6 Feb 2023 15:26:10 +0800 Subject: [PATCH 3/6] updated tests --- .../category/wallet/encryptWallet.test.ts | 18 ++-- .../category/wallet/walletPassphrase.test.ts | 96 +++++++++++++++++++ 2 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts index 2ee9805e89..7b9639fddf 100644 --- a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts @@ -14,18 +14,17 @@ describe('Wallet without masternode', () => { }) it('should throw error if passphrase for encryptWallet is empty', async () => { - return await expect(client.wallet.encryptWallet('')) + await expect(client.wallet.encryptWallet('')) .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") }) it('should encryptWallet with a given passphrase', async () => { - const promise = await client.wallet.encryptWallet('yourpassphrase') - - expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') + expect(await client.wallet.encryptWallet('yourpassphrase')) + .toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') }) it('should throw error when encryptWallet is called again after encryption', async () => { - return await expect(client.wallet.encryptWallet('yourpassphrase')) + await expect(client.wallet.encryptWallet('yourpassphrase')) .rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") }) }) @@ -43,18 +42,17 @@ describe('Wallet on masternode', () => { }) it('should throw error if passphrase for encryptWallet is empty', async () => { - return await expect(client.wallet.encryptWallet('')) + await expect(client.wallet.encryptWallet('')) .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") }) it('should encryptWallet with a given passphrase', async () => { - const promise = await client.wallet.encryptWallet('yourpassphrase') - - expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') + expect(await client.wallet.encryptWallet('yourpassphrase')) + .toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') }) it('should throw error when encryptWallet is called again after encryption', async () => { - return await expect(client.wallet.encryptWallet('yourpassphrase')) + await expect(client.wallet.encryptWallet('yourpassphrase')) .rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") }) }) diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts new file mode 100644 index 0000000000..1fee5ebcdf --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts @@ -0,0 +1,96 @@ +import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers' +import { ContainerAdapterClient } from '../../container_adapter_client' + +describe('Unencrypted Wallet without masternode', () => { + const container = new RegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if walletPassphrase is called', async () => { + await expect(client.wallet.walletPassphrase('password', -100)) + .rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") + }) +}) + +describe('Encrypted Wallet without masternode', () => { + const container = new RegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + await client.wallet.encryptWallet('password') + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if timeout is negative', async () => { + await expect(client.wallet.walletPassphrase('password', -100)) + .rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") + }) + + it('should throw error if passphrase is empty', async () => { + await expect(client.wallet.walletPassphrase('', 100)) + .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") + }) + + it('should throw error if passphrase is incorrect', async () => { + await expect(client.wallet.walletPassphrase('wrongpassphrase', 100)) + .rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") + }) +}) + +describe('Unencrypted Wallet on masternode', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if walletPassphrase is called', async () => { + await expect(client.wallet.walletPassphrase('password', -100)) + .rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") + }) +}) + +describe('Encrypted Wallet on masternode', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + await client.wallet.encryptWallet('password') + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if timeout is negative', async () => { + await expect(client.wallet.walletPassphrase('password', -100)) + .rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") + }) + + it('should throw error if passphrase is empty', async () => { + await expect(client.wallet.walletPassphrase('', 100)) + .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") + }) + + it('should throw error if passphrase is incorrect', async () => { + await expect(client.wallet.walletPassphrase('wrongpassword', 100)) + .rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") + }) +}) From 746c6c61141f33132aa1af30859c911a565efb50 Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 7 Feb 2023 13:40:27 +0800 Subject: [PATCH 4/6] amended tests with RpcApiError --- .../category/wallet/encryptWallet.test.ts | 35 ++++++++----- .../category/wallet/walletPassphrase.test.ts | 49 +++++++++++++------ 2 files changed, 56 insertions(+), 28 deletions(-) diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts index 7b9639fddf..cde9b72482 100644 --- a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts @@ -1,5 +1,6 @@ import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers' import { ContainerAdapterClient } from '../../container_adapter_client' +import { RpcApiError } from '@defichain/jellyfish-api-core' describe('Wallet without masternode', () => { const container = new RegTestContainer() @@ -14,18 +15,23 @@ describe('Wallet without masternode', () => { }) it('should throw error if passphrase for encryptWallet is empty', async () => { - await expect(client.wallet.encryptWallet('')) - .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") + const promise = client.wallet.encryptWallet('') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") }) it('should encryptWallet with a given passphrase', async () => { - expect(await client.wallet.encryptWallet('yourpassphrase')) - .toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') + const promise = await client.wallet.encryptWallet('yourpassphrase') + + expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') }) it('should throw error when encryptWallet is called again after encryption', async () => { - await expect(client.wallet.encryptWallet('yourpassphrase')) - .rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") + const promise = client.wallet.encryptWallet('yourpassphrase') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") }) }) @@ -42,17 +48,22 @@ describe('Wallet on masternode', () => { }) it('should throw error if passphrase for encryptWallet is empty', async () => { - await expect(client.wallet.encryptWallet('')) - .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") + const promise = client.wallet.encryptWallet('') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") }) it('should encryptWallet with a given passphrase', async () => { - expect(await client.wallet.encryptWallet('yourpassphrase')) - .toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') + const promise = await client.wallet.encryptWallet('yourpassphrase') + + expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') }) it('should throw error when encryptWallet is called again after encryption', async () => { - await expect(client.wallet.encryptWallet('yourpassphrase')) - .rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") + const promise = client.wallet.encryptWallet('yourpassphrase') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") }) }) diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts index 1fee5ebcdf..737c1c05da 100644 --- a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts @@ -1,5 +1,6 @@ import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers' import { ContainerAdapterClient } from '../../container_adapter_client' +import { RpcApiError } from '@defichain/jellyfish-api-core' describe('Unencrypted Wallet without masternode', () => { const container = new RegTestContainer() @@ -14,8 +15,10 @@ describe('Unencrypted Wallet without masternode', () => { }) it('should throw error if walletPassphrase is called', async () => { - await expect(client.wallet.walletPassphrase('password', -100)) - .rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('password', -100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") }) }) @@ -33,18 +36,24 @@ describe('Encrypted Wallet without masternode', () => { }) it('should throw error if timeout is negative', async () => { - await expect(client.wallet.walletPassphrase('password', -100)) - .rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('password', -100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") }) it('should throw error if passphrase is empty', async () => { - await expect(client.wallet.walletPassphrase('', 100)) - .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('', 100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") }) it('should throw error if passphrase is incorrect', async () => { - await expect(client.wallet.walletPassphrase('wrongpassphrase', 100)) - .rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('incorrectpassword', 100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") }) }) @@ -61,8 +70,10 @@ describe('Unencrypted Wallet on masternode', () => { }) it('should throw error if walletPassphrase is called', async () => { - await expect(client.wallet.walletPassphrase('password', -100)) - .rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('password', -100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") }) }) @@ -80,17 +91,23 @@ describe('Encrypted Wallet on masternode', () => { }) it('should throw error if timeout is negative', async () => { - await expect(client.wallet.walletPassphrase('password', -100)) - .rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('password', -100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") }) it('should throw error if passphrase is empty', async () => { - await expect(client.wallet.walletPassphrase('', 100)) - .rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('', 100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") }) it('should throw error if passphrase is incorrect', async () => { - await expect(client.wallet.walletPassphrase('wrongpassword', 100)) - .rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") + const promise = client.wallet.walletPassphrase('incorrectpassword', 100) + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") }) }) From 82ebac386a778008d84c105c07a25813bcf838be Mon Sep 17 00:00:00 2001 From: shana Date: Tue, 7 Feb 2023 15:36:36 +0800 Subject: [PATCH 5/6] removed RegTestContainer tests --- docs/node/CATEGORIES/05-wallet.md | 1 + .../category/wallet/encryptWallet.test.ts | 35 +---------- .../category/wallet/walletPassphrase.test.ts | 63 +++---------------- 3 files changed, 9 insertions(+), 90 deletions(-) diff --git a/docs/node/CATEGORIES/05-wallet.md b/docs/node/CATEGORIES/05-wallet.md index dc0ff3963f..0080bc21ff 100644 --- a/docs/node/CATEGORIES/05-wallet.md +++ b/docs/node/CATEGORIES/05-wallet.md @@ -407,6 +407,7 @@ interface wallet { signMessage (address: string, message: string): Promise } ``` + ## encryptWallet Encrypts the wallet for the first time using a custom ‘passphrase’. Transactions related to private keys will thereafter require a passphrase before execution. diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts index cde9b72482..becb109cbe 100644 --- a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts @@ -1,40 +1,7 @@ -import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers' +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' import { ContainerAdapterClient } from '../../container_adapter_client' import { RpcApiError } from '@defichain/jellyfish-api-core' -describe('Wallet without masternode', () => { - const container = new RegTestContainer() - const client = new ContainerAdapterClient(container) - - beforeAll(async () => { - await container.start() - }) - - afterAll(async () => { - await container.stop() - }) - - it('should throw error if passphrase for encryptWallet is empty', async () => { - const promise = client.wallet.encryptWallet('') - - await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") - }) - - it('should encryptWallet with a given passphrase', async () => { - const promise = await client.wallet.encryptWallet('yourpassphrase') - - expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') - }) - - it('should throw error when encryptWallet is called again after encryption', async () => { - const promise = client.wallet.encryptWallet('yourpassphrase') - - await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") - }) -}) - describe('Wallet on masternode', () => { const container = new MasterNodeRegTestContainer() const client = new ContainerAdapterClient(container) diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts index 737c1c05da..d36720f534 100644 --- a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts @@ -1,62 +1,7 @@ -import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers' +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' import { ContainerAdapterClient } from '../../container_adapter_client' import { RpcApiError } from '@defichain/jellyfish-api-core' -describe('Unencrypted Wallet without masternode', () => { - const container = new RegTestContainer() - const client = new ContainerAdapterClient(container) - - beforeAll(async () => { - await container.start() - }) - - afterAll(async () => { - await container.stop() - }) - - it('should throw error if walletPassphrase is called', async () => { - const promise = client.wallet.walletPassphrase('password', -100) - - await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") - }) -}) - -describe('Encrypted Wallet without masternode', () => { - const container = new RegTestContainer() - const client = new ContainerAdapterClient(container) - - beforeAll(async () => { - await container.start() - await client.wallet.encryptWallet('password') - }) - - afterAll(async () => { - await container.stop() - }) - - it('should throw error if timeout is negative', async () => { - const promise = client.wallet.walletPassphrase('password', -100) - - await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") - }) - - it('should throw error if passphrase is empty', async () => { - const promise = client.wallet.walletPassphrase('', 100) - - await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") - }) - - it('should throw error if passphrase is incorrect', async () => { - const promise = client.wallet.walletPassphrase('incorrectpassword', 100) - - await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") - }) -}) - describe('Unencrypted Wallet on masternode', () => { const container = new MasterNodeRegTestContainer() const client = new ContainerAdapterClient(container) @@ -110,4 +55,10 @@ describe('Encrypted Wallet on masternode', () => { await expect(promise).rejects.toThrow(RpcApiError) await expect(promise).rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") }) + + it('should unlock wallet without errors', async () => { + const promise = client.wallet.walletPassphrase('password', 100) + + await expect(promise).resolves.not.toThrow() + }) }) From a765ddee39cd991c8fc1a403389067d31ea62250 Mon Sep 17 00:00:00 2001 From: shana Date: Fri, 10 Feb 2023 06:54:19 +0800 Subject: [PATCH 6/6] add walletLock & walletPassphraseChange --- docs/node/CATEGORIES/05-wallet.md | 26 +++++++- .../category/wallet/encryptWallet.test.ts | 53 ++++++++++++++-- .../category/wallet/walletLock.test.ts | 48 ++++++++++++++ .../category/wallet/walletPassphrase.test.ts | 30 +++++---- .../wallet/walletPassphraseChange.test.ts | 63 +++++++++++++++++++ .../jellyfish-api-core/src/category/wallet.ts | 23 ++++++- 6 files changed, 225 insertions(+), 18 deletions(-) create mode 100644 packages/jellyfish-api-core/__tests__/category/wallet/walletLock.test.ts create mode 100644 packages/jellyfish-api-core/__tests__/category/wallet/walletPassphraseChange.test.ts diff --git a/docs/node/CATEGORIES/05-wallet.md b/docs/node/CATEGORIES/05-wallet.md index 0080bc21ff..ae8e8ea6d6 100644 --- a/docs/node/CATEGORIES/05-wallet.md +++ b/docs/node/CATEGORIES/05-wallet.md @@ -424,10 +424,32 @@ interface wallet { Stores the wallet decryption key in memory for ‘timeout’ seconds. Calling `walletPassphrase` when wallet is unlocked will set a new unlock time that overrides the old setting. -To encrypt the wallet for the first time, use use [encryptWallet](#encryptWallet) +To encrypt the wallet for the first time, use [encryptWallet](#encryptWallet) ```ts title="client.wallet.walletPassphrase()" interface wallet { - walletPassphrase (passphrase: string, timeout: number): Promise + walletPassphrase (passphrase: string, timeout: number): Promise +} +``` + +## walletPassphraseChange + +Changes the wallet passphrase from ‘oldpassphrase’ to ‘newpassphrase’. + +```ts title="client.wallet.walletPassphraseChange()" +interface wallet { + walletPassphraseChange (oldpassphrase: string, newpassphrase: string): Promise +} +``` + +## walletLock + +Removes the wallet encryption key from memory, locking the wallet. + +After locking the wallet, `walletPassphrase` must be called again to use methods that requires an unlocked wallet. + +```ts title="client.wallet.walletLock()" +interface wallet { + walletLock (): Promise } ``` \ No newline at end of file diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts index becb109cbe..4f140a1896 100644 --- a/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/wallet/encryptWallet.test.ts @@ -1,4 +1,4 @@ -import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { MasterNodeRegTestContainer, RegTestContainer } from '@defichain/testcontainers' import { ContainerAdapterClient } from '../../container_adapter_client' import { RpcApiError } from '@defichain/jellyfish-api-core' @@ -18,19 +18,64 @@ describe('Wallet on masternode', () => { const promise = client.wallet.encryptWallet('') await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet") + await expect(promise).rejects.toThrow( + "RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet" + ) }) it('should encryptWallet with a given passphrase', async () => { const promise = await client.wallet.encryptWallet('yourpassphrase') - expect(promise).toStrictEqual('wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.') + expect(promise).toStrictEqual( + 'wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.' + ) }) it('should throw error when encryptWallet is called again after encryption', async () => { const promise = client.wallet.encryptWallet('yourpassphrase') await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet") + await expect(promise).rejects.toThrow( + "RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet" + ) + }) +}) + +describe('Wallet without masternode', () => { + const container = new RegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error if passphrase for wallet encryption is empty', async () => { + const promise = client.wallet.encryptWallet('') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow( + "RpcApiError: 'passphrase can not be empty', code: -8, method: encryptwallet" + ) + }) + + it('should encryptWallet with a given passphrase', async () => { + const promise = await client.wallet.encryptWallet('yourpassphrase') + + expect(promise).toStrictEqual( + 'wallet encrypted; The keypool has been flushed and a new HD seed was generated (if you are using HD). You need to make a new backup.' + ) + }) + + it('should throw error when encryptWallet is called again after encryption', async () => { + const promise = client.wallet.encryptWallet('yourpassphrase') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow( + "RpcApiError: 'Error: running with an encrypted wallet, but encryptwallet was called.', code: -15, method: encryptwallet" + ) }) }) diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/walletLock.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/walletLock.test.ts new file mode 100644 index 0000000000..234047916d --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/wallet/walletLock.test.ts @@ -0,0 +1,48 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { ContainerAdapterClient } from '../../container_adapter_client' +import { RpcApiError } from '@defichain/jellyfish-api-core' + +describe('Unencrypted wallet on masternode', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error when walletLock is called', async () => { + const promise = client.wallet.walletLock() + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow( + "RpcApiError: 'Error: running with an unencrypted wallet, but walletlock was called.', code: -15, method: walletlock" + ) + }) +}) + +describe('Encrypted wallet on masternode', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + await client.wallet.encryptWallet('password') + await client.wallet.walletPassphrase('password', 10000) + }) + + afterAll(async () => { + await container.stop() + }) + + it('should walletLock without failing', async () => { + const method = client.wallet.importPrivKey(await client.wallet.dumpPrivKey(await client.wallet.getNewAddress())) + const promise = client.wallet.walletLock() + + await expect(method).resolves.not.toThrow() + await expect(promise).resolves.not.toThrow() + }) +}) diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts index d36720f534..ac23a7108d 100644 --- a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts +++ b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphrase.test.ts @@ -2,7 +2,7 @@ import { MasterNodeRegTestContainer } from '@defichain/testcontainers' import { ContainerAdapterClient } from '../../container_adapter_client' import { RpcApiError } from '@defichain/jellyfish-api-core' -describe('Unencrypted Wallet on masternode', () => { +describe('Unencrypted wallet on masternode', () => { const container = new MasterNodeRegTestContainer() const client = new ContainerAdapterClient(container) @@ -14,15 +14,17 @@ describe('Unencrypted Wallet on masternode', () => { await container.stop() }) - it('should throw error if walletPassphrase is called', async () => { + it('should throw error when walletPassphrase is called', async () => { const promise = client.wallet.walletPassphrase('password', -100) await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase") + await expect(promise).rejects.toThrow( + "RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrase was called.', code: -15, method: walletpassphrase" + ) }) }) -describe('Encrypted Wallet on masternode', () => { +describe('Encrypted wallet on masternode', () => { const container = new MasterNodeRegTestContainer() const client = new ContainerAdapterClient(container) @@ -35,28 +37,34 @@ describe('Encrypted Wallet on masternode', () => { await container.stop() }) - it('should throw error if timeout is negative', async () => { + it('should throw error when walletPassphrase is called with a negative timeout', async () => { const promise = client.wallet.walletPassphrase('password', -100) await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase") + await expect(promise).rejects.toThrow( + "RpcApiError: 'Timeout cannot be negative.', code: -8, method: walletpassphrase" + ) }) - it('should throw error if passphrase is empty', async () => { + it('should throw error when walletPassphrase is called without a passphrase', async () => { const promise = client.wallet.walletPassphrase('', 100) await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase") + await expect(promise).rejects.toThrow( + "RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrase" + ) }) - it('should throw error if passphrase is incorrect', async () => { + it('should throw error when walletPassphrase is called with a wrong passphrase', async () => { const promise = client.wallet.walletPassphrase('incorrectpassword', 100) await expect(promise).rejects.toThrow(RpcApiError) - await expect(promise).rejects.toThrow("RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase") + await expect(promise).rejects.toThrow( + "RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrase" + ) }) - it('should unlock wallet without errors', async () => { + it('should unlock wallet when walletPassphrase is called with the correct passphrase', async () => { const promise = client.wallet.walletPassphrase('password', 100) await expect(promise).resolves.not.toThrow() diff --git a/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphraseChange.test.ts b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphraseChange.test.ts new file mode 100644 index 0000000000..063d662533 --- /dev/null +++ b/packages/jellyfish-api-core/__tests__/category/wallet/walletPassphraseChange.test.ts @@ -0,0 +1,63 @@ +import { MasterNodeRegTestContainer } from '@defichain/testcontainers' +import { ContainerAdapterClient } from '../../container_adapter_client' +import { RpcApiError } from '@defichain/jellyfish-api-core' + +describe('Unencrypted Wallet on masternode', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error when walletPassphraseChange is called', async () => { + const promise = client.wallet.walletPassphraseChange('wrongpassword', 'newpassword') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow( + "RpcApiError: 'Error: running with an unencrypted wallet, but walletpassphrasechange was called.', code: -15, method: walletpassphrasechange" + ) + }) +}) + +describe('Encrypted Wallet on masternode', () => { + const container = new MasterNodeRegTestContainer() + const client = new ContainerAdapterClient(container) + + beforeAll(async () => { + await container.start() + await client.wallet.encryptWallet('password') + }) + + afterAll(async () => { + await container.stop() + }) + + it('should throw error when walletPassphraseChange is called with an incorrect passphrase', async () => { + const promise = client.wallet.walletPassphraseChange('wrongpassword', 'newpassword') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow( + "RpcApiError: 'Error: The wallet passphrase entered was incorrect.', code: -14, method: walletpassphrasechange" + ) + }) + + it('should throw error when walletPassphraseChange is called with an empty passphrase', async () => { + const promise = client.wallet.walletPassphraseChange('', 'newpassword') + + await expect(promise).rejects.toThrow(RpcApiError) + await expect(promise).rejects.toThrow( + "RpcApiError: 'passphrase can not be empty', code: -8, method: walletpassphrasechange" + ) + }) + + it('should walletPassphraseChange when the correct old passphrase is provided', async () => { + const promise = client.wallet.walletPassphraseChange('password', 'newpassword') + + await expect(promise).resolves.not.toThrow() + }) +}) diff --git a/packages/jellyfish-api-core/src/category/wallet.ts b/packages/jellyfish-api-core/src/category/wallet.ts index 60c442880e..b2bbb71b73 100644 --- a/packages/jellyfish-api-core/src/category/wallet.ts +++ b/packages/jellyfish-api-core/src/category/wallet.ts @@ -357,9 +357,30 @@ export class Wallet { * @param {number} timeout The time to keep the decryption key in seconds; capped at 100000000 (~3 years). * @return {Promise<>} */ - async walletPassphrase (passphrase: string, timeout: number): Promise { + async walletPassphrase (passphrase: string, timeout: number): Promise { return await this.client.call('walletpassphrase', [passphrase, timeout], 'number') } + + /** + * Changes the wallet passphrase from ‘oldpassphrase’ to ‘newpassphrase’. + * + * @param {string} oldpassphrase The old wallet passphrase. + * @param {string} newpassphrase The new wallet passphrase. + * @return {Promise<>} + */ + async walletPassphraseChange (oldpassphrase: string, newpassphrase: string): Promise { + return await this.client.call('walletpassphrasechange', [oldpassphrase, newpassphrase], 'number') + } + + /** + * Removes the wallet encryption key from memory, locking the wallet. + * Unlock wallet by calling 'walletpassphrase' to perform wallet-related methods. + * + * @return {Promise<>} + */ + async walletLock (): Promise { + return await this.client.call('walletlock', [], 'number') + } } export interface UTXO {