From 8c773dab4a9ea05ddfccc7319e6e2e8e46d4f8c0 Mon Sep 17 00:00:00 2001 From: air1one <36802613+air1one@users.noreply.github.com> Date: Sat, 31 Aug 2019 03:14:40 +0400 Subject: [PATCH] fix: move wallet manager "zero balance" check to transaction handlers (#2896) --- __tests__/e2e/lib/utils/test-utils.js | 116 ++---------------- .../e2e/tests/scenarios/scenario1/config.js | 3 + .../0.transfer-new-wallet.action.js | 27 ++++ .../htlc-claim/1.create-lock-txs.action.js | 92 ++++++++++++++ .../scenario1/htlc-claim/2.check-lock.test.js | 25 ++++ .../htlc-claim/3.create-claim-txs.action.js | 62 ++++++++++ .../htlc-claim/4.check-claim.test.js | 31 +++++ .../scenarios/scenario1/htlc-claim/config.js | 25 ++++ .../scenarios/scenario1/htlc-claim/shared.js | 16 +++ .../scenarios/scenario1/htlc-claim/utils.js | 26 ++++ .../0.transfer-new-wallet.action.js | 27 ++++ .../htlc-refund/1.create-lock-txs.action.js | 74 +++++++++++ .../htlc-refund/2.check-lock.test.js | 25 ++++ .../htlc-refund/3.create-refund-txs.action.js | 49 ++++++++ .../htlc-refund/4.check-refund.test.js | 31 +++++ .../scenarios/scenario1/htlc-refund/config.js | 25 ++++ .../scenarios/scenario1/htlc-refund/shared.js | 14 +++ .../scenarios/scenario1/htlc-refund/utils.js | 26 ++++ .../0.transfer-new-wallet.action.js | 27 ++++ .../1.create-multisig-registration.action.js | 35 ++++++ .../2.check-registration.test.js | 24 ++++ .../3.transfer-multisig-wallet.action.js | 29 +++++ .../4.create-multisig-txs.action.js | 35 ++++++ .../multisignature/5.check-multisig.test.js | 23 ++++ .../scenario1/multisignature/config.js | 26 ++++ .../scenario1/multisignature/shared.js | 5 + .../scenario1/multisignature/utils.js | 30 +++++ .../core-transaction-pool/processor.test.ts | 3 +- .../src/wallet-manager.ts | 17 +-- packages/core-transactions/src/errors.ts | 6 + .../src/handlers/transaction.ts | 8 ++ 31 files changed, 841 insertions(+), 121 deletions(-) create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/0.transfer-new-wallet.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/1.create-lock-txs.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/2.check-lock.test.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/3.create-claim-txs.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/4.check-claim.test.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/config.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/shared.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-claim/utils.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/0.transfer-new-wallet.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/1.create-lock-txs.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/2.check-lock.test.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/3.create-refund-txs.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/4.check-refund.test.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/config.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/shared.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/htlc-refund/utils.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/0.transfer-new-wallet.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/1.create-multisig-registration.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/2.check-registration.test.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/3.transfer-multisig-wallet.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/4.create-multisig-txs.action.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/5.check-multisig.test.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/config.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/shared.js create mode 100644 __tests__/e2e/tests/scenarios/scenario1/multisignature/utils.js diff --git a/__tests__/e2e/lib/utils/test-utils.js b/__tests__/e2e/lib/utils/test-utils.js index a3e90dbb34..2759f8629b 100644 --- a/__tests__/e2e/lib/utils/test-utils.js +++ b/__tests__/e2e/lib/utils/test-utils.js @@ -1,5 +1,7 @@ "use strict"; +const { Managers, Identities } = require("@arkecosystem/crypto"); +const { generateMnemonic } = require("bip39"); const axios = require("axios"); axios.defaults.adapter = require("axios/lib/adapters/http"); @@ -65,119 +67,25 @@ class Helpers { expect(response.status).toBe(code); } - expectResource(response) { - expect(response.data.data).toBeObject(); - } - - expectCollection(response) { - expect(Array.isArray(response.data.data)).toBe(true); - } - - expectPaginator(response, firstPage = true) { - expect(response.data.meta).toBeObject(); - expect(response.data.meta).toHaveProperty("count"); - expect(response.data.meta).toHaveProperty("pageCount"); - expect(response.data.meta).toHaveProperty("totalCount"); - expect(response.data.meta).toHaveProperty("next"); - expect(response.data.meta).toHaveProperty("previous"); - expect(response.data.meta).toHaveProperty("self"); - expect(response.data.meta).toHaveProperty("first"); - expect(response.data.meta).toHaveProperty("last"); - } - expectSuccessful(response, statusCode = 200) { this.expectStatus(response, statusCode); this.expectJson(response); } - expectError(response, statusCode = 404) { - this.expectStatus(response, statusCode); - this.expectJson(response); - expect(response.data.statusCode).toBeNumber(); - expect(response.data.error).toBeString(); - expect(response.data.message).toBeString(); - } + generateWallets(walletsNames = []) { + Managers.configManager.setFromPreset("testnet"); + const wallets = {}; - expectTransaction(transaction) { - expect(transaction).toBeObject(); - expect(transaction).toHaveProperty("id"); - expect(transaction).toHaveProperty("blockId"); - expect(transaction).toHaveProperty("type"); - expect(transaction).toHaveProperty("amount"); - expect(transaction).toHaveProperty("fee"); - expect(transaction).toHaveProperty("sender"); - - if ([1, 2].indexOf(transaction.type) === -1) { - expect(transaction.recipient).toBeString(); + for (const walletName of walletsNames) { + const passphrase = generateMnemonic(); + wallets[walletName] = { + passphrase, + address: Identities.Address.fromPassphrase(passphrase) + } } - expect(transaction.signature).toBeString(); - expect(transaction.confirmations).toBeNumber(); + return wallets; } - - expectBlock(block) { - expect(block).toBeObject(); - expect(block).toHaveProperty("id"); - expect(block).toHaveProperty("version"); - expect(block).toHaveProperty("height"); - expect(block).toHaveProperty("previous"); - expect(block).toHaveProperty("forged"); - expect(block.forged).toHaveProperty("reward"); - expect(block.forged).toHaveProperty("fee"); - expect(block).toHaveProperty("payload"); - expect(block.payload).toHaveProperty("length"); - expect(block.payload).toHaveProperty("hash"); - expect(block).toHaveProperty("generator"); - expect(block.generator).toHaveProperty("publicKey"); - expect(block).toHaveProperty("signature"); - expect(block).toHaveProperty("transactions"); - } - - expectDelegate(delegate, expected) { - expect(delegate).toBeObject(); - expect(delegate.username).toBeString(); - expect(delegate.address).toBeString(); - expect(delegate.publicKey).toBeString(); - expect(delegate.votes).toBeNumber(); - expect(delegate.rank).toBeNumber(); - expect(delegate.blocks).toBeObject(); - expect(delegate.blocks.missed).toBeNumber(); - expect(delegate.blocks.produced).toBeNumber(); - expect(delegate.production).toBeObject(); - expect(delegate.production.approval).toBeString(); - expect(delegate.production.productivity).toBeString(); - - Object.keys(expected || {}).forEach(attr => { - expect(delegate[attr]).toBe(expected[attr]); - }); - } - - expectWallet(wallet) { - expect(wallet).toBeObject(); - expect(wallet).toHaveProperty("address"); - expect(wallet).toHaveProperty("publicKey"); - expect(wallet).toHaveProperty("balance"); - expect(wallet).toHaveProperty("isDelegate"); - } - - async createTransaction(options) {} - /*async createTransaction (options) { - client.setConfig(options.network) - - let transaction = transactionBuilder - .transfer() - .amount(options.amount) - .recipientId(options.recipientId) - .vendorField(options.vendorField) - .sign(options.passphrase) - .getStruct() - - await axios.post('http://127.0.0.1:4300/api/v2/transactions', { - transactions: [transaction] - }) - - return transaction - }*/ } /** diff --git a/__tests__/e2e/tests/scenarios/scenario1/config.js b/__tests__/e2e/tests/scenarios/scenario1/config.js index 9e5ec2529a..2dc4fb4bb2 100644 --- a/__tests__/e2e/tests/scenarios/scenario1/config.js +++ b/__tests__/e2e/tests/scenarios/scenario1/config.js @@ -8,5 +8,8 @@ module.exports = { "insufficient-balance", //'pool-restart', "transactions-valid", + "htlc-claim", + "htlc-refund", + "multisignature" ], }; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/0.transfer-new-wallet.action.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/0.transfer-new-wallet.action.js new file mode 100644 index 0000000000..03a59213f6 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/0.transfer-new-wallet.action.js @@ -0,0 +1,27 @@ +"use strict"; + +const { Managers, Utils } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { delegates } = require("../../../../lib/utils/testnet"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Creates a transaction to a new wallet + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const senderWallet = delegates[7]; // better use a different delegate for each scenario initial transfer + let transaction1 = TransactionFactory.transfer(utils.htlcSender.address, 1000 * Math.pow(10, 8), "send coins to htlc sender") + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(2)) + .withPassphrase(senderWallet.passphrase) + .createOne(); + + await testUtils.POST("transactions", { + transactions: [transaction1], + }); +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/1.create-lock-txs.action.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/1.create-lock-txs.action.js new file mode 100644 index 0000000000..0218bd7c14 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/1.create-lock-txs.action.js @@ -0,0 +1,92 @@ +"use strict"; + +const { Managers, Crypto, Utils } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const shared = require("./shared"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Send lock transactions, we need 4 lock transactions to then test : + * - associated lock tx does not exist + * - secret hash does not match + * - not recipient of lock tx + * - lock expired + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const nodesHeight = await testUtils.getNodesHeight(); + const lastHeight = Math.max(...nodesHeight); + + // "normal" htlc lock transaction that will allow to claim without issue + shared.lockTransactions.normal = TransactionFactory.htlcLock( + { + secretHash: Crypto.HashAlgorithms.sha256(utils.htlcRecipient1.address.slice(0, 32)).toString("hex"), + expiration: { + type: 2, + value: lastHeight + 12, + }, + }, + utils.htlcRecipient1.address, + 3 * Math.pow(10, 8) + ) + .withFee(0.1 * Math.pow(10, 8)) + .withPassphrase(utils.htlcSender.passphrase) + .createOne(); + + // htlc lock transaction that we will claim with a wrong secret hash + shared.lockTransactions.wrongSecret = TransactionFactory.htlcLock( + { + secretHash: Crypto.HashAlgorithms.sha256(utils.htlcRecipient2.address.slice(0, 32)).toString("hex"), + expiration: { + type: 2, + value: lastHeight + 12, + }, + }, + utils.htlcRecipient2.address, + 3 * Math.pow(10, 8) + ) + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(1)) + .withPassphrase(utils.htlcSender.passphrase) + .createOne(); + + // htlc lock transaction that we will claim with a wallet not recipient of the lock tx + shared.lockTransactions.notRecipient = TransactionFactory.htlcLock( + { + secretHash: Crypto.HashAlgorithms.sha256(utils.htlcRecipient3.address.slice(0, 32)).toString("hex"), + expiration: { + type: 2, + value: lastHeight + 12, + }, + }, + utils.htlcRecipient3.address, + 3 * Math.pow(10, 8) + ) + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(2)) + .withPassphrase(utils.htlcSender.passphrase) + .createOne(); + + // htlc lock transaction that we will claim after lock expiration + shared.lockTransactions.lockExpired = TransactionFactory.htlcLock( + { + secretHash: Crypto.HashAlgorithms.sha256(utils.htlcRecipient4.address.slice(0, 32)).toString("hex"), + expiration: { + type: 2, + value: lastHeight + 1, + }, + }, + utils.htlcRecipient4.address, + 3 * Math.pow(10, 8) + ) + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(3)) + .withPassphrase(utils.htlcSender.passphrase) + .createOne(); + + await testUtils.POST("transactions", { transactions: Object.values(shared.lockTransactions) }, 1); // to node 1 +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/2.check-lock.test.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/2.check-lock.test.js new file mode 100644 index 0000000000..704621ac50 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/2.check-lock.test.js @@ -0,0 +1,25 @@ +"use strict"; + +const testUtils = require("../../../../lib/utils/test-utils"); +const utils = require("./utils"); +const shared = require("./shared"); + +describe("Check confirmed and unconfirmed transactions", () => { + it("should have no unconfirmed transaction", async () => { + const response = await testUtils.GET("transactions/unconfirmed", {}, 1); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.length).toBe(0); + }); + + it("should have all lock transactions forged", async () => { + const response = await testUtils.GET("transactions"); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + for (const recipientName of ["htlcRecipient1", "htlcRecipient2", "htlcRecipient3", "htlcRecipient4"]) { + expect(transactions.filter(transaction => transaction.recipient === utils[recipientName].address).length).toBe(1); + } + }); +}); diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/3.create-claim-txs.action.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/3.create-claim-txs.action.js new file mode 100644 index 0000000000..502878b537 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/3.create-claim-txs.action.js @@ -0,0 +1,62 @@ +"use strict"; + +const { Managers, Utils } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const shared = require("./shared"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Send claim transactions, we need 4 claim transactions to test when : + * - associated lock tx does not exist + * - secret hash does not match + * - not recipient of lock tx + * - lock expired + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + // 1st transaction : "normal" htlc lock transaction that we claim without issue + shared.claimTransactions.normal = TransactionFactory.htlcClaim( + { + lockTransactionId: shared.lockTransactions.normal.id, + unlockSecret: shared.lockTransactions.normal.recipientId.slice(0, 32), + } + ) + .withPassphrase(utils.htlcRecipient1.passphrase) + .createOne(); + + // 2nd transaction : htlc lock transaction that we claim with a wrong secret hash + shared.claimTransactions.wrongSecret = TransactionFactory.htlcClaim( + { + lockTransactionId: shared.lockTransactions.wrongSecret.id, + unlockSecret: "thatisasecretthatissooooooowrong", + } + ) + .withPassphrase(utils.htlcRecipient2.passphrase) + .createOne(); + + // 3rd transaction : htlc lock transaction that we claim with a wallet not recipient of the lock tx + shared.claimTransactions.notRecipient = TransactionFactory.htlcClaim( + { + lockTransactionId: shared.lockTransactions.notRecipient.id, + unlockSecret: shared.lockTransactions.notRecipient.recipientId.slice(0, 32), + } + ) + .withPassphrase(utils.htlcNotRecipient.passphrase) + .createOne(); + + // 4th transaction : htlc lock transaction that we will claim after lock expiration + shared.claimTransactions.lockExpired = TransactionFactory.htlcClaim( + { + lockTransactionId: shared.lockTransactions.lockExpired.id, + unlockSecret: shared.lockTransactions.lockExpired.recipientId.slice(0, 32), + } + ) + .withPassphrase(utils.htlcRecipient4.passphrase) + .createOne(); + + await testUtils.POST("transactions", { transactions: Object.values(shared.claimTransactions) }, 1); // to node 1 +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/4.check-claim.test.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/4.check-claim.test.js new file mode 100644 index 0000000000..79541eb7eb --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/4.check-claim.test.js @@ -0,0 +1,31 @@ +"use strict"; + +const testUtils = require("../../../../lib/utils/test-utils"); +const utils = require("./utils"); +const shared = require("./shared"); + +describe("Check confirmed and unconfirmed transactions", () => { + it("should have no unconfirmed transaction", async () => { + const response = await testUtils.GET("transactions/unconfirmed", {}, 1); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.length).toBe(0); + }); + + it("should have valid transactions forged and invalid not forged", async () => { + const response = await testUtils.GET("transactions"); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + // recipients 1 and htlcNotRecipient sent valid htlc claim transactions + for (const senderName of ["htlcRecipient1", "htlcNotRecipient"]) { + expect(transactions.filter(transaction => transaction.sender === utils[senderName].address).length).toBe(1); + } + + // recipients 2 and 4 sent invalid htlc claim transactions + for (const senderName of ["htlcRecipient2", "htlcRecipient4"]) { + expect(transactions.filter(transaction => transaction.sender === utils[senderName].address).length).toBe(0); + } + }); +}); diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/config.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/config.js new file mode 100644 index 0000000000..e9bbf088ef --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/config.js @@ -0,0 +1,25 @@ +"use strict"; + +/* +Test summary : +We want to test that the HTLC workflow lock => claim works as expected. + +Workflow : +- step 0 : we just pick from genesis wallet what we need for our test, sending it to a new wallet +- step 1 : we create and send the lock transactions +- step 2 : we check that the lock transactions were forged +- step 3 : we create and send the claim transactions +- step 4 : we check that valid claim transaction were forged and invalid were not +*/ + +module.exports = { + events: { + newBlock: { + 35: ["0.transfer-new-wallet.action"], + 37: ["1.create-lock-txs.action"], + 41: ["2.check-lock.test"], + 43: ["3.create-claim-txs.action"], + 47: ["4.check-claim.test"], + }, + }, +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/shared.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/shared.js new file mode 100644 index 0000000000..70260e81b6 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/shared.js @@ -0,0 +1,16 @@ +const transactions = { + lockTransactions: { // this will be filled by 1.create-lock-txs + normal: {}, + wrongSecret: {}, + notRecipient: {}, + lockExpired: {} + }, + claimTransactions: { // this will be filled by 2.create-claim-txs + normal: {}, + wrongSecret: {}, + notRecipient: {}, + lockExpired: {} + }, +} + +module.exports = transactions; \ No newline at end of file diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/utils.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/utils.js new file mode 100644 index 0000000000..7e43777bea --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-claim/utils.js @@ -0,0 +1,26 @@ +module.exports = { + htlcSender: { + passphrase: "design sniff clean critic grass stool offer hollow impose snap rubber about", + address: "AQ9xjoj87bbPq13BvxHQxGG63xGqrqt5V7" + }, + htlcRecipient1: { + passphrase: "client vote control liar rhythm steak that reduce luxury pipe demand taste", + address: "AJGrJbq9zES7kEHCyhVhffbmcqa2A14od8" + }, + htlcRecipient2: { + passphrase: "arena often sorry shrimp city emerge tiger protect butter donate visa borrow", + address: "AKkkoN8tV5BBHYkzDPAJRdFPxqkf9GFLc5" + }, + htlcRecipient3: { + passphrase: "salt whip key later april omit copy drive glad cup inherit total", + address: "AWZPueuVWJUtqXTjdJFwRXxaAtgjddyE6Y" + }, + htlcRecipient4: { + passphrase: "swallow mystery speak ill divorce inflict bamboo tumble bacon enact tool practice", + address: "AWRsmT7SLMjZnk1grzn7vQtyywcAuwLhyf" + }, + htlcNotRecipient: { + passphrase: "begin eager alcohol exclude inform doll drink evil donate way roof trigger", + address: "AXNwz76CNRfDcUmE1HwhdM9cpYHAFf3qbe" + } + }; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/0.transfer-new-wallet.action.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/0.transfer-new-wallet.action.js new file mode 100644 index 0000000000..5c6f2c68f5 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/0.transfer-new-wallet.action.js @@ -0,0 +1,27 @@ +"use strict"; + +const { Managers, Utils } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { delegates } = require("../../../../lib/utils/testnet"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Creates a transaction to a new wallet + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const senderWallet = delegates[8]; // better use a different delegate for each scenario initial transfer + let transaction1 = TransactionFactory.transfer(utils.htlcSender.address, 1000 * Math.pow(10, 8), "send coins to htlc sender") + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(2)) + .withPassphrase(senderWallet.passphrase) + .createOne(); + + await testUtils.POST("transactions", { + transactions: [transaction1], + }); +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/1.create-lock-txs.action.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/1.create-lock-txs.action.js new file mode 100644 index 0000000000..415a597574 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/1.create-lock-txs.action.js @@ -0,0 +1,74 @@ +"use strict"; + +const { Managers, Crypto, Utils } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const shared = require("./shared"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Send lock transactions, we need 3 lock transactions to then test : + * - associated lock tx does not exist + * - not sender of lock tx + * - lock not expired + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const nodesHeight = await testUtils.getNodesHeight(); + const lastHeight = Math.max(...nodesHeight); + + // "normal" htlc lock transaction that will allow to refund without issue + shared.lockTransactions.normal = TransactionFactory.htlcLock( + { + secretHash: Crypto.HashAlgorithms.sha256(utils.htlcRecipient1.address.slice(0, 32)).toString("hex"), + expiration: { + type: 2, + value: lastHeight + 1, + }, + }, + utils.htlcRecipient1.address, + 3 * Math.pow(10, 8) + ) + .withFee(0.1 * Math.pow(10, 8)) + .withPassphrase(utils.htlcSender.passphrase) + .createOne(); + + // htlc lock transaction that we will refund with a wallet not sender of the lock tx + shared.lockTransactions.notSender = TransactionFactory.htlcLock( + { + secretHash: Crypto.HashAlgorithms.sha256(utils.htlcRecipient2.address.slice(0, 32)).toString("hex"), + expiration: { + type: 2, + value: lastHeight + 1, + }, + }, + utils.htlcRecipient2.address, + 3 * Math.pow(10, 8) + ) + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(1)) + .withPassphrase(utils.htlcSender.passphrase) + .createOne(); + + // htlc lock transaction that we will refund before lock expiration + shared.lockTransactions.lockNotExpired = TransactionFactory.htlcLock( + { + secretHash: Crypto.HashAlgorithms.sha256(utils.htlcRecipient3.address.slice(0, 32)).toString("hex"), + expiration: { + type: 2, + value: lastHeight + 12, + }, + }, + utils.htlcRecipient3.address, + 3 * Math.pow(10, 8) + ) + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(2)) + .withPassphrase(utils.htlcSender.passphrase) + .createOne(); + + await testUtils.POST("transactions", { transactions: Object.values(shared.lockTransactions) }, 1); // to node 1 +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/2.check-lock.test.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/2.check-lock.test.js new file mode 100644 index 0000000000..dba347530a --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/2.check-lock.test.js @@ -0,0 +1,25 @@ +"use strict"; + +const testUtils = require("../../../../lib/utils/test-utils"); +const utils = require("./utils"); +const shared = require("./shared"); + +describe("Check confirmed and unconfirmed transactions", () => { + it("should have no unconfirmed transaction", async () => { + const response = await testUtils.GET("transactions/unconfirmed", {}, 1); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.length).toBe(0); + }); + + it("should have all lock transactions forged", async () => { + const response = await testUtils.GET("transactions"); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + for (const recipientName of ["htlcRecipient1", "htlcRecipient2", "htlcRecipient3"]) { + expect(transactions.filter(transaction => transaction.recipient === utils[recipientName].address).length).toBe(1); + } + }); +}); diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/3.create-refund-txs.action.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/3.create-refund-txs.action.js new file mode 100644 index 0000000000..e586016481 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/3.create-refund-txs.action.js @@ -0,0 +1,49 @@ +"use strict"; + +const { Managers, Utils } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const shared = require("./shared"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Send refund transactions, we need 3 refund transactions to test when : + * - associated lock tx does not exist + * - not sender of lock tx + * - lock not expired + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + // 1st transaction : "normal" htlc lock transaction that we refund without issue + shared.refundTransactions.normal = TransactionFactory.htlcRefund( + { + lockTransactionId: shared.lockTransactions.normal.id, + } + ) + .withPassphrase(utils.htlcSender.passphrase) + .withNonce(Utils.BigNumber.make(3)) + .createOne(); + + // 2nd transaction : htlc lock transaction that we refund with a wallet not sender of the lock tx + shared.refundTransactions.notSender = TransactionFactory.htlcRefund( + { + lockTransactionId: shared.lockTransactions.notSender.id, + } + ) + .withPassphrase(utils.htlcNotSender.passphrase) + .createOne(); + + // 3rd transaction : htlc lock transaction that we will refund before lock expiration + shared.refundTransactions.lockNotExpired = TransactionFactory.htlcRefund( + { + lockTransactionId: shared.lockTransactions.lockNotExpired.id, + } + ) + .withPassphrase(utils.htlcBeforeExpiration.passphrase) + .createOne(); + + await testUtils.POST("transactions", { transactions: Object.values(shared.refundTransactions) }, 1); // to node 1 +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/4.check-refund.test.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/4.check-refund.test.js new file mode 100644 index 0000000000..29d1adf5bd --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/4.check-refund.test.js @@ -0,0 +1,31 @@ +"use strict"; + +const testUtils = require("../../../../lib/utils/test-utils"); +const utils = require("./utils"); +const { Enums } = require("@arkecosystem/crypto"); + +describe("Check confirmed and unconfirmed transactions", () => { + it("should have no unconfirmed transaction", async () => { + const response = await testUtils.GET("transactions/unconfirmed", {}, 1); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.length).toBe(0); + }); + + it("should have valid transactions forged and invalid not forged", async () => { + const response = await testUtils.GET("transactions"); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + // htlcSender and htlcNotSender sent valid htlc refund transactions + for (const senderName of ["htlcSender", "htlcNotSender"]) { + expect(transactions.filter( + tx => tx.sender === utils[senderName].address && tx.type === Enums.TransactionType.HtlcRefund + ).length).toBe(1); + } + + // htlcBeforeExpiration sent invalid htlc refund transaction + expect(transactions.filter(transaction => transaction.sender === utils.htlcBeforeExpiration.address).length).toBe(0); + }); +}); diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/config.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/config.js new file mode 100644 index 0000000000..992428f8aa --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/config.js @@ -0,0 +1,25 @@ +"use strict"; + +/* +Test summary : +We want to test that the HTLC workflow lock => refund works as expected. + +Workflow : +- step 0 : we just pick from genesis wallet what we need for our test, sending it to a new wallet +- step 1 : we create and send the lock transactions +- step 2 : we check that the lock transactions were forged +- step 3 : we create and send the refund transactions +- step 4 : we check that valid refund transaction were forged and invalid were not +*/ + +module.exports = { + events: { + newBlock: { + 48: ["0.transfer-new-wallet.action"], + 50: ["1.create-lock-txs.action"], + 55: ["2.check-lock.test"], + 56: ["3.create-refund-txs.action"], + 61: ["4.check-refund.test"], + }, + }, +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/shared.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/shared.js new file mode 100644 index 0000000000..06e8c0c801 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/shared.js @@ -0,0 +1,14 @@ +const transactions = { + lockTransactions: { // this will be filled by 1.create-lock-txs + normal: {}, + notSender: {}, + lockNotExpired: {} + }, + refundTransactions: { // this will be filled by 2.create-refund-txs + normal: {}, + notSender: {}, + lockNotExpired: {} + }, +} + +module.exports = transactions; \ No newline at end of file diff --git a/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/utils.js b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/utils.js new file mode 100644 index 0000000000..5d0c642af2 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/htlc-refund/utils.js @@ -0,0 +1,26 @@ +module.exports = { + htlcSender: { + passphrase: "recycle able curve near buffalo lend link prize water desert van select", + address: "AeoQzvzc15SQuCMq1UJaGi7qyB9ct8oub8" + }, + htlcRecipient1: { + passphrase: "ahead ordinary during bless dance oxygen opera mixture start stand silent hard", + address: "AGhcfzxfEowU9SXdeitRpmjwqtkUTboA9C" + }, + htlcRecipient2: { + passphrase: "clip lonely company syrup month permit amount chaos when float dish present", + address: "AWJUzV1eZGXmwXXMM9yQavR6pdbxiSStBe" + }, + htlcRecipient3: { + passphrase: "card caught neck light lab hope weapon tell sure drop night load", + address: "AU2AruVVaJdhZwKqY9T9dxzDHPKze9egz5" + }, + htlcBeforeExpiration: { + passphrase: "odor tide urban satoshi wagon kangaroo upon catalog wish split example employ", + address: "AKxqQai3qZCsL8vEwdsaKhXvmXyNXznkV1" + }, + htlcNotSender: { + passphrase: "draft train veteran entry kind quiz monkey blouse sausage define purchase escape", + address: "AP9QmKFvVyBESGrkbGcdL6MnfHE3jegivW" + } + } \ No newline at end of file diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/0.transfer-new-wallet.action.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/0.transfer-new-wallet.action.js new file mode 100644 index 0000000000..77b805fbb7 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/0.transfer-new-wallet.action.js @@ -0,0 +1,27 @@ +"use strict"; + +const { Managers, Utils } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { delegates } = require("../../../../lib/utils/testnet"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Creates a transaction to a new wallet + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const senderWallet = delegates[9]; // better use a different delegate for each scenario initial transfer + let transaction1 = TransactionFactory.transfer(utils.multiSender1.address, 1000 * Math.pow(10, 8), "send coins to multisig participant 1") + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(2)) + .withPassphrase(senderWallet.passphrase) + .createOne(); + + await testUtils.POST("transactions", { + transactions: [transaction1], + }); +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/1.create-multisig-registration.action.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/1.create-multisig-registration.action.js new file mode 100644 index 0000000000..f350daea65 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/1.create-multisig-registration.action.js @@ -0,0 +1,35 @@ +"use strict"; + +const { Managers, Identities } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const shared = require("./shared"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Send multisig registration transaction + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const passphrases = [ + utils.multiSender1.passphrase, + utils.multiSender2.passphrase, + utils.multiSender3.passphrase, + utils.multiSender4.passphrase, + utils.multiSender5.passphrase, + ]; + const participants = passphrases.map(p => Identities.PublicKey.fromPassphrase(p)); + + const transactions = [ + TransactionFactory.multiSignature(participants, 3) + .withPassphrase(utils.multiSender1.passphrase) + .withPassphraseList(passphrases) + .createOne() + ]; + shared.multisigRegistration = transactions[0]; + + await testUtils.POST("transactions", { transactions }, 1); // to node 1 +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/2.check-registration.test.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/2.check-registration.test.js new file mode 100644 index 0000000000..e9d9c39e36 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/2.check-registration.test.js @@ -0,0 +1,24 @@ +"use strict"; + +const testUtils = require("../../../../lib/utils/test-utils"); +const utils = require("./utils"); +const shared = require("./shared"); + +describe("Check confirmed and unconfirmed transactions", () => { + it("should have no unconfirmed transaction", async () => { + const response = await testUtils.GET("transactions/unconfirmed", {}, 1); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.length).toBe(0); + }); + + it("should have all lock transactions forged", async () => { + const response = await testUtils.GET("transactions"); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.filter(transaction => transaction.sender === utils.multiSender1.address).length).toBe(1); + + }); +}); diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/3.transfer-multisig-wallet.action.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/3.transfer-multisig-wallet.action.js new file mode 100644 index 0000000000..9dc75dc5d2 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/3.transfer-multisig-wallet.action.js @@ -0,0 +1,29 @@ +"use strict"; + +const { Managers, Utils, Identities } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const shared = require("./shared"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { delegates } = require("../../../../lib/utils/testnet"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Creates a transaction to the multisig wallet + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const senderWallet = delegates[9]; + const multisigAddress = Identities.Address.fromMultiSignatureAsset(shared.multisigRegistration.asset.multiSignature); + let transaction1 = TransactionFactory.transfer(multisigAddress, 1000 * Math.pow(10, 8), "send coins to multisig wallet") + .withFee(0.1 * Math.pow(10, 8)) + .withNonce(Utils.BigNumber.make(3)) + .withPassphrase(senderWallet.passphrase) + .createOne(); + + await testUtils.POST("transactions", { + transactions: [transaction1], + }); +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/4.create-multisig-txs.action.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/4.create-multisig-txs.action.js new file mode 100644 index 0000000000..bea3728599 --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/4.create-multisig-txs.action.js @@ -0,0 +1,35 @@ +"use strict"; + +const { Managers, Utils, Identities } = require("@arkecosystem/crypto"); +const utils = require("./utils"); +const shared = require("./shared"); +const testUtils = require("../../../../lib/utils/test-utils"); +const { TransactionFactory } = require('../../../../../helpers/transaction-factory'); + +/** + * Send multisig transactions, we need 3 transactions to test when : + * - valid multisig transaction with 3 participants signing + * - not enough signatures : only 2 participants signing + * - wrong signature : signed with 3 participants but 1 is wrong + * @param {Object} options = { } + * @return {void} + */ +module.exports = async options => { + Managers.configManager.setFromPreset("testnet"); + + const multisigPublicKey = Identities.PublicKey.fromMultiSignatureAsset(shared.multisigRegistration.asset.multiSignature); + + const transactions = [ + TransactionFactory.transfer(utils.randomWallet1.address, 1e8) + .withFee(1e7) + .withSenderPublicKey(multisigPublicKey) + .withPassphraseList([ + utils.multiSender1.passphrase, + utils.multiSender2.passphrase, + utils.multiSender3.passphrase, + ]) + .createOne() + ]; + + await testUtils.POST("transactions", { transactions }, 1); // to node 1 +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/5.check-multisig.test.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/5.check-multisig.test.js new file mode 100644 index 0000000000..c573f6c3bd --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/5.check-multisig.test.js @@ -0,0 +1,23 @@ +"use strict"; + +const testUtils = require("../../../../lib/utils/test-utils"); +const utils = require("./utils"); +const { Enums } = require("@arkecosystem/crypto"); + +describe("Check confirmed and unconfirmed transactions", () => { + it("should have no unconfirmed transaction", async () => { + const response = await testUtils.GET("transactions/unconfirmed", {}, 1); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.length).toBe(0); + }); + + it("should have valid transactions forged and invalid not forged", async () => { + const response = await testUtils.GET("transactions"); + testUtils.expectSuccessful(response); + const transactions = response.data.data; + + expect(transactions.filter(tx => tx.recipient === utils.randomWallet1.address).length).toBe(1); + }); +}); diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/config.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/config.js new file mode 100644 index 0000000000..fc94ca14bf --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/config.js @@ -0,0 +1,26 @@ +"use strict"; + +/* +Test summary : +We want to test that the multisig transactions work as expected. + +Workflow : +- step 0 : we just pick from genesis wallet what we need for our test, sending it to a new wallet +- step 1 : we create and send the multisig registration transaction +- step 2 : we check that the registration transaction was forged +- step 3 : we create and send the multisigned transactions +- step 4 : we check that valid multisigned transactions were forged and invalid were not +*/ + +module.exports = { + events: { + newBlock: { + 62: ["0.transfer-new-wallet.action"], + 65: ["1.create-multisig-registration.action"], + 69: ["2.check-registration.test"], + 70: ["3.transfer-multisig-wallet.action"], + 74: ["4.create-multisig-txs.action"], + 79: ["5.check-multisig.test"], + }, + }, +}; diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/shared.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/shared.js new file mode 100644 index 0000000000..b45a9c540e --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/shared.js @@ -0,0 +1,5 @@ +const transactions = { + multisigRegistration: {} // this will be filled by 1.create-multisig-registration +} + +module.exports = transactions; \ No newline at end of file diff --git a/__tests__/e2e/tests/scenarios/scenario1/multisignature/utils.js b/__tests__/e2e/tests/scenarios/scenario1/multisignature/utils.js new file mode 100644 index 0000000000..77eb5f93ba --- /dev/null +++ b/__tests__/e2e/tests/scenarios/scenario1/multisignature/utils.js @@ -0,0 +1,30 @@ +module.exports = { + multiSender1: { + passphrase: "aware misery artist purchase live century dog embark rose drift ribbon shuffle", + address: "AazbJGLhFRfDwDnbXmnf4wmqSs25JsGsRC" + }, + multiSender2: { + passphrase: "siren jacket situate fun mule industry ozone spike world baby sell topic", + address: "AS8xJ5wikGKF7aXDrdiZWPUEd3wC12odpq" + }, + multiSender3: { + passphrase: "other garage easy member crack undo concert country guess twist boost engage", + address: "ASXrmErTDsH1FSwLYjMVE5Gj859SBv3k6D" + }, + multiSender4: { + passphrase: "basket insect draw faith judge satisfy skin game debate pulse question goddess", + address: "AX5khkGFK9bZyR6B3zqmHXuai3QvAtoWnT" + }, + multiSender5: { + passphrase: "vote solar loop huge visa hover blanket sight alert clump home nerve", + address: "APZ2Y7YKjWB9ZGohenojzyiHbJ5BY3Sqjo" + }, + randomWallet1: { + passphrase: "spike imitate spider boil village rebel steak anchor slogan cushion initial letter", + address: "AXK1nzsFpUyT1wQLki62CauByX8Ef3yG3m" + }, + randomWallet2: { + passphrase: "reduce keep crush gospel december bind fit apart giant envelope comic switch", + address: "ALFuawuGu9nveUNhXf6vDZdp9DFTMeD4VN" + } + } \ No newline at end of file diff --git a/__tests__/integration/core-transaction-pool/processor.test.ts b/__tests__/integration/core-transaction-pool/processor.test.ts index e01652b398..9714b852b2 100644 --- a/__tests__/integration/core-transaction-pool/processor.test.ts +++ b/__tests__/integration/core-transaction-pool/processor.test.ts @@ -92,7 +92,8 @@ describe("Transaction Guard", () => { await processor.validate([transfer.data]); const expectedError = { - message: "Wallet not allowed to spend before funding is confirmed.", + message: + "Insufficient balance in database wallet. Wallet is not allowed to spend before funding is confirmed.", type: "ERR_APPLY", }; expect(processor.getErrors()[transfer.id]).toContainEqual(expectedError); diff --git a/packages/core-transaction-pool/src/wallet-manager.ts b/packages/core-transaction-pool/src/wallet-manager.ts index 7c676121be..23e67b3346 100644 --- a/packages/core-transaction-pool/src/wallet-manager.ts +++ b/packages/core-transaction-pool/src/wallet-manager.ts @@ -37,22 +37,7 @@ export class WalletManager extends Wallets.WalletManager { } public async throwIfCannotBeApplied(transaction: Interfaces.ITransaction): Promise { - // Edge case if sender is unknown and has no balance. - // NOTE: Check is performed against the database wallet manager. - const senderPublicKey: string = transaction.data.senderPublicKey; - if (!this.databaseService.walletManager.hasByPublicKey(senderPublicKey)) { - const senderAddress: string = Identities.Address.fromPublicKey(senderPublicKey); - - if (this.databaseService.walletManager.findByAddress(senderAddress).balance.isZero()) { - const message: string = "Wallet not allowed to spend before funding is confirmed."; - - this.logger.error(message); - - throw new Error(message); - } - } - - const sender: State.IWallet = this.findByPublicKey(senderPublicKey); + const sender: State.IWallet = this.findByPublicKey(transaction.data.senderPublicKey); const handler: Handlers.TransactionHandler = await Handlers.Registry.get( transaction.type, transaction.typeGroup, diff --git a/packages/core-transactions/src/errors.ts b/packages/core-transactions/src/errors.ts index 800637a36e..660a5b1908 100644 --- a/packages/core-transactions/src/errors.ts +++ b/packages/core-transactions/src/errors.ts @@ -49,6 +49,12 @@ export class UnexpectedNonceError extends TransactionError { } } +export class ZeroDatabaseBalanceError extends TransactionError { + constructor() { + super(`Insufficient balance in database wallet. Wallet is not allowed to spend before funding is confirmed.`); + } +} + export class InsufficientBalanceError extends TransactionError { constructor() { super(`Insufficient balance in the wallet.`); diff --git a/packages/core-transactions/src/handlers/transaction.ts b/packages/core-transactions/src/handlers/transaction.ts index f9604a440b..68cb88d7a8 100644 --- a/packages/core-transactions/src/handlers/transaction.ts +++ b/packages/core-transactions/src/handlers/transaction.ts @@ -13,6 +13,7 @@ import { UnexpectedMultiSignatureError, UnexpectedNonceError, UnexpectedSecondSignatureError, + ZeroDatabaseBalanceError, } from "../errors"; import { ITransactionHandler } from "../interfaces"; @@ -65,6 +66,13 @@ export abstract class TransactionHandler implements ITransactionHandler { sender: State.IWallet, databaseWalletManager: State.IWalletManager, ): Promise { + if ( + !databaseWalletManager.hasByPublicKey(sender.publicKey) && + databaseWalletManager.findByAddress(sender.address).balance.isZero() + ) { + throw new ZeroDatabaseBalanceError(); + } + const data: Interfaces.ITransactionData = transaction.data; if (Utils.isException(data)) {