From 29ca737f2b3971aa360f37df24a3725928c5a1cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Carneiro?= Date: Thu, 28 Sep 2023 18:46:55 -0300 Subject: [PATCH 1/7] feat: compile entrypoint file with typescript (#577) * feat: move index to typescript * chore: encapsulate error messages enum * chore: add indirection to make compatible to how we used to export the lib * chore: lib should be ts * chore: formatting --- index.js | 103 --------------------------------------------------- package.json | 2 +- src/index.ts | 4 ++ src/lib.ts | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 107 insertions(+), 104 deletions(-) delete mode 100644 index.js create mode 100644 src/index.ts create mode 100644 src/lib.ts diff --git a/index.js b/index.js deleted file mode 100644 index abb36f89e..000000000 --- a/index.js +++ /dev/null @@ -1,103 +0,0 @@ -const constants = require('./lib/constants'); -const dateFormatter = require('./lib/utils/date'); -const websocket = require('./lib/websocket'); -const errors = require('./lib/errors'); -const ErrorMessages = require('./lib/errorMessages'); -const walletApi = require('./lib/api/wallet'); -const txApi = require('./lib/api/txApi'); -const txMiningApi = require('./lib/api/txMining'); -const versionApi = require('./lib/api/version'); -const axios = require('./lib/api/axiosInstance'); -const metadataApi = require('./lib/api/metadataApi'); -const { Storage } = require('./lib/storage/storage'); -const LevelDBStore = require('./lib/storage/leveldb/store'); -const memoryStore = require('./lib/storage/memory_store'); -const network = require('./lib/network'); -const HathorWallet = require('./lib/new/wallet'); -const Connection = require('./lib/new/connection'); -const WalletServiceConnection = require('./lib/wallet/connection'); -const SendTransaction = require('./lib/new/sendTransaction'); -const Address = require('./lib/models/address'); -const Output = require('./lib/models/output'); -const P2PKH = require('./lib/models/p2pkh'); -const P2SH = require('./lib/models/p2sh'); -const P2SHSignature = require('./lib/models/p2sh_signature'); -const ScriptData = require('./lib/models/script_data'); -const Input = require('./lib/models/input'); -const Transaction = require('./lib/models/transaction'); -const CreateTokenTransaction = require('./lib/models/create_token_transaction'); -const Network = require('./lib/models/network'); -const addressUtils = require('./lib/utils/address'); -const cryptoUtils = require('./lib/utils/crypto'); -const dateUtils = require('./lib/utils/date'); -const tokensUtils = require('./lib/utils/tokens'); -const walletUtils = require('./lib/utils/wallet'); -const helpersUtils = require('./lib/utils/helpers'); -const numberUtils = require('./lib/utils/numbers'); -const scriptsUtils = require('./lib/utils/scripts'); -const transactionUtils = require('./lib/utils/transaction'); -const bufferUtils = require('./lib/utils/buffer'); -const HathorWalletServiceWallet = require('./lib/wallet/wallet'); -const walletServiceApi = require('./lib/wallet/api/walletApi'); -const SendTransactionWalletService = require('./lib/wallet/sendTransactionWalletService'); -const config = require('./lib/config'); -const PushNotification = require('./lib/pushNotification'); -const { WalletType } = require('./lib/types'); - -const {PartialTx, PartialTxInputData} = require('./lib/models/partial_tx'); -const PartialTxProposal = require('./lib/wallet/partialTxProposal'); -const swapService = require('./lib/wallet/api/swapService'); -const { AtomicSwapServiceConnection } = require('./lib/swapService/swapConnection'); - -module.exports = { - PartialTx, - PartialTxInputData, - PartialTxProposal: PartialTxProposal.default, - dateFormatter: dateFormatter.default, - websocket: websocket.default, - walletApi: walletApi.default, - txApi: txApi.default, - txMiningApi: txMiningApi.default, - versionApi: versionApi.default, - metadataApi: metadataApi.default, - errors, - ErrorMessages, - constants, - axios, - Storage, - LevelDBStore: LevelDBStore.default, - MemoryStore: memoryStore.MemoryStore, - network: network.default, - HathorWallet: HathorWallet.default, - Connection: Connection.default, - AtomicSwapServiceConnection: AtomicSwapServiceConnection, - WalletServiceConnection: WalletServiceConnection.default, - SendTransaction: SendTransaction.default, - Address: Address.default, - Output: Output.default, - P2PKH: P2PKH.default, - P2SH: P2SH.default, - P2SHSignature: P2SHSignature.default, - ScriptData: ScriptData.default, - Input: Input.default, - Transaction: Transaction.default, - CreateTokenTransaction: CreateTokenTransaction.default, - Network: Network.default, - addressUtils, - cryptoUtils, - dateUtils: dateUtils.default, - tokensUtils: tokensUtils.default, - walletUtils: walletUtils.default, - numberUtils: numberUtils, - helpersUtils: helpersUtils.default, - scriptsUtils, - bufferUtils, - transactionUtils: transactionUtils.default, - HathorWalletServiceWallet: HathorWalletServiceWallet.default, - walletServiceApi: walletServiceApi.default, - SendTransactionWalletService: SendTransactionWalletService.default, - config: config.default, - PushNotification, - swapService, - WalletType, -} diff --git a/package.json b/package.json index 48fb00e48..0d6a94629 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@hathor/wallet-lib", "version": "1.0.2", "description": "Library used by Hathor Wallet", - "main": "index.js", + "main": "lib/index.js", "directories": { "lib": "lib" }, diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..090f25273 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,4 @@ +import * as hathorLib from './lib'; + +export default hathorLib; +export * from './lib'; diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 000000000..d48dfde6c --- /dev/null +++ b/src/lib.ts @@ -0,0 +1,102 @@ +import * as constants from './constants'; +import dateFormatter from './utils/date'; +import websocket from './websocket'; +import * as errors from './errors'; +import * as ErrorMessages from './errorMessages'; +import walletApi from './api/wallet'; +import txApi from './api/txApi'; +import txMiningApi from './api/txMining'; +import versionApi from './api/version'; +import * as axios from './api/axiosInstance'; +import metadataApi from './api/metadataApi'; +import { Storage } from './storage/storage'; +import LevelDBStore from './storage/leveldb/store'; +import { MemoryStore } from './storage/memory_store'; +import network from './network'; +import HathorWallet from './new/wallet'; +import Connection from './new/connection'; +import WalletServiceConnection from './wallet/connection'; +import SendTransaction from './new/sendTransaction'; +import Address from './models/address'; +import Output from './models/output'; +import P2PKH from './models/p2pkh'; +import P2SH from './models/p2sh'; +import P2SHSignature from './models/p2sh_signature'; +import ScriptData from './models/script_data'; +import Input from './models/input'; +import Transaction from './models/transaction'; +import CreateTokenTransaction from './models/create_token_transaction'; +import Network from './models/network'; +import * as addressUtils from './utils/address'; +import * as cryptoUtils from './utils/crypto'; +import dateUtils from './utils/date'; +import tokensUtils from './utils/tokens'; +import walletUtils from './utils/wallet'; +import helpersUtils from './utils/helpers'; +import * as numberUtils from './utils/numbers'; +import * as scriptsUtils from './utils/scripts'; +import transactionUtils from './utils/transaction'; +import * as bufferUtils from './utils/buffer'; +import HathorWalletServiceWallet from './wallet/wallet'; +import walletServiceApi from './wallet/api/walletApi'; +import SendTransactionWalletService from './wallet/sendTransactionWalletService'; +import config from './config'; +import * as PushNotification from './pushNotification'; +import { WalletType } from './types'; +import { PartialTx, PartialTxInputData } from './models/partial_tx'; +import PartialTxProposal from './wallet/partialTxProposal'; +import * as swapService from './wallet/api/swapService'; +import { AtomicSwapServiceConnection } from './swapService/swapConnection'; + +export { + PartialTx, + PartialTxInputData, + PartialTxProposal, + dateFormatter, + websocket, + walletApi, + txApi, + txMiningApi, + versionApi, + metadataApi, + errors, + ErrorMessages, + constants, + axios, + Storage, + LevelDBStore, + MemoryStore, + network, + HathorWallet, + Connection, + AtomicSwapServiceConnection, + WalletServiceConnection, + SendTransaction, + Address, + Output, + P2PKH, + P2SH, + P2SHSignature, + ScriptData, + Input, + Transaction, + CreateTokenTransaction, + Network, + addressUtils, + cryptoUtils, + dateUtils, + tokensUtils, + walletUtils, + numberUtils, + helpersUtils, + scriptsUtils, + bufferUtils, + transactionUtils, + HathorWalletServiceWallet, + walletServiceApi, + SendTransactionWalletService, + config, + PushNotification, + swapService, + WalletType, +}; From 217797fd95104a6bb84ff51e673c5354afd991ca Mon Sep 17 00:00:00 2001 From: Alex Ruzenhack Date: Tue, 31 Oct 2023 17:47:41 +0000 Subject: [PATCH 2/7] fix: promise leak by capturing rejection from getVersion (#584) * fix: promise leak by capturing rejection from getVersion * chore: add semicolon in the end of instruction --- src/new/wallet.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/new/wallet.js b/src/new/wallet.js index 90c259511..99657af27 100644 --- a/src/new/wallet.js +++ b/src/new/wallet.js @@ -227,8 +227,8 @@ class HathorWallet extends EventEmitter { * @inner **/ async getVersionData() { - const versionData = await new Promise(resolve => { - versionApi.getVersion(resolve) + const versionData = await new Promise((resolve, reject)=> { + versionApi.getVersion(resolve).catch((error) => reject(error)); }); return { @@ -1312,8 +1312,8 @@ class HathorWallet extends EventEmitter { this.walletStopped = false; this.setState(HathorWallet.CONNECTING); - const info = await new Promise(resolve => { - versionApi.getVersion(resolve); + const info = await new Promise((resolve , reject)=> { + versionApi.getVersion(resolve).catch((error) => reject(error)); }); if (info.network.indexOf(this.conn.network) >= 0) { this.storage.setApiVersion(info); From ff84c40de4a86c571d0e34bd25e6a1d0f720da1b Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 8 Nov 2023 11:38:48 -0300 Subject: [PATCH 3/7] tests: use yml as configuration file for hathor core (#588) --- .../configuration/docker-compose.yml | 2 +- .../integration/configuration/privnet.py | 31 ------------------ .../integration/configuration/privnet.yml | 32 +++++++++++++++++++ 3 files changed, 33 insertions(+), 32 deletions(-) delete mode 100644 __tests__/integration/configuration/privnet.py create mode 100644 __tests__/integration/configuration/privnet.yml diff --git a/__tests__/integration/configuration/docker-compose.yml b/__tests__/integration/configuration/docker-compose.yml index 96b89d711..b10250c8f 100644 --- a/__tests__/integration/configuration/docker-compose.yml +++ b/__tests__/integration/configuration/docker-compose.yml @@ -17,7 +17,7 @@ services: "--memory-storage", ] environment: - HATHOR_CONFIG_FILE: privnet.conf.privnet + HATHOR_CONFIG_YAML: privnet/conf/privnet.yml ports: - "8083:8080" - "40404:40404" diff --git a/__tests__/integration/configuration/privnet.py b/__tests__/integration/configuration/privnet.py deleted file mode 100644 index e2831e01e..000000000 --- a/__tests__/integration/configuration/privnet.py +++ /dev/null @@ -1,31 +0,0 @@ -from hathor.conf.settings import HathorSettings - -# This file is the Private Network Configuration for the Fullnode -# It is consumed by the docker-compose.yml file on the integration folder. -# For more information, refer to: -# https://github.com/HathorNetwork/rfcs/blob/master/text/0033-private-network-guide.md - -# This genesis adds the funds to the following wallet seed: -# avocado spot town typical traffic vault danger century property shallow divorce festival spend attack anchor afford rotate green audit adjust fade wagon depart level - -SETTINGS = HathorSettings( - P2PKH_VERSION_BYTE=b'\x49', - MULTISIG_VERSION_BYTE=b'\x87', - NETWORK_NAME='privatenet', - BOOTSTRAP_DNS=[], - ENABLE_PEER_WHITELIST=False, - # Genesis stuff - GENESIS_OUTPUT_SCRIPT=bytes.fromhex("76a91466665b27f7dbc4c8c089d2f686c170c74d66f0b588ac"), - GENESIS_TIMESTAMP=1643902665, - MIN_TX_WEIGHT_K=0, - MIN_TX_WEIGHT_COEFFICIENT=0, - MIN_TX_WEIGHT=1, - REWARD_SPEND_MIN_BLOCKS=1, - - GENESIS_BLOCK_HASH=bytes.fromhex('00000334a21fbb58b4db8d7ff282d018e03e2977abd3004cf378fb1d677c3967'), - GENESIS_BLOCK_NONCE=4784939, - GENESIS_TX1_HASH=bytes.fromhex('54165cef1fd4cf2240d702b8383c307c822c16ca407f78014bdefa189a7571c2'), - GENESIS_TX1_NONCE=0, - GENESIS_TX2_HASH=bytes.fromhex('039906854ce6309b3180945f2a23deb9edff369753f7082e19053f5ac11bfbae'), - GENESIS_TX2_NONCE=0 -) diff --git a/__tests__/integration/configuration/privnet.yml b/__tests__/integration/configuration/privnet.yml new file mode 100644 index 000000000..ea8d82b06 --- /dev/null +++ b/__tests__/integration/configuration/privnet.yml @@ -0,0 +1,32 @@ +# This file is the Private Network Configuration for the Fullnode +# It is consumed by the docker-compose.yml file on the integration folder. +# For more information, refer to: +# https://github.com/HathorNetwork/rfcs/blob/master/text/0033-private-network-guide.md + +# This genesis adds the funds to the following wallet seed: +# avocado spot town typical traffic vault danger century property shallow divorce festival spend attack anchor afford rotate green audit adjust fade wagon depart level + +P2PKH_VERSION_BYTE: x49 +MULTISIG_VERSION_BYTE: x87 +NETWORK_NAME: privatenet +BOOTSTRAP_DNS: [] +ENABLE_PEER_WHITELIST: false + +# Genesis stuff +GENESIS_OUTPUT_SCRIPT: 76a91466665b27f7dbc4c8c089d2f686c170c74d66f0b588ac +GENESIS_BLOCK_TIMESTAMP: 1643902665 +GENESIS_BLOCK_NONCE: 4784939 +GENESIS_BLOCK_HASH: 00000334a21fbb58b4db8d7ff282d018e03e2977abd3004cf378fb1d677c3967 +GENESIS_TX1_NONCE: 0 +GENESIS_TX1_HASH: 54165cef1fd4cf2240d702b8383c307c822c16ca407f78014bdefa189a7571c2 +GENESIS_TX2_NONCE: 0 +GENESIS_TX2_HASH: 039906854ce6309b3180945f2a23deb9edff369753f7082e19053f5ac11bfbae + +MIN_TX_WEIGHT_K: 0 +MIN_TX_WEIGHT_COEFFICIENT: 0 +MIN_TX_WEIGHT: 1 +REWARD_SPEND_MIN_BLOCKS: 1 + +CHECKPOINTS: [] + +extends: mainnet.yml \ No newline at end of file From e0757a95a843bacafa0188600c49adb5bae8c252 Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 8 Nov 2023 17:54:12 -0300 Subject: [PATCH 4/7] tests: wait until the first block is mined before start the integration tests, to release the genesis reward (#589) --- .../integration/helpers/wallet.helper.js | 38 +++++++++++++++++++ setupTests-integration.js | 18 +++++++++ 2 files changed, 56 insertions(+) diff --git a/__tests__/integration/helpers/wallet.helper.js b/__tests__/integration/helpers/wallet.helper.js index ef89a8abb..82541e0b4 100644 --- a/__tests__/integration/helpers/wallet.helper.js +++ b/__tests__/integration/helpers/wallet.helper.js @@ -381,3 +381,41 @@ export async function waitUntilNextTimestamp(hWallet, txId) { loggers.test.log(`Waiting for ${timeToWait}ms for the next timestamp.`); await delay(timeToWait); } + +/** + * This method gets the current height of the network + * and awaits until the height changes + * + * We are ignoring the possibility of reorgs here, we + * assume that if the height changes, a new block has arrived + * + * @param {Storage} storage + * @returns {Promise} + */ +export async function waitNextBlock(storage) { + const currentHeight = await storage.getCurrentHeight(); + let height = currentHeight; + + const timeout = 120000; // 120s of timeout to find the next block + let timeoutReached = false; + // Timeout handler + const timeoutHandler = setTimeout(() => { + timeoutReached = true; + }, timeout); + + while (height === currentHeight) { + if (timeoutReached) { + break; + } + + await delay(1000); + height = await storage.getCurrentHeight(); + } + + // Clear timeout handler + clearTimeout(timeoutHandler); + + if (timeoutReached) { + throw new Error('Timeout reached when waiting for the next block.'); + } +} diff --git a/setupTests-integration.js b/setupTests-integration.js index cdcc8ed35..d1803d3e8 100644 --- a/setupTests-integration.js +++ b/setupTests-integration.js @@ -13,6 +13,8 @@ import { TX_MINING_URL } from './__tests__/integration/configuration/test-consta import { precalculationHelpers, WalletPrecalculationHelper } from './__tests__/integration/helpers/wallet-precalculation.helper'; +import { GenesisWalletHelper } from './__tests__/integration/helpers/genesis-wallet.helper'; +import { waitNextBlock } from './__tests__/integration/helpers/wallet.helper'; config.setTxMiningUrl(TX_MINING_URL); @@ -40,6 +42,22 @@ beforeAll(async () => { // Loading pre-calculated wallets precalculationHelpers.test = new WalletPrecalculationHelper('./tmp/wallets.json'); await precalculationHelpers.test.initWithWalletsFile(); + + // Await first block to be mined to release genesis reward lock + const { hWallet: gWallet } = await GenesisWalletHelper.getSingleton(); + try { + await waitNextBlock(gWallet.storage); + } catch (err) { + // When running jest with jasmine there's a bug (or behavior) + // that any error thrown inside beforeAll methods don't stop the tests + // https://github.com/jestjs/jest/issues/2713 + // The solution for that is to capture the error and call process.exit + // https://github.com/jestjs/jest/issues/2713#issuecomment-319822476 + // The downside of that is that we don't get logs, however is the only + // way for now. We should stop using jasmine soon (and change for jest-circus) + // when we do some package upgrades + process.exit(1); + } }); afterAll(async () => { From c2fb30156bc8b082aad98fc4e1b60b85bba0f75e Mon Sep 17 00:00:00 2001 From: Pedro Ferreira Date: Wed, 8 Nov 2023 19:29:44 -0300 Subject: [PATCH 5/7] tests: start marking utxos as spent when tx is received for integration tests (#590) --- __tests__/integration/atomic_swap.test.js | 6 +- .../integration/hathorwallet_facade.test.js | 136 ++++++++--------- .../integration/hathorwallet_others.test.js | 36 +++-- .../helpers/genesis-wallet.helper.js | 9 +- .../integration/helpers/wallet.helper.js | 142 ++++++++++-------- __tests__/integration/storage/leveldb.test.js | 3 +- __tests__/integration/storage/storage.test.js | 5 +- jest-integration.config.js | 2 +- src/new/wallet.js | 8 +- src/types.ts | 6 + 10 files changed, 196 insertions(+), 157 deletions(-) diff --git a/__tests__/integration/atomic_swap.test.js b/__tests__/integration/atomic_swap.test.js index aac23951b..f1307e2bc 100644 --- a/__tests__/integration/atomic_swap.test.js +++ b/__tests__/integration/atomic_swap.test.js @@ -24,7 +24,7 @@ describe('partial tx proposal', () => { const hWallet2 = await generateWalletHelper(); // Injecting funds and creating a new custom token - const txI = await GenesisWalletHelper.injectFunds(await hWallet1.getAddressAtIndex(0), 103); + const txI = await GenesisWalletHelper.injectFunds(hWallet1, await hWallet1.getAddressAtIndex(0), 103); const { hash: token1Uid } = await createTokenHelper( hWallet1, 'Token1', @@ -33,7 +33,7 @@ describe('partial tx proposal', () => { ); // Injecting funds and creating a new custom token - await GenesisWalletHelper.injectFunds(await hWallet2.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet2, await hWallet2.getAddressAtIndex(0), 10); const { hash: token2Uid } = await createTokenHelper( hWallet2, 'Token2', @@ -99,7 +99,7 @@ describe('partial tx proposal', () => { expect(tx.hash).toBeDefined(); await waitForTxReceived(hWallet1, tx.hash); - await delay(1000); // This transaction seems to take longer than usual to complete + await waitForTxReceived(hWallet2, tx.hash); // Get the balance states before the exchange const w1HTRAfter = await hWallet1.getBalance(HATHOR_TOKEN_CONFIG.uid); diff --git a/__tests__/integration/hathorwallet_facade.test.js b/__tests__/integration/hathorwallet_facade.test.js index e1bf81bfb..1bd46535a 100644 --- a/__tests__/integration/hathorwallet_facade.test.js +++ b/__tests__/integration/hathorwallet_facade.test.js @@ -47,8 +47,7 @@ describe('getTxById', () => { // Injecting some funds on this wallet const fundDestinationAddress = await hWallet.getAddressAtIndex(0); - const tx1 = await GenesisWalletHelper.injectFunds(fundDestinationAddress, 10); - await delay(1000); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, fundDestinationAddress, 10); // Validating the full history increased in one expect(Object.keys(await hWallet.getFullHistory())).toHaveLength(1); @@ -133,7 +132,7 @@ describe('getTxById', () => { // Test case: custom token with funds const address = await hWallet.getAddressAtIndex(0); // Inject 10 HTR into the wallet - await GenesisWalletHelper.injectFunds(address, 10); + await GenesisWalletHelper.injectFunds(hWallet, address, 10); // Generate a random amount of new tokens const newTokenAmount = getRandomInt(1000, 10); // Create a new custom token with the generated amount @@ -306,9 +305,17 @@ describe('start', () => { it('should start a wallet with a transaction history', async () => { // Send a transaction to one of the wallet's addresses const walletData = precalculationHelpers.test.getPrecalculatedWallet(); + + // We are not using the injectFunds helper method here because + // we want to send this transaction before the wallet is started + // then we don't have the wallet object, which is an expected parameter + // for the injectFunds method now + // Since we start and load the wallet after the transaction is sent to the full node + // we don't need to worry for it to be received in the websocket const injectAddress = walletData.addresses[0]; const injectValue = getRandomInt(10, 1); - const injectionTx = await GenesisWalletHelper.injectFunds(injectAddress, injectValue); + const { hWallet: gWallet } = await GenesisWalletHelper.getSingleton(); + const injectionTx = await gWallet.sendTransaction(injectAddress, injectValue); // Start the wallet const walletConfig = { @@ -401,7 +408,7 @@ describe('start', () => { seed: walletData.words, preCalculatedAddresses: walletData.addresses, }); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 2); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 2); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Dedicated Wallet Token', @@ -510,8 +517,7 @@ describe('start', () => { await expect(hWallet.getUtxos()).resolves.toHaveProperty('total_utxos_available', 0); // Generating a transaction and validating it shows correctly - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(1), 1); - await delay(1000); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(1), 1); await expect(hWallet.getBalance(HATHOR_TOKEN_CONFIG.uid)).resolves.toMatchObject([ expect.objectContaining({ @@ -533,7 +539,7 @@ describe('start', () => { }); // Adding funds to it - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); /* * XXX: The code branches that require a PIN would not be achievable without this hack that @@ -657,8 +663,7 @@ describe('addresses methods', () => { // Expect the "current address" to change when a transaction arrives at the current one currentAddress = await hWallet.getCurrentAddress(); - await GenesisWalletHelper.injectFunds(currentAddress.address, 1); - await new Promise(resolve => setTimeout(resolve, 100)); + await GenesisWalletHelper.injectFunds(hWallet, currentAddress.address, 1); const currentAfterTx = await hWallet.getCurrentAddress(); expect(currentAfterTx).toMatchObject({ index: currentAddress.index + 1, @@ -735,7 +740,7 @@ describe('getBalance', () => { // Generating one transaction to validate its effects const injectedValue = getRandomInt(10, 2); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), injectedValue); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), injectedValue); // Validating the transaction effects const balance1 = await hWallet.getBalance(HATHOR_TOKEN_CONFIG.uid); @@ -765,7 +770,7 @@ describe('getBalance', () => { }); // Creating a new custom token - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const newTokenAmount = getRandomInt(1000, 10); const { hash: tokenUid } = await createTokenHelper( hWallet, @@ -807,7 +812,7 @@ describe('getFullHistory', () => { // Injecting some funds on this wallet const fundDestinationAddress = await hWallet.getAddressAtIndex(0); - const { hash: fundTxId } = await GenesisWalletHelper.injectFunds(fundDestinationAddress, 10); + const { hash: fundTxId } = await GenesisWalletHelper.injectFunds(hWallet, fundDestinationAddress, 10); // Validating the full history increased in one await expect(hWallet.storage.store.historyCount()).resolves.toEqual(1); @@ -887,6 +892,7 @@ describe('getFullHistory', () => { it('should return full history (custom token)', async () => { const hWallet = await generateWalletHelper(); await GenesisWalletHelper.injectFunds( + hWallet, await hWallet.getAddressAtIndex(0), 10 ); @@ -958,6 +964,7 @@ describe('getTxBalance', () => { it('should get tx balance', async () => { const hWallet = await generateWalletHelper(); const { hash: tx1Hash } = await GenesisWalletHelper.injectFunds( + hWallet, await hWallet.getAddressAtIndex(0), 10 ); @@ -1048,7 +1055,7 @@ describe('getFullTxById', () => { it('should download an existing transaction from the fullnode', async () => { const hWallet = await generateWalletHelper(); - const tx1 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const fullTx = await hWallet.getFullTxById(tx1.hash); expect(fullTx.success).toStrictEqual(true); @@ -1084,7 +1091,7 @@ describe('getTxConfirmationData', () => { it('should download confirmation data for an existing transaction from the fullnode', async () => { const hWallet = await generateWalletHelper(); - const tx1 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const confirmationData = await hWallet.getTxConfirmationData(tx1.hash); @@ -1120,7 +1127,7 @@ describe('graphvizNeighborsQuery', () => { it('should download graphviz neighbors data for a existing transaction from the fullnode', async () => { const hWallet = await generateWalletHelper(); - const tx1 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const neighborsData = await hWallet.graphvizNeighborsQuery(tx1.hash, 'funds', 1); expect(neighborsData).toMatch(/digraph {/); @@ -1128,7 +1135,7 @@ describe('graphvizNeighborsQuery', () => { it('should capture errors when graphviz returns error', async () => { const hWallet = await generateWalletHelper(); - const tx1 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); await expect(hWallet.graphvizNeighborsQuery(tx1.hash)).rejects.toThrowError('Request failed with status code 500'); }); @@ -1150,7 +1157,7 @@ describe('sendTransaction', () => { it('should send HTR transactions', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); // Sending a transaction inside the same wallet const tx1 = await hWallet.sendTransaction(await hWallet.getAddressAtIndex(2), 6); @@ -1204,7 +1211,7 @@ describe('sendTransaction', () => { it('should send custom token transactions', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Token to Send', @@ -1241,6 +1248,7 @@ describe('sendTransaction', () => { } ); await waitForTxReceived(hWallet, tx2Hash); + await waitForTxReceived(gWallet, tx2Hash); // Balance was reduced htrBalance = await hWallet.getBalance(tokenUid); @@ -1256,13 +1264,7 @@ describe('sendTransaction', () => { const mhWallet1 = await generateMultisigWalletHelper({ walletIndex: 0 }); const mhWallet2 = await generateMultisigWalletHelper({ walletIndex: 1 }); const mhWallet3 = await generateMultisigWalletHelper({ walletIndex: 2 }); - await GenesisWalletHelper.injectFunds(await mhWallet1.getAddressAtIndex(0), 10); - - /* - * Under heavy test processing this transaction takes longer than usual to be assimilated by the - * HathorWallet. Here we increase this tolerance to increase the success rate of this test. - */ - await delay(1000); + await GenesisWalletHelper.injectFunds(mhWallet1, await mhWallet1.getAddressAtIndex(0), 10); /* * Building tx proposal: @@ -1330,7 +1332,7 @@ describe('sendManyOutputsTransaction', () => { it('should send simple HTR transactions', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 100); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 100); // Single input and single output const rawSimpleTx = await hWallet.sendManyOutputsTransaction( @@ -1407,7 +1409,7 @@ describe('sendManyOutputsTransaction', () => { it('should send transactions with multiple tokens', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Multiple Tokens Tk', @@ -1468,7 +1470,7 @@ describe('sendManyOutputsTransaction', () => { it('should respect timelocks', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); // Defining timelocks (milliseconds) and timestamps (seconds) const startTime = Date.now().valueOf(); @@ -1552,7 +1554,7 @@ describe('createNewToken', () => { // Creating the wallet with the funds const hWallet = await generateWalletHelper(); const addr0 = await hWallet.getAddressAtIndex(0); - await GenesisWalletHelper.injectFunds(addr0, 10); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 10); // Creating the new token const newTokenResponse = await hWallet.createNewToken( @@ -1580,7 +1582,7 @@ describe('createNewToken', () => { // Creating the wallet with the funds const hWallet = await generateWalletHelper(); const addr0 = await hWallet.getAddressAtIndex(0); - await GenesisWalletHelper.injectFunds(addr0, 10); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 10); // Creating the new token const destinationAddress = await hWallet.getAddressAtIndex(4); @@ -1595,7 +1597,6 @@ describe('createNewToken', () => { } ); await waitForTxReceived(hWallet, tokenUid); - await delay(500); // Validating the tokens are on the correct addresses const { utxos: utxosTokens } = await hWallet.getUtxos({ token: tokenUid }); expect(utxosTokens).toContainEqual( @@ -1612,7 +1613,7 @@ describe('createNewToken', () => { // Creating the wallet with the funds const hWallet = await generateWalletHelper(); const addr0 = await hWallet.getAddressAtIndex(0); - await GenesisWalletHelper.injectFunds(addr0, 1); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 1); // Creating the new token const newTokenResponse = await hWallet.createNewToken( @@ -1628,6 +1629,7 @@ describe('createNewToken', () => { // Checking for authority outputs on the transaction const authorityOutputs = newTokenResponse.outputs.filter(o => transaction.isAuthorityOutput(o)); expect(authorityOutputs).toHaveLength(0); + await waitForTxReceived(hWallet, newTokenResponse.hash); }); it('Create token using mint/melt address', async () => { @@ -1636,7 +1638,7 @@ describe('createNewToken', () => { const addr0 = await hWallet.getAddressAtIndex(0); const addr10 = await hWallet.getAddressAtIndex(10); const addr11 = await hWallet.getAddressAtIndex(11); - await GenesisWalletHelper.injectFunds(addr0, 1); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 1); // Creating the new token const newTokenResponse = await hWallet.createNewToken( @@ -1687,7 +1689,7 @@ describe('createNewToken', () => { const addr0 = await hWallet.getAddressAtIndex(0); const addr2_0 = await hWallet2.getAddressAtIndex(0); const addr2_1 = await hWallet2.getAddressAtIndex(1); - await GenesisWalletHelper.injectFunds(addr0, 1); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 1); // Error creating token with external address await expect(hWallet.createNewToken( @@ -1728,6 +1730,7 @@ describe('createNewToken', () => { // Validating the creation tx expect(newTokenResponse).toHaveProperty('hash'); await waitForTxReceived(hWallet, newTokenResponse.hash); + await waitForTxReceived(hWallet2, newTokenResponse.hash); // Validating a new mint authority was created by default const authorityOutputs = newTokenResponse.outputs.filter( @@ -1764,7 +1767,7 @@ describe('mintTokens', () => { it('should mint new tokens', async () => { // Setting up the custom token const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Token to Mint', @@ -1839,6 +1842,7 @@ describe('mintTokens', () => { }); expect(mintResponse4.hash).toBeDefined(); await waitForTxReceived(hWallet, mintResponse4.hash); + await waitForTxReceived(hWallet2, mintResponse4.hash); // Validating there is a correct reference to the custom token expect(mintResponse4).toHaveProperty('tokens.length', 1); @@ -1874,7 +1878,7 @@ describe('mintTokens', () => { // Setting up scenario const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Token to Mint', @@ -1928,7 +1932,7 @@ describe('meltTokens', () => { it('should melt tokens', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); // Creating the token const { hash: tokenUid } = await createTokenHelper( @@ -1986,6 +1990,7 @@ describe('meltTokens', () => { allowExternalMeltAuthorityAddress: true }); await waitForTxReceived(hWallet, meltResponse3.hash); + await waitForTxReceived(hWallet2, meltResponse3.hash); // Validating a new melt authority was created by default const authorityOutputs3 = meltResponse3.outputs.filter( @@ -2017,8 +2022,7 @@ describe('meltTokens', () => { // Setting up scenario const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 20); - await delay(500); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 20); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Token to Melt', @@ -2038,7 +2042,6 @@ describe('meltTokens', () => { meltResponse = await hWallet.meltTokens(tokenUid, 100); expectedHtrFunds += 1; await waitForTxReceived(hWallet, meltResponse.hash); - await delay(100); expect(await getHtrBalance(hWallet)).toBe(expectedHtrFunds); // Melting between 1.00 and 2.00 tokens recovers 0.01 HTR @@ -2085,7 +2088,7 @@ describe('delegateAuthority', () => { it('should delegate authority between wallets', async () => { // Creating a Custom Token on wallet 1 - await GenesisWalletHelper.injectFunds(await hWallet1.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet1, await hWallet1.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet1, 'Delegate Token', @@ -2104,14 +2107,7 @@ describe('delegateAuthority', () => { await hWallet2.getAddressAtIndex(0) ); await waitForTxReceived(hWallet1, delegateMintTxId); - - /* - * XXX: Authority Token delegation usually takes longer than usual to be reflected on the local - * caches. This forced recalculation will be executed before each authority validation below - * to avoid a small possibility of the caches being obsolete at assertion time. - */ - await delay(100); - await hWallet1.storage.processHistory(); + await waitForTxReceived(hWallet2, delegateMintTxId); // Expect wallet 1 to still have one mint authority let authorities1 = await hWallet1.getMintAuthority(tokenUid); @@ -2121,7 +2117,6 @@ describe('delegateAuthority', () => { authorities: TOKEN_MINT_MASK }); // Expect wallet 2 to also have one mint authority - await hWallet2.storage.processHistory(); let authorities2 = await hWallet2.getMintAuthority(tokenUid); expect(authorities2).toHaveLength(1); expect(authorities2[0]).toMatchObject({ @@ -2137,9 +2132,9 @@ describe('delegateAuthority', () => { await hWallet2.getAddressAtIndex(0) ); await waitForTxReceived(hWallet1, delegateMeltTxId); + await waitForTxReceived(hWallet2, delegateMeltTxId); // Expect wallet 1 to still have one melt authority - await hWallet1.storage.processHistory(); authorities1 = await hWallet1.getMeltAuthority(tokenUid); expect(authorities1).toHaveLength(1); expect(authorities1[0]).toMatchObject({ @@ -2147,7 +2142,6 @@ describe('delegateAuthority', () => { authorities: TOKEN_MELT_MASK }); // Expect wallet 2 to also have one melt authority - await hWallet2.storage.processHistory(); authorities2 = await hWallet2.getMeltAuthority(tokenUid); expect(authorities2).toHaveLength(1); expect(authorities2[0]).toMatchObject({ @@ -2158,7 +2152,7 @@ describe('delegateAuthority', () => { it('should delegate authority to another wallet without keeping one', async () => { // Creating a Custom Token on wallet 1 - await GenesisWalletHelper.injectFunds(await hWallet1.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet1, await hWallet1.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet1, 'Delegate Token', @@ -2174,6 +2168,7 @@ describe('delegateAuthority', () => { { createAnother: false } ); await waitForTxReceived(hWallet1, giveAwayMintTx); + await waitForTxReceived(hWallet2, giveAwayMintTx); // Validating error on mint tokens from Wallet 1 await waitUntilNextTimestamp(hWallet1, giveAwayMintTx); @@ -2181,9 +2176,10 @@ describe('delegateAuthority', () => { // TODO: The type of errors on mint and melt are different. They should have a standard. // Validating success on mint tokens from Wallet 2 - await GenesisWalletHelper.injectFunds(await hWallet2.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet2, await hWallet2.getAddressAtIndex(0), 10); const mintTxWallet2 = await hWallet2.mintTokens(tokenUid, 100); expect(mintTxWallet2).toHaveProperty('hash'); + await waitForTxReceived(hWallet2, mintTxWallet2.hash); // Delegate melt authority without keeping one on wallet 1 const { hash: giveAwayMeltTx } = await hWallet1.delegateAuthority( @@ -2193,6 +2189,7 @@ describe('delegateAuthority', () => { { createAnother: false } ); await waitForTxReceived(hWallet1, giveAwayMeltTx); + await waitForTxReceived(hWallet2, giveAwayMeltTx); // Validating error on mint tokens from Wallet 1 await waitUntilNextTimestamp(hWallet1, giveAwayMeltTx); @@ -2204,7 +2201,7 @@ describe('delegateAuthority', () => { it('should delegate mint authority to another wallet while keeping one', async () => { // Creating a Custom Token on wallet 1 - await GenesisWalletHelper.injectFunds(await hWallet1.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet1, await hWallet1.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet1, 'Delegate Token 2', @@ -2246,6 +2243,7 @@ describe('delegateAuthority', () => { { createAnother: false } ); await waitForTxReceived(hWallet1, delegateMintAuth); + await waitForTxReceived(hWallet2, delegateMintAuth); // Confirming only one authority token was sent from wallet1 to wallet2 auth1 = await hWallet1.getMintAuthority(tokenUid, { many: true }); @@ -2272,7 +2270,7 @@ describe('delegateAuthority', () => { it('should delegate melt authority to another wallet while keeping one', async () => { // Creating a Custom Token on wallet 1 - await GenesisWalletHelper.injectFunds(await hWallet1.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet1, await hWallet1.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet1, 'Delegate Token 2', @@ -2314,6 +2312,7 @@ describe('delegateAuthority', () => { { createAnother: false } ); await waitForTxReceived(hWallet1, delegateMintAuth); + await waitForTxReceived(hWallet2, delegateMintAuth); // Confirming only one authority token was sent from wallet1 to wallet2 auth1 = await hWallet1.getMeltAuthority(tokenUid, { many: true }); @@ -2347,7 +2346,7 @@ describe('destroyAuthority', () => { it('should destroy mint authorities', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Token for MintDestroy', @@ -2392,7 +2391,7 @@ describe('destroyAuthority', () => { it('should destroy melt authorities', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet, 'Token for MeltDestroy', @@ -2443,7 +2442,7 @@ describe('createNFT', () => { it('should create an NFT with mint/melt authorities', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); // Creating one NFT with default authorities const nftTx = await hWallet.createNFT( @@ -2514,7 +2513,7 @@ describe('createNFT', () => { it('should create an NFT without authorities', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); // Creating one NFT without authorities, and with a specific destination address const nftTx = await hWallet.createNFT( @@ -2548,7 +2547,7 @@ describe('createNFT', () => { const addr0 = await hWallet.getAddressAtIndex(0); const addr10 = await hWallet.getAddressAtIndex(10); const addr11 = await hWallet.getAddressAtIndex(11); - await GenesisWalletHelper.injectFunds(addr0, 10); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 10); // Creating the new token const newTokenResponse = await hWallet.createNFT( @@ -2600,7 +2599,7 @@ describe('createNFT', () => { const addr0 = await hWallet.getAddressAtIndex(0); const addr2_0 = await hWallet2.getAddressAtIndex(0); const addr2_1 = await hWallet2.getAddressAtIndex(1); - await GenesisWalletHelper.injectFunds(addr0, 10); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 10); // Error creating token with external address await expect(hWallet.createNFT( @@ -2644,6 +2643,7 @@ describe('createNFT', () => { // Validating the creation tx expect(newTokenResponse).toHaveProperty('hash'); await waitForTxReceived(hWallet, newTokenResponse.hash); + await waitForTxReceived(hWallet2, newTokenResponse.hash); // Validating a new mint authority was created by default const authorityOutputs = newTokenResponse.outputs.filter( @@ -2679,7 +2679,7 @@ describe('getToken methods', () => { it('should get the correct responses for a valid token', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); // Validating `getTokenDetails` for custom token not in this wallet await expect(hWallet.getTokenDetails(fakeTokenUid)).rejects.toThrow('Unknown token'); @@ -2759,7 +2759,7 @@ describe('signTx', () => { const hWallet = await generateWalletHelper(); const addr0 = await hWallet.getAddressAtIndex(0); - await GenesisWalletHelper.injectFunds(addr0, 10); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 10); const { hash: tokenUid } = await createTokenHelper( hWallet, @@ -2819,7 +2819,7 @@ describe('getTxHistory', () => { expect(txHistory).toHaveLength(0); // HTR transaction incoming - const tx1 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); txHistory = await hWallet.getTxHistory(); expect(txHistory).toStrictEqual([ expect.objectContaining({ @@ -2837,6 +2837,7 @@ describe('getTxHistory', () => { await waitUntilNextTimestamp(hWallet, tx2.hash); const tx3 = await hWallet.sendTransaction(await gWallet.getAddressAtIndex(0), 3); await waitForTxReceived(hWallet, tx3.hash); + await waitForTxReceived(gWallet, tx3.hash); txHistory = await hWallet.getTxHistory(); expect(txHistory).toHaveLength(3); @@ -2858,7 +2859,7 @@ describe('getTxHistory', () => { it('should show custom token transactions in correct order', async () => { const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); let txHistory = await hWallet.getTxHistory({ token_id: fakeTokenUid, @@ -2895,6 +2896,7 @@ describe('getTxHistory', () => { { token: tokenUid } ); await waitForTxReceived(hWallet, tx2Hash); + await waitForTxReceived(gWallet, tx2Hash); txHistory = await hWallet.getTxHistory({ token_id: tokenUid }); expect(txHistory).toHaveLength(3); diff --git a/__tests__/integration/hathorwallet_others.test.js b/__tests__/integration/hathorwallet_others.test.js index 7330a4a33..2e054e983 100644 --- a/__tests__/integration/hathorwallet_others.test.js +++ b/__tests__/integration/hathorwallet_others.test.js @@ -40,8 +40,7 @@ describe('getAddressInfo', () => { }); // Validating address after 1 transaction - await GenesisWalletHelper.injectFunds(addr0, 10); - await delay(500); + await GenesisWalletHelper.injectFunds(hWallet, addr0, 10); await expect(hWallet.getAddressInfo(addr0)).resolves.toMatchObject({ total_amount_received: 10, total_amount_sent: 0, @@ -152,7 +151,7 @@ describe('getAddressInfo', () => { const addr1Custom = await hWalletCustom.getAddressAtIndex(1); // Creating custom token - await GenesisWalletHelper.injectFunds(addr0Custom, 1); + await GenesisWalletHelper.injectFunds(hWalletCustom, addr0Custom, 1); const { hash: tokenUid } = await createTokenHelper( hWalletCustom, 'getAddressInfo Token', @@ -213,6 +212,7 @@ describe('getTxAddresses', () => { changeAddress: WALLET_CONSTANTS.genesis.addresses[0] }); await waitForTxReceived(hWallet, tx.hash); + await waitForTxReceived(gWallet, tx.hash); // Validating the method results const decodedTx = await hWallet.getTx(tx.hash); @@ -269,7 +269,7 @@ describe('getAvailableUtxos', () => { expect(utxoGenResult).toStrictEqual({ done: true, value: undefined }); // Inject a transaction and validate the results - const tx1 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); // Get correct results for a single transaction utxoGenerator = await hWallet.getAvailableUtxos(); @@ -300,8 +300,8 @@ describe('getAvailableUtxos', () => { * @type HathorWallet */ const hWallet = await generateWalletHelper(); - const tx1 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); - const tx2 = await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(1), 5); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); + const tx2 = await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(1), 5); // Validate that on the address that received tx1, the UTXO is listed let utxoGenerator = await hWallet.getAvailableUtxos({ filter_address: await hWallet.getAddressAtIndex(0) }); @@ -331,7 +331,7 @@ describe('getAvailableUtxos', () => { * @type HathorWallet */ const hWallet = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 10); const { hash: tokenUid } = await createTokenHelper( hWallet, 'getAvailableUtxos Token', @@ -420,7 +420,7 @@ describe('getUtxosForAmount', () => { it('should work on a wallet containing a single tx', async () => { const addr0 = await hWallet.getAddressAtIndex(0); const addr1 = await hWallet.getAddressAtIndex(1); - const tx1 = await GenesisWalletHelper.injectFunds(addr0, 10); + const tx1 = await GenesisWalletHelper.injectFunds(hWallet, addr0, 10); fundTx1hash = tx1.hash; // No change amount @@ -463,7 +463,7 @@ describe('getUtxosForAmount', () => { it('should work on a wallet containing multiple txs', async () => { const addr0 = await hWallet.getAddressAtIndex(0); const addr1 = await hWallet.getAddressAtIndex(1); - const tx2 = await GenesisWalletHelper.injectFunds(addr1, 20); + const tx2 = await GenesisWalletHelper.injectFunds(hWallet, addr1, 20); /* * Since we don't know which order the transactions will be stored on the history, @@ -572,8 +572,7 @@ describe('getUtxosForAmount', () => { it('should not retrieve utxos marked as selected', async () => { // Retrieving the utxo's data and marking it as selected const addr = await hWallet.getAddressAtIndex(11); - await GenesisWalletHelper.injectFunds(addr, 100); - await delay(100); + await GenesisWalletHelper.injectFunds(hWallet, addr, 100); const utxosAddr1 = await hWallet.getUtxos({ filter_address: addr }); const singleUtxoAddr1 = utxosAddr1.utxos[0]; @@ -601,7 +600,7 @@ describe('consolidateUtxos', () => { beforeAll(async () => { hWallet1 = await generateWalletHelper(); hWallet2 = await generateWalletHelper(); - await GenesisWalletHelper.injectFunds(await hWallet2.getAddressAtIndex(0), 110); + await GenesisWalletHelper.injectFunds(hWallet2, await hWallet2.getAddressAtIndex(0), 110); const { hash: tokenUid } = await createTokenHelper( hWallet2, 'Consolidate Token', @@ -635,6 +634,7 @@ describe('consolidateUtxos', () => { { token } ); await waitForTxReceived(hWallet1, cleanTx.hash); + await waitForTxReceived(hWallet2, cleanTx.hash); await waitUntilNextTimestamp(hWallet1, cleanTx.hash); } @@ -649,6 +649,7 @@ describe('consolidateUtxos', () => { { address: await hWallet1.getAddressAtIndex(1), value: 5, token: HATHOR_TOKEN_CONFIG.uid }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); await expect(hWallet1.consolidateUtxos(hWallet2.getAddressAtIndex(0))) .rejects.toThrow('not owned by this wallet'); @@ -663,6 +664,7 @@ describe('consolidateUtxos', () => { { address: await hWallet1.getAddressAtIndex(1), value: 5, token: HATHOR_TOKEN_CONFIG.uid }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); // Sending transaction and validating the method response const consolidateTx = await hWallet1.consolidateUtxos( @@ -718,6 +720,7 @@ describe('consolidateUtxos', () => { { address: await hWallet1.getAddressAtIndex(4), value: 50, token: tokenHash }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); // Sending transaction and validating the method response const consolidateTx = await hWallet1.consolidateUtxos( @@ -779,6 +782,7 @@ describe('consolidateUtxos', () => { { address: addr2, value: 3, token: HATHOR_TOKEN_CONFIG.uid }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); // Sending transaction and validating the method response const consolidateTx = await hWallet1.consolidateUtxos( @@ -823,6 +827,7 @@ describe('consolidateUtxos', () => { { address: addr1, value: 5, token: HATHOR_TOKEN_CONFIG.uid }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); // Sending transaction and validating the method response const consolidateTx = await hWallet1.consolidateUtxos( @@ -861,6 +866,7 @@ describe('consolidateUtxos', () => { { address: addr2, value: 5, token: HATHOR_TOKEN_CONFIG.uid }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); // Sending transaction and validating the method response const consolidateTx = await hWallet1.consolidateUtxos( @@ -899,10 +905,11 @@ describe('consolidateUtxos', () => { { address: addr2, value: 40, token: HATHOR_TOKEN_CONFIG.uid }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); // Sending transaction and validating the method response const consolidateTx = await hWallet1.consolidateUtxos( - await await hWallet1.getAddressAtIndex(4), + await hWallet1.getAddressAtIndex(4), { token: HATHOR_TOKEN_CONFIG.uid, amount_bigger_than: 2, @@ -940,6 +947,7 @@ describe('consolidateUtxos', () => { { address: await hWallet1.getAddressAtIndex(0), value: 1, token: tokenHash }, ]); await waitForTxReceived(hWallet1, fundTx.hash); + await waitForTxReceived(hWallet2, fundTx.hash); // We should now have 4 utxos on wallet1 for this custom token expect(await hWallet1.getUtxos({ token: tokenHash })).toHaveProperty('total_utxos_available', 4); @@ -990,7 +998,7 @@ describe('getAuthorityUtxos', () => { it('should find one authority utxo', async () => { // Creating the token - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(0), 1); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(0), 1); const { hash: tokenUid } = await createTokenHelper( hWallet, 'getAuthorityUtxos Token', diff --git a/__tests__/integration/helpers/genesis-wallet.helper.js b/__tests__/integration/helpers/genesis-wallet.helper.js index 1227b88ec..8d5cc2405 100644 --- a/__tests__/integration/helpers/genesis-wallet.helper.js +++ b/__tests__/integration/helpers/genesis-wallet.helper.js @@ -57,6 +57,7 @@ export class GenesisWalletHelper { /** * Internal method to send HTR to another wallet's address. + * @param {HathorWallet} destinationWallet Wallet object that we are sending the funds to * @param {string} address * @param {number} value * @param [options] @@ -65,7 +66,7 @@ export class GenesisWalletHelper { * @returns {Promise} * @private */ - async _injectFunds(address, value, options = {}) { + async _injectFunds(destinationWallet, address, value, options = {}) { try { const result = await this.hWallet.sendTransaction( address, @@ -80,6 +81,7 @@ export class GenesisWalletHelper { } await waitForTxReceived(this.hWallet, result.hash, options.waitTimeout); + await waitForTxReceived(destinationWallet, result.hash, options.waitTimeout); await waitUntilNextTimestamp(this.hWallet, result.hash); return result; } catch (e) { @@ -107,6 +109,7 @@ export class GenesisWalletHelper { /** * An easy way to send HTR to another wallet's address for testing. + * @param {HathorWallet} destinationWallet Wallet object that we are sending the funds to * @param {string} address * @param {number} value * @param [options] @@ -114,9 +117,9 @@ export class GenesisWalletHelper { * Passing 0 here skips this waiting. * @returns {Promise} */ - static async injectFunds(address, value, options) { + static async injectFunds(destinationWallet, address, value, options) { const instance = await GenesisWalletHelper.getSingleton(); - return instance._injectFunds(address, value, options); + return instance._injectFunds(destinationWallet, address, value, options); } /** diff --git a/__tests__/integration/helpers/wallet.helper.js b/__tests__/integration/helpers/wallet.helper.js index 82541e0b4..15959dd75 100644 --- a/__tests__/integration/helpers/wallet.helper.js +++ b/__tests__/integration/helpers/wallet.helper.js @@ -18,6 +18,7 @@ import { multisigWalletsData, precalculationHelpers } from './wallet-precalculat import { delay } from '../utils/core.util'; import { loggers } from '../utils/logger.util'; import { MemoryStore, Storage } from '../../../src/storage'; +import { TxHistoryProcessingStatus } from '../../../src/types'; /** * @typedef SendTxResponse @@ -244,7 +245,6 @@ export async function createTokenHelper(hWallet, name, symbol, amount, options) const tokenUid = newTokenResponse.hash; await waitForTxReceived(hWallet, tokenUid); await waitUntilNextTimestamp(hWallet, tokenUid); - await delay(1000); return newTokenResponse; } @@ -274,82 +274,94 @@ export function waitForWalletReady(hWallet) { } /** - * Translates the tx success event into a promise. - * Waits for the wallet event that indicates this transaction id has been fully integrated - * into the lib's local caches. Actions that depend on this state can be executed after the - * successful response from this function. + * Waits for the wallet to receive the transaction from a websocket message + * and process the history + * * @param {HathorWallet} hWallet * @param {string} txId * @param {number} [timeout] Timeout in milisseconds. Default value defined on test-constants. - * @returns {Promise} + * @returns {Promise} */ export async function waitForTxReceived(hWallet, txId, timeout) { const startTime = Date.now().valueOf(); - let alreadyResponded = false; - const existingTx = await hWallet.getTx(txId); + let timeoutHandler; + let timeoutReached = false; + const timeoutPeriod = timeout || TX_TIMEOUT_DEFAULT; + // Timeout handler + timeoutHandler = setTimeout(() => { + timeoutReached = true; + }, timeoutPeriod); + + let storageTx = await hWallet.getTx(txId); + + // We consider that the tx was received after it's in the storage + // and the history processing is finished + while (!storageTx || storageTx.processingStatus !== TxHistoryProcessingStatus.FINISHED) { + if (timeoutReached) { + break; + } - // If the transaction was already received, return it immediately - if (existingTx) { - return existingTx; + // Tx not found, wait 1s before trying again + await delay(1000); + storageTx = await hWallet.getTx(txId); } - // Only return the positive response after the transaction was received by the websocket - return new Promise(async (resolve, reject) => { - // Event listener - const handleNewTx = newTx => { - // Ignore this event if we didn't receive the transaction we expected. - if (newTx.tx_id !== txId) { - return; - } + // Clean timeout handler + if (timeoutHandler) { + clearTimeout(timeoutHandler); + } - // This is the correct transaction: resolving the promise. - resolveWithSuccess(newTx); - }; - hWallet.on('new-tx', handleNewTx); + if (timeoutReached) { + // Throw error in case of timeout + throw new Error(`Timeout of ${timeoutPeriod}ms without receiving the tx with id ${txId}`); + } - // Timeout handler - const timeoutPeriod = timeout || TX_TIMEOUT_DEFAULT; - setTimeout(async () => { - hWallet.removeListener('new-tx', handleNewTx); + const timeDiff = Date.now().valueOf() - startTime; + if (DEBUG_LOGGING) { + loggers.test.log(`Wait for ${txId} took ${timeDiff}ms.`); + } - // No need to respond if the event listener worked. - if (alreadyResponded) { - return; - } + if (storageTx.is_voided === false) { + // We can't consider the metadata only of the transaction, it affects + // also the metadata of the transactions that were spent on it + // We could await for the update-tx event of the transactions of the inputs to arrive + // before considering the transaction metadata fully updated, however it's complicated + // to handle these events, since they might arrive even before we call this method + // To simplify everything, here we manually set the utxos as spent and process the history + // so after the transaction arrives, all the metadata involved on it is updated and we can + // continue running the tests to correctly check balances, addresses, and everyting else + await updateInputsSpentBy(hWallet, storageTx); + await hWallet.storage.processHistory(); + } - // Event listener did not receive the tx and it is not on local cache. - alreadyResponded = true; - reject(new Error(`Timeout of ${timeoutPeriod}ms without receiving tx ${txId}`)); - }, timeoutPeriod); + return storageTx; +} - async function resolveWithSuccess(newTx) { - const timeDiff = Date.now().valueOf() - startTime; - if (DEBUG_LOGGING) { - loggers.test.log(`Wait for ${txId} took ${timeDiff}ms.`); - } +/** + * Loop through all inputs of a tx, get the corresponding transaction in the storage and + * update the spent_by attribute + * + * @param {HathorWallet} hWallet + * @param {IHistoryTx} txId + * @returns {Promise} + */ +async function updateInputsSpentBy(hWallet, tx) { + for (const input of tx.inputs) { + const inputTx = await hWallet.getTx(input.tx_id); + if (!inputTx) { + // This input is not spending an output from this wallet + continue; + } - if (alreadyResponded) { - return; - } - alreadyResponded = true; - - /* - * Sometimes even after receiving the `new-tx` event, the transaction is not available on - * memory. The code below tries to eliminate these short time-senstive issues with a minimum - * of delays. - */ - await delay(500); - let txObj = await hWallet.getTx(txId); - while (!txObj) { - if (DEBUG_LOGGING) { - loggers.test.warn(`Tx was not available on history. Waiting for 50ms and retrying.`); - } - await delay(50); - txObj = await hWallet.getTx(txId); - } - resolve(newTx); + if (input.index > inputTx.outputs.length - 1) { + // Invalid output index + throw new Error('Try to get output in an index that doesn\'t exist.'); } - }); + + const output = inputTx.outputs[input.index]; + output.spent_by = tx.tx_id; + await hWallet.storage.addTx(inputTx); + } } /** @@ -396,7 +408,13 @@ export async function waitNextBlock(storage) { const currentHeight = await storage.getCurrentHeight(); let height = currentHeight; - const timeout = 120000; // 120s of timeout to find the next block + // This timeout is a protection, so the integration tests + // don't keep running in case of a problem + // After using the timeout as 120s, we had some timeouts + // because the CI runs in a free github runner + // so we decided to increase this timeout to 600s, so + // we don't have this error anymore + const timeout = 600000; let timeoutReached = false; // Timeout handler const timeoutHandler = setTimeout(() => { diff --git a/__tests__/integration/storage/leveldb.test.js b/__tests__/integration/storage/leveldb.test.js index 217c86af5..460e66391 100644 --- a/__tests__/integration/storage/leveldb.test.js +++ b/__tests__/integration/storage/leveldb.test.js @@ -65,9 +65,8 @@ describe('LevelDB persistent store', () => { expect(Object.keys(hWallet.getFullHistory())).toHaveLength(0); // Injecting some funds on this wallet - await GenesisWalletHelper.injectFunds(await hWallet.getAddressAtIndex(1), 10); + await GenesisWalletHelper.injectFunds(hWallet, await hWallet.getAddressAtIndex(1), 10); - await delay(100); // Validating the full history increased in one expect(Object.keys(await hWallet.getFullHistory())).toHaveLength(1); diff --git a/__tests__/integration/storage/storage.test.js b/__tests__/integration/storage/storage.test.js index a07558c0f..85c904b8a 100644 --- a/__tests__/integration/storage/storage.test.js +++ b/__tests__/integration/storage/storage.test.js @@ -65,9 +65,7 @@ describe('locked utxos', () => { async function testUnlockWhenSpent(storage, walletData) { const hwallet = await startWallet(storage, walletData); const address = await hwallet.getAddressAtIndex(0); - const tx = await GenesisWalletHelper.injectFunds(address, 1); - await waitForTxReceived(hwallet, tx.hash); - await delay(500); + const tx = await GenesisWalletHelper.injectFunds(hwallet, address, 1); const sendTx = new SendTransaction({ storage: hwallet.storage, @@ -88,7 +86,6 @@ describe('locked utxos', () => { // Send a transaction spending the only utxo on the wallet. const tx1 = await sendTx.runFromMining(); await waitForTxReceived(hwallet, tx1.hash); - await delay(500); await expect(hwallet.storage.isUtxoSelectedAsInput(utxoId)).resolves.toBe(false); } diff --git a/jest-integration.config.js b/jest-integration.config.js index ee183e3d4..6a02de81d 100644 --- a/jest-integration.config.js +++ b/jest-integration.config.js @@ -26,7 +26,7 @@ module.exports = { './src/new/wallet.js': { statements: 92, branches: 85, - functions: 91, + functions: 90, lines: 92 } }, diff --git a/src/new/wallet.js b/src/new/wallet.js index 99657af27..5d8ace397 100644 --- a/src/new/wallet.js +++ b/src/new/wallet.js @@ -27,7 +27,7 @@ import { signMessage } from '../utils/crypto'; import Transaction from '../models/transaction'; import Queue from '../models/queue'; import FullnodeConnection from './connection'; -import { WalletType } from '../types'; +import { TxHistoryProcessingStatus, WalletType } from '../types'; import { syncHistory, reloadStorage, checkGapLimit } from '../utils/storage'; import txApi from '../api/txApi'; import { MemoryStore, Storage } from '../storage'; @@ -1141,6 +1141,8 @@ class HathorWallet extends EventEmitter { const storageTx = await this.storage.getTx(newTx.tx_id); const isNewTx = storageTx === null; + newTx.processingStatus = TxHistoryProcessingStatus.PROCESSING; + // Save the transaction in the storage await this.storage.addTx(newTx); @@ -1152,6 +1154,10 @@ class HathorWallet extends EventEmitter { // Process history to update metadatas await this.storage.processHistory(); + newTx.processingStatus = TxHistoryProcessingStatus.FINISHED; + // Save the transaction in the storage + await this.storage.addTx(newTx); + if (isNewTx) { this.emit('new-tx', newTx); } else { diff --git a/src/types.ts b/src/types.ts index 7354dec8a..265f9cb51 100644 --- a/src/types.ts +++ b/src/types.ts @@ -67,6 +67,12 @@ export interface IHistoryTx { token_symbol?: string; // For create token transaction tokens: string[]; height?: number; + processingStatus?: TxHistoryProcessingStatus; +} + +export enum TxHistoryProcessingStatus { + PROCESSING = 'processing', + FINISHED = 'finished', } export interface IHistoryInput { From d0ae3256be2612581bc70bdba41d18f10c318d9e Mon Sep 17 00:00:00 2001 From: Luis Helder Date: Thu, 9 Nov 2023 11:36:19 -0300 Subject: [PATCH 6/7] feat: support health-check endpoints on hathor-core and tx-mining-service (#579) --- __tests__/api/health.test.ts | 30 +++++++ __tests__/api/txMining.test.ts | 147 +++++++++++++++++++++++++++++++++ __tests__/api/version.test.ts | 73 ++++++++++++++++ src/api/health.js | 36 ++++++++ src/api/txApi.js | 10 +-- src/api/txMining.js | 23 +++++- src/api/version.js | 17 ++++ src/lib.ts | 2 + 8 files changed, 330 insertions(+), 8 deletions(-) create mode 100644 __tests__/api/health.test.ts create mode 100644 __tests__/api/txMining.test.ts create mode 100644 __tests__/api/version.test.ts create mode 100644 src/api/health.js diff --git a/__tests__/api/health.test.ts b/__tests__/api/health.test.ts new file mode 100644 index 000000000..a3fc4eb00 --- /dev/null +++ b/__tests__/api/health.test.ts @@ -0,0 +1,30 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; +import healthApi from '../../src/api/health'; + +describe('healthApi', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + it('getHealth should return health data', async () => { + const data = { status: 'pass' }; + mock.onGet('version').reply(200, data); + + const result = await healthApi.getHealth(); + + expect(result).toEqual(data); + }); + + it('getHealth should allow capturing errors in case of network error', async () => { + mock.onGet('version').networkError(); + + await expect(healthApi.getHealth()).rejects.toThrow(); + }); +}); \ No newline at end of file diff --git a/__tests__/api/txMining.test.ts b/__tests__/api/txMining.test.ts new file mode 100644 index 000000000..2b16534bd --- /dev/null +++ b/__tests__/api/txMining.test.ts @@ -0,0 +1,147 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; +import txMiningApi from '../../src/api/txMining'; + +describe('txMiningApi', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('getJobStatus', () => { + it('should send the right request', async () => { + const data = { status: 'done' }; + mock.onGet('job-status').reply(200, data); + + const result = await new Promise((resolve, reject) => { + txMiningApi.getJobStatus('jobId', resolve); + }); + + expect(result).toEqual(data); + + expect(mock.history.get.length).toBe(1); + expect(mock.history.get[0].params).toEqual({ "job-id": 'jobId' }); + }); + + it('should allow capturing errors in case of network error', async () => { + mock.onGet('job-status').networkError(); + + await expect(txMiningApi.getJobStatus('jobId', () => {})).rejects.toThrow(); + }); + + it('should allow capturing errors in case the server responds with 500', async () => { + mock.onGet('job-status').reply(500); + + try { + await txMiningApi.getJobStatus('jobId', () => {}); + } catch (e) { + expect(e.message).toEqual('Request failed with status code 500'); + expect(e.response.status).toEqual(500); + } + }); + }); + + describe('cancelJob', () => { + it('should send the right request', async () => { + const data = { status: 'cancelled' }; + mock.onPost('cancel-job').reply(200, data); + + const result = await new Promise((resolve, reject) => { + txMiningApi.cancelJob('jobId', resolve); + }); + + expect(result).toEqual(data); + + expect(mock.history.post.length).toBe(1); + expect(mock.history.post[0].data).toEqual(JSON.stringify({ "job-id": 'jobId' })); + }); + + it('should allow capturing errors in case of network error', async () => { + mock.onPost('cancel-job').networkError(); + + await expect(txMiningApi.cancelJob('jobId', () => {})).rejects.toThrow(); + }); + + it('should allow capturing errors in case the server responds with 500', async () => { + mock.onPost('cancel-job').reply(500); + + try { + await txMiningApi.cancelJob('jobId', () => {}); + } catch (e) { + expect(e.message).toEqual('Request failed with status code 500'); + expect(e.response.status).toEqual(500); + } + }); + }); + + describe('getHealth', () => { + it('should send the right request', async () => { + const data = { status: 'pass' }; + mock.onGet('health').reply(200, data); + + const result = await txMiningApi.getHealth(); + + expect(result).toEqual(data); + }); + + it('should allow capturing errors in case of network error', async () => { + mock.onGet('health').networkError(); + + await expect(txMiningApi.getHealth()).rejects.toThrow(); + }); + + it('should allow capturing errors in case the server responds with 500', async () => { + mock.onGet('health').reply(500); + + try { + await txMiningApi.getHealth(); + } catch (e) { + expect(e.message).toEqual('Request failed with status code 500'); + expect(e.response.status).toEqual(500); + } + }); + }); + + describe('submitJob', () => { + it('should send the right request', async () => { + const data = { job: 'jobId' }; + mock.onPost('submit-job').reply(200, data); + + const result = await new Promise((resolve, reject) => { + txMiningApi.submitJob('tx', true, true, 10, resolve); + }); + + expect(result).toEqual(data); + + expect(mock.history.post.length).toBe(1); + expect(mock.history.post[0].data).toBe(JSON.stringify({ + 'tx': 'tx', + 'propagate': true, + 'add_parents': true, + 'timeout': 10 + })); + }); + + it('should allow capturing errors in case of network error', async () => { + mock.onPost('submit-job').networkError(); + + await expect(txMiningApi.submitJob('tx', true, true, 10, () => {})).rejects.toThrow(); + }); + + it('should allow capturing errors in case the server responds with 500', async () => { + mock.onPost('submit-job').reply(500); + + try { + await txMiningApi.submitJob('tx', true, true, 10, () => {}); + } catch (e) { + expect(e.message).toEqual('Request failed with status code 500'); + expect(e.response.status).toEqual(500); + } + }); + }); +}); \ No newline at end of file diff --git a/__tests__/api/version.test.ts b/__tests__/api/version.test.ts new file mode 100644 index 000000000..d11b16651 --- /dev/null +++ b/__tests__/api/version.test.ts @@ -0,0 +1,73 @@ +import MockAdapter from 'axios-mock-adapter'; +import axios from 'axios'; +import versionApi from '../../src/api/version'; + +describe('versionApi', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + + describe('getVersion', () => { + it('should return version data', async () => { + const data = { version: '1.0.0' }; + mock.onGet('/version').reply(200, data); + + const result = await new Promise((resolve, reject) => { + versionApi.getVersion(resolve); + }); + + expect(result).toEqual(data); + }); + + it('should allow capturing errors in case of network error', async () => { + mock.onGet('/version').networkError(); + + await expect(versionApi.getVersion(() => {})).rejects.toThrow(); + }); + + it('should allow capturing errors in case the server responds with 500', async () => { + mock.onGet('/version').reply(500); + + try { + await versionApi.getVersion(() => {}); + } catch (e) { + expect(e.message).toEqual('Request failed with status code 500'); + expect(e.response.status).toEqual(500); + } + }); + }); + + describe('asyncGetVersion', () => { + it('should return version data', async () => { + const data = { version: '1.0.0' }; + mock.onGet('/version').reply(200, data); + + const result = await versionApi.asyncGetVersion(); + + expect(result).toEqual(data); + }); + + it('should allow capturing errors in case of network error', async () => { + mock.onGet('/version').networkError(); + + await expect(versionApi.asyncGetVersion()).rejects.toThrow(); + }); + + it('should allow capturing errors in case the server responds with 500', async () => { + mock.onGet('/version').reply(500); + + try { + await versionApi.asyncGetVersion(); + } catch (e) { + expect(e.message).toEqual('Request failed with status code 500'); + expect(e.response.status).toEqual(500); + } + }); + }); +}); \ No newline at end of file diff --git a/src/api/health.js b/src/api/health.js new file mode 100644 index 000000000..53a4110f7 --- /dev/null +++ b/src/api/health.js @@ -0,0 +1,36 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { createRequestInstance } from './axiosInstance'; + +/** + * Api calls for healthcheck + * + * @namespace ApiHealth + */ + +const healthApi = { + /** + * Get health information of full node running in connected server + * + * @return {Promise} + * @memberof ApiHealth + * @inner + */ + async getHealth() { + return new Promise((resolve, reject) => { + // TODO: We should chage this to get `health` instead of `version` + createRequestInstance(resolve).get(`version`).then((res) => { + resolve(res.data); + }, (err) => { + reject(err); + }); + }); + } +}; + +export default healthApi; diff --git a/src/api/txApi.js b/src/api/txApi.js index 21e48270c..92739d4a3 100644 --- a/src/api/txApi.js +++ b/src/api/txApi.js @@ -71,7 +71,7 @@ const txApi = { return this.getTransactionBase(data, resolve); }, - /* + /** * Call api to get confirmation data of a tx * * @param {string} id Transaction hash in hex @@ -90,7 +90,7 @@ const txApi = { }); }, - /* + /** * Call api to decode a transaction * * @param {string} hex_tx Full transaction in hexadecimal @@ -109,7 +109,7 @@ const txApi = { }); }, - /* + /** * Call api to push a transaction * * @param {string} hex_tx Full transaction in hexadecimal @@ -128,7 +128,7 @@ const txApi = { }); }, - /* + /** * Call api to get dashboard data * * @param {number} block Quantity of blocks to return @@ -148,7 +148,7 @@ const txApi = { }); }, - /* + /** * Call api to get graphviz * * @param {string} url URL to get graph data diff --git a/src/api/txMining.js b/src/api/txMining.js index 2d15fa129..8075da0e1 100644 --- a/src/api/txMining.js +++ b/src/api/txMining.js @@ -23,7 +23,7 @@ const txMiningApi = { * @param {Number} timeout Optional parameter to define the timeout of the submit job in seconds * * @return {Promise} - * @memberof txMiningApi + * @memberof ApiTxMining * @inner */ submitJob(tx, propagate, add_parents, timeout, resolve) { @@ -44,7 +44,7 @@ const txMiningApi = { * @param {String} job Job id * * @return {Promise} - * @memberof txMiningApi + * @memberof ApiTxMining * @inner */ getJobStatus(job, resolve) { @@ -62,7 +62,7 @@ const txMiningApi = { * @param {String} job Job id * * @return {Promise} - * @memberof txMiningApi + * @memberof ApiTxMining * @inner */ cancelJob(job, resolve) { @@ -73,6 +73,23 @@ const txMiningApi = { return Promise.reject(error); }); }, + + /** + * Get health information for the tx-mining-service + * + * @return {Promise} + * @memberof ApiTxMining + * @inner + */ + async getHealth() { + return new Promise((resolve, reject) => { + txMiningRequestClient(resolve).get(`health`).then((res) => { + resolve(res.data); + }, (err) => { + reject(err); + }); + }); + } }; export default txMiningApi; \ No newline at end of file diff --git a/src/api/version.js b/src/api/version.js index 2ba424957..89a321cb7 100644 --- a/src/api/version.js +++ b/src/api/version.js @@ -29,6 +29,23 @@ const versionApi = { }, (res) => { return Promise.reject(res); }); + }, + + /** + * Get version of full node running in connected server + * + * @return {Promise} + * @memberof ApiVersion + * @inner + */ + async asyncGetVersion() { + return new Promise((resolve, reject) => { + createRequestInstance(resolve).get(`version`).then((res) => { + resolve(res.data); + }, (err) => { + reject(err); + }); + }); } }; diff --git a/src/lib.ts b/src/lib.ts index d48dfde6c..d1808f589 100644 --- a/src/lib.ts +++ b/src/lib.ts @@ -6,6 +6,7 @@ import * as ErrorMessages from './errorMessages'; import walletApi from './api/wallet'; import txApi from './api/txApi'; import txMiningApi from './api/txMining'; +import healthApi from './api/health'; import versionApi from './api/version'; import * as axios from './api/axiosInstance'; import metadataApi from './api/metadataApi'; @@ -57,6 +58,7 @@ export { walletApi, txApi, txMiningApi, + healthApi, versionApi, metadataApi, errors, From 58b2015578564495becb8760ab095a7a09eace78 Mon Sep 17 00:00:00 2001 From: Alex Ruzenhack Date: Thu, 9 Nov 2023 17:31:11 +0000 Subject: [PATCH 7/7] bump: v1.0.3 (#587) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 399c3f6ce..59f36d7d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@hathor/wallet-lib", - "version": "1.0.2", + "version": "1.0.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 0d6a94629..a5ed392c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hathor/wallet-lib", - "version": "1.0.2", + "version": "1.0.3", "description": "Library used by Hathor Wallet", "main": "lib/index.js", "directories": {